@h-rig/runtime 0.0.6-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/bin/rig-agent-dispatch.js +9615 -0
- package/dist/bin/rig-agent.js +9512 -0
- package/dist/bin/rig-browser-tool.js +269 -0
- package/dist/src/agent-mode.js +48 -0
- package/dist/src/baked-secrets.js +121 -0
- package/dist/src/binary-build-worker.js +312 -0
- package/dist/src/binary-run.js +540 -0
- package/dist/src/boundaries.js +1 -0
- package/dist/src/build-time-config.js +25 -0
- package/dist/src/control-plane/agent-roles.js +27 -0
- package/dist/src/control-plane/agent-wrapper.js +9621 -0
- package/dist/src/control-plane/authority-files.js +582 -0
- package/dist/src/control-plane/browser-contract.js +135 -0
- package/dist/src/control-plane/controlled-bash.js +1111 -0
- package/dist/src/control-plane/errors.js +13 -0
- package/dist/src/control-plane/harness-main.js +10828 -0
- package/dist/src/control-plane/hook-materializer.js +75 -0
- package/dist/src/control-plane/hooks/audit-trail.js +353 -0
- package/dist/src/control-plane/hooks/completion-verification.js +7552 -0
- package/dist/src/control-plane/hooks/import-guard.js +890 -0
- package/dist/src/control-plane/hooks/inject-context.js +4189 -0
- package/dist/src/control-plane/hooks/post-edit-lint.js +43 -0
- package/dist/src/control-plane/hooks/safety-guard.js +910 -0
- package/dist/src/control-plane/hooks/scope-guard.js +907 -0
- package/dist/src/control-plane/hooks/shared.js +44 -0
- package/dist/src/control-plane/hooks/submodule-branch.js +7797 -0
- package/dist/src/control-plane/hooks/task-runtime-start.js +7799 -0
- package/dist/src/control-plane/hooks/test-integrity-guard.js +891 -0
- package/dist/src/control-plane/materialize-task-config.js +453 -0
- package/dist/src/control-plane/memory-sync/cli.js +2019 -0
- package/dist/src/control-plane/memory-sync/db.js +753 -0
- package/dist/src/control-plane/memory-sync/embed.js +281 -0
- package/dist/src/control-plane/memory-sync/index.js +2049 -0
- package/dist/src/control-plane/memory-sync/query.js +294 -0
- package/dist/src/control-plane/memory-sync/read.js +784 -0
- package/dist/src/control-plane/memory-sync/types.js +6 -0
- package/dist/src/control-plane/memory-sync/write.js +1547 -0
- package/dist/src/control-plane/native/git-native.js +490 -0
- package/dist/src/control-plane/native/git-ops.js +2860 -0
- package/dist/src/control-plane/native/harness-cli.js +9721 -0
- package/dist/src/control-plane/native/pr-automation.js +373 -0
- package/dist/src/control-plane/native/profile-ops.js +481 -0
- package/dist/src/control-plane/native/repo-ops.js +2342 -0
- package/dist/src/control-plane/native/root-resolver.js +66 -0
- package/dist/src/control-plane/native/run-ops.js +3281 -0
- package/dist/src/control-plane/native/runtime-native-sidecar.js +299 -0
- package/dist/src/control-plane/native/runtime-native.js +392 -0
- package/dist/src/control-plane/native/scope-rules.js +17 -0
- package/dist/src/control-plane/native/task-ops.js +6320 -0
- package/dist/src/control-plane/native/task-state.js +1512 -0
- package/dist/src/control-plane/native/utils.js +535 -0
- package/dist/src/control-plane/native/validator-binaries.js +889 -0
- package/dist/src/control-plane/native/validator.js +2197 -0
- package/dist/src/control-plane/native/verifier.js +3249 -0
- package/dist/src/control-plane/native/workspace-ops.js +1635 -0
- package/dist/src/control-plane/plugin-host-context.js +334 -0
- package/dist/src/control-plane/project-main-pre-run-sync.js +630 -0
- package/dist/src/control-plane/provider/claude-stream-records.js +158 -0
- package/dist/src/control-plane/provider/codex-app-server.js +885 -0
- package/dist/src/control-plane/provider/codex-exec-records.js +203 -0
- package/dist/src/control-plane/provider/rig-task-run-skill.js +39 -0
- package/dist/src/control-plane/provider/runtime-instructions.js +96 -0
- package/dist/src/control-plane/remote.js +854 -0
- package/dist/src/control-plane/repos/index.js +473 -0
- package/dist/src/control-plane/repos/layout.js +124 -0
- package/dist/src/control-plane/repos/mirror/bootstrap.js +268 -0
- package/dist/src/control-plane/repos/mirror/refresh.js +398 -0
- package/dist/src/control-plane/repos/mirror/state.js +167 -0
- package/dist/src/control-plane/repos/registry.js +77 -0
- package/dist/src/control-plane/repos/types.js +1 -0
- package/dist/src/control-plane/runtime/agent-mode.js +48 -0
- package/dist/src/control-plane/runtime/baked-secrets.js +120 -0
- package/dist/src/control-plane/runtime/claude-tool-router-binary.js +343 -0
- package/dist/src/control-plane/runtime/claude-tool-router.js +520 -0
- package/dist/src/control-plane/runtime/context.js +216 -0
- package/dist/src/control-plane/runtime/events.js +218 -0
- package/dist/src/control-plane/runtime/guard-types.js +6 -0
- package/dist/src/control-plane/runtime/guard.js +880 -0
- package/dist/src/control-plane/runtime/image/fingerprint-sidecar.js +1194 -0
- package/dist/src/control-plane/runtime/image/index.js +2255 -0
- package/dist/src/control-plane/runtime/image-fingerprint-sidecar.js +1191 -0
- package/dist/src/control-plane/runtime/image.js +2255 -0
- package/dist/src/control-plane/runtime/index.js +8511 -0
- package/dist/src/control-plane/runtime/isolation/discovery.js +599 -0
- package/dist/src/control-plane/runtime/isolation/home.js +1217 -0
- package/dist/src/control-plane/runtime/isolation/index.js +8193 -0
- package/dist/src/control-plane/runtime/isolation/runner.js +2651 -0
- package/dist/src/control-plane/runtime/isolation/shared.js +501 -0
- package/dist/src/control-plane/runtime/isolation/toolchain.js +1892 -0
- package/dist/src/control-plane/runtime/isolation/types.js +1 -0
- package/dist/src/control-plane/runtime/isolation/worktree.js +509 -0
- package/dist/src/control-plane/runtime/isolation.js +8193 -0
- package/dist/src/control-plane/runtime/overlay.js +67 -0
- package/dist/src/control-plane/runtime/plugin-mode.js +41 -0
- package/dist/src/control-plane/runtime/plugins.js +1131 -0
- package/dist/src/control-plane/runtime/provisioning-env.js +220 -0
- package/dist/src/control-plane/runtime/queue.js +8358 -0
- package/dist/src/control-plane/runtime/rig-shell.js +205 -0
- package/dist/src/control-plane/runtime/rig-tools.js +182 -0
- package/dist/src/control-plane/runtime/runner-context.js +1 -0
- package/dist/src/control-plane/runtime/runtime-paths.js +184 -0
- package/dist/src/control-plane/runtime/sandbox/backend-bwrap.js +311 -0
- package/dist/src/control-plane/runtime/sandbox/backend-none.js +21 -0
- package/dist/src/control-plane/runtime/sandbox/backend-seatbelt.js +268 -0
- package/dist/src/control-plane/runtime/sandbox/backend.js +1718 -0
- package/dist/src/control-plane/runtime/sandbox/orchestrator.js +1745 -0
- package/dist/src/control-plane/runtime/sandbox/utils.js +137 -0
- package/dist/src/control-plane/runtime/sandbox-backend-bwrap.js +311 -0
- package/dist/src/control-plane/runtime/sandbox-backend-none.js +21 -0
- package/dist/src/control-plane/runtime/sandbox-backend-seatbelt.js +268 -0
- package/dist/src/control-plane/runtime/sandbox-backend.js +1718 -0
- package/dist/src/control-plane/runtime/sandbox-orchestrator.js +1745 -0
- package/dist/src/control-plane/runtime/sandbox-utils.js +137 -0
- package/dist/src/control-plane/runtime/snapshot/index.js +454 -0
- package/dist/src/control-plane/runtime/snapshot/sidecar.js +502 -0
- package/dist/src/control-plane/runtime/snapshot/task-run.js +1578 -0
- package/dist/src/control-plane/runtime/snapshot-sidecar.js +498 -0
- package/dist/src/control-plane/runtime/snapshot.js +454 -0
- package/dist/src/control-plane/runtime/task-run-snapshot.js +1578 -0
- package/dist/src/control-plane/runtime/tool-gateway.js +422 -0
- package/dist/src/control-plane/runtime/tooling/browser-tools.js +32 -0
- package/dist/src/control-plane/runtime/tooling/claude-router-binary.js +343 -0
- package/dist/src/control-plane/runtime/tooling/claude-router.js +524 -0
- package/dist/src/control-plane/runtime/tooling/file-tools.js +182 -0
- package/dist/src/control-plane/runtime/tooling/gateway.js +422 -0
- package/dist/src/control-plane/runtime/tooling/index.js +1290 -0
- package/dist/src/control-plane/runtime/tooling/shell.js +205 -0
- package/dist/src/control-plane/runtime/types.js +1 -0
- package/dist/src/control-plane/setup-version.js +14 -0
- package/dist/src/control-plane/state-sync/index.js +1509 -0
- package/dist/src/control-plane/state-sync/read.js +856 -0
- package/dist/src/control-plane/state-sync/reconcile.js +260 -0
- package/dist/src/control-plane/state-sync/repo.js +302 -0
- package/dist/src/control-plane/state-sync/types.js +111 -0
- package/dist/src/control-plane/state-sync/write.js +1469 -0
- package/dist/src/control-plane/task-fields.js +38 -0
- package/dist/src/control-plane/task-source-bootstrap.js +46 -0
- package/dist/src/control-plane/task-source.js +30 -0
- package/dist/src/control-plane/tasks/legacy-task-config-source.js +130 -0
- package/dist/src/control-plane/tasks/plugin-task-source.js +103 -0
- package/dist/src/control-plane/tasks/source-aware-task-config-source.js +611 -0
- package/dist/src/control-plane/tasks/source-lifecycle.js +1093 -0
- package/dist/src/control-plane/tasks/task-record-reader.js +9 -0
- package/dist/src/control-plane/validators/boundary/public-apis.js +107 -0
- package/dist/src/control-plane/validators/integration/_shared.js +51 -0
- package/dist/src/control-plane/validators/integration/adm-audit-http.js +85 -0
- package/dist/src/control-plane/validators/integration/adm-auth-http.js +78 -0
- package/dist/src/control-plane/validators/integration/adm-issuer-http.js +80 -0
- package/dist/src/control-plane/validators/integration/adm-migration.js +78 -0
- package/dist/src/control-plane/validators/integration/adm-scaffold.js +78 -0
- package/dist/src/control-plane/validators/runtime-registration.js +64 -0
- package/dist/src/control-plane/validators/shared.js +683 -0
- package/dist/src/events.js +218 -0
- package/dist/src/execution.js +35 -0
- package/dist/src/index.js +1633 -0
- package/dist/src/layout.js +145 -0
- package/dist/src/local-server.js +202 -0
- package/dist/src/plugins.js +329 -0
- package/dist/src/remote-http.js +83 -0
- package/dist/src/runtime-context.js +216 -0
- package/dist/src/types.js +1 -0
- package/native/darwin-arm64/bin/rig-git +0 -0
- package/native/darwin-arm64/bin/rig-shell +0 -0
- package/native/darwin-arm64/bin/rig-tools +0 -0
- package/native/darwin-arm64/lib/runtime-native-darwin-arm64.dylib +0 -0
- package/native/darwin-arm64/lib/runtime-native.dylib +0 -0
- package/native/darwin-arm64/manifest.json +1 -0
- package/native/linux-x64/bin/rig-git +0 -0
- package/native/linux-x64/bin/rig-shell +0 -0
- package/native/linux-x64/bin/rig-tools +0 -0
- package/native/linux-x64/lib/runtime-native-linux-x64.so +0 -0
- package/native/linux-x64/lib/runtime-native.so +0 -0
- package/native/linux-x64/manifest.json +1 -0
- package/package.json +74 -0
- package/skills/rig-task-run.md +71 -0
|
@@ -0,0 +1,1635 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/runtime/src/control-plane/native/workspace-ops.ts
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import {
|
|
5
|
+
appendFileSync as appendFileSync2,
|
|
6
|
+
closeSync,
|
|
7
|
+
existsSync as existsSync8,
|
|
8
|
+
mkdirSync as mkdirSync4,
|
|
9
|
+
openSync,
|
|
10
|
+
readdirSync as readdirSync2,
|
|
11
|
+
readFileSync as readFileSync4,
|
|
12
|
+
statSync as statSync3,
|
|
13
|
+
writeFileSync as writeFileSync3
|
|
14
|
+
} from "fs";
|
|
15
|
+
import { join as join3, resolve as resolve8 } from "path";
|
|
16
|
+
|
|
17
|
+
// packages/runtime/src/control-plane/authority-files.ts
|
|
18
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync, appendFileSync, copyFileSync, statSync, readdirSync, chmodSync } from "fs";
|
|
19
|
+
import { dirname as dirname2, join, relative, resolve as resolve2 } from "path";
|
|
20
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
21
|
+
|
|
22
|
+
// packages/runtime/src/layout.ts
|
|
23
|
+
import { existsSync } from "fs";
|
|
24
|
+
import { basename, dirname, resolve } from "path";
|
|
25
|
+
function resolveMonorepoRoot(projectRoot) {
|
|
26
|
+
const normalizedProjectRoot = resolve(projectRoot);
|
|
27
|
+
const explicit = process.env.MONOREPO_ROOT?.trim();
|
|
28
|
+
if (explicit) {
|
|
29
|
+
const explicitRoot = resolve(explicit);
|
|
30
|
+
const explicitParent = dirname(explicitRoot);
|
|
31
|
+
if (basename(explicitParent) === ".worktrees") {
|
|
32
|
+
const owner = dirname(explicitParent);
|
|
33
|
+
const ownerHasGit = existsSync(resolve(owner, ".git"));
|
|
34
|
+
const ownerHasTaskConfig = existsSync(resolve(owner, ".rig", "task-config.json"));
|
|
35
|
+
const ownerHasRigConfig = existsSync(resolve(owner, "rig.config.ts"));
|
|
36
|
+
if (ownerHasGit && (ownerHasTaskConfig || ownerHasRigConfig)) {
|
|
37
|
+
return owner;
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`MONOREPO_ROOT points to worktree ${explicitRoot}, but the owner checkout is incomplete at ${owner}.`);
|
|
40
|
+
}
|
|
41
|
+
if (!existsSync(resolve(explicitRoot, ".git"))) {
|
|
42
|
+
throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but no git checkout was found there.`);
|
|
43
|
+
}
|
|
44
|
+
const hasTaskConfig = existsSync(resolve(explicitRoot, ".rig", "task-config.json"));
|
|
45
|
+
const hasRigConfig = existsSync(resolve(explicitRoot, "rig.config.ts"));
|
|
46
|
+
if (!hasTaskConfig && !hasRigConfig) {
|
|
47
|
+
throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but neither .rig/task-config.json nor rig.config.ts exists there.`);
|
|
48
|
+
}
|
|
49
|
+
return explicitRoot;
|
|
50
|
+
}
|
|
51
|
+
const projectParent = dirname(normalizedProjectRoot);
|
|
52
|
+
if (basename(projectParent) === ".worktrees") {
|
|
53
|
+
const worktreeOwner = dirname(projectParent);
|
|
54
|
+
const ownerHasGit = existsSync(resolve(worktreeOwner, ".git"));
|
|
55
|
+
const ownerHasTaskConfig = existsSync(resolve(worktreeOwner, ".rig", "task-config.json"));
|
|
56
|
+
const ownerHasRigConfig = existsSync(resolve(worktreeOwner, "rig.config.ts"));
|
|
57
|
+
if (ownerHasGit && (ownerHasTaskConfig || ownerHasRigConfig)) {
|
|
58
|
+
return worktreeOwner;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return normalizedProjectRoot;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// packages/runtime/src/control-plane/authority-files.ts
|
|
65
|
+
function normalizeString(value) {
|
|
66
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
67
|
+
}
|
|
68
|
+
function resolveAuthorityStateRoot(projectRoot) {
|
|
69
|
+
const normalizedRoot = resolve2(projectRoot);
|
|
70
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
71
|
+
if (taskWorkspace) {
|
|
72
|
+
return resolve2(taskWorkspace, ".rig");
|
|
73
|
+
}
|
|
74
|
+
const stateDir = process.env.RIG_STATE_DIR?.trim();
|
|
75
|
+
if (stateDir) {
|
|
76
|
+
return dirname2(resolve2(stateDir));
|
|
77
|
+
}
|
|
78
|
+
const logsDir = process.env.RIG_LOGS_DIR?.trim();
|
|
79
|
+
if (logsDir) {
|
|
80
|
+
return dirname2(resolve2(logsDir));
|
|
81
|
+
}
|
|
82
|
+
const sessionFile = process.env.RIG_SESSION_FILE?.trim();
|
|
83
|
+
if (sessionFile) {
|
|
84
|
+
return dirname2(dirname2(resolve2(sessionFile)));
|
|
85
|
+
}
|
|
86
|
+
const projectStateRoot = resolve2(normalizedRoot, ".rig");
|
|
87
|
+
if (existsSync2(projectStateRoot)) {
|
|
88
|
+
return projectStateRoot;
|
|
89
|
+
}
|
|
90
|
+
return resolve2(normalizedRoot, ".rig");
|
|
91
|
+
}
|
|
92
|
+
function resolveAuthorityArtifactsRoot(projectRoot) {
|
|
93
|
+
const explicit = process.env.ARTIFACTS_DIR?.trim();
|
|
94
|
+
if (explicit) {
|
|
95
|
+
return resolve2(explicit);
|
|
96
|
+
}
|
|
97
|
+
const projectArtifactsRoot = resolve2(projectRoot, "artifacts");
|
|
98
|
+
if (existsSync2(projectArtifactsRoot)) {
|
|
99
|
+
return projectArtifactsRoot;
|
|
100
|
+
}
|
|
101
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
102
|
+
if (taskWorkspace) {
|
|
103
|
+
return resolve2(taskWorkspace, "artifacts");
|
|
104
|
+
}
|
|
105
|
+
if (process.env.MONOREPO_ROOT?.trim()) {
|
|
106
|
+
return resolve2(resolveMonorepoRoot(projectRoot), "artifacts");
|
|
107
|
+
}
|
|
108
|
+
const normalizedRoot = resolve2(projectRoot);
|
|
109
|
+
const projectLooksLikeMonorepo = existsSync2(resolve2(normalizedRoot, ".git")) && existsSync2(resolve2(normalizedRoot, ".rig", "task-config.json"));
|
|
110
|
+
if (!projectLooksLikeMonorepo) {
|
|
111
|
+
return projectArtifactsRoot;
|
|
112
|
+
}
|
|
113
|
+
return resolve2(resolveMonorepoRoot(projectRoot), "artifacts");
|
|
114
|
+
}
|
|
115
|
+
function uniquePaths(paths) {
|
|
116
|
+
const seen = new Set;
|
|
117
|
+
const result = [];
|
|
118
|
+
for (const value of paths) {
|
|
119
|
+
const normalized = normalizeString(value);
|
|
120
|
+
if (!normalized || seen.has(normalized))
|
|
121
|
+
continue;
|
|
122
|
+
seen.add(normalized);
|
|
123
|
+
result.push(normalized);
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
function listAuthorityRunRoots(projectRoot) {
|
|
128
|
+
const normalizedRoot = resolve2(projectRoot);
|
|
129
|
+
const monorepoRoot = resolveMonorepoRoot(normalizedRoot);
|
|
130
|
+
return uniquePaths([
|
|
131
|
+
resolve2(resolveAuthorityStateRoot(normalizedRoot), "runs"),
|
|
132
|
+
resolve2(monorepoRoot, ".rig", "runs")
|
|
133
|
+
]);
|
|
134
|
+
}
|
|
135
|
+
function listAuthorityArtifactRoots(projectRoot) {
|
|
136
|
+
return uniquePaths([
|
|
137
|
+
resolveAuthorityArtifactsRoot(projectRoot)
|
|
138
|
+
]);
|
|
139
|
+
}
|
|
140
|
+
function readJsonFile(path, fallback) {
|
|
141
|
+
if (!existsSync2(path))
|
|
142
|
+
return fallback;
|
|
143
|
+
try {
|
|
144
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
145
|
+
} catch {
|
|
146
|
+
return fallback;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function listAuthorityRuns(projectRoot) {
|
|
150
|
+
const runs = new Map;
|
|
151
|
+
for (const runsDir of listAuthorityRunRoots(projectRoot)) {
|
|
152
|
+
if (!existsSync2(runsDir))
|
|
153
|
+
continue;
|
|
154
|
+
for (const runId of readDirRuns(runsDir)) {
|
|
155
|
+
if (runs.has(runId))
|
|
156
|
+
continue;
|
|
157
|
+
const entry = readJsonFile(resolve2(runsDir, runId, "run.json"), null);
|
|
158
|
+
if (entry && runBelongsToProject(projectRoot, entry, runsDir)) {
|
|
159
|
+
runs.set(runId, entry);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return [...runs.values()].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
164
|
+
}
|
|
165
|
+
function runBelongsToProject(projectRoot, run, runsDir) {
|
|
166
|
+
const normalizedRoot = resolve2(projectRoot);
|
|
167
|
+
const record = run;
|
|
168
|
+
const recordedProjectRoot = normalizeString(record.projectRoot);
|
|
169
|
+
if (recordedProjectRoot) {
|
|
170
|
+
return resolve2(recordedProjectRoot) === normalizedRoot;
|
|
171
|
+
}
|
|
172
|
+
const projectLocalRunsDir = resolve2(normalizedRoot, ".rig", "runs");
|
|
173
|
+
if (isPathWithin(projectLocalRunsDir, resolve2(runsDir))) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
const pathFields = [
|
|
177
|
+
run.worktreePath,
|
|
178
|
+
run.artifactRoot,
|
|
179
|
+
run.logRoot,
|
|
180
|
+
run.sessionPath,
|
|
181
|
+
run.sessionLogPath
|
|
182
|
+
].filter((value) => typeof value === "string" && value.trim().length > 0);
|
|
183
|
+
if (pathFields.length === 0) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
return pathFields.some((value) => isPathWithin(normalizedRoot, resolve2(value)));
|
|
187
|
+
}
|
|
188
|
+
function isPathWithin(root, candidate) {
|
|
189
|
+
const relativePath = relative(root, candidate);
|
|
190
|
+
return relativePath === "" || !relativePath.startsWith("..") && !relativePath.startsWith(`/`) && !relativePath.startsWith(`\\`);
|
|
191
|
+
}
|
|
192
|
+
function readDirRuns(runsDir) {
|
|
193
|
+
try {
|
|
194
|
+
return readdirSync(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
195
|
+
} catch {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function resolveTaskArtifactDirs(projectRoot, taskId) {
|
|
200
|
+
const candidates = [];
|
|
201
|
+
const seen = new Set;
|
|
202
|
+
const add = (value) => {
|
|
203
|
+
const normalized = normalizeString(value);
|
|
204
|
+
if (!normalized || seen.has(normalized))
|
|
205
|
+
return;
|
|
206
|
+
seen.add(normalized);
|
|
207
|
+
candidates.push(normalized);
|
|
208
|
+
};
|
|
209
|
+
for (const run of listAuthorityRuns(projectRoot)) {
|
|
210
|
+
if (run.taskId !== taskId)
|
|
211
|
+
continue;
|
|
212
|
+
add(run.artifactRoot);
|
|
213
|
+
if (run.worktreePath) {
|
|
214
|
+
add(resolve2(run.worktreePath, "artifacts", taskId));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
for (const artifactsRoot of listAuthorityArtifactRoots(projectRoot)) {
|
|
218
|
+
add(resolve2(artifactsRoot, taskId));
|
|
219
|
+
}
|
|
220
|
+
return candidates;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// packages/runtime/src/control-plane/state-sync/types.ts
|
|
224
|
+
var SUPPORTED_TASK_STATE_SCHEMA_VERSION = 1;
|
|
225
|
+
var CANONICAL_TASK_LIFECYCLE_STATUSES = new Set([
|
|
226
|
+
"draft",
|
|
227
|
+
"open",
|
|
228
|
+
"ready",
|
|
229
|
+
"queued",
|
|
230
|
+
"in_progress",
|
|
231
|
+
"under_review",
|
|
232
|
+
"blocked",
|
|
233
|
+
"completed",
|
|
234
|
+
"cancelled"
|
|
235
|
+
]);
|
|
236
|
+
function normalizeTaskLifecycleStatus(status) {
|
|
237
|
+
switch (status) {
|
|
238
|
+
case "draft":
|
|
239
|
+
case "open":
|
|
240
|
+
case "ready":
|
|
241
|
+
case "queued":
|
|
242
|
+
case "in_progress":
|
|
243
|
+
case "under_review":
|
|
244
|
+
case "blocked":
|
|
245
|
+
case "completed":
|
|
246
|
+
case "cancelled":
|
|
247
|
+
return status;
|
|
248
|
+
case "closed":
|
|
249
|
+
return "completed";
|
|
250
|
+
case "running":
|
|
251
|
+
return "in_progress";
|
|
252
|
+
case "failed":
|
|
253
|
+
return "ready";
|
|
254
|
+
default:
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function normalizeTaskStateMetadataStatus(status) {
|
|
259
|
+
if (CANONICAL_TASK_LIFECYCLE_STATUSES.has(status)) {
|
|
260
|
+
return status;
|
|
261
|
+
}
|
|
262
|
+
switch (status) {
|
|
263
|
+
case "closed":
|
|
264
|
+
return "completed";
|
|
265
|
+
case "running":
|
|
266
|
+
return "in_progress";
|
|
267
|
+
case "failed":
|
|
268
|
+
return "ready";
|
|
269
|
+
default:
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function canonicalizeTaskStateMetadata(raw) {
|
|
274
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
const metadata = raw;
|
|
278
|
+
const claimId = typeof metadata.claimId === "string" && metadata.claimId.length > 0 ? metadata.claimId : undefined;
|
|
279
|
+
const status = normalizeTaskStateMetadataStatus(metadata.status);
|
|
280
|
+
if (!status) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
...claimId ? { claimId } : {},
|
|
285
|
+
status,
|
|
286
|
+
...typeof metadata.ownerId === "string" && metadata.ownerId.length > 0 ? { ownerId: metadata.ownerId } : {},
|
|
287
|
+
...typeof metadata.claimedAt === "string" && metadata.claimedAt.length > 0 ? { claimedAt: metadata.claimedAt } : {},
|
|
288
|
+
...typeof metadata.lastEvidenceAt === "string" && metadata.lastEvidenceAt.length > 0 ? { lastEvidenceAt: metadata.lastEvidenceAt } : {},
|
|
289
|
+
...typeof metadata.runId === "string" && metadata.runId.length > 0 ? { runId: metadata.runId } : {},
|
|
290
|
+
...typeof metadata.branchName === "string" && metadata.branchName.length > 0 ? { branchName: metadata.branchName } : {},
|
|
291
|
+
...typeof metadata.prNumber === "number" ? { prNumber: metadata.prNumber } : {},
|
|
292
|
+
...typeof metadata.prUrl === "string" && metadata.prUrl.length > 0 ? { prUrl: metadata.prUrl } : {},
|
|
293
|
+
...typeof metadata.reviewState === "string" && metadata.reviewState.length > 0 ? { reviewState: metadata.reviewState } : {},
|
|
294
|
+
...typeof metadata.blockerReason === "string" && metadata.blockerReason.length > 0 ? { blockerReason: metadata.blockerReason } : {},
|
|
295
|
+
...typeof metadata.sourceCommit === "string" && metadata.sourceCommit.length > 0 ? { sourceCommit: metadata.sourceCommit } : {}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function readTaskStateMetadataEnvelope(raw) {
|
|
299
|
+
if (!raw || typeof raw !== "object") {
|
|
300
|
+
return { schemaVersion: SUPPORTED_TASK_STATE_SCHEMA_VERSION, supported: true, baseTrackerCommit: null, tasks: {} };
|
|
301
|
+
}
|
|
302
|
+
const envelope = raw;
|
|
303
|
+
const schemaVersion = typeof envelope.schemaVersion === "number" ? envelope.schemaVersion : SUPPORTED_TASK_STATE_SCHEMA_VERSION;
|
|
304
|
+
if (schemaVersion !== SUPPORTED_TASK_STATE_SCHEMA_VERSION) {
|
|
305
|
+
return { schemaVersion, supported: false, baseTrackerCommit: null, tasks: {} };
|
|
306
|
+
}
|
|
307
|
+
const rawTasks = envelope.tasks && typeof envelope.tasks === "object" && !Array.isArray(envelope.tasks) ? envelope.tasks : {};
|
|
308
|
+
const tasks = Object.fromEntries(Object.entries(rawTasks).map(([taskId, metadata]) => [taskId, canonicalizeTaskStateMetadata(metadata)]).filter((entry) => entry[1] != null));
|
|
309
|
+
return {
|
|
310
|
+
schemaVersion,
|
|
311
|
+
supported: true,
|
|
312
|
+
baseTrackerCommit: typeof envelope.baseTrackerCommit === "string" && envelope.baseTrackerCommit.length > 0 ? envelope.baseTrackerCommit : null,
|
|
313
|
+
tasks
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
// packages/runtime/src/control-plane/state-sync/read.ts
|
|
317
|
+
import { existsSync as existsSync7, readFileSync as readFileSync3 } from "fs";
|
|
318
|
+
import { resolve as resolve7 } from "path";
|
|
319
|
+
|
|
320
|
+
// packages/runtime/src/control-plane/native/git-native.ts
|
|
321
|
+
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
322
|
+
import { tmpdir } from "os";
|
|
323
|
+
import { dirname as dirname3, isAbsolute, resolve as resolve3 } from "path";
|
|
324
|
+
import { createHash } from "crypto";
|
|
325
|
+
var sharedGitNativeOutputDir = resolve3(tmpdir(), "rig-native");
|
|
326
|
+
var sharedGitNativeOutputPath = resolve3(sharedGitNativeOutputDir, `rig-git-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
327
|
+
var trackerCommandUsageProbe = "usage: rig-git fetch-ref <repo-path> <remote> <branch>";
|
|
328
|
+
function temporaryGitBinaryOutputPath(outputPath) {
|
|
329
|
+
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
330
|
+
return resolve3(dirname3(outputPath), `.rig-git-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}${suffix}`);
|
|
331
|
+
}
|
|
332
|
+
function publishGitBinary(tempOutputPath, outputPath) {
|
|
333
|
+
try {
|
|
334
|
+
renameSync(tempOutputPath, outputPath);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
if (process.platform === "win32" && existsSync3(outputPath)) {
|
|
337
|
+
rmSync(outputPath, { force: true });
|
|
338
|
+
renameSync(tempOutputPath, outputPath);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
throw error;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function runtimeRigGitFileName() {
|
|
345
|
+
return `rig-git${process.platform === "win32" ? ".exe" : ""}`;
|
|
346
|
+
}
|
|
347
|
+
function rigGitSourceCandidates() {
|
|
348
|
+
const execDir = process.execPath?.trim() ? dirname3(process.execPath.trim()) : "";
|
|
349
|
+
const cwd = process.cwd()?.trim() || "";
|
|
350
|
+
const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
|
|
351
|
+
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
|
|
352
|
+
const moduleRelativeSource = resolve3(import.meta.dir, "../../../native/rig-git.zig");
|
|
353
|
+
return [...new Set([
|
|
354
|
+
process.env.RIG_NATIVE_GIT_SOURCE?.trim() || "",
|
|
355
|
+
moduleRelativeSource,
|
|
356
|
+
projectRoot ? resolve3(projectRoot, "packages/runtime/native/rig-git.zig") : "",
|
|
357
|
+
hostProjectRoot ? resolve3(hostProjectRoot, "packages/runtime/native/rig-git.zig") : "",
|
|
358
|
+
cwd ? resolve3(cwd, "packages/runtime/native/rig-git.zig") : "",
|
|
359
|
+
execDir ? resolve3(execDir, "..", "..", "packages/runtime/native/rig-git.zig") : "",
|
|
360
|
+
execDir ? resolve3(execDir, "..", "native", "rig-git.zig") : ""
|
|
361
|
+
].filter(Boolean))];
|
|
362
|
+
}
|
|
363
|
+
function nativePackageBinaryCandidates(fromDir, fileName) {
|
|
364
|
+
const candidates = [];
|
|
365
|
+
let cursor = resolve3(fromDir);
|
|
366
|
+
for (let index = 0;index < 8; index += 1) {
|
|
367
|
+
candidates.push(resolve3(cursor, "native", `${process.platform}-${process.arch}`, fileName), resolve3(cursor, "native", `${process.platform}-${process.arch}`, "bin", fileName), resolve3(cursor, "native", fileName), resolve3(cursor, "native", "bin", fileName));
|
|
368
|
+
const parent = dirname3(cursor);
|
|
369
|
+
if (parent === cursor)
|
|
370
|
+
break;
|
|
371
|
+
cursor = parent;
|
|
372
|
+
}
|
|
373
|
+
return candidates;
|
|
374
|
+
}
|
|
375
|
+
function rigGitBinaryCandidates() {
|
|
376
|
+
const execDir = process.execPath?.trim() ? dirname3(process.execPath.trim()) : "";
|
|
377
|
+
const fileName = runtimeRigGitFileName();
|
|
378
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
379
|
+
return [...new Set([
|
|
380
|
+
explicit,
|
|
381
|
+
...nativePackageBinaryCandidates(import.meta.dir, fileName),
|
|
382
|
+
execDir ? resolve3(execDir, fileName) : "",
|
|
383
|
+
execDir ? resolve3(execDir, "..", fileName) : "",
|
|
384
|
+
execDir ? resolve3(execDir, "..", "bin", fileName) : "",
|
|
385
|
+
sharedGitNativeOutputPath
|
|
386
|
+
].filter(Boolean))];
|
|
387
|
+
}
|
|
388
|
+
function resolveGitSourcePath() {
|
|
389
|
+
for (const candidate of rigGitSourceCandidates()) {
|
|
390
|
+
if (candidate && existsSync3(candidate)) {
|
|
391
|
+
return candidate;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
function resolveGitBinaryPath() {
|
|
397
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
for (const candidate of rigGitBinaryCandidates()) {
|
|
401
|
+
if (candidate && existsSync3(candidate)) {
|
|
402
|
+
return candidate;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
function preferredGitBinaryOutputPath() {
|
|
408
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
409
|
+
return explicit || sharedGitNativeOutputPath;
|
|
410
|
+
}
|
|
411
|
+
function binarySupportsTrackerCommandsSync(binaryPath) {
|
|
412
|
+
try {
|
|
413
|
+
const probe = Bun.spawnSync([binaryPath, "fetch-ref", "."], {
|
|
414
|
+
stdout: "pipe",
|
|
415
|
+
stderr: "pipe"
|
|
416
|
+
});
|
|
417
|
+
const stdout = probe.stdout.toString().trim();
|
|
418
|
+
const stderr = probe.stderr.toString().trim();
|
|
419
|
+
if (stdout.includes('"error":"unknown command"')) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
return probe.exitCode === 2 && stderr.includes(trackerCommandUsageProbe);
|
|
423
|
+
} catch {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function nativeBuildManifestPath(outputPath) {
|
|
428
|
+
return `${outputPath}.build-manifest.json`;
|
|
429
|
+
}
|
|
430
|
+
function hasMatchingNativeBuildManifestSync(manifestPath, buildKey) {
|
|
431
|
+
if (!existsSync3(manifestPath)) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
const manifest = JSON.parse(readFileSync2(manifestPath, "utf8"));
|
|
436
|
+
return manifest.version === 1 && manifest.buildKey === buildKey;
|
|
437
|
+
} catch {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function sha256FileSync(path) {
|
|
442
|
+
return createHash("sha256").update(readFileSync2(path)).digest("hex");
|
|
443
|
+
}
|
|
444
|
+
function ensureRigGitBinaryPathSync(outputPath = preferredGitBinaryOutputPath()) {
|
|
445
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
446
|
+
throw new Error("Zig native git is disabled via RIG_DISABLE_ZIG_NATIVE=1");
|
|
447
|
+
}
|
|
448
|
+
const sourcePath = resolveGitSourcePath();
|
|
449
|
+
if (!sourcePath) {
|
|
450
|
+
const binaryPath = resolveGitBinaryPath();
|
|
451
|
+
if (binaryPath) {
|
|
452
|
+
return binaryPath;
|
|
453
|
+
}
|
|
454
|
+
throw new Error("rig-git.zig source file not found.");
|
|
455
|
+
}
|
|
456
|
+
const zigBinary = Bun.which("zig");
|
|
457
|
+
if (!zigBinary) {
|
|
458
|
+
throw new Error("zig is required to build native Rig git tools.");
|
|
459
|
+
}
|
|
460
|
+
mkdirSync2(dirname3(outputPath), { recursive: true });
|
|
461
|
+
const sourceDigest = sha256FileSync(sourcePath);
|
|
462
|
+
const buildKey = JSON.stringify({
|
|
463
|
+
version: 1,
|
|
464
|
+
zigBinary,
|
|
465
|
+
platform: process.platform,
|
|
466
|
+
arch: process.arch,
|
|
467
|
+
sourcePath,
|
|
468
|
+
sourceDigest
|
|
469
|
+
});
|
|
470
|
+
const manifestPath = nativeBuildManifestPath(outputPath);
|
|
471
|
+
const needsBuild = !existsSync3(outputPath) || !hasMatchingNativeBuildManifestSync(manifestPath, buildKey) || !binarySupportsTrackerCommandsSync(outputPath);
|
|
472
|
+
if (!needsBuild) {
|
|
473
|
+
chmodSync2(outputPath, 493);
|
|
474
|
+
return outputPath;
|
|
475
|
+
}
|
|
476
|
+
const tempOutputPath = temporaryGitBinaryOutputPath(outputPath);
|
|
477
|
+
const build = Bun.spawnSync([
|
|
478
|
+
zigBinary,
|
|
479
|
+
"build-exe",
|
|
480
|
+
sourcePath,
|
|
481
|
+
"-O",
|
|
482
|
+
"ReleaseFast",
|
|
483
|
+
`-femit-bin=${tempOutputPath}`
|
|
484
|
+
], {
|
|
485
|
+
cwd: dirname3(sourcePath),
|
|
486
|
+
stdout: "pipe",
|
|
487
|
+
stderr: "pipe"
|
|
488
|
+
});
|
|
489
|
+
if (build.exitCode !== 0 || !existsSync3(tempOutputPath)) {
|
|
490
|
+
const stderr = build.stderr.toString().trim();
|
|
491
|
+
const stdout = build.stdout.toString().trim();
|
|
492
|
+
const details = [stderr, stdout].filter(Boolean).join(`
|
|
493
|
+
`);
|
|
494
|
+
throw new Error(`Failed to build native Rig git tools: ${details || `zig exited with code ${build.exitCode}`}`);
|
|
495
|
+
}
|
|
496
|
+
chmodSync2(tempOutputPath, 493);
|
|
497
|
+
if (existsSync3(outputPath) && hasMatchingNativeBuildManifestSync(manifestPath, buildKey)) {
|
|
498
|
+
rmSync(tempOutputPath, { force: true });
|
|
499
|
+
chmodSync2(outputPath, 493);
|
|
500
|
+
return outputPath;
|
|
501
|
+
}
|
|
502
|
+
publishGitBinary(tempOutputPath, outputPath);
|
|
503
|
+
if (!binarySupportsTrackerCommandsSync(outputPath)) {
|
|
504
|
+
rmSync(outputPath, { force: true });
|
|
505
|
+
throw new Error("Failed to build native Rig git tools: tracker command probe failed");
|
|
506
|
+
}
|
|
507
|
+
writeFileSync2(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
508
|
+
`, "utf8");
|
|
509
|
+
return outputPath;
|
|
510
|
+
}
|
|
511
|
+
function runGitNative(command, args) {
|
|
512
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
513
|
+
return { ok: false, error: "rig-git native disabled" };
|
|
514
|
+
}
|
|
515
|
+
const trackerCommand = command === "fetch-ref" || command === "read-blob-at-ref" || command === "write-tree-commit" || command === "push-ref-with-lease";
|
|
516
|
+
let binaryPath = null;
|
|
517
|
+
if (trackerCommand) {
|
|
518
|
+
try {
|
|
519
|
+
binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
|
|
520
|
+
} catch (error) {
|
|
521
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
522
|
+
if (message.includes("rig-git.zig source file not found")) {
|
|
523
|
+
return { ok: false, error: "rig-git binary not found" };
|
|
524
|
+
}
|
|
525
|
+
return { ok: false, error: message };
|
|
526
|
+
}
|
|
527
|
+
} else {
|
|
528
|
+
const explicitBinaryPath = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
529
|
+
binaryPath = explicitBinaryPath && existsSync3(explicitBinaryPath) ? explicitBinaryPath : !explicitBinaryPath ? resolveGitBinaryPath() : null;
|
|
530
|
+
if (!binaryPath) {
|
|
531
|
+
try {
|
|
532
|
+
binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
|
|
533
|
+
} catch (error) {
|
|
534
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
535
|
+
if (message.includes("rig-git.zig source file not found")) {
|
|
536
|
+
return { ok: false, error: "rig-git binary not found" };
|
|
537
|
+
}
|
|
538
|
+
return { ok: false, error: message };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
const proc = Bun.spawnSync([binaryPath, command, ...args], {
|
|
544
|
+
stdout: "pipe",
|
|
545
|
+
stderr: "pipe",
|
|
546
|
+
env: process.env
|
|
547
|
+
});
|
|
548
|
+
if (proc.exitCode !== 0) {
|
|
549
|
+
const stdoutText = proc.stdout.toString().trim();
|
|
550
|
+
if (stdoutText) {
|
|
551
|
+
try {
|
|
552
|
+
const parsed = JSON.parse(stdoutText);
|
|
553
|
+
if (!parsed.ok) {
|
|
554
|
+
return parsed;
|
|
555
|
+
}
|
|
556
|
+
} catch {}
|
|
557
|
+
}
|
|
558
|
+
const errText = proc.stderr.toString().trim() || `exit code ${proc.exitCode}`;
|
|
559
|
+
return { ok: false, error: errText };
|
|
560
|
+
}
|
|
561
|
+
const output = proc.stdout.toString().trim();
|
|
562
|
+
return JSON.parse(output);
|
|
563
|
+
} catch (err) {
|
|
564
|
+
return { ok: false, error: String(err) };
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function requireGitNative(command, args) {
|
|
568
|
+
const result = runGitNative(command, args);
|
|
569
|
+
if (!result.ok) {
|
|
570
|
+
throw new Error(`rig-git ${command} failed: ${result.error}`);
|
|
571
|
+
}
|
|
572
|
+
return result;
|
|
573
|
+
}
|
|
574
|
+
function requireGitNativeString(command, args) {
|
|
575
|
+
const result = requireGitNative(command, args);
|
|
576
|
+
if ("value" in result && typeof result.value === "string") {
|
|
577
|
+
return result.value;
|
|
578
|
+
}
|
|
579
|
+
throw new Error(`rig-git ${command} returned an unexpected result payload`);
|
|
580
|
+
}
|
|
581
|
+
function nativeFetchRef(repoPath, remote, branch) {
|
|
582
|
+
return requireGitNativeString("fetch-ref", [repoPath, remote, branch]);
|
|
583
|
+
}
|
|
584
|
+
function nativeReadBlobAtRef(repoPath, ref, path) {
|
|
585
|
+
const requestDir = resolve3(sharedGitNativeOutputDir, "reads", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
|
|
586
|
+
mkdirSync2(requestDir, { recursive: true });
|
|
587
|
+
const outputPath = resolve3(requestDir, "blob.txt");
|
|
588
|
+
try {
|
|
589
|
+
requireGitNative("read-blob-at-ref", [repoPath, ref, path, outputPath]);
|
|
590
|
+
return readFileSync2(outputPath, "utf8");
|
|
591
|
+
} finally {
|
|
592
|
+
rmSync(requestDir, { recursive: true, force: true });
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// packages/runtime/src/control-plane/native/runtime-native.ts
|
|
597
|
+
import { dlopen, ptr, suffix, toBuffer } from "bun:ffi";
|
|
598
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3, renameSync as renameSync2, rmSync as rmSync2, statSync as statSync2 } from "fs";
|
|
599
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
600
|
+
import { dirname as dirname4, resolve as resolve4 } from "path";
|
|
601
|
+
var sharedNativeRuntimeOutputDir = resolve4(tmpdir2(), "rig-native");
|
|
602
|
+
var sharedNativeRuntimeOutputPath = resolve4(sharedNativeRuntimeOutputDir, `runtime-native-${process.platform}-${process.arch}.${suffix}`);
|
|
603
|
+
var colocatedNativeRuntimeFileName = `runtime-native.${suffix}`;
|
|
604
|
+
var nativeRuntimeLibrary = await loadNativeRuntimeLibrary();
|
|
605
|
+
async function ensureNativeRuntimeLibraryPath(outputPath = sharedNativeRuntimeOutputPath, options = {}) {
|
|
606
|
+
if (await buildNativeRuntimeLibrary(outputPath, options)) {
|
|
607
|
+
return outputPath;
|
|
608
|
+
}
|
|
609
|
+
return !options.force && existsSync4(outputPath) ? outputPath : null;
|
|
610
|
+
}
|
|
611
|
+
async function loadNativeRuntimeLibrary() {
|
|
612
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
for (const candidate of nativeRuntimeLibraryCandidates()) {
|
|
616
|
+
if (!candidate || !existsSync4(candidate)) {
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
const loaded = tryDlopenNativeRuntimeLibrary(candidate);
|
|
620
|
+
if (loaded) {
|
|
621
|
+
return loaded;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const builtLibraryPath = await ensureNativeRuntimeLibraryPath(sharedNativeRuntimeOutputPath, { force: true });
|
|
625
|
+
if (!builtLibraryPath) {
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
return tryDlopenNativeRuntimeLibrary(builtLibraryPath);
|
|
629
|
+
}
|
|
630
|
+
function nativePackageLibraryCandidates(fromDir, names) {
|
|
631
|
+
const candidates = [];
|
|
632
|
+
let cursor = resolve4(fromDir);
|
|
633
|
+
for (let index = 0;index < 8; index += 1) {
|
|
634
|
+
for (const name of names) {
|
|
635
|
+
candidates.push(resolve4(cursor, "native", `${process.platform}-${process.arch}`, name), resolve4(cursor, "native", `${process.platform}-${process.arch}`, "lib", name), resolve4(cursor, "native", name), resolve4(cursor, "native", "lib", name));
|
|
636
|
+
}
|
|
637
|
+
const parent = dirname4(cursor);
|
|
638
|
+
if (parent === cursor)
|
|
639
|
+
break;
|
|
640
|
+
cursor = parent;
|
|
641
|
+
}
|
|
642
|
+
return candidates;
|
|
643
|
+
}
|
|
644
|
+
function nativeRuntimeLibraryCandidates() {
|
|
645
|
+
const explicit = process.env.RIG_NATIVE_RUNTIME_LIB?.trim() || "";
|
|
646
|
+
const execDir = process.execPath?.trim() ? dirname4(process.execPath.trim()) : "";
|
|
647
|
+
const platformSpecific = `runtime-native-${process.platform}-${process.arch}.${suffix}`;
|
|
648
|
+
return [...new Set([
|
|
649
|
+
explicit,
|
|
650
|
+
...nativePackageLibraryCandidates(import.meta.dir, [colocatedNativeRuntimeFileName, platformSpecific]),
|
|
651
|
+
execDir ? resolve4(execDir, colocatedNativeRuntimeFileName) : "",
|
|
652
|
+
execDir ? resolve4(execDir, platformSpecific) : "",
|
|
653
|
+
execDir ? resolve4(execDir, "..", colocatedNativeRuntimeFileName) : "",
|
|
654
|
+
execDir ? resolve4(execDir, "..", platformSpecific) : "",
|
|
655
|
+
execDir ? resolve4(execDir, "lib", colocatedNativeRuntimeFileName) : "",
|
|
656
|
+
execDir ? resolve4(execDir, "..", "lib", colocatedNativeRuntimeFileName) : "",
|
|
657
|
+
sharedNativeRuntimeOutputPath
|
|
658
|
+
].filter(Boolean))];
|
|
659
|
+
}
|
|
660
|
+
function resolveNativeRuntimeSourcePath() {
|
|
661
|
+
const explicit = process.env.RIG_NATIVE_RUNTIME_SOURCE?.trim();
|
|
662
|
+
if (explicit && existsSync4(explicit)) {
|
|
663
|
+
return explicit;
|
|
664
|
+
}
|
|
665
|
+
const bundled = resolve4(import.meta.dir, "../../../native/snapshot.zig");
|
|
666
|
+
return existsSync4(bundled) ? bundled : null;
|
|
667
|
+
}
|
|
668
|
+
async function buildNativeRuntimeLibrary(outputPath, options = {}) {
|
|
669
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
const zigBinary = Bun.which("zig");
|
|
673
|
+
const sourcePath = resolveNativeRuntimeSourcePath();
|
|
674
|
+
if (!zigBinary || !sourcePath) {
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
const tempOutputPath = `${outputPath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
678
|
+
try {
|
|
679
|
+
mkdirSync3(dirname4(outputPath), { recursive: true });
|
|
680
|
+
const needsBuild = options.force === true || !existsSync4(outputPath) || statSync2(sourcePath).mtimeMs > statSync2(outputPath).mtimeMs;
|
|
681
|
+
if (!needsBuild) {
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
684
|
+
const build = Bun.spawn([
|
|
685
|
+
zigBinary,
|
|
686
|
+
"build-lib",
|
|
687
|
+
sourcePath,
|
|
688
|
+
"-dynamic",
|
|
689
|
+
"-O",
|
|
690
|
+
"ReleaseFast",
|
|
691
|
+
`-femit-bin=${tempOutputPath}`
|
|
692
|
+
], {
|
|
693
|
+
cwd: import.meta.dir,
|
|
694
|
+
stdout: "pipe",
|
|
695
|
+
stderr: "pipe"
|
|
696
|
+
});
|
|
697
|
+
const exitCode = await build.exited;
|
|
698
|
+
if (exitCode !== 0 || !existsSync4(tempOutputPath)) {
|
|
699
|
+
rmSync2(tempOutputPath, { force: true });
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
renameSync2(tempOutputPath, outputPath);
|
|
703
|
+
return true;
|
|
704
|
+
} catch {
|
|
705
|
+
rmSync2(tempOutputPath, { force: true });
|
|
706
|
+
return false;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function tryDlopenNativeRuntimeLibrary(outputPath) {
|
|
710
|
+
try {
|
|
711
|
+
return dlopen(outputPath, {
|
|
712
|
+
rig_scope_match: {
|
|
713
|
+
args: ["ptr", "ptr"],
|
|
714
|
+
returns: "u8"
|
|
715
|
+
},
|
|
716
|
+
snapshot_capture: {
|
|
717
|
+
args: ["ptr", "u64", "ptr", "u64"],
|
|
718
|
+
returns: "ptr"
|
|
719
|
+
},
|
|
720
|
+
snapshot_delta: {
|
|
721
|
+
args: ["ptr", "ptr"],
|
|
722
|
+
returns: "ptr"
|
|
723
|
+
},
|
|
724
|
+
snapshot_store_delta: {
|
|
725
|
+
args: ["ptr", "ptr", "ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64"],
|
|
726
|
+
returns: "ptr"
|
|
727
|
+
},
|
|
728
|
+
snapshot_inspect_delta: {
|
|
729
|
+
args: ["ptr", "u64"],
|
|
730
|
+
returns: "ptr"
|
|
731
|
+
},
|
|
732
|
+
snapshot_apply_delta: {
|
|
733
|
+
args: ["ptr", "u64", "ptr", "u64"],
|
|
734
|
+
returns: "ptr"
|
|
735
|
+
},
|
|
736
|
+
snapshot_release: {
|
|
737
|
+
args: ["ptr"],
|
|
738
|
+
returns: "void"
|
|
739
|
+
},
|
|
740
|
+
runtime_hash_file: {
|
|
741
|
+
args: ["ptr", "u64"],
|
|
742
|
+
returns: "ptr"
|
|
743
|
+
},
|
|
744
|
+
runtime_hash_tree: {
|
|
745
|
+
args: ["ptr", "u64"],
|
|
746
|
+
returns: "ptr"
|
|
747
|
+
},
|
|
748
|
+
runtime_prepare_paths: {
|
|
749
|
+
args: ["ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64"],
|
|
750
|
+
returns: "ptr"
|
|
751
|
+
},
|
|
752
|
+
runtime_link_dependency_layer: {
|
|
753
|
+
args: ["ptr", "u64", "ptr", "u64"],
|
|
754
|
+
returns: "ptr"
|
|
755
|
+
},
|
|
756
|
+
runtime_scan_worktrees: {
|
|
757
|
+
args: ["ptr", "u64"],
|
|
758
|
+
returns: "ptr"
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
} catch {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// packages/runtime/src/control-plane/native/utils.ts
|
|
767
|
+
function resolveMonorepoRoot2(projectRoot) {
|
|
768
|
+
return resolveMonorepoRoot(projectRoot);
|
|
769
|
+
}
|
|
770
|
+
var scopeRegexCache = new Map;
|
|
771
|
+
|
|
772
|
+
// packages/runtime/src/control-plane/runtime/context.ts
|
|
773
|
+
var RUNTIME_CONTEXT_ENV = "RIG_RUNTIME_CONTEXT_FILE";
|
|
774
|
+
|
|
775
|
+
// packages/runtime/src/control-plane/state-sync/repo.ts
|
|
776
|
+
import { existsSync as existsSync6 } from "fs";
|
|
777
|
+
import { resolve as resolve6 } from "path";
|
|
778
|
+
|
|
779
|
+
// packages/runtime/src/control-plane/repos/layout.ts
|
|
780
|
+
import { existsSync as existsSync5 } from "fs";
|
|
781
|
+
import { basename as basename2, dirname as dirname5, join as join2, resolve as resolve5 } from "path";
|
|
782
|
+
|
|
783
|
+
// packages/runtime/src/control-plane/repos/registry.ts
|
|
784
|
+
var MANAGED_REPOS = new Map;
|
|
785
|
+
function getManagedRepoEntry(repoId) {
|
|
786
|
+
const entry = MANAGED_REPOS.get(repoId);
|
|
787
|
+
if (!entry) {
|
|
788
|
+
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.`);
|
|
789
|
+
}
|
|
790
|
+
return entry;
|
|
791
|
+
}
|
|
792
|
+
function listManagedRepoEntries() {
|
|
793
|
+
return Array.from(MANAGED_REPOS.values());
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// packages/runtime/src/control-plane/repos/layout.ts
|
|
797
|
+
function resolveRepoStateDir(projectRoot) {
|
|
798
|
+
const normalizedProjectRoot = resolve5(projectRoot);
|
|
799
|
+
const projectParent = dirname5(normalizedProjectRoot);
|
|
800
|
+
if (basename2(projectParent) === ".worktrees") {
|
|
801
|
+
const ownerRoot = dirname5(projectParent);
|
|
802
|
+
const ownerHasRepoMarkers = existsSync5(resolve5(ownerRoot, ".git")) || existsSync5(resolve5(ownerRoot, ".rig", "state"));
|
|
803
|
+
if (ownerHasRepoMarkers) {
|
|
804
|
+
return resolve5(ownerRoot, ".rig", "state");
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return resolve5(projectRoot, ".rig", "state");
|
|
808
|
+
}
|
|
809
|
+
function resolveManagedRepoLayout(projectRoot, repoId) {
|
|
810
|
+
const normalizedProjectRoot = resolve5(projectRoot);
|
|
811
|
+
const entry = getManagedRepoEntry(repoId);
|
|
812
|
+
const stateDir = resolveRepoStateDir(normalizedProjectRoot);
|
|
813
|
+
const metadataRelativePath = join2("repos", entry.id);
|
|
814
|
+
const metadataRoot = resolve5(stateDir, metadataRelativePath);
|
|
815
|
+
const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
816
|
+
const runsInsideTaskWorktree = runtimeWorkspace && resolve5(runtimeWorkspace) === normalizedProjectRoot || basename2(dirname5(normalizedProjectRoot)) === ".worktrees";
|
|
817
|
+
const isPrimaryManagedRepo = listManagedRepoEntries()[0]?.id === repoId;
|
|
818
|
+
const checkoutRoot = isPrimaryManagedRepo && runsInsideTaskWorktree ? resolveMonorepoRoot(normalizedProjectRoot) : entry.checkoutEnvVar && process.env[entry.checkoutEnvVar]?.trim() ? resolve5(process.env[entry.checkoutEnvVar].trim()) : resolve5(normalizedProjectRoot, entry.alias);
|
|
819
|
+
return {
|
|
820
|
+
projectRoot: normalizedProjectRoot,
|
|
821
|
+
repoId: entry.id,
|
|
822
|
+
alias: entry.alias,
|
|
823
|
+
defaultBranch: entry.defaultBranch,
|
|
824
|
+
remoteUrl: entry.remoteEnvVar && process.env[entry.remoteEnvVar]?.trim() ? process.env[entry.remoteEnvVar].trim() : entry.defaultRemoteUrl,
|
|
825
|
+
checkoutRoot,
|
|
826
|
+
worktreesRoot: resolve5(checkoutRoot, ".worktrees"),
|
|
827
|
+
stateDir,
|
|
828
|
+
metadataRoot,
|
|
829
|
+
metadataRelativePath,
|
|
830
|
+
mirrorRoot: resolve5(metadataRoot, "mirror.git"),
|
|
831
|
+
mirrorStatePath: resolve5(metadataRoot, "mirror-state.json"),
|
|
832
|
+
mirrorStateRelativePath: join2(metadataRelativePath, "mirror-state.json")
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
function resolveMonorepoRepoLayout(projectRoot) {
|
|
836
|
+
const entries = listManagedRepoEntries();
|
|
837
|
+
if (entries.length === 0) {
|
|
838
|
+
throw new Error("resolveMonorepoRepoLayout: no managed repos registered. Either contribute one via " + "RigPlugin.contributes.repoSources (with defaultBranch set), or avoid calling this " + "function for projects where the project root IS the monorepo.");
|
|
839
|
+
}
|
|
840
|
+
const primary = entries[0];
|
|
841
|
+
return resolveManagedRepoLayout(projectRoot, primary.id);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// packages/runtime/src/control-plane/state-sync/repo.ts
|
|
845
|
+
function resolveTrackerRepoPath(projectRoot) {
|
|
846
|
+
const monorepoRoot = resolveMonorepoRoot2(projectRoot);
|
|
847
|
+
try {
|
|
848
|
+
const layout = resolveMonorepoRepoLayout(projectRoot);
|
|
849
|
+
if (existsSync6(resolve6(layout.mirrorRoot, "HEAD"))) {
|
|
850
|
+
return layout.mirrorRoot;
|
|
851
|
+
}
|
|
852
|
+
} catch {}
|
|
853
|
+
return monorepoRoot;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// packages/runtime/src/control-plane/state-sync/read.ts
|
|
857
|
+
var DEFAULT_READ_DEPS = {
|
|
858
|
+
fetchRef: nativeFetchRef,
|
|
859
|
+
readBlobAtRef: nativeReadBlobAtRef,
|
|
860
|
+
exists: existsSync7,
|
|
861
|
+
readFile: (path) => readFileSync3(path, "utf8")
|
|
862
|
+
};
|
|
863
|
+
function parseIssueStatus(rawStatus) {
|
|
864
|
+
const normalized = normalizeTaskLifecycleStatus(rawStatus);
|
|
865
|
+
return normalized ?? "unknown";
|
|
866
|
+
}
|
|
867
|
+
function parseIssueDependencies(raw) {
|
|
868
|
+
if (!Array.isArray(raw)) {
|
|
869
|
+
return [];
|
|
870
|
+
}
|
|
871
|
+
return raw.filter((entry) => !!entry && typeof entry === "object" && !Array.isArray(entry)).map((entry) => ({
|
|
872
|
+
issueId: typeof entry.issue_id === "string" && entry.issue_id.trim() ? entry.issue_id.trim() : null,
|
|
873
|
+
dependsOnId: typeof entry.depends_on_id === "string" && entry.depends_on_id.trim() ? entry.depends_on_id.trim() : null,
|
|
874
|
+
id: typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null,
|
|
875
|
+
type: typeof entry.type === "string" && entry.type.trim() ? entry.type.trim() : null
|
|
876
|
+
}));
|
|
877
|
+
}
|
|
878
|
+
function parseIssuesJsonl(raw) {
|
|
879
|
+
const issues = [];
|
|
880
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
881
|
+
const trimmed = line.trim();
|
|
882
|
+
if (!trimmed) {
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
try {
|
|
886
|
+
const record = JSON.parse(trimmed);
|
|
887
|
+
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : "";
|
|
888
|
+
if (!id) {
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
const rawStatus = typeof record.status === "string" && record.status.trim() ? record.status.trim() : null;
|
|
892
|
+
issues.push({
|
|
893
|
+
id,
|
|
894
|
+
title: typeof record.title === "string" && record.title.trim() ? record.title.trim() : null,
|
|
895
|
+
description: typeof record.description === "string" && record.description.trim() ? record.description.trim() : null,
|
|
896
|
+
acceptanceCriteria: typeof record.acceptance_criteria === "string" && record.acceptance_criteria.trim() ? record.acceptance_criteria.trim() : typeof record.acceptanceCriteria === "string" && record.acceptanceCriteria.trim() ? record.acceptanceCriteria.trim() : null,
|
|
897
|
+
issueType: typeof record.issue_type === "string" && record.issue_type.trim() ? record.issue_type.trim() : null,
|
|
898
|
+
status: parseIssueStatus(rawStatus),
|
|
899
|
+
rawStatus,
|
|
900
|
+
priority: typeof record.priority === "number" ? record.priority : null,
|
|
901
|
+
dependencies: parseIssueDependencies(record.dependencies)
|
|
902
|
+
});
|
|
903
|
+
} catch {}
|
|
904
|
+
}
|
|
905
|
+
return issues;
|
|
906
|
+
}
|
|
907
|
+
function parseTaskStateEnvelope(raw) {
|
|
908
|
+
if (!raw || !raw.trim()) {
|
|
909
|
+
return readTaskStateMetadataEnvelope(null);
|
|
910
|
+
}
|
|
911
|
+
try {
|
|
912
|
+
return readTaskStateMetadataEnvelope(JSON.parse(raw));
|
|
913
|
+
} catch {
|
|
914
|
+
return readTaskStateMetadataEnvelope(null);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function readRemoteBlobAtRef(deps, repoPath, ref, path, options) {
|
|
918
|
+
try {
|
|
919
|
+
return deps.readBlobAtRef(repoPath, ref, path);
|
|
920
|
+
} catch (error) {
|
|
921
|
+
if (options.required) {
|
|
922
|
+
throw error;
|
|
923
|
+
}
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
function shouldPreferLocalTrackerState(options) {
|
|
928
|
+
if (!options.allowLocalFallback) {
|
|
929
|
+
return false;
|
|
930
|
+
}
|
|
931
|
+
const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
932
|
+
if (!runtimeWorkspace) {
|
|
933
|
+
return false;
|
|
934
|
+
}
|
|
935
|
+
if (process.env.RIG_TASK_RUNTIME_ID?.trim()) {
|
|
936
|
+
return true;
|
|
937
|
+
}
|
|
938
|
+
const runtimeContextPath = process.env[RUNTIME_CONTEXT_ENV]?.trim();
|
|
939
|
+
if (runtimeContextPath) {
|
|
940
|
+
return true;
|
|
941
|
+
}
|
|
942
|
+
return existsSync7(resolve7(runtimeWorkspace, ".rig", "runtime-context.json"));
|
|
943
|
+
}
|
|
944
|
+
function readLocalTrackerState(projectRoot, deps) {
|
|
945
|
+
const monorepoRoot = resolveMonorepoRoot2(projectRoot);
|
|
946
|
+
const issuesPath = resolve7(monorepoRoot, ".beads", "issues.jsonl");
|
|
947
|
+
const taskStatePath = resolve7(monorepoRoot, ".beads", "task-state.json");
|
|
948
|
+
return projectSyncedTrackerSnapshot({
|
|
949
|
+
source: "local",
|
|
950
|
+
issuesBaseOid: null,
|
|
951
|
+
issuesText: deps.exists(issuesPath) ? deps.readFile(issuesPath) : "",
|
|
952
|
+
taskStateBaseOid: null,
|
|
953
|
+
taskStateText: deps.exists(taskStatePath) ? deps.readFile(taskStatePath) : null
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
function projectSyncedTrackerSnapshot(input) {
|
|
957
|
+
if (input.source === "remote" && input.issuesBaseOid && input.taskStateBaseOid && input.issuesBaseOid !== input.taskStateBaseOid) {
|
|
958
|
+
throw new Error("Remote tracker files must be read from the same fetched base.");
|
|
959
|
+
}
|
|
960
|
+
return {
|
|
961
|
+
source: input.source,
|
|
962
|
+
baseOid: input.issuesBaseOid ?? input.taskStateBaseOid ?? null,
|
|
963
|
+
issues: parseIssuesJsonl(input.issuesText),
|
|
964
|
+
taskState: parseTaskStateEnvelope(input.taskStateText)
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
function readSyncedTrackerState(projectRoot, deps = {}, options = {}) {
|
|
968
|
+
const readDeps = { ...DEFAULT_READ_DEPS, ...deps };
|
|
969
|
+
const trackerRepoPath = resolveTrackerRepoPath(projectRoot);
|
|
970
|
+
if (shouldPreferLocalTrackerState(options)) {
|
|
971
|
+
return readLocalTrackerState(projectRoot, readDeps);
|
|
972
|
+
}
|
|
973
|
+
try {
|
|
974
|
+
const baseOid = readDeps.fetchRef(trackerRepoPath, "origin", "main");
|
|
975
|
+
return projectSyncedTrackerSnapshot({
|
|
976
|
+
source: "remote",
|
|
977
|
+
issuesBaseOid: baseOid,
|
|
978
|
+
issuesText: readRemoteBlobAtRef(readDeps, trackerRepoPath, baseOid, ".beads/issues.jsonl", {
|
|
979
|
+
required: true
|
|
980
|
+
}) ?? "",
|
|
981
|
+
taskStateBaseOid: baseOid,
|
|
982
|
+
taskStateText: readRemoteBlobAtRef(readDeps, trackerRepoPath, baseOid, ".beads/task-state.json", {
|
|
983
|
+
required: false
|
|
984
|
+
})
|
|
985
|
+
});
|
|
986
|
+
} catch (error) {
|
|
987
|
+
if (!options.allowLocalFallback) {
|
|
988
|
+
throw error;
|
|
989
|
+
}
|
|
990
|
+
return readLocalTrackerState(projectRoot, readDeps);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
// packages/runtime/src/control-plane/state-sync/reconcile.ts
|
|
994
|
+
var STALE_CLAIM_MS = 24 * 60 * 60 * 1000;
|
|
995
|
+
// packages/runtime/src/control-plane/native/workspace-ops.ts
|
|
996
|
+
var ONLINE_REMOTE_HOST_STATUSES = new Set(["ready", "busy", "degraded", "draining"]);
|
|
997
|
+
function asRecord(value) {
|
|
998
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
999
|
+
}
|
|
1000
|
+
function asString(value) {
|
|
1001
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
1002
|
+
}
|
|
1003
|
+
function asStringArray(value) {
|
|
1004
|
+
return Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.trim().length > 0 ? [entry.trim()] : []) : [];
|
|
1005
|
+
}
|
|
1006
|
+
function asStringMap(value) {
|
|
1007
|
+
const record = asRecord(value);
|
|
1008
|
+
if (!record) {
|
|
1009
|
+
return {};
|
|
1010
|
+
}
|
|
1011
|
+
const entries = Object.entries(record).flatMap(([key, entry]) => {
|
|
1012
|
+
if (typeof entry === "string" && entry.trim().length > 0) {
|
|
1013
|
+
return [[key, entry]];
|
|
1014
|
+
}
|
|
1015
|
+
if (typeof entry === "number" || typeof entry === "boolean") {
|
|
1016
|
+
return [[key, String(entry)]];
|
|
1017
|
+
}
|
|
1018
|
+
return [];
|
|
1019
|
+
});
|
|
1020
|
+
return Object.fromEntries(entries);
|
|
1021
|
+
}
|
|
1022
|
+
function parseScalar(raw) {
|
|
1023
|
+
const value = raw.trim();
|
|
1024
|
+
if (value.length === 0)
|
|
1025
|
+
return "";
|
|
1026
|
+
if (value === "null")
|
|
1027
|
+
return null;
|
|
1028
|
+
if (value === "true")
|
|
1029
|
+
return true;
|
|
1030
|
+
if (value === "false")
|
|
1031
|
+
return false;
|
|
1032
|
+
if (/^-?\d+$/.test(value))
|
|
1033
|
+
return Number.parseInt(value, 10);
|
|
1034
|
+
if (/^-?\d+\.\d+$/.test(value))
|
|
1035
|
+
return Number.parseFloat(value);
|
|
1036
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1037
|
+
return value.slice(1, -1);
|
|
1038
|
+
}
|
|
1039
|
+
return value;
|
|
1040
|
+
}
|
|
1041
|
+
function parseYamlSubset(source) {
|
|
1042
|
+
const lines = source.split(/\r?\n/).map((line) => line.replace(/\t/g, " ")).filter((line) => line.trim().length > 0 && !line.trimStart().startsWith("#"));
|
|
1043
|
+
const root = {};
|
|
1044
|
+
const stack = [
|
|
1045
|
+
{ indent: -1, value: root }
|
|
1046
|
+
];
|
|
1047
|
+
const ensureContainer = (indent) => {
|
|
1048
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
1049
|
+
stack.pop();
|
|
1050
|
+
}
|
|
1051
|
+
return stack[stack.length - 1];
|
|
1052
|
+
};
|
|
1053
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
1054
|
+
const rawLine = lines[index];
|
|
1055
|
+
const indent = rawLine.match(/^ */)?.[0].length ?? 0;
|
|
1056
|
+
const line = rawLine.trim();
|
|
1057
|
+
const parent = ensureContainer(indent);
|
|
1058
|
+
if (line.startsWith("- ")) {
|
|
1059
|
+
const itemContent = line.slice(2);
|
|
1060
|
+
if (!Array.isArray(parent.value)) {
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1063
|
+
if (itemContent.includes(":")) {
|
|
1064
|
+
const [firstKey, ...rest] = itemContent.split(":");
|
|
1065
|
+
const valueText2 = rest.join(":").trim();
|
|
1066
|
+
const entry = {};
|
|
1067
|
+
entry[firstKey.trim()] = valueText2.length > 0 ? parseScalar(valueText2) : "";
|
|
1068
|
+
parent.value.push(entry);
|
|
1069
|
+
stack.push({ indent, value: entry });
|
|
1070
|
+
} else {
|
|
1071
|
+
parent.value.push(parseScalar(itemContent));
|
|
1072
|
+
}
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
const colonIndex = line.indexOf(":");
|
|
1076
|
+
if (colonIndex === -1 || !asRecord(parent.value)) {
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
const key = line.slice(0, colonIndex).trim();
|
|
1080
|
+
const valueText = line.slice(colonIndex + 1).trim();
|
|
1081
|
+
const parentRecord = parent.value;
|
|
1082
|
+
if (valueText.length > 0) {
|
|
1083
|
+
parentRecord[key] = parseScalar(valueText);
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
const nextLine = lines[index + 1]?.trim() ?? "";
|
|
1087
|
+
const container = nextLine.startsWith("- ") ? [] : {};
|
|
1088
|
+
parentRecord[key] = container;
|
|
1089
|
+
stack.push({ indent, value: container });
|
|
1090
|
+
}
|
|
1091
|
+
return root;
|
|
1092
|
+
}
|
|
1093
|
+
function relativePath(rootPath, targetPath) {
|
|
1094
|
+
return targetPath.startsWith(rootPath) ? targetPath.slice(rootPath.length + 1) || "." : targetPath;
|
|
1095
|
+
}
|
|
1096
|
+
function resolveRuntimeWorkspaceRoot(projectRoot) {
|
|
1097
|
+
return resolve8(process.env.RIG_TASK_WORKSPACE?.trim() || projectRoot);
|
|
1098
|
+
}
|
|
1099
|
+
function resolveServiceFabricPaths(projectRoot) {
|
|
1100
|
+
const workspaceRoot = resolveRuntimeWorkspaceRoot(projectRoot);
|
|
1101
|
+
const fabricRoot = resolve8(workspaceRoot, ".rig", "service-fabric");
|
|
1102
|
+
return {
|
|
1103
|
+
workspaceRoot,
|
|
1104
|
+
fabricRoot,
|
|
1105
|
+
statePath: resolve8(fabricRoot, "fabric-state.json"),
|
|
1106
|
+
logsDir: resolve8(fabricRoot, "logs")
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
function issueStatus(status) {
|
|
1110
|
+
switch (status) {
|
|
1111
|
+
case "ready":
|
|
1112
|
+
case "open":
|
|
1113
|
+
case "queued":
|
|
1114
|
+
case "blocked":
|
|
1115
|
+
case "completed":
|
|
1116
|
+
case "draft":
|
|
1117
|
+
case "cancelled":
|
|
1118
|
+
return status;
|
|
1119
|
+
case "in_progress":
|
|
1120
|
+
case "under_review":
|
|
1121
|
+
return "running";
|
|
1122
|
+
case "unknown":
|
|
1123
|
+
return "unknown";
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
function collectTaskCounts(projectRoot) {
|
|
1127
|
+
const initial = {
|
|
1128
|
+
total: 0,
|
|
1129
|
+
ready: 0,
|
|
1130
|
+
open: 0,
|
|
1131
|
+
queued: 0,
|
|
1132
|
+
running: 0,
|
|
1133
|
+
blocked: 0,
|
|
1134
|
+
failed: 0,
|
|
1135
|
+
unknown: 0,
|
|
1136
|
+
completed: 0,
|
|
1137
|
+
draft: 0,
|
|
1138
|
+
cancelled: 0
|
|
1139
|
+
};
|
|
1140
|
+
const snapshot = readSyncedTrackerState(projectRoot, {}, { allowLocalFallback: true });
|
|
1141
|
+
for (const issue of snapshot.issues) {
|
|
1142
|
+
if (!issue.id.startsWith("bd-"))
|
|
1143
|
+
continue;
|
|
1144
|
+
if (issue.issueType === "epic")
|
|
1145
|
+
continue;
|
|
1146
|
+
initial.total += 1;
|
|
1147
|
+
initial[issueStatus(issue.status)] += 1;
|
|
1148
|
+
}
|
|
1149
|
+
return initial;
|
|
1150
|
+
}
|
|
1151
|
+
function listManifestDirs(rootPath) {
|
|
1152
|
+
const candidates = [join3(rootPath, "microservices")];
|
|
1153
|
+
const discovered = new Set;
|
|
1154
|
+
for (const candidate of candidates) {
|
|
1155
|
+
if (!existsSync8(candidate))
|
|
1156
|
+
continue;
|
|
1157
|
+
for (const entry of readdirSync2(candidate, { withFileTypes: true })) {
|
|
1158
|
+
if (!entry.isDirectory() || entry.name.startsWith("_"))
|
|
1159
|
+
continue;
|
|
1160
|
+
const serviceDir = join3(candidate, entry.name);
|
|
1161
|
+
if (existsSync8(join3(serviceDir, "service.yaml"))) {
|
|
1162
|
+
discovered.add(serviceDir);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return [...discovered].sort((left, right) => left.localeCompare(right));
|
|
1167
|
+
}
|
|
1168
|
+
function parsePortValue(value) {
|
|
1169
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1170
|
+
return Number(value);
|
|
1171
|
+
}
|
|
1172
|
+
if (typeof value === "string") {
|
|
1173
|
+
const parsed = Number(value);
|
|
1174
|
+
if (Number.isFinite(parsed)) {
|
|
1175
|
+
return parsed;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return 0;
|
|
1179
|
+
}
|
|
1180
|
+
function resolveMonorepoWorkspaceRoot(projectRoot) {
|
|
1181
|
+
return resolve8(process.env.MONOREPO_ROOT?.trim() || projectRoot);
|
|
1182
|
+
}
|
|
1183
|
+
function resolveLocalWorkingDir(monorepoRoot, serviceDir, rawWorkingDir) {
|
|
1184
|
+
if (!rawWorkingDir) {
|
|
1185
|
+
return serviceDir;
|
|
1186
|
+
}
|
|
1187
|
+
if (rawWorkingDir.startsWith("/")) {
|
|
1188
|
+
return rawWorkingDir;
|
|
1189
|
+
}
|
|
1190
|
+
if (rawWorkingDir.startsWith(".")) {
|
|
1191
|
+
return resolve8(serviceDir, rawWorkingDir);
|
|
1192
|
+
}
|
|
1193
|
+
return resolve8(monorepoRoot, rawWorkingDir);
|
|
1194
|
+
}
|
|
1195
|
+
function readNativeServicePlans(projectRoot, selectedServices = []) {
|
|
1196
|
+
const manifestDirs = listManifestDirs(projectRoot);
|
|
1197
|
+
const warnings = [];
|
|
1198
|
+
const services = [];
|
|
1199
|
+
const monorepoRoot = resolveMonorepoWorkspaceRoot(projectRoot);
|
|
1200
|
+
for (const serviceDir of manifestDirs) {
|
|
1201
|
+
const manifestPath = join3(serviceDir, "service.yaml");
|
|
1202
|
+
try {
|
|
1203
|
+
const parsed = asRecord(parseYamlSubset(readFileSync4(manifestPath, "utf-8")));
|
|
1204
|
+
const name = asString(parsed?.name) ?? serviceDir.split("/").at(-1) ?? "service";
|
|
1205
|
+
if (selectedServices.length > 0 && !selectedServices.includes(name)) {
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
const localDev = asRecord(parsed?.local_dev);
|
|
1209
|
+
const runtime = asString(parsed?.runtime) ?? "unknown";
|
|
1210
|
+
const healthcheck = asString(parsed?.healthcheck) ?? "/health";
|
|
1211
|
+
const routePrefix = asString(localDev?.route_prefix);
|
|
1212
|
+
const command = asString(localDev?.command);
|
|
1213
|
+
const requestedMode = asString(localDev?.mode);
|
|
1214
|
+
const environment = asStringMap(localDev?.env);
|
|
1215
|
+
const port = parsePortValue(environment.PORT) || parsePortValue(environment.HTTP_PORT) || parsePortValue(parsed?.port) || 0;
|
|
1216
|
+
const workingDir = resolveLocalWorkingDir(monorepoRoot, serviceDir, asString(localDev?.working_dir));
|
|
1217
|
+
const readinessPath = asString(localDev?.readiness_path) ?? healthcheck;
|
|
1218
|
+
let mode = requestedMode === "process" || requestedMode !== "stub" && command ? "process" : "stub";
|
|
1219
|
+
if (requestedMode === "proxy" && !command) {
|
|
1220
|
+
warnings.push(`${relativePath(projectRoot, manifestPath)} declares local_dev.mode=proxy without a command; treating it as a stub`);
|
|
1221
|
+
mode = "stub";
|
|
1222
|
+
}
|
|
1223
|
+
if (mode === "process" && !command) {
|
|
1224
|
+
warnings.push(`${relativePath(projectRoot, manifestPath)} declares process local_dev mode without a command; treating it as a stub`);
|
|
1225
|
+
mode = "stub";
|
|
1226
|
+
}
|
|
1227
|
+
services.push({
|
|
1228
|
+
name,
|
|
1229
|
+
relativeRootPath: relativePath(projectRoot, serviceDir),
|
|
1230
|
+
absoluteRootPath: serviceDir,
|
|
1231
|
+
runtime,
|
|
1232
|
+
port,
|
|
1233
|
+
healthcheck,
|
|
1234
|
+
routePrefix,
|
|
1235
|
+
mode,
|
|
1236
|
+
command: mode === "process" ? command : null,
|
|
1237
|
+
workingDir,
|
|
1238
|
+
environment,
|
|
1239
|
+
readinessPath
|
|
1240
|
+
});
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
warnings.push(`Failed to parse ${relativePath(projectRoot, manifestPath)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
return { services, warnings };
|
|
1246
|
+
}
|
|
1247
|
+
function readPersistedFabricState(projectRoot) {
|
|
1248
|
+
const { statePath } = resolveServiceFabricPaths(projectRoot);
|
|
1249
|
+
if (!existsSync8(statePath)) {
|
|
1250
|
+
return null;
|
|
1251
|
+
}
|
|
1252
|
+
try {
|
|
1253
|
+
return JSON.parse(readFileSync4(statePath, "utf-8"));
|
|
1254
|
+
} catch {
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
function writePersistedFabricState(projectRoot, state) {
|
|
1259
|
+
const { fabricRoot, logsDir, statePath } = resolveServiceFabricPaths(projectRoot);
|
|
1260
|
+
mkdirSync4(fabricRoot, { recursive: true });
|
|
1261
|
+
mkdirSync4(logsDir, { recursive: true });
|
|
1262
|
+
writeFileSync3(statePath, `${JSON.stringify(state, null, 2)}
|
|
1263
|
+
`, "utf-8");
|
|
1264
|
+
}
|
|
1265
|
+
function isProcessRunning(pid) {
|
|
1266
|
+
if (!pid || pid <= 0) {
|
|
1267
|
+
return false;
|
|
1268
|
+
}
|
|
1269
|
+
try {
|
|
1270
|
+
process.kill(pid, 0);
|
|
1271
|
+
return true;
|
|
1272
|
+
} catch {
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
async function stopProcess(pid) {
|
|
1277
|
+
if (!pid || pid <= 0) {
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
try {
|
|
1281
|
+
process.kill(pid, "SIGTERM");
|
|
1282
|
+
} catch {
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
const deadline = Date.now() + 5000;
|
|
1286
|
+
while (Date.now() < deadline) {
|
|
1287
|
+
if (!isProcessRunning(pid)) {
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
await Bun.sleep(100);
|
|
1291
|
+
}
|
|
1292
|
+
try {
|
|
1293
|
+
process.kill(pid, "SIGKILL");
|
|
1294
|
+
} catch {}
|
|
1295
|
+
}
|
|
1296
|
+
async function probeReadiness(service, timeoutMs = 1000) {
|
|
1297
|
+
if (service.mode === "stub") {
|
|
1298
|
+
return true;
|
|
1299
|
+
}
|
|
1300
|
+
if (!isProcessRunning(service.pid) || service.port <= 0) {
|
|
1301
|
+
return false;
|
|
1302
|
+
}
|
|
1303
|
+
const controller = new AbortController;
|
|
1304
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1305
|
+
try {
|
|
1306
|
+
const response = await fetch(`http://127.0.0.1:${service.port}${service.readinessPath}`, {
|
|
1307
|
+
signal: controller.signal
|
|
1308
|
+
});
|
|
1309
|
+
return response.ok;
|
|
1310
|
+
} catch {
|
|
1311
|
+
return false;
|
|
1312
|
+
} finally {
|
|
1313
|
+
clearTimeout(timeout);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
async function waitForServiceHealthy(service, timeoutMs = 30000) {
|
|
1317
|
+
if (service.mode === "stub") {
|
|
1318
|
+
return true;
|
|
1319
|
+
}
|
|
1320
|
+
const deadline = Date.now() + timeoutMs;
|
|
1321
|
+
while (Date.now() < deadline) {
|
|
1322
|
+
if (await probeReadiness(service, 1000)) {
|
|
1323
|
+
return true;
|
|
1324
|
+
}
|
|
1325
|
+
if (!isProcessRunning(service.pid)) {
|
|
1326
|
+
return false;
|
|
1327
|
+
}
|
|
1328
|
+
await Bun.sleep(250);
|
|
1329
|
+
}
|
|
1330
|
+
return false;
|
|
1331
|
+
}
|
|
1332
|
+
function materializeStateFromPlans(projectRoot, plans, persisted) {
|
|
1333
|
+
const persistedByName = new Map((persisted?.services ?? []).map((service) => [service.name, service]));
|
|
1334
|
+
const { logsDir } = resolveServiceFabricPaths(projectRoot);
|
|
1335
|
+
return plans.map((plan) => {
|
|
1336
|
+
const previous = persistedByName.get(plan.name);
|
|
1337
|
+
return {
|
|
1338
|
+
...plan,
|
|
1339
|
+
pid: previous?.pid ?? null,
|
|
1340
|
+
logPath: previous?.logPath ?? resolve8(logsDir, `${plan.name}.log`),
|
|
1341
|
+
status: previous?.status ?? "stopped"
|
|
1342
|
+
};
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
function summarizeFabricState(projectRoot, services, warnings, updatedAt) {
|
|
1346
|
+
const { fabricRoot } = resolveServiceFabricPaths(projectRoot);
|
|
1347
|
+
const healthyServiceCount = services.filter((service) => service.status === "healthy").length;
|
|
1348
|
+
const status = services.length === 0 ? "empty" : services.every((service) => service.status === "stopped") ? "stopped" : services.every((service) => service.status === "healthy") ? "ready" : services.some((service) => service.status === "booting") ? "booting" : "degraded";
|
|
1349
|
+
return {
|
|
1350
|
+
updatedAt,
|
|
1351
|
+
status,
|
|
1352
|
+
serviceCount: services.length,
|
|
1353
|
+
healthyServiceCount,
|
|
1354
|
+
stateDir: fabricRoot,
|
|
1355
|
+
services: services.map((service) => ({
|
|
1356
|
+
name: service.name,
|
|
1357
|
+
relativeRootPath: service.relativeRootPath,
|
|
1358
|
+
absoluteRootPath: service.absoluteRootPath,
|
|
1359
|
+
mode: service.mode,
|
|
1360
|
+
port: service.port,
|
|
1361
|
+
healthcheck: service.healthcheck,
|
|
1362
|
+
routePrefix: service.routePrefix,
|
|
1363
|
+
command: service.command,
|
|
1364
|
+
environment: service.environment,
|
|
1365
|
+
status: service.status,
|
|
1366
|
+
pid: service.pid,
|
|
1367
|
+
logPath: service.logPath
|
|
1368
|
+
})),
|
|
1369
|
+
warnings
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
function readWorkspaceTopology(projectRoot, compiledAt = new Date().toISOString()) {
|
|
1373
|
+
const manifestDirs = listManifestDirs(projectRoot);
|
|
1374
|
+
const warnings = [];
|
|
1375
|
+
const services = [];
|
|
1376
|
+
for (const serviceDir of manifestDirs) {
|
|
1377
|
+
const manifestPath = join3(serviceDir, "service.yaml");
|
|
1378
|
+
try {
|
|
1379
|
+
const parsed = asRecord(parseYamlSubset(readFileSync4(manifestPath, "utf-8")));
|
|
1380
|
+
const name = asString(parsed?.name) ?? serviceDir.split("/").at(-1) ?? "service";
|
|
1381
|
+
const runtime = asString(parsed?.runtime) ?? "unknown";
|
|
1382
|
+
const portValue = typeof parsed?.port === "number" ? parsed.port : typeof parsed?.port === "string" ? Number(parsed.port) : NaN;
|
|
1383
|
+
services.push({
|
|
1384
|
+
name,
|
|
1385
|
+
relativeRootPath: relativePath(projectRoot, serviceDir),
|
|
1386
|
+
runtime,
|
|
1387
|
+
port: Number.isFinite(portValue) ? Number(portValue) : null,
|
|
1388
|
+
healthcheck: asString(parsed?.healthcheck),
|
|
1389
|
+
splitReadyChecklist: asStringArray(parsed?.split_ready_checklist)
|
|
1390
|
+
});
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
warnings.push(`Failed to parse ${relativePath(projectRoot, manifestPath)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
return {
|
|
1396
|
+
compiledAt,
|
|
1397
|
+
status: services.length === 0 ? "empty" : warnings.length > 0 ? "degraded" : "ready",
|
|
1398
|
+
manifestCount: manifestDirs.length,
|
|
1399
|
+
serviceCount: services.length,
|
|
1400
|
+
services,
|
|
1401
|
+
warnings
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
function discoverRemoteHostManifests(projectRoot) {
|
|
1405
|
+
const workspaceRoot = resolveRuntimeWorkspaceRoot(projectRoot);
|
|
1406
|
+
return [
|
|
1407
|
+
join3(projectRoot, "rig.remote-hosts.yaml"),
|
|
1408
|
+
resolve8(workspaceRoot, ".rig", "remote-hosts.yaml")
|
|
1409
|
+
].filter((candidate) => existsSync8(candidate));
|
|
1410
|
+
}
|
|
1411
|
+
function readWorkspaceRemoteFleet(projectRoot, updatedAt = new Date().toISOString()) {
|
|
1412
|
+
const manifestPaths = discoverRemoteHostManifests(projectRoot);
|
|
1413
|
+
const warnings = [];
|
|
1414
|
+
const hostsById = new Map;
|
|
1415
|
+
for (const manifestPath of manifestPaths) {
|
|
1416
|
+
try {
|
|
1417
|
+
const parsed = asRecord(parseYamlSubset(readFileSync4(manifestPath, "utf-8")));
|
|
1418
|
+
const hosts2 = Array.isArray(parsed?.hosts) ? parsed.hosts : [];
|
|
1419
|
+
for (const entry of hosts2) {
|
|
1420
|
+
const host = asRecord(entry);
|
|
1421
|
+
const id = asString(host?.id);
|
|
1422
|
+
const name = asString(host?.name);
|
|
1423
|
+
const baseUrl = asString(host?.base_url);
|
|
1424
|
+
if (!id || !name || !baseUrl) {
|
|
1425
|
+
warnings.push(`Skipped invalid remote host entry in ${relativePath(projectRoot, manifestPath)}`);
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
hostsById.set(id, {
|
|
1429
|
+
id,
|
|
1430
|
+
name,
|
|
1431
|
+
baseUrl,
|
|
1432
|
+
workspacePath: asString(host?.workspace_path),
|
|
1433
|
+
transport: asString(host?.transport) ?? "websocket",
|
|
1434
|
+
hostname: asString(host?.hostname),
|
|
1435
|
+
region: asString(host?.region),
|
|
1436
|
+
labels: asStringArray(host?.labels),
|
|
1437
|
+
capabilities: asStringArray(host?.capabilities),
|
|
1438
|
+
runtimeAdapters: asStringArray(host?.runtime_adapters),
|
|
1439
|
+
status: asString(host?.status) ?? "offline",
|
|
1440
|
+
currentLeaseCount: typeof host?.current_lease_count === "number" ? Number(host.current_lease_count) : 0,
|
|
1441
|
+
manifestPath: relativePath(projectRoot, manifestPath)
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
warnings.push(`Failed to parse ${relativePath(projectRoot, manifestPath)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
const hosts = [...hostsById.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
1449
|
+
const onlineHostCount = hosts.filter((host) => ONLINE_REMOTE_HOST_STATUSES.has(host.status)).length;
|
|
1450
|
+
return {
|
|
1451
|
+
updatedAt,
|
|
1452
|
+
status: hosts.length === 0 ? "empty" : warnings.length > 0 || hosts.some((host) => host.status === "degraded" || host.status === "quarantined") ? "degraded" : onlineHostCount > 0 ? "ready" : "degraded",
|
|
1453
|
+
manifestCount: manifestPaths.length,
|
|
1454
|
+
hostCount: hosts.length,
|
|
1455
|
+
onlineHostCount,
|
|
1456
|
+
hosts,
|
|
1457
|
+
warnings
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
async function readWorkspaceServiceFabric(projectRoot) {
|
|
1461
|
+
try {
|
|
1462
|
+
const updatedAt = new Date().toISOString();
|
|
1463
|
+
const persisted = readPersistedFabricState(projectRoot);
|
|
1464
|
+
const plans = readNativeServicePlans(projectRoot);
|
|
1465
|
+
const services = materializeStateFromPlans(projectRoot, plans.services, persisted);
|
|
1466
|
+
for (const service of services) {
|
|
1467
|
+
if (service.status === "stopped") {
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
if (!isProcessRunning(service.pid)) {
|
|
1471
|
+
service.pid = null;
|
|
1472
|
+
service.status = "stopped";
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
service.status = await probeReadiness(service, 1000) ? "healthy" : "degraded";
|
|
1476
|
+
}
|
|
1477
|
+
const state = {
|
|
1478
|
+
updatedAt,
|
|
1479
|
+
rootPath: projectRoot,
|
|
1480
|
+
services
|
|
1481
|
+
};
|
|
1482
|
+
writePersistedFabricState(projectRoot, state);
|
|
1483
|
+
return summarizeFabricState(projectRoot, services, plans.warnings, updatedAt);
|
|
1484
|
+
} catch (error) {
|
|
1485
|
+
return {
|
|
1486
|
+
status: "unavailable",
|
|
1487
|
+
warnings: [error instanceof Error ? error.message : String(error)]
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
async function mutateWorkspaceServiceFabric(projectRoot, command, services = []) {
|
|
1492
|
+
const updatedAt = new Date().toISOString();
|
|
1493
|
+
const persisted = readPersistedFabricState(projectRoot);
|
|
1494
|
+
const plans = readNativeServicePlans(projectRoot, services);
|
|
1495
|
+
const stateServices = materializeStateFromPlans(projectRoot, plans.services, persisted);
|
|
1496
|
+
if (command === "verify") {
|
|
1497
|
+
for (const service of stateServices) {
|
|
1498
|
+
if (service.status === "stopped") {
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
if (!isProcessRunning(service.pid)) {
|
|
1502
|
+
service.pid = null;
|
|
1503
|
+
service.status = "stopped";
|
|
1504
|
+
continue;
|
|
1505
|
+
}
|
|
1506
|
+
service.status = await probeReadiness(service, 1000) ? "healthy" : "degraded";
|
|
1507
|
+
}
|
|
1508
|
+
} else if (command === "up") {
|
|
1509
|
+
const { logsDir } = resolveServiceFabricPaths(projectRoot);
|
|
1510
|
+
mkdirSync4(logsDir, { recursive: true });
|
|
1511
|
+
for (const service of stateServices) {
|
|
1512
|
+
if (service.mode === "stub") {
|
|
1513
|
+
service.pid = null;
|
|
1514
|
+
service.status = "healthy";
|
|
1515
|
+
continue;
|
|
1516
|
+
}
|
|
1517
|
+
if (isProcessRunning(service.pid) && await probeReadiness(service, 1000)) {
|
|
1518
|
+
service.status = "healthy";
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
if (isProcessRunning(service.pid)) {
|
|
1522
|
+
await stopProcess(service.pid);
|
|
1523
|
+
}
|
|
1524
|
+
const logPath = resolve8(logsDir, `${service.name}.log`);
|
|
1525
|
+
service.logPath = logPath;
|
|
1526
|
+
appendFileSync2(logPath, `
|
|
1527
|
+
=== ${updatedAt} :: rig service-fabric up :: ${service.name} ===
|
|
1528
|
+
`, "utf-8");
|
|
1529
|
+
const logFd = openSync(logPath, "a");
|
|
1530
|
+
try {
|
|
1531
|
+
const child = spawn(service.command || "true", {
|
|
1532
|
+
cwd: service.workingDir,
|
|
1533
|
+
env: { ...process.env, ...service.environment },
|
|
1534
|
+
shell: true,
|
|
1535
|
+
detached: true,
|
|
1536
|
+
stdio: ["ignore", logFd, logFd]
|
|
1537
|
+
});
|
|
1538
|
+
child.unref();
|
|
1539
|
+
service.pid = child.pid ?? null;
|
|
1540
|
+
service.status = "booting";
|
|
1541
|
+
} finally {
|
|
1542
|
+
closeSync(logFd);
|
|
1543
|
+
}
|
|
1544
|
+
service.status = await waitForServiceHealthy(service) ? "healthy" : "degraded";
|
|
1545
|
+
if (service.status !== "healthy" && !isProcessRunning(service.pid)) {
|
|
1546
|
+
service.pid = null;
|
|
1547
|
+
service.status = "failed";
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
} else if (command === "down") {
|
|
1551
|
+
for (const service of stateServices) {
|
|
1552
|
+
await stopProcess(service.pid);
|
|
1553
|
+
service.pid = null;
|
|
1554
|
+
service.status = "stopped";
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
const merged = new Map;
|
|
1558
|
+
for (const service of persisted?.services ?? []) {
|
|
1559
|
+
merged.set(service.name, service);
|
|
1560
|
+
}
|
|
1561
|
+
for (const service of stateServices) {
|
|
1562
|
+
merged.set(service.name, service);
|
|
1563
|
+
}
|
|
1564
|
+
const nextState = {
|
|
1565
|
+
updatedAt,
|
|
1566
|
+
rootPath: projectRoot,
|
|
1567
|
+
services: [...merged.values()].sort((left, right) => left.name.localeCompare(right.name))
|
|
1568
|
+
};
|
|
1569
|
+
writePersistedFabricState(projectRoot, nextState);
|
|
1570
|
+
return summarizeFabricState(projectRoot, stateServices, plans.warnings, updatedAt);
|
|
1571
|
+
}
|
|
1572
|
+
function countRuntimeDirs(projectRoot) {
|
|
1573
|
+
const runtimeRoot = resolve8(resolveRuntimeWorkspaceRoot(projectRoot), ".rig", "runtime", "agents");
|
|
1574
|
+
if (!existsSync8(runtimeRoot)) {
|
|
1575
|
+
return 0;
|
|
1576
|
+
}
|
|
1577
|
+
return readdirSync2(runtimeRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length;
|
|
1578
|
+
}
|
|
1579
|
+
function countArtifactTaskDirs(projectRoot) {
|
|
1580
|
+
const artifactRoot = resolve8(resolveRuntimeWorkspaceRoot(projectRoot), "artifacts");
|
|
1581
|
+
if (!existsSync8(artifactRoot)) {
|
|
1582
|
+
return 0;
|
|
1583
|
+
}
|
|
1584
|
+
return readdirSync2(artifactRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== "remote-runs").length;
|
|
1585
|
+
}
|
|
1586
|
+
async function readWorkspaceSummary(projectRoot) {
|
|
1587
|
+
const generatedAt = new Date().toISOString();
|
|
1588
|
+
const warnings = [];
|
|
1589
|
+
const topology = readWorkspaceTopology(projectRoot, generatedAt);
|
|
1590
|
+
const remoteFleet = readWorkspaceRemoteFleet(projectRoot, generatedAt);
|
|
1591
|
+
const serviceFabric = await readWorkspaceServiceFabric(projectRoot);
|
|
1592
|
+
if (serviceFabric?.warnings) {
|
|
1593
|
+
warnings.push(...serviceFabric.warnings.map((warning) => String(warning)));
|
|
1594
|
+
}
|
|
1595
|
+
warnings.push(...topology.warnings, ...remoteFleet.warnings);
|
|
1596
|
+
return {
|
|
1597
|
+
rootPath: projectRoot,
|
|
1598
|
+
taskCounts: collectTaskCounts(projectRoot),
|
|
1599
|
+
runtimeCount: countRuntimeDirs(projectRoot),
|
|
1600
|
+
artifactTaskCount: countArtifactTaskDirs(projectRoot),
|
|
1601
|
+
failedApproachesPresent: existsSync8(resolve8(resolveRuntimeWorkspaceRoot(projectRoot), ".rig", "state", "failed_approaches.md")),
|
|
1602
|
+
topology,
|
|
1603
|
+
remoteFleet,
|
|
1604
|
+
serviceFabric,
|
|
1605
|
+
warnings,
|
|
1606
|
+
generatedAt
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
function readTaskArtifactPreview(projectRoot, taskId, fileName, maxBytes = 64 * 1024) {
|
|
1610
|
+
const artifactPath = resolveTaskArtifactDirs(projectRoot, taskId).map((dir) => resolve8(dir, fileName)).find((candidate) => existsSync8(candidate));
|
|
1611
|
+
if (!artifactPath) {
|
|
1612
|
+
throw new Error(`Artifact not found for ${taskId}: ${fileName}`);
|
|
1613
|
+
}
|
|
1614
|
+
const stat = statSync3(artifactPath);
|
|
1615
|
+
const buffer = readFileSync4(artifactPath);
|
|
1616
|
+
const slice = buffer.subarray(0, maxBytes);
|
|
1617
|
+
const contents = slice.includes(0) ? "[binary content omitted]" : slice.toString("utf-8");
|
|
1618
|
+
return {
|
|
1619
|
+
taskId,
|
|
1620
|
+
fileName,
|
|
1621
|
+
path: artifactPath,
|
|
1622
|
+
sizeBytes: stat.size,
|
|
1623
|
+
contents,
|
|
1624
|
+
truncated: buffer.length > maxBytes,
|
|
1625
|
+
maxBytes
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
export {
|
|
1629
|
+
readWorkspaceTopology,
|
|
1630
|
+
readWorkspaceSummary,
|
|
1631
|
+
readWorkspaceServiceFabric,
|
|
1632
|
+
readWorkspaceRemoteFleet,
|
|
1633
|
+
readTaskArtifactPreview,
|
|
1634
|
+
mutateWorkspaceServiceFabric
|
|
1635
|
+
};
|