@h-rig/rig-host 0.0.6-alpha.91

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,52 @@
1
+ import { ensureAgentRuntime, runtimeEnv as hydrateRuntimeEnv, type AgentRuntime } from "@rig/runtime/control-plane/runtime/isolation";
2
+ import { spawnDetachedPtyRun } from "./pty-spawn";
3
+ type EnsureRuntime = typeof ensureAgentRuntime;
4
+ type RuntimeEnv = typeof hydrateRuntimeEnv;
5
+ export type ProvisionRunWorkspaceInput = {
6
+ readonly projectRoot: string;
7
+ readonly taskId: string;
8
+ readonly runId: string;
9
+ readonly ensureRuntime?: EnsureRuntime;
10
+ };
11
+ export type ApplyRunEnvInput = {
12
+ readonly projectRoot: string;
13
+ readonly runtime: AgentRuntime;
14
+ readonly runtimeEnv?: RuntimeEnv;
15
+ };
16
+ export type LaunchInteractiveRunInput = {
17
+ readonly projectRoot: string;
18
+ readonly runtime: AgentRuntime;
19
+ readonly runId: string;
20
+ readonly taskId: string;
21
+ readonly title?: string | null;
22
+ readonly model?: string | null;
23
+ readonly prompt?: string | null;
24
+ readonly resolveInitialPrompt?: (projectRoot: string, taskId: string) => Promise<string>;
25
+ readonly forceSteerOnce?: boolean;
26
+ };
27
+ export type RunRunProcessInput = {
28
+ readonly projectRoot?: string;
29
+ readonly taskId: string;
30
+ readonly runId?: string;
31
+ readonly title?: string | null;
32
+ readonly model?: string | null;
33
+ readonly prompt?: string | null;
34
+ readonly rigRunBin?: string;
35
+ readonly writeStdout?: (text: string) => void;
36
+ readonly ensureRuntime?: EnsureRuntime;
37
+ readonly spawnPtyRun?: typeof spawnDetachedPtyRun;
38
+ readonly resolveInitialPrompt?: (projectRoot: string, taskId: string) => Promise<string>;
39
+ readonly forceSteerOnce?: boolean;
40
+ };
41
+ export declare function provisionRunWorkspace(input: ProvisionRunWorkspaceInput): Promise<AgentRuntime>;
42
+ export declare function loadRunWorkspace(projectRoot: string, runId: string): Promise<AgentRuntime>;
43
+ export declare function applyRunEnv(input: ApplyRunEnvInput): Promise<Record<string, string>>;
44
+ export declare function launchInteractiveRun(input: LaunchInteractiveRunInput): Promise<void>;
45
+ export declare function runRunProcess(input: RunRunProcessInput): Promise<{
46
+ runId: string;
47
+ pid: number;
48
+ sessionName: string;
49
+ runtime: AgentRuntime;
50
+ }>;
51
+ export declare function buildRunInitialPrompt(projectRoot: string, taskId: string): Promise<string>;
52
+ export {};
@@ -0,0 +1,184 @@
1
+ // @bun
2
+ // packages/rig-host/src/local-omp-runner.ts
3
+ import { randomUUID } from "crypto";
4
+ import { resolve } from "path";
5
+ import { createAgentSession, SessionManager } from "@oh-my-pi/pi-coding-agent";
6
+ import { parseArgs } from "@oh-my-pi/pi-coding-agent/cli/args";
7
+ import { runRootCommand } from "@oh-my-pi/pi-coding-agent/main";
8
+ import {
9
+ ensureAgentRuntime,
10
+ listAgentRuntimes,
11
+ resolveRuntimeTaskRecord,
12
+ runtimeEnv as hydrateRuntimeEnv
13
+ } from "@rig/runtime/control-plane/runtime/isolation";
14
+ import rigExtension from "@rig/rig-extension";
15
+
16
+ // packages/rig-host/src/pty-spawn.ts
17
+ import { spawn as nodeSpawn } from "child_process";
18
+ import { closeSync as fsCloseSync, mkdirSync, openSync as fsOpenSync } from "fs";
19
+ import { dirname } from "path";
20
+ function buildPtyRunArgv(input) {
21
+ return ["-L", `rig-${input.runId}`, "new-session", "-d", "-s", `rig-run-${input.runId}`, input.rigRunBin, "--inside-pty", ...input.args];
22
+ }
23
+ async function spawnDetachedPtyRun(input, deps = {}) {
24
+ const tmux = await (deps.which ?? Bun.which)("tmux");
25
+ if (!tmux) {
26
+ throw new Error("tmux is required to launch a detached Rig PTY run. Install tmux or provide a Phase-0-approved PTY fallback.");
27
+ }
28
+ mkdirSync(dirname(input.logPath), { recursive: true });
29
+ const openSync = deps.openSync ?? fsOpenSync;
30
+ const closeSync = deps.closeSync ?? fsCloseSync;
31
+ const spawn = deps.spawn ?? nodeSpawn;
32
+ const logFd = openSync(input.logPath, "a");
33
+ const child = spawn(tmux, buildPtyRunArgv(input), {
34
+ cwd: input.cwd,
35
+ env: input.env,
36
+ detached: true,
37
+ stdio: ["ignore", logFd, logFd]
38
+ });
39
+ closeSync(logFd);
40
+ child.unref();
41
+ if (typeof child.pid !== "number")
42
+ throw new Error("tmux did not report a child process pid for the detached Rig run.");
43
+ return { pid: child.pid };
44
+ }
45
+
46
+ // packages/rig-host/src/local-omp-runner.ts
47
+ async function provisionRunWorkspace(input) {
48
+ return (input.ensureRuntime ?? ensureAgentRuntime)({
49
+ projectRoot: input.projectRoot,
50
+ id: input.runId,
51
+ taskId: input.taskId,
52
+ mode: "worktree",
53
+ provider: "pi"
54
+ });
55
+ }
56
+ async function loadRunWorkspace(projectRoot, runId) {
57
+ const runtimes = await listAgentRuntimes(projectRoot);
58
+ const runtime = runtimes.find((candidate) => candidate.id === runId);
59
+ if (!runtime)
60
+ throw new Error(`Rig run workspace is not provisioned for run ${runId}.`);
61
+ return runtime;
62
+ }
63
+ async function applyRunEnv(input) {
64
+ const env = await (input.runtimeEnv ?? hydrateRuntimeEnv)(input.projectRoot, input.runtime);
65
+ for (const [key, value] of Object.entries(env))
66
+ process.env[key] = value;
67
+ return env;
68
+ }
69
+ async function launchInteractiveRun(input) {
70
+ await applyRunEnv({ projectRoot: input.projectRoot, runtime: input.runtime });
71
+ process.env.RIG_RUN_PROCESS = "1";
72
+ process.env.RIG_RUN_ID = input.runId;
73
+ process.env.RIG_TASK_ID = input.taskId;
74
+ process.env.PROJECT_RIG_ROOT = input.projectRoot;
75
+ process.env.RIG_HOST_PROJECT_ROOT = input.projectRoot;
76
+ if (input.title?.trim())
77
+ process.env.RIG_RUN_TITLE = input.title.trim();
78
+ if (input.forceSteerOnce)
79
+ process.env.RIG_RUN_FORCE_STEER_ONCE = "1";
80
+ const sessionManager = SessionManager.create(input.runtime.workspaceDir, input.runtime.sessionDir);
81
+ await sessionManager.ensureOnDisk();
82
+ const initialPrompt = input.prompt?.trim() || await (input.resolveInitialPrompt ?? buildRunInitialPrompt)(input.projectRoot, input.taskId);
83
+ const args = withRigDefaultConfig([
84
+ ...input.model?.trim() ? ["--model", input.model.trim()] : [],
85
+ initialPrompt
86
+ ]);
87
+ const createRigAgentSession = async (options = {}) => createAgentSession({
88
+ ...options,
89
+ cwd: input.runtime.workspaceDir,
90
+ agentDir: resolve(input.runtime.homeDir, ".pi", "agent"),
91
+ sessionManager,
92
+ extensions: [(api) => rigExtension(api), ...options.extensions ?? []]
93
+ });
94
+ await runRootCommand(parseArgs(args), args, { createAgentSession: createRigAgentSession });
95
+ }
96
+ async function runRunProcess(input) {
97
+ const projectRoot = resolve(input.projectRoot ?? process.cwd());
98
+ const runId = input.runId?.trim() || `run-${randomUUID()}`;
99
+ const runtime = await provisionRunWorkspace({
100
+ projectRoot,
101
+ taskId: input.taskId,
102
+ runId,
103
+ ...input.ensureRuntime ? { ensureRuntime: input.ensureRuntime } : {}
104
+ });
105
+ const rigRunBin = input.rigRunBin ?? resolve(import.meta.dir, "../bin/rig-run.ts");
106
+ const initialPrompt = input.prompt?.trim() || await (input.resolveInitialPrompt ?? buildRunInitialPrompt)(projectRoot, input.taskId);
107
+ const env = {
108
+ ...process.env,
109
+ RIG_RUN_PROCESS: "1",
110
+ RIG_RUN_ID: runId,
111
+ RIG_TASK_ID: input.taskId,
112
+ ...input.title?.trim() ? { RIG_RUN_TITLE: input.title.trim() } : {}
113
+ };
114
+ const args = [
115
+ "--task",
116
+ input.taskId,
117
+ "--run-id",
118
+ runId,
119
+ "--project",
120
+ projectRoot,
121
+ ...input.title?.trim() ? ["--title", input.title.trim()] : [],
122
+ ...input.model?.trim() ? ["--model", input.model.trim()] : [],
123
+ ...input.forceSteerOnce || process.env.RIG_RUN_FORCE_STEER_ONCE === "1" ? ["--force-steer-once"] : [],
124
+ "--prompt",
125
+ initialPrompt
126
+ ];
127
+ const spawnRun = input.spawnPtyRun ?? spawnDetachedPtyRun;
128
+ const { pid } = await spawnRun({
129
+ runId,
130
+ rigRunBin,
131
+ args,
132
+ cwd: runtime.workspaceDir,
133
+ env,
134
+ logPath: resolve(runtime.logsDir, "run-process.log")
135
+ });
136
+ const sessionName = `rig-run-${runId}`;
137
+ input.writeStdout?.(`${runId}
138
+ ${sessionName}
139
+ `);
140
+ return { runId, pid, sessionName, runtime };
141
+ }
142
+ async function buildRunInitialPrompt(projectRoot, taskId) {
143
+ const { task } = await resolveRuntimeTaskRecord({ projectRoot, taskId });
144
+ const record = task;
145
+ const title = readPromptString(record.title) ?? taskId;
146
+ const body = readPromptString(record.body) ?? readPromptString(record.description);
147
+ const acceptance = readPromptString(record.acceptanceCriteria) ?? readPromptString(record.acceptance_criteria);
148
+ const scope = readPromptList(record.scope);
149
+ const validation = readPromptList(record.validation).length > 0 ? readPromptList(record.validation) : readPromptList(record.validators);
150
+ const sections = [
151
+ `You are working on task ${taskId}: ${title}.`,
152
+ body ? `Task:
153
+ ${body}` : null,
154
+ acceptance ? `Acceptance criteria:
155
+ ${acceptance}` : null,
156
+ scope.length > 0 ? `Scope:
157
+ - ${scope.join(`
158
+ - `)}` : null,
159
+ validation.length > 0 ? `Validation:
160
+ - ${validation.join(`
161
+ - `)}` : null,
162
+ "Work directly in the assigned runtime workspace and leave the result in a reviewable state."
163
+ ];
164
+ return sections.filter((value) => Boolean(value)).join(`
165
+
166
+ `);
167
+ }
168
+ function readPromptString(value) {
169
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
170
+ }
171
+ function readPromptList(value) {
172
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0).map((entry) => entry.trim()) : [];
173
+ }
174
+ function withRigDefaultConfig(argv) {
175
+ return ["--config", resolve(import.meta.dir, "../../cli/config/rig-default-config.yml"), ...argv];
176
+ }
177
+ export {
178
+ runRunProcess,
179
+ provisionRunWorkspace,
180
+ loadRunWorkspace,
181
+ launchInteractiveRun,
182
+ buildRunInitialPrompt,
183
+ applyRunEnv
184
+ };
@@ -0,0 +1,7 @@
1
+ type OperatorCliRunOptions = {
2
+ readonly argv?: readonly string[];
3
+ readonly cwd?: string;
4
+ readonly envProjectRoot?: string;
5
+ };
6
+ export declare function runRigHostOperatorCli(input?: readonly string[] | OperatorCliRunOptions): Promise<void>;
7
+ export {};
@@ -0,0 +1,79 @@
1
+ // @bun
2
+ // packages/rig-host/src/domain/task.ts
3
+ import { createPluginHost } from "@rig/core";
4
+ import { loadConfig } from "@rig/core/load-config";
5
+
6
+ // packages/rig-host/src/domain/run.ts
7
+ var LEGACY_AUTHORITY_RUN_CREATION_RETIRED_MESSAGE = "Legacy rig-host run-record creation is retired for the product path. Use the default Rig OMP extension/collab session flow instead.";
8
+ var LEGACY_AUTHORITY_RUN_CONTROL_RETIRED_MESSAGE = "Legacy rig-host run-record control, inspection, and active-run state are retired for the product path. Use the default Rig OMP extension/collab session flow instead.";
9
+ function legacyAuthorityRunUnsupportedMessage() {
10
+ return LEGACY_AUTHORITY_RUN_CREATION_RETIRED_MESSAGE;
11
+ }
12
+ function legacyAuthorityRunControlUnsupportedMessage() {
13
+ return LEGACY_AUTHORITY_RUN_CONTROL_RETIRED_MESSAGE;
14
+ }
15
+
16
+ // packages/rig-host/src/operator-entry.ts
17
+ var LEGACY_HOST_OPERATOR_ENTRY_RETIRED_MESSAGE = "Legacy rig-host operator entrypoint is retired for the product path. Use the default Rig OMP extension/collab session flow instead.";
18
+ var OPERATOR_GROUPS = {
19
+ init: true,
20
+ task: true,
21
+ run: true,
22
+ inspect: true,
23
+ stats: true,
24
+ inbox: true,
25
+ doctor: true,
26
+ setup: true
27
+ };
28
+ function isOperatorCliRunOptions(input) {
29
+ return typeof input === "object" && input !== null && !Array.isArray(input);
30
+ }
31
+ function normalizeInput(input) {
32
+ if (isOperatorCliRunOptions(input)) {
33
+ return {
34
+ argv: input.argv ?? process.argv.slice(2),
35
+ cwd: input.cwd ?? process.cwd(),
36
+ envProjectRoot: input.envProjectRoot ?? process.env.RIG_PROJECT_ROOT ?? process.env.PROJECT_RIG_ROOT
37
+ };
38
+ }
39
+ return { argv: input ?? process.argv.slice(2), cwd: process.cwd(), envProjectRoot: process.env.RIG_PROJECT_ROOT ?? process.env.PROJECT_RIG_ROOT };
40
+ }
41
+ function parseOperatorCli(options) {
42
+ let group = null;
43
+ const args = [];
44
+ for (let index = 0;index < options.argv.length; index += 1) {
45
+ const token = options.argv[index];
46
+ if (!token)
47
+ continue;
48
+ if (token === "--cwd" || token === "--project" || token === "--project-root") {
49
+ index += 1;
50
+ continue;
51
+ }
52
+ if (token.startsWith("--"))
53
+ continue;
54
+ if (!group && OPERATOR_GROUPS[token]) {
55
+ group = token;
56
+ continue;
57
+ }
58
+ args.push(token);
59
+ }
60
+ return { group, args };
61
+ }
62
+ function retiredOperatorEntrypointError(parsed) {
63
+ const group = parsed.group ?? "task";
64
+ const command = parsed.args[0] ?? "";
65
+ if (group === "run" && (command === "start" || command === "task") || group === "task" && command === "run") {
66
+ return new Error(legacyAuthorityRunUnsupportedMessage());
67
+ }
68
+ if (group === "run" || group === "inspect" || group === "stats" || group === "inbox") {
69
+ return new Error(legacyAuthorityRunControlUnsupportedMessage());
70
+ }
71
+ return new Error(LEGACY_HOST_OPERATOR_ENTRY_RETIRED_MESSAGE);
72
+ }
73
+ async function runRigHostOperatorCli(input) {
74
+ const parsed = parseOperatorCli(normalizeInput(input));
75
+ throw retiredOperatorEntrypointError(parsed);
76
+ }
77
+ export {
78
+ runRigHostOperatorCli
79
+ };
@@ -0,0 +1,123 @@
1
+ import type { ApprovalSummary, EngineRunLog, RigCreateAdhocRunInput, RigCreateTaskRunInput, RigInterruptRunInput, RigResolveApprovalInput, RigResolveUserInputInput, RigResumeRunInput, RigSnapshot, RigStopRunInput, RigSubmitRunMessageInput, RunId, RunJournalEvent, TaskId, UserInputRequestSummary, WorkspaceId } from "@rig/contracts";
2
+ export declare const RigHostFrameKinds: readonly ["welcome", "entry", "event", "state", "bus", "backfill", "bye", "error"];
3
+ export type RigHostFrameKind = (typeof RigHostFrameKinds)[number];
4
+ export declare const RigGuestFrameKinds: readonly ["hello", "command", "abort", "fetch-transcript"];
5
+ export type RigGuestFrameKind = (typeof RigGuestFrameKinds)[number];
6
+ type UnsupportedLegacyHostRunLaunchOptions = Partial<Pick<RigCreateAdhocRunInput, "runtimeAdapter" | "model" | "runtimeMode" | "interactionMode" | "initialPrompt" | "baselineMode" | "prMode">>;
7
+ export type RigIdeaSnapshot = Omit<RigSnapshot, "snapshotSequence"> & {
8
+ readonly workspaceRoot: string;
9
+ readonly activeRunId: RunId | null;
10
+ readonly sequence: RigSnapshot["snapshotSequence"];
11
+ };
12
+ export type RigCreateAdhocRunCommand = {
13
+ readonly kind: "run.createAdhoc";
14
+ readonly workspaceId: WorkspaceId;
15
+ readonly title: RigCreateAdhocRunInput["title"];
16
+ } & UnsupportedLegacyHostRunLaunchOptions;
17
+ export type RigRunTaskCommand = {
18
+ readonly kind: "task.run";
19
+ readonly workspaceId: WorkspaceId;
20
+ readonly taskId: TaskId;
21
+ } & Partial<Pick<RigCreateTaskRunInput, "runtimeAdapter" | "model" | "runtimeMode" | "interactionMode" | "initialPrompt" | "baselineMode" | "prMode">>;
22
+ export type RigResumeRunCommand = {
23
+ readonly kind: "run.resume";
24
+ } & Omit<RigResumeRunInput, "commandId" | "createdAt">;
25
+ export type RigStopRunCommand = {
26
+ readonly kind: "run.stop";
27
+ } & Omit<RigStopRunInput, "commandId" | "createdAt">;
28
+ export type RigInterruptRunCommand = {
29
+ readonly kind: "run.interrupt";
30
+ } & Omit<RigInterruptRunInput, "commandId" | "createdAt">;
31
+ export type RigSubmitRunMessageCommand = {
32
+ readonly kind: "run.submitMessage";
33
+ } & Omit<RigSubmitRunMessageInput, "commandId" | "messageId" | "createdAt">;
34
+ export type RigResolveApprovalCommand = {
35
+ readonly kind: "approval.resolve";
36
+ } & Omit<RigResolveApprovalInput, "commandId" | "createdAt">;
37
+ export type RigResolveUserInputCommand = {
38
+ readonly kind: "input.resolve";
39
+ } & Omit<RigResolveUserInputInput, "commandId" | "createdAt">;
40
+ export type RigGuestCommand = {
41
+ readonly kind: "workspace.refresh";
42
+ readonly workspaceId?: WorkspaceId;
43
+ } | RigCreateAdhocRunCommand | RigRunTaskCommand | RigResumeRunCommand | RigStopRunCommand | RigInterruptRunCommand | RigSubmitRunMessageCommand | RigResolveApprovalCommand | RigResolveUserInputCommand;
44
+ export type RigGuestFrame = {
45
+ readonly t: "hello";
46
+ readonly sessionName: string;
47
+ readonly readOnly?: boolean;
48
+ readonly lastSeenSequence?: RigIdeaSnapshot["sequence"];
49
+ } | {
50
+ readonly t: "command";
51
+ readonly command: RigGuestCommand;
52
+ } | {
53
+ readonly t: "abort";
54
+ readonly runId?: RunId;
55
+ } | {
56
+ readonly t: "fetch-transcript";
57
+ readonly reqId: number;
58
+ readonly runId: RunId;
59
+ readonly fromByte: number;
60
+ };
61
+ export type RigShareNotice = {
62
+ readonly kind: "guest-joined";
63
+ readonly sessionName: string;
64
+ readonly readOnly: boolean;
65
+ } | {
66
+ readonly kind: "guest-left";
67
+ readonly sessionName: string;
68
+ readonly reason?: string;
69
+ };
70
+ export type RigHostBusFrame = {
71
+ readonly t: "bus";
72
+ readonly channel: "run-log";
73
+ readonly data: EngineRunLog;
74
+ } | {
75
+ readonly t: "bus";
76
+ readonly channel: "approval";
77
+ readonly data: ApprovalSummary;
78
+ } | {
79
+ readonly t: "bus";
80
+ readonly channel: "input";
81
+ readonly data: UserInputRequestSummary;
82
+ } | {
83
+ readonly t: "bus";
84
+ readonly channel: "share";
85
+ readonly data: RigShareNotice;
86
+ };
87
+ export type RigHostFrame = {
88
+ readonly t: "welcome";
89
+ readonly snapshot: RigIdeaSnapshot;
90
+ readonly readOnly: boolean;
91
+ } | {
92
+ readonly t: "entry";
93
+ readonly entry: RunJournalEvent;
94
+ } | {
95
+ readonly t: "event";
96
+ readonly event: RunJournalEvent;
97
+ readonly sequence: RigIdeaSnapshot["sequence"];
98
+ } | {
99
+ readonly t: "state";
100
+ readonly snapshot: RigIdeaSnapshot;
101
+ } | RigHostBusFrame | {
102
+ readonly t: "backfill";
103
+ readonly reqId: number;
104
+ readonly runId: RunId;
105
+ readonly text: string;
106
+ readonly newSize: number;
107
+ } | {
108
+ readonly t: "bye";
109
+ readonly reason: string;
110
+ } | {
111
+ readonly t: "error";
112
+ readonly message: string;
113
+ readonly code?: string;
114
+ };
115
+ export type RigReplicaState = {
116
+ readonly snapshot?: RigIdeaSnapshot;
117
+ readonly readOnly: boolean;
118
+ readonly selectionPath: readonly string[];
119
+ readonly draftCommand: string;
120
+ readonly connection: "connecting" | "connected" | "reconnecting" | "closed";
121
+ readonly error?: string;
122
+ };
123
+ export {};
@@ -0,0 +1,8 @@
1
+ // @bun
2
+ // packages/rig-host/src/protocol.ts
3
+ var RigHostFrameKinds = ["welcome", "entry", "event", "state", "bus", "backfill", "bye", "error"];
4
+ var RigGuestFrameKinds = ["hello", "command", "abort", "fetch-transcript"];
5
+ export {
6
+ RigHostFrameKinds,
7
+ RigGuestFrameKinds
8
+ };
@@ -0,0 +1,25 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ import { closeSync as fsCloseSync, openSync as fsOpenSync } from "node:fs";
3
+ export type SpawnDetachedPtyRunInput = {
4
+ readonly runId: string;
5
+ readonly rigRunBin: string;
6
+ readonly args: readonly string[];
7
+ readonly cwd: string;
8
+ readonly env: NodeJS.ProcessEnv | Record<string, string | undefined>;
9
+ readonly logPath: string;
10
+ };
11
+ export declare function buildPtyRunArgv(input: Pick<SpawnDetachedPtyRunInput, "runId" | "rigRunBin" | "args">): string[];
12
+ export type PtySpawnDeps = {
13
+ readonly which?: (command: string) => string | null | Promise<string | null>;
14
+ readonly spawn?: (command: string, args: readonly string[], options: {
15
+ readonly cwd: string;
16
+ readonly env: NodeJS.ProcessEnv | Record<string, string | undefined>;
17
+ readonly detached: true;
18
+ readonly stdio: ["ignore", number, number];
19
+ }) => Pick<ChildProcess, "pid" | "unref">;
20
+ readonly openSync?: typeof fsOpenSync;
21
+ readonly closeSync?: typeof fsCloseSync;
22
+ };
23
+ export declare function spawnDetachedPtyRun(input: SpawnDetachedPtyRunInput, deps?: PtySpawnDeps): Promise<{
24
+ pid: number;
25
+ }>;
@@ -0,0 +1,34 @@
1
+ // @bun
2
+ // packages/rig-host/src/pty-spawn.ts
3
+ import { spawn as nodeSpawn } from "child_process";
4
+ import { closeSync as fsCloseSync, mkdirSync, openSync as fsOpenSync } from "fs";
5
+ import { dirname } from "path";
6
+ function buildPtyRunArgv(input) {
7
+ return ["-L", `rig-${input.runId}`, "new-session", "-d", "-s", `rig-run-${input.runId}`, input.rigRunBin, "--inside-pty", ...input.args];
8
+ }
9
+ async function spawnDetachedPtyRun(input, deps = {}) {
10
+ const tmux = await (deps.which ?? Bun.which)("tmux");
11
+ if (!tmux) {
12
+ throw new Error("tmux is required to launch a detached Rig PTY run. Install tmux or provide a Phase-0-approved PTY fallback.");
13
+ }
14
+ mkdirSync(dirname(input.logPath), { recursive: true });
15
+ const openSync = deps.openSync ?? fsOpenSync;
16
+ const closeSync = deps.closeSync ?? fsCloseSync;
17
+ const spawn = deps.spawn ?? nodeSpawn;
18
+ const logFd = openSync(input.logPath, "a");
19
+ const child = spawn(tmux, buildPtyRunArgv(input), {
20
+ cwd: input.cwd,
21
+ env: input.env,
22
+ detached: true,
23
+ stdio: ["ignore", logFd, logFd]
24
+ });
25
+ closeSync(logFd);
26
+ child.unref();
27
+ if (typeof child.pid !== "number")
28
+ throw new Error("tmux did not report a child process pid for the detached Rig run.");
29
+ return { pid: child.pid };
30
+ }
31
+ export {
32
+ spawnDetachedPtyRun,
33
+ buildPtyRunArgv
34
+ };
@@ -0,0 +1,13 @@
1
+ import type { CollabSocket } from "@oh-my-pi/pi-coding-agent/collab/relay-client";
2
+ import type { RigHostFrame, RigIdeaSnapshot } from "../protocol";
3
+ export type RigHostFrameListener = (frame: RigHostFrame) => void;
4
+ export type RigHostCollabTransport = {
5
+ readonly socket?: CollabSocket;
6
+ send(frame: RigHostFrame, targetPeer?: number): void | Promise<void>;
7
+ };
8
+ export type RigHostBroadcaster = {
9
+ publish(frame: RigHostFrame, targetPeer?: number): void;
10
+ publishWelcome(snapshot: RigIdeaSnapshot, readOnly: boolean, targetPeer?: number): void;
11
+ rejectReadOnly(action: string, targetPeer?: number): void;
12
+ };
13
+ export declare function createRigHostBroadcaster(listener?: RigHostFrameListener, collab?: RigHostCollabTransport): RigHostBroadcaster;
@@ -0,0 +1,63 @@
1
+ // @bun
2
+ // packages/rig-host/src/replication/broadcast.ts
3
+ function createRigHostBroadcaster(listener, collab) {
4
+ const backlog = [];
5
+ const welcomedPeers = new Set;
6
+ const hasWelcomedRelayPeer = () => {
7
+ for (const peer of welcomedPeers) {
8
+ if (peer !== 0)
9
+ return true;
10
+ }
11
+ return false;
12
+ };
13
+ const emit = (frame, targetPeer = 0) => {
14
+ listener?.(frame);
15
+ collab?.send(frame, targetPeer);
16
+ };
17
+ const flush = (targetPeer) => {
18
+ let write = 0;
19
+ for (let read = 0;read < backlog.length; read += 1) {
20
+ const next = backlog[read];
21
+ if (next.targetPeer === targetPeer) {
22
+ emit(next.frame, next.targetPeer);
23
+ } else {
24
+ backlog[write] = next;
25
+ write += 1;
26
+ }
27
+ }
28
+ backlog.length = write;
29
+ };
30
+ return {
31
+ publish(frame, targetPeer = 0) {
32
+ if (frame.t === "welcome") {
33
+ welcomedPeers.add(targetPeer);
34
+ emit(frame, targetPeer);
35
+ flush(targetPeer);
36
+ return;
37
+ }
38
+ if (!welcomedPeers.has(targetPeer) && frame.t !== "bye" && frame.t !== "error") {
39
+ if (targetPeer === 0) {
40
+ if (hasWelcomedRelayPeer())
41
+ emit(frame, targetPeer);
42
+ return;
43
+ }
44
+ backlog.push({ frame, targetPeer });
45
+ return;
46
+ }
47
+ emit(frame, targetPeer);
48
+ },
49
+ publishWelcome(snapshot, readOnly, targetPeer) {
50
+ this.publish({ t: "welcome", snapshot, readOnly }, targetPeer);
51
+ },
52
+ rejectReadOnly(action, targetPeer) {
53
+ this.publish({
54
+ t: "error",
55
+ code: "READ_ONLY",
56
+ message: `${action} is disabled on a read-only link`
57
+ }, targetPeer);
58
+ }
59
+ };
60
+ }
61
+ export {
62
+ createRigHostBroadcaster
63
+ };
@@ -0,0 +1,12 @@
1
+ import type { RunId } from "@rig/contracts";
2
+ import type { RigHostFrame, RigIdeaSnapshot } from "../protocol";
3
+ export type RigHostReplayState = {
4
+ readonly runs: RigIdeaSnapshot["runs"];
5
+ readonly approvals: RigIdeaSnapshot["approvals"];
6
+ readonly userInputs: NonNullable<RigIdeaSnapshot["userInputs"]>;
7
+ readonly sequence: RigIdeaSnapshot["sequence"];
8
+ readonly updatedAt: RigIdeaSnapshot["updatedAt"];
9
+ readonly activeRunId: RigIdeaSnapshot["activeRunId"];
10
+ };
11
+ export declare function loadRigHostReplayState(_workspaceRoot: string): RigHostReplayState;
12
+ export declare function readRigTranscriptBackfill(_workspaceRoot: string, runId: RunId, _reqId: number, _fromByte: number): Promise<RigHostFrame>;
@@ -0,0 +1,25 @@
1
+ // @bun
2
+ // packages/rig-host/src/replication/replay.ts
3
+ var LEGACY_REPLAY_RETIRED_MESSAGE = "Legacy host run replay from authority files is retired. Use the OMP collab run detail/session projection flow for live run state.";
4
+ function loadRigHostReplayState(_workspaceRoot) {
5
+ const updatedAt = new Date().toISOString();
6
+ return {
7
+ runs: [],
8
+ approvals: [],
9
+ userInputs: [],
10
+ sequence: 0,
11
+ updatedAt,
12
+ activeRunId: null
13
+ };
14
+ }
15
+ async function readRigTranscriptBackfill(_workspaceRoot, runId, _reqId, _fromByte) {
16
+ return {
17
+ t: "error",
18
+ code: "LEGACY_REPLAY_RETIRED",
19
+ message: `${LEGACY_REPLAY_RETIRED_MESSAGE} No transcript backfill is available for run ${runId}.`
20
+ };
21
+ }
22
+ export {
23
+ readRigTranscriptBackfill,
24
+ loadRigHostReplayState
25
+ };
@@ -0,0 +1,3 @@
1
+ import type { RigIdeaSnapshot } from "../protocol";
2
+ import type { RigHostReplayState } from "./replay";
3
+ export declare function buildRigWelcomeSnapshot(workspaceRoot: string, replay: RigHostReplayState): RigIdeaSnapshot;