@h-rig/cli 0.0.6-alpha.82 → 0.0.6-alpha.84
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/build-rig-binaries.js +40 -8
- package/dist/bin/rig.js +23402 -14577
- package/dist/src/app/board.js +217 -41
- package/dist/src/app-opentui/adapters/command.d.ts +2 -0
- package/dist/src/app-opentui/adapters/command.js +329 -0
- package/dist/src/app-opentui/adapters/common.js +2 -2
- package/dist/src/app-opentui/adapters/doctor.d.ts +0 -2
- package/dist/src/app-opentui/adapters/doctor.js +64 -39
- package/dist/src/app-opentui/adapters/family.d.ts +62 -0
- package/dist/src/app-opentui/adapters/family.js +14305 -0
- package/dist/src/app-opentui/adapters/fleet.d.ts +0 -2
- package/dist/src/app-opentui/adapters/fleet.js +90 -60
- package/dist/src/app-opentui/adapters/inbox.d.ts +12 -2
- package/dist/src/app-opentui/adapters/inbox.js +137 -78
- package/dist/src/app-opentui/adapters/init.d.ts +0 -2
- package/dist/src/app-opentui/adapters/init.js +85 -47
- package/dist/src/app-opentui/adapters/inspect.d.ts +52 -0
- package/dist/src/app-opentui/adapters/inspect.js +1024 -0
- package/dist/src/app-opentui/adapters/pi-attach.d.ts +15 -6
- package/dist/src/app-opentui/adapters/pi-attach.js +442 -125
- package/dist/src/app-opentui/adapters/pi.d.ts +23 -0
- package/dist/src/app-opentui/adapters/pi.js +363 -0
- package/dist/src/app-opentui/adapters/plugin.d.ts +84 -0
- package/dist/src/app-opentui/adapters/plugin.js +544 -0
- package/dist/src/app-opentui/adapters/repo.d.ts +37 -0
- package/dist/src/app-opentui/adapters/repo.js +186 -0
- package/dist/src/app-opentui/adapters/run-detail.d.ts +9 -2
- package/dist/src/app-opentui/adapters/run-detail.js +180 -63
- package/dist/src/app-opentui/adapters/server.d.ts +10 -2
- package/dist/src/app-opentui/adapters/server.js +191 -45
- package/dist/src/app-opentui/adapters/tasks.d.ts +11 -2
- package/dist/src/app-opentui/adapters/tasks.js +1123 -143
- package/dist/src/app-opentui/adapters/workspace.d.ts +49 -0
- package/dist/src/app-opentui/adapters/workspace.js +333 -0
- package/dist/src/app-opentui/autocomplete.d.ts +20 -0
- package/dist/src/app-opentui/autocomplete.js +576 -0
- package/dist/src/app-opentui/bootstrap.d.ts +1 -6
- package/dist/src/app-opentui/bootstrap.js +25252 -16474
- package/dist/src/app-opentui/command-palette.d.ts +3 -0
- package/dist/src/app-opentui/command-palette.js +1010 -0
- package/dist/src/app-opentui/command-pty-host.d.ts +62 -0
- package/dist/src/app-opentui/command-pty-host.js +248 -0
- package/dist/src/app-opentui/drone.js +8 -6
- package/dist/src/app-opentui/events.js +1 -1
- package/dist/src/app-opentui/fleet-stats.d.ts +32 -0
- package/dist/src/app-opentui/fleet-stats.js +114 -0
- package/dist/src/app-opentui/focus-manager.d.ts +14 -0
- package/dist/src/app-opentui/focus-manager.js +24 -0
- package/dist/src/app-opentui/index.js +5431 -2797
- package/dist/src/app-opentui/intent.js +179 -50
- package/dist/src/app-opentui/keymap.d.ts +21 -0
- package/dist/src/app-opentui/keymap.js +1748 -0
- package/dist/src/app-opentui/launch-routing.d.ts +16 -0
- package/dist/src/app-opentui/launch-routing.js +55 -0
- package/dist/src/app-opentui/layout.d.ts +7 -0
- package/dist/src/app-opentui/layout.js +13 -6
- package/dist/src/app-opentui/list-search.d.ts +24 -0
- package/dist/src/app-opentui/list-search.js +88 -1
- package/dist/src/app-opentui/pi-host-child.js +99 -17
- package/dist/src/app-opentui/pi-pty-host.d.ts +14 -0
- package/dist/src/app-opentui/pi-pty-host.js +30 -14
- package/dist/src/app-opentui/react/App.d.ts +9 -0
- package/dist/src/app-opentui/react/App.js +5144 -0
- package/dist/src/app-opentui/react/Backdrop.d.ts +5 -0
- package/dist/src/app-opentui/react/Backdrop.js +1834 -0
- package/dist/src/app-opentui/react/ChromeHost.d.ts +5 -0
- package/dist/src/app-opentui/react/ChromeHost.js +2942 -0
- package/dist/src/app-opentui/react/SceneFrameView.d.ts +7 -0
- package/dist/src/app-opentui/react/SceneFrameView.js +529 -0
- package/dist/src/app-opentui/react/context.d.ts +17 -0
- package/dist/src/app-opentui/react/context.js +37 -0
- package/dist/src/app-opentui/react/launch.d.ts +2 -0
- package/dist/src/app-opentui/react/launch.js +5743 -0
- package/dist/src/app-opentui/react/nav.d.ts +18 -0
- package/dist/src/app-opentui/react/nav.js +54 -0
- package/dist/src/app-opentui/react/scroll.d.ts +12 -0
- package/dist/src/app-opentui/react/scroll.js +21 -0
- package/dist/src/app-opentui/react/syntax.d.ts +2 -0
- package/dist/src/app-opentui/react/syntax.js +64 -0
- package/dist/src/app-opentui/registry.js +20428 -4828
- package/dist/src/app-opentui/render/ascii-fleet.d.ts +15 -0
- package/dist/src/app-opentui/render/ascii-fleet.js +82 -0
- package/dist/src/app-opentui/render/constants.d.ts +31 -0
- package/dist/src/app-opentui/render/constants.js +66 -0
- package/dist/src/app-opentui/render/graphics.d.ts +2 -1
- package/dist/src/app-opentui/render/graphics.js +228 -46
- package/dist/src/app-opentui/render/image-visual-layer-worker.js +469 -930
- package/dist/src/app-opentui/render/image-visual-layer.d.ts +25 -10
- package/dist/src/app-opentui/render/image-visual-layer.js +448 -329
- package/dist/src/app-opentui/render/native-host.d.ts +37 -0
- package/dist/src/app-opentui/render/native-host.js +179 -0
- package/dist/src/app-opentui/render/panel-layout.d.ts +38 -0
- package/dist/src/app-opentui/render/panel-layout.js +48 -0
- package/dist/src/app-opentui/render/panels.d.ts +2 -0
- package/dist/src/app-opentui/render/panels.js +78 -38
- package/dist/src/app-opentui/render/preloader.d.ts +10 -0
- package/dist/src/app-opentui/render/preloader.js +165 -0
- package/dist/src/app-opentui/render/scene.d.ts +53 -1
- package/dist/src/app-opentui/render/scene.js +195 -6
- package/dist/src/app-opentui/render/text.d.ts +7 -1
- package/dist/src/app-opentui/render/text.js +15 -8
- package/dist/src/app-opentui/render/type-bar.d.ts +2 -1
- package/dist/src/app-opentui/render/type-bar.js +113 -39
- package/dist/src/app-opentui/runtime-resources.d.ts +16 -0
- package/dist/src/app-opentui/runtime-resources.js +62 -0
- package/dist/src/app-opentui/runtime.d.ts +44 -1
- package/dist/src/app-opentui/runtime.js +5415 -2738
- package/dist/src/app-opentui/scenes/command.d.ts +3 -0
- package/dist/src/app-opentui/scenes/command.js +117 -0
- package/dist/src/app-opentui/scenes/doctor.d.ts +2 -1
- package/dist/src/app-opentui/scenes/doctor.js +221 -17
- package/dist/src/app-opentui/scenes/error.js +60 -20
- package/dist/src/app-opentui/scenes/family-domains/agent.d.ts +2 -0
- package/dist/src/app-opentui/scenes/family-domains/agent.js +348 -0
- package/dist/src/app-opentui/scenes/family-domains/browser.d.ts +2 -0
- package/dist/src/app-opentui/scenes/family-domains/browser.js +195 -0
- package/dist/src/app-opentui/scenes/family-domains/dist.d.ts +2 -0
- package/dist/src/app-opentui/scenes/family-domains/dist.js +243 -0
- package/dist/src/app-opentui/scenes/family-domains/git.d.ts +2 -0
- package/dist/src/app-opentui/scenes/family-domains/git.js +195 -0
- package/dist/src/app-opentui/scenes/family-domains/github.d.ts +2 -0
- package/dist/src/app-opentui/scenes/family-domains/github.js +274 -0
- package/dist/src/app-opentui/scenes/family-domains/harness.d.ts +2 -0
- package/dist/src/app-opentui/scenes/family-domains/harness.js +152 -0
- package/dist/src/app-opentui/scenes/family-domains/index.d.ts +4 -0
- package/dist/src/app-opentui/scenes/family-domains/index.js +1679 -0
- package/dist/src/app-opentui/scenes/family-domains/kit.d.ts +76 -0
- package/dist/src/app-opentui/scenes/family-domains/kit.js +305 -0
- package/dist/src/app-opentui/scenes/family-domains/profile.d.ts +2 -0
- package/dist/src/app-opentui/scenes/family-domains/profile.js +212 -0
- package/dist/src/app-opentui/scenes/family-domains/queue.d.ts +2 -0
- package/dist/src/app-opentui/scenes/family-domains/queue.js +146 -0
- package/dist/src/app-opentui/scenes/family-domains/remote.d.ts +2 -0
- package/dist/src/app-opentui/scenes/family-domains/remote.js +518 -0
- package/dist/src/app-opentui/scenes/family-domains/review.d.ts +2 -0
- package/dist/src/app-opentui/scenes/family-domains/review.js +280 -0
- package/dist/src/app-opentui/scenes/family-domains/setup.d.ts +2 -0
- package/dist/src/app-opentui/scenes/family-domains/setup.js +267 -0
- package/dist/src/app-opentui/scenes/family-domains/stats.d.ts +2 -0
- package/dist/src/app-opentui/scenes/family-domains/stats.js +370 -0
- package/dist/src/app-opentui/scenes/family.d.ts +3 -0
- package/dist/src/app-opentui/scenes/family.js +2144 -0
- package/dist/src/app-opentui/scenes/fleet.js +552 -43
- package/dist/src/app-opentui/scenes/handoff.js +342 -35
- package/dist/src/app-opentui/scenes/help.js +640 -56
- package/dist/src/app-opentui/scenes/inbox.d.ts +2 -1
- package/dist/src/app-opentui/scenes/inbox.js +329 -21
- package/dist/src/app-opentui/scenes/init.d.ts +2 -1
- package/dist/src/app-opentui/scenes/init.js +120 -34
- package/dist/src/app-opentui/scenes/inspect.d.ts +3 -0
- package/dist/src/app-opentui/scenes/inspect.js +793 -0
- package/dist/src/app-opentui/scenes/main.d.ts +2 -1
- package/dist/src/app-opentui/scenes/main.js +264 -29
- package/dist/src/app-opentui/scenes/pi.d.ts +3 -0
- package/dist/src/app-opentui/scenes/pi.js +508 -0
- package/dist/src/app-opentui/scenes/plugin.d.ts +3 -0
- package/dist/src/app-opentui/scenes/plugin.js +486 -0
- package/dist/src/app-opentui/scenes/repo.d.ts +3 -0
- package/dist/src/app-opentui/scenes/repo.js +424 -0
- package/dist/src/app-opentui/scenes/run-detail.d.ts +2 -1
- package/dist/src/app-opentui/scenes/run-detail.js +362 -35
- package/dist/src/app-opentui/scenes/server.d.ts +2 -1
- package/dist/src/app-opentui/scenes/server.js +243 -26
- package/dist/src/app-opentui/scenes/tasks.js +518 -41
- package/dist/src/app-opentui/scenes/workspace.d.ts +3 -0
- package/dist/src/app-opentui/scenes/workspace.js +426 -0
- package/dist/src/app-opentui/selectable.d.ts +19 -0
- package/dist/src/app-opentui/selectable.js +79 -0
- package/dist/src/app-opentui/state.js +129 -36
- package/dist/src/app-opentui/surface-catalog.d.ts +20 -0
- package/dist/src/app-opentui/surface-catalog.js +540 -0
- package/dist/src/app-opentui/terminal-capabilities.d.ts +7 -0
- package/dist/src/app-opentui/terminal-capabilities.js +15 -0
- package/dist/src/app-opentui/theme.d.ts +28 -6
- package/dist/src/app-opentui/theme.js +61 -8
- package/dist/src/app-opentui/types.d.ts +130 -5
- package/dist/src/commands/_authority-runs.d.ts +1 -1
- package/dist/src/commands/_authority-runs.js +2 -12
- package/dist/src/commands/_doctor-checks.js +62 -13
- package/dist/src/commands/_help-catalog.js +95 -15
- package/dist/src/commands/_operator-view.js +97 -15
- package/dist/src/commands/_pi-frontend.d.ts +2 -0
- package/dist/src/commands/_pi-frontend.js +97 -13
- package/dist/src/commands/_preflight.js +64 -14
- package/dist/src/commands/_server-client.js +82 -18
- package/dist/src/commands/_server-events.d.ts +26 -0
- package/dist/src/commands/_server-events.js +310 -0
- package/dist/src/commands/_snapshot-upload.js +62 -13
- package/dist/src/commands/agent.js +2 -12
- package/dist/src/commands/connect.js +7 -1
- package/dist/src/commands/doctor.js +62 -13
- package/dist/src/commands/github.js +144 -23
- package/dist/src/commands/inbox.js +62 -13
- package/dist/src/commands/init.js +82 -18
- package/dist/src/commands/inspect.js +62 -13
- package/dist/src/commands/run.js +100 -15
- package/dist/src/commands/server.js +71 -26
- package/dist/src/commands/setup.js +62 -13
- package/dist/src/commands/stats.js +157 -28
- package/dist/src/commands/task-run-driver.js +64 -25
- package/dist/src/commands/task.js +196 -43
- package/dist/src/commands.js +419 -123
- package/dist/src/index.js +426 -130
- package/package.json +11 -10
- package/dist/src/app-opentui/render/image-visual-layer-native-canvas.d.ts +0 -2
- package/dist/src/app-opentui/render/image-visual-layer-native-canvas.js +0 -1484
|
@@ -134,17 +134,21 @@ function cleanToken(value) {
|
|
|
134
134
|
const trimmed = value?.trim();
|
|
135
135
|
return trimmed ? trimmed : null;
|
|
136
136
|
}
|
|
137
|
-
function
|
|
137
|
+
function readRemoteAuthState(projectRoot) {
|
|
138
138
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
139
139
|
if (!existsSync2(path))
|
|
140
140
|
return null;
|
|
141
141
|
try {
|
|
142
142
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
143
|
-
return
|
|
143
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
144
144
|
} catch {
|
|
145
145
|
return null;
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
149
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
150
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
151
|
+
}
|
|
148
152
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
149
153
|
const scopedKey = resolve2(projectRoot);
|
|
150
154
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -155,15 +159,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
155
159
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
156
160
|
}
|
|
157
161
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
158
|
-
const
|
|
159
|
-
|
|
162
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
163
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
164
|
+
}
|
|
165
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
166
|
+
const repo = readRepoConnection(projectRoot);
|
|
167
|
+
const slug = repo?.project?.trim();
|
|
168
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
160
169
|
return null;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
170
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
171
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
172
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
173
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
165
174
|
return null;
|
|
166
|
-
|
|
175
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
176
|
+
if (!checkoutBaseDir)
|
|
177
|
+
return null;
|
|
178
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
179
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
180
|
+
return inferred;
|
|
167
181
|
}
|
|
168
182
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
169
183
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -174,7 +188,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
174
188
|
if (selected?.connection.kind === "remote") {
|
|
175
189
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
176
190
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
177
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
191
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
178
192
|
return {
|
|
179
193
|
baseUrl: selected.connection.baseUrl,
|
|
180
194
|
authToken,
|
|
@@ -203,7 +217,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
203
217
|
if (!slug)
|
|
204
218
|
return null;
|
|
205
219
|
try {
|
|
206
|
-
const
|
|
220
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
221
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
222
|
+
url.searchParams.set("rt", authToken);
|
|
223
|
+
const response = await fetch(url, {
|
|
207
224
|
headers: mergeHeaders(undefined, authToken)
|
|
208
225
|
});
|
|
209
226
|
if (!response.ok)
|
|
@@ -220,10 +237,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
220
237
|
return null;
|
|
221
238
|
}
|
|
222
239
|
}
|
|
240
|
+
function mergeCookie(existing, name, value) {
|
|
241
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
242
|
+
if (!existing?.trim())
|
|
243
|
+
return encoded;
|
|
244
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
245
|
+
return [...parts, encoded].join("; ");
|
|
246
|
+
}
|
|
247
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
248
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
249
|
+
}
|
|
223
250
|
function mergeHeaders(headers, authToken) {
|
|
224
251
|
const merged = new Headers(headers);
|
|
225
252
|
if (authToken) {
|
|
226
|
-
|
|
253
|
+
const bearer = `Bearer ${authToken}`;
|
|
254
|
+
merged.set("authorization", bearer);
|
|
255
|
+
merged.set("x-auth", bearer);
|
|
256
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
227
257
|
}
|
|
228
258
|
return merged;
|
|
229
259
|
}
|
|
@@ -284,15 +314,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
284
314
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
285
315
|
};
|
|
286
316
|
}
|
|
317
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
318
|
+
try {
|
|
319
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
320
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
321
|
+
} catch {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
326
|
+
return pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/server/project-root" || pathname.startsWith("/api/github/auth/") || pathname === "/api/projects" || pathname.startsWith("/api/projects/");
|
|
327
|
+
}
|
|
287
328
|
async function requestServerJson(context, pathname, init = {}) {
|
|
288
329
|
const server = await ensureServerForCli(context.projectRoot);
|
|
330
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
331
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
332
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
333
|
+
throw new CliError(`Remote server is selected for ${repo?.project ?? "this repo"}, but this checkout has no server-host project root link.`, 1, { hint: "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to repair the remote project link before task/run commands." });
|
|
334
|
+
}
|
|
289
335
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
290
336
|
if (server.serverProjectRoot)
|
|
291
337
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
338
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
339
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
340
|
+
}
|
|
292
341
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
293
342
|
let response;
|
|
294
343
|
try {
|
|
295
|
-
response = await fetch(
|
|
344
|
+
response = await fetch(requestUrl, {
|
|
296
345
|
...init,
|
|
297
346
|
headers
|
|
298
347
|
});
|
|
@@ -770,6 +819,14 @@ async function withSpinner(label, work, options = {}) {
|
|
|
770
819
|
}
|
|
771
820
|
|
|
772
821
|
// packages/cli/src/commands/_pi-frontend.ts
|
|
822
|
+
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
823
|
+
|
|
824
|
+
class OperatorTranscriptUnavailableError extends Error {
|
|
825
|
+
constructor(message) {
|
|
826
|
+
super(message);
|
|
827
|
+
this.name = "OperatorTranscriptUnavailableError";
|
|
828
|
+
}
|
|
829
|
+
}
|
|
773
830
|
function setTemporaryEnv(updates) {
|
|
774
831
|
const previous = new Map;
|
|
775
832
|
for (const [key, value] of Object.entries(updates)) {
|
|
@@ -797,6 +854,23 @@ function buildOperatorPiEnv(input) {
|
|
|
797
854
|
...input.serverProjectRoot ? { RIG_PROJECT_ROOT: input.serverProjectRoot } : {}
|
|
798
855
|
};
|
|
799
856
|
}
|
|
857
|
+
function shouldRequireOperatorTranscript(status) {
|
|
858
|
+
return typeof status === "string" && TERMINAL_RUN_STATUSES.has(status.trim().toLowerCase());
|
|
859
|
+
}
|
|
860
|
+
function missingOperatorTranscriptMessage(runId, status) {
|
|
861
|
+
const shortRun = runId.trim().slice(0, 8) || "unknown";
|
|
862
|
+
const statusText = typeof status === "string" && status.trim() ? status.trim() : "terminal";
|
|
863
|
+
return `Run ${shortRun} is ${statusText}, but no worker Pi session transcript is recorded for this run. It likely failed before Pi started or predates full-session capture. Use \`rig run show ${runId}\` and \`rig run logs ${runId}\` for the lifecycle details.`;
|
|
864
|
+
}
|
|
865
|
+
function statusFromRunDetails(run) {
|
|
866
|
+
if (!run || typeof run !== "object" || Array.isArray(run))
|
|
867
|
+
return;
|
|
868
|
+
const record = run;
|
|
869
|
+
if (typeof record.status === "string")
|
|
870
|
+
return record.status;
|
|
871
|
+
const nested = record.run;
|
|
872
|
+
return nested && typeof nested === "object" && !Array.isArray(nested) ? nested.status : undefined;
|
|
873
|
+
}
|
|
800
874
|
async function prepareOperatorConsole(context, runId, tempSessionDir) {
|
|
801
875
|
const server = await ensureServerForCli(context.projectRoot);
|
|
802
876
|
const localCwd = join(homedir2(), ".rig", "drones", runId.slice(0, 8));
|
|
@@ -826,6 +900,13 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
|
|
|
826
900
|
sessionFileArg = ["--session", localSessionPath];
|
|
827
901
|
}
|
|
828
902
|
} catch {}
|
|
903
|
+
if (sessionFileArg.length === 0) {
|
|
904
|
+
const run = await getRunDetailsViaServer(context, runId).catch(() => null);
|
|
905
|
+
const status = statusFromRunDetails(run);
|
|
906
|
+
if (shouldRequireOperatorTranscript(status)) {
|
|
907
|
+
throw new OperatorTranscriptUnavailableError(missingOperatorTranscriptMessage(runId, status));
|
|
908
|
+
}
|
|
909
|
+
}
|
|
829
910
|
return { server, sessionFileArg };
|
|
830
911
|
}
|
|
831
912
|
var RIG_PI_THEME = {
|
|
@@ -943,6 +1024,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
943
1024
|
let detached = false;
|
|
944
1025
|
try {
|
|
945
1026
|
await runPiMain([
|
|
1027
|
+
"--offline",
|
|
946
1028
|
"--no-extensions",
|
|
947
1029
|
"--no-skills",
|
|
948
1030
|
"--no-prompt-templates",
|
|
@@ -972,7 +1054,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
972
1054
|
}
|
|
973
1055
|
|
|
974
1056
|
// packages/cli/src/commands/_operator-view.ts
|
|
975
|
-
var
|
|
1057
|
+
var TERMINAL_RUN_STATUSES2 = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
976
1058
|
function runStatusFromPayload(payload) {
|
|
977
1059
|
const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
978
1060
|
return String(run.status ?? "unknown").toLowerCase();
|
|
@@ -1050,7 +1132,7 @@ async function attachRunOperatorView(context, input) {
|
|
|
1050
1132
|
}
|
|
1051
1133
|
const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
|
|
1052
1134
|
let timelineCursor = snapshot.timelineCursor;
|
|
1053
|
-
while (!detached && !
|
|
1135
|
+
while (!detached && !TERMINAL_RUN_STATUSES2.has(runStatusFromPayload(snapshot.run))) {
|
|
1054
1136
|
await Bun.sleep(pollMs);
|
|
1055
1137
|
snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
|
|
1056
1138
|
timelineCursor = snapshot.timelineCursor;
|
|
@@ -20,6 +20,8 @@ export declare function buildOperatorPiEnv(input: {
|
|
|
20
20
|
readonly serverProjectRoot?: string | null;
|
|
21
21
|
readonly sessionDir: string;
|
|
22
22
|
}): Record<string, string>;
|
|
23
|
+
export declare function shouldRequireOperatorTranscript(status: unknown): boolean;
|
|
24
|
+
export declare function missingOperatorTranscriptMessage(runId: string, status: unknown): string;
|
|
23
25
|
export declare function attachRunBundledPiFrontend(context: Pick<RunnerContext, "projectRoot" | "outputMode">, input: {
|
|
24
26
|
readonly runId: string;
|
|
25
27
|
readonly steered?: boolean;
|
|
@@ -141,17 +141,21 @@ function cleanToken(value) {
|
|
|
141
141
|
const trimmed = value?.trim();
|
|
142
142
|
return trimmed ? trimmed : null;
|
|
143
143
|
}
|
|
144
|
-
function
|
|
144
|
+
function readRemoteAuthState(projectRoot) {
|
|
145
145
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
146
146
|
if (!existsSync2(path))
|
|
147
147
|
return null;
|
|
148
148
|
try {
|
|
149
149
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
150
|
-
return
|
|
150
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
151
151
|
} catch {
|
|
152
152
|
return null;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
156
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
157
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
158
|
+
}
|
|
155
159
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
156
160
|
const scopedKey = resolve2(projectRoot);
|
|
157
161
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -162,15 +166,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
162
166
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
163
167
|
}
|
|
164
168
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
165
|
-
const
|
|
166
|
-
|
|
169
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
170
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
171
|
+
}
|
|
172
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
173
|
+
const repo = readRepoConnection(projectRoot);
|
|
174
|
+
const slug = repo?.project?.trim();
|
|
175
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
167
176
|
return null;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
177
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
178
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
179
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
180
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
172
181
|
return null;
|
|
173
|
-
|
|
182
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
183
|
+
if (!checkoutBaseDir)
|
|
184
|
+
return null;
|
|
185
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
186
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
187
|
+
return inferred;
|
|
174
188
|
}
|
|
175
189
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
176
190
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -181,7 +195,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
181
195
|
if (selected?.connection.kind === "remote") {
|
|
182
196
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
183
197
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
184
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
198
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
185
199
|
return {
|
|
186
200
|
baseUrl: selected.connection.baseUrl,
|
|
187
201
|
authToken,
|
|
@@ -210,7 +224,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
210
224
|
if (!slug)
|
|
211
225
|
return null;
|
|
212
226
|
try {
|
|
213
|
-
const
|
|
227
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
228
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
229
|
+
url.searchParams.set("rt", authToken);
|
|
230
|
+
const response = await fetch(url, {
|
|
214
231
|
headers: mergeHeaders(undefined, authToken)
|
|
215
232
|
});
|
|
216
233
|
if (!response.ok)
|
|
@@ -227,10 +244,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
227
244
|
return null;
|
|
228
245
|
}
|
|
229
246
|
}
|
|
247
|
+
function mergeCookie(existing, name, value) {
|
|
248
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
249
|
+
if (!existing?.trim())
|
|
250
|
+
return encoded;
|
|
251
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
252
|
+
return [...parts, encoded].join("; ");
|
|
253
|
+
}
|
|
254
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
255
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
256
|
+
}
|
|
230
257
|
function mergeHeaders(headers, authToken) {
|
|
231
258
|
const merged = new Headers(headers);
|
|
232
259
|
if (authToken) {
|
|
233
|
-
|
|
260
|
+
const bearer = `Bearer ${authToken}`;
|
|
261
|
+
merged.set("authorization", bearer);
|
|
262
|
+
merged.set("x-auth", bearer);
|
|
263
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
234
264
|
}
|
|
235
265
|
return merged;
|
|
236
266
|
}
|
|
@@ -291,15 +321,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
291
321
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
292
322
|
};
|
|
293
323
|
}
|
|
324
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
325
|
+
try {
|
|
326
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
327
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
328
|
+
} catch {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
333
|
+
return pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/server/project-root" || pathname.startsWith("/api/github/auth/") || pathname === "/api/projects" || pathname.startsWith("/api/projects/");
|
|
334
|
+
}
|
|
294
335
|
async function requestServerJson(context, pathname, init = {}) {
|
|
295
336
|
const server = await ensureServerForCli(context.projectRoot);
|
|
337
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
338
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
339
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
340
|
+
throw new CliError(`Remote server is selected for ${repo?.project ?? "this repo"}, but this checkout has no server-host project root link.`, 1, { hint: "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to repair the remote project link before task/run commands." });
|
|
341
|
+
}
|
|
296
342
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
297
343
|
if (server.serverProjectRoot)
|
|
298
344
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
345
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
346
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
347
|
+
}
|
|
299
348
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
300
349
|
let response;
|
|
301
350
|
try {
|
|
302
|
-
response = await fetch(
|
|
351
|
+
response = await fetch(requestUrl, {
|
|
303
352
|
...init,
|
|
304
353
|
headers
|
|
305
354
|
});
|
|
@@ -536,6 +585,14 @@ async function withSpinner(label, work, options = {}) {
|
|
|
536
585
|
}
|
|
537
586
|
|
|
538
587
|
// packages/cli/src/commands/_pi-frontend.ts
|
|
588
|
+
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
589
|
+
|
|
590
|
+
class OperatorTranscriptUnavailableError extends Error {
|
|
591
|
+
constructor(message) {
|
|
592
|
+
super(message);
|
|
593
|
+
this.name = "OperatorTranscriptUnavailableError";
|
|
594
|
+
}
|
|
595
|
+
}
|
|
539
596
|
function setTemporaryEnv(updates) {
|
|
540
597
|
const previous = new Map;
|
|
541
598
|
for (const [key, value] of Object.entries(updates)) {
|
|
@@ -563,6 +620,23 @@ function buildOperatorPiEnv(input) {
|
|
|
563
620
|
...input.serverProjectRoot ? { RIG_PROJECT_ROOT: input.serverProjectRoot } : {}
|
|
564
621
|
};
|
|
565
622
|
}
|
|
623
|
+
function shouldRequireOperatorTranscript(status) {
|
|
624
|
+
return typeof status === "string" && TERMINAL_RUN_STATUSES.has(status.trim().toLowerCase());
|
|
625
|
+
}
|
|
626
|
+
function missingOperatorTranscriptMessage(runId, status) {
|
|
627
|
+
const shortRun = runId.trim().slice(0, 8) || "unknown";
|
|
628
|
+
const statusText = typeof status === "string" && status.trim() ? status.trim() : "terminal";
|
|
629
|
+
return `Run ${shortRun} is ${statusText}, but no worker Pi session transcript is recorded for this run. It likely failed before Pi started or predates full-session capture. Use \`rig run show ${runId}\` and \`rig run logs ${runId}\` for the lifecycle details.`;
|
|
630
|
+
}
|
|
631
|
+
function statusFromRunDetails(run) {
|
|
632
|
+
if (!run || typeof run !== "object" || Array.isArray(run))
|
|
633
|
+
return;
|
|
634
|
+
const record = run;
|
|
635
|
+
if (typeof record.status === "string")
|
|
636
|
+
return record.status;
|
|
637
|
+
const nested = record.run;
|
|
638
|
+
return nested && typeof nested === "object" && !Array.isArray(nested) ? nested.status : undefined;
|
|
639
|
+
}
|
|
566
640
|
async function prepareOperatorConsole(context, runId, tempSessionDir) {
|
|
567
641
|
const server = await ensureServerForCli(context.projectRoot);
|
|
568
642
|
const localCwd = join(homedir2(), ".rig", "drones", runId.slice(0, 8));
|
|
@@ -592,6 +666,13 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
|
|
|
592
666
|
sessionFileArg = ["--session", localSessionPath];
|
|
593
667
|
}
|
|
594
668
|
} catch {}
|
|
669
|
+
if (sessionFileArg.length === 0) {
|
|
670
|
+
const run = await getRunDetailsViaServer(context, runId).catch(() => null);
|
|
671
|
+
const status = statusFromRunDetails(run);
|
|
672
|
+
if (shouldRequireOperatorTranscript(status)) {
|
|
673
|
+
throw new OperatorTranscriptUnavailableError(missingOperatorTranscriptMessage(runId, status));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
595
676
|
return { server, sessionFileArg };
|
|
596
677
|
}
|
|
597
678
|
var RIG_PI_THEME = {
|
|
@@ -709,6 +790,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
709
790
|
let detached = false;
|
|
710
791
|
try {
|
|
711
792
|
await runPiMain([
|
|
793
|
+
"--offline",
|
|
712
794
|
"--no-extensions",
|
|
713
795
|
"--no-skills",
|
|
714
796
|
"--no-prompt-templates",
|
|
@@ -737,6 +819,8 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
737
819
|
};
|
|
738
820
|
}
|
|
739
821
|
export {
|
|
822
|
+
shouldRequireOperatorTranscript,
|
|
823
|
+
missingOperatorTranscriptMessage,
|
|
740
824
|
buildOperatorPiEnv,
|
|
741
825
|
attachRunBundledPiFrontend
|
|
742
826
|
};
|
|
@@ -128,17 +128,21 @@ function cleanToken(value) {
|
|
|
128
128
|
const trimmed = value?.trim();
|
|
129
129
|
return trimmed ? trimmed : null;
|
|
130
130
|
}
|
|
131
|
-
function
|
|
131
|
+
function readRemoteAuthState(projectRoot) {
|
|
132
132
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
133
133
|
if (!existsSync2(path))
|
|
134
134
|
return null;
|
|
135
135
|
try {
|
|
136
136
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
137
|
-
return
|
|
137
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
138
138
|
} catch {
|
|
139
139
|
return null;
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
143
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
144
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
145
|
+
}
|
|
142
146
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
143
147
|
const scopedKey = resolve2(projectRoot);
|
|
144
148
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -149,15 +153,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
149
153
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
150
154
|
}
|
|
151
155
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
152
|
-
const
|
|
153
|
-
|
|
156
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
157
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
158
|
+
}
|
|
159
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
160
|
+
const repo = readRepoConnection(projectRoot);
|
|
161
|
+
const slug = repo?.project?.trim();
|
|
162
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
154
163
|
return null;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
164
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
165
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
166
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
167
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
159
168
|
return null;
|
|
160
|
-
|
|
169
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
170
|
+
if (!checkoutBaseDir)
|
|
171
|
+
return null;
|
|
172
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
173
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
174
|
+
return inferred;
|
|
161
175
|
}
|
|
162
176
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
163
177
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -168,7 +182,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
168
182
|
if (selected?.connection.kind === "remote") {
|
|
169
183
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
170
184
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
171
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
185
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
172
186
|
return {
|
|
173
187
|
baseUrl: selected.connection.baseUrl,
|
|
174
188
|
authToken,
|
|
@@ -197,7 +211,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
197
211
|
if (!slug)
|
|
198
212
|
return null;
|
|
199
213
|
try {
|
|
200
|
-
const
|
|
214
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
215
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
216
|
+
url.searchParams.set("rt", authToken);
|
|
217
|
+
const response = await fetch(url, {
|
|
201
218
|
headers: mergeHeaders(undefined, authToken)
|
|
202
219
|
});
|
|
203
220
|
if (!response.ok)
|
|
@@ -214,10 +231,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
214
231
|
return null;
|
|
215
232
|
}
|
|
216
233
|
}
|
|
234
|
+
function mergeCookie(existing, name, value) {
|
|
235
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
236
|
+
if (!existing?.trim())
|
|
237
|
+
return encoded;
|
|
238
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
239
|
+
return [...parts, encoded].join("; ");
|
|
240
|
+
}
|
|
241
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
242
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
243
|
+
}
|
|
217
244
|
function mergeHeaders(headers, authToken) {
|
|
218
245
|
const merged = new Headers(headers);
|
|
219
246
|
if (authToken) {
|
|
220
|
-
|
|
247
|
+
const bearer = `Bearer ${authToken}`;
|
|
248
|
+
merged.set("authorization", bearer);
|
|
249
|
+
merged.set("x-auth", bearer);
|
|
250
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
221
251
|
}
|
|
222
252
|
return merged;
|
|
223
253
|
}
|
|
@@ -278,15 +308,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
278
308
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
279
309
|
};
|
|
280
310
|
}
|
|
311
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
312
|
+
try {
|
|
313
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
314
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
315
|
+
} catch {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
320
|
+
return pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/server/project-root" || pathname.startsWith("/api/github/auth/") || pathname === "/api/projects" || pathname.startsWith("/api/projects/");
|
|
321
|
+
}
|
|
281
322
|
async function requestServerJson(context, pathname, init = {}) {
|
|
282
323
|
const server = await ensureServerForCli(context.projectRoot);
|
|
324
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
325
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
326
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
327
|
+
throw new CliError(`Remote server is selected for ${repo?.project ?? "this repo"}, but this checkout has no server-host project root link.`, 1, { hint: "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to repair the remote project link before task/run commands." });
|
|
328
|
+
}
|
|
283
329
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
284
330
|
if (server.serverProjectRoot)
|
|
285
331
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
332
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
333
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
334
|
+
}
|
|
286
335
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
287
336
|
let response;
|
|
288
337
|
try {
|
|
289
|
-
response = await fetch(
|
|
338
|
+
response = await fetch(requestUrl, {
|
|
290
339
|
...init,
|
|
291
340
|
headers
|
|
292
341
|
});
|
|
@@ -437,7 +486,8 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
437
486
|
}
|
|
438
487
|
}
|
|
439
488
|
const repo = readRepoConnection(context.projectRoot);
|
|
440
|
-
|
|
489
|
+
const remoteWithoutProjectLink = selectedServer?.connectionKind === "remote" && !repo?.project;
|
|
490
|
+
checks.push(repo ? preflightCheck("project-link", "project linked to selected server", repo.project ? "pass" : remoteWithoutProjectLink ? "fail" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : " (missing project link)"}`, remoteWithoutProjectLink ? "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to restore the remote project link." : "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to selected server", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
|
|
441
491
|
try {
|
|
442
492
|
const auth = await request("/api/github/auth/status");
|
|
443
493
|
checks.push(isAuthenticated(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
|