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