@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.
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # @h-rig/rig-host
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
@@ -0,0 +1,283 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // packages/rig-host/bin/rig-run.ts
5
+ import { randomUUID as randomUUID2 } from "crypto";
6
+ import { resolve as resolve2 } from "path";
7
+
8
+ // packages/rig-host/src/local-omp-runner.ts
9
+ import { randomUUID } from "crypto";
10
+ import { resolve } from "path";
11
+ import { createAgentSession, SessionManager } from "@oh-my-pi/pi-coding-agent";
12
+ import { parseArgs } from "@oh-my-pi/pi-coding-agent/cli/args";
13
+ import { runRootCommand } from "@oh-my-pi/pi-coding-agent/main";
14
+ import {
15
+ ensureAgentRuntime,
16
+ listAgentRuntimes,
17
+ resolveRuntimeTaskRecord,
18
+ runtimeEnv as hydrateRuntimeEnv
19
+ } from "@rig/runtime/control-plane/runtime/isolation";
20
+ import rigExtension from "@rig/rig-extension";
21
+
22
+ // packages/rig-host/src/pty-spawn.ts
23
+ import { spawn as nodeSpawn } from "child_process";
24
+ import { closeSync as fsCloseSync, mkdirSync, openSync as fsOpenSync } from "fs";
25
+ import { dirname } from "path";
26
+ function buildPtyRunArgv(input) {
27
+ return ["-L", `rig-${input.runId}`, "new-session", "-d", "-s", `rig-run-${input.runId}`, input.rigRunBin, "--inside-pty", ...input.args];
28
+ }
29
+ async function spawnDetachedPtyRun(input, deps = {}) {
30
+ const tmux = await (deps.which ?? Bun.which)("tmux");
31
+ if (!tmux) {
32
+ throw new Error("tmux is required to launch a detached Rig PTY run. Install tmux or provide a Phase-0-approved PTY fallback.");
33
+ }
34
+ mkdirSync(dirname(input.logPath), { recursive: true });
35
+ const openSync = deps.openSync ?? fsOpenSync;
36
+ const closeSync = deps.closeSync ?? fsCloseSync;
37
+ const spawn = deps.spawn ?? nodeSpawn;
38
+ const logFd = openSync(input.logPath, "a");
39
+ const child = spawn(tmux, buildPtyRunArgv(input), {
40
+ cwd: input.cwd,
41
+ env: input.env,
42
+ detached: true,
43
+ stdio: ["ignore", logFd, logFd]
44
+ });
45
+ closeSync(logFd);
46
+ child.unref();
47
+ if (typeof child.pid !== "number")
48
+ throw new Error("tmux did not report a child process pid for the detached Rig run.");
49
+ return { pid: child.pid };
50
+ }
51
+
52
+ // packages/rig-host/src/local-omp-runner.ts
53
+ async function provisionRunWorkspace(input) {
54
+ return (input.ensureRuntime ?? ensureAgentRuntime)({
55
+ projectRoot: input.projectRoot,
56
+ id: input.runId,
57
+ taskId: input.taskId,
58
+ mode: "worktree",
59
+ provider: "pi"
60
+ });
61
+ }
62
+ async function loadRunWorkspace(projectRoot, runId) {
63
+ const runtimes = await listAgentRuntimes(projectRoot);
64
+ const runtime = runtimes.find((candidate) => candidate.id === runId);
65
+ if (!runtime)
66
+ throw new Error(`Rig run workspace is not provisioned for run ${runId}.`);
67
+ return runtime;
68
+ }
69
+ async function applyRunEnv(input) {
70
+ const env = await (input.runtimeEnv ?? hydrateRuntimeEnv)(input.projectRoot, input.runtime);
71
+ for (const [key, value] of Object.entries(env))
72
+ process.env[key] = value;
73
+ return env;
74
+ }
75
+ async function launchInteractiveRun(input) {
76
+ await applyRunEnv({ projectRoot: input.projectRoot, runtime: input.runtime });
77
+ process.env.RIG_RUN_PROCESS = "1";
78
+ process.env.RIG_RUN_ID = input.runId;
79
+ process.env.RIG_TASK_ID = input.taskId;
80
+ process.env.PROJECT_RIG_ROOT = input.projectRoot;
81
+ process.env.RIG_HOST_PROJECT_ROOT = input.projectRoot;
82
+ if (input.title?.trim())
83
+ process.env.RIG_RUN_TITLE = input.title.trim();
84
+ if (input.forceSteerOnce)
85
+ process.env.RIG_RUN_FORCE_STEER_ONCE = "1";
86
+ const sessionManager = SessionManager.create(input.runtime.workspaceDir, input.runtime.sessionDir);
87
+ await sessionManager.ensureOnDisk();
88
+ const initialPrompt = input.prompt?.trim() || await (input.resolveInitialPrompt ?? buildRunInitialPrompt)(input.projectRoot, input.taskId);
89
+ const args = withRigDefaultConfig([
90
+ ...input.model?.trim() ? ["--model", input.model.trim()] : [],
91
+ initialPrompt
92
+ ]);
93
+ const createRigAgentSession = async (options = {}) => createAgentSession({
94
+ ...options,
95
+ cwd: input.runtime.workspaceDir,
96
+ agentDir: resolve(input.runtime.homeDir, ".pi", "agent"),
97
+ sessionManager,
98
+ extensions: [(api) => rigExtension(api), ...options.extensions ?? []]
99
+ });
100
+ await runRootCommand(parseArgs(args), args, { createAgentSession: createRigAgentSession });
101
+ }
102
+ async function runRunProcess(input) {
103
+ const projectRoot = resolve(input.projectRoot ?? process.cwd());
104
+ const runId = input.runId?.trim() || `run-${randomUUID()}`;
105
+ const runtime = await provisionRunWorkspace({
106
+ projectRoot,
107
+ taskId: input.taskId,
108
+ runId,
109
+ ...input.ensureRuntime ? { ensureRuntime: input.ensureRuntime } : {}
110
+ });
111
+ const rigRunBin = input.rigRunBin ?? resolve(import.meta.dir, "../bin/rig-run.ts");
112
+ const initialPrompt = input.prompt?.trim() || await (input.resolveInitialPrompt ?? buildRunInitialPrompt)(projectRoot, input.taskId);
113
+ const env = {
114
+ ...process.env,
115
+ RIG_RUN_PROCESS: "1",
116
+ RIG_RUN_ID: runId,
117
+ RIG_TASK_ID: input.taskId,
118
+ ...input.title?.trim() ? { RIG_RUN_TITLE: input.title.trim() } : {}
119
+ };
120
+ const args = [
121
+ "--task",
122
+ input.taskId,
123
+ "--run-id",
124
+ runId,
125
+ "--project",
126
+ projectRoot,
127
+ ...input.title?.trim() ? ["--title", input.title.trim()] : [],
128
+ ...input.model?.trim() ? ["--model", input.model.trim()] : [],
129
+ ...input.forceSteerOnce || process.env.RIG_RUN_FORCE_STEER_ONCE === "1" ? ["--force-steer-once"] : [],
130
+ "--prompt",
131
+ initialPrompt
132
+ ];
133
+ const spawnRun = input.spawnPtyRun ?? spawnDetachedPtyRun;
134
+ const { pid } = await spawnRun({
135
+ runId,
136
+ rigRunBin,
137
+ args,
138
+ cwd: runtime.workspaceDir,
139
+ env,
140
+ logPath: resolve(runtime.logsDir, "run-process.log")
141
+ });
142
+ const sessionName = `rig-run-${runId}`;
143
+ input.writeStdout?.(`${runId}
144
+ ${sessionName}
145
+ `);
146
+ return { runId, pid, sessionName, runtime };
147
+ }
148
+ async function buildRunInitialPrompt(projectRoot, taskId) {
149
+ const { task } = await resolveRuntimeTaskRecord({ projectRoot, taskId });
150
+ const record = task;
151
+ const title = readPromptString(record.title) ?? taskId;
152
+ const body = readPromptString(record.body) ?? readPromptString(record.description);
153
+ const acceptance = readPromptString(record.acceptanceCriteria) ?? readPromptString(record.acceptance_criteria);
154
+ const scope = readPromptList(record.scope);
155
+ const validation = readPromptList(record.validation).length > 0 ? readPromptList(record.validation) : readPromptList(record.validators);
156
+ const sections = [
157
+ `You are working on task ${taskId}: ${title}.`,
158
+ body ? `Task:
159
+ ${body}` : null,
160
+ acceptance ? `Acceptance criteria:
161
+ ${acceptance}` : null,
162
+ scope.length > 0 ? `Scope:
163
+ - ${scope.join(`
164
+ - `)}` : null,
165
+ validation.length > 0 ? `Validation:
166
+ - ${validation.join(`
167
+ - `)}` : null,
168
+ "Work directly in the assigned runtime workspace and leave the result in a reviewable state."
169
+ ];
170
+ return sections.filter((value) => Boolean(value)).join(`
171
+
172
+ `);
173
+ }
174
+ function readPromptString(value) {
175
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
176
+ }
177
+ function readPromptList(value) {
178
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0).map((entry) => entry.trim()) : [];
179
+ }
180
+ function withRigDefaultConfig(argv) {
181
+ return ["--config", resolve(import.meta.dir, "../../cli/config/rig-default-config.yml"), ...argv];
182
+ }
183
+
184
+ // packages/rig-host/bin/rig-run.ts
185
+ function takeValue(args, index, flag) {
186
+ const value = args[index + 1]?.trim();
187
+ if (!value)
188
+ throw new Error(`${flag} requires a value.`);
189
+ return value;
190
+ }
191
+ function parseRigRunArgs(argv) {
192
+ let insidePty = false;
193
+ let taskId = "";
194
+ let runId = `run-${randomUUID2()}`;
195
+ let projectRoot = process.env.RIG_PROJECT_ROOT?.trim() || process.cwd();
196
+ let title;
197
+ let model;
198
+ let prompt;
199
+ let forceSteerOnce = process.env.RIG_RUN_FORCE_STEER_ONCE === "1";
200
+ for (let index = 0;index < argv.length; index += 1) {
201
+ const arg = argv[index];
202
+ if (arg === "--inside-pty") {
203
+ insidePty = true;
204
+ } else if (arg === "--task") {
205
+ taskId = takeValue(argv, index, arg);
206
+ index += 1;
207
+ } else if (arg === "--run-id") {
208
+ runId = takeValue(argv, index, arg);
209
+ index += 1;
210
+ } else if (arg === "--project") {
211
+ projectRoot = resolve2(takeValue(argv, index, arg));
212
+ index += 1;
213
+ } else if (arg === "--title") {
214
+ title = takeValue(argv, index, arg);
215
+ index += 1;
216
+ } else if (arg === "--model") {
217
+ model = takeValue(argv, index, arg);
218
+ index += 1;
219
+ } else if (arg === "--prompt" || arg === "--initial-prompt") {
220
+ prompt = takeValue(argv, index, arg);
221
+ index += 1;
222
+ } else if (arg === "--force-steer-once") {
223
+ forceSteerOnce = true;
224
+ } else {
225
+ throw new Error(`Unknown rig-run argument: ${arg}`);
226
+ }
227
+ }
228
+ if (!taskId.trim())
229
+ throw new Error("Usage: rig-run --task <id> [--run-id <id>] [--project <path>] [--title <title>] [--model <model>] [--prompt <prompt>]");
230
+ return {
231
+ insidePty,
232
+ taskId,
233
+ runId,
234
+ projectRoot: resolve2(projectRoot),
235
+ ...title !== undefined ? { title } : {},
236
+ ...model !== undefined ? { model } : {},
237
+ ...prompt !== undefined ? { prompt } : {},
238
+ ...forceSteerOnce ? { forceSteerOnce } : {}
239
+ };
240
+ }
241
+ async function main() {
242
+ let parsed;
243
+ try {
244
+ parsed = parseRigRunArgs(process.argv.slice(2));
245
+ } catch (error) {
246
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
247
+ `);
248
+ process.exitCode = 2;
249
+ return;
250
+ }
251
+ if (!parsed.insidePty) {
252
+ try {
253
+ await runRunProcess({
254
+ projectRoot: parsed.projectRoot,
255
+ taskId: parsed.taskId,
256
+ runId: parsed.runId,
257
+ ...parsed.title !== undefined ? { title: parsed.title } : {},
258
+ ...parsed.model !== undefined ? { model: parsed.model } : {},
259
+ ...parsed.prompt !== undefined ? { prompt: parsed.prompt } : {},
260
+ ...parsed.forceSteerOnce ? { forceSteerOnce: true } : {},
261
+ rigRunBin: import.meta.path,
262
+ writeStdout: (text) => process.stdout.write(text)
263
+ });
264
+ } catch (error) {
265
+ process.stderr.write(`Rig run bootstrap failed: ${error instanceof Error ? error.message : String(error)}
266
+ `);
267
+ process.exitCode = 1;
268
+ }
269
+ return;
270
+ }
271
+ const runtime = await loadRunWorkspace(parsed.projectRoot, parsed.runId);
272
+ await launchInteractiveRun({
273
+ projectRoot: parsed.projectRoot,
274
+ runtime,
275
+ runId: parsed.runId,
276
+ taskId: parsed.taskId,
277
+ ...parsed.title !== undefined ? { title: parsed.title } : {},
278
+ ...parsed.model !== undefined ? { model: parsed.model } : {},
279
+ ...parsed.prompt !== undefined ? { prompt: parsed.prompt } : {},
280
+ ...parsed.forceSteerOnce ? { forceSteerOnce: true } : {}
281
+ });
282
+ }
283
+ await main();
@@ -0,0 +1 @@
1
+ export declare function runRigHostCli(args: readonly string[]): Promise<void>;