@h-rig/cli 0.0.6-alpha.81 → 0.0.6-alpha.83
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 +2807 -1443
- package/dist/src/app/board.js +62 -13
- 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.js +63 -14
- package/dist/src/app-opentui/adapters/fleet.js +84 -20
- package/dist/src/app-opentui/adapters/inbox.js +83 -19
- package/dist/src/app-opentui/adapters/init.js +87 -23
- package/dist/src/app-opentui/adapters/pi-attach.js +102 -36
- package/dist/src/app-opentui/adapters/run-detail.js +83 -19
- package/dist/src/app-opentui/adapters/server.js +100 -23
- package/dist/src/app-opentui/adapters/tasks.d.ts +11 -0
- package/dist/src/app-opentui/adapters/tasks.js +742 -94
- package/dist/src/app-opentui/bootstrap.d.ts +1 -6
- package/dist/src/app-opentui/bootstrap.js +13776 -12369
- 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/events.js +1 -1
- 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 +3806 -2878
- package/dist/src/app-opentui/intent.js +154 -48
- package/dist/src/app-opentui/keymap.d.ts +20 -0
- package/dist/src/app-opentui/keymap.js +707 -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.js +2 -2
- package/dist/src/app-opentui/pi-host-child.js +66 -16
- package/dist/src/app-opentui/pi-pty-host.d.ts +9 -0
- package/dist/src/app-opentui/pi-pty-host.js +15 -13
- package/dist/src/app-opentui/registry.js +3228 -2396
- 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/graphics.d.ts +1 -1
- package/dist/src/app-opentui/render/graphics.js +131 -16
- package/dist/src/app-opentui/render/image-visual-layer-worker.js +413 -958
- package/dist/src/app-opentui/render/image-visual-layer.d.ts +19 -10
- package/dist/src/app-opentui/render/image-visual-layer.js +391 -357
- 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 +62 -29
- package/dist/src/app-opentui/render/preloader.d.ts +10 -0
- package/dist/src/app-opentui/render/preloader.js +163 -0
- package/dist/src/app-opentui/render/scene.d.ts +3 -0
- package/dist/src/app-opentui/render/scene.js +12 -0
- package/dist/src/app-opentui/render/text.js +5 -1
- package/dist/src/app-opentui/render/type-bar.d.ts +2 -1
- package/dist/src/app-opentui/render/type-bar.js +51 -16
- 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.js +2329 -1512
- package/dist/src/app-opentui/scenes/command.d.ts +3 -0
- package/dist/src/app-opentui/scenes/command.js +103 -0
- package/dist/src/app-opentui/scenes/doctor.d.ts +2 -1
- package/dist/src/app-opentui/scenes/doctor.js +53 -14
- package/dist/src/app-opentui/scenes/error.js +44 -14
- package/dist/src/app-opentui/scenes/fleet.js +40 -21
- package/dist/src/app-opentui/scenes/handoff.js +142 -27
- package/dist/src/app-opentui/scenes/help.js +63 -48
- package/dist/src/app-opentui/scenes/inbox.d.ts +2 -1
- package/dist/src/app-opentui/scenes/inbox.js +64 -17
- package/dist/src/app-opentui/scenes/init.d.ts +2 -1
- package/dist/src/app-opentui/scenes/init.js +62 -21
- package/dist/src/app-opentui/scenes/main.d.ts +2 -1
- package/dist/src/app-opentui/scenes/main.js +80 -24
- package/dist/src/app-opentui/scenes/run-detail.d.ts +2 -1
- package/dist/src/app-opentui/scenes/run-detail.js +39 -25
- package/dist/src/app-opentui/scenes/server.d.ts +2 -1
- package/dist/src/app-opentui/scenes/server.js +71 -20
- package/dist/src/app-opentui/scenes/tasks.js +44 -22
- package/dist/src/app-opentui/state.js +70 -30
- 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/types.d.ts +10 -2
- package/dist/src/commands/_doctor-checks.js +62 -13
- package/dist/src/commands/_operator-view.js +63 -13
- package/dist/src/commands/_pi-frontend.js +63 -13
- package/dist/src/commands/_preflight.js +64 -14
- package/dist/src/commands/_server-client.js +82 -18
- package/dist/src/commands/_snapshot-upload.js +62 -13
- 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 +66 -13
- package/dist/src/commands/server.js +69 -14
- package/dist/src/commands/setup.js +62 -13
- package/dist/src/commands/stats.js +62 -13
- package/dist/src/commands/task-run-driver.js +62 -13
- package/dist/src/commands/task.js +65 -14
- package/dist/src/commands.js +227 -92
- package/dist/src/index.js +234 -99
- package/package.json +8 -9
- 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 -1480
|
@@ -15,6 +15,89 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
|
|
18
|
+
// packages/cli/src/app-opentui/adapters/common.ts
|
|
19
|
+
function adapterContextFromRuntime(runtime, renderer) {
|
|
20
|
+
return {
|
|
21
|
+
projectRoot: runtime.getState().projectRoot,
|
|
22
|
+
renderer,
|
|
23
|
+
ensureRuntime: () => runtime.getRunnerContext(),
|
|
24
|
+
emit: (event) => runtime.emit(event),
|
|
25
|
+
getState: () => runtime.getState()
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function projectRootOf(ctx) {
|
|
29
|
+
const root = ctx.rig?.projectRoot ?? ctx.projectRoot ?? ctx.getState().projectRoot;
|
|
30
|
+
if (typeof root === "string" && root.trim())
|
|
31
|
+
return root;
|
|
32
|
+
throw new Error("App adapter requires a projectRoot.");
|
|
33
|
+
}
|
|
34
|
+
async function runtimeOf(ctx) {
|
|
35
|
+
if (ctx.rig && "runId" in ctx.rig && ctx.ensureRuntime === undefined) {
|
|
36
|
+
return ctx.rig;
|
|
37
|
+
}
|
|
38
|
+
if (ctx.ensureRuntime)
|
|
39
|
+
return ctx.ensureRuntime();
|
|
40
|
+
throw new Error("App adapter requires ensureRuntime() before this action can run.");
|
|
41
|
+
}
|
|
42
|
+
function normalizeAppError(error) {
|
|
43
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
+
const rawHint = error instanceof Error ? error.hint : undefined;
|
|
45
|
+
return {
|
|
46
|
+
message,
|
|
47
|
+
...typeof rawHint === "string" && rawHint.trim() ? { hint: rawHint.trim() } : {}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function emitStarted(ctx, label, optimistic) {
|
|
51
|
+
ctx.emit({ type: "action.started", label, ...optimistic ? { optimistic } : {} });
|
|
52
|
+
}
|
|
53
|
+
function emitProgress(ctx, label, detail, data) {
|
|
54
|
+
ctx.emit({ type: "action.progress", label, ...detail ? { detail } : {}, ...data ? { data } : {} });
|
|
55
|
+
}
|
|
56
|
+
function emitCompleted(ctx, label, data) {
|
|
57
|
+
ctx.emit({ type: "action.completed", label, ...data ? { data } : {} });
|
|
58
|
+
}
|
|
59
|
+
function emitFailed(ctx, label, error, data) {
|
|
60
|
+
if (data)
|
|
61
|
+
patchData(ctx, data);
|
|
62
|
+
const normalized = normalizeAppError(error);
|
|
63
|
+
ctx.emit({
|
|
64
|
+
type: "action.failed",
|
|
65
|
+
label,
|
|
66
|
+
message: normalized.message,
|
|
67
|
+
...normalized.hint ? { hint: normalized.hint } : {},
|
|
68
|
+
cause: error
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function patchData(ctx, data) {
|
|
72
|
+
ctx.emit({ type: "data.patch", data });
|
|
73
|
+
}
|
|
74
|
+
function patchFooter(ctx, footer) {
|
|
75
|
+
ctx.emit({ type: "footer.patch", footer });
|
|
76
|
+
}
|
|
77
|
+
function stringField(record, keys, fallback = "") {
|
|
78
|
+
for (const key of keys) {
|
|
79
|
+
const value = record[key];
|
|
80
|
+
if (typeof value === "string" && value.trim())
|
|
81
|
+
return value.trim();
|
|
82
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
83
|
+
return String(value);
|
|
84
|
+
}
|
|
85
|
+
return fallback;
|
|
86
|
+
}
|
|
87
|
+
function compactRecord(record, keys) {
|
|
88
|
+
const out = {};
|
|
89
|
+
for (const key of keys) {
|
|
90
|
+
const value = record[key];
|
|
91
|
+
if (value !== undefined && value !== null && value !== "")
|
|
92
|
+
out[key] = value;
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
function payloadString(intent, key) {
|
|
97
|
+
const value = intent.action.payload?.[key];
|
|
98
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
18
101
|
// packages/cli/src/runner.ts
|
|
19
102
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
20
103
|
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
@@ -237,17 +320,21 @@ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
|
237
320
|
const scopedKey = resolve2(projectRoot ?? process.cwd());
|
|
238
321
|
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
239
322
|
}
|
|
240
|
-
function
|
|
323
|
+
function readRemoteAuthState(projectRoot) {
|
|
241
324
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
242
325
|
if (!existsSync2(path))
|
|
243
326
|
return null;
|
|
244
327
|
try {
|
|
245
328
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
246
|
-
return
|
|
329
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
247
330
|
} catch {
|
|
248
331
|
return null;
|
|
249
332
|
}
|
|
250
333
|
}
|
|
334
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
335
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
336
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
337
|
+
}
|
|
251
338
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
252
339
|
const scopedKey = resolve2(projectRoot);
|
|
253
340
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -258,15 +345,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
258
345
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
259
346
|
}
|
|
260
347
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
261
|
-
const
|
|
262
|
-
|
|
348
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
349
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
350
|
+
}
|
|
351
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
352
|
+
const repo = readRepoConnection(projectRoot);
|
|
353
|
+
const slug = repo?.project?.trim();
|
|
354
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
263
355
|
return null;
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
356
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
357
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
358
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
359
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
268
360
|
return null;
|
|
269
|
-
|
|
361
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
362
|
+
if (!checkoutBaseDir)
|
|
363
|
+
return null;
|
|
364
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
365
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
366
|
+
return inferred;
|
|
270
367
|
}
|
|
271
368
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
272
369
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -277,7 +374,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
277
374
|
if (selected?.connection.kind === "remote") {
|
|
278
375
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
279
376
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
280
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
377
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
281
378
|
return {
|
|
282
379
|
baseUrl: selected.connection.baseUrl,
|
|
283
380
|
authToken,
|
|
@@ -306,7 +403,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
306
403
|
if (!slug)
|
|
307
404
|
return null;
|
|
308
405
|
try {
|
|
309
|
-
const
|
|
406
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
407
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
408
|
+
url.searchParams.set("rt", authToken);
|
|
409
|
+
const response = await fetch(url, {
|
|
310
410
|
headers: mergeHeaders(undefined, authToken)
|
|
311
411
|
});
|
|
312
412
|
if (!response.ok)
|
|
@@ -333,10 +433,23 @@ function appendTaskFilterParams(url, filters) {
|
|
|
333
433
|
if (filters.limit !== undefined)
|
|
334
434
|
url.searchParams.set("limit", String(filters.limit));
|
|
335
435
|
}
|
|
436
|
+
function mergeCookie(existing, name, value) {
|
|
437
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
438
|
+
if (!existing?.trim())
|
|
439
|
+
return encoded;
|
|
440
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
441
|
+
return [...parts, encoded].join("; ");
|
|
442
|
+
}
|
|
443
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
444
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
445
|
+
}
|
|
336
446
|
function mergeHeaders(headers, authToken) {
|
|
337
447
|
const merged = new Headers(headers);
|
|
338
448
|
if (authToken) {
|
|
339
|
-
|
|
449
|
+
const bearer = `Bearer ${authToken}`;
|
|
450
|
+
merged.set("authorization", bearer);
|
|
451
|
+
merged.set("x-auth", bearer);
|
|
452
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
340
453
|
}
|
|
341
454
|
return merged;
|
|
342
455
|
}
|
|
@@ -396,15 +509,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
396
509
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
397
510
|
};
|
|
398
511
|
}
|
|
512
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
513
|
+
try {
|
|
514
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
515
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
516
|
+
} catch {
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
521
|
+
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/");
|
|
522
|
+
}
|
|
399
523
|
async function requestServerJson(context, pathname, init = {}) {
|
|
400
524
|
const server = await ensureServerForCli(context.projectRoot);
|
|
525
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
526
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
527
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
528
|
+
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." });
|
|
529
|
+
}
|
|
401
530
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
402
531
|
if (server.serverProjectRoot)
|
|
403
532
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
533
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
534
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
535
|
+
}
|
|
404
536
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
405
537
|
let response;
|
|
406
538
|
try {
|
|
407
|
-
response = await fetch(
|
|
539
|
+
response = await fetch(requestUrl, {
|
|
408
540
|
...init,
|
|
409
541
|
headers
|
|
410
542
|
});
|
|
@@ -466,11 +598,26 @@ async function getGitHubAuthStatusViaServer(context) {
|
|
|
466
598
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
467
599
|
}
|
|
468
600
|
async function postGitHubTokenViaServer(context, token, options = {}) {
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
601
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
602
|
+
const requestUrl = new URL(`${server.baseUrl}/api/github/auth/token`);
|
|
603
|
+
reportServerPhase("POST /api/github/auth/token\u2026");
|
|
604
|
+
let response;
|
|
605
|
+
try {
|
|
606
|
+
response = await fetch(requestUrl, {
|
|
607
|
+
method: "POST",
|
|
608
|
+
headers: { "content-type": "application/json" },
|
|
609
|
+
body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot ?? server.serverProjectRoot ?? undefined })
|
|
610
|
+
});
|
|
611
|
+
} catch (error) {
|
|
612
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
613
|
+
throw new CliError(`Rig server auth bootstrap failed: ${error instanceof Error ? error.message : String(error)}
|
|
614
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
615
|
+
}
|
|
616
|
+
const payload = await response.json().catch(() => null);
|
|
617
|
+
if (!response.ok) {
|
|
618
|
+
const detail = payload && typeof payload === "object" && !Array.isArray(payload) ? String(payload.error ?? JSON.stringify(payload)) : `HTTP ${response.status}`;
|
|
619
|
+
throw new CliError(`Rig server auth bootstrap failed (${response.status}): ${detail}`, 1, { hint: "Re-run `rig github auth import-gh`, or check the selected server with `rig server status`." });
|
|
620
|
+
}
|
|
474
621
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
475
622
|
}
|
|
476
623
|
async function prepareRemoteCheckoutViaServer(context, input) {
|
|
@@ -1107,7 +1254,8 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
1107
1254
|
}
|
|
1108
1255
|
}
|
|
1109
1256
|
const repo = readRepoConnection(context.projectRoot);
|
|
1110
|
-
|
|
1257
|
+
const remoteWithoutProjectLink = selectedServer?.connectionKind === "remote" && !repo?.project;
|
|
1258
|
+
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>`."));
|
|
1111
1259
|
try {
|
|
1112
1260
|
const auth = await request("/api/github/auth/status");
|
|
1113
1261
|
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>`."));
|
|
@@ -1682,6 +1830,7 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1682
1830
|
let detached = false;
|
|
1683
1831
|
try {
|
|
1684
1832
|
await runPiMain([
|
|
1833
|
+
"--offline",
|
|
1685
1834
|
"--no-extensions",
|
|
1686
1835
|
"--no-skills",
|
|
1687
1836
|
"--no-prompt-templates",
|
|
@@ -2990,93 +3139,536 @@ var init_task = __esm(() => {
|
|
|
2990
3139
|
init__connection_state();
|
|
2991
3140
|
});
|
|
2992
3141
|
|
|
2993
|
-
// packages/cli/src/app-opentui/
|
|
2994
|
-
import {
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
3142
|
+
// packages/cli/src/app-opentui/pi-pty-host.ts
|
|
3143
|
+
import { fileURLToPath } from "url";
|
|
3144
|
+
import { basename } from "path";
|
|
3145
|
+
import { RGBA, StyledText, TextAttributes } from "@opentui/core";
|
|
3146
|
+
import { Terminal as XtermTerminal } from "@xterm/headless";
|
|
3147
|
+
function clampCols(cols) {
|
|
3148
|
+
return Math.max(MIN_COLS, Math.trunc(cols || 100));
|
|
3149
|
+
}
|
|
3150
|
+
function clampRows(rows) {
|
|
3151
|
+
return Math.max(MIN_ROWS, Math.min(MAX_ROWS, Math.trunc(rows || 35)));
|
|
3152
|
+
}
|
|
3153
|
+
function rgbaFromXtermColor(mode, value) {
|
|
3154
|
+
if (mode === 1 || mode === 2 || mode === XTERM_COLOR_MODE_INDEXED_16 || mode === XTERM_COLOR_MODE_INDEXED_256) {
|
|
3155
|
+
return RGBA.fromIndex(value);
|
|
3156
|
+
}
|
|
3157
|
+
if (mode === 3 || mode === XTERM_COLOR_MODE_RGB) {
|
|
3158
|
+
return RGBA.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255);
|
|
3159
|
+
}
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
function textAttributesFromCell(cell) {
|
|
3163
|
+
let attributes = TextAttributes.NONE;
|
|
3164
|
+
if (cell.isBold())
|
|
3165
|
+
attributes |= TextAttributes.BOLD;
|
|
3166
|
+
if (cell.isDim())
|
|
3167
|
+
attributes |= TextAttributes.DIM;
|
|
3168
|
+
if (cell.isItalic())
|
|
3169
|
+
attributes |= TextAttributes.ITALIC;
|
|
3170
|
+
if (cell.isUnderline())
|
|
3171
|
+
attributes |= TextAttributes.UNDERLINE;
|
|
3172
|
+
if (cell.isBlink())
|
|
3173
|
+
attributes |= TextAttributes.BLINK;
|
|
3174
|
+
if (cell.isInverse())
|
|
3175
|
+
attributes |= TextAttributes.INVERSE;
|
|
3176
|
+
if (cell.isInvisible())
|
|
3177
|
+
attributes |= TextAttributes.HIDDEN;
|
|
3178
|
+
if (cell.isStrikethrough())
|
|
3179
|
+
attributes |= TextAttributes.STRIKETHROUGH;
|
|
3180
|
+
return attributes;
|
|
3181
|
+
}
|
|
3182
|
+
function sameRgba(a, b) {
|
|
3183
|
+
if (!a && !b)
|
|
3184
|
+
return true;
|
|
3185
|
+
return Boolean(a && b && a.equals(b));
|
|
3186
|
+
}
|
|
3187
|
+
function sameStyle(a, b) {
|
|
3188
|
+
return sameRgba(a.fg, b.fg) && sameRgba(a.bg, b.bg) && (a.attributes ?? 0) === (b.attributes ?? 0);
|
|
3189
|
+
}
|
|
3190
|
+
function styleFromCell(cell) {
|
|
2998
3191
|
return {
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
emit: (event) => runtime.emit(event),
|
|
3003
|
-
getState: () => runtime.getState()
|
|
3192
|
+
fg: rgbaFromXtermColor(cell.getFgColorMode(), cell.getFgColor()),
|
|
3193
|
+
bg: rgbaFromXtermColor(cell.getBgColorMode(), cell.getBgColor()),
|
|
3194
|
+
attributes: textAttributesFromCell(cell)
|
|
3004
3195
|
};
|
|
3005
3196
|
}
|
|
3006
|
-
function
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3197
|
+
function lineToStyledText(line, cols) {
|
|
3198
|
+
if (!line)
|
|
3199
|
+
return new StyledText([{ __isChunk: true, text: "" }]);
|
|
3200
|
+
const chunks = [];
|
|
3201
|
+
let run = "";
|
|
3202
|
+
let runStyle = null;
|
|
3203
|
+
const flush = () => {
|
|
3204
|
+
if (!run)
|
|
3205
|
+
return;
|
|
3206
|
+
chunks.push({
|
|
3207
|
+
__isChunk: true,
|
|
3208
|
+
text: run,
|
|
3209
|
+
...runStyle?.fg ? { fg: runStyle.fg } : {},
|
|
3210
|
+
...runStyle?.bg ? { bg: runStyle.bg } : {},
|
|
3211
|
+
...(runStyle?.attributes ?? 0) !== 0 ? { attributes: runStyle.attributes } : {}
|
|
3212
|
+
});
|
|
3213
|
+
run = "";
|
|
3214
|
+
};
|
|
3215
|
+
for (let index = 0;index < cols; index += 1) {
|
|
3216
|
+
const cell = line.getCell(index);
|
|
3217
|
+
if (!cell) {
|
|
3218
|
+
if (runStyle !== null)
|
|
3219
|
+
flush();
|
|
3220
|
+
runStyle = null;
|
|
3221
|
+
run += " ";
|
|
3222
|
+
continue;
|
|
3223
|
+
}
|
|
3224
|
+
if (cell.getWidth() === 0)
|
|
3225
|
+
continue;
|
|
3226
|
+
const style = styleFromCell(cell);
|
|
3227
|
+
if (!runStyle || !sameStyle(runStyle, style)) {
|
|
3228
|
+
flush();
|
|
3229
|
+
runStyle = style;
|
|
3230
|
+
}
|
|
3231
|
+
run += cell.getChars() || " ";
|
|
3015
3232
|
}
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3233
|
+
flush();
|
|
3234
|
+
return new StyledText(chunks.length > 0 ? chunks : [{ __isChunk: true, text: "" }]);
|
|
3235
|
+
}
|
|
3236
|
+
function childCommandPrefix() {
|
|
3237
|
+
const execName = basename(process.execPath).toLowerCase();
|
|
3238
|
+
const currentEntry = process.argv[1];
|
|
3239
|
+
if (execName === "bun" || execName === "bun.exe") {
|
|
3240
|
+
return currentEntry ? [process.execPath, currentEntry, "__opentui-pi-host"] : [process.execPath, fallbackChildScriptPath];
|
|
3241
|
+
}
|
|
3242
|
+
return [process.execPath, "__opentui-pi-host"];
|
|
3019
3243
|
}
|
|
3020
|
-
function
|
|
3021
|
-
const
|
|
3022
|
-
const
|
|
3244
|
+
function withEnv(base) {
|
|
3245
|
+
const env = {};
|
|
3246
|
+
for (const [key, value] of Object.entries(base ?? process.env)) {
|
|
3247
|
+
if (key === "RIG_PLAIN" || key === "RIG_CLI_PLAIN_HELP" || key === "NO_COLOR")
|
|
3248
|
+
continue;
|
|
3249
|
+
if (typeof value === "string")
|
|
3250
|
+
env[key] = value;
|
|
3251
|
+
}
|
|
3023
3252
|
return {
|
|
3024
|
-
|
|
3025
|
-
|
|
3253
|
+
...env,
|
|
3254
|
+
TERM: "xterm-256color",
|
|
3255
|
+
COLORTERM: "truecolor",
|
|
3256
|
+
FORCE_COLOR: env.FORCE_COLOR ?? "1",
|
|
3257
|
+
RIG_OPENTUI_PI_HOST: "1"
|
|
3026
3258
|
};
|
|
3027
3259
|
}
|
|
3028
|
-
function
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3260
|
+
async function startPiPtyHost(options) {
|
|
3261
|
+
activeHost?.dispose("replace");
|
|
3262
|
+
const host = new PiPtyHost(options);
|
|
3263
|
+
activeHost = host;
|
|
3264
|
+
await host.start();
|
|
3265
|
+
return host;
|
|
3033
3266
|
}
|
|
3034
|
-
|
|
3035
|
-
|
|
3267
|
+
|
|
3268
|
+
class PiPtyHost {
|
|
3269
|
+
runId;
|
|
3270
|
+
projectRoot;
|
|
3271
|
+
onSnapshot;
|
|
3272
|
+
onExit;
|
|
3273
|
+
onError;
|
|
3274
|
+
terminal;
|
|
3275
|
+
disposables = [];
|
|
3276
|
+
decoder = new TextDecoder("utf-8");
|
|
3277
|
+
proc = null;
|
|
3278
|
+
pty = null;
|
|
3279
|
+
status = "starting";
|
|
3280
|
+
cols;
|
|
3281
|
+
rows;
|
|
3282
|
+
message = "starting bundled Pi";
|
|
3283
|
+
exitCode;
|
|
3284
|
+
signal;
|
|
3285
|
+
notifyTimer = null;
|
|
3286
|
+
_disposed = false;
|
|
3287
|
+
constructor(options) {
|
|
3288
|
+
this.runId = options.runId;
|
|
3289
|
+
this.projectRoot = options.projectRoot;
|
|
3290
|
+
this.cols = clampCols(options.cols);
|
|
3291
|
+
this.rows = clampRows(options.rows);
|
|
3292
|
+
this.onSnapshot = options.onSnapshot;
|
|
3293
|
+
this.onExit = options.onExit;
|
|
3294
|
+
this.onError = options.onError;
|
|
3295
|
+
this.terminal = new XtermTerminal({
|
|
3296
|
+
allowProposedApi: true,
|
|
3297
|
+
cols: this.cols,
|
|
3298
|
+
rows: this.rows,
|
|
3299
|
+
scrollback: 1000
|
|
3300
|
+
});
|
|
3301
|
+
this.registerTerminalResponders();
|
|
3302
|
+
}
|
|
3303
|
+
get disposed() {
|
|
3304
|
+
return this._disposed;
|
|
3305
|
+
}
|
|
3306
|
+
get snapshot() {
|
|
3307
|
+
return this.createSnapshot();
|
|
3308
|
+
}
|
|
3309
|
+
async start() {
|
|
3310
|
+
if (this._disposed)
|
|
3311
|
+
throw new Error("Pi PTY host is disposed.");
|
|
3312
|
+
if (typeof Bun.Terminal !== "function") {
|
|
3313
|
+
throw new Error("Bun native PTY is unavailable in this runtime. Pi host requires Bun.Terminal.");
|
|
3314
|
+
}
|
|
3315
|
+
const spawnOptions = {
|
|
3316
|
+
cwd: this.projectRoot,
|
|
3317
|
+
env: withEnv(process.env),
|
|
3318
|
+
terminal: {
|
|
3319
|
+
cols: this.cols,
|
|
3320
|
+
rows: this.rows,
|
|
3321
|
+
name: "xterm-256color",
|
|
3322
|
+
data: (_terminal, data) => this.handlePtyData(data)
|
|
3323
|
+
}
|
|
3324
|
+
};
|
|
3325
|
+
const proc = Bun.spawn([
|
|
3326
|
+
...childCommandPrefix(),
|
|
3327
|
+
"--run-id",
|
|
3328
|
+
this.runId,
|
|
3329
|
+
"--project-root",
|
|
3330
|
+
this.projectRoot
|
|
3331
|
+
], spawnOptions);
|
|
3332
|
+
if (!proc.terminal)
|
|
3333
|
+
throw new Error("Bun did not attach a terminal to the bundled Pi child process.");
|
|
3334
|
+
this.proc = proc;
|
|
3335
|
+
this.pty = proc.terminal;
|
|
3336
|
+
this.status = "running";
|
|
3337
|
+
this.message = "bundled Pi running inside this app";
|
|
3338
|
+
this.emitSnapshotSoon(0);
|
|
3339
|
+
proc.exited.then((exitCode) => {
|
|
3340
|
+
if (this._disposed)
|
|
3341
|
+
return;
|
|
3342
|
+
this.status = exitCode === 0 ? "exited" : "failed";
|
|
3343
|
+
this.exitCode = exitCode;
|
|
3344
|
+
this.signal = null;
|
|
3345
|
+
this.message = exitCode === 0 ? "bundled Pi exited" : `bundled Pi exited with code ${exitCode}`;
|
|
3346
|
+
const snapshot = this.createSnapshot();
|
|
3347
|
+
this.onSnapshot?.(snapshot);
|
|
3348
|
+
this.onExit?.(snapshot);
|
|
3349
|
+
if (activeHost === this)
|
|
3350
|
+
activeHost = null;
|
|
3351
|
+
this.dispose("exit", { kill: false, notify: false });
|
|
3352
|
+
}).catch((error) => {
|
|
3353
|
+
if (this._disposed)
|
|
3354
|
+
return;
|
|
3355
|
+
this.status = "failed";
|
|
3356
|
+
this.message = error instanceof Error ? error.message : String(error);
|
|
3357
|
+
const snapshot = this.createSnapshot();
|
|
3358
|
+
this.onSnapshot?.(snapshot);
|
|
3359
|
+
this.onError?.(error, snapshot);
|
|
3360
|
+
if (activeHost === this)
|
|
3361
|
+
activeHost = null;
|
|
3362
|
+
this.dispose("error", { kill: false, notify: false });
|
|
3363
|
+
});
|
|
3364
|
+
}
|
|
3365
|
+
write(data) {
|
|
3366
|
+
if (this._disposed || !this.pty)
|
|
3367
|
+
return;
|
|
3368
|
+
try {
|
|
3369
|
+
this.pty.write(data);
|
|
3370
|
+
} catch (error) {
|
|
3371
|
+
this.status = "failed";
|
|
3372
|
+
this.message = error instanceof Error ? error.message : String(error);
|
|
3373
|
+
const snapshot = this.createSnapshot();
|
|
3374
|
+
this.onSnapshot?.(snapshot);
|
|
3375
|
+
this.onError?.(error, snapshot);
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
resize(cols, rows) {
|
|
3379
|
+
const nextCols = clampCols(cols);
|
|
3380
|
+
const nextRows = clampRows(rows);
|
|
3381
|
+
if (nextCols === this.cols && nextRows === this.rows)
|
|
3382
|
+
return;
|
|
3383
|
+
this.cols = nextCols;
|
|
3384
|
+
this.rows = nextRows;
|
|
3385
|
+
this.terminal.resize(nextCols, nextRows);
|
|
3386
|
+
try {
|
|
3387
|
+
this.pty?.resize(nextCols, nextRows);
|
|
3388
|
+
} catch {}
|
|
3389
|
+
this.emitSnapshotSoon(0);
|
|
3390
|
+
}
|
|
3391
|
+
detach() {
|
|
3392
|
+
this.dispose("detach");
|
|
3393
|
+
return this.createSnapshot("detached from bundled Pi");
|
|
3394
|
+
}
|
|
3395
|
+
dispose(reason = "dispose", options = {}) {
|
|
3396
|
+
if (this._disposed)
|
|
3397
|
+
return;
|
|
3398
|
+
this._disposed = true;
|
|
3399
|
+
if (this.notifyTimer)
|
|
3400
|
+
clearTimeout(this.notifyTimer);
|
|
3401
|
+
this.notifyTimer = null;
|
|
3402
|
+
for (const disposable of this.disposables.splice(0)) {
|
|
3403
|
+
try {
|
|
3404
|
+
disposable.dispose();
|
|
3405
|
+
} catch {}
|
|
3406
|
+
}
|
|
3407
|
+
if (options.kill !== false) {
|
|
3408
|
+
try {
|
|
3409
|
+
this.proc?.kill("SIGTERM");
|
|
3410
|
+
} catch {}
|
|
3411
|
+
}
|
|
3412
|
+
try {
|
|
3413
|
+
this.pty?.close();
|
|
3414
|
+
} catch {}
|
|
3415
|
+
try {
|
|
3416
|
+
this.terminal.dispose();
|
|
3417
|
+
} catch {}
|
|
3418
|
+
if (activeHost === this)
|
|
3419
|
+
activeHost = null;
|
|
3420
|
+
if (options.notify) {
|
|
3421
|
+
this.message = reason;
|
|
3422
|
+
this.onSnapshot?.(this.createSnapshot(reason));
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
handlePtyData(data) {
|
|
3426
|
+
if (this._disposed)
|
|
3427
|
+
return;
|
|
3428
|
+
const text = this.decoder.decode(data, { stream: true });
|
|
3429
|
+
this.respondToRawTerminalQueries(text);
|
|
3430
|
+
this.terminal.write(data, () => this.emitSnapshotSoon());
|
|
3431
|
+
}
|
|
3432
|
+
emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS) {
|
|
3433
|
+
if (this._disposed || this.notifyTimer)
|
|
3434
|
+
return;
|
|
3435
|
+
this.notifyTimer = setTimeout(() => {
|
|
3436
|
+
this.notifyTimer = null;
|
|
3437
|
+
if (this._disposed)
|
|
3438
|
+
return;
|
|
3439
|
+
this.onSnapshot?.(this.createSnapshot());
|
|
3440
|
+
}, delayMs);
|
|
3441
|
+
}
|
|
3442
|
+
createSnapshot(message2 = this.message) {
|
|
3443
|
+
const buffer = this.terminal.buffer.active;
|
|
3444
|
+
const end = buffer.length;
|
|
3445
|
+
const start = Math.max(0, end - MAX_SNAPSHOT_LINES);
|
|
3446
|
+
const styledStart = Math.max(start, end - this.rows - STYLED_SNAPSHOT_MARGIN);
|
|
3447
|
+
const lines = [];
|
|
3448
|
+
const styledLines = [];
|
|
3449
|
+
for (let row = start;row < end; row += 1) {
|
|
3450
|
+
const line = buffer.getLine(row);
|
|
3451
|
+
lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
|
|
3452
|
+
if (row >= styledStart)
|
|
3453
|
+
styledLines[lines.length - 1] = lineToStyledText(line, this.cols);
|
|
3454
|
+
}
|
|
3455
|
+
while (lines.length < this.rows) {
|
|
3456
|
+
lines.push("");
|
|
3457
|
+
styledLines[lines.length - 1] = new StyledText([{ __isChunk: true, text: "" }]);
|
|
3458
|
+
}
|
|
3459
|
+
return {
|
|
3460
|
+
runId: this.runId,
|
|
3461
|
+
status: this.status,
|
|
3462
|
+
cols: this.cols,
|
|
3463
|
+
rows: this.rows,
|
|
3464
|
+
lines,
|
|
3465
|
+
styledLines,
|
|
3466
|
+
message: message2,
|
|
3467
|
+
...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
|
|
3468
|
+
...this.signal !== undefined ? { signal: this.signal } : {}
|
|
3469
|
+
};
|
|
3470
|
+
}
|
|
3471
|
+
registerTerminalResponders() {
|
|
3472
|
+
this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "c" }, (params) => {
|
|
3473
|
+
if (params.length === 0 || params[0] === 0)
|
|
3474
|
+
this.write("\x1B[?62;22c");
|
|
3475
|
+
return false;
|
|
3476
|
+
}));
|
|
3477
|
+
this.disposables.push(this.terminal.parser.registerCsiHandler({ prefix: ">", final: "c" }, (params) => {
|
|
3478
|
+
if (params.length === 0 || params[0] === 0)
|
|
3479
|
+
this.write("\x1B[>0;0;0c");
|
|
3480
|
+
return false;
|
|
3481
|
+
}));
|
|
3482
|
+
this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "n" }, (params) => {
|
|
3483
|
+
if (params[0] === 5) {
|
|
3484
|
+
this.write("\x1B[0n");
|
|
3485
|
+
} else if (params[0] === 6) {
|
|
3486
|
+
const row = this.terminal.buffer.active.cursorY + 1;
|
|
3487
|
+
const col = this.terminal.buffer.active.cursorX + 1;
|
|
3488
|
+
this.write(`\x1B[${row};${col}R`);
|
|
3489
|
+
}
|
|
3490
|
+
return false;
|
|
3491
|
+
}));
|
|
3492
|
+
}
|
|
3493
|
+
respondToRawTerminalQueries(text) {
|
|
3494
|
+
if (!text)
|
|
3495
|
+
return;
|
|
3496
|
+
const decrqm = /\x1b\[\?(\d+)\$p/g;
|
|
3497
|
+
let match;
|
|
3498
|
+
while ((match = decrqm.exec(text)) !== null) {
|
|
3499
|
+
this.write(`\x1B[?${match[1]};2$y`);
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3036
3502
|
}
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3503
|
+
var MIN_COLS = 40, MIN_ROWS = 12, MAX_ROWS = 300, MAX_SNAPSHOT_LINES = 360, STYLED_SNAPSHOT_MARGIN = 6, SNAPSHOT_DELAY_MS = 120, fallbackChildScriptPath, activeHost = null, XTERM_COLOR_MODE_INDEXED_16 = 16777216, XTERM_COLOR_MODE_INDEXED_256 = 33554432, XTERM_COLOR_MODE_RGB = 50331648;
|
|
3504
|
+
var init_pi_pty_host = __esm(() => {
|
|
3505
|
+
fallbackChildScriptPath = fileURLToPath(new URL("./pi-host-child.ts", import.meta.url));
|
|
3506
|
+
});
|
|
3507
|
+
|
|
3508
|
+
// packages/cli/src/app-opentui/adapters/fleet.ts
|
|
3509
|
+
function normalizeRunRecord(record) {
|
|
3510
|
+
const runId = stringField(record, ["runId", "id"]);
|
|
3511
|
+
if (!runId)
|
|
3512
|
+
return null;
|
|
3513
|
+
const title = stringField(record, ["title", "taskTitle", "taskId"], "untitled run");
|
|
3514
|
+
const taskId3 = stringField(record, ["taskId"]);
|
|
3515
|
+
const updatedAt = stringField(record, ["updatedAt", "createdAt"]);
|
|
3516
|
+
return {
|
|
3517
|
+
runId,
|
|
3518
|
+
status: stringField(record, ["status", "state"], "unknown"),
|
|
3519
|
+
title,
|
|
3520
|
+
raw: record,
|
|
3521
|
+
...taskId3 ? { taskId: taskId3 } : {},
|
|
3522
|
+
...updatedAt ? { updatedAt } : {}
|
|
3523
|
+
};
|
|
3048
3524
|
}
|
|
3049
|
-
function
|
|
3050
|
-
|
|
3525
|
+
async function refreshFleet(ctx, options = {}) {
|
|
3526
|
+
const label = "Refreshing fleet";
|
|
3527
|
+
emitStarted(ctx, label);
|
|
3528
|
+
try {
|
|
3529
|
+
const { listRunsViaServer: listRunsViaServer2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
|
|
3530
|
+
const projectRoot = projectRootOf(ctx);
|
|
3531
|
+
emitProgress(ctx, label, "contacting selected server");
|
|
3532
|
+
const [runs, serverLabel] = await Promise.all([
|
|
3533
|
+
listRunsViaServer2({ projectRoot }, { limit: options.limit ?? 50 }),
|
|
3534
|
+
resolveServerConnectionLabel2(projectRoot)
|
|
3535
|
+
]);
|
|
3536
|
+
const normalized = runs.flatMap((run) => {
|
|
3537
|
+
const entry = normalizeRunRecord(run);
|
|
3538
|
+
return entry ? [entry] : [];
|
|
3539
|
+
});
|
|
3540
|
+
patchData(ctx, { fleet: { runs: normalized, refreshedAt: new Date().toISOString() } });
|
|
3541
|
+
patchFooter(ctx, { server: serverLabel, runs: String(normalized.length) });
|
|
3542
|
+
emitCompleted(ctx, label, { count: normalized.length });
|
|
3543
|
+
return normalized;
|
|
3544
|
+
} catch (error) {
|
|
3545
|
+
emitFailed(ctx, label, error);
|
|
3546
|
+
throw error;
|
|
3547
|
+
}
|
|
3051
3548
|
}
|
|
3052
|
-
|
|
3053
|
-
|
|
3549
|
+
var init_fleet = () => {};
|
|
3550
|
+
|
|
3551
|
+
// packages/cli/src/app-opentui/adapters/pi-attach.ts
|
|
3552
|
+
var exports_pi_attach = {};
|
|
3553
|
+
__export(exports_pi_attach, {
|
|
3554
|
+
preparePiAttachHandoff: () => preparePiAttachHandoff,
|
|
3555
|
+
createPiAttachAdapter: () => createPiAttachAdapter,
|
|
3556
|
+
attachRunWithBundledPi: () => attachRunWithBundledPi
|
|
3557
|
+
});
|
|
3558
|
+
function createPiAttachAdapter(renderer) {
|
|
3559
|
+
return {
|
|
3560
|
+
id: "pi-attach",
|
|
3561
|
+
async handleIntent(runtime, intent) {
|
|
3562
|
+
if (intent.scene !== "handoff" && intent.action.kind !== "run-attach")
|
|
3563
|
+
return false;
|
|
3564
|
+
const ctx = adapterContextFromRuntime(runtime, renderer);
|
|
3565
|
+
const runId = payloadString(intent, "runId") ?? runtime.getState().data.selectedRunId;
|
|
3566
|
+
if (intent.action.kind === "run-attach" && runId) {
|
|
3567
|
+
await attachRunWithBundledPi(ctx, runId);
|
|
3568
|
+
return true;
|
|
3569
|
+
}
|
|
3570
|
+
if (runId) {
|
|
3571
|
+
await preparePiAttachHandoff(ctx, runId);
|
|
3572
|
+
return true;
|
|
3573
|
+
}
|
|
3574
|
+
return false;
|
|
3575
|
+
}
|
|
3576
|
+
};
|
|
3054
3577
|
}
|
|
3055
|
-
function
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3578
|
+
async function preparePiAttachHandoff(ctx, runId) {
|
|
3579
|
+
const cleanRunId = runId.trim();
|
|
3580
|
+
const label = `Preparing Pi ${cleanRunId.slice(0, 8)}`;
|
|
3581
|
+
emitStarted(ctx, label, { piAttach: { runId: cleanRunId, status: "preparing" } });
|
|
3582
|
+
try {
|
|
3583
|
+
const { getRunDetailsViaServer: getRunDetailsViaServer2, getRunPiStatusViaServer: getRunPiStatusViaServer2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
|
|
3584
|
+
const projectRoot = projectRootOf(ctx);
|
|
3585
|
+
emitProgress(ctx, label, "checking run and Pi session readiness");
|
|
3586
|
+
const [run, piStatus, server] = await Promise.all([
|
|
3587
|
+
getRunDetailsViaServer2({ projectRoot }, cleanRunId).catch((error) => ({ error: error instanceof Error ? error.message : String(error) })),
|
|
3588
|
+
getRunPiStatusViaServer2({ projectRoot }, cleanRunId).catch((error) => ({ error: error instanceof Error ? error.message : String(error) })),
|
|
3589
|
+
resolveServerConnectionLabel2(projectRoot)
|
|
3590
|
+
]);
|
|
3591
|
+
const state = {
|
|
3592
|
+
runId: cleanRunId,
|
|
3593
|
+
status: "preparing",
|
|
3594
|
+
message: "ready to enter worker Pi",
|
|
3595
|
+
result: { run, piStatus, server },
|
|
3596
|
+
ready: true
|
|
3597
|
+
};
|
|
3598
|
+
patchData(ctx, { piAttach: state });
|
|
3599
|
+
emitCompleted(ctx, label, { runId: cleanRunId, server });
|
|
3600
|
+
return state;
|
|
3601
|
+
} catch (error) {
|
|
3602
|
+
const state = { runId: cleanRunId, status: "failed", message: error instanceof Error ? error.message : String(error) };
|
|
3603
|
+
patchData(ctx, { piAttach: state });
|
|
3604
|
+
emitFailed(ctx, label, error, { runId: cleanRunId });
|
|
3605
|
+
throw error;
|
|
3062
3606
|
}
|
|
3063
|
-
return fallback;
|
|
3064
3607
|
}
|
|
3065
|
-
function
|
|
3066
|
-
const
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3608
|
+
async function attachRunWithBundledPi(ctx, runId) {
|
|
3609
|
+
const cleanRunId = runId.trim();
|
|
3610
|
+
const label = `Entering Pi ${cleanRunId.slice(0, 8)}`;
|
|
3611
|
+
if (!cleanRunId) {
|
|
3612
|
+
const error = new Error("Attach requires a run id.");
|
|
3613
|
+
emitFailed(ctx, label, error);
|
|
3614
|
+
throw error;
|
|
3615
|
+
}
|
|
3616
|
+
emitStarted(ctx, label, { piAttach: { runId: cleanRunId, status: "entering-pi" } });
|
|
3617
|
+
patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "starting bundled Pi" } });
|
|
3618
|
+
try {
|
|
3619
|
+
await preparePiAttachHandoff(ctx, cleanRunId).catch(() => null);
|
|
3620
|
+
emitProgress(ctx, label, "starting Bun PTY host for bundled Pi");
|
|
3621
|
+
const projectRoot = projectRootOf(ctx);
|
|
3622
|
+
const cols = typeof process.stdout.columns === "number" ? process.stdout.columns : 100;
|
|
3623
|
+
const rows = Math.max(1, (typeof process.stdout.rows === "number" ? process.stdout.rows : 35) - 1);
|
|
3624
|
+
const patchTerminal = (snapshot) => {
|
|
3625
|
+
patchData(ctx, {
|
|
3626
|
+
piTerminal: snapshot,
|
|
3627
|
+
piAttach: {
|
|
3628
|
+
runId: cleanRunId,
|
|
3629
|
+
status: snapshot.status === "failed" ? "failed" : snapshot.status === "exited" ? "returned" : "entering-pi",
|
|
3630
|
+
message: snapshot.message ?? "bundled Pi running",
|
|
3631
|
+
result: { runId: cleanRunId, status: snapshot.status, exitCode: snapshot.exitCode }
|
|
3632
|
+
}
|
|
3633
|
+
});
|
|
3634
|
+
};
|
|
3635
|
+
await startPiPtyHost({
|
|
3636
|
+
runId: cleanRunId,
|
|
3637
|
+
projectRoot,
|
|
3638
|
+
cols,
|
|
3639
|
+
rows,
|
|
3640
|
+
onSnapshot: patchTerminal,
|
|
3641
|
+
onExit: (snapshot) => {
|
|
3642
|
+
patchTerminal(snapshot);
|
|
3643
|
+
emitCompleted(ctx, label, { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
|
|
3644
|
+
ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
|
|
3645
|
+
refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
|
|
3646
|
+
},
|
|
3647
|
+
onError: (error, snapshot) => {
|
|
3648
|
+
patchTerminal(snapshot);
|
|
3649
|
+
emitFailed(ctx, label, error, { runId: cleanRunId });
|
|
3650
|
+
}
|
|
3651
|
+
});
|
|
3652
|
+
const record = {
|
|
3653
|
+
runId: cleanRunId,
|
|
3654
|
+
status: "running",
|
|
3655
|
+
rendered: "bundled Pi embedded via Bun.Terminal + @xterm/headless"
|
|
3656
|
+
};
|
|
3657
|
+
emitProgress(ctx, label, "bundled Pi is running", { piAttach: { runId: cleanRunId, status: "entering-pi", message: "bundled Pi running", result: record } });
|
|
3658
|
+
return record;
|
|
3659
|
+
} catch (error) {
|
|
3660
|
+
patchData(ctx, { piAttach: { runId: cleanRunId, status: "failed", message: error instanceof Error ? error.message : String(error) } });
|
|
3661
|
+
emitFailed(ctx, label, error, { runId: cleanRunId });
|
|
3662
|
+
throw error;
|
|
3071
3663
|
}
|
|
3072
|
-
return out;
|
|
3073
|
-
}
|
|
3074
|
-
function payloadString(intent, key) {
|
|
3075
|
-
const value = intent.action.payload?.[key];
|
|
3076
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
3077
3664
|
}
|
|
3665
|
+
var init_pi_attach = __esm(() => {
|
|
3666
|
+
init_pi_pty_host();
|
|
3667
|
+
init_fleet();
|
|
3668
|
+
});
|
|
3078
3669
|
|
|
3079
3670
|
// packages/cli/src/app-opentui/adapters/tasks.ts
|
|
3671
|
+
import { randomUUID } from "crypto";
|
|
3080
3672
|
function createTasksAdapter() {
|
|
3081
3673
|
return {
|
|
3082
3674
|
id: "tasks",
|
|
@@ -3084,12 +3676,13 @@ function createTasksAdapter() {
|
|
|
3084
3676
|
if (intent.scene !== "tasks" && intent.action.kind !== "task-run-next" && intent.action.kind !== "task-run-id")
|
|
3085
3677
|
return false;
|
|
3086
3678
|
const ctx = adapterContextFromRuntime(runtime);
|
|
3679
|
+
const detach = intent.action.payload?.detach === true;
|
|
3087
3680
|
if (intent.action.kind === "task-run-next") {
|
|
3088
|
-
await submitNextTaskRun(ctx);
|
|
3681
|
+
await submitNextTaskRun(ctx, {}, { detach });
|
|
3089
3682
|
return true;
|
|
3090
3683
|
}
|
|
3091
3684
|
if (intent.action.kind === "task-run-id") {
|
|
3092
|
-
await submitTaskRun(ctx, { taskId: payloadString(intent, "task") });
|
|
3685
|
+
await submitTaskRun(ctx, { taskId: payloadString(intent, "task"), detach });
|
|
3093
3686
|
return true;
|
|
3094
3687
|
}
|
|
3095
3688
|
if (intent.action.kind === "refresh" || intent.action.kind === "navigate") {
|
|
@@ -3150,6 +3743,30 @@ function labelsFor(record) {
|
|
|
3150
3743
|
function assigneesFor(record) {
|
|
3151
3744
|
return stringList(rawRecord(record, "assignees"));
|
|
3152
3745
|
}
|
|
3746
|
+
function activeRunForRecord(record) {
|
|
3747
|
+
const value = rawRecord(record, "activeRun");
|
|
3748
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3749
|
+
return;
|
|
3750
|
+
const activeRun = value;
|
|
3751
|
+
const runId = optionalString(activeRun, ["runId", "id"]);
|
|
3752
|
+
if (!runId)
|
|
3753
|
+
return;
|
|
3754
|
+
const status = optionalString(activeRun, ["status", "state"]);
|
|
3755
|
+
const stage = optionalString(activeRun, ["stage", "phase"]);
|
|
3756
|
+
return {
|
|
3757
|
+
runId,
|
|
3758
|
+
...status ? { status } : {},
|
|
3759
|
+
...stage ? { stage } : {}
|
|
3760
|
+
};
|
|
3761
|
+
}
|
|
3762
|
+
function activeRunIdFromTaskLike(task) {
|
|
3763
|
+
return activeRunForRecord(task)?.runId;
|
|
3764
|
+
}
|
|
3765
|
+
function activeRunIdFromTaskRunError(error) {
|
|
3766
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
3767
|
+
const match = message2.match(/already has an active (?:Rig )?run:\s*([^\s"'}]+)/i);
|
|
3768
|
+
return match?.[1]?.replace(/[),.;]+$/g, "") ?? null;
|
|
3769
|
+
}
|
|
3153
3770
|
function normalizeTaskRecord(record) {
|
|
3154
3771
|
const id = stringField(record, ["id", "taskId", "number"]);
|
|
3155
3772
|
if (!id)
|
|
@@ -3157,6 +3774,7 @@ function normalizeTaskRecord(record) {
|
|
|
3157
3774
|
const priority = optionalNumber(record, ["priority", "rank", "score"]);
|
|
3158
3775
|
const updatedAt = optionalString(record, ["updatedAt", "updated_at", "modifiedAt"]);
|
|
3159
3776
|
const createdAt = optionalString(record, ["createdAt", "created_at"]);
|
|
3777
|
+
const activeRun = activeRunForRecord(record);
|
|
3160
3778
|
return {
|
|
3161
3779
|
id,
|
|
3162
3780
|
status: stringField(record, ["status", "state"], "unknown"),
|
|
@@ -3166,6 +3784,7 @@ function normalizeTaskRecord(record) {
|
|
|
3166
3784
|
...createdAt ? { createdAt } : {},
|
|
3167
3785
|
labels: labelsFor(record),
|
|
3168
3786
|
assignees: assigneesFor(record),
|
|
3787
|
+
...activeRun ? { activeRun } : {},
|
|
3169
3788
|
raw: record
|
|
3170
3789
|
};
|
|
3171
3790
|
}
|
|
@@ -3393,7 +4012,7 @@ async function submitTaskRun(ctx, input = {}) {
|
|
|
3393
4012
|
try {
|
|
3394
4013
|
const { submitTaskRunViaServer: submitTaskRunViaServer2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
|
|
3395
4014
|
const defaults = await loadRunDefaults(projectRoot);
|
|
3396
|
-
emitProgress(ctx, label, "submitting run to selected
|
|
4015
|
+
emitProgress(ctx, label, "submitting run to selected server", { runId, taskId: taskId3 ?? null });
|
|
3397
4016
|
const submitted = await submitTaskRunViaServer2({ projectRoot }, {
|
|
3398
4017
|
runId,
|
|
3399
4018
|
taskId: taskId3 || undefined,
|
|
@@ -3413,14 +4032,41 @@ async function submitTaskRun(ctx, input = {}) {
|
|
|
3413
4032
|
latestSubmittedRun: { runId: submitted.runId, taskId: taskId3 ?? null },
|
|
3414
4033
|
runDetail: { runId: submitted.runId, status: "submitted", title: optimisticTitle, run: submitted, logs: [], timeline: [], timelineCursor: null }
|
|
3415
4034
|
});
|
|
3416
|
-
patchFooter(ctx, { run: `${submitted.runId.slice(0, 8)} submitted` });
|
|
4035
|
+
patchFooter(ctx, { run: `${submitted.runId.slice(0, 8)} ${input.detach ? "submitted" : "attaching"}` });
|
|
4036
|
+
if (!input.detach) {
|
|
4037
|
+
ctx.emit({
|
|
4038
|
+
type: "scene.change",
|
|
4039
|
+
scene: "handoff",
|
|
4040
|
+
intent: { scene: "handoff", argv: ["attach", submitted.runId], action: { kind: "run-attach", payload: { runId: submitted.runId }, label: `Attach Pi ${submitted.runId.slice(0, 8)}` } }
|
|
4041
|
+
});
|
|
4042
|
+
const { attachRunWithBundledPi: attachRunWithBundledPi2 } = await Promise.resolve().then(() => (init_pi_attach(), exports_pi_attach));
|
|
4043
|
+
await attachRunWithBundledPi2(ctx, submitted.runId);
|
|
4044
|
+
}
|
|
3417
4045
|
return submitted;
|
|
3418
4046
|
} catch (error) {
|
|
4047
|
+
const activeRunId = activeRunIdFromTaskRunError(error);
|
|
4048
|
+
if (activeRunId) {
|
|
4049
|
+
emitProgress(ctx, label, `task already has active run ${activeRunId.slice(0, 8)}; attaching Pi`, {
|
|
4050
|
+
selectedRunId: activeRunId,
|
|
4051
|
+
dispatchingRun: null,
|
|
4052
|
+
latestSubmittedRun: { runId: activeRunId, taskId: taskId3 ?? null }
|
|
4053
|
+
});
|
|
4054
|
+
patchData(ctx, { selectedRunId: activeRunId, dispatchingRun: null });
|
|
4055
|
+
patchFooter(ctx, { run: `${activeRunId.slice(0, 8)} active` });
|
|
4056
|
+
ctx.emit({
|
|
4057
|
+
type: "scene.change",
|
|
4058
|
+
scene: "handoff",
|
|
4059
|
+
intent: { scene: "handoff", argv: ["attach", activeRunId], action: { kind: "run-attach", payload: { runId: activeRunId }, label: `Attach Pi ${activeRunId.slice(0, 8)}` } }
|
|
4060
|
+
});
|
|
4061
|
+
const { attachRunWithBundledPi: attachRunWithBundledPi2 } = await Promise.resolve().then(() => (init_pi_attach(), exports_pi_attach));
|
|
4062
|
+
await attachRunWithBundledPi2(ctx, activeRunId);
|
|
4063
|
+
return { runId: activeRunId };
|
|
4064
|
+
}
|
|
3419
4065
|
emitFailed(ctx, label, error, { runId, taskId: taskId3 ?? null, revert: { dispatchingRun: null } });
|
|
3420
4066
|
throw error;
|
|
3421
4067
|
}
|
|
3422
4068
|
}
|
|
3423
|
-
async function submitNextTaskRun(ctx, filters = {}) {
|
|
4069
|
+
async function submitNextTaskRun(ctx, filters = {}, options = {}) {
|
|
3424
4070
|
const label = "Selecting next task";
|
|
3425
4071
|
emitStarted(ctx, label, { dispatch: { status: "selecting-next" } });
|
|
3426
4072
|
try {
|
|
@@ -3436,7 +4082,7 @@ async function submitNextTaskRun(ctx, filters = {}) {
|
|
|
3436
4082
|
throw error;
|
|
3437
4083
|
}
|
|
3438
4084
|
emitCompleted(ctx, label, { taskId: task.id, count: selected.count });
|
|
3439
|
-
const submitted = await submitTaskRun(ctx, { taskId: task.id, title: task.title });
|
|
4085
|
+
const submitted = await submitTaskRun(ctx, { taskId: task.id, title: task.title, detach: options.detach });
|
|
3440
4086
|
return { ...submitted, task };
|
|
3441
4087
|
} catch (error) {
|
|
3442
4088
|
emitFailed(ctx, label, error);
|
|
@@ -3468,5 +4114,7 @@ export {
|
|
|
3468
4114
|
normalizeTaskRecord,
|
|
3469
4115
|
loadTaskPreview,
|
|
3470
4116
|
ensureTaskRuntime,
|
|
3471
|
-
createTasksAdapter
|
|
4117
|
+
createTasksAdapter,
|
|
4118
|
+
activeRunIdFromTaskRunError,
|
|
4119
|
+
activeRunIdFromTaskLike
|
|
3472
4120
|
};
|