@h-rig/runtime 0.0.6-alpha.32 → 0.0.6-alpha.33
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.js +12 -0
- package/dist/bin/rig-agent.js +12 -14
- package/dist/src/control-plane/agent-wrapper.js +12 -0
- package/dist/src/control-plane/harness-main.js +1353 -1398
- package/dist/src/control-plane/hooks/completion-verification.js +510 -511
- package/dist/src/control-plane/hooks/inject-context.js +333 -333
- package/dist/src/control-plane/hooks/submodule-branch.js +1600 -1600
- package/dist/src/control-plane/hooks/task-runtime-start.js +1600 -1600
- package/dist/src/control-plane/native/git-ops.js +78 -151
- package/dist/src/control-plane/native/harness-cli.js +1353 -1398
- package/dist/src/control-plane/native/repo-ops.js +50 -50
- package/dist/src/control-plane/native/task-ops.js +1467 -1503
- package/dist/src/control-plane/native/verifier.js +34 -34
- package/dist/src/control-plane/pi-sessiond/bin.js +3 -2
- package/dist/src/control-plane/pi-sessiond/launcher.js +12 -0
- package/dist/src/control-plane/pi-sessiond/server.js +3 -2
- package/dist/src/control-plane/pi-sessiond/session-service.js +3 -2
- package/package.json +8 -8
|
@@ -25,1631 +25,1639 @@ function readBuildConfig() {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// packages/runtime/src/control-plane/
|
|
29
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
];
|
|
45
|
-
var runtimeContextArrayFields = ["scopes", "validation"];
|
|
46
|
-
var runtimeContextOptionalStringFields = [
|
|
47
|
-
"artifactRoot",
|
|
48
|
-
"hostProjectRoot",
|
|
49
|
-
"monorepoMainRoot",
|
|
50
|
-
"monorepoBaseRef",
|
|
51
|
-
"monorepoBaseCommit"
|
|
52
|
-
];
|
|
53
|
-
function loadRuntimeContext(path) {
|
|
54
|
-
const absPath = resolve(path);
|
|
55
|
-
if (!existsSync(absPath)) {
|
|
56
|
-
throw new Error(`RuntimeTaskContext file not found: ${absPath}`);
|
|
57
|
-
}
|
|
58
|
-
let raw;
|
|
28
|
+
// packages/runtime/src/control-plane/native/git-native.ts
|
|
29
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
30
|
+
import { tmpdir } from "os";
|
|
31
|
+
import { dirname, isAbsolute, resolve } from "path";
|
|
32
|
+
import { createHash } from "crypto";
|
|
33
|
+
function isTextTreeCommitUpdate(update) {
|
|
34
|
+
return typeof update.content === "string";
|
|
35
|
+
}
|
|
36
|
+
var sharedGitNativeOutputDir = resolve(tmpdir(), "rig-native");
|
|
37
|
+
var sharedGitNativeOutputPath = resolve(sharedGitNativeOutputDir, `rig-git-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
38
|
+
var trackerCommandUsageProbe = "usage: rig-git fetch-ref <repo-path> <remote> <branch>";
|
|
39
|
+
function temporaryGitBinaryOutputPath(outputPath) {
|
|
40
|
+
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
41
|
+
return resolve(dirname(outputPath), `.rig-git-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}${suffix}`);
|
|
42
|
+
}
|
|
43
|
+
function publishGitBinary(tempOutputPath, outputPath) {
|
|
59
44
|
try {
|
|
60
|
-
|
|
61
|
-
} catch (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
const obj = raw;
|
|
68
|
-
for (const field of runtimeContextStringFields) {
|
|
69
|
-
if (typeof obj[field] !== "string" || obj[field].length === 0) {
|
|
70
|
-
throw new Error(`RuntimeTaskContext field "${field}" must be a non-empty string (at ${absPath})`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
for (const field of runtimeContextArrayFields) {
|
|
74
|
-
if (!Array.isArray(obj[field])) {
|
|
75
|
-
throw new Error(`RuntimeTaskContext field "${field}" must be an array (at ${absPath})`);
|
|
76
|
-
}
|
|
77
|
-
if (!obj[field].every((entry) => typeof entry === "string")) {
|
|
78
|
-
throw new Error(`RuntimeTaskContext field "${field}" must be a string[] (at ${absPath})`);
|
|
45
|
+
renameSync(tempOutputPath, outputPath);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (process.platform === "win32" && existsSync(outputPath)) {
|
|
48
|
+
rmSync(outputPath, { force: true });
|
|
49
|
+
renameSync(tempOutputPath, outputPath);
|
|
50
|
+
return;
|
|
79
51
|
}
|
|
52
|
+
throw error;
|
|
80
53
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
54
|
+
}
|
|
55
|
+
function runtimeRigGitFileName() {
|
|
56
|
+
return `rig-git${process.platform === "win32" ? ".exe" : ""}`;
|
|
57
|
+
}
|
|
58
|
+
function rigGitSourceCandidates() {
|
|
59
|
+
const execDir = process.execPath?.trim() ? dirname(process.execPath.trim()) : "";
|
|
60
|
+
const cwd = process.cwd()?.trim() || "";
|
|
61
|
+
const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
|
|
62
|
+
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
|
|
63
|
+
const moduleRelativeSource = resolve(import.meta.dir, "../../../native/rig-git.zig");
|
|
64
|
+
return [...new Set([
|
|
65
|
+
process.env.RIG_NATIVE_GIT_SOURCE?.trim() || "",
|
|
66
|
+
moduleRelativeSource,
|
|
67
|
+
projectRoot ? resolve(projectRoot, "packages/runtime/native/rig-git.zig") : "",
|
|
68
|
+
hostProjectRoot ? resolve(hostProjectRoot, "packages/runtime/native/rig-git.zig") : "",
|
|
69
|
+
cwd ? resolve(cwd, "packages/runtime/native/rig-git.zig") : "",
|
|
70
|
+
execDir ? resolve(execDir, "..", "..", "packages/runtime/native/rig-git.zig") : "",
|
|
71
|
+
execDir ? resolve(execDir, "..", "native", "rig-git.zig") : ""
|
|
72
|
+
].filter(Boolean))];
|
|
73
|
+
}
|
|
74
|
+
function nativePackageBinaryCandidates(fromDir, fileName) {
|
|
75
|
+
const candidates = [];
|
|
76
|
+
let cursor = resolve(fromDir);
|
|
77
|
+
for (let index = 0;index < 8; index += 1) {
|
|
78
|
+
candidates.push(resolve(cursor, "native", `${process.platform}-${process.arch}`, fileName), resolve(cursor, "native", `${process.platform}-${process.arch}`, "bin", fileName), resolve(cursor, "native", fileName), resolve(cursor, "native", "bin", fileName));
|
|
79
|
+
const parent = dirname(cursor);
|
|
80
|
+
if (parent === cursor)
|
|
81
|
+
break;
|
|
82
|
+
cursor = parent;
|
|
85
83
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (typeof browser[field] !== "string" || browser[field].length === 0) {
|
|
106
|
-
throw new Error(`RuntimeTaskContext field "browser.${field}" must be a non-empty string (at ${absPath})`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
for (const field of ["devCommand", "launchCommand", "checkCommand", "e2eCommand"]) {
|
|
110
|
-
if (browser[field] !== undefined && typeof browser[field] !== "string") {
|
|
111
|
-
throw new Error(`RuntimeTaskContext field "browser.${field}" must be a string when present (at ${absPath})`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (typeof browser.required !== "boolean") {
|
|
115
|
-
throw new Error(`RuntimeTaskContext field "browser.required" must be a boolean (at ${absPath})`);
|
|
84
|
+
return candidates;
|
|
85
|
+
}
|
|
86
|
+
function rigGitBinaryCandidates() {
|
|
87
|
+
const execDir = process.execPath?.trim() ? dirname(process.execPath.trim()) : "";
|
|
88
|
+
const fileName = runtimeRigGitFileName();
|
|
89
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
90
|
+
return [...new Set([
|
|
91
|
+
explicit,
|
|
92
|
+
...nativePackageBinaryCandidates(import.meta.dir, fileName),
|
|
93
|
+
execDir ? resolve(execDir, fileName) : "",
|
|
94
|
+
execDir ? resolve(execDir, "..", fileName) : "",
|
|
95
|
+
execDir ? resolve(execDir, "..", "bin", fileName) : "",
|
|
96
|
+
sharedGitNativeOutputPath
|
|
97
|
+
].filter(Boolean))];
|
|
98
|
+
}
|
|
99
|
+
function resolveGitSourcePath() {
|
|
100
|
+
for (const candidate of rigGitSourceCandidates()) {
|
|
101
|
+
if (candidate && existsSync(candidate)) {
|
|
102
|
+
return candidate;
|
|
116
103
|
}
|
|
117
104
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
for (const field of ["canonicalPath", "canonicalRef", "canonicalBaseOid", "hydratedPath"]) {
|
|
124
|
-
if (typeof memory[field] !== "string" || memory[field].length === 0) {
|
|
125
|
-
throw new Error(`RuntimeTaskContext field "memory.${field}" must be a non-empty string (at ${absPath})`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
if (typeof memory.createdFresh !== "boolean") {
|
|
129
|
-
throw new Error(`RuntimeTaskContext field "memory.createdFresh" must be a boolean (at ${absPath})`);
|
|
130
|
-
}
|
|
131
|
-
if (typeof memory.retrieval !== "object" || memory.retrieval === null || Array.isArray(memory.retrieval)) {
|
|
132
|
-
throw new Error(`RuntimeTaskContext field "memory.retrieval" must be an object (at ${absPath})`);
|
|
133
|
-
}
|
|
134
|
-
const retrieval = memory.retrieval;
|
|
135
|
-
for (const field of ["topK", "lexicalWeight", "vectorWeight", "recencyWeight", "confidenceWeight"]) {
|
|
136
|
-
if (typeof retrieval[field] !== "number" || Number.isNaN(retrieval[field])) {
|
|
137
|
-
throw new Error(`RuntimeTaskContext field "memory.retrieval.${field}" must be a number (at ${absPath})`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
function resolveGitBinaryPath() {
|
|
108
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
109
|
+
return null;
|
|
140
110
|
}
|
|
141
|
-
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
const dirtyFiles = obj.initialDirtyFiles;
|
|
146
|
-
for (const key of ["project", "monorepo"]) {
|
|
147
|
-
if (dirtyFiles[key] === undefined) {
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
if (!Array.isArray(dirtyFiles[key]) || !dirtyFiles[key].every((entry) => typeof entry === "string")) {
|
|
151
|
-
throw new Error(`RuntimeTaskContext field "initialDirtyFiles.${key}" must be a string[] when present (at ${absPath})`);
|
|
152
|
-
}
|
|
111
|
+
for (const candidate of rigGitBinaryCandidates()) {
|
|
112
|
+
if (candidate && existsSync(candidate)) {
|
|
113
|
+
return candidate;
|
|
153
114
|
}
|
|
154
115
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
function preferredGitBinaryOutputPath() {
|
|
119
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
120
|
+
return explicit || sharedGitNativeOutputPath;
|
|
121
|
+
}
|
|
122
|
+
function binarySupportsTrackerCommandsSync(binaryPath) {
|
|
123
|
+
try {
|
|
124
|
+
const probe = Bun.spawnSync([binaryPath, "fetch-ref", "."], {
|
|
125
|
+
stdout: "pipe",
|
|
126
|
+
stderr: "pipe"
|
|
127
|
+
});
|
|
128
|
+
const stdout = probe.stdout.toString().trim();
|
|
129
|
+
const stderr = probe.stderr.toString().trim();
|
|
130
|
+
if (stdout.includes('"error":"unknown command"')) {
|
|
131
|
+
return false;
|
|
167
132
|
}
|
|
133
|
+
return probe.exitCode === 2 && stderr.includes(trackerCommandUsageProbe);
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
168
136
|
}
|
|
169
|
-
return obj;
|
|
170
137
|
}
|
|
171
|
-
function
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
138
|
+
function nativeBuildManifestPath(outputPath) {
|
|
139
|
+
return `${outputPath}.build-manifest.json`;
|
|
140
|
+
}
|
|
141
|
+
function hasMatchingNativeBuildManifestSync(manifestPath, buildKey) {
|
|
142
|
+
if (!existsSync(manifestPath)) {
|
|
143
|
+
return false;
|
|
175
144
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return
|
|
145
|
+
try {
|
|
146
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
147
|
+
return manifest.version === 1 && manifest.buildKey === buildKey;
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
179
150
|
}
|
|
180
|
-
return loadRuntimeContext(inferred);
|
|
181
151
|
}
|
|
182
|
-
function
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
152
|
+
function sha256FileSync(path) {
|
|
153
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
154
|
+
}
|
|
155
|
+
function ensureRigGitBinaryPathSync(outputPath = preferredGitBinaryOutputPath()) {
|
|
156
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
157
|
+
throw new Error("Zig native git is disabled via RIG_DISABLE_ZIG_NATIVE=1");
|
|
158
|
+
}
|
|
159
|
+
const sourcePath = resolveGitSourcePath();
|
|
160
|
+
if (!sourcePath) {
|
|
161
|
+
const binaryPath = resolveGitBinaryPath();
|
|
162
|
+
if (binaryPath) {
|
|
163
|
+
return binaryPath;
|
|
192
164
|
}
|
|
193
|
-
|
|
165
|
+
throw new Error("rig-git.zig source file not found.");
|
|
194
166
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return /\/\.rig\/runtime-context\.json$/.test(normalized);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// packages/runtime/src/control-plane/runtime/tooling/shell.ts
|
|
202
|
-
import { tmpdir } from "os";
|
|
203
|
-
import { basename, dirname as dirname2, resolve as resolve2 } from "path";
|
|
204
|
-
var sharedNativeShellOutputDir = resolve2(tmpdir(), "rig-native");
|
|
205
|
-
var sharedNativeShellOutputPath = resolve2(sharedNativeShellOutputDir, `rig-shell-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
206
|
-
function runtimeToolGatewayNames() {
|
|
207
|
-
return [
|
|
208
|
-
"bash",
|
|
209
|
-
"sh",
|
|
210
|
-
"zsh",
|
|
211
|
-
"git",
|
|
212
|
-
"bun",
|
|
213
|
-
"node",
|
|
214
|
-
"python3",
|
|
215
|
-
"rg",
|
|
216
|
-
"grep",
|
|
217
|
-
"sed",
|
|
218
|
-
"cat",
|
|
219
|
-
"ls",
|
|
220
|
-
"find",
|
|
221
|
-
"tsc",
|
|
222
|
-
"gh",
|
|
223
|
-
"mkdir",
|
|
224
|
-
"rm",
|
|
225
|
-
"mv",
|
|
226
|
-
"cp",
|
|
227
|
-
"touch",
|
|
228
|
-
"pwd",
|
|
229
|
-
"head",
|
|
230
|
-
"tail",
|
|
231
|
-
"wc",
|
|
232
|
-
"sort",
|
|
233
|
-
"uniq",
|
|
234
|
-
"awk",
|
|
235
|
-
"xargs",
|
|
236
|
-
"dirname",
|
|
237
|
-
"basename",
|
|
238
|
-
"realpath",
|
|
239
|
-
"env",
|
|
240
|
-
"jq",
|
|
241
|
-
"tee",
|
|
242
|
-
"which"
|
|
243
|
-
];
|
|
244
|
-
}
|
|
245
|
-
// packages/runtime/src/control-plane/runtime/tooling/file-tools.ts
|
|
246
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
247
|
-
import { basename as basename2, dirname as dirname3, resolve as resolve3 } from "path";
|
|
248
|
-
var sharedNativeToolsOutputDir = resolve3(tmpdir2(), "rig-native");
|
|
249
|
-
var sharedNativeToolsOutputPath = resolve3(sharedNativeToolsOutputDir, `rig-tools-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
250
|
-
function runtimeFileToolNames() {
|
|
251
|
-
return [
|
|
252
|
-
"rig-read",
|
|
253
|
-
"rig-write",
|
|
254
|
-
"rig-edit",
|
|
255
|
-
"rig-glob",
|
|
256
|
-
"rig-grep"
|
|
257
|
-
];
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// packages/runtime/src/control-plane/runtime/tooling/gateway.ts
|
|
261
|
-
function runtimeGatewayToolNames() {
|
|
262
|
-
return runtimeToolGatewayNames();
|
|
263
|
-
}
|
|
264
|
-
// packages/runtime/src/control-plane/browser-contract.ts
|
|
265
|
-
import { resolve as resolve4 } from "path";
|
|
266
|
-
var DEFAULT_BROWSER_ATTACH_URL = "http://127.0.0.1:9333";
|
|
267
|
-
var DEFAULT_BROWSER_MODE = "persistent";
|
|
268
|
-
var RUNTIME_BROWSER_HELPERS = {
|
|
269
|
-
launch: "rig-browser-launch",
|
|
270
|
-
check: "rig-browser-check",
|
|
271
|
-
attachInfo: "rig-browser-attach-info",
|
|
272
|
-
e2e: "rig-browser-e2e",
|
|
273
|
-
resetProfile: "rig-browser-reset-profile"
|
|
274
|
-
};
|
|
275
|
-
var BASE_REMOTE_DEBUGGING_PORT = 9222;
|
|
276
|
-
var REMOTE_DEBUGGING_PORT_SPREAD = 4000;
|
|
277
|
-
function hashString(input) {
|
|
278
|
-
let hash = 0;
|
|
279
|
-
for (let index = 0;index < input.length; index += 1) {
|
|
280
|
-
hash = (hash << 5) - hash + input.charCodeAt(index) | 0;
|
|
281
|
-
}
|
|
282
|
-
return Math.abs(hash);
|
|
283
|
-
}
|
|
284
|
-
function derivePortFromProfile(profileName) {
|
|
285
|
-
return BASE_REMOTE_DEBUGGING_PORT + hashString(profileName) % REMOTE_DEBUGGING_PORT_SPREAD;
|
|
286
|
-
}
|
|
287
|
-
function parseAttachUrl(attachUrl) {
|
|
288
|
-
try {
|
|
289
|
-
return new URL((attachUrl || DEFAULT_BROWSER_ATTACH_URL).trim() || DEFAULT_BROWSER_ATTACH_URL);
|
|
290
|
-
} catch {
|
|
291
|
-
return new URL(DEFAULT_BROWSER_ATTACH_URL);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
function sanitizeRuntimeSuffix(runtimeId) {
|
|
295
|
-
return runtimeId.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 16) || "runtime";
|
|
296
|
-
}
|
|
297
|
-
function resolveBrowserStateDir(projectRoot, configuredStateDir) {
|
|
298
|
-
const trimmed = configuredStateDir?.trim() || ".tmp/rig-browser";
|
|
299
|
-
if (trimmed.startsWith("/")) {
|
|
300
|
-
return resolve4(trimmed);
|
|
167
|
+
const zigBinary = Bun.which("zig");
|
|
168
|
+
if (!zigBinary) {
|
|
169
|
+
throw new Error("zig is required to build native Rig git tools.");
|
|
301
170
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
171
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
172
|
+
const sourceDigest = sha256FileSync(sourcePath);
|
|
173
|
+
const buildKey = JSON.stringify({
|
|
174
|
+
version: 1,
|
|
175
|
+
zigBinary,
|
|
176
|
+
platform: process.platform,
|
|
177
|
+
arch: process.arch,
|
|
178
|
+
sourcePath,
|
|
179
|
+
sourceDigest
|
|
180
|
+
});
|
|
181
|
+
const manifestPath = nativeBuildManifestPath(outputPath);
|
|
182
|
+
const needsBuild = !existsSync(outputPath) || !hasMatchingNativeBuildManifestSync(manifestPath, buildKey) || !binarySupportsTrackerCommandsSync(outputPath);
|
|
183
|
+
if (!needsBuild) {
|
|
184
|
+
chmodSync(outputPath, 493);
|
|
185
|
+
return outputPath;
|
|
307
186
|
}
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
checkCommand: browser.check_command?.trim() || undefined,
|
|
328
|
-
e2eCommand: browser.e2e_command?.trim() || undefined,
|
|
329
|
-
launchHelper: RUNTIME_BROWSER_HELPERS.launch,
|
|
330
|
-
checkHelper: RUNTIME_BROWSER_HELPERS.check,
|
|
331
|
-
attachInfoHelper: RUNTIME_BROWSER_HELPERS.attachInfo,
|
|
332
|
-
e2eHelper: RUNTIME_BROWSER_HELPERS.e2e,
|
|
333
|
-
resetProfileHelper: RUNTIME_BROWSER_HELPERS.resetProfile
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
function buildBrowserGuidanceLines(browser) {
|
|
337
|
-
const lines = [
|
|
338
|
-
"This task requires Rig Browser.",
|
|
339
|
-
`Launch the browser: \`${browser.launchHelper}\`${browser.devCommand ? " or `rig-browser-launch --dev`" : ""}.`,
|
|
340
|
-
`Check the browser contract: \`${browser.checkHelper}\`.`,
|
|
341
|
-
`Show attach details: \`${browser.attachInfoHelper}\`.`,
|
|
342
|
-
`Attach Chrome DevTools MCP to ${browser.effectiveAttachUrl}.`,
|
|
343
|
-
`Preset: ${browser.preset}.`,
|
|
344
|
-
`Profile: ${browser.effectiveProfile}.`,
|
|
345
|
-
`State dir: ${browser.stateDir}.`,
|
|
346
|
-
`Reset the active profile with \`${browser.resetProfileHelper}\`.`
|
|
347
|
-
];
|
|
348
|
-
if (browser.e2eCommand) {
|
|
349
|
-
lines.push(`Run app-owned browser e2e with \`${browser.e2eHelper}\`.`);
|
|
187
|
+
const tempOutputPath = temporaryGitBinaryOutputPath(outputPath);
|
|
188
|
+
const build = Bun.spawnSync([
|
|
189
|
+
zigBinary,
|
|
190
|
+
"build-exe",
|
|
191
|
+
sourcePath,
|
|
192
|
+
"-O",
|
|
193
|
+
"ReleaseFast",
|
|
194
|
+
`-femit-bin=${tempOutputPath}`
|
|
195
|
+
], {
|
|
196
|
+
cwd: dirname(sourcePath),
|
|
197
|
+
stdout: "pipe",
|
|
198
|
+
stderr: "pipe"
|
|
199
|
+
});
|
|
200
|
+
if (build.exitCode !== 0 || !existsSync(tempOutputPath)) {
|
|
201
|
+
const stderr = build.stderr.toString().trim();
|
|
202
|
+
const stdout = build.stdout.toString().trim();
|
|
203
|
+
const details = [stderr, stdout].filter(Boolean).join(`
|
|
204
|
+
`);
|
|
205
|
+
throw new Error(`Failed to build native Rig git tools: ${details || `zig exited with code ${build.exitCode}`}`);
|
|
350
206
|
}
|
|
351
|
-
|
|
352
|
-
|
|
207
|
+
chmodSync(tempOutputPath, 493);
|
|
208
|
+
if (existsSync(outputPath) && hasMatchingNativeBuildManifestSync(manifestPath, buildKey)) {
|
|
209
|
+
rmSync(tempOutputPath, { force: true });
|
|
210
|
+
chmodSync(outputPath, 493);
|
|
211
|
+
return outputPath;
|
|
353
212
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
import { createPluginHost } from "@rig/core";
|
|
359
|
-
import { loadConfig } from "@rig/core/load-config";
|
|
360
|
-
|
|
361
|
-
// packages/runtime/src/control-plane/task-source.ts
|
|
362
|
-
function createTaskSourceRegistry() {
|
|
363
|
-
const byId = new Map;
|
|
364
|
-
const order = [];
|
|
365
|
-
return {
|
|
366
|
-
register(s) {
|
|
367
|
-
if (byId.has(s.id))
|
|
368
|
-
throw new Error(`task source already registered: ${s.id}`);
|
|
369
|
-
byId.set(s.id, s);
|
|
370
|
-
order.push(s);
|
|
371
|
-
},
|
|
372
|
-
resolveById(id) {
|
|
373
|
-
const s = byId.get(id);
|
|
374
|
-
if (!s)
|
|
375
|
-
throw new Error(`task source not registered: ${id}`);
|
|
376
|
-
return s;
|
|
377
|
-
},
|
|
378
|
-
resolveByKind(kind) {
|
|
379
|
-
for (const s of order)
|
|
380
|
-
if (s.kind === kind)
|
|
381
|
-
return s;
|
|
382
|
-
throw new Error(`no task source registered for kind: ${kind}`);
|
|
383
|
-
},
|
|
384
|
-
list: () => order
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// packages/runtime/src/control-plane/task-source-bootstrap.ts
|
|
389
|
-
function formatRegisteredKinds(pluginHost) {
|
|
390
|
-
const kinds = pluginHost ? pluginHost.listExecutableTaskSources().map((source) => source.kind) : [];
|
|
391
|
-
return kinds.length > 0 ? kinds.join(", ") : "none";
|
|
392
|
-
}
|
|
393
|
-
function buildTaskSourceRegistry(config, pluginHost) {
|
|
394
|
-
const registry = createTaskSourceRegistry();
|
|
395
|
-
const taskSourceConfig = config.taskSource;
|
|
396
|
-
const factory = pluginHost?.resolveTaskSourceFactoryByKind(taskSourceConfig.kind);
|
|
397
|
-
if (!factory) {
|
|
398
|
-
throw new Error(`No task source factory registered for kind "${taskSourceConfig.kind}". ` + `Registered kinds: ${formatRegisteredKinds(pluginHost)}. ` + "Load a plugin that contributes an executable task source factory for this kind.");
|
|
213
|
+
publishGitBinary(tempOutputPath, outputPath);
|
|
214
|
+
if (!binarySupportsTrackerCommandsSync(outputPath)) {
|
|
215
|
+
rmSync(outputPath, { force: true });
|
|
216
|
+
throw new Error("Failed to build native Rig git tools: tracker command probe failed");
|
|
399
217
|
}
|
|
400
|
-
|
|
401
|
-
|
|
218
|
+
writeFileSync(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
219
|
+
`, "utf8");
|
|
220
|
+
return outputPath;
|
|
402
221
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const map = new Map;
|
|
407
|
-
for (const e of entries) {
|
|
408
|
-
if (map.has(e.id))
|
|
409
|
-
throw new Error(`repo already registered: ${e.id}`);
|
|
410
|
-
map.set(e.id, { ...e });
|
|
222
|
+
function runGitNative(command, args) {
|
|
223
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
224
|
+
return { ok: false, error: "rig-git native disabled" };
|
|
411
225
|
}
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
throw new Error(`managed repo already registered: ${e.id}`);
|
|
226
|
+
const trackerCommand = command === "fetch-ref" || command === "read-blob-at-ref" || command === "write-tree-commit" || command === "push-ref-with-lease";
|
|
227
|
+
let binaryPath = null;
|
|
228
|
+
if (trackerCommand) {
|
|
229
|
+
try {
|
|
230
|
+
binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
|
|
231
|
+
} catch (error) {
|
|
232
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
233
|
+
if (message.includes("rig-git.zig source file not found")) {
|
|
234
|
+
return { ok: false, error: "rig-git binary not found" };
|
|
235
|
+
}
|
|
236
|
+
return { ok: false, error: message };
|
|
424
237
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
function repoRegistrationToManagedEntry(reg) {
|
|
440
|
-
if (!reg.defaultBranch) {
|
|
441
|
-
return null;
|
|
442
|
-
}
|
|
443
|
-
return {
|
|
444
|
-
id: reg.id,
|
|
445
|
-
alias: reg.defaultPath ?? reg.id,
|
|
446
|
-
defaultBranch: reg.defaultBranch,
|
|
447
|
-
defaultRemoteUrl: reg.url,
|
|
448
|
-
remoteEnvVar: reg.remoteEnvVar,
|
|
449
|
-
checkoutEnvVar: reg.checkoutEnvVar
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// packages/runtime/src/control-plane/agent-roles.ts
|
|
454
|
-
function createAgentRoleRegistry(pluginRoles, configOverrides) {
|
|
455
|
-
const map = new Map;
|
|
456
|
-
for (const r of pluginRoles) {
|
|
457
|
-
if (map.has(r.id))
|
|
458
|
-
throw new Error(`agent role already registered: ${r.id}`);
|
|
459
|
-
const override = configOverrides?.[r.id];
|
|
460
|
-
const model = override?.model ?? r.defaultModel;
|
|
461
|
-
if (!model) {
|
|
462
|
-
throw new Error(`agent role "${r.id}" has no model \u2014 provide defaultModel in plugin or model in config.runtime.agentRoles.${r.id}`);
|
|
238
|
+
} else {
|
|
239
|
+
const explicitBinaryPath = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
240
|
+
binaryPath = explicitBinaryPath && existsSync(explicitBinaryPath) ? explicitBinaryPath : !explicitBinaryPath ? resolveGitBinaryPath() : null;
|
|
241
|
+
if (!binaryPath) {
|
|
242
|
+
try {
|
|
243
|
+
binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
|
|
244
|
+
} catch (error) {
|
|
245
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
246
|
+
if (message.includes("rig-git.zig source file not found")) {
|
|
247
|
+
return { ok: false, error: "rig-git binary not found" };
|
|
248
|
+
}
|
|
249
|
+
return { ok: false, error: message };
|
|
250
|
+
}
|
|
463
251
|
}
|
|
464
|
-
map.set(r.id, { ...r, model });
|
|
465
|
-
}
|
|
466
|
-
return {
|
|
467
|
-
resolve(id) {
|
|
468
|
-
const r = map.get(id);
|
|
469
|
-
if (!r)
|
|
470
|
-
throw new Error(`agent role not registered: ${id}`);
|
|
471
|
-
return r;
|
|
472
|
-
},
|
|
473
|
-
list: () => Array.from(map.values())
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// packages/runtime/src/control-plane/task-fields.ts
|
|
478
|
-
function createTaskFieldRegistry(extensions) {
|
|
479
|
-
const byId = new Map;
|
|
480
|
-
for (const e of extensions) {
|
|
481
|
-
if (byId.has(e.id))
|
|
482
|
-
throw new Error(`task field extension already registered: ${e.id}`);
|
|
483
|
-
byId.set(e.id, e);
|
|
484
252
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
253
|
+
try {
|
|
254
|
+
const proc = Bun.spawnSync([binaryPath, command, ...args], {
|
|
255
|
+
stdout: "pipe",
|
|
256
|
+
stderr: "pipe",
|
|
257
|
+
env: process.env
|
|
258
|
+
});
|
|
259
|
+
if (proc.exitCode !== 0) {
|
|
260
|
+
const stdoutText = proc.stdout.toString().trim();
|
|
261
|
+
if (stdoutText) {
|
|
493
262
|
try {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
const isRequired = typeof schema === "object" && schema !== null && schema.required === true;
|
|
500
|
-
if (!isRequired)
|
|
501
|
-
continue;
|
|
502
|
-
const value = task[ext.fieldName];
|
|
503
|
-
if (value === undefined || value === null || value === "") {
|
|
504
|
-
errors.push(`task field "${ext.fieldName}" (from extension "${ext.id}") is required but missing`);
|
|
505
|
-
}
|
|
263
|
+
const parsed = JSON.parse(stdoutText);
|
|
264
|
+
if (!parsed.ok) {
|
|
265
|
+
return parsed;
|
|
266
|
+
}
|
|
267
|
+
} catch {}
|
|
506
268
|
}
|
|
507
|
-
|
|
269
|
+
const errText = proc.stderr.toString().trim() || `exit code ${proc.exitCode}`;
|
|
270
|
+
return { ok: false, error: errText };
|
|
508
271
|
}
|
|
509
|
-
|
|
272
|
+
const output = proc.stdout.toString().trim();
|
|
273
|
+
return JSON.parse(output);
|
|
274
|
+
} catch (err) {
|
|
275
|
+
return { ok: false, error: String(err) };
|
|
276
|
+
}
|
|
510
277
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const order = [];
|
|
518
|
-
const registry = {
|
|
519
|
-
register(v) {
|
|
520
|
-
if (map.has(v.id))
|
|
521
|
-
throw new Error(`validator already registered: ${v.id}`);
|
|
522
|
-
map.set(v.id, v);
|
|
523
|
-
order.push(v);
|
|
524
|
-
},
|
|
525
|
-
resolve(id) {
|
|
526
|
-
const v = map.get(id);
|
|
527
|
-
if (!v)
|
|
528
|
-
throw new Error(`validator not registered: ${id}`);
|
|
529
|
-
return v;
|
|
530
|
-
},
|
|
531
|
-
list: () => order
|
|
532
|
-
};
|
|
533
|
-
registerBuiltInValidators(registry);
|
|
534
|
-
return registry;
|
|
278
|
+
function requireGitNative(command, args) {
|
|
279
|
+
const result = runGitNative(command, args);
|
|
280
|
+
if (!result.ok) {
|
|
281
|
+
throw new Error(`rig-git ${command} failed: ${result.error}`);
|
|
282
|
+
}
|
|
283
|
+
return result;
|
|
535
284
|
}
|
|
536
|
-
function
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
});
|
|
285
|
+
function requireGitNativeString(command, args) {
|
|
286
|
+
const result = requireGitNative(command, args);
|
|
287
|
+
if ("value" in result && typeof result.value === "string") {
|
|
288
|
+
return result.value;
|
|
289
|
+
}
|
|
290
|
+
throw new Error(`rig-git ${command} returned an unexpected result payload`);
|
|
543
291
|
}
|
|
544
|
-
|
|
545
|
-
const
|
|
546
|
-
if (!
|
|
547
|
-
return
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
summary: `package.json not found at ${packageJsonPath}`
|
|
551
|
-
};
|
|
292
|
+
function nativePendingFiles(repoPath) {
|
|
293
|
+
const result = runGitNative("pending-files", [repoPath]);
|
|
294
|
+
if (!result.ok)
|
|
295
|
+
return null;
|
|
296
|
+
if ("files" in result && Array.isArray(result.files)) {
|
|
297
|
+
return result.files.map((f) => ({ path: f.path, status: f.status }));
|
|
552
298
|
}
|
|
553
|
-
|
|
554
|
-
cwd: ctx.workspaceRoot,
|
|
555
|
-
env: process.env,
|
|
556
|
-
stdout: "pipe",
|
|
557
|
-
stderr: "pipe"
|
|
558
|
-
});
|
|
559
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
560
|
-
proc.exited,
|
|
561
|
-
new Response(proc.stdout).text(),
|
|
562
|
-
new Response(proc.stderr).text()
|
|
563
|
-
]);
|
|
564
|
-
const output = `${stdout}${stderr}`.trim();
|
|
565
|
-
return {
|
|
566
|
-
id: "std:typecheck",
|
|
567
|
-
passed: exitCode === 0,
|
|
568
|
-
summary: exitCode === 0 ? "typecheck passed" : "typecheck failed",
|
|
569
|
-
...output ? { details: output.slice(0, 4000) } : {}
|
|
570
|
-
};
|
|
299
|
+
return null;
|
|
571
300
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
var activeRules = null;
|
|
575
|
-
function setScopeRules(rules) {
|
|
576
|
-
activeRules = rules ?? null;
|
|
301
|
+
function nativeFetchRef(repoPath, remote, branch) {
|
|
302
|
+
return requireGitNativeString("fetch-ref", [repoPath, remote, branch]);
|
|
577
303
|
}
|
|
578
|
-
function
|
|
579
|
-
|
|
304
|
+
function nativeReadBlobAtRef(repoPath, ref, path) {
|
|
305
|
+
const requestDir = resolve(sharedGitNativeOutputDir, "reads", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
|
|
306
|
+
mkdirSync(requestDir, { recursive: true });
|
|
307
|
+
const outputPath = resolve(requestDir, "blob.txt");
|
|
308
|
+
try {
|
|
309
|
+
requireGitNative("read-blob-at-ref", [repoPath, ref, path, outputPath]);
|
|
310
|
+
return readFileSync(outputPath, "utf8");
|
|
311
|
+
} finally {
|
|
312
|
+
rmSync(requestDir, { recursive: true, force: true });
|
|
313
|
+
}
|
|
580
314
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
return;
|
|
590
|
-
|
|
591
|
-
return matcher.name;
|
|
592
|
-
return matcher.pattern;
|
|
315
|
+
function serializeTreeCommitUpdates(updates) {
|
|
316
|
+
return updates.map((update) => {
|
|
317
|
+
if (isTextTreeCommitUpdate(update)) {
|
|
318
|
+
return { path: update.path, kind: "text", content: update.content };
|
|
319
|
+
}
|
|
320
|
+
if (!isAbsolute(update.sourceFilePath)) {
|
|
321
|
+
throw new Error("tree commit binary updates require an absolute sourceFilePath");
|
|
322
|
+
}
|
|
323
|
+
return { path: update.path, kind: "file", sourceFilePath: update.sourceFilePath };
|
|
324
|
+
});
|
|
593
325
|
}
|
|
594
|
-
function
|
|
595
|
-
return
|
|
326
|
+
function buildTreeCommitUpdatesJson(updates) {
|
|
327
|
+
return `${JSON.stringify(serializeTreeCommitUpdates(updates), null, 2)}
|
|
328
|
+
`;
|
|
596
329
|
}
|
|
597
|
-
function
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
330
|
+
function nativeWriteTreeCommit(repoPath, baseRef, updates, message) {
|
|
331
|
+
const requestDir = resolve(sharedGitNativeOutputDir, "requests", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
|
|
332
|
+
mkdirSync(requestDir, { recursive: true });
|
|
333
|
+
const messagePath = resolve(requestDir, "message.txt");
|
|
334
|
+
const updatesPath = resolve(requestDir, "updates.json");
|
|
335
|
+
try {
|
|
336
|
+
writeFileSync(messagePath, message, "utf8");
|
|
337
|
+
writeFileSync(updatesPath, buildTreeCommitUpdatesJson(updates), "utf8");
|
|
338
|
+
return requireGitNativeString("write-tree-commit", [repoPath, baseRef, messagePath, updatesPath]);
|
|
339
|
+
} finally {
|
|
340
|
+
rmSync(requestDir, { recursive: true, force: true });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function nativePushRefWithLease(repoPath, localOid, remoteRef, expectedOldOid, remote = "origin") {
|
|
344
|
+
return requireGitNativeString("push-ref-with-lease", [
|
|
345
|
+
repoPath,
|
|
346
|
+
localOid,
|
|
347
|
+
remoteRef,
|
|
348
|
+
expectedOldOid,
|
|
349
|
+
remote
|
|
350
|
+
]);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// packages/runtime/src/control-plane/runtime/context.ts
|
|
354
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
355
|
+
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
356
|
+
var RUNTIME_CONTEXT_ENV = "RIG_RUNTIME_CONTEXT_FILE";
|
|
357
|
+
var runtimeContextStringFields = [
|
|
358
|
+
"runtimeId",
|
|
359
|
+
"taskId",
|
|
360
|
+
"role",
|
|
361
|
+
"workspaceDir",
|
|
362
|
+
"stateDir",
|
|
363
|
+
"logsDir",
|
|
364
|
+
"sessionDir",
|
|
365
|
+
"sessionFile",
|
|
366
|
+
"policyFile",
|
|
367
|
+
"binDir",
|
|
368
|
+
"createdAt"
|
|
369
|
+
];
|
|
370
|
+
var runtimeContextArrayFields = ["scopes", "validation"];
|
|
371
|
+
var runtimeContextOptionalStringFields = [
|
|
372
|
+
"artifactRoot",
|
|
373
|
+
"hostProjectRoot",
|
|
374
|
+
"monorepoMainRoot",
|
|
375
|
+
"monorepoBaseRef",
|
|
376
|
+
"monorepoBaseCommit"
|
|
377
|
+
];
|
|
378
|
+
function loadRuntimeContext(path) {
|
|
379
|
+
const absPath = resolve2(path);
|
|
380
|
+
if (!existsSync2(absPath)) {
|
|
381
|
+
throw new Error(`RuntimeTaskContext file not found: ${absPath}`);
|
|
382
|
+
}
|
|
383
|
+
let raw;
|
|
384
|
+
try {
|
|
385
|
+
raw = JSON.parse(readFileSync2(absPath, "utf8"));
|
|
386
|
+
} catch (err) {
|
|
387
|
+
throw new Error(`Failed to parse RuntimeTaskContext at ${absPath}: ${String(err)}`);
|
|
388
|
+
}
|
|
389
|
+
if (typeof raw !== "object" || raw === null) {
|
|
390
|
+
throw new Error(`RuntimeTaskContext at ${absPath} is not an object`);
|
|
391
|
+
}
|
|
392
|
+
const obj = raw;
|
|
393
|
+
for (const field of runtimeContextStringFields) {
|
|
394
|
+
if (typeof obj[field] !== "string" || obj[field].length === 0) {
|
|
395
|
+
throw new Error(`RuntimeTaskContext field "${field}" must be a non-empty string (at ${absPath})`);
|
|
614
396
|
}
|
|
615
397
|
}
|
|
616
|
-
for (const
|
|
617
|
-
if (!
|
|
618
|
-
|
|
398
|
+
for (const field of runtimeContextArrayFields) {
|
|
399
|
+
if (!Array.isArray(obj[field])) {
|
|
400
|
+
throw new Error(`RuntimeTaskContext field "${field}" must be an array (at ${absPath})`);
|
|
619
401
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const groups = hooks[event] ??= [];
|
|
623
|
-
let group = groups.find((g) => g.matcher === matcherString);
|
|
624
|
-
if (!group) {
|
|
625
|
-
group = matcherString === undefined ? { hooks: [] } : { matcher: matcherString, hooks: [] };
|
|
626
|
-
groups.push(group);
|
|
402
|
+
if (!obj[field].every((entry) => typeof entry === "string")) {
|
|
403
|
+
throw new Error(`RuntimeTaskContext field "${field}" must be a string[] (at ${absPath})`);
|
|
627
404
|
}
|
|
628
|
-
group.hooks.push({
|
|
629
|
-
type: "command",
|
|
630
|
-
command: hook.command,
|
|
631
|
-
[MARKER_PLUGIN]: pluginName,
|
|
632
|
-
[MARKER_HOOK_ID]: hook.id
|
|
633
|
-
});
|
|
634
405
|
}
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
delete next.hooks;
|
|
406
|
+
for (const field of runtimeContextOptionalStringFields) {
|
|
407
|
+
if (field in obj && obj[field] !== undefined && typeof obj[field] !== "string") {
|
|
408
|
+
throw new Error(`RuntimeTaskContext field "${field}" must be a string when present (at ${absPath})`);
|
|
409
|
+
}
|
|
640
410
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
411
|
+
if (obj.browser !== undefined) {
|
|
412
|
+
if (typeof obj.browser !== "object" || obj.browser === null || Array.isArray(obj.browser)) {
|
|
413
|
+
throw new Error(`RuntimeTaskContext field "browser" must be an object when present (at ${absPath})`);
|
|
414
|
+
}
|
|
415
|
+
const browser = obj.browser;
|
|
416
|
+
for (const field of [
|
|
417
|
+
"preset",
|
|
418
|
+
"mode",
|
|
419
|
+
"stateDir",
|
|
420
|
+
"defaultProfile",
|
|
421
|
+
"effectiveProfile",
|
|
422
|
+
"defaultAttachUrl",
|
|
423
|
+
"effectiveAttachUrl",
|
|
424
|
+
"launchHelper",
|
|
425
|
+
"checkHelper",
|
|
426
|
+
"attachInfoHelper",
|
|
427
|
+
"e2eHelper",
|
|
428
|
+
"resetProfileHelper"
|
|
429
|
+
]) {
|
|
430
|
+
if (typeof browser[field] !== "string" || browser[field].length === 0) {
|
|
431
|
+
throw new Error(`RuntimeTaskContext field "browser.${field}" must be a non-empty string (at ${absPath})`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
for (const field of ["devCommand", "launchCommand", "checkCommand", "e2eCommand"]) {
|
|
435
|
+
if (browser[field] !== undefined && typeof browser[field] !== "string") {
|
|
436
|
+
throw new Error(`RuntimeTaskContext field "browser.${field}" must be a string when present (at ${absPath})`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (typeof browser.required !== "boolean") {
|
|
440
|
+
throw new Error(`RuntimeTaskContext field "browser.required" must be a boolean (at ${absPath})`);
|
|
441
|
+
}
|
|
651
442
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
443
|
+
if (obj.memory !== undefined) {
|
|
444
|
+
if (typeof obj.memory !== "object" || obj.memory === null || Array.isArray(obj.memory)) {
|
|
445
|
+
throw new Error(`RuntimeTaskContext field "memory" must be an object when present (at ${absPath})`);
|
|
446
|
+
}
|
|
447
|
+
const memory = obj.memory;
|
|
448
|
+
for (const field of ["canonicalPath", "canonicalRef", "canonicalBaseOid", "hydratedPath"]) {
|
|
449
|
+
if (typeof memory[field] !== "string" || memory[field].length === 0) {
|
|
450
|
+
throw new Error(`RuntimeTaskContext field "memory.${field}" must be a non-empty string (at ${absPath})`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (typeof memory.createdFresh !== "boolean") {
|
|
454
|
+
throw new Error(`RuntimeTaskContext field "memory.createdFresh" must be a boolean (at ${absPath})`);
|
|
455
|
+
}
|
|
456
|
+
if (typeof memory.retrieval !== "object" || memory.retrieval === null || Array.isArray(memory.retrieval)) {
|
|
457
|
+
throw new Error(`RuntimeTaskContext field "memory.retrieval" must be an object (at ${absPath})`);
|
|
458
|
+
}
|
|
459
|
+
const retrieval = memory.retrieval;
|
|
460
|
+
for (const field of ["topK", "lexicalWeight", "vectorWeight", "recencyWeight", "confidenceWeight"]) {
|
|
461
|
+
if (typeof retrieval[field] !== "number" || Number.isNaN(retrieval[field])) {
|
|
462
|
+
throw new Error(`RuntimeTaskContext field "memory.retrieval.${field}" must be a number (at ${absPath})`);
|
|
669
463
|
}
|
|
670
464
|
}
|
|
671
465
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
if (!existsSync4(sourcePath)) {
|
|
676
|
-
console.warn(`[plugin-host] skill "${skill.id}" from plugin "${pluginName}" not materialized: ${sourcePath} does not exist`);
|
|
677
|
-
continue;
|
|
466
|
+
if (obj.initialDirtyFiles !== undefined) {
|
|
467
|
+
if (typeof obj.initialDirtyFiles !== "object" || obj.initialDirtyFiles === null || Array.isArray(obj.initialDirtyFiles)) {
|
|
468
|
+
throw new Error(`RuntimeTaskContext field "initialDirtyFiles" must be an object when present (at ${absPath})`);
|
|
678
469
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
470
|
+
const dirtyFiles = obj.initialDirtyFiles;
|
|
471
|
+
for (const key of ["project", "monorepo"]) {
|
|
472
|
+
if (dirtyFiles[key] === undefined) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (!Array.isArray(dirtyFiles[key]) || !dirtyFiles[key].every((entry) => typeof entry === "string")) {
|
|
476
|
+
throw new Error(`RuntimeTaskContext field "initialDirtyFiles.${key}" must be a string[] when present (at ${absPath})`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (obj.initialHeadCommits !== undefined) {
|
|
481
|
+
if (typeof obj.initialHeadCommits !== "object" || obj.initialHeadCommits === null || Array.isArray(obj.initialHeadCommits)) {
|
|
482
|
+
throw new Error(`RuntimeTaskContext field "initialHeadCommits" must be an object when present (at ${absPath})`);
|
|
483
|
+
}
|
|
484
|
+
const headCommits = obj.initialHeadCommits;
|
|
485
|
+
for (const key of ["project", "monorepo"]) {
|
|
486
|
+
if (headCommits[key] === undefined) {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (typeof headCommits[key] !== "string") {
|
|
490
|
+
throw new Error(`RuntimeTaskContext field "initialHeadCommits.${key}" must be a string when present (at ${absPath})`);
|
|
491
|
+
}
|
|
686
492
|
}
|
|
687
|
-
const dir = resolve6(skillsRoot, skillDirName(skill.id));
|
|
688
|
-
mkdirSync3(dir, { recursive: true });
|
|
689
|
-
writeFileSync3(resolve6(dir, "SKILL.md"), body, "utf-8");
|
|
690
|
-
writeFileSync3(resolve6(dir, MARKER_FILENAME), `${JSON.stringify({ plugin: pluginName, skillId: skill.id }, null, 2)}
|
|
691
|
-
`, "utf-8");
|
|
692
|
-
written.push({ id: skill.id, pluginName, directory: dir });
|
|
693
493
|
}
|
|
694
|
-
return
|
|
494
|
+
return obj;
|
|
695
495
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
try {
|
|
701
|
-
config = await loadConfig(projectRoot);
|
|
702
|
-
} catch (err) {
|
|
703
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
704
|
-
if (msg.includes("no rig.config")) {
|
|
705
|
-
return null;
|
|
706
|
-
}
|
|
707
|
-
throw err;
|
|
496
|
+
function loadRuntimeContextFromEnv(env = process.env) {
|
|
497
|
+
const contextFile = env[RUNTIME_CONTEXT_ENV];
|
|
498
|
+
if (contextFile) {
|
|
499
|
+
return loadRuntimeContext(contextFile);
|
|
708
500
|
}
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
for (const impl of pluginHost.listExecutableValidators()) {
|
|
713
|
-
validatorRegistry.register(impl);
|
|
501
|
+
const inferred = findRuntimeContextFile(process.cwd());
|
|
502
|
+
if (!inferred) {
|
|
503
|
+
return null;
|
|
714
504
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
const hookEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.hooks ?? []).map((hook) => ({
|
|
724
|
-
pluginName: plugin.name,
|
|
725
|
-
hook
|
|
726
|
-
})));
|
|
727
|
-
if (hookEntries.length > 0) {
|
|
728
|
-
materializeHooks(projectRoot, hookEntries);
|
|
505
|
+
return loadRuntimeContext(inferred);
|
|
506
|
+
}
|
|
507
|
+
function findRuntimeContextFile(startPath) {
|
|
508
|
+
let current = resolve2(startPath);
|
|
509
|
+
while (true) {
|
|
510
|
+
const candidate = resolve2(current, "runtime-context.json");
|
|
511
|
+
if (existsSync2(candidate) && isAgentRuntimeContextPath(candidate)) {
|
|
512
|
+
return candidate;
|
|
729
513
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
try {
|
|
734
|
-
const skillEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.skills ?? []).map((skill) => ({
|
|
735
|
-
pluginName: plugin.name,
|
|
736
|
-
skill
|
|
737
|
-
})));
|
|
738
|
-
if (skillEntries.length > 0) {
|
|
739
|
-
await materializeSkills(projectRoot, skillEntries);
|
|
514
|
+
const parent = dirname2(current);
|
|
515
|
+
if (parent === current) {
|
|
516
|
+
return "";
|
|
740
517
|
}
|
|
741
|
-
|
|
742
|
-
console.warn(`[plugin-host] skill materialization failed: ${err instanceof Error ? err.message : err}`);
|
|
518
|
+
current = parent;
|
|
743
519
|
}
|
|
744
|
-
return {
|
|
745
|
-
config,
|
|
746
|
-
pluginHost,
|
|
747
|
-
validatorRegistry,
|
|
748
|
-
taskSourceRegistry,
|
|
749
|
-
repoRegistry,
|
|
750
|
-
agentRoleRegistry,
|
|
751
|
-
taskFieldRegistry
|
|
752
|
-
};
|
|
753
520
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync4 } from "fs";
|
|
758
|
-
import { basename as basename3, join as join2, resolve as resolve8 } from "path";
|
|
759
|
-
|
|
760
|
-
// packages/runtime/src/control-plane/tasks/legacy-task-config-source.ts
|
|
761
|
-
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
762
|
-
import { resolve as resolve7 } from "path";
|
|
763
|
-
|
|
764
|
-
// packages/runtime/src/control-plane/tasks/task-record-reader.ts
|
|
765
|
-
async function findTaskById(reader, id) {
|
|
766
|
-
const tasks = await reader.listTasks();
|
|
767
|
-
return tasks.find((task) => task.id === id) ?? null;
|
|
521
|
+
function isAgentRuntimeContextPath(path) {
|
|
522
|
+
const normalized = path.replace(/\\/g, "/");
|
|
523
|
+
return /\/\.rig\/runtime-context\.json$/.test(normalized);
|
|
768
524
|
}
|
|
769
525
|
|
|
770
|
-
// packages/runtime/src/control-plane/
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
526
|
+
// packages/runtime/src/control-plane/runtime/tooling/shell.ts
|
|
527
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
528
|
+
import { basename, dirname as dirname3, resolve as resolve3 } from "path";
|
|
529
|
+
var sharedNativeShellOutputDir = resolve3(tmpdir2(), "rig-native");
|
|
530
|
+
var sharedNativeShellOutputPath = resolve3(sharedNativeShellOutputDir, `rig-shell-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
531
|
+
function runtimeToolGatewayNames() {
|
|
532
|
+
return [
|
|
533
|
+
"bash",
|
|
534
|
+
"sh",
|
|
535
|
+
"zsh",
|
|
536
|
+
"git",
|
|
537
|
+
"bun",
|
|
538
|
+
"node",
|
|
539
|
+
"python3",
|
|
540
|
+
"rg",
|
|
541
|
+
"grep",
|
|
542
|
+
"sed",
|
|
543
|
+
"cat",
|
|
544
|
+
"ls",
|
|
545
|
+
"find",
|
|
546
|
+
"tsc",
|
|
547
|
+
"gh",
|
|
548
|
+
"mkdir",
|
|
549
|
+
"rm",
|
|
550
|
+
"mv",
|
|
551
|
+
"cp",
|
|
552
|
+
"touch",
|
|
553
|
+
"pwd",
|
|
554
|
+
"head",
|
|
555
|
+
"tail",
|
|
556
|
+
"wc",
|
|
557
|
+
"sort",
|
|
558
|
+
"uniq",
|
|
559
|
+
"awk",
|
|
560
|
+
"xargs",
|
|
561
|
+
"dirname",
|
|
562
|
+
"basename",
|
|
563
|
+
"realpath",
|
|
564
|
+
"env",
|
|
565
|
+
"jq",
|
|
566
|
+
"tee",
|
|
567
|
+
"which"
|
|
568
|
+
];
|
|
783
569
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
570
|
+
// packages/runtime/src/control-plane/runtime/tooling/file-tools.ts
|
|
571
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
572
|
+
import { basename as basename2, dirname as dirname4, resolve as resolve4 } from "path";
|
|
573
|
+
var sharedNativeToolsOutputDir = resolve4(tmpdir3(), "rig-native");
|
|
574
|
+
var sharedNativeToolsOutputPath = resolve4(sharedNativeToolsOutputDir, `rig-tools-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
575
|
+
function runtimeFileToolNames() {
|
|
576
|
+
return [
|
|
577
|
+
"rig-read",
|
|
578
|
+
"rig-write",
|
|
579
|
+
"rig-edit",
|
|
580
|
+
"rig-glob",
|
|
581
|
+
"rig-grep"
|
|
582
|
+
];
|
|
795
583
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
const rawConfig = readLegacyTaskConfigJson(projectRoot, configPath);
|
|
801
|
-
return Object.entries(stripLegacyTaskConfigMetadata(rawConfig)).map(([id, entry]) => legacyTaskConfigEntryToRecord(id, entry)).filter((record) => record !== null);
|
|
584
|
+
|
|
585
|
+
// packages/runtime/src/control-plane/runtime/tooling/gateway.ts
|
|
586
|
+
function runtimeGatewayToolNames() {
|
|
587
|
+
return runtimeToolGatewayNames();
|
|
802
588
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
589
|
+
// packages/runtime/src/control-plane/browser-contract.ts
|
|
590
|
+
import { resolve as resolve5 } from "path";
|
|
591
|
+
var DEFAULT_BROWSER_ATTACH_URL = "http://127.0.0.1:9333";
|
|
592
|
+
var DEFAULT_BROWSER_MODE = "persistent";
|
|
593
|
+
var RUNTIME_BROWSER_HELPERS = {
|
|
594
|
+
launch: "rig-browser-launch",
|
|
595
|
+
check: "rig-browser-check",
|
|
596
|
+
attachInfo: "rig-browser-attach-info",
|
|
597
|
+
e2e: "rig-browser-e2e",
|
|
598
|
+
resetProfile: "rig-browser-reset-profile"
|
|
599
|
+
};
|
|
600
|
+
var BASE_REMOTE_DEBUGGING_PORT = 9222;
|
|
601
|
+
var REMOTE_DEBUGGING_PORT_SPREAD = 4000;
|
|
602
|
+
function hashString(input) {
|
|
603
|
+
let hash = 0;
|
|
604
|
+
for (let index = 0;index < input.length; index += 1) {
|
|
605
|
+
hash = (hash << 5) - hash + input.charCodeAt(index) | 0;
|
|
817
606
|
}
|
|
607
|
+
return Math.abs(hash);
|
|
818
608
|
}
|
|
819
|
-
function
|
|
820
|
-
|
|
821
|
-
return tasks;
|
|
822
|
-
}
|
|
823
|
-
function legacyTaskConfigEntryToRecord(id, entry) {
|
|
824
|
-
if (!isPlainRecord(entry)) {
|
|
825
|
-
return null;
|
|
826
|
-
}
|
|
827
|
-
const deps = firstStringList(entry.deps, entry.dependencies, entry.validation_deps, entry.validationDeps);
|
|
828
|
-
const validation = readStringList(entry.validation);
|
|
829
|
-
const validators = readStringList(entry.validators);
|
|
830
|
-
const scope = readStringList(entry.scope);
|
|
831
|
-
const status = typeof entry.status === "string" ? entry.status : "open";
|
|
832
|
-
const title = typeof entry.title === "string" ? entry.title : undefined;
|
|
833
|
-
const description = typeof entry.description === "string" ? entry.description : undefined;
|
|
834
|
-
const acceptanceCriteria = typeof entry.acceptance_criteria === "string" ? entry.acceptance_criteria : typeof entry.acceptanceCriteria === "string" ? entry.acceptanceCriteria : undefined;
|
|
835
|
-
return {
|
|
836
|
-
id,
|
|
837
|
-
deps,
|
|
838
|
-
status,
|
|
839
|
-
source: "legacy-task-config",
|
|
840
|
-
...title ? { title } : {},
|
|
841
|
-
...description ? { description } : {},
|
|
842
|
-
...acceptanceCriteria ? { acceptanceCriteria } : {},
|
|
843
|
-
...scope.length > 0 ? { scope } : {},
|
|
844
|
-
...validation.length > 0 ? { validation } : {},
|
|
845
|
-
...validators.length > 0 ? { validators } : {},
|
|
846
|
-
...preservedLegacyFields(entry)
|
|
847
|
-
};
|
|
609
|
+
function derivePortFromProfile(profileName) {
|
|
610
|
+
return BASE_REMOTE_DEBUGGING_PORT + hashString(profileName) % REMOTE_DEBUGGING_PORT_SPREAD;
|
|
848
611
|
}
|
|
849
|
-
function
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
"repo_pins",
|
|
855
|
-
"criticality",
|
|
856
|
-
"queue_weight",
|
|
857
|
-
"creates_repo",
|
|
858
|
-
"auto_synced"
|
|
859
|
-
]) {
|
|
860
|
-
if (entry[key] !== undefined) {
|
|
861
|
-
preserved[key] = entry[key];
|
|
862
|
-
}
|
|
612
|
+
function parseAttachUrl(attachUrl) {
|
|
613
|
+
try {
|
|
614
|
+
return new URL((attachUrl || DEFAULT_BROWSER_ATTACH_URL).trim() || DEFAULT_BROWSER_ATTACH_URL);
|
|
615
|
+
} catch {
|
|
616
|
+
return new URL(DEFAULT_BROWSER_ATTACH_URL);
|
|
863
617
|
}
|
|
864
|
-
return preserved;
|
|
865
618
|
}
|
|
866
|
-
function
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
619
|
+
function sanitizeRuntimeSuffix(runtimeId) {
|
|
620
|
+
return runtimeId.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 16) || "runtime";
|
|
621
|
+
}
|
|
622
|
+
function resolveBrowserStateDir(projectRoot, configuredStateDir) {
|
|
623
|
+
const trimmed = configuredStateDir?.trim() || ".tmp/rig-browser";
|
|
624
|
+
if (trimmed.startsWith("/")) {
|
|
625
|
+
return resolve5(trimmed);
|
|
872
626
|
}
|
|
873
|
-
return
|
|
627
|
+
return resolve5(projectRoot || process.cwd(), trimmed);
|
|
874
628
|
}
|
|
875
|
-
function
|
|
876
|
-
if (!
|
|
877
|
-
return
|
|
629
|
+
function resolveTaskBrowserContext(browser, options = {}) {
|
|
630
|
+
if (!browser?.required) {
|
|
631
|
+
return;
|
|
878
632
|
}
|
|
879
|
-
|
|
633
|
+
const defaultProfile = browser.profile?.trim() || "default";
|
|
634
|
+
const mode = browser.mode?.trim() || DEFAULT_BROWSER_MODE;
|
|
635
|
+
const defaultAttach = parseAttachUrl(browser.attach_url);
|
|
636
|
+
const shouldDeriveRuntimeProfile = Boolean(options.runtimeId?.trim()) && mode !== "shared";
|
|
637
|
+
const effectiveProfile = shouldDeriveRuntimeProfile ? `${defaultProfile}-${sanitizeRuntimeSuffix(options.runtimeId.trim())}` : defaultProfile;
|
|
638
|
+
const effectivePort = shouldDeriveRuntimeProfile ? derivePortFromProfile(effectiveProfile) : Number(defaultAttach.port || "80");
|
|
639
|
+
const effectiveAttach = new URL(defaultAttach.toString());
|
|
640
|
+
effectiveAttach.port = String(effectivePort);
|
|
641
|
+
return {
|
|
642
|
+
required: true,
|
|
643
|
+
preset: browser.preset?.trim() || "default",
|
|
644
|
+
mode,
|
|
645
|
+
stateDir: resolveBrowserStateDir(options.hostProjectRoot, browser.state_dir),
|
|
646
|
+
defaultProfile,
|
|
647
|
+
effectiveProfile,
|
|
648
|
+
defaultAttachUrl: defaultAttach.toString(),
|
|
649
|
+
effectiveAttachUrl: effectiveAttach.toString(),
|
|
650
|
+
devCommand: browser.dev_command?.trim() || undefined,
|
|
651
|
+
launchCommand: browser.launch_command?.trim() || undefined,
|
|
652
|
+
checkCommand: browser.check_command?.trim() || undefined,
|
|
653
|
+
e2eCommand: browser.e2e_command?.trim() || undefined,
|
|
654
|
+
launchHelper: RUNTIME_BROWSER_HELPERS.launch,
|
|
655
|
+
checkHelper: RUNTIME_BROWSER_HELPERS.check,
|
|
656
|
+
attachInfoHelper: RUNTIME_BROWSER_HELPERS.attachInfo,
|
|
657
|
+
e2eHelper: RUNTIME_BROWSER_HELPERS.e2e,
|
|
658
|
+
resetProfileHelper: RUNTIME_BROWSER_HELPERS.resetProfile
|
|
659
|
+
};
|
|
880
660
|
}
|
|
881
|
-
function
|
|
882
|
-
|
|
661
|
+
function buildBrowserGuidanceLines(browser) {
|
|
662
|
+
const lines = [
|
|
663
|
+
"This task requires Rig Browser.",
|
|
664
|
+
`Launch the browser: \`${browser.launchHelper}\`${browser.devCommand ? " or `rig-browser-launch --dev`" : ""}.`,
|
|
665
|
+
`Check the browser contract: \`${browser.checkHelper}\`.`,
|
|
666
|
+
`Show attach details: \`${browser.attachInfoHelper}\`.`,
|
|
667
|
+
`Attach Chrome DevTools MCP to ${browser.effectiveAttachUrl}.`,
|
|
668
|
+
`Preset: ${browser.preset}.`,
|
|
669
|
+
`Profile: ${browser.effectiveProfile}.`,
|
|
670
|
+
`State dir: ${browser.stateDir}.`,
|
|
671
|
+
`Reset the active profile with \`${browser.resetProfileHelper}\`.`
|
|
672
|
+
];
|
|
673
|
+
if (browser.e2eCommand) {
|
|
674
|
+
lines.push(`Run app-owned browser e2e with \`${browser.e2eHelper}\`.`);
|
|
675
|
+
}
|
|
676
|
+
if (browser.defaultProfile !== browser.effectiveProfile) {
|
|
677
|
+
lines.push(`Base profile: ${browser.defaultProfile}. Runtime launches derive an isolated effective profile.`);
|
|
678
|
+
}
|
|
679
|
+
return lines;
|
|
883
680
|
}
|
|
884
681
|
|
|
885
|
-
// packages/runtime/src/control-plane/
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
const
|
|
892
|
-
const
|
|
893
|
-
const allowLocalFallback = options.allowLocalTaskConfigStatusFallback ?? true;
|
|
682
|
+
// packages/runtime/src/control-plane/plugin-host-context.ts
|
|
683
|
+
import { createPluginHost } from "@rig/core";
|
|
684
|
+
import { loadConfig } from "@rig/core/load-config";
|
|
685
|
+
|
|
686
|
+
// packages/runtime/src/control-plane/task-source.ts
|
|
687
|
+
function createTaskSourceRegistry() {
|
|
688
|
+
const byId = new Map;
|
|
689
|
+
const order = [];
|
|
894
690
|
return {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
901
|
-
const tasks = [];
|
|
902
|
-
const legacyTasks = await legacy.listTasks();
|
|
903
|
-
const legacyById = new Map(legacyTasks.map((task) => [task.id, task]));
|
|
904
|
-
for (const [id, rawEntry] of Object.entries(stripLegacyTaskConfigMetadata2(rawConfig))) {
|
|
905
|
-
if (!isPlainRecord2(rawEntry)) {
|
|
906
|
-
continue;
|
|
907
|
-
}
|
|
908
|
-
const metadata = readMaterializedTaskMetadata(rawEntry);
|
|
909
|
-
if (metadata.taskSource?.kind === "github-issues") {
|
|
910
|
-
tasks.push(readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry));
|
|
911
|
-
continue;
|
|
912
|
-
}
|
|
913
|
-
if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
|
|
914
|
-
const fileTask = readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
|
|
915
|
-
if (fileTask)
|
|
916
|
-
tasks.push(fileTask);
|
|
917
|
-
continue;
|
|
918
|
-
}
|
|
919
|
-
if (!allowLocalFallback) {
|
|
920
|
-
continue;
|
|
921
|
-
}
|
|
922
|
-
const legacyTask = legacyById.get(id);
|
|
923
|
-
if (legacyTask) {
|
|
924
|
-
tasks.push(legacyTask);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
return tasks;
|
|
691
|
+
register(s) {
|
|
692
|
+
if (byId.has(s.id))
|
|
693
|
+
throw new Error(`task source already registered: ${s.id}`);
|
|
694
|
+
byId.set(s.id, s);
|
|
695
|
+
order.push(s);
|
|
928
696
|
},
|
|
929
|
-
|
|
930
|
-
const
|
|
931
|
-
if (!
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
return allowLocalFallback ? legacy.getTask(id) : null;
|
|
943
|
-
}
|
|
697
|
+
resolveById(id) {
|
|
698
|
+
const s = byId.get(id);
|
|
699
|
+
if (!s)
|
|
700
|
+
throw new Error(`task source not registered: ${id}`);
|
|
701
|
+
return s;
|
|
702
|
+
},
|
|
703
|
+
resolveByKind(kind) {
|
|
704
|
+
for (const s of order)
|
|
705
|
+
if (s.kind === kind)
|
|
706
|
+
return s;
|
|
707
|
+
throw new Error(`no task source registered for kind: ${kind}`);
|
|
708
|
+
},
|
|
709
|
+
list: () => order
|
|
944
710
|
};
|
|
945
711
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
const rawSource = rawRig.taskSource;
|
|
952
|
-
const metadata = {};
|
|
953
|
-
if (isPlainRecord2(rawSource)) {
|
|
954
|
-
const kind = typeof rawSource.kind === "string" ? rawSource.kind : "";
|
|
955
|
-
if (kind.length > 0) {
|
|
956
|
-
metadata.taskSource = {
|
|
957
|
-
kind,
|
|
958
|
-
...typeof rawSource.path === "string" ? { path: rawSource.path } : {},
|
|
959
|
-
...typeof rawSource.owner === "string" ? { owner: rawSource.owner } : {},
|
|
960
|
-
...typeof rawSource.repo === "string" ? { repo: rawSource.repo } : {},
|
|
961
|
-
...Array.isArray(rawSource.labels) ? { labels: rawSource.labels.filter((label) => typeof label === "string") } : {},
|
|
962
|
-
...rawSource.state === "open" || rawSource.state === "closed" || rawSource.state === "all" ? { state: rawSource.state } : {}
|
|
963
|
-
};
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
if (typeof rawRig.sourceIssueId === "string") {
|
|
967
|
-
metadata.sourceIssueId = rawRig.sourceIssueId;
|
|
968
|
-
}
|
|
969
|
-
return metadata;
|
|
970
|
-
}
|
|
971
|
-
function readConfiguredFilesTaskSourcePath(projectRoot) {
|
|
972
|
-
const jsonPath = resolve8(projectRoot, "rig.config.json");
|
|
973
|
-
if (existsSync6(jsonPath)) {
|
|
974
|
-
try {
|
|
975
|
-
const parsed = JSON.parse(readFileSync5(jsonPath, "utf8"));
|
|
976
|
-
if (isPlainRecord2(parsed) && isPlainRecord2(parsed.taskSource)) {
|
|
977
|
-
const source = parsed.taskSource;
|
|
978
|
-
return source.kind === "files" && typeof source.path === "string" ? source.path : null;
|
|
979
|
-
}
|
|
980
|
-
} catch {
|
|
981
|
-
return null;
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
const tsPath = resolve8(projectRoot, "rig.config.ts");
|
|
985
|
-
if (!existsSync6(tsPath)) {
|
|
986
|
-
return null;
|
|
987
|
-
}
|
|
988
|
-
try {
|
|
989
|
-
const source = readFileSync5(tsPath, "utf8");
|
|
990
|
-
const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
|
|
991
|
-
const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
|
|
992
|
-
if (kind !== "files") {
|
|
993
|
-
return null;
|
|
994
|
-
}
|
|
995
|
-
return taskSourceBlock.match(/path\s*:\s*["']([^"']+)["']/)?.[1] ?? null;
|
|
996
|
-
} catch {
|
|
997
|
-
return null;
|
|
998
|
-
}
|
|
712
|
+
|
|
713
|
+
// packages/runtime/src/control-plane/task-source-bootstrap.ts
|
|
714
|
+
function formatRegisteredKinds(pluginHost) {
|
|
715
|
+
const kinds = pluginHost ? pluginHost.listExecutableTaskSources().map((source) => source.kind) : [];
|
|
716
|
+
return kinds.length > 0 ? kinds.join(", ") : "none";
|
|
999
717
|
}
|
|
1000
|
-
function
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
718
|
+
function buildTaskSourceRegistry(config, pluginHost) {
|
|
719
|
+
const registry = createTaskSourceRegistry();
|
|
720
|
+
const taskSourceConfig = config.taskSource;
|
|
721
|
+
const factory = pluginHost?.resolveTaskSourceFactoryByKind(taskSourceConfig.kind);
|
|
722
|
+
if (!factory) {
|
|
723
|
+
throw new Error(`No task source factory registered for kind "${taskSourceConfig.kind}". ` + `Registered kinds: ${formatRegisteredKinds(pluginHost)}. ` + "Load a plugin that contributes an executable task source factory for this kind.");
|
|
1004
724
|
}
|
|
1005
|
-
|
|
1006
|
-
return
|
|
725
|
+
registry.register(factory.factory(taskSourceConfig));
|
|
726
|
+
return registry;
|
|
1007
727
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
728
|
+
|
|
729
|
+
// packages/runtime/src/control-plane/repos/registry.ts
|
|
730
|
+
function createRepoRegistry(entries) {
|
|
731
|
+
const map = new Map;
|
|
732
|
+
for (const e of entries) {
|
|
733
|
+
if (map.has(e.id))
|
|
734
|
+
throw new Error(`repo already registered: ${e.id}`);
|
|
735
|
+
map.set(e.id, { ...e });
|
|
1011
736
|
}
|
|
1012
|
-
const
|
|
1013
|
-
return
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
return tasks;
|
|
737
|
+
const ordered = Array.from(map.values());
|
|
738
|
+
return {
|
|
739
|
+
getById: (id) => map.get(id),
|
|
740
|
+
list: () => ordered
|
|
741
|
+
};
|
|
1018
742
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
continue;
|
|
1028
|
-
const inferredId = basename3(name).replace(FILE_TASK_PATTERN, "");
|
|
1029
|
-
const task = readFileBackedTask(projectRoot, sourcePath, inferredId, {});
|
|
1030
|
-
if (task)
|
|
1031
|
-
tasks.push(task);
|
|
743
|
+
var MANAGED_REPOS = new Map;
|
|
744
|
+
function setManagedRepos(entries) {
|
|
745
|
+
const next = new Map;
|
|
746
|
+
for (const e of entries) {
|
|
747
|
+
if (next.has(e.id)) {
|
|
748
|
+
throw new Error(`managed repo already registered: ${e.id}`);
|
|
749
|
+
}
|
|
750
|
+
next.set(e.id, e);
|
|
1032
751
|
}
|
|
1033
|
-
|
|
752
|
+
MANAGED_REPOS = next;
|
|
1034
753
|
}
|
|
1035
|
-
function
|
|
1036
|
-
const
|
|
1037
|
-
if (!
|
|
1038
|
-
|
|
754
|
+
function getManagedRepoEntry(repoId) {
|
|
755
|
+
const entry = MANAGED_REPOS.get(repoId);
|
|
756
|
+
if (!entry) {
|
|
757
|
+
throw new Error(`managed repo not registered: ${repoId}. Plugins contribute repos via RigPlugin.contributes.repoSources; ` + `make sure a plugin declares this id and the plugin host has been initialized.`);
|
|
1039
758
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
759
|
+
return entry;
|
|
760
|
+
}
|
|
761
|
+
function listManagedRepoEntries() {
|
|
762
|
+
return Array.from(MANAGED_REPOS.values());
|
|
763
|
+
}
|
|
764
|
+
function repoRegistrationToManagedEntry(reg) {
|
|
765
|
+
if (!reg.defaultBranch) {
|
|
1042
766
|
return null;
|
|
1043
767
|
}
|
|
1044
768
|
return {
|
|
1045
|
-
id:
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
769
|
+
id: reg.id,
|
|
770
|
+
alias: reg.defaultPath ?? reg.id,
|
|
771
|
+
defaultBranch: reg.defaultBranch,
|
|
772
|
+
defaultRemoteUrl: reg.url,
|
|
773
|
+
remoteEnvVar: reg.remoteEnvVar,
|
|
774
|
+
checkoutEnvVar: reg.checkoutEnvVar
|
|
1050
775
|
};
|
|
1051
776
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
777
|
+
|
|
778
|
+
// packages/runtime/src/control-plane/agent-roles.ts
|
|
779
|
+
function createAgentRoleRegistry(pluginRoles, configOverrides) {
|
|
780
|
+
const map = new Map;
|
|
781
|
+
for (const r of pluginRoles) {
|
|
782
|
+
if (map.has(r.id))
|
|
783
|
+
throw new Error(`agent role already registered: ${r.id}`);
|
|
784
|
+
const override = configOverrides?.[r.id];
|
|
785
|
+
const model = override?.model ?? r.defaultModel;
|
|
786
|
+
if (!model) {
|
|
787
|
+
throw new Error(`agent role "${r.id}" has no model \u2014 provide defaultModel in plugin or model in config.runtime.agentRoles.${r.id}`);
|
|
788
|
+
}
|
|
789
|
+
map.set(r.id, { ...r, model });
|
|
1055
790
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
791
|
+
return {
|
|
792
|
+
resolve(id) {
|
|
793
|
+
const r = map.get(id);
|
|
794
|
+
if (!r)
|
|
795
|
+
throw new Error(`agent role not registered: ${id}`);
|
|
796
|
+
return r;
|
|
797
|
+
},
|
|
798
|
+
list: () => Array.from(map.values())
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// packages/runtime/src/control-plane/task-fields.ts
|
|
803
|
+
function createTaskFieldRegistry(extensions) {
|
|
804
|
+
const byId = new Map;
|
|
805
|
+
for (const e of extensions) {
|
|
806
|
+
if (byId.has(e.id))
|
|
807
|
+
throw new Error(`task field extension already registered: ${e.id}`);
|
|
808
|
+
byId.set(e.id, e);
|
|
1070
809
|
}
|
|
1071
|
-
return
|
|
810
|
+
return {
|
|
811
|
+
get: (id) => byId.get(id),
|
|
812
|
+
list: () => Array.from(byId.values()),
|
|
813
|
+
fieldNames: () => Array.from(byId.values()).map((e) => e.fieldName),
|
|
814
|
+
validateTaskFields(task) {
|
|
815
|
+
const errors = [];
|
|
816
|
+
for (const ext of byId.values()) {
|
|
817
|
+
let schema;
|
|
818
|
+
try {
|
|
819
|
+
schema = JSON.parse(ext.schemaJson);
|
|
820
|
+
} catch {
|
|
821
|
+
errors.push(`task field "${ext.id}": schemaJson is not valid JSON`);
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
const isRequired = typeof schema === "object" && schema !== null && schema.required === true;
|
|
825
|
+
if (!isRequired)
|
|
826
|
+
continue;
|
|
827
|
+
const value = task[ext.fieldName];
|
|
828
|
+
if (value === undefined || value === null || value === "") {
|
|
829
|
+
errors.push(`task field "${ext.fieldName}" (from extension "${ext.id}") is required but missing`);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return errors.length === 0 ? { ok: true } : { ok: false, errors };
|
|
833
|
+
}
|
|
834
|
+
};
|
|
1072
835
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
836
|
+
|
|
837
|
+
// packages/runtime/src/control-plane/validators/runtime-registration.ts
|
|
838
|
+
import { existsSync as existsSync3 } from "fs";
|
|
839
|
+
import { join } from "path";
|
|
840
|
+
function createValidatorRegistry() {
|
|
841
|
+
const map = new Map;
|
|
842
|
+
const order = [];
|
|
843
|
+
const registry = {
|
|
844
|
+
register(v) {
|
|
845
|
+
if (map.has(v.id))
|
|
846
|
+
throw new Error(`validator already registered: ${v.id}`);
|
|
847
|
+
map.set(v.id, v);
|
|
848
|
+
order.push(v);
|
|
849
|
+
},
|
|
850
|
+
resolve(id) {
|
|
851
|
+
const v = map.get(id);
|
|
852
|
+
if (!v)
|
|
853
|
+
throw new Error(`validator not registered: ${id}`);
|
|
854
|
+
return v;
|
|
855
|
+
},
|
|
856
|
+
list: () => order
|
|
857
|
+
};
|
|
858
|
+
registerBuiltInValidators(registry);
|
|
859
|
+
return registry;
|
|
1085
860
|
}
|
|
1086
|
-
function
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
return { owner: parsed[1], repo: parsed[2] };
|
|
1094
|
-
}
|
|
1095
|
-
throw new Error(`Task ${id} is marked as github-issues but has no owner/repo source metadata`);
|
|
861
|
+
function registerBuiltInValidators(registry) {
|
|
862
|
+
registry.register({
|
|
863
|
+
id: "std:typecheck",
|
|
864
|
+
category: "custom",
|
|
865
|
+
description: "Runs the package typecheck script when present.",
|
|
866
|
+
run: async (ctx) => runStdTypecheck(ctx)
|
|
867
|
+
});
|
|
1096
868
|
}
|
|
1097
|
-
function
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
869
|
+
async function runStdTypecheck(ctx) {
|
|
870
|
+
const packageJsonPath = join(ctx.workspaceRoot, "package.json");
|
|
871
|
+
if (!existsSync3(packageJsonPath)) {
|
|
872
|
+
return {
|
|
873
|
+
id: "std:typecheck",
|
|
874
|
+
passed: false,
|
|
875
|
+
summary: `package.json not found at ${packageJsonPath}`
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
const proc = Bun.spawn(["bun", "run", "typecheck"], {
|
|
879
|
+
cwd: ctx.workspaceRoot,
|
|
880
|
+
env: process.env,
|
|
881
|
+
stdout: "pipe",
|
|
882
|
+
stderr: "pipe"
|
|
883
|
+
});
|
|
884
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
885
|
+
proc.exited,
|
|
886
|
+
new Response(proc.stdout).text(),
|
|
887
|
+
new Response(proc.stderr).text()
|
|
888
|
+
]);
|
|
889
|
+
const output = `${stdout}${stderr}`.trim();
|
|
1104
890
|
return {
|
|
1105
|
-
id:
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
body,
|
|
1110
|
-
...scope.length > 0 ? { scope } : {},
|
|
1111
|
-
...roleLabel ? { role: roleLabel.slice("role:".length) } : typeof rawEntry.role === "string" ? { role: rawEntry.role } : {},
|
|
1112
|
-
...validators.length > 0 ? { validators } : {},
|
|
1113
|
-
...issue.url ? { url: issue.url } : {},
|
|
1114
|
-
issueType: issueTypeFor(labelNames),
|
|
1115
|
-
sourceIssueId: `${repo}#${issue.number}`,
|
|
1116
|
-
parentChildDeps: parseParents(body),
|
|
1117
|
-
labels: labelNames,
|
|
1118
|
-
raw: issue,
|
|
1119
|
-
source: "github-issues",
|
|
1120
|
-
_rig: {
|
|
1121
|
-
taskSource: { kind: "github-issues", owner: source.owner, repo: source.repo },
|
|
1122
|
-
sourceIssueId: `${repo}#${issue.number}`
|
|
1123
|
-
}
|
|
891
|
+
id: "std:typecheck",
|
|
892
|
+
passed: exitCode === 0,
|
|
893
|
+
summary: exitCode === 0 ? "typecheck passed" : "typecheck failed",
|
|
894
|
+
...output ? { details: output.slice(0, 4000) } : {}
|
|
1124
895
|
};
|
|
1125
896
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
if (labelNames.includes("in-progress"))
|
|
1132
|
-
return "in_progress";
|
|
1133
|
-
if (labelNames.includes("blocked"))
|
|
1134
|
-
return "blocked";
|
|
1135
|
-
if (labelNames.includes("ready"))
|
|
1136
|
-
return "ready";
|
|
1137
|
-
if (labelNames.includes("under-review"))
|
|
1138
|
-
return "under_review";
|
|
1139
|
-
if (labelNames.includes("failed"))
|
|
1140
|
-
return "failed";
|
|
1141
|
-
if (labelNames.includes("cancelled"))
|
|
1142
|
-
return "cancelled";
|
|
1143
|
-
return "open";
|
|
897
|
+
|
|
898
|
+
// packages/runtime/src/control-plane/native/scope-rules.ts
|
|
899
|
+
var activeRules = null;
|
|
900
|
+
function setScopeRules(rules) {
|
|
901
|
+
activeRules = rules ?? null;
|
|
1144
902
|
}
|
|
1145
|
-
function
|
|
1146
|
-
|
|
1147
|
-
return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
|
|
903
|
+
function getScopeRules() {
|
|
904
|
+
return activeRules;
|
|
1148
905
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
906
|
+
|
|
907
|
+
// packages/runtime/src/control-plane/hook-materializer.ts
|
|
908
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
909
|
+
import { dirname as dirname5, resolve as resolve6 } from "path";
|
|
910
|
+
var MARKER_PLUGIN = "_rigPlugin";
|
|
911
|
+
var MARKER_HOOK_ID = "_rigHookId";
|
|
912
|
+
function matcherToString(matcher) {
|
|
913
|
+
if (matcher.kind === "all")
|
|
914
|
+
return;
|
|
915
|
+
if (matcher.kind === "tool")
|
|
916
|
+
return matcher.name;
|
|
917
|
+
return matcher.pattern;
|
|
1151
918
|
}
|
|
1152
|
-
function
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
919
|
+
function isPluginOwned(cmd) {
|
|
920
|
+
return typeof cmd[MARKER_PLUGIN] === "string";
|
|
921
|
+
}
|
|
922
|
+
function materializeHooks(projectRoot, entries) {
|
|
923
|
+
const settingsPath = resolve6(projectRoot, ".claude", "settings.json");
|
|
924
|
+
const existing = existsSync4(settingsPath) ? safeReadJson(settingsPath) : {};
|
|
925
|
+
const hooks = existing.hooks ?? {};
|
|
926
|
+
for (const event of Object.keys(hooks)) {
|
|
927
|
+
const groups = hooks[event] ?? [];
|
|
928
|
+
const cleaned = [];
|
|
929
|
+
for (const group of groups) {
|
|
930
|
+
const operatorHooks = group.hooks.filter((h) => !isPluginOwned(h));
|
|
931
|
+
if (operatorHooks.length > 0) {
|
|
932
|
+
cleaned.push({ ...group, hooks: operatorHooks });
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (cleaned.length > 0) {
|
|
936
|
+
hooks[event] = cleaned;
|
|
937
|
+
} else {
|
|
938
|
+
delete hooks[event];
|
|
939
|
+
}
|
|
1157
940
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
const
|
|
1163
|
-
|
|
941
|
+
for (const { pluginName, hook } of entries) {
|
|
942
|
+
if (!hook.command) {
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
const event = hook.event;
|
|
946
|
+
const matcherString = matcherToString(hook.matcher);
|
|
947
|
+
const groups = hooks[event] ??= [];
|
|
948
|
+
let group = groups.find((g) => g.matcher === matcherString);
|
|
949
|
+
if (!group) {
|
|
950
|
+
group = matcherString === undefined ? { hooks: [] } : { matcher: matcherString, hooks: [] };
|
|
951
|
+
groups.push(group);
|
|
952
|
+
}
|
|
953
|
+
group.hooks.push({
|
|
954
|
+
type: "command",
|
|
955
|
+
command: hook.command,
|
|
956
|
+
[MARKER_PLUGIN]: pluginName,
|
|
957
|
+
[MARKER_HOOK_ID]: hook.id
|
|
958
|
+
});
|
|
1164
959
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
960
|
+
const next = { ...existing };
|
|
961
|
+
if (Object.keys(hooks).length > 0) {
|
|
962
|
+
next.hooks = hooks;
|
|
963
|
+
} else {
|
|
964
|
+
delete next.hooks;
|
|
1167
965
|
}
|
|
966
|
+
mkdirSync3(dirname5(settingsPath), { recursive: true });
|
|
967
|
+
writeFileSync3(settingsPath, `${JSON.stringify(next, null, 2)}
|
|
968
|
+
`, "utf-8");
|
|
969
|
+
return settingsPath;
|
|
1168
970
|
}
|
|
1169
|
-
function
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
}
|
|
1175
|
-
function parseIssueRefs(body, pattern) {
|
|
1176
|
-
const match = body.match(pattern);
|
|
1177
|
-
if (!match)
|
|
1178
|
-
return [];
|
|
1179
|
-
return match[1].split(",").map((value) => value.trim()).map((value) => value.replace(/^#/, "").match(/^(\d+)/)?.[1] ?? "").filter((value) => value.length > 0);
|
|
1180
|
-
}
|
|
1181
|
-
function issueTypeFor(labels) {
|
|
1182
|
-
const typed = labels.find((label) => label.startsWith("type:"));
|
|
1183
|
-
if (typed)
|
|
1184
|
-
return typed.slice("type:".length);
|
|
1185
|
-
if (labels.includes("epic"))
|
|
1186
|
-
return "epic";
|
|
1187
|
-
return "task";
|
|
1188
|
-
}
|
|
1189
|
-
function isPlainRecord2(candidate) {
|
|
1190
|
-
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
971
|
+
function safeReadJson(path) {
|
|
972
|
+
try {
|
|
973
|
+
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
974
|
+
} catch {
|
|
975
|
+
return {};
|
|
976
|
+
}
|
|
1191
977
|
}
|
|
1192
978
|
|
|
1193
|
-
// packages/runtime/src/control-plane/
|
|
1194
|
-
|
|
1195
|
-
|
|
979
|
+
// packages/runtime/src/control-plane/skill-materializer.ts
|
|
980
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, readdirSync, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
981
|
+
import { resolve as resolve7 } from "path";
|
|
982
|
+
import { loadSkill } from "@rig/skill-loader";
|
|
983
|
+
var MARKER_FILENAME = ".rig-plugin";
|
|
984
|
+
function skillDirName(id) {
|
|
985
|
+
return id.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
1196
986
|
}
|
|
1197
|
-
async function
|
|
1198
|
-
const
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
987
|
+
async function materializeSkills(projectRoot, entries) {
|
|
988
|
+
const skillsRoot = resolve7(projectRoot, ".pi", "skills");
|
|
989
|
+
if (existsSync5(skillsRoot)) {
|
|
990
|
+
for (const name of readdirSync(skillsRoot)) {
|
|
991
|
+
const dir = resolve7(skillsRoot, name);
|
|
992
|
+
if (existsSync5(resolve7(dir, MARKER_FILENAME))) {
|
|
993
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
994
|
+
}
|
|
995
|
+
}
|
|
1202
996
|
}
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
997
|
+
const written = [];
|
|
998
|
+
for (const { pluginName, skill } of entries) {
|
|
999
|
+
const sourcePath = resolve7(projectRoot, skill.path);
|
|
1000
|
+
if (!existsSync5(sourcePath)) {
|
|
1001
|
+
console.warn(`[plugin-host] skill "${skill.id}" from plugin "${pluginName}" not materialized: ${sourcePath} does not exist`);
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
let body;
|
|
1005
|
+
try {
|
|
1006
|
+
await loadSkill(sourcePath);
|
|
1007
|
+
body = readFileSync4(sourcePath, "utf-8");
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
console.warn(`[plugin-host] skill "${skill.id}" from plugin "${pluginName}" not materialized: ${err instanceof Error ? err.message : err}`);
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
const dir = resolve7(skillsRoot, skillDirName(skill.id));
|
|
1013
|
+
mkdirSync4(dir, { recursive: true });
|
|
1014
|
+
writeFileSync4(resolve7(dir, "SKILL.md"), body, "utf-8");
|
|
1015
|
+
writeFileSync4(resolve7(dir, MARKER_FILENAME), `${JSON.stringify({ plugin: pluginName, skillId: skill.id }, null, 2)}
|
|
1016
|
+
`, "utf-8");
|
|
1017
|
+
written.push({ id: skill.id, pluginName, directory: dir });
|
|
1018
|
+
}
|
|
1019
|
+
return written;
|
|
1209
1020
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1021
|
+
|
|
1022
|
+
// packages/runtime/src/control-plane/plugin-host-context.ts
|
|
1023
|
+
async function buildPluginHostContext(projectRoot) {
|
|
1024
|
+
let config;
|
|
1025
|
+
try {
|
|
1026
|
+
config = await loadConfig(projectRoot);
|
|
1027
|
+
} catch (err) {
|
|
1028
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1029
|
+
if (msg.includes("no rig.config")) {
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
throw err;
|
|
1033
|
+
}
|
|
1034
|
+
const pluginHost = createPluginHost(config.plugins);
|
|
1035
|
+
setScopeRules(config.workspace.scopeNormalization);
|
|
1036
|
+
const validatorRegistry = createValidatorRegistry();
|
|
1037
|
+
for (const impl of pluginHost.listExecutableValidators()) {
|
|
1038
|
+
validatorRegistry.register(impl);
|
|
1039
|
+
}
|
|
1040
|
+
const taskSourceRegistry = buildTaskSourceRegistry(config, pluginHost);
|
|
1041
|
+
const repoRegistry = createRepoRegistry(pluginHost.listRepoSources());
|
|
1042
|
+
const managedEntries = pluginHost.listRepoSources().map(repoRegistrationToManagedEntry).filter((e) => e !== null);
|
|
1043
|
+
setManagedRepos(managedEntries);
|
|
1044
|
+
const configRoleOverrides = config.runtime?.agentRoles;
|
|
1045
|
+
const agentRoleRegistry = createAgentRoleRegistry(pluginHost.listAgentRoles(), configRoleOverrides);
|
|
1046
|
+
const taskFieldRegistry = createTaskFieldRegistry(pluginHost.listTaskFieldExtensions());
|
|
1047
|
+
try {
|
|
1048
|
+
const hookEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.hooks ?? []).map((hook) => ({
|
|
1049
|
+
pluginName: plugin.name,
|
|
1050
|
+
hook
|
|
1051
|
+
})));
|
|
1052
|
+
if (hookEntries.length > 0) {
|
|
1053
|
+
materializeHooks(projectRoot, hookEntries);
|
|
1054
|
+
}
|
|
1055
|
+
} catch (err) {
|
|
1056
|
+
console.warn(`[plugin-host] hook materialization failed: ${err instanceof Error ? err.message : err}`);
|
|
1057
|
+
}
|
|
1058
|
+
try {
|
|
1059
|
+
const skillEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.skills ?? []).map((skill) => ({
|
|
1060
|
+
pluginName: plugin.name,
|
|
1061
|
+
skill
|
|
1062
|
+
})));
|
|
1063
|
+
if (skillEntries.length > 0) {
|
|
1064
|
+
await materializeSkills(projectRoot, skillEntries);
|
|
1065
|
+
}
|
|
1066
|
+
} catch (err) {
|
|
1067
|
+
console.warn(`[plugin-host] skill materialization failed: ${err instanceof Error ? err.message : err}`);
|
|
1068
|
+
}
|
|
1215
1069
|
return {
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1070
|
+
config,
|
|
1071
|
+
pluginHost,
|
|
1072
|
+
validatorRegistry,
|
|
1073
|
+
taskSourceRegistry,
|
|
1074
|
+
repoRegistry,
|
|
1075
|
+
agentRoleRegistry,
|
|
1076
|
+
taskFieldRegistry
|
|
1219
1077
|
};
|
|
1220
1078
|
}
|
|
1221
1079
|
|
|
1222
|
-
// packages/runtime/src/control-plane/
|
|
1223
|
-
import {
|
|
1224
|
-
import {
|
|
1080
|
+
// packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
|
|
1081
|
+
import { spawnSync } from "child_process";
|
|
1082
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync5 } from "fs";
|
|
1083
|
+
import { basename as basename3, join as join2, resolve as resolve9 } from "path";
|
|
1225
1084
|
|
|
1226
|
-
// packages/runtime/src/control-plane/
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
"under_review",
|
|
1235
|
-
"blocked",
|
|
1236
|
-
"completed",
|
|
1237
|
-
"cancelled"
|
|
1238
|
-
]);
|
|
1239
|
-
function normalizeTaskLifecycleStatus(status) {
|
|
1240
|
-
switch (status) {
|
|
1241
|
-
case "draft":
|
|
1242
|
-
case "open":
|
|
1243
|
-
case "ready":
|
|
1244
|
-
case "queued":
|
|
1245
|
-
case "in_progress":
|
|
1246
|
-
case "under_review":
|
|
1247
|
-
case "blocked":
|
|
1248
|
-
case "completed":
|
|
1249
|
-
case "cancelled":
|
|
1250
|
-
return status;
|
|
1251
|
-
case "closed":
|
|
1252
|
-
return "completed";
|
|
1253
|
-
case "running":
|
|
1254
|
-
return "in_progress";
|
|
1255
|
-
case "failed":
|
|
1256
|
-
return "ready";
|
|
1257
|
-
default:
|
|
1258
|
-
return null;
|
|
1259
|
-
}
|
|
1085
|
+
// packages/runtime/src/control-plane/tasks/legacy-task-config-source.ts
|
|
1086
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
1087
|
+
import { resolve as resolve8 } from "path";
|
|
1088
|
+
|
|
1089
|
+
// packages/runtime/src/control-plane/tasks/task-record-reader.ts
|
|
1090
|
+
async function findTaskById(reader, id) {
|
|
1091
|
+
const tasks = await reader.listTasks();
|
|
1092
|
+
return tasks.find((task) => task.id === id) ?? null;
|
|
1260
1093
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1094
|
+
|
|
1095
|
+
// packages/runtime/src/control-plane/tasks/legacy-task-config-source.ts
|
|
1096
|
+
class LegacyTaskConfigReadError extends Error {
|
|
1097
|
+
code = "LEGACY_TASK_CONFIG_READ_FAILED";
|
|
1098
|
+
projectRoot;
|
|
1099
|
+
configPath;
|
|
1100
|
+
cause;
|
|
1101
|
+
constructor(input) {
|
|
1102
|
+
super(input.message, { cause: input.cause });
|
|
1103
|
+
this.name = "LegacyTaskConfigReadError";
|
|
1104
|
+
this.projectRoot = input.projectRoot;
|
|
1105
|
+
this.configPath = input.configPath;
|
|
1106
|
+
this.cause = input.cause;
|
|
1264
1107
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
return;
|
|
1108
|
+
}
|
|
1109
|
+
function createLegacyTaskConfigRecordReader(projectRoot, options = {}) {
|
|
1110
|
+
const configPath = options.configPath ?? resolve8(projectRoot, ".rig", "task-config.json");
|
|
1111
|
+
const reader = {
|
|
1112
|
+
async listTasks() {
|
|
1113
|
+
return readLegacyTaskRecords(projectRoot, configPath);
|
|
1114
|
+
},
|
|
1115
|
+
async getTask(id) {
|
|
1116
|
+
return findTaskById(reader, id);
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
return reader;
|
|
1120
|
+
}
|
|
1121
|
+
function readLegacyTaskRecords(projectRoot, configPath = resolve8(projectRoot, ".rig", "task-config.json")) {
|
|
1122
|
+
if (!existsSync6(configPath)) {
|
|
1123
|
+
return [];
|
|
1274
1124
|
}
|
|
1125
|
+
const rawConfig = readLegacyTaskConfigJson(projectRoot, configPath);
|
|
1126
|
+
return Object.entries(stripLegacyTaskConfigMetadata(rawConfig)).map(([id, entry]) => legacyTaskConfigEntryToRecord(id, entry)).filter((record) => record !== null);
|
|
1275
1127
|
}
|
|
1276
|
-
function
|
|
1277
|
-
|
|
1278
|
-
|
|
1128
|
+
function readLegacyTaskConfigJson(projectRoot, configPath) {
|
|
1129
|
+
try {
|
|
1130
|
+
const parsed = JSON.parse(readFileSync5(configPath, "utf8"));
|
|
1131
|
+
if (isPlainRecord(parsed)) {
|
|
1132
|
+
return parsed;
|
|
1133
|
+
}
|
|
1134
|
+
throw new Error("task config root must be a JSON object");
|
|
1135
|
+
} catch (cause) {
|
|
1136
|
+
throw new LegacyTaskConfigReadError({
|
|
1137
|
+
projectRoot,
|
|
1138
|
+
configPath,
|
|
1139
|
+
message: `Could not read legacy task config at ${configPath} for project ${projectRoot}: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1140
|
+
cause
|
|
1141
|
+
});
|
|
1279
1142
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1143
|
+
}
|
|
1144
|
+
function stripLegacyTaskConfigMetadata(raw) {
|
|
1145
|
+
const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
|
|
1146
|
+
return tasks;
|
|
1147
|
+
}
|
|
1148
|
+
function legacyTaskConfigEntryToRecord(id, entry) {
|
|
1149
|
+
if (!isPlainRecord(entry)) {
|
|
1284
1150
|
return null;
|
|
1285
1151
|
}
|
|
1152
|
+
const deps = firstStringList(entry.deps, entry.dependencies, entry.validation_deps, entry.validationDeps);
|
|
1153
|
+
const validation = readStringList(entry.validation);
|
|
1154
|
+
const validators = readStringList(entry.validators);
|
|
1155
|
+
const scope = readStringList(entry.scope);
|
|
1156
|
+
const status = typeof entry.status === "string" ? entry.status : "open";
|
|
1157
|
+
const title = typeof entry.title === "string" ? entry.title : undefined;
|
|
1158
|
+
const description = typeof entry.description === "string" ? entry.description : undefined;
|
|
1159
|
+
const acceptanceCriteria = typeof entry.acceptance_criteria === "string" ? entry.acceptance_criteria : typeof entry.acceptanceCriteria === "string" ? entry.acceptanceCriteria : undefined;
|
|
1286
1160
|
return {
|
|
1287
|
-
|
|
1161
|
+
id,
|
|
1162
|
+
deps,
|
|
1288
1163
|
status,
|
|
1289
|
-
|
|
1290
|
-
...
|
|
1291
|
-
...
|
|
1292
|
-
...
|
|
1293
|
-
...
|
|
1294
|
-
...
|
|
1295
|
-
...
|
|
1296
|
-
...
|
|
1297
|
-
...typeof metadata.blockerReason === "string" && metadata.blockerReason.length > 0 ? { blockerReason: metadata.blockerReason } : {},
|
|
1298
|
-
...typeof metadata.sourceCommit === "string" && metadata.sourceCommit.length > 0 ? { sourceCommit: metadata.sourceCommit } : {}
|
|
1164
|
+
source: "legacy-task-config",
|
|
1165
|
+
...title ? { title } : {},
|
|
1166
|
+
...description ? { description } : {},
|
|
1167
|
+
...acceptanceCriteria ? { acceptanceCriteria } : {},
|
|
1168
|
+
...scope.length > 0 ? { scope } : {},
|
|
1169
|
+
...validation.length > 0 ? { validation } : {},
|
|
1170
|
+
...validators.length > 0 ? { validators } : {},
|
|
1171
|
+
...preservedLegacyFields(entry)
|
|
1299
1172
|
};
|
|
1300
1173
|
}
|
|
1301
|
-
function
|
|
1302
|
-
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1174
|
+
function preservedLegacyFields(entry) {
|
|
1175
|
+
const preserved = {};
|
|
1176
|
+
for (const key of [
|
|
1177
|
+
"role",
|
|
1178
|
+
"browser",
|
|
1179
|
+
"repo_pins",
|
|
1180
|
+
"criticality",
|
|
1181
|
+
"queue_weight",
|
|
1182
|
+
"creates_repo",
|
|
1183
|
+
"auto_synced"
|
|
1184
|
+
]) {
|
|
1185
|
+
if (entry[key] !== undefined) {
|
|
1186
|
+
preserved[key] = entry[key];
|
|
1187
|
+
}
|
|
1310
1188
|
}
|
|
1311
|
-
return
|
|
1189
|
+
return preserved;
|
|
1312
1190
|
}
|
|
1313
|
-
function
|
|
1314
|
-
|
|
1315
|
-
|
|
1191
|
+
function firstStringList(...candidates) {
|
|
1192
|
+
for (const candidate of candidates) {
|
|
1193
|
+
const list = readStringList(candidate);
|
|
1194
|
+
if (list.length > 0) {
|
|
1195
|
+
return list;
|
|
1196
|
+
}
|
|
1316
1197
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1198
|
+
return [];
|
|
1199
|
+
}
|
|
1200
|
+
function readStringList(candidate) {
|
|
1201
|
+
if (!Array.isArray(candidate)) {
|
|
1202
|
+
return [];
|
|
1321
1203
|
}
|
|
1322
|
-
|
|
1323
|
-
const tasks = Object.fromEntries(Object.entries(rawTasks).map(([taskId, metadata]) => [taskId, canonicalizeTaskStateMetadata(metadata)]).filter((entry) => entry[1] != null));
|
|
1324
|
-
return {
|
|
1325
|
-
schemaVersion,
|
|
1326
|
-
supported: true,
|
|
1327
|
-
baseTrackerCommit: typeof envelope.baseTrackerCommit === "string" && envelope.baseTrackerCommit.length > 0 ? envelope.baseTrackerCommit : null,
|
|
1328
|
-
tasks
|
|
1329
|
-
};
|
|
1204
|
+
return candidate.filter((value) => typeof value === "string");
|
|
1330
1205
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
import { resolve as resolve15 } from "path";
|
|
1334
|
-
|
|
1335
|
-
// packages/runtime/src/control-plane/native/git-native.ts
|
|
1336
|
-
import { chmodSync, copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
1337
|
-
import { tmpdir as tmpdir3 } from "os";
|
|
1338
|
-
import { dirname as dirname5, isAbsolute, resolve as resolve9 } from "path";
|
|
1339
|
-
import { createHash } from "crypto";
|
|
1340
|
-
function isTextTreeCommitUpdate(update) {
|
|
1341
|
-
return typeof update.content === "string";
|
|
1206
|
+
function isPlainRecord(candidate) {
|
|
1207
|
+
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
1342
1208
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
var
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1209
|
+
|
|
1210
|
+
// packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
|
|
1211
|
+
var STATUS_LABELS = new Set(["ready", "blocked", "in-progress", "under-review", "failed", "cancelled"]);
|
|
1212
|
+
var FILE_TASK_PATTERN = /\.(task\.)?json$/;
|
|
1213
|
+
function createSourceAwareTaskConfigRecordReader(projectRoot, options = {}) {
|
|
1214
|
+
const configPath = options.configPath ?? resolve9(projectRoot, ".rig", "task-config.json");
|
|
1215
|
+
const legacy = createLegacyTaskConfigRecordReader(projectRoot, { configPath });
|
|
1216
|
+
const spawnFn = options.spawn ?? spawnSync;
|
|
1217
|
+
const ghBinary = options.ghBinary ?? "gh";
|
|
1218
|
+
const allowLocalFallback = options.allowLocalTaskConfigStatusFallback ?? true;
|
|
1219
|
+
return {
|
|
1220
|
+
async listTasks() {
|
|
1221
|
+
const rawConfig = readRawTaskConfig(configPath);
|
|
1222
|
+
if (!rawConfig) {
|
|
1223
|
+
const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
|
|
1224
|
+
return configuredFilesPath ? listFileBackedTasks(projectRoot, configuredFilesPath) : [];
|
|
1225
|
+
}
|
|
1226
|
+
const tasks = [];
|
|
1227
|
+
const legacyTasks = await legacy.listTasks();
|
|
1228
|
+
const legacyById = new Map(legacyTasks.map((task) => [task.id, task]));
|
|
1229
|
+
for (const [id, rawEntry] of Object.entries(stripLegacyTaskConfigMetadata2(rawConfig))) {
|
|
1230
|
+
if (!isPlainRecord2(rawEntry)) {
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
const metadata = readMaterializedTaskMetadata(rawEntry);
|
|
1234
|
+
if (metadata.taskSource?.kind === "github-issues") {
|
|
1235
|
+
tasks.push(readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry));
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
|
|
1239
|
+
const fileTask = readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
|
|
1240
|
+
if (fileTask)
|
|
1241
|
+
tasks.push(fileTask);
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
if (!allowLocalFallback) {
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
const legacyTask = legacyById.get(id);
|
|
1248
|
+
if (legacyTask) {
|
|
1249
|
+
tasks.push(legacyTask);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
return tasks;
|
|
1253
|
+
},
|
|
1254
|
+
async getTask(id) {
|
|
1255
|
+
const rawEntry = readRawTaskEntry(configPath, id);
|
|
1256
|
+
if (!rawEntry) {
|
|
1257
|
+
const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
|
|
1258
|
+
return configuredFilesPath ? readFileBackedTask(projectRoot, configuredFilesPath, id, {}) : null;
|
|
1259
|
+
}
|
|
1260
|
+
const metadata = readMaterializedTaskMetadata(rawEntry);
|
|
1261
|
+
if (metadata.taskSource?.kind === "github-issues") {
|
|
1262
|
+
return readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry);
|
|
1263
|
+
}
|
|
1264
|
+
if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
|
|
1265
|
+
return readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
|
|
1266
|
+
}
|
|
1267
|
+
return allowLocalFallback ? legacy.getTask(id) : null;
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1349
1270
|
}
|
|
1350
|
-
function
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1271
|
+
function readMaterializedTaskMetadata(entry) {
|
|
1272
|
+
const rawRig = entry._rig;
|
|
1273
|
+
if (!isPlainRecord2(rawRig)) {
|
|
1274
|
+
return {};
|
|
1275
|
+
}
|
|
1276
|
+
const rawSource = rawRig.taskSource;
|
|
1277
|
+
const metadata = {};
|
|
1278
|
+
if (isPlainRecord2(rawSource)) {
|
|
1279
|
+
const kind = typeof rawSource.kind === "string" ? rawSource.kind : "";
|
|
1280
|
+
if (kind.length > 0) {
|
|
1281
|
+
metadata.taskSource = {
|
|
1282
|
+
kind,
|
|
1283
|
+
...typeof rawSource.path === "string" ? { path: rawSource.path } : {},
|
|
1284
|
+
...typeof rawSource.owner === "string" ? { owner: rawSource.owner } : {},
|
|
1285
|
+
...typeof rawSource.repo === "string" ? { repo: rawSource.repo } : {},
|
|
1286
|
+
...Array.isArray(rawSource.labels) ? { labels: rawSource.labels.filter((label) => typeof label === "string") } : {},
|
|
1287
|
+
...rawSource.state === "open" || rawSource.state === "closed" || rawSource.state === "all" ? { state: rawSource.state } : {}
|
|
1288
|
+
};
|
|
1358
1289
|
}
|
|
1359
|
-
throw error;
|
|
1360
1290
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
return `rig-git${process.platform === "win32" ? ".exe" : ""}`;
|
|
1364
|
-
}
|
|
1365
|
-
function rigGitSourceCandidates() {
|
|
1366
|
-
const execDir = process.execPath?.trim() ? dirname5(process.execPath.trim()) : "";
|
|
1367
|
-
const cwd = process.cwd()?.trim() || "";
|
|
1368
|
-
const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
|
|
1369
|
-
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
|
|
1370
|
-
const moduleRelativeSource = resolve9(import.meta.dir, "../../../native/rig-git.zig");
|
|
1371
|
-
return [...new Set([
|
|
1372
|
-
process.env.RIG_NATIVE_GIT_SOURCE?.trim() || "",
|
|
1373
|
-
moduleRelativeSource,
|
|
1374
|
-
projectRoot ? resolve9(projectRoot, "packages/runtime/native/rig-git.zig") : "",
|
|
1375
|
-
hostProjectRoot ? resolve9(hostProjectRoot, "packages/runtime/native/rig-git.zig") : "",
|
|
1376
|
-
cwd ? resolve9(cwd, "packages/runtime/native/rig-git.zig") : "",
|
|
1377
|
-
execDir ? resolve9(execDir, "..", "..", "packages/runtime/native/rig-git.zig") : "",
|
|
1378
|
-
execDir ? resolve9(execDir, "..", "native", "rig-git.zig") : ""
|
|
1379
|
-
].filter(Boolean))];
|
|
1380
|
-
}
|
|
1381
|
-
function nativePackageBinaryCandidates(fromDir, fileName) {
|
|
1382
|
-
const candidates = [];
|
|
1383
|
-
let cursor = resolve9(fromDir);
|
|
1384
|
-
for (let index = 0;index < 8; index += 1) {
|
|
1385
|
-
candidates.push(resolve9(cursor, "native", `${process.platform}-${process.arch}`, fileName), resolve9(cursor, "native", `${process.platform}-${process.arch}`, "bin", fileName), resolve9(cursor, "native", fileName), resolve9(cursor, "native", "bin", fileName));
|
|
1386
|
-
const parent = dirname5(cursor);
|
|
1387
|
-
if (parent === cursor)
|
|
1388
|
-
break;
|
|
1389
|
-
cursor = parent;
|
|
1291
|
+
if (typeof rawRig.sourceIssueId === "string") {
|
|
1292
|
+
metadata.sourceIssueId = rawRig.sourceIssueId;
|
|
1390
1293
|
}
|
|
1391
|
-
return
|
|
1392
|
-
}
|
|
1393
|
-
function rigGitBinaryCandidates() {
|
|
1394
|
-
const execDir = process.execPath?.trim() ? dirname5(process.execPath.trim()) : "";
|
|
1395
|
-
const fileName = runtimeRigGitFileName();
|
|
1396
|
-
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
1397
|
-
return [...new Set([
|
|
1398
|
-
explicit,
|
|
1399
|
-
...nativePackageBinaryCandidates(import.meta.dir, fileName),
|
|
1400
|
-
execDir ? resolve9(execDir, fileName) : "",
|
|
1401
|
-
execDir ? resolve9(execDir, "..", fileName) : "",
|
|
1402
|
-
execDir ? resolve9(execDir, "..", "bin", fileName) : "",
|
|
1403
|
-
sharedGitNativeOutputPath
|
|
1404
|
-
].filter(Boolean))];
|
|
1294
|
+
return metadata;
|
|
1405
1295
|
}
|
|
1406
|
-
function
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1296
|
+
function readConfiguredFilesTaskSourcePath(projectRoot) {
|
|
1297
|
+
const jsonPath = resolve9(projectRoot, "rig.config.json");
|
|
1298
|
+
if (existsSync7(jsonPath)) {
|
|
1299
|
+
try {
|
|
1300
|
+
const parsed = JSON.parse(readFileSync6(jsonPath, "utf8"));
|
|
1301
|
+
if (isPlainRecord2(parsed) && isPlainRecord2(parsed.taskSource)) {
|
|
1302
|
+
const source = parsed.taskSource;
|
|
1303
|
+
return source.kind === "files" && typeof source.path === "string" ? source.path : null;
|
|
1304
|
+
}
|
|
1305
|
+
} catch {
|
|
1306
|
+
return null;
|
|
1410
1307
|
}
|
|
1411
1308
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
function resolveGitBinaryPath() {
|
|
1415
|
-
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
1309
|
+
const tsPath = resolve9(projectRoot, "rig.config.ts");
|
|
1310
|
+
if (!existsSync7(tsPath)) {
|
|
1416
1311
|
return null;
|
|
1417
1312
|
}
|
|
1418
|
-
for (const candidate of rigGitBinaryCandidates()) {
|
|
1419
|
-
if (candidate && existsSync7(candidate)) {
|
|
1420
|
-
return candidate;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
return null;
|
|
1424
|
-
}
|
|
1425
|
-
function preferredGitBinaryOutputPath() {
|
|
1426
|
-
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
1427
|
-
return explicit || sharedGitNativeOutputPath;
|
|
1428
|
-
}
|
|
1429
|
-
function binarySupportsTrackerCommandsSync(binaryPath) {
|
|
1430
1313
|
try {
|
|
1431
|
-
const
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
const stderr = probe.stderr.toString().trim();
|
|
1437
|
-
if (stdout.includes('"error":"unknown command"')) {
|
|
1438
|
-
return false;
|
|
1314
|
+
const source = readFileSync6(tsPath, "utf8");
|
|
1315
|
+
const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
|
|
1316
|
+
const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
|
|
1317
|
+
if (kind !== "files") {
|
|
1318
|
+
return null;
|
|
1439
1319
|
}
|
|
1440
|
-
return
|
|
1320
|
+
return taskSourceBlock.match(/path\s*:\s*["']([^"']+)["']/)?.[1] ?? null;
|
|
1441
1321
|
} catch {
|
|
1442
|
-
return
|
|
1322
|
+
return null;
|
|
1443
1323
|
}
|
|
1444
1324
|
}
|
|
1445
|
-
function
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
if (!existsSync7(manifestPath)) {
|
|
1450
|
-
return false;
|
|
1325
|
+
function readRawTaskEntry(configPath, taskId) {
|
|
1326
|
+
const rawConfig = readRawTaskConfig(configPath);
|
|
1327
|
+
if (!rawConfig) {
|
|
1328
|
+
return null;
|
|
1451
1329
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1330
|
+
const entry = stripLegacyTaskConfigMetadata2(rawConfig)[taskId];
|
|
1331
|
+
return isPlainRecord2(entry) ? entry : null;
|
|
1332
|
+
}
|
|
1333
|
+
function readRawTaskConfig(configPath) {
|
|
1334
|
+
if (!existsSync7(configPath)) {
|
|
1335
|
+
return null;
|
|
1457
1336
|
}
|
|
1337
|
+
const parsed = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
1338
|
+
return isPlainRecord2(parsed) ? parsed : null;
|
|
1458
1339
|
}
|
|
1459
|
-
function
|
|
1460
|
-
|
|
1340
|
+
function stripLegacyTaskConfigMetadata2(raw) {
|
|
1341
|
+
const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
|
|
1342
|
+
return tasks;
|
|
1461
1343
|
}
|
|
1462
|
-
function
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
const sourcePath = resolveGitSourcePath();
|
|
1467
|
-
if (!sourcePath) {
|
|
1468
|
-
const binaryPath = resolveGitBinaryPath();
|
|
1469
|
-
if (binaryPath) {
|
|
1470
|
-
return binaryPath;
|
|
1471
|
-
}
|
|
1472
|
-
throw new Error("rig-git.zig source file not found.");
|
|
1473
|
-
}
|
|
1474
|
-
const zigBinary = Bun.which("zig");
|
|
1475
|
-
if (!zigBinary) {
|
|
1476
|
-
throw new Error("zig is required to build native Rig git tools.");
|
|
1477
|
-
}
|
|
1478
|
-
mkdirSync4(dirname5(outputPath), { recursive: true });
|
|
1479
|
-
const sourceDigest = sha256FileSync(sourcePath);
|
|
1480
|
-
const buildKey = JSON.stringify({
|
|
1481
|
-
version: 1,
|
|
1482
|
-
zigBinary,
|
|
1483
|
-
platform: process.platform,
|
|
1484
|
-
arch: process.arch,
|
|
1485
|
-
sourcePath,
|
|
1486
|
-
sourceDigest
|
|
1487
|
-
});
|
|
1488
|
-
const manifestPath = nativeBuildManifestPath(outputPath);
|
|
1489
|
-
const needsBuild = !existsSync7(outputPath) || !hasMatchingNativeBuildManifestSync(manifestPath, buildKey) || !binarySupportsTrackerCommandsSync(outputPath);
|
|
1490
|
-
if (!needsBuild) {
|
|
1491
|
-
chmodSync(outputPath, 493);
|
|
1492
|
-
return outputPath;
|
|
1344
|
+
function listFileBackedTasks(projectRoot, sourcePath) {
|
|
1345
|
+
const directory = resolve9(projectRoot, sourcePath);
|
|
1346
|
+
if (!existsSync7(directory)) {
|
|
1347
|
+
return [];
|
|
1493
1348
|
}
|
|
1494
|
-
const
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
], {
|
|
1503
|
-
cwd: dirname5(sourcePath),
|
|
1504
|
-
stdout: "pipe",
|
|
1505
|
-
stderr: "pipe"
|
|
1506
|
-
});
|
|
1507
|
-
if (build.exitCode !== 0 || !existsSync7(tempOutputPath)) {
|
|
1508
|
-
const stderr = build.stderr.toString().trim();
|
|
1509
|
-
const stdout = build.stdout.toString().trim();
|
|
1510
|
-
const details = [stderr, stdout].filter(Boolean).join(`
|
|
1511
|
-
`);
|
|
1512
|
-
throw new Error(`Failed to build native Rig git tools: ${details || `zig exited with code ${build.exitCode}`}`);
|
|
1349
|
+
const tasks = [];
|
|
1350
|
+
for (const name of readdirSync2(directory)) {
|
|
1351
|
+
if (!FILE_TASK_PATTERN.test(name))
|
|
1352
|
+
continue;
|
|
1353
|
+
const inferredId = basename3(name).replace(FILE_TASK_PATTERN, "");
|
|
1354
|
+
const task = readFileBackedTask(projectRoot, sourcePath, inferredId, {});
|
|
1355
|
+
if (task)
|
|
1356
|
+
tasks.push(task);
|
|
1513
1357
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1358
|
+
return tasks;
|
|
1359
|
+
}
|
|
1360
|
+
function readFileBackedTask(projectRoot, sourcePath, taskId, rawEntry) {
|
|
1361
|
+
const file = findFileBackedTaskFile(resolve9(projectRoot, sourcePath), taskId);
|
|
1362
|
+
if (!file) {
|
|
1363
|
+
return null;
|
|
1519
1364
|
}
|
|
1520
|
-
|
|
1521
|
-
if (!
|
|
1522
|
-
|
|
1523
|
-
throw new Error("Failed to build native Rig git tools: tracker command probe failed");
|
|
1365
|
+
const raw = JSON.parse(readFileSync6(file, "utf8"));
|
|
1366
|
+
if (!isPlainRecord2(raw)) {
|
|
1367
|
+
return null;
|
|
1524
1368
|
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1369
|
+
return {
|
|
1370
|
+
id: typeof raw.id === "string" ? raw.id : taskId,
|
|
1371
|
+
deps: Array.isArray(raw.deps) ? raw.deps : Array.isArray(raw.depends_on) ? raw.depends_on : [],
|
|
1372
|
+
status: typeof raw.status === "string" ? raw.status : "ready",
|
|
1373
|
+
title: typeof raw.title === "string" ? raw.title : typeof rawEntry.title === "string" ? rawEntry.title : taskId,
|
|
1374
|
+
...raw
|
|
1375
|
+
};
|
|
1528
1376
|
}
|
|
1529
|
-
function
|
|
1530
|
-
if (
|
|
1531
|
-
return
|
|
1377
|
+
function findFileBackedTaskFile(directory, taskId) {
|
|
1378
|
+
if (!existsSync7(directory)) {
|
|
1379
|
+
return null;
|
|
1532
1380
|
}
|
|
1533
|
-
const
|
|
1534
|
-
|
|
1535
|
-
|
|
1381
|
+
for (const name of readdirSync2(directory)) {
|
|
1382
|
+
if (!FILE_TASK_PATTERN.test(name))
|
|
1383
|
+
continue;
|
|
1384
|
+
const file = join2(directory, name);
|
|
1536
1385
|
try {
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
}
|
|
1545
|
-
} else {
|
|
1546
|
-
const explicitBinaryPath = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
1547
|
-
binaryPath = explicitBinaryPath && existsSync7(explicitBinaryPath) ? explicitBinaryPath : !explicitBinaryPath ? resolveGitBinaryPath() : null;
|
|
1548
|
-
if (!binaryPath) {
|
|
1549
|
-
try {
|
|
1550
|
-
binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
|
|
1551
|
-
} catch (error) {
|
|
1552
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1553
|
-
if (message.includes("rig-git.zig source file not found")) {
|
|
1554
|
-
return { ok: false, error: "rig-git binary not found" };
|
|
1555
|
-
}
|
|
1556
|
-
return { ok: false, error: message };
|
|
1386
|
+
if (!statSync(file).isFile())
|
|
1387
|
+
continue;
|
|
1388
|
+
const raw = JSON.parse(readFileSync6(file, "utf8"));
|
|
1389
|
+
const inferredId = basename3(file).replace(FILE_TASK_PATTERN, "");
|
|
1390
|
+
const id = isPlainRecord2(raw) && typeof raw.id === "string" ? raw.id : inferredId;
|
|
1391
|
+
if (id === taskId) {
|
|
1392
|
+
return file;
|
|
1557
1393
|
}
|
|
1558
|
-
}
|
|
1394
|
+
} catch {}
|
|
1559
1395
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1396
|
+
return null;
|
|
1397
|
+
}
|
|
1398
|
+
function readGithubIssueTask(bin, spawnFn, id, metadata, rawEntry) {
|
|
1399
|
+
const source = requireGithubIssueSource(metadata, id);
|
|
1400
|
+
const issue = runGh(bin, [
|
|
1401
|
+
"issue",
|
|
1402
|
+
"view",
|
|
1403
|
+
String(id),
|
|
1404
|
+
"--repo",
|
|
1405
|
+
`${source.owner}/${source.repo}`,
|
|
1406
|
+
"--json",
|
|
1407
|
+
"number,title,body,labels,state,url,assignees"
|
|
1408
|
+
], spawnFn);
|
|
1409
|
+
return githubIssueToTask(issue, source, rawEntry);
|
|
1410
|
+
}
|
|
1411
|
+
function requireGithubIssueSource(metadata, id) {
|
|
1412
|
+
const source = metadata.taskSource;
|
|
1413
|
+
if (source?.kind === "github-issues" && source.owner && source.repo) {
|
|
1414
|
+
return { owner: source.owner, repo: source.repo };
|
|
1415
|
+
}
|
|
1416
|
+
const parsed = metadata.sourceIssueId?.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
1417
|
+
if (parsed && parsed[3] === id) {
|
|
1418
|
+
return { owner: parsed[1], repo: parsed[2] };
|
|
1419
|
+
}
|
|
1420
|
+
throw new Error(`Task ${id} is marked as github-issues but has no owner/repo source metadata`);
|
|
1421
|
+
}
|
|
1422
|
+
function githubIssueToTask(issue, source, rawEntry) {
|
|
1423
|
+
const labelNames = (issue.labels ?? []).map((label) => label.name);
|
|
1424
|
+
const scope = labelNames.filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length));
|
|
1425
|
+
const roleLabel = labelNames.find((label) => label.startsWith("role:"));
|
|
1426
|
+
const validators = labelNames.filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length));
|
|
1427
|
+
const body = issue.body ?? "";
|
|
1428
|
+
const repo = `${source.owner}/${source.repo}`;
|
|
1429
|
+
return {
|
|
1430
|
+
id: String(issue.number),
|
|
1431
|
+
deps: parseDeps(body),
|
|
1432
|
+
status: githubStatusFor(issue),
|
|
1433
|
+
title: issue.title,
|
|
1434
|
+
body,
|
|
1435
|
+
...scope.length > 0 ? { scope } : {},
|
|
1436
|
+
...roleLabel ? { role: roleLabel.slice("role:".length) } : typeof rawEntry.role === "string" ? { role: rawEntry.role } : {},
|
|
1437
|
+
...validators.length > 0 ? { validators } : {},
|
|
1438
|
+
...issue.url ? { url: issue.url } : {},
|
|
1439
|
+
issueType: issueTypeFor(labelNames),
|
|
1440
|
+
sourceIssueId: `${repo}#${issue.number}`,
|
|
1441
|
+
parentChildDeps: parseParents(body),
|
|
1442
|
+
labels: labelNames,
|
|
1443
|
+
raw: issue,
|
|
1444
|
+
source: "github-issues",
|
|
1445
|
+
_rig: {
|
|
1446
|
+
taskSource: { kind: "github-issues", owner: source.owner, repo: source.repo },
|
|
1447
|
+
sourceIssueId: `${repo}#${issue.number}`
|
|
1578
1448
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
function githubStatusFor(issue) {
|
|
1452
|
+
const state = (issue.state ?? "").toUpperCase();
|
|
1453
|
+
if (state === "CLOSED")
|
|
1454
|
+
return "closed";
|
|
1455
|
+
const labelNames = (issue.labels ?? []).map((label) => label.name);
|
|
1456
|
+
if (labelNames.includes("in-progress"))
|
|
1457
|
+
return "in_progress";
|
|
1458
|
+
if (labelNames.includes("blocked"))
|
|
1459
|
+
return "blocked";
|
|
1460
|
+
if (labelNames.includes("ready"))
|
|
1461
|
+
return "ready";
|
|
1462
|
+
if (labelNames.includes("under-review"))
|
|
1463
|
+
return "under_review";
|
|
1464
|
+
if (labelNames.includes("failed"))
|
|
1465
|
+
return "failed";
|
|
1466
|
+
if (labelNames.includes("cancelled"))
|
|
1467
|
+
return "cancelled";
|
|
1468
|
+
return "open";
|
|
1469
|
+
}
|
|
1470
|
+
function selectedGitHubEnv() {
|
|
1471
|
+
const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
|
|
1472
|
+
return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
|
|
1473
|
+
}
|
|
1474
|
+
function ghSpawnOptions() {
|
|
1475
|
+
return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
|
|
1476
|
+
}
|
|
1477
|
+
function runGh(bin, args, spawnFn) {
|
|
1478
|
+
const res = spawnFn(bin, [...args], ghSpawnOptions());
|
|
1479
|
+
assertGhSuccess(args, res);
|
|
1480
|
+
if (!res.stdout || res.stdout.trim() === "") {
|
|
1481
|
+
throw new Error(`gh ${args.join(" ")} returned empty stdout`);
|
|
1583
1482
|
}
|
|
1483
|
+
return JSON.parse(res.stdout);
|
|
1584
1484
|
}
|
|
1585
|
-
function
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
throw new Error(`
|
|
1485
|
+
function assertGhSuccess(args, res) {
|
|
1486
|
+
if (res.error) {
|
|
1487
|
+
const msg = res.error.message ?? String(res.error);
|
|
1488
|
+
throw new Error(`gh CLI not available \u2014 install gh (brew install gh / apt install gh): ${msg}`);
|
|
1489
|
+
}
|
|
1490
|
+
if (res.status !== 0) {
|
|
1491
|
+
throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}`);
|
|
1589
1492
|
}
|
|
1590
|
-
return result;
|
|
1591
1493
|
}
|
|
1592
|
-
function
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1494
|
+
function parseDeps(body) {
|
|
1495
|
+
return parseIssueRefs(body, /^depends-on:\s*([^\n]+)/im);
|
|
1496
|
+
}
|
|
1497
|
+
function parseParents(body) {
|
|
1498
|
+
return parseIssueRefs(body, /^parents?:\s*([^\n]+)/im);
|
|
1499
|
+
}
|
|
1500
|
+
function parseIssueRefs(body, pattern) {
|
|
1501
|
+
const match = body.match(pattern);
|
|
1502
|
+
if (!match)
|
|
1503
|
+
return [];
|
|
1504
|
+
return match[1].split(",").map((value) => value.trim()).map((value) => value.replace(/^#/, "").match(/^(\d+)/)?.[1] ?? "").filter((value) => value.length > 0);
|
|
1505
|
+
}
|
|
1506
|
+
function issueTypeFor(labels) {
|
|
1507
|
+
const typed = labels.find((label) => label.startsWith("type:"));
|
|
1508
|
+
if (typed)
|
|
1509
|
+
return typed.slice("type:".length);
|
|
1510
|
+
if (labels.includes("epic"))
|
|
1511
|
+
return "epic";
|
|
1512
|
+
return "task";
|
|
1513
|
+
}
|
|
1514
|
+
function isPlainRecord2(candidate) {
|
|
1515
|
+
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// packages/runtime/src/control-plane/tasks/source-lifecycle.ts
|
|
1519
|
+
function hasRunnableTaskSource(source) {
|
|
1520
|
+
return Boolean(source && typeof source === "object" && !Array.isArray(source));
|
|
1521
|
+
}
|
|
1522
|
+
async function getPluginTask(projectRoot, taskId) {
|
|
1523
|
+
const ctx = await buildPluginHostContext(projectRoot);
|
|
1524
|
+
const [source] = ctx?.taskSourceRegistry.list() ?? [];
|
|
1525
|
+
if (!hasRunnableTaskSource(source)) {
|
|
1526
|
+
return ctx ? { configured: false, sourceKind: null, task: null } : null;
|
|
1596
1527
|
}
|
|
1597
|
-
|
|
1528
|
+
const task = source.get ? await source.get(taskId) ?? null : (await source.list()).find((entry) => entry.id === taskId) ?? null;
|
|
1529
|
+
return {
|
|
1530
|
+
configured: true,
|
|
1531
|
+
sourceKind: source.kind,
|
|
1532
|
+
task
|
|
1533
|
+
};
|
|
1598
1534
|
}
|
|
1599
|
-
function
|
|
1600
|
-
|
|
1535
|
+
async function readConfiguredTaskSourceTask(projectRoot, taskId) {
|
|
1536
|
+
const pluginResult = await getPluginTask(projectRoot, taskId);
|
|
1537
|
+
if (pluginResult)
|
|
1538
|
+
return pluginResult;
|
|
1539
|
+
const task = await createSourceAwareTaskConfigRecordReader(projectRoot).getTask(taskId);
|
|
1540
|
+
return {
|
|
1541
|
+
configured: false,
|
|
1542
|
+
sourceKind: null,
|
|
1543
|
+
task
|
|
1544
|
+
};
|
|
1601
1545
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1546
|
+
|
|
1547
|
+
// packages/runtime/src/control-plane/native/task-state.ts
|
|
1548
|
+
import { existsSync as existsSync14, readFileSync as readFileSync9, readdirSync as readdirSync3, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
1549
|
+
import { basename as basename6, resolve as resolve16 } from "path";
|
|
1550
|
+
|
|
1551
|
+
// packages/runtime/src/control-plane/state-sync/types.ts
|
|
1552
|
+
var SUPPORTED_TASK_STATE_SCHEMA_VERSION = 1;
|
|
1553
|
+
var CANONICAL_TASK_LIFECYCLE_STATUSES = new Set([
|
|
1554
|
+
"draft",
|
|
1555
|
+
"open",
|
|
1556
|
+
"ready",
|
|
1557
|
+
"queued",
|
|
1558
|
+
"in_progress",
|
|
1559
|
+
"under_review",
|
|
1560
|
+
"blocked",
|
|
1561
|
+
"completed",
|
|
1562
|
+
"cancelled"
|
|
1563
|
+
]);
|
|
1564
|
+
function normalizeTaskLifecycleStatus(status) {
|
|
1565
|
+
switch (status) {
|
|
1566
|
+
case "draft":
|
|
1567
|
+
case "open":
|
|
1568
|
+
case "ready":
|
|
1569
|
+
case "queued":
|
|
1570
|
+
case "in_progress":
|
|
1571
|
+
case "under_review":
|
|
1572
|
+
case "blocked":
|
|
1573
|
+
case "completed":
|
|
1574
|
+
case "cancelled":
|
|
1575
|
+
return status;
|
|
1576
|
+
case "closed":
|
|
1577
|
+
return "completed";
|
|
1578
|
+
case "running":
|
|
1579
|
+
return "in_progress";
|
|
1580
|
+
case "failed":
|
|
1581
|
+
return "ready";
|
|
1582
|
+
default:
|
|
1583
|
+
return null;
|
|
1611
1584
|
}
|
|
1612
1585
|
}
|
|
1613
|
-
function
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1586
|
+
function normalizeTaskStateMetadataStatus(status) {
|
|
1587
|
+
if (CANONICAL_TASK_LIFECYCLE_STATUSES.has(status)) {
|
|
1588
|
+
return status;
|
|
1589
|
+
}
|
|
1590
|
+
switch (status) {
|
|
1591
|
+
case "closed":
|
|
1592
|
+
return "completed";
|
|
1593
|
+
case "running":
|
|
1594
|
+
return "in_progress";
|
|
1595
|
+
case "failed":
|
|
1596
|
+
return "ready";
|
|
1597
|
+
default:
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1623
1600
|
}
|
|
1624
|
-
function
|
|
1625
|
-
|
|
1626
|
-
|
|
1601
|
+
function canonicalizeTaskStateMetadata(raw) {
|
|
1602
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1603
|
+
return null;
|
|
1604
|
+
}
|
|
1605
|
+
const metadata = raw;
|
|
1606
|
+
const claimId = typeof metadata.claimId === "string" && metadata.claimId.length > 0 ? metadata.claimId : undefined;
|
|
1607
|
+
const status = normalizeTaskStateMetadataStatus(metadata.status);
|
|
1608
|
+
if (!status) {
|
|
1609
|
+
return null;
|
|
1610
|
+
}
|
|
1611
|
+
return {
|
|
1612
|
+
...claimId ? { claimId } : {},
|
|
1613
|
+
status,
|
|
1614
|
+
...typeof metadata.ownerId === "string" && metadata.ownerId.length > 0 ? { ownerId: metadata.ownerId } : {},
|
|
1615
|
+
...typeof metadata.claimedAt === "string" && metadata.claimedAt.length > 0 ? { claimedAt: metadata.claimedAt } : {},
|
|
1616
|
+
...typeof metadata.lastEvidenceAt === "string" && metadata.lastEvidenceAt.length > 0 ? { lastEvidenceAt: metadata.lastEvidenceAt } : {},
|
|
1617
|
+
...typeof metadata.runId === "string" && metadata.runId.length > 0 ? { runId: metadata.runId } : {},
|
|
1618
|
+
...typeof metadata.branchName === "string" && metadata.branchName.length > 0 ? { branchName: metadata.branchName } : {},
|
|
1619
|
+
...typeof metadata.prNumber === "number" ? { prNumber: metadata.prNumber } : {},
|
|
1620
|
+
...typeof metadata.prUrl === "string" && metadata.prUrl.length > 0 ? { prUrl: metadata.prUrl } : {},
|
|
1621
|
+
...typeof metadata.reviewState === "string" && metadata.reviewState.length > 0 ? { reviewState: metadata.reviewState } : {},
|
|
1622
|
+
...typeof metadata.blockerReason === "string" && metadata.blockerReason.length > 0 ? { blockerReason: metadata.blockerReason } : {},
|
|
1623
|
+
...typeof metadata.sourceCommit === "string" && metadata.sourceCommit.length > 0 ? { sourceCommit: metadata.sourceCommit } : {}
|
|
1624
|
+
};
|
|
1627
1625
|
}
|
|
1628
|
-
function
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
return
|
|
1637
|
-
} finally {
|
|
1638
|
-
rmSync2(requestDir, { recursive: true, force: true });
|
|
1626
|
+
function discardMismatchedTaskStateMetadata(input) {
|
|
1627
|
+
input.taskId;
|
|
1628
|
+
const canonicalMetadata = canonicalizeTaskStateMetadata(input.metadata);
|
|
1629
|
+
if (!canonicalMetadata || !input.lifecycleStatus) {
|
|
1630
|
+
return null;
|
|
1631
|
+
}
|
|
1632
|
+
const metadataStatus = canonicalMetadata.status ?? null;
|
|
1633
|
+
if (metadataStatus && metadataStatus !== input.lifecycleStatus) {
|
|
1634
|
+
return null;
|
|
1639
1635
|
}
|
|
1636
|
+
return canonicalMetadata;
|
|
1640
1637
|
}
|
|
1641
|
-
function
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1638
|
+
function readTaskStateMetadataEnvelope(raw) {
|
|
1639
|
+
if (!raw || typeof raw !== "object") {
|
|
1640
|
+
return { schemaVersion: SUPPORTED_TASK_STATE_SCHEMA_VERSION, supported: true, baseTrackerCommit: null, tasks: {} };
|
|
1641
|
+
}
|
|
1642
|
+
const envelope = raw;
|
|
1643
|
+
const schemaVersion = typeof envelope.schemaVersion === "number" ? envelope.schemaVersion : SUPPORTED_TASK_STATE_SCHEMA_VERSION;
|
|
1644
|
+
if (schemaVersion !== SUPPORTED_TASK_STATE_SCHEMA_VERSION) {
|
|
1645
|
+
return { schemaVersion, supported: false, baseTrackerCommit: null, tasks: {} };
|
|
1646
|
+
}
|
|
1647
|
+
const rawTasks = envelope.tasks && typeof envelope.tasks === "object" && !Array.isArray(envelope.tasks) ? envelope.tasks : {};
|
|
1648
|
+
const tasks = Object.fromEntries(Object.entries(rawTasks).map(([taskId, metadata]) => [taskId, canonicalizeTaskStateMetadata(metadata)]).filter((entry) => entry[1] != null));
|
|
1649
|
+
return {
|
|
1650
|
+
schemaVersion,
|
|
1651
|
+
supported: true,
|
|
1652
|
+
baseTrackerCommit: typeof envelope.baseTrackerCommit === "string" && envelope.baseTrackerCommit.length > 0 ? envelope.baseTrackerCommit : null,
|
|
1653
|
+
tasks
|
|
1654
|
+
};
|
|
1649
1655
|
}
|
|
1656
|
+
// packages/runtime/src/control-plane/state-sync/read.ts
|
|
1657
|
+
import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
|
|
1658
|
+
import { resolve as resolve15 } from "path";
|
|
1650
1659
|
|
|
1651
1660
|
// packages/runtime/src/control-plane/native/utils.ts
|
|
1652
|
-
import { ptr as ptr2 } from "bun:ffi";
|
|
1653
1661
|
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
1654
1662
|
import { resolve as resolve12 } from "path";
|
|
1655
1663
|
|
|
@@ -1771,12 +1779,6 @@ var sharedNativeRuntimeOutputDir = resolve11(tmpdir4(), "rig-native");
|
|
|
1771
1779
|
var sharedNativeRuntimeOutputPath = resolve11(sharedNativeRuntimeOutputDir, `runtime-native-${process.platform}-${process.arch}.${suffix}`);
|
|
1772
1780
|
var colocatedNativeRuntimeFileName = `runtime-native.${suffix}`;
|
|
1773
1781
|
var nativeRuntimeLibrary = await loadNativeRuntimeLibrary();
|
|
1774
|
-
function requireNativeRuntimeLibrary(feature) {
|
|
1775
|
-
if (!nativeRuntimeLibrary) {
|
|
1776
|
-
throw new Error(`Native Zig runtime is required for ${feature}`);
|
|
1777
|
-
}
|
|
1778
|
-
return nativeRuntimeLibrary;
|
|
1779
|
-
}
|
|
1780
1782
|
async function ensureNativeRuntimeLibraryPath(outputPath = sharedNativeRuntimeOutputPath, options = {}) {
|
|
1781
1783
|
if (await buildNativeRuntimeLibrary(outputPath, options)) {
|
|
1782
1784
|
return outputPath;
|
|
@@ -1942,7 +1944,6 @@ function tryDlopenNativeRuntimeLibrary(outputPath) {
|
|
|
1942
1944
|
function resolveMonorepoRoot2(projectRoot) {
|
|
1943
1945
|
return resolveMonorepoRoot(projectRoot);
|
|
1944
1946
|
}
|
|
1945
|
-
var nativeScopeMatcher = null;
|
|
1946
1947
|
var scopeRegexCache = new Map;
|
|
1947
1948
|
function runCapture(command, cwd, env) {
|
|
1948
1949
|
const result = Bun.spawnSync(command, {
|
|
@@ -2099,41 +2100,6 @@ function monorepoSearchCandidates(inputPath) {
|
|
|
2099
2100
|
}
|
|
2100
2101
|
return [...candidates];
|
|
2101
2102
|
}
|
|
2102
|
-
function scopeMatches(path, scopes) {
|
|
2103
|
-
const matcher = getNativeScopeMatcher();
|
|
2104
|
-
const pathVariants = unique([path, normalizeRelativeScopePath(path)]);
|
|
2105
|
-
for (const scope of scopes) {
|
|
2106
|
-
const scopeVariants = unique([scope, normalizeRelativeScopePath(scope)]);
|
|
2107
|
-
for (const candidatePath of pathVariants) {
|
|
2108
|
-
for (const candidateScope of scopeVariants) {
|
|
2109
|
-
if (candidatePath === candidateScope) {
|
|
2110
|
-
return true;
|
|
2111
|
-
}
|
|
2112
|
-
if (matcher.match(candidateScope, candidatePath)) {
|
|
2113
|
-
return true;
|
|
2114
|
-
}
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
return false;
|
|
2119
|
-
}
|
|
2120
|
-
function getNativeScopeMatcher() {
|
|
2121
|
-
if (nativeScopeMatcher) {
|
|
2122
|
-
return nativeScopeMatcher;
|
|
2123
|
-
}
|
|
2124
|
-
nativeScopeMatcher = createNativeScopeMatcher();
|
|
2125
|
-
return nativeScopeMatcher;
|
|
2126
|
-
}
|
|
2127
|
-
function createNativeScopeMatcher() {
|
|
2128
|
-
const library = requireNativeRuntimeLibrary("scope matching");
|
|
2129
|
-
return {
|
|
2130
|
-
match: (pattern, path) => {
|
|
2131
|
-
const patternBuffer = Buffer.from(`${pattern}\x00`);
|
|
2132
|
-
const pathBuffer = Buffer.from(`${path}\x00`);
|
|
2133
|
-
return library.symbols.rig_scope_match(Number(ptr2(patternBuffer)), Number(ptr2(pathBuffer))) !== 0;
|
|
2134
|
-
}
|
|
2135
|
-
};
|
|
2136
|
-
}
|
|
2137
2103
|
|
|
2138
2104
|
// packages/runtime/src/control-plane/state-sync/repo.ts
|
|
2139
2105
|
import { existsSync as existsSync12 } from "fs";
|
|
@@ -7209,20 +7175,7 @@ function filterTaskChangedFiles(projectRoot, taskId, files, scoped) {
|
|
|
7209
7175
|
if (!taskId) {
|
|
7210
7176
|
return unique(uniqueFiles).sort();
|
|
7211
7177
|
}
|
|
7212
|
-
|
|
7213
|
-
if (!scoped) {
|
|
7214
|
-
return filteredFiles.sort();
|
|
7215
|
-
}
|
|
7216
|
-
const paths = resolveHarnessPaths(projectRoot);
|
|
7217
|
-
const monorepoRoot = resolveTaskMonorepoRoot(projectRoot);
|
|
7218
|
-
const scopes = readTaskConfigForInvocation(projectRoot)[taskId]?.scope || [];
|
|
7219
|
-
if (scopes.length === 0) {
|
|
7220
|
-
return [];
|
|
7221
|
-
}
|
|
7222
|
-
return filteredFiles.filter((file) => {
|
|
7223
|
-
const normalized = normalizePathToScope(projectRoot, monorepoRoot || paths.monorepoRoot, file);
|
|
7224
|
-
return scopeMatches(file, scopes) || scopeMatches(normalized, scopes);
|
|
7225
|
-
}).sort();
|
|
7178
|
+
return unique(uniqueFiles).filter((file) => !isGeneratedTaskChangePath(taskId, file)).sort();
|
|
7226
7179
|
}
|
|
7227
7180
|
function resolveTaskMonorepoRoot(projectRoot) {
|
|
7228
7181
|
const runtimeWorkspace = loadRuntimeContextFromEnv()?.workspaceDir || process.env.RIG_TASK_WORKSPACE?.trim();
|
|
@@ -7273,6 +7226,17 @@ function resolveMonorepoBaseCommit(projectRoot, repo) {
|
|
|
7273
7226
|
}
|
|
7274
7227
|
function collectWorkingTreeFiles(projectRoot, repo, baseline) {
|
|
7275
7228
|
const files = new Set;
|
|
7229
|
+
const nativeFiles = nativePendingFiles(repo);
|
|
7230
|
+
if (nativeFiles !== null) {
|
|
7231
|
+
for (const entry of nativeFiles) {
|
|
7232
|
+
const normalized = normalizeChangedFilePath(entry.path);
|
|
7233
|
+
if (!normalized || baseline.has(normalized)) {
|
|
7234
|
+
continue;
|
|
7235
|
+
}
|
|
7236
|
+
files.add(normalized);
|
|
7237
|
+
}
|
|
7238
|
+
return [...files].sort();
|
|
7239
|
+
}
|
|
7276
7240
|
for (const args of [
|
|
7277
7241
|
["diff", "--name-only"],
|
|
7278
7242
|
["diff", "--cached", "--name-only"],
|