@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 +1 -0
- package/dist/bin/rig-run.d.ts +2 -0
- package/dist/bin/rig-run.js +283 -0
- package/dist/src/cli-entry.d.ts +1 -0
- package/dist/src/cli-entry.js +476 -0
- package/dist/src/domain/approval.d.ts +1 -0
- package/dist/src/domain/approval.js +9 -0
- package/dist/src/domain/input.d.ts +1 -0
- package/dist/src/domain/input.js +9 -0
- package/dist/src/domain/run.d.ts +28 -0
- package/dist/src/domain/run.js +176 -0
- package/dist/src/domain/task.d.ts +4 -0
- package/dist/src/domain/task.js +94 -0
- package/dist/src/domain/workspace.d.ts +4 -0
- package/dist/src/domain/workspace.js +183 -0
- package/dist/src/host-session.d.ts +47 -0
- package/dist/src/host-session.js +355 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +684 -0
- package/dist/src/local-omp-runner.d.ts +52 -0
- package/dist/src/local-omp-runner.js +184 -0
- package/dist/src/operator-entry.d.ts +7 -0
- package/dist/src/operator-entry.js +79 -0
- package/dist/src/protocol.d.ts +123 -0
- package/dist/src/protocol.js +8 -0
- package/dist/src/pty-spawn.d.ts +25 -0
- package/dist/src/pty-spawn.js +34 -0
- package/dist/src/replication/broadcast.d.ts +13 -0
- package/dist/src/replication/broadcast.js +63 -0
- package/dist/src/replication/replay.d.ts +12 -0
- package/dist/src/replication/replay.js +25 -0
- package/dist/src/replication/welcome.d.ts +3 -0
- package/dist/src/replication/welcome.js +45 -0
- package/package.json +39 -0
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/rig-host/src/host-session.ts
|
|
3
|
+
import { resolve as resolve3 } from "path";
|
|
4
|
+
|
|
5
|
+
// packages/rig-host/src/domain/run.ts
|
|
6
|
+
import { basename, resolve as resolve2 } from "path";
|
|
7
|
+
|
|
8
|
+
// packages/rig-host/src/domain/task.ts
|
|
9
|
+
import { resolve } from "path";
|
|
10
|
+
import { createPluginHost } from "@rig/core";
|
|
11
|
+
import { loadConfig } from "@rig/core/load-config";
|
|
12
|
+
function stringList(value) {
|
|
13
|
+
if (!Array.isArray(value))
|
|
14
|
+
return [];
|
|
15
|
+
return value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
16
|
+
}
|
|
17
|
+
function stringOrNull(value) {
|
|
18
|
+
if (typeof value !== "string")
|
|
19
|
+
return null;
|
|
20
|
+
const trimmed = value.trim();
|
|
21
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
22
|
+
}
|
|
23
|
+
function titleFor(record) {
|
|
24
|
+
return stringOrNull(record.title) ?? stringOrNull(record.name) ?? record.id;
|
|
25
|
+
}
|
|
26
|
+
function descriptionFor(record) {
|
|
27
|
+
return stringOrNull(record.description) ?? stringOrNull(record.body) ?? "";
|
|
28
|
+
}
|
|
29
|
+
function scopeFor(record) {
|
|
30
|
+
const direct = stringList(record.scope);
|
|
31
|
+
if (direct.length > 0)
|
|
32
|
+
return direct;
|
|
33
|
+
return stringList(record.labels).filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length).trim()).filter((label) => label.length > 0);
|
|
34
|
+
}
|
|
35
|
+
function validationKeysFor(record) {
|
|
36
|
+
const direct = stringList(record.validationKeys);
|
|
37
|
+
if (direct.length > 0)
|
|
38
|
+
return direct;
|
|
39
|
+
const validation = stringList(record.validation);
|
|
40
|
+
if (validation.length > 0)
|
|
41
|
+
return validation;
|
|
42
|
+
return stringList(record.labels).filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length).trim()).filter((label) => label.length > 0);
|
|
43
|
+
}
|
|
44
|
+
function createdAtFor(record, fallback) {
|
|
45
|
+
return stringOrNull(record.createdAt) ?? fallback;
|
|
46
|
+
}
|
|
47
|
+
function updatedAtFor(record, fallback) {
|
|
48
|
+
return stringOrNull(record.updatedAt) ?? createdAtFor(record, fallback) ?? fallback;
|
|
49
|
+
}
|
|
50
|
+
function mapTaskRecordToSummary(record, workspaceId, fallbackTimestamp) {
|
|
51
|
+
const metadata = record;
|
|
52
|
+
const createdAt = createdAtFor(record, fallbackTimestamp);
|
|
53
|
+
const updatedAt = updatedAtFor(record, fallbackTimestamp);
|
|
54
|
+
return {
|
|
55
|
+
id: record.id,
|
|
56
|
+
workspaceId,
|
|
57
|
+
graphId: null,
|
|
58
|
+
externalId: stringOrNull(metadata.externalId) ?? stringOrNull(metadata.externalRef),
|
|
59
|
+
title: titleFor(record),
|
|
60
|
+
description: descriptionFor(record),
|
|
61
|
+
status: record.status,
|
|
62
|
+
priority: typeof metadata.priority === "number" && Number.isInteger(metadata.priority) && metadata.priority > 0 ? metadata.priority : null,
|
|
63
|
+
role: stringOrNull(metadata.role),
|
|
64
|
+
scope: scopeFor(record),
|
|
65
|
+
validationKeys: validationKeysFor(record),
|
|
66
|
+
sourceIssueId: stringOrNull(metadata.sourceIssueId),
|
|
67
|
+
dependencies: [...record.deps],
|
|
68
|
+
parentChildDeps: [],
|
|
69
|
+
metadata,
|
|
70
|
+
createdAt,
|
|
71
|
+
updatedAt
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function loadRigIdeaTasks(workspaceRoot) {
|
|
75
|
+
const normalizedRoot = resolve(workspaceRoot);
|
|
76
|
+
const config = await loadConfig(normalizedRoot);
|
|
77
|
+
const pluginHost = createPluginHost(config.plugins);
|
|
78
|
+
const taskSourceFactory = pluginHost.resolveTaskSourceFactoryByKind(config.taskSource.kind);
|
|
79
|
+
if (!taskSourceFactory) {
|
|
80
|
+
const kinds = pluginHost.listExecutableTaskSources().map((entry) => entry.kind).join(", ") || "none";
|
|
81
|
+
throw new Error(`No task source factory registered for kind "${config.taskSource.kind}". Registered kinds: ${kinds}.`);
|
|
82
|
+
}
|
|
83
|
+
const source = taskSourceFactory.factory(config.taskSource, { projectRoot: normalizedRoot });
|
|
84
|
+
const fallbackTimestamp = new Date().toISOString();
|
|
85
|
+
const workspaceId = normalizedRoot;
|
|
86
|
+
const tasks = await source.list();
|
|
87
|
+
return tasks.map((task) => mapTaskRecordToSummary(task, workspaceId, fallbackTimestamp));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// packages/rig-host/src/domain/run.ts
|
|
91
|
+
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.";
|
|
92
|
+
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.";
|
|
93
|
+
function legacyAuthorityRunUnsupportedMessage() {
|
|
94
|
+
return LEGACY_AUTHORITY_RUN_CREATION_RETIRED_MESSAGE;
|
|
95
|
+
}
|
|
96
|
+
function legacyAuthorityRunControlUnsupportedMessage() {
|
|
97
|
+
return LEGACY_AUTHORITY_RUN_CONTROL_RETIRED_MESSAGE;
|
|
98
|
+
}
|
|
99
|
+
function legacyAuthorityRunControlUnsupportedError() {
|
|
100
|
+
return new Error(legacyAuthorityRunControlUnsupportedMessage());
|
|
101
|
+
}
|
|
102
|
+
function projectRetiredLegacyRigIdeaWorkspace(workspaceRoot, tasks) {
|
|
103
|
+
const normalizedRoot = resolve2(workspaceRoot);
|
|
104
|
+
const updatedAt = new Date().toISOString();
|
|
105
|
+
const title = basename(normalizedRoot) || normalizedRoot;
|
|
106
|
+
return {
|
|
107
|
+
workspaceRoot: normalizedRoot,
|
|
108
|
+
activeRunId: null,
|
|
109
|
+
sequence: 0,
|
|
110
|
+
workspaces: [{
|
|
111
|
+
id: normalizedRoot,
|
|
112
|
+
title,
|
|
113
|
+
rootPath: normalizedRoot,
|
|
114
|
+
sourceKind: "native",
|
|
115
|
+
defaultModel: null,
|
|
116
|
+
createdAt: updatedAt,
|
|
117
|
+
updatedAt
|
|
118
|
+
}],
|
|
119
|
+
graphs: [],
|
|
120
|
+
tasks: [...tasks],
|
|
121
|
+
runs: [],
|
|
122
|
+
runtimes: [],
|
|
123
|
+
conversations: [],
|
|
124
|
+
messages: [],
|
|
125
|
+
actions: [],
|
|
126
|
+
logs: [],
|
|
127
|
+
approvals: [],
|
|
128
|
+
userInputs: [],
|
|
129
|
+
validations: [],
|
|
130
|
+
reviews: [],
|
|
131
|
+
artifacts: [],
|
|
132
|
+
policyDecisions: [],
|
|
133
|
+
queue: [],
|
|
134
|
+
worktrees: [],
|
|
135
|
+
remoteEndpoints: [],
|
|
136
|
+
remoteConnections: [],
|
|
137
|
+
remoteOrchestrations: [],
|
|
138
|
+
updatedAt
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async function createRigIdeaHarness(workspaceRoot) {
|
|
142
|
+
const normalizedRoot = resolve2(workspaceRoot);
|
|
143
|
+
const tasks = await loadRigIdeaTasks(normalizedRoot).catch(() => []);
|
|
144
|
+
return {
|
|
145
|
+
snapshot() {
|
|
146
|
+
return projectRetiredLegacyRigIdeaWorkspace(normalizedRoot, tasks);
|
|
147
|
+
},
|
|
148
|
+
async stopRun(_runId) {
|
|
149
|
+
throw legacyAuthorityRunControlUnsupportedError();
|
|
150
|
+
},
|
|
151
|
+
async resolveApproval(_runId, _requestId, _decision) {
|
|
152
|
+
throw legacyAuthorityRunControlUnsupportedError();
|
|
153
|
+
},
|
|
154
|
+
async resolveUserInput(_runId, _requestId, _answers) {
|
|
155
|
+
throw legacyAuthorityRunControlUnsupportedError();
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// packages/rig-host/src/replication/broadcast.ts
|
|
161
|
+
function createRigHostBroadcaster(listener, collab) {
|
|
162
|
+
const backlog = [];
|
|
163
|
+
const welcomedPeers = new Set;
|
|
164
|
+
const hasWelcomedRelayPeer = () => {
|
|
165
|
+
for (const peer of welcomedPeers) {
|
|
166
|
+
if (peer !== 0)
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
};
|
|
171
|
+
const emit = (frame, targetPeer = 0) => {
|
|
172
|
+
listener?.(frame);
|
|
173
|
+
collab?.send(frame, targetPeer);
|
|
174
|
+
};
|
|
175
|
+
const flush = (targetPeer) => {
|
|
176
|
+
let write = 0;
|
|
177
|
+
for (let read = 0;read < backlog.length; read += 1) {
|
|
178
|
+
const next = backlog[read];
|
|
179
|
+
if (next.targetPeer === targetPeer) {
|
|
180
|
+
emit(next.frame, next.targetPeer);
|
|
181
|
+
} else {
|
|
182
|
+
backlog[write] = next;
|
|
183
|
+
write += 1;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
backlog.length = write;
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
publish(frame, targetPeer = 0) {
|
|
190
|
+
if (frame.t === "welcome") {
|
|
191
|
+
welcomedPeers.add(targetPeer);
|
|
192
|
+
emit(frame, targetPeer);
|
|
193
|
+
flush(targetPeer);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (!welcomedPeers.has(targetPeer) && frame.t !== "bye" && frame.t !== "error") {
|
|
197
|
+
if (targetPeer === 0) {
|
|
198
|
+
if (hasWelcomedRelayPeer())
|
|
199
|
+
emit(frame, targetPeer);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
backlog.push({ frame, targetPeer });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
emit(frame, targetPeer);
|
|
206
|
+
},
|
|
207
|
+
publishWelcome(snapshot, readOnly, targetPeer) {
|
|
208
|
+
this.publish({ t: "welcome", snapshot, readOnly }, targetPeer);
|
|
209
|
+
},
|
|
210
|
+
rejectReadOnly(action, targetPeer) {
|
|
211
|
+
this.publish({
|
|
212
|
+
t: "error",
|
|
213
|
+
code: "READ_ONLY",
|
|
214
|
+
message: `${action} is disabled on a read-only link`
|
|
215
|
+
}, targetPeer);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// packages/rig-host/src/host-session.ts
|
|
221
|
+
var LOCAL_PEER_ID = 0;
|
|
222
|
+
var RIG_GENERIC_OMP_JOIN_LIMITATION = "Legacy rig-host remote join and attachment are retired for the product path. Use the default Rig OMP extension/collab session flow instead.";
|
|
223
|
+
var RIG_RUN_SCOPE_LIMITATION = "Legacy rig-host selected-run remote join is retired for the product path. Selected-run discovery now belongs to OMP collab; use the default Rig OMP extension/collab session flow instead.";
|
|
224
|
+
async function createRigOmpWritableJoinLink(_options) {
|
|
225
|
+
throw new Error(RIG_GENERIC_OMP_JOIN_LIMITATION);
|
|
226
|
+
}
|
|
227
|
+
async function createRigOmpJoinRoute(options) {
|
|
228
|
+
const runId = options.runId?.trim() ? options.runId : null;
|
|
229
|
+
throw new Error(runId ? RIG_RUN_SCOPE_LIMITATION : RIG_GENERIC_OMP_JOIN_LIMITATION);
|
|
230
|
+
}
|
|
231
|
+
async function joinRigOmpRelayLink(_options) {
|
|
232
|
+
throw new Error(RIG_GENERIC_OMP_JOIN_LIMITATION);
|
|
233
|
+
}
|
|
234
|
+
async function runRigJoinSelectedRun(_options) {
|
|
235
|
+
throw new Error(RIG_RUN_SCOPE_LIMITATION);
|
|
236
|
+
}
|
|
237
|
+
function rigGenericOmpJoinLimitation() {
|
|
238
|
+
return RIG_GENERIC_OMP_JOIN_LIMITATION;
|
|
239
|
+
}
|
|
240
|
+
async function startRigHostSession(options) {
|
|
241
|
+
if (options.relayUrl) {
|
|
242
|
+
throw new Error(RIG_GENERIC_OMP_JOIN_LIMITATION);
|
|
243
|
+
}
|
|
244
|
+
const workspaceRoot = resolve3(options.workspaceRoot);
|
|
245
|
+
let stopped = false;
|
|
246
|
+
let harness = await createRigIdeaHarness(workspaceRoot);
|
|
247
|
+
let snapshot = harness.snapshot();
|
|
248
|
+
let hostSequence = snapshot.sequence;
|
|
249
|
+
const guests = new Map;
|
|
250
|
+
const broadcast = createRigHostBroadcaster(options.onFrame);
|
|
251
|
+
function publishState(nextSnapshot) {
|
|
252
|
+
hostSequence = Math.max(hostSequence, nextSnapshot.sequence) + 1;
|
|
253
|
+
snapshot = { ...nextSnapshot, sequence: hostSequence };
|
|
254
|
+
broadcast.publish({ t: "state", snapshot });
|
|
255
|
+
return snapshot;
|
|
256
|
+
}
|
|
257
|
+
async function refreshWorkspaceState() {
|
|
258
|
+
harness = await createRigIdeaHarness(workspaceRoot);
|
|
259
|
+
publishState(harness.snapshot());
|
|
260
|
+
}
|
|
261
|
+
function publishLegacyRunError(message, fromPeer) {
|
|
262
|
+
broadcast.publish({
|
|
263
|
+
t: "error",
|
|
264
|
+
code: "LEGACY_AUTHORITY_RUN_UNSUPPORTED",
|
|
265
|
+
message
|
|
266
|
+
}, fromPeer);
|
|
267
|
+
}
|
|
268
|
+
async function handleCommand(frame, fromPeer) {
|
|
269
|
+
switch (frame.command.kind) {
|
|
270
|
+
case "workspace.refresh":
|
|
271
|
+
await refreshWorkspaceState();
|
|
272
|
+
return;
|
|
273
|
+
case "run.createAdhoc":
|
|
274
|
+
case "task.run":
|
|
275
|
+
publishLegacyRunError(legacyAuthorityRunUnsupportedMessage(), fromPeer);
|
|
276
|
+
return;
|
|
277
|
+
case "run.stop":
|
|
278
|
+
case "run.resume":
|
|
279
|
+
case "run.interrupt":
|
|
280
|
+
case "run.submitMessage":
|
|
281
|
+
case "approval.resolve":
|
|
282
|
+
case "input.resolve":
|
|
283
|
+
publishLegacyRunError(legacyAuthorityRunControlUnsupportedMessage(), fromPeer);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function handleGuestFrame(frame, fromPeer = LOCAL_PEER_ID) {
|
|
288
|
+
if (stopped)
|
|
289
|
+
return;
|
|
290
|
+
switch (frame.t) {
|
|
291
|
+
case "hello": {
|
|
292
|
+
const guest = {
|
|
293
|
+
sessionName: frame.sessionName.trim().slice(0, 64) || `guest-${fromPeer}`,
|
|
294
|
+
readOnly: frame.readOnly === true
|
|
295
|
+
};
|
|
296
|
+
guests.set(fromPeer, guest);
|
|
297
|
+
broadcast.publishWelcome(snapshot, guest.readOnly, fromPeer);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
case "command": {
|
|
301
|
+
if (guests.get(fromPeer)?.readOnly !== false) {
|
|
302
|
+
broadcast.rejectReadOnly("command", fromPeer);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
await handleCommand(frame, fromPeer);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
broadcast.publish({
|
|
309
|
+
t: "error",
|
|
310
|
+
code: "COMMAND_FAILED",
|
|
311
|
+
message: error instanceof Error ? error.message : String(error)
|
|
312
|
+
}, fromPeer);
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
case "abort": {
|
|
317
|
+
if (guests.get(fromPeer)?.readOnly !== false) {
|
|
318
|
+
broadcast.rejectReadOnly("abort", fromPeer);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
publishLegacyRunError(legacyAuthorityRunControlUnsupportedMessage(), fromPeer);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
case "fetch-transcript": {
|
|
325
|
+
publishLegacyRunError(legacyAuthorityRunControlUnsupportedMessage(), fromPeer);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
snapshot() {
|
|
332
|
+
return snapshot;
|
|
333
|
+
},
|
|
334
|
+
async createShareLink(_readOnly = false) {
|
|
335
|
+
throw new Error(RIG_GENERIC_OMP_JOIN_LIMITATION);
|
|
336
|
+
},
|
|
337
|
+
handleGuestFrame(frame) {
|
|
338
|
+
return handleGuestFrame(frame);
|
|
339
|
+
},
|
|
340
|
+
async stop(reason) {
|
|
341
|
+
if (stopped)
|
|
342
|
+
return;
|
|
343
|
+
stopped = true;
|
|
344
|
+
broadcast.publish({ t: "bye", reason });
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
// packages/rig-host/src/protocol.ts
|
|
349
|
+
var RigHostFrameKinds = ["welcome", "entry", "event", "state", "bus", "backfill", "bye", "error"];
|
|
350
|
+
var RigGuestFrameKinds = ["hello", "command", "abort", "fetch-transcript"];
|
|
351
|
+
// packages/rig-host/src/cli-entry.ts
|
|
352
|
+
import { existsSync, readFileSync } from "fs";
|
|
353
|
+
import { dirname, resolve as resolve4 } from "path";
|
|
354
|
+
function packageVersionFrom(startDir) {
|
|
355
|
+
let current = resolve4(startDir);
|
|
356
|
+
while (true) {
|
|
357
|
+
const pkgPath = resolve4(current, "package.json");
|
|
358
|
+
if (existsSync(pkgPath)) {
|
|
359
|
+
try {
|
|
360
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
361
|
+
if ((pkg.name === "rig" || pkg.name === "@rig/cli" || pkg.name === "@h-rig/cli") && pkg.version)
|
|
362
|
+
return pkg.version;
|
|
363
|
+
} catch {}
|
|
364
|
+
}
|
|
365
|
+
const parent = dirname(current);
|
|
366
|
+
if (parent === current)
|
|
367
|
+
return null;
|
|
368
|
+
current = parent;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function usageText() {
|
|
372
|
+
return [
|
|
373
|
+
"Usage: rig [host|share] [--workspace <path>] [--json]",
|
|
374
|
+
"",
|
|
375
|
+
"Commands:",
|
|
376
|
+
" host Boot the host-authoritative runtime and print the current workspace summary.",
|
|
377
|
+
" share Boot the host-authoritative runtime and print a read-only share link.",
|
|
378
|
+
"",
|
|
379
|
+
"Flags:",
|
|
380
|
+
" --workspace <path> Override the workspace root (defaults to the current working directory).",
|
|
381
|
+
" --json Emit JSON instead of the text summary.",
|
|
382
|
+
" --write For share, emit a writable collaboration link instead of read-only.",
|
|
383
|
+
" --help, -h Show this help text.",
|
|
384
|
+
" --version, -V Show the rig CLI version."
|
|
385
|
+
].join(`
|
|
386
|
+
`);
|
|
387
|
+
}
|
|
388
|
+
function parseRigHostCli(args) {
|
|
389
|
+
let command = "host";
|
|
390
|
+
let help = false;
|
|
391
|
+
let json = false;
|
|
392
|
+
let readOnly = true;
|
|
393
|
+
let version = false;
|
|
394
|
+
let workspaceRoot = process.cwd();
|
|
395
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
396
|
+
const arg = args[index];
|
|
397
|
+
if (arg === undefined)
|
|
398
|
+
continue;
|
|
399
|
+
if (arg === "--help" || arg === "-h" || arg === "help") {
|
|
400
|
+
help = true;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (arg === "--version" || arg === "-V" || arg === "version") {
|
|
404
|
+
version = true;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (arg === "--json") {
|
|
408
|
+
json = true;
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (arg === "--write") {
|
|
412
|
+
readOnly = false;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (arg === "--workspace") {
|
|
416
|
+
const next = args[index + 1];
|
|
417
|
+
if (!next)
|
|
418
|
+
throw new Error("Missing value for --workspace");
|
|
419
|
+
workspaceRoot = next;
|
|
420
|
+
index += 1;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
if (arg === "host" || arg === "share") {
|
|
424
|
+
command = arg;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
command,
|
|
431
|
+
help,
|
|
432
|
+
json,
|
|
433
|
+
readOnly,
|
|
434
|
+
version,
|
|
435
|
+
workspaceRoot: resolve4(workspaceRoot)
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function writeLine(text) {
|
|
439
|
+
process.stdout.write(`${text}
|
|
440
|
+
`);
|
|
441
|
+
}
|
|
442
|
+
function renderHostSummary(workspaceRoot, snapshot) {
|
|
443
|
+
const pendingApprovals = snapshot.approvals.filter((item) => item.status === "pending").length;
|
|
444
|
+
const pendingInputs = (snapshot.userInputs ?? []).filter((item) => item.status === "pending").length;
|
|
445
|
+
return [
|
|
446
|
+
`Workspace: ${workspaceRoot}`,
|
|
447
|
+
`Tasks: ${snapshot.tasks.length}`,
|
|
448
|
+
`Runs: ${snapshot.runs.length}`,
|
|
449
|
+
`Approvals: ${pendingApprovals}`,
|
|
450
|
+
`Inputs: ${pendingInputs}`
|
|
451
|
+
];
|
|
452
|
+
}
|
|
453
|
+
async function runRigHostCli(args) {
|
|
454
|
+
const options = parseRigHostCli(args);
|
|
455
|
+
if (!options) {
|
|
456
|
+
throw new Error("__RIG_HOST_CLI_FALLBACK__");
|
|
457
|
+
}
|
|
458
|
+
if (options.version) {
|
|
459
|
+
writeLine(`rig ${process.env.RIG_CLI_VERSION?.trim() || packageVersionFrom(import.meta.dir) || "0.0.0-dev"}`);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (options.help) {
|
|
463
|
+
writeLine(usageText());
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const host = await startRigHostSession({ workspaceRoot: options.workspaceRoot });
|
|
467
|
+
try {
|
|
468
|
+
if (options.command === "share") {
|
|
469
|
+
const link = await host.createShareLink(options.readOnly);
|
|
470
|
+
if (options.json) {
|
|
471
|
+
writeLine(JSON.stringify({ link, readOnly: options.readOnly, workspaceRoot: options.workspaceRoot }, null, 2));
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
writeLine(`Share link (${options.readOnly ? "read-only" : "write"}): ${link}`);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const snapshot = host.snapshot();
|
|
478
|
+
if (options.json) {
|
|
479
|
+
writeLine(JSON.stringify({
|
|
480
|
+
connection: "connected",
|
|
481
|
+
snapshot
|
|
482
|
+
}, null, 2));
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
for (const line of renderHostSummary(options.workspaceRoot, snapshot))
|
|
486
|
+
writeLine(line);
|
|
487
|
+
} finally {
|
|
488
|
+
await host.stop("rig host cli complete");
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// packages/rig-host/src/local-omp-runner.ts
|
|
492
|
+
import { randomUUID } from "crypto";
|
|
493
|
+
import { resolve as resolve5 } from "path";
|
|
494
|
+
import { createAgentSession, SessionManager } from "@oh-my-pi/pi-coding-agent";
|
|
495
|
+
import { parseArgs } from "@oh-my-pi/pi-coding-agent/cli/args";
|
|
496
|
+
import { runRootCommand } from "@oh-my-pi/pi-coding-agent/main";
|
|
497
|
+
import {
|
|
498
|
+
ensureAgentRuntime,
|
|
499
|
+
listAgentRuntimes,
|
|
500
|
+
resolveRuntimeTaskRecord,
|
|
501
|
+
runtimeEnv as hydrateRuntimeEnv
|
|
502
|
+
} from "@rig/runtime/control-plane/runtime/isolation";
|
|
503
|
+
import rigExtension from "@rig/rig-extension";
|
|
504
|
+
|
|
505
|
+
// packages/rig-host/src/pty-spawn.ts
|
|
506
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
507
|
+
import { closeSync as fsCloseSync, mkdirSync, openSync as fsOpenSync } from "fs";
|
|
508
|
+
import { dirname as dirname2 } from "path";
|
|
509
|
+
function buildPtyRunArgv(input) {
|
|
510
|
+
return ["-L", `rig-${input.runId}`, "new-session", "-d", "-s", `rig-run-${input.runId}`, input.rigRunBin, "--inside-pty", ...input.args];
|
|
511
|
+
}
|
|
512
|
+
async function spawnDetachedPtyRun(input, deps = {}) {
|
|
513
|
+
const tmux = await (deps.which ?? Bun.which)("tmux");
|
|
514
|
+
if (!tmux) {
|
|
515
|
+
throw new Error("tmux is required to launch a detached Rig PTY run. Install tmux or provide a Phase-0-approved PTY fallback.");
|
|
516
|
+
}
|
|
517
|
+
mkdirSync(dirname2(input.logPath), { recursive: true });
|
|
518
|
+
const openSync = deps.openSync ?? fsOpenSync;
|
|
519
|
+
const closeSync = deps.closeSync ?? fsCloseSync;
|
|
520
|
+
const spawn = deps.spawn ?? nodeSpawn;
|
|
521
|
+
const logFd = openSync(input.logPath, "a");
|
|
522
|
+
const child = spawn(tmux, buildPtyRunArgv(input), {
|
|
523
|
+
cwd: input.cwd,
|
|
524
|
+
env: input.env,
|
|
525
|
+
detached: true,
|
|
526
|
+
stdio: ["ignore", logFd, logFd]
|
|
527
|
+
});
|
|
528
|
+
closeSync(logFd);
|
|
529
|
+
child.unref();
|
|
530
|
+
if (typeof child.pid !== "number")
|
|
531
|
+
throw new Error("tmux did not report a child process pid for the detached Rig run.");
|
|
532
|
+
return { pid: child.pid };
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// packages/rig-host/src/local-omp-runner.ts
|
|
536
|
+
async function provisionRunWorkspace(input) {
|
|
537
|
+
return (input.ensureRuntime ?? ensureAgentRuntime)({
|
|
538
|
+
projectRoot: input.projectRoot,
|
|
539
|
+
id: input.runId,
|
|
540
|
+
taskId: input.taskId,
|
|
541
|
+
mode: "worktree",
|
|
542
|
+
provider: "pi"
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
async function loadRunWorkspace(projectRoot, runId) {
|
|
546
|
+
const runtimes = await listAgentRuntimes(projectRoot);
|
|
547
|
+
const runtime = runtimes.find((candidate) => candidate.id === runId);
|
|
548
|
+
if (!runtime)
|
|
549
|
+
throw new Error(`Rig run workspace is not provisioned for run ${runId}.`);
|
|
550
|
+
return runtime;
|
|
551
|
+
}
|
|
552
|
+
async function applyRunEnv(input) {
|
|
553
|
+
const env = await (input.runtimeEnv ?? hydrateRuntimeEnv)(input.projectRoot, input.runtime);
|
|
554
|
+
for (const [key, value] of Object.entries(env))
|
|
555
|
+
process.env[key] = value;
|
|
556
|
+
return env;
|
|
557
|
+
}
|
|
558
|
+
async function launchInteractiveRun(input) {
|
|
559
|
+
await applyRunEnv({ projectRoot: input.projectRoot, runtime: input.runtime });
|
|
560
|
+
process.env.RIG_RUN_PROCESS = "1";
|
|
561
|
+
process.env.RIG_RUN_ID = input.runId;
|
|
562
|
+
process.env.RIG_TASK_ID = input.taskId;
|
|
563
|
+
process.env.PROJECT_RIG_ROOT = input.projectRoot;
|
|
564
|
+
process.env.RIG_HOST_PROJECT_ROOT = input.projectRoot;
|
|
565
|
+
if (input.title?.trim())
|
|
566
|
+
process.env.RIG_RUN_TITLE = input.title.trim();
|
|
567
|
+
if (input.forceSteerOnce)
|
|
568
|
+
process.env.RIG_RUN_FORCE_STEER_ONCE = "1";
|
|
569
|
+
const sessionManager = SessionManager.create(input.runtime.workspaceDir, input.runtime.sessionDir);
|
|
570
|
+
await sessionManager.ensureOnDisk();
|
|
571
|
+
const initialPrompt = input.prompt?.trim() || await (input.resolveInitialPrompt ?? buildRunInitialPrompt)(input.projectRoot, input.taskId);
|
|
572
|
+
const args = withRigDefaultConfig([
|
|
573
|
+
...input.model?.trim() ? ["--model", input.model.trim()] : [],
|
|
574
|
+
initialPrompt
|
|
575
|
+
]);
|
|
576
|
+
const createRigAgentSession = async (options = {}) => createAgentSession({
|
|
577
|
+
...options,
|
|
578
|
+
cwd: input.runtime.workspaceDir,
|
|
579
|
+
agentDir: resolve5(input.runtime.homeDir, ".pi", "agent"),
|
|
580
|
+
sessionManager,
|
|
581
|
+
extensions: [(api) => rigExtension(api), ...options.extensions ?? []]
|
|
582
|
+
});
|
|
583
|
+
await runRootCommand(parseArgs(args), args, { createAgentSession: createRigAgentSession });
|
|
584
|
+
}
|
|
585
|
+
async function runRunProcess(input) {
|
|
586
|
+
const projectRoot = resolve5(input.projectRoot ?? process.cwd());
|
|
587
|
+
const runId = input.runId?.trim() || `run-${randomUUID()}`;
|
|
588
|
+
const runtime = await provisionRunWorkspace({
|
|
589
|
+
projectRoot,
|
|
590
|
+
taskId: input.taskId,
|
|
591
|
+
runId,
|
|
592
|
+
...input.ensureRuntime ? { ensureRuntime: input.ensureRuntime } : {}
|
|
593
|
+
});
|
|
594
|
+
const rigRunBin = input.rigRunBin ?? resolve5(import.meta.dir, "../bin/rig-run.ts");
|
|
595
|
+
const initialPrompt = input.prompt?.trim() || await (input.resolveInitialPrompt ?? buildRunInitialPrompt)(projectRoot, input.taskId);
|
|
596
|
+
const env = {
|
|
597
|
+
...process.env,
|
|
598
|
+
RIG_RUN_PROCESS: "1",
|
|
599
|
+
RIG_RUN_ID: runId,
|
|
600
|
+
RIG_TASK_ID: input.taskId,
|
|
601
|
+
...input.title?.trim() ? { RIG_RUN_TITLE: input.title.trim() } : {}
|
|
602
|
+
};
|
|
603
|
+
const args = [
|
|
604
|
+
"--task",
|
|
605
|
+
input.taskId,
|
|
606
|
+
"--run-id",
|
|
607
|
+
runId,
|
|
608
|
+
"--project",
|
|
609
|
+
projectRoot,
|
|
610
|
+
...input.title?.trim() ? ["--title", input.title.trim()] : [],
|
|
611
|
+
...input.model?.trim() ? ["--model", input.model.trim()] : [],
|
|
612
|
+
...input.forceSteerOnce || process.env.RIG_RUN_FORCE_STEER_ONCE === "1" ? ["--force-steer-once"] : [],
|
|
613
|
+
"--prompt",
|
|
614
|
+
initialPrompt
|
|
615
|
+
];
|
|
616
|
+
const spawnRun = input.spawnPtyRun ?? spawnDetachedPtyRun;
|
|
617
|
+
const { pid } = await spawnRun({
|
|
618
|
+
runId,
|
|
619
|
+
rigRunBin,
|
|
620
|
+
args,
|
|
621
|
+
cwd: runtime.workspaceDir,
|
|
622
|
+
env,
|
|
623
|
+
logPath: resolve5(runtime.logsDir, "run-process.log")
|
|
624
|
+
});
|
|
625
|
+
const sessionName = `rig-run-${runId}`;
|
|
626
|
+
input.writeStdout?.(`${runId}
|
|
627
|
+
${sessionName}
|
|
628
|
+
`);
|
|
629
|
+
return { runId, pid, sessionName, runtime };
|
|
630
|
+
}
|
|
631
|
+
async function buildRunInitialPrompt(projectRoot, taskId) {
|
|
632
|
+
const { task } = await resolveRuntimeTaskRecord({ projectRoot, taskId });
|
|
633
|
+
const record = task;
|
|
634
|
+
const title = readPromptString(record.title) ?? taskId;
|
|
635
|
+
const body = readPromptString(record.body) ?? readPromptString(record.description);
|
|
636
|
+
const acceptance = readPromptString(record.acceptanceCriteria) ?? readPromptString(record.acceptance_criteria);
|
|
637
|
+
const scope = readPromptList(record.scope);
|
|
638
|
+
const validation = readPromptList(record.validation).length > 0 ? readPromptList(record.validation) : readPromptList(record.validators);
|
|
639
|
+
const sections = [
|
|
640
|
+
`You are working on task ${taskId}: ${title}.`,
|
|
641
|
+
body ? `Task:
|
|
642
|
+
${body}` : null,
|
|
643
|
+
acceptance ? `Acceptance criteria:
|
|
644
|
+
${acceptance}` : null,
|
|
645
|
+
scope.length > 0 ? `Scope:
|
|
646
|
+
- ${scope.join(`
|
|
647
|
+
- `)}` : null,
|
|
648
|
+
validation.length > 0 ? `Validation:
|
|
649
|
+
- ${validation.join(`
|
|
650
|
+
- `)}` : null,
|
|
651
|
+
"Work directly in the assigned runtime workspace and leave the result in a reviewable state."
|
|
652
|
+
];
|
|
653
|
+
return sections.filter((value) => Boolean(value)).join(`
|
|
654
|
+
|
|
655
|
+
`);
|
|
656
|
+
}
|
|
657
|
+
function readPromptString(value) {
|
|
658
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
659
|
+
}
|
|
660
|
+
function readPromptList(value) {
|
|
661
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0).map((entry) => entry.trim()) : [];
|
|
662
|
+
}
|
|
663
|
+
function withRigDefaultConfig(argv) {
|
|
664
|
+
return ["--config", resolve5(import.meta.dir, "../../cli/config/rig-default-config.yml"), ...argv];
|
|
665
|
+
}
|
|
666
|
+
export {
|
|
667
|
+
startRigHostSession,
|
|
668
|
+
spawnDetachedPtyRun,
|
|
669
|
+
runRunProcess,
|
|
670
|
+
runRigJoinSelectedRun,
|
|
671
|
+
runRigHostCli,
|
|
672
|
+
rigGenericOmpJoinLimitation,
|
|
673
|
+
provisionRunWorkspace,
|
|
674
|
+
loadRunWorkspace,
|
|
675
|
+
launchInteractiveRun,
|
|
676
|
+
joinRigOmpRelayLink,
|
|
677
|
+
createRigOmpWritableJoinLink,
|
|
678
|
+
createRigOmpJoinRoute,
|
|
679
|
+
buildRunInitialPrompt,
|
|
680
|
+
buildPtyRunArgv,
|
|
681
|
+
applyRunEnv,
|
|
682
|
+
RigHostFrameKinds,
|
|
683
|
+
RigGuestFrameKinds
|
|
684
|
+
};
|