@h-rig/provider-plugin 0.0.6-alpha.157 → 0.0.6-alpha.158
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/dist/bin/rig-agent-dispatch.d.ts +2 -0
- package/dist/bin/rig-agent-dispatch.js +863 -0
- package/dist/src/agent-harness/agent-mode.d.ts +1 -0
- package/dist/src/agent-harness/agent-mode.js +48 -0
- package/dist/src/agent-harness/agent-wrapper.d.ts +53 -0
- package/dist/src/agent-harness/agent-wrapper.js +916 -0
- package/dist/src/agent-harness/controlled-bash.d.ts +3 -0
- package/dist/src/agent-harness/controlled-bash.js +45 -0
- package/dist/src/agent-harness/git-ops.d.ts +2 -0
- package/dist/src/agent-harness/git-ops.js +27 -0
- package/dist/src/agent-harness/repo-ops.d.ts +8 -0
- package/dist/src/agent-harness/repo-ops.js +471 -0
- package/dist/src/agent-harness/rig-agent-entrypoint.d.ts +1 -0
- package/dist/src/agent-harness/rig-agent-entrypoint.js +1277 -0
- package/dist/src/agent-harness/rig-agent.d.ts +2 -0
- package/dist/src/agent-harness/rig-agent.js +1244 -0
- package/dist/src/agent-harness/runtime-snapshot-config.d.ts +2 -0
- package/dist/src/agent-harness/runtime-snapshot-config.js +25 -0
- package/dist/src/agent-harness/task-data.d.ts +2 -0
- package/dist/src/agent-harness/task-data.js +12 -0
- package/dist/src/agent-harness/task-ops.d.ts +10 -0
- package/dist/src/agent-harness/task-ops.js +53 -0
- package/dist/src/index.js +3366 -16
- package/dist/src/pi-settings-materializer.d.ts +10 -0
- package/dist/src/pi-settings-materializer.js +52 -0
- package/dist/src/plugin.d.ts +9 -2
- package/dist/src/plugin.js +3360 -8
- package/dist/src/service.d.ts +1 -1
- package/dist/src/session-asset-materializer-service.d.ts +13 -0
- package/dist/src/session-asset-materializer-service.js +124 -0
- package/dist/src/skill-materializer.d.ts +25 -0
- package/dist/src/skill-materializer.js +46 -0
- package/dist/src/tooling/binary-build-worker.d.ts +1 -0
- package/dist/src/tooling/binary-build-worker.js +323 -0
- package/dist/src/tooling/browser-tool-entrypoint.d.ts +2 -0
- package/dist/src/tooling/browser-tool-entrypoint.js +125 -0
- package/dist/src/tooling/browser-tools.d.ts +3 -0
- package/dist/src/tooling/browser-tools.js +27 -0
- package/dist/src/tooling/claude-router-binary.d.ts +3 -0
- package/dist/src/tooling/claude-router-binary.js +381 -0
- package/dist/src/tooling/claude-router.d.ts +22 -0
- package/dist/src/tooling/claude-router.js +524 -0
- package/dist/src/tooling/embedded-native-assets.d.ts +7 -0
- package/dist/src/tooling/embedded-native-assets.js +6 -0
- package/dist/src/tooling/file-tools.d.ts +5 -0
- package/dist/src/tooling/file-tools.js +224 -0
- package/dist/src/tooling/gateway.d.ts +4 -0
- package/dist/src/tooling/gateway.js +430 -0
- package/dist/src/tooling/native-extract.d.ts +2 -0
- package/dist/src/tooling/native-extract.js +44 -0
- package/dist/src/tooling/runtime-binary-build.d.ts +88 -0
- package/dist/src/tooling/runtime-binary-build.js +480 -0
- package/dist/src/tooling/shell-tools.d.ts +5 -0
- package/dist/src/tooling/shell-tools.js +217 -0
- package/native/darwin-arm64/rig-shell +0 -0
- package/native/darwin-arm64/rig-shell.build-manifest.json +4 -0
- package/native/darwin-arm64/rig-tools +0 -0
- package/native/darwin-arm64/rig-tools.build-manifest.json +4 -0
- package/native/darwin-x64/rig-shell +0 -0
- package/native/darwin-x64/rig-tools +0 -0
- package/native/linux-arm64/rig-shell +0 -0
- package/native/linux-arm64/rig-tools +0 -0
- package/native/linux-x64/rig-shell +0 -0
- package/native/linux-x64/rig-tools +0 -0
- package/native/win32-x64/rig-shell.exe +0 -0
- package/native/win32-x64/rig-tools.exe +0 -0
- package/package.json +54 -5
- package/dist/src/claude-stream-records.d.ts +0 -24
- package/dist/src/claude-stream-records.js +0 -158
- package/dist/src/codex-app-server.d.ts +0 -16
- package/dist/src/codex-app-server.js +0 -548
- package/dist/src/codex-exec-records.d.ts +0 -27
- package/dist/src/codex-exec-records.js +0 -203
|
@@ -0,0 +1,916 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// packages/provider-plugin/src/agent-harness/agent-wrapper.ts
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import { resolve as resolve2 } from "path";
|
|
7
|
+
import { existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
|
|
8
|
+
import { assertPathInsideRoot, safePathSegment, taskRuntimeId } from "@rig/core/safe-identifiers";
|
|
9
|
+
import {
|
|
10
|
+
ISOLATION_BACKEND,
|
|
11
|
+
TASK_DATA_SERVICE_CAPABILITY
|
|
12
|
+
} from "@rig/contracts";
|
|
13
|
+
import { defineCapability } from "@rig/core/capability";
|
|
14
|
+
import { requireCapabilityForRoot } from "@rig/core/capability-loaders";
|
|
15
|
+
import { loadRuntimeContextFromEnv } from "@rig/core/runtime-context";
|
|
16
|
+
import { resolveCheckoutRoot as resolveMonorepoRoot } from "@rig/core/checkout-root";
|
|
17
|
+
|
|
18
|
+
// packages/provider-plugin/src/agent-harness/runtime-snapshot-config.ts
|
|
19
|
+
import { existsSync, readFileSync } from "fs";
|
|
20
|
+
import { resolve } from "path";
|
|
21
|
+
var DEFAULT_RUNTIME_SNAPSHOT = { enabled: true };
|
|
22
|
+
function loadRuntimeSnapshotConfig(projectRoot) {
|
|
23
|
+
const configPath = resolve(projectRoot, "rig/policy/policy.json");
|
|
24
|
+
if (!existsSync(configPath))
|
|
25
|
+
return { ...DEFAULT_RUNTIME_SNAPSHOT };
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
|
28
|
+
const runtimeSnapshot = parsed.runtime_snapshot;
|
|
29
|
+
if (runtimeSnapshot && typeof runtimeSnapshot === "object" && !Array.isArray(runtimeSnapshot)) {
|
|
30
|
+
const enabled = runtimeSnapshot.enabled;
|
|
31
|
+
if (typeof enabled === "boolean")
|
|
32
|
+
return { enabled };
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
return { ...DEFAULT_RUNTIME_SNAPSHOT };
|
|
36
|
+
}
|
|
37
|
+
return { ...DEFAULT_RUNTIME_SNAPSHOT };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// packages/provider-plugin/src/agent-harness/agent-wrapper.ts
|
|
41
|
+
import { buildPluginHostContext } from "@rig/core/plugin-host-context";
|
|
42
|
+
var taskDataServiceByRoot = new Map;
|
|
43
|
+
function resolveTaskDataService(projectRoot) {
|
|
44
|
+
let cached = taskDataServiceByRoot.get(projectRoot);
|
|
45
|
+
if (!cached) {
|
|
46
|
+
cached = requireCapabilityForRoot(projectRoot, defineCapability(TASK_DATA_SERVICE_CAPABILITY), `No TASK_DATA_SERVICE capability is registered for project root "${projectRoot}". ` + "Install @rig/task-sources-plugin (it ships in the default bundle) so the agent " + "executor can resolve task-state reads and lifecycle updates.");
|
|
47
|
+
taskDataServiceByRoot.set(projectRoot, cached);
|
|
48
|
+
}
|
|
49
|
+
return cached;
|
|
50
|
+
}
|
|
51
|
+
var requireFromRuntime = createRequire(import.meta.url);
|
|
52
|
+
async function finalizeRuntimeSnapshot(snapshotSidecar, providerCommand, exitCode, context) {
|
|
53
|
+
try {
|
|
54
|
+
await snapshotSidecar.finalize(providerCommand, exitCode);
|
|
55
|
+
return { ok: true };
|
|
56
|
+
} catch (error) {
|
|
57
|
+
emitWrapperEvent("runtime.snapshot.finalize.failed", {
|
|
58
|
+
runtimeId: context.runtimeId,
|
|
59
|
+
taskId: context.taskId,
|
|
60
|
+
exitCode,
|
|
61
|
+
error: error instanceof Error ? error.message : String(error)
|
|
62
|
+
});
|
|
63
|
+
console.error(`[rig-agent] Snapshot finalize failed for ${context.taskId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
64
|
+
if (exitCode !== 0) {
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
68
|
+
return { ok: false, snapshotMissing: true, error: message };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function startOptionalRuntimeSnapshotSidecar(runtime, startSidecar) {
|
|
72
|
+
try {
|
|
73
|
+
return await startSidecar(runtime, { instanceId: `${runtime.id}-wrapper` });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
emitWrapperEvent("runtime.snapshot.start.failed", {
|
|
76
|
+
runtimeId: runtime.id,
|
|
77
|
+
taskId: runtime.taskId,
|
|
78
|
+
error: error instanceof Error ? error.message : String(error)
|
|
79
|
+
});
|
|
80
|
+
console.error(`[rig-agent] Runtime snapshotting unavailable for ${runtime.taskId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function runAgentWrapper(options = {}) {
|
|
85
|
+
const projectRoot = resolve2(options.projectRoot || process.env.PROJECT_RIG_ROOT || process.cwd());
|
|
86
|
+
const monorepoRoot = resolveMonorepoRoot(projectRoot);
|
|
87
|
+
const argv = options.argv || process.argv.slice(2);
|
|
88
|
+
if (argv.length === 0 || argv[0] === "--version" || argv[0] === "--help" || argv[0] === "help") {
|
|
89
|
+
console.log("[rig-agent] dispatch ready");
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
const taskId = await resolveTaskId(projectRoot, monorepoRoot);
|
|
93
|
+
if (!taskId) {
|
|
94
|
+
console.error("[rig-agent] No active task ID found (checked env, session, beads).");
|
|
95
|
+
console.error("[rig-agent] Refusing to run unsandboxed. Ensure task runtime/session injection is active.");
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
const provider = resolveProvider();
|
|
99
|
+
await printTaskContext(monorepoRoot, taskId);
|
|
100
|
+
let runtime;
|
|
101
|
+
let isolationBackend;
|
|
102
|
+
try {
|
|
103
|
+
emitWrapperEvent("runtime.provision.started", {
|
|
104
|
+
projectRoot,
|
|
105
|
+
taskId,
|
|
106
|
+
mode: "worktree"
|
|
107
|
+
});
|
|
108
|
+
isolationBackend = await requireCapabilityForRoot(projectRoot, defineCapability(ISOLATION_BACKEND), `No ISOLATION_BACKEND capability is registered for project root "${projectRoot}". ` + "Install @rig/isolation-plugin (it ships in the default bundle) so runtime provisioning can resolve a backend.");
|
|
109
|
+
const taskRecordReader = taskRecordReaderFromEnv(taskId);
|
|
110
|
+
runtime = await isolationBackend.ensureAgentRuntime({
|
|
111
|
+
projectRoot,
|
|
112
|
+
id: taskRuntimeId(taskId),
|
|
113
|
+
taskId,
|
|
114
|
+
mode: "worktree",
|
|
115
|
+
provider,
|
|
116
|
+
...taskRecordReader ? { taskRecordReader } : {},
|
|
117
|
+
preserveTaskArtifacts: process.env.RIG_RUN_RESUME === "1" || process.env.RIG_RUNTIME_ARTIFACT_CLEANUP === "preserve"
|
|
118
|
+
});
|
|
119
|
+
emitWrapperEvent("runtime.provision.completed", {
|
|
120
|
+
runtimeId: runtime.id,
|
|
121
|
+
taskId,
|
|
122
|
+
workspaceDir: runtime.workspaceDir,
|
|
123
|
+
sessionDir: runtime.sessionDir,
|
|
124
|
+
logsDir: runtime.logsDir,
|
|
125
|
+
stateDir: runtime.stateDir,
|
|
126
|
+
contextFile: runtime.contextFile,
|
|
127
|
+
snapshotManaged: true
|
|
128
|
+
});
|
|
129
|
+
} catch (error) {
|
|
130
|
+
emitWrapperEvent("runtime.provision.failed", {
|
|
131
|
+
taskId,
|
|
132
|
+
error: error instanceof Error ? error.message : String(error)
|
|
133
|
+
});
|
|
134
|
+
console.error(`[rig-agent] Failed to provision runtime: ${error}`);
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
await waitForDirtyBaselineReady(runtime, taskId);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
emitWrapperEvent("runtime.baseline.failed", {
|
|
141
|
+
runtimeId: runtime.id,
|
|
142
|
+
taskId,
|
|
143
|
+
workspaceDir: runtime.workspaceDir,
|
|
144
|
+
error: error instanceof Error ? error.message : String(error)
|
|
145
|
+
});
|
|
146
|
+
console.error(`[rig-agent] Failed to apply selected baseline before provider launch: ${error}`);
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
const providerArgs = buildProviderArgs(provider, runtime, argv);
|
|
150
|
+
const providerCommand = [providerBinary(provider), ...providerArgs];
|
|
151
|
+
emitWrapperEvent("provider.launch", {
|
|
152
|
+
provider,
|
|
153
|
+
runtimeId: runtime.id,
|
|
154
|
+
taskId,
|
|
155
|
+
workspaceDir: runtime.workspaceDir,
|
|
156
|
+
command: providerCommand
|
|
157
|
+
});
|
|
158
|
+
const env = await isolationBackend.runtimeEnv(projectRoot, runtime);
|
|
159
|
+
const bypassOuterRuntimeSandbox = shouldBypassProviderSandboxOnPlatform(provider, process.platform);
|
|
160
|
+
if (provider === "pi") {
|
|
161
|
+
env.PI_CODING_AGENT_DIR = resolve2(runtime.homeDir, ".pi", "agent");
|
|
162
|
+
env.PI_CODING_AGENT_SESSION_DIR = runtime.sessionDir;
|
|
163
|
+
}
|
|
164
|
+
env.RIG_RUNTIME_SANDBOX = process.env.RIG_RUNTIME_SANDBOX?.trim() || "enforce";
|
|
165
|
+
const snapshotEnabled = loadRuntimeSnapshotConfig(projectRoot).enabled;
|
|
166
|
+
const snapshotSidecar = snapshotEnabled ? await startOptionalRuntimeSnapshotSidecar(runtime, (rt, opts) => isolationBackend.startRuntimeSnapshotSidecar(rt, opts)) : null;
|
|
167
|
+
let exitCode = 1;
|
|
168
|
+
let snapshotFinalizeResult = { ok: true };
|
|
169
|
+
try {
|
|
170
|
+
const command = bypassOuterRuntimeSandbox ? providerCommand : (await isolationBackend.wrapRuntimeSandbox({
|
|
171
|
+
projectRoot,
|
|
172
|
+
runtime: {
|
|
173
|
+
id: runtime.id,
|
|
174
|
+
mode: runtime.mode,
|
|
175
|
+
rootDir: runtime.rootDir,
|
|
176
|
+
workspaceDir: runtime.workspaceDir,
|
|
177
|
+
binDir: runtime.binDir,
|
|
178
|
+
homeDir: runtime.homeDir,
|
|
179
|
+
tmpDir: runtime.tmpDir,
|
|
180
|
+
cacheDir: runtime.cacheDir,
|
|
181
|
+
stateDir: runtime.stateDir,
|
|
182
|
+
sessionDir: runtime.sessionDir,
|
|
183
|
+
claudeHomeDir: runtime.claudeHomeDir
|
|
184
|
+
},
|
|
185
|
+
command: providerCommand
|
|
186
|
+
})).command;
|
|
187
|
+
if (isPiRpcArgs(providerArgs)) {
|
|
188
|
+
const prompt = await readProcessStdin();
|
|
189
|
+
const runId = process.env.RIG_SERVER_RUN_ID?.trim();
|
|
190
|
+
exitCode = await runPiRpcProviderFallback({
|
|
191
|
+
command,
|
|
192
|
+
cwd: runtime.workspaceDir,
|
|
193
|
+
env,
|
|
194
|
+
prompt,
|
|
195
|
+
...runId ? { runId } : {},
|
|
196
|
+
sessionName: runId ? `Rig ${runId}` : `Rig ${runtime.taskId}`
|
|
197
|
+
});
|
|
198
|
+
} else {
|
|
199
|
+
const proc = Bun.spawn(command, {
|
|
200
|
+
cwd: runtime.workspaceDir,
|
|
201
|
+
env,
|
|
202
|
+
stdin: "inherit",
|
|
203
|
+
stdout: "inherit",
|
|
204
|
+
stderr: "inherit"
|
|
205
|
+
});
|
|
206
|
+
exitCode = await proc.exited;
|
|
207
|
+
}
|
|
208
|
+
if (snapshotSidecar) {
|
|
209
|
+
snapshotFinalizeResult = await finalizeRuntimeSnapshot(snapshotSidecar, providerCommand, exitCode, {
|
|
210
|
+
runtimeId: runtime.id,
|
|
211
|
+
taskId
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (snapshotSidecar) {
|
|
216
|
+
await snapshotSidecar.cancel();
|
|
217
|
+
}
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
const serverManagedRun = Boolean(process.env.RIG_SERVER_RUN_ID?.trim());
|
|
221
|
+
const taskClosed = await isTaskClosed(monorepoRoot, taskId);
|
|
222
|
+
const finalExitCode = resolveFinalProviderExitCode({
|
|
223
|
+
providerExitCode: exitCode,
|
|
224
|
+
taskClosed,
|
|
225
|
+
serverManagedRun
|
|
226
|
+
});
|
|
227
|
+
const snapshotMissing = !snapshotFinalizeResult.ok;
|
|
228
|
+
const handoffRequired = exitCode !== 0 || snapshotMissing || !taskClosed && !serverManagedRun;
|
|
229
|
+
emitWrapperEvent("provider.completed", {
|
|
230
|
+
provider,
|
|
231
|
+
runtimeId: runtime.id,
|
|
232
|
+
taskId,
|
|
233
|
+
exitCode: finalExitCode,
|
|
234
|
+
providerExitCode: exitCode,
|
|
235
|
+
taskClosed,
|
|
236
|
+
serverManagedRun,
|
|
237
|
+
handoffRequired,
|
|
238
|
+
snapshotMissing,
|
|
239
|
+
...!snapshotFinalizeResult.ok ? { snapshotFinalizeError: snapshotFinalizeResult.error } : {}
|
|
240
|
+
});
|
|
241
|
+
if (handoffRequired) {
|
|
242
|
+
recordRuntimeHandoff(projectRoot, runtime, taskId, exitCode, {
|
|
243
|
+
snapshotMissing,
|
|
244
|
+
snapshotFinalizeError: snapshotFinalizeResult.ok ? null : snapshotFinalizeResult.error
|
|
245
|
+
});
|
|
246
|
+
if (!snapshotFinalizeResult.ok) {
|
|
247
|
+
await recordSnapshotMissingTaskSourceEvidence(projectRoot, taskId, runtime, snapshotFinalizeResult.error);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return finalExitCode;
|
|
251
|
+
}
|
|
252
|
+
function parseJsonRecord(line) {
|
|
253
|
+
try {
|
|
254
|
+
const parsed = JSON.parse(line);
|
|
255
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
256
|
+
} catch {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function readProcessStdin() {
|
|
261
|
+
if (process.stdin.isTTY)
|
|
262
|
+
return "";
|
|
263
|
+
return await new Promise((resolveRead) => {
|
|
264
|
+
let data = "";
|
|
265
|
+
process.stdin.setEncoding("utf8");
|
|
266
|
+
process.stdin.on("data", (chunk) => {
|
|
267
|
+
data += String(chunk);
|
|
268
|
+
});
|
|
269
|
+
process.stdin.on("end", () => resolveRead(data));
|
|
270
|
+
process.stdin.resume();
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
async function pumpReadableLines(stream, onLine) {
|
|
274
|
+
if (!stream)
|
|
275
|
+
return;
|
|
276
|
+
const reader = stream.getReader();
|
|
277
|
+
const decoder = new TextDecoder;
|
|
278
|
+
let buffer = "";
|
|
279
|
+
try {
|
|
280
|
+
while (true) {
|
|
281
|
+
const { done, value } = await reader.read();
|
|
282
|
+
if (done)
|
|
283
|
+
break;
|
|
284
|
+
buffer += decoder.decode(value, { stream: true });
|
|
285
|
+
const parts = buffer.split(/\r?\n/);
|
|
286
|
+
buffer = parts.pop() ?? "";
|
|
287
|
+
for (const part of parts)
|
|
288
|
+
onLine(part);
|
|
289
|
+
}
|
|
290
|
+
buffer += decoder.decode();
|
|
291
|
+
if (buffer)
|
|
292
|
+
onLine(buffer);
|
|
293
|
+
} finally {
|
|
294
|
+
reader.releaseLock();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function isBlockingPiRpcUiRequest(record) {
|
|
298
|
+
if (record.type !== "extension_ui_request")
|
|
299
|
+
return false;
|
|
300
|
+
return record.method === "select" || record.method === "confirm" || record.method === "input" || record.method === "editor";
|
|
301
|
+
}
|
|
302
|
+
function writeRpcCommand(stdin, command) {
|
|
303
|
+
stdin.write(`${JSON.stringify(command)}
|
|
304
|
+
`);
|
|
305
|
+
}
|
|
306
|
+
function joinUrl(baseUrl, pathname) {
|
|
307
|
+
return `${baseUrl.replace(/\/+$/, "")}${pathname.startsWith("/") ? pathname : `/${pathname}`}`;
|
|
308
|
+
}
|
|
309
|
+
async function readQueuedSteeringFromServer(input) {
|
|
310
|
+
if (!input.serverUrl || !input.runId)
|
|
311
|
+
return [];
|
|
312
|
+
const headers = {};
|
|
313
|
+
if (input.authToken)
|
|
314
|
+
headers.authorization = `Bearer ${input.authToken}`;
|
|
315
|
+
if (input.projectRoot)
|
|
316
|
+
headers["x-rig-project-root"] = input.projectRoot;
|
|
317
|
+
const response = await fetch(joinUrl(input.serverUrl, `/api/runs/${encodeURIComponent(input.runId)}/steering?ack=1`), { headers });
|
|
318
|
+
if (!response.ok) {
|
|
319
|
+
throw new Error(`steering read rejected (${response.status}) at ${input.serverUrl}`);
|
|
320
|
+
}
|
|
321
|
+
const payload = await response.json().catch(() => null);
|
|
322
|
+
const messages = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.messages : null;
|
|
323
|
+
return Array.isArray(messages) ? messages.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
324
|
+
}
|
|
325
|
+
function steeringMessageText(entry) {
|
|
326
|
+
const message = typeof entry.message === "string" ? entry.message.trim() : "";
|
|
327
|
+
return message || null;
|
|
328
|
+
}
|
|
329
|
+
function isPiRpcArgs(args) {
|
|
330
|
+
return cliOptionValue(args, "--mode") === "rpc";
|
|
331
|
+
}
|
|
332
|
+
async function runPiRpcProviderFallback(input) {
|
|
333
|
+
const stdout = input.stdout ?? process.stdout;
|
|
334
|
+
const stderr = input.stderr ?? process.stderr;
|
|
335
|
+
const proc = Bun.spawn(input.command, {
|
|
336
|
+
cwd: input.cwd,
|
|
337
|
+
env: input.env,
|
|
338
|
+
stdin: "pipe",
|
|
339
|
+
stdout: "pipe",
|
|
340
|
+
stderr: "pipe"
|
|
341
|
+
});
|
|
342
|
+
let sawAgentEnd = false;
|
|
343
|
+
let promptError = null;
|
|
344
|
+
let stdinOpen = true;
|
|
345
|
+
let steeringPollStopped = false;
|
|
346
|
+
const closeStdin = () => {
|
|
347
|
+
if (!stdinOpen)
|
|
348
|
+
return;
|
|
349
|
+
stdinOpen = false;
|
|
350
|
+
try {
|
|
351
|
+
steeringPollStopped = true;
|
|
352
|
+
proc.stdin.end();
|
|
353
|
+
} catch {}
|
|
354
|
+
};
|
|
355
|
+
const send = (command) => {
|
|
356
|
+
if (!stdinOpen)
|
|
357
|
+
return;
|
|
358
|
+
try {
|
|
359
|
+
writeRpcCommand(proc.stdin, command);
|
|
360
|
+
} catch (error) {
|
|
361
|
+
promptError ??= error instanceof Error ? error.message : String(error);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
const forwardSigterm = () => {
|
|
365
|
+
try {
|
|
366
|
+
proc.kill("SIGTERM");
|
|
367
|
+
} catch {}
|
|
368
|
+
};
|
|
369
|
+
process.once("SIGTERM", forwardSigterm);
|
|
370
|
+
const pollSteering = async () => {
|
|
371
|
+
const serverUrl = input.env.RIG_SERVER_URL || input.env.RIG_SERVER_BASE_URL;
|
|
372
|
+
const authToken = input.env.RIG_AUTH_TOKEN || input.env.RIG_SERVER_AUTH_TOKEN;
|
|
373
|
+
const projectRoot = input.env.PROJECT_RIG_ROOT || input.env.RIG_HOST_PROJECT_ROOT || process.env.PROJECT_RIG_ROOT;
|
|
374
|
+
let lastReportedPollError = null;
|
|
375
|
+
while (!steeringPollStopped && stdinOpen) {
|
|
376
|
+
try {
|
|
377
|
+
const messages = await readQueuedSteeringFromServer({
|
|
378
|
+
...serverUrl ? { serverUrl } : {},
|
|
379
|
+
...authToken ? { authToken } : {},
|
|
380
|
+
...input.runId ? { runId: input.runId } : {},
|
|
381
|
+
...projectRoot ? { projectRoot } : {}
|
|
382
|
+
});
|
|
383
|
+
lastReportedPollError = null;
|
|
384
|
+
for (const message of messages) {
|
|
385
|
+
const text = steeringMessageText(message);
|
|
386
|
+
if (!text || !stdinOpen)
|
|
387
|
+
continue;
|
|
388
|
+
send({
|
|
389
|
+
id: typeof message.id === "string" ? `rig_steer_${message.id}` : `rig_steer_${Date.now()}`,
|
|
390
|
+
type: "prompt",
|
|
391
|
+
message: text,
|
|
392
|
+
streamingBehavior: "steer"
|
|
393
|
+
});
|
|
394
|
+
emitWrapperEvent("pi.rpc.steering.delivered", {
|
|
395
|
+
runId: input.runId ?? null,
|
|
396
|
+
steeringId: typeof message.id === "string" ? message.id : null,
|
|
397
|
+
actor: typeof message.actor === "string" ? message.actor : "operator",
|
|
398
|
+
message: text
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
} catch (error) {
|
|
402
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
403
|
+
if (message !== lastReportedPollError) {
|
|
404
|
+
lastReportedPollError = message;
|
|
405
|
+
emitWrapperEvent("pi.rpc.steering.poll.failed", {
|
|
406
|
+
runId: input.runId ?? null,
|
|
407
|
+
error: message
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
await sleep(1000);
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
const stdoutPump = pumpReadableLines(proc.stdout, (line) => {
|
|
415
|
+
stdout.write(`${line}
|
|
416
|
+
`);
|
|
417
|
+
const record = parseJsonRecord(line.trim());
|
|
418
|
+
if (!record)
|
|
419
|
+
return;
|
|
420
|
+
if (record.type === "agent_end") {
|
|
421
|
+
sawAgentEnd = true;
|
|
422
|
+
closeStdin();
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (record.type === "response" && record.command === "prompt" && record.success === false) {
|
|
426
|
+
promptError = typeof record.error === "string" ? record.error : "Pi RPC prompt failed.";
|
|
427
|
+
closeStdin();
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (isBlockingPiRpcUiRequest(record)) {
|
|
431
|
+
const id = typeof record.id === "string" ? record.id : "";
|
|
432
|
+
if (id) {
|
|
433
|
+
send({ type: "extension_ui_response", id, cancelled: true });
|
|
434
|
+
emitWrapperEvent("pi.rpc.extension_ui.cancelled", {
|
|
435
|
+
id,
|
|
436
|
+
method: record.method,
|
|
437
|
+
reason: "noninteractive Rig worker RPC session"
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
const stderrPump = pumpReadableLines(proc.stderr, (line) => {
|
|
443
|
+
stderr.write(`${line}
|
|
444
|
+
`);
|
|
445
|
+
});
|
|
446
|
+
if (input.sessionName?.trim()) {
|
|
447
|
+
send({ id: "rig_set_session_name", type: "set_session_name", name: input.sessionName.trim() });
|
|
448
|
+
}
|
|
449
|
+
const steeringPollPromise = pollSteering();
|
|
450
|
+
if (input.prompt.trim()) {
|
|
451
|
+
send({ id: "rig_initial_prompt", type: "prompt", message: input.prompt });
|
|
452
|
+
emitWrapperEvent("pi.rpc.prompt.sent", {
|
|
453
|
+
runId: input.runId ?? null,
|
|
454
|
+
kind: "initial",
|
|
455
|
+
bytes: Buffer.byteLength(input.prompt)
|
|
456
|
+
});
|
|
457
|
+
} else {
|
|
458
|
+
closeStdin();
|
|
459
|
+
}
|
|
460
|
+
const exitCode = await proc.exited;
|
|
461
|
+
process.off("SIGTERM", forwardSigterm);
|
|
462
|
+
steeringPollStopped = true;
|
|
463
|
+
await Promise.all([stdoutPump, stderrPump, steeringPollPromise]);
|
|
464
|
+
if (promptError) {
|
|
465
|
+
stderr.write(`[rig-agent] Pi RPC prompt failed: ${promptError}
|
|
466
|
+
`);
|
|
467
|
+
return exitCode === 0 ? 1 : exitCode;
|
|
468
|
+
}
|
|
469
|
+
if (input.prompt.trim() && !sawAgentEnd && exitCode === 0) {
|
|
470
|
+
stderr.write(`[rig-agent] Pi RPC exited before emitting agent_end.
|
|
471
|
+
`);
|
|
472
|
+
return 1;
|
|
473
|
+
}
|
|
474
|
+
return exitCode;
|
|
475
|
+
}
|
|
476
|
+
var runPiRpcProvider = runPiRpcProviderFallback;
|
|
477
|
+
function resolveFinalProviderExitCode(input) {
|
|
478
|
+
if (input.providerExitCode !== 0) {
|
|
479
|
+
return input.providerExitCode;
|
|
480
|
+
}
|
|
481
|
+
if (input.taskClosed || input.serverManagedRun) {
|
|
482
|
+
return 0;
|
|
483
|
+
}
|
|
484
|
+
return 0;
|
|
485
|
+
}
|
|
486
|
+
function shouldBypassProviderSandboxOnPlatform(provider, platform) {
|
|
487
|
+
if (platform !== "darwin") {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
return provider === "pi";
|
|
491
|
+
}
|
|
492
|
+
function buildProviderArgs(_provider, _runtime, argv) {
|
|
493
|
+
const piArgs = argv.filter((arg) => arg !== "__rig_pi_session_daemon__");
|
|
494
|
+
const piProvider = cliOptionValue(piArgs, "--provider") || process.env.RIG_PI_PROVIDER?.trim() || "openai-codex";
|
|
495
|
+
if (!hasCliOption(piArgs, "--provider")) {
|
|
496
|
+
piArgs.unshift(piProvider);
|
|
497
|
+
piArgs.unshift("--provider");
|
|
498
|
+
}
|
|
499
|
+
const model = cliOptionValue(piArgs, "--model") || process.env.RIG_PI_MODEL?.trim() || "gpt-5.5";
|
|
500
|
+
if (hasCliOption(piArgs, "--model")) {
|
|
501
|
+
rewriteCliOptionValue(piArgs, "--model", normalizePiModelForProvider(model, piProvider));
|
|
502
|
+
} else {
|
|
503
|
+
piArgs.unshift(normalizePiModelForProvider(model, piProvider));
|
|
504
|
+
piArgs.unshift("--model");
|
|
505
|
+
}
|
|
506
|
+
if (!hasCliOption(piArgs, "--mode")) {
|
|
507
|
+
piArgs.push("--mode", process.env.RIG_PI_TRANSPORT?.trim() === "print" ? "json" : "rpc");
|
|
508
|
+
if (process.env.RIG_PI_TRANSPORT?.trim() === "print" && !hasCliOption(piArgs, "--print")) {
|
|
509
|
+
piArgs.push("--print");
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return piArgs;
|
|
513
|
+
}
|
|
514
|
+
function taskRecordReaderFromEnv(taskId) {
|
|
515
|
+
const raw = process.env.RIG_SOURCE_TASK_JSON?.trim();
|
|
516
|
+
if (!raw) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
try {
|
|
520
|
+
const parsed = JSON.parse(raw);
|
|
521
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const task = parsed;
|
|
525
|
+
if (typeof task.id !== "string" || task.id !== taskId) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
async listTasks() {
|
|
530
|
+
return [task];
|
|
531
|
+
},
|
|
532
|
+
async getTask(id) {
|
|
533
|
+
return id === task.id ? task : null;
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
} catch {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async function runBeadsJson(projectRoot, args) {
|
|
541
|
+
const output = await Bun.$`br --no-db ${args} --json`.cwd(projectRoot).quiet().nothrow();
|
|
542
|
+
if (output.exitCode !== 0) {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
return await output.json();
|
|
547
|
+
} catch {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
function resolveProvider() {
|
|
552
|
+
return "pi";
|
|
553
|
+
}
|
|
554
|
+
function hasCliOption(argv, option) {
|
|
555
|
+
return argv.some((arg) => arg === option || arg.startsWith(`${option}=`));
|
|
556
|
+
}
|
|
557
|
+
function cliOptionValue(argv, option) {
|
|
558
|
+
for (let index = 0;index < argv.length; index += 1) {
|
|
559
|
+
const arg = argv[index];
|
|
560
|
+
if (arg === option) {
|
|
561
|
+
const next = argv[index + 1];
|
|
562
|
+
return next && !next.startsWith("--") ? next : undefined;
|
|
563
|
+
}
|
|
564
|
+
if (arg?.startsWith(`${option}=`)) {
|
|
565
|
+
return arg.slice(option.length + 1);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
function rewriteCliOptionValue(argv, option, value) {
|
|
571
|
+
for (let index = 0;index < argv.length; index += 1) {
|
|
572
|
+
const arg = argv[index];
|
|
573
|
+
if (arg === option && argv[index + 1] && !argv[index + 1].startsWith("--")) {
|
|
574
|
+
argv[index + 1] = value;
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (arg?.startsWith(`${option}=`)) {
|
|
578
|
+
argv[index] = `${option}=${value}`;
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function normalizePiModelForProvider(model, provider) {
|
|
584
|
+
if (provider === "openrouter" && model === "openai-codex/gpt-5.5") {
|
|
585
|
+
return "openai/gpt-5.5";
|
|
586
|
+
}
|
|
587
|
+
return model;
|
|
588
|
+
}
|
|
589
|
+
function resolveFromShellPath(binary) {
|
|
590
|
+
const resolved = Bun.spawnSync(["sh", "-lc", `command -v ${binary}`], {
|
|
591
|
+
stdout: "pipe",
|
|
592
|
+
stderr: "ignore",
|
|
593
|
+
stdin: "ignore",
|
|
594
|
+
env: process.env
|
|
595
|
+
});
|
|
596
|
+
if (resolved.exitCode !== 0)
|
|
597
|
+
return null;
|
|
598
|
+
const path = resolved.stdout.toString().trim().split(/\r?\n/)[0]?.trim();
|
|
599
|
+
return path || null;
|
|
600
|
+
}
|
|
601
|
+
function resolveBundledPiBinary() {
|
|
602
|
+
try {
|
|
603
|
+
const packageJson = requireFromRuntime.resolve("@oh-my-pi/pi-coding-agent/package.json");
|
|
604
|
+
const binaryPath = resolve2(packageJson, "..", "dist", "cli.js");
|
|
605
|
+
return existsSync2(binaryPath) ? binaryPath : null;
|
|
606
|
+
} catch {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function providerBinary(_provider) {
|
|
611
|
+
return process.env.RIG_PI_BINARY?.trim() || resolveBundledPiBinary() || resolveFromShellPath("pi") || Bun.which("pi") || "pi";
|
|
612
|
+
}
|
|
613
|
+
function emitWrapperEvent(type, payload) {
|
|
614
|
+
console.log(`__RIG_WRAPPER_EVENT__${JSON.stringify({ type, payload, at: new Date().toISOString() })}`);
|
|
615
|
+
}
|
|
616
|
+
function sleep(ms) {
|
|
617
|
+
return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|
|
618
|
+
}
|
|
619
|
+
async function waitForDirtyBaselineReady(runtime, taskId) {
|
|
620
|
+
const readyFile = process.env.RIG_DIRTY_BASELINE_READY_FILE?.trim();
|
|
621
|
+
if (process.env.RIG_BASELINE_MODE !== "dirty-snapshot" || !readyFile)
|
|
622
|
+
return;
|
|
623
|
+
const timeoutMs = Number.parseInt(process.env.RIG_DIRTY_BASELINE_TIMEOUT_MS ?? "30000", 10);
|
|
624
|
+
const deadline = Date.now() + (Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 30000);
|
|
625
|
+
emitWrapperEvent("runtime.baseline.waiting", {
|
|
626
|
+
runtimeId: runtime.id,
|
|
627
|
+
taskId,
|
|
628
|
+
workspaceDir: runtime.workspaceDir,
|
|
629
|
+
readyFile
|
|
630
|
+
});
|
|
631
|
+
while (!existsSync2(readyFile)) {
|
|
632
|
+
if (Date.now() >= deadline) {
|
|
633
|
+
throw new Error(`Timed out waiting for dirty baseline ready file: ${readyFile}`);
|
|
634
|
+
}
|
|
635
|
+
await sleep(50);
|
|
636
|
+
}
|
|
637
|
+
emitWrapperEvent("runtime.baseline.completed", {
|
|
638
|
+
runtimeId: runtime.id,
|
|
639
|
+
taskId,
|
|
640
|
+
workspaceDir: runtime.workspaceDir,
|
|
641
|
+
readyFile
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
async function resolveTaskId(projectRoot, monorepoRoot) {
|
|
645
|
+
if (process.env.RIG_TASK_ID) {
|
|
646
|
+
return process.env.RIG_TASK_ID;
|
|
647
|
+
}
|
|
648
|
+
const fromState = (await resolveTaskDataService(projectRoot)).currentTaskId(projectRoot);
|
|
649
|
+
if (fromState) {
|
|
650
|
+
return fromState;
|
|
651
|
+
}
|
|
652
|
+
return resolveTaskFromBeads(monorepoRoot);
|
|
653
|
+
}
|
|
654
|
+
async function resolveTaskFromBeads(projectRoot, readBeadsJson = runBeadsJson) {
|
|
655
|
+
const inProgress = await readBeadsJson(projectRoot, ["list", "--status", "in_progress"]);
|
|
656
|
+
if (inProgress) {
|
|
657
|
+
const task = inProgress.find((t) => t.issue_type === "task");
|
|
658
|
+
if (task?.id) {
|
|
659
|
+
return task.id;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const ready = await readBeadsJson(projectRoot, ["ready"]);
|
|
663
|
+
if (ready) {
|
|
664
|
+
const task = ready.find((t) => t.issue_type === "task");
|
|
665
|
+
if (task?.id) {
|
|
666
|
+
return task.id;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return "";
|
|
670
|
+
}
|
|
671
|
+
async function isTaskClosed(projectRoot, taskId, readBeadsJson = runBeadsJson) {
|
|
672
|
+
const pluginStatus = await readPluginTaskStatus(projectRoot, taskId);
|
|
673
|
+
if (isTerminalTaskStatus(pluginStatus)) {
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
const parsed = await readBeadsJson(projectRoot, ["show", taskId]);
|
|
677
|
+
const record = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
678
|
+
if (record && typeof record === "object" && !Array.isArray(record)) {
|
|
679
|
+
return isTerminalTaskStatus(record.status);
|
|
680
|
+
}
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
function isTerminalTaskStatus(status) {
|
|
684
|
+
return status === "closed" || status === "CLOSED" || status === "completed" || status === "cancelled" || status === "blocked";
|
|
685
|
+
}
|
|
686
|
+
async function readPluginTaskStatus(projectRoot, taskId) {
|
|
687
|
+
const taskData = await resolveTaskDataService(projectRoot);
|
|
688
|
+
try {
|
|
689
|
+
const ctx = await buildPluginHostContext(projectRoot);
|
|
690
|
+
const [source] = ctx?.taskSourceRegistry.list() ?? [];
|
|
691
|
+
if (!source?.get) {
|
|
692
|
+
return taskData.readSourceAwareTaskStatus(projectRoot, taskId);
|
|
693
|
+
}
|
|
694
|
+
return (await source.get(taskId))?.status ?? taskData.readSourceAwareTaskStatus(projectRoot, taskId);
|
|
695
|
+
} catch {
|
|
696
|
+
return taskData.readSourceAwareTaskStatus(projectRoot, taskId);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
async function recordSnapshotMissingTaskSourceEvidence(projectRoot, taskId, runtime, snapshotFinalizeError) {
|
|
700
|
+
const taskData = await resolveTaskDataService(projectRoot);
|
|
701
|
+
const comment = taskData.buildTaskRunLifecycleComment({
|
|
702
|
+
runId: process.env.RIG_SERVER_RUN_ID || "(unknown)",
|
|
703
|
+
status: "snapshot-missing",
|
|
704
|
+
summary: "Rig runtime completed, but the after-snapshot finalize failed. Closeout evidence is degraded until the runtime workspace is inspected directly.",
|
|
705
|
+
runtimeWorkspace: runtime.workspaceDir,
|
|
706
|
+
logsDir: runtime.logsDir,
|
|
707
|
+
sessionDir: runtime.sessionDir,
|
|
708
|
+
errorText: snapshotFinalizeError
|
|
709
|
+
});
|
|
710
|
+
try {
|
|
711
|
+
const result = await taskData.updateConfiguredTaskSourceTask(projectRoot, {
|
|
712
|
+
taskId,
|
|
713
|
+
update: { comment }
|
|
714
|
+
});
|
|
715
|
+
if (result.updated) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
throw new Error(result.source === "plugin" || result.sourceKind ? `configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} does not support comment updates` : "no source-aware task source is configured");
|
|
719
|
+
} catch (error) {
|
|
720
|
+
let fallbackUpdated = false;
|
|
721
|
+
try {
|
|
722
|
+
const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
|
|
723
|
+
fallbackUpdated = taskData.updateGithubIssueTaskBySourceIssueId(sourceIssueId, taskId, { comment });
|
|
724
|
+
fallbackUpdated = taskData.updateSourceAwareTaskConfigTask(projectRoot, taskId, { comment }) || fallbackUpdated;
|
|
725
|
+
} catch (fallbackError) {
|
|
726
|
+
console.error(`[rig-agent] Snapshot-missing compatibility comment also failed for ${taskId}: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
|
|
727
|
+
}
|
|
728
|
+
const message = `[rig-agent] Failed to record snapshot-missing evidence for ${taskId}${fallbackUpdated ? "; applied source-aware compatibility comment" : ""}: ${error instanceof Error ? error.message : String(error)}`;
|
|
729
|
+
if (fallbackUpdated) {
|
|
730
|
+
console.log(message);
|
|
731
|
+
} else {
|
|
732
|
+
console.error(message);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
async function updateTaskSourceAfterRun(projectRoot, taskId, runtime) {
|
|
737
|
+
const taskData = await resolveTaskDataService(projectRoot);
|
|
738
|
+
const comment = taskData.buildTaskRunLifecycleComment({
|
|
739
|
+
runId: process.env.RIG_SERVER_RUN_ID || "(unknown)",
|
|
740
|
+
status: "completed",
|
|
741
|
+
summary: "Rig task run completed; source closeout waits for approved PR merge.",
|
|
742
|
+
runtimeWorkspace: runtime.workspaceDir,
|
|
743
|
+
logsDir: runtime.logsDir,
|
|
744
|
+
sessionDir: runtime.sessionDir
|
|
745
|
+
});
|
|
746
|
+
try {
|
|
747
|
+
const result = await taskData.updateConfiguredTaskSourceTask(projectRoot, {
|
|
748
|
+
taskId,
|
|
749
|
+
update: { status: "completed", comment }
|
|
750
|
+
});
|
|
751
|
+
if (result.updated) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
throw new Error(result.source === "plugin" || result.sourceKind ? `configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} does not support lifecycle updates` : "no source-aware task source is configured");
|
|
755
|
+
} catch (error) {
|
|
756
|
+
let fallbackUpdated = false;
|
|
757
|
+
try {
|
|
758
|
+
const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
|
|
759
|
+
fallbackUpdated = taskData.updateGithubIssueTaskBySourceIssueId(sourceIssueId, taskId, { status: "completed", comment });
|
|
760
|
+
fallbackUpdated = taskData.updateSourceAwareTaskConfigTask(projectRoot, taskId, { status: "completed", comment }) || fallbackUpdated;
|
|
761
|
+
} catch (fallbackError) {
|
|
762
|
+
console.error(`[rig-agent] Source-aware compatibility update also failed for ${taskId}: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
|
|
763
|
+
}
|
|
764
|
+
const message = `[rig-agent] Failed to update task source for ${taskId}${fallbackUpdated ? "; applied source-aware compatibility update" : ""}: ${error instanceof Error ? error.message : String(error)}`;
|
|
765
|
+
if (fallbackUpdated) {
|
|
766
|
+
console.log(message);
|
|
767
|
+
} else {
|
|
768
|
+
console.error(message);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function resolveRuntimeHandoffPath(hostProjectRoot, taskId, timestampMs = Date.now()) {
|
|
773
|
+
const handoffDir = resolve2(hostProjectRoot, ".rig/runtime/handoffs");
|
|
774
|
+
const safeTaskId = safePathSegment(taskId, { fallback: "task", maxLength: 96 });
|
|
775
|
+
return assertPathInsideRoot(handoffDir, resolve2(handoffDir, `${safeTaskId}-${timestampMs}.json`), "runtime handoff path");
|
|
776
|
+
}
|
|
777
|
+
function recordRuntimeHandoff(hostProjectRoot, runtime, taskId, exitCode, options = {}) {
|
|
778
|
+
const handoffDir = resolve2(hostProjectRoot, ".rig/runtime/handoffs");
|
|
779
|
+
mkdirSync(handoffDir, { recursive: true });
|
|
780
|
+
const handoffPath = resolveRuntimeHandoffPath(hostProjectRoot, taskId);
|
|
781
|
+
const snapshotMissing = options.snapshotMissing === true;
|
|
782
|
+
const handoff = {
|
|
783
|
+
taskId,
|
|
784
|
+
runtimeId: runtime.id,
|
|
785
|
+
runtimeMode: runtime.mode,
|
|
786
|
+
workspaceDir: runtime.workspaceDir,
|
|
787
|
+
logsDir: runtime.logsDir,
|
|
788
|
+
sessionDir: runtime.sessionDir,
|
|
789
|
+
exitCode,
|
|
790
|
+
degraded: snapshotMissing,
|
|
791
|
+
snapshotMissing,
|
|
792
|
+
snapshotFinalizeError: options.snapshotFinalizeError ?? null,
|
|
793
|
+
recordedAt: new Date().toISOString(),
|
|
794
|
+
message: snapshotMissing ? "Runtime completed but the after-snapshot finalize failed; closeout evidence is degraded until the runtime workspace is inspected directly." : "Completion verification did not finish in this run. Open or refresh the PR from the runtime workspace for verifier review, or rerun completion verification once the workspace is ready.",
|
|
795
|
+
nextSteps: snapshotMissing ? [
|
|
796
|
+
`cd ${runtime.workspaceDir}`,
|
|
797
|
+
"inspect the runtime workspace directly before relying on closeout evidence",
|
|
798
|
+
`rig git status --task ${taskId}`
|
|
799
|
+
] : [
|
|
800
|
+
`cd ${runtime.workspaceDir}`,
|
|
801
|
+
`rig git status --task ${taskId}`,
|
|
802
|
+
`rig completion-verification --task ${taskId}`,
|
|
803
|
+
`rig git open-pr --task ${taskId}`
|
|
804
|
+
]
|
|
805
|
+
};
|
|
806
|
+
writeFileSync(handoffPath, `${JSON.stringify(handoff, null, 2)}
|
|
807
|
+
`, "utf-8");
|
|
808
|
+
if (snapshotMissing) {
|
|
809
|
+
console.log(`[rig-agent] Runtime after-snapshot missing for ${taskId}; degraded handoff saved: ${handoffPath}`);
|
|
810
|
+
console.log(`[rig-agent] Inspect runtime workspace before relying on closeout evidence: ${runtime.workspaceDir}`);
|
|
811
|
+
} else {
|
|
812
|
+
console.log(`[rig-agent] Completion verification paused for ${taskId}.`);
|
|
813
|
+
console.log(`[rig-agent] Runtime handoff saved: ${handoffPath}`);
|
|
814
|
+
console.log(`[rig-agent] Review and open PR from runtime workspace: ${runtime.workspaceDir}`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
async function printTaskContext(projectRoot, taskId) {
|
|
818
|
+
const metadata = await readTaskMetadata(projectRoot, taskId);
|
|
819
|
+
const configHints = await readTaskConfigHints(projectRoot, taskId);
|
|
820
|
+
const description = firstNonEmpty(metadata?.description, configHints.description);
|
|
821
|
+
const acceptanceCriteria = firstNonEmpty(metadata?.acceptanceCriteria, configHints.acceptanceCriteria);
|
|
822
|
+
if (metadata?.title) {
|
|
823
|
+
console.log(`[rig-agent] Task ${taskId}: ${metadata.title}`);
|
|
824
|
+
} else {
|
|
825
|
+
console.log(`[rig-agent] Task ${taskId}`);
|
|
826
|
+
}
|
|
827
|
+
if (description) {
|
|
828
|
+
console.log("[rig-agent] Description:");
|
|
829
|
+
for (const line of description.split(/\r?\n/)) {
|
|
830
|
+
console.log(`[rig-agent] ${line}`);
|
|
831
|
+
}
|
|
832
|
+
} else {
|
|
833
|
+
console.log(`[rig-agent] Description: (not set; use \`br update ${taskId} --description "..."\`)`);
|
|
834
|
+
}
|
|
835
|
+
if (acceptanceCriteria) {
|
|
836
|
+
console.log("[rig-agent] Acceptance Criteria:");
|
|
837
|
+
for (const line of acceptanceCriteria.split(/\r?\n/)) {
|
|
838
|
+
console.log(`[rig-agent] ${line}`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
async function readTaskMetadata(taskRoot, taskId) {
|
|
843
|
+
const parsed = await runBeadsJson(taskRoot, ["show", taskId]);
|
|
844
|
+
if (!parsed) {
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
const record = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
848
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
const value = record;
|
|
852
|
+
return {
|
|
853
|
+
title: asNonEmptyString(value.title),
|
|
854
|
+
description: asNonEmptyString(value.description),
|
|
855
|
+
acceptanceCriteria: firstNonEmpty(asNonEmptyString(value.acceptance_criteria), asNonEmptyString(value.acceptanceCriteria))
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
async function readTaskConfigHints(taskRoot, taskId) {
|
|
859
|
+
const runtimeContext = loadRuntimeContextFromEnv();
|
|
860
|
+
const candidates = [
|
|
861
|
+
runtimeContext?.monorepoMainRoot ? resolve2(runtimeContext.monorepoMainRoot, ".rig", "task-config.json") : "",
|
|
862
|
+
process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve2(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
|
|
863
|
+
resolve2(taskRoot, ".rig", "task-config.json"),
|
|
864
|
+
resolve2(taskRoot, "rig", "task-config.json")
|
|
865
|
+
].filter(Boolean);
|
|
866
|
+
for (const configPath of candidates) {
|
|
867
|
+
if (!existsSync2(configPath)) {
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
try {
|
|
871
|
+
const config = await Bun.file(configPath).json();
|
|
872
|
+
const entry = config[taskId] || {};
|
|
873
|
+
return {
|
|
874
|
+
description: asNonEmptyString(entry.description),
|
|
875
|
+
acceptanceCriteria: asNonEmptyString(entry.acceptance_criteria)
|
|
876
|
+
};
|
|
877
|
+
} catch {
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return { description: "", acceptanceCriteria: "" };
|
|
882
|
+
}
|
|
883
|
+
function asNonEmptyString(value) {
|
|
884
|
+
return typeof value === "string" ? value.trim() : "";
|
|
885
|
+
}
|
|
886
|
+
function firstNonEmpty(...values) {
|
|
887
|
+
for (const value of values) {
|
|
888
|
+
if (typeof value === "string" && value.trim()) {
|
|
889
|
+
return value.trim();
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return "";
|
|
893
|
+
}
|
|
894
|
+
if (import.meta.main) {
|
|
895
|
+
runAgentWrapper().then((code) => process.exit(code)).catch((error) => {
|
|
896
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
897
|
+
process.exit(1);
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
export {
|
|
901
|
+
updateTaskSourceAfterRun,
|
|
902
|
+
startOptionalRuntimeSnapshotSidecar,
|
|
903
|
+
shouldBypassProviderSandboxOnPlatform,
|
|
904
|
+
runPiRpcProviderFallback,
|
|
905
|
+
runPiRpcProvider,
|
|
906
|
+
runAgentWrapper,
|
|
907
|
+
resolveTaskFromBeads,
|
|
908
|
+
resolveRuntimeHandoffPath,
|
|
909
|
+
resolveProvider,
|
|
910
|
+
resolveFinalProviderExitCode,
|
|
911
|
+
recordSnapshotMissingTaskSourceEvidence,
|
|
912
|
+
recordRuntimeHandoff,
|
|
913
|
+
isTaskClosed,
|
|
914
|
+
finalizeRuntimeSnapshot,
|
|
915
|
+
buildProviderArgs
|
|
916
|
+
};
|