@h-rig/server 0.0.6-alpha.22 → 0.0.6-alpha.23
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/src/index.js +313 -57
- package/dist/src/server-helpers/http-router.js +157 -6
- package/dist/src/server-helpers/pi-session-proxy.js +84 -0
- package/dist/src/server-helpers/run-io.js +17 -1
- package/dist/src/server-helpers/run-mutations.js +134 -59
- package/dist/src/server-helpers/run-writers.js +7 -0
- package/dist/src/server-helpers/ws-router.js +9 -5
- package/dist/src/server.js +313 -57
- package/package.json +4 -4
package/dist/src/index.js
CHANGED
|
@@ -1530,14 +1530,29 @@ function summarizeUsefulRunError(projectRoot, runId, fallback) {
|
|
|
1530
1530
|
const nonGeneric = errorLines.at(-1);
|
|
1531
1531
|
return nonGeneric ?? (typeof fallback === "string" ? fallback : null);
|
|
1532
1532
|
}
|
|
1533
|
+
function readRunPiSessionMetadata(projectRoot, runId) {
|
|
1534
|
+
const run = readAuthorityRun(projectRoot, runId);
|
|
1535
|
+
const metadata = run?.piSessionPrivate;
|
|
1536
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
|
|
1537
|
+
return null;
|
|
1538
|
+
const record = metadata;
|
|
1539
|
+
const publicMetadata = record.public;
|
|
1540
|
+
const daemonConnection = record.daemonConnection;
|
|
1541
|
+
if (!publicMetadata || typeof publicMetadata !== "object" || Array.isArray(publicMetadata))
|
|
1542
|
+
return null;
|
|
1543
|
+
if (!daemonConnection || typeof daemonConnection !== "object" || Array.isArray(daemonConnection))
|
|
1544
|
+
return null;
|
|
1545
|
+
return metadata;
|
|
1546
|
+
}
|
|
1533
1547
|
function readRunDetails(projectRoot, runId) {
|
|
1534
1548
|
const run = readAuthorityRun(projectRoot, runId);
|
|
1535
1549
|
if (!run) {
|
|
1536
1550
|
return null;
|
|
1537
1551
|
}
|
|
1538
1552
|
const usefulErrorText = isGenericRunFailure(run.errorText) ? summarizeUsefulRunError(projectRoot, runId, run.errorText) : null;
|
|
1553
|
+
const { piSessionPrivate: _piSessionPrivate, ...publicRun } = run;
|
|
1539
1554
|
return {
|
|
1540
|
-
run: usefulErrorText ? { ...
|
|
1555
|
+
run: usefulErrorText ? { ...publicRun, errorText: usefulErrorText } : publicRun,
|
|
1541
1556
|
timeline: readJsonlFile(resolve4(resolveAuthorityRunDir(projectRoot, runId), "timeline.jsonl")),
|
|
1542
1557
|
approvals: readApprovals(projectRoot, { runId }),
|
|
1543
1558
|
userInputs: readUserInputs(projectRoot, { runId })
|
|
@@ -3244,6 +3259,12 @@ function patchRunRecord(projectRoot, runId, patch) {
|
|
|
3244
3259
|
writeJsonFile2(resolve11(resolveAuthorityRunDir2(projectRoot, runId), "run.json"), next);
|
|
3245
3260
|
return next;
|
|
3246
3261
|
}
|
|
3262
|
+
function patchRunPiSessionMetadata(projectRoot, runId, metadata) {
|
|
3263
|
+
return patchRunRecord(projectRoot, runId, {
|
|
3264
|
+
piSession: metadata?.public ?? null,
|
|
3265
|
+
piSessionPrivate: metadata
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3247
3268
|
function buildRunStartPatch(startedAt) {
|
|
3248
3269
|
return {
|
|
3249
3270
|
status: "preparing",
|
|
@@ -4435,10 +4456,10 @@ function closeoutPhasePatch(phase, status, extra = {}) {
|
|
|
4435
4456
|
const updatedAt = new Date().toISOString();
|
|
4436
4457
|
return {
|
|
4437
4458
|
serverCloseout: {
|
|
4459
|
+
...extra,
|
|
4438
4460
|
phase,
|
|
4439
4461
|
status,
|
|
4440
|
-
updatedAt
|
|
4441
|
-
...extra
|
|
4462
|
+
updatedAt
|
|
4442
4463
|
}
|
|
4443
4464
|
};
|
|
4444
4465
|
}
|
|
@@ -4556,6 +4577,58 @@ async function updateRunTaskSourceLifecycle(projectRoot, run, status, summary, o
|
|
|
4556
4577
|
});
|
|
4557
4578
|
}
|
|
4558
4579
|
}
|
|
4580
|
+
async function markServerOwnedCloseoutFailed(state, runId, error) {
|
|
4581
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
4582
|
+
const current = readAuthorityRun4(state.projectRoot, runId);
|
|
4583
|
+
patchRunRecord(state.projectRoot, runId, {
|
|
4584
|
+
status: "failed",
|
|
4585
|
+
completedAt: new Date().toISOString(),
|
|
4586
|
+
errorText: detail,
|
|
4587
|
+
...closeoutPhasePatch("failed", "failed", { error: detail })
|
|
4588
|
+
});
|
|
4589
|
+
appendRunLogEntryAndBroadcast(state, runId, {
|
|
4590
|
+
id: `log:${runId}:server-closeout-failed`,
|
|
4591
|
+
title: "Server-owned closeout failed",
|
|
4592
|
+
detail,
|
|
4593
|
+
tone: "error",
|
|
4594
|
+
status: "failed",
|
|
4595
|
+
createdAt: new Date().toISOString()
|
|
4596
|
+
}, "server-closeout-failed");
|
|
4597
|
+
if (current?.taskId) {
|
|
4598
|
+
await updateRunTaskSourceLifecycle(state.projectRoot, { ...current, status: "failed", errorText: detail }, "failed", "Rig server-owned closeout failed.", { errorText: detail }).catch((sourceError) => {
|
|
4599
|
+
appendRunLogEntry(state.projectRoot, runId, {
|
|
4600
|
+
id: `log:${runId}:task-source-closeout-failed-update`,
|
|
4601
|
+
title: "Task source closeout failure update failed",
|
|
4602
|
+
detail: sourceError instanceof Error ? sourceError.message : String(sourceError),
|
|
4603
|
+
tone: "error",
|
|
4604
|
+
status: "failed",
|
|
4605
|
+
createdAt: new Date().toISOString()
|
|
4606
|
+
});
|
|
4607
|
+
});
|
|
4608
|
+
}
|
|
4609
|
+
}
|
|
4610
|
+
function scheduleServerOwnedPrCloseout(state, runId, reason) {
|
|
4611
|
+
const startedAt = new Date().toISOString();
|
|
4612
|
+
state.runProcesses.set(runId, {
|
|
4613
|
+
runId,
|
|
4614
|
+
child: null,
|
|
4615
|
+
startedAt,
|
|
4616
|
+
stopped: false
|
|
4617
|
+
});
|
|
4618
|
+
queueMicrotask(() => {
|
|
4619
|
+
withServerAuthorityEnvIfNeeded(state.projectRoot, async () => {
|
|
4620
|
+
try {
|
|
4621
|
+
await runServerOwnedPrCloseout(state, runId);
|
|
4622
|
+
} catch (error) {
|
|
4623
|
+
await markServerOwnedCloseoutFailed(state, runId, error);
|
|
4624
|
+
} finally {
|
|
4625
|
+
state.runProcesses.delete(runId);
|
|
4626
|
+
broadcastSnapshotInvalidation(state, `server-closeout-${reason}-terminal`);
|
|
4627
|
+
await reconcileScheduler(state, `server-closeout-${reason}-terminal`);
|
|
4628
|
+
}
|
|
4629
|
+
});
|
|
4630
|
+
});
|
|
4631
|
+
}
|
|
4559
4632
|
async function runServerOwnedPrCloseout(state, runId) {
|
|
4560
4633
|
const run = readAuthorityRun4(state.projectRoot, runId);
|
|
4561
4634
|
if (!run)
|
|
@@ -4567,7 +4640,7 @@ async function runServerOwnedPrCloseout(state, runId) {
|
|
|
4567
4640
|
if (!taskId)
|
|
4568
4641
|
throw new Error("Server-owned closeout requires a task id.");
|
|
4569
4642
|
const workspace = normalizeString(closeout.runtimeWorkspace) ?? normalizeString(run.worktreePath) ?? state.projectRoot;
|
|
4570
|
-
|
|
4643
|
+
let branch = normalizeString(closeout.branch) ?? `rig/${taskId}-${runId}`;
|
|
4571
4644
|
const config = await loadRigLifecycleConfig(state.projectRoot);
|
|
4572
4645
|
const runPrMode = normalizeString(run.prMode);
|
|
4573
4646
|
const prMode = runPrMode === "auto" || runPrMode === "ask" || runPrMode === "off" ? runPrMode : config?.pr?.mode ?? "off";
|
|
@@ -4641,6 +4714,12 @@ async function runServerOwnedPrCloseout(state, runId) {
|
|
|
4641
4714
|
const githubEnv = githubToken ? { RIG_GITHUB_TOKEN: githubToken, GITHUB_TOKEN: githubToken, GH_TOKEN: githubToken } : {};
|
|
4642
4715
|
const gitCommand = createCommandRunner("git", githubEnv);
|
|
4643
4716
|
const ghCommand = createCommandRunner("gh", githubEnv);
|
|
4717
|
+
const workspaceBranch = await gitCommand(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: workspace });
|
|
4718
|
+
const currentWorkspaceBranch = workspaceBranch.exitCode === 0 ? normalizeString(workspaceBranch.stdout) : null;
|
|
4719
|
+
if (currentWorkspaceBranch && currentWorkspaceBranch !== "HEAD" && currentWorkspaceBranch !== branch) {
|
|
4720
|
+
appendCloseoutStage(state, runId, "branch", `Using runtime workspace branch ${currentWorkspaceBranch} instead of recorded branch ${branch}.`, "reviewing", "info");
|
|
4721
|
+
branch = currentWorkspaceBranch;
|
|
4722
|
+
}
|
|
4644
4723
|
const setCloseout = (phase, status, extra = {}) => {
|
|
4645
4724
|
const previous = closeoutRecord(readCurrentRun()) ?? closeout;
|
|
4646
4725
|
patchRunRecord(state.projectRoot, runId, {
|
|
@@ -5011,6 +5090,25 @@ async function startLocalRun(state, runId, options) {
|
|
|
5011
5090
|
broadcastSnapshotInvalidation(state);
|
|
5012
5091
|
continue;
|
|
5013
5092
|
}
|
|
5093
|
+
if (line.startsWith("__RIG_WRAPPER_EVENT__")) {
|
|
5094
|
+
try {
|
|
5095
|
+
const wrapperEvent = JSON.parse(line.slice("__RIG_WRAPPER_EVENT__".length));
|
|
5096
|
+
const eventType = normalizeString(wrapperEvent.type);
|
|
5097
|
+
const payload = wrapperEvent.payload && typeof wrapperEvent.payload === "object" && !Array.isArray(wrapperEvent.payload) ? wrapperEvent.payload : {};
|
|
5098
|
+
if (eventType === "pi.session.ready" && payload.privateMetadata && typeof payload.privateMetadata === "object" && !Array.isArray(payload.privateMetadata)) {
|
|
5099
|
+
patchRunPiSessionMetadata(state.projectRoot, runId, payload.privateMetadata);
|
|
5100
|
+
}
|
|
5101
|
+
appendRunTimelineEntry(state.projectRoot, runId, {
|
|
5102
|
+
id: `timeline:${runId}:${Date.now()}:wrapper:${eventType ?? "event"}`,
|
|
5103
|
+
type: "wrapper-event",
|
|
5104
|
+
eventType,
|
|
5105
|
+
payload,
|
|
5106
|
+
createdAt: normalizeString(wrapperEvent.at) ?? new Date().toISOString()
|
|
5107
|
+
});
|
|
5108
|
+
} catch {}
|
|
5109
|
+
broadcastSnapshotInvalidation(state, "wrapper-event");
|
|
5110
|
+
continue;
|
|
5111
|
+
}
|
|
5014
5112
|
appendRunLogEntryAndBroadcast(state, runId, {
|
|
5015
5113
|
id: `log:${runId}:${Date.now()}`,
|
|
5016
5114
|
title,
|
|
@@ -5039,7 +5137,13 @@ async function startLocalRun(state, runId, options) {
|
|
|
5039
5137
|
if (!current) {
|
|
5040
5138
|
return;
|
|
5041
5139
|
}
|
|
5042
|
-
if (
|
|
5140
|
+
if (closeoutRecord(current)?.status === "pending") {
|
|
5141
|
+
try {
|
|
5142
|
+
await runServerOwnedPrCloseout(state, runId);
|
|
5143
|
+
} catch (closeoutError) {
|
|
5144
|
+
await markServerOwnedCloseoutFailed(state, runId, closeoutError);
|
|
5145
|
+
}
|
|
5146
|
+
} else if (exit.code !== 0 && current.status !== "completed" && current.status !== "stopped") {
|
|
5043
5147
|
const completedAt = current.completedAt ?? new Date().toISOString();
|
|
5044
5148
|
const failureSummary = normalizeString(current.errorText) ?? summarizeRunValidationFailure(state.projectRoot, current) ?? `Rig task-run exited with code ${String(exit.code ?? "unknown")}`;
|
|
5045
5149
|
if (current.status !== "failed") {
|
|
@@ -5074,38 +5178,6 @@ ${sourceFailure}` });
|
|
|
5074
5178
|
agent: current.runtimeAdapter,
|
|
5075
5179
|
summary: failureSummary
|
|
5076
5180
|
});
|
|
5077
|
-
} else if (closeoutRecord(current)?.status === "pending") {
|
|
5078
|
-
try {
|
|
5079
|
-
await runServerOwnedPrCloseout(state, runId);
|
|
5080
|
-
} catch (closeoutError) {
|
|
5081
|
-
const closeoutFailure = closeoutError instanceof Error ? closeoutError.message : String(closeoutError);
|
|
5082
|
-
patchRunRecord(state.projectRoot, runId, {
|
|
5083
|
-
status: "failed",
|
|
5084
|
-
completedAt: new Date().toISOString(),
|
|
5085
|
-
errorText: closeoutFailure,
|
|
5086
|
-
...closeoutPhasePatch("failed", "failed", { error: closeoutFailure })
|
|
5087
|
-
});
|
|
5088
|
-
appendRunLogEntryAndBroadcast(state, runId, {
|
|
5089
|
-
id: `log:${runId}:server-closeout-failed`,
|
|
5090
|
-
title: "Server-owned closeout failed",
|
|
5091
|
-
detail: closeoutFailure,
|
|
5092
|
-
tone: "error",
|
|
5093
|
-
status: "failed",
|
|
5094
|
-
createdAt: new Date().toISOString()
|
|
5095
|
-
}, "server-closeout-failed");
|
|
5096
|
-
if (current.taskId) {
|
|
5097
|
-
await updateRunTaskSourceLifecycle(state.projectRoot, { ...current, status: "failed", errorText: closeoutFailure }, "failed", "Rig server-owned closeout failed.", { errorText: closeoutFailure }).catch((error) => {
|
|
5098
|
-
appendRunLogEntry(state.projectRoot, runId, {
|
|
5099
|
-
id: `log:${runId}:task-source-closeout-failed-update`,
|
|
5100
|
-
title: "Task source closeout failure update failed",
|
|
5101
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
5102
|
-
tone: "error",
|
|
5103
|
-
status: "failed",
|
|
5104
|
-
createdAt: new Date().toISOString()
|
|
5105
|
-
});
|
|
5106
|
-
});
|
|
5107
|
-
}
|
|
5108
|
-
}
|
|
5109
5181
|
}
|
|
5110
5182
|
broadcastSnapshotInvalidation(state);
|
|
5111
5183
|
} catch (error) {
|
|
@@ -5194,8 +5266,14 @@ async function resumeRunRecord(state, input) {
|
|
|
5194
5266
|
}
|
|
5195
5267
|
const closeout = closeoutRecord(run);
|
|
5196
5268
|
const closeoutStatus = normalizeString(closeout?.status)?.toLowerCase() ?? "";
|
|
5197
|
-
if (
|
|
5198
|
-
|
|
5269
|
+
if (EXPLICIT_RESUMABLE_SERVER_CLOSEOUT_STATUSES.has(closeoutStatus)) {
|
|
5270
|
+
patchRunRecord(state.projectRoot, input.runId, {
|
|
5271
|
+
status: "reviewing",
|
|
5272
|
+
completedAt: null,
|
|
5273
|
+
errorText: null,
|
|
5274
|
+
...closeoutPhasePatch("queued", "pending", { ...closeout, resumedAt: input.createdAt })
|
|
5275
|
+
});
|
|
5276
|
+
scheduleServerOwnedPrCloseout(state, input.runId, "explicit-resume");
|
|
5199
5277
|
return;
|
|
5200
5278
|
}
|
|
5201
5279
|
await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null, resume: input.restart !== true });
|
|
@@ -5283,6 +5361,7 @@ function removeTaskIdsFromQueueState(projectRoot, taskIds) {
|
|
|
5283
5361
|
return next;
|
|
5284
5362
|
}
|
|
5285
5363
|
var RESUMABLE_SERVER_CLOSEOUT_STATUSES = new Set(["pending", "running"]);
|
|
5364
|
+
var EXPLICIT_RESUMABLE_SERVER_CLOSEOUT_STATUSES = new Set(["pending", "running", "needs_attention"]);
|
|
5286
5365
|
var ACTIVE_LOCAL_RUN_STATUSES = new Set(["created", "preparing", "running", "validating", "reviewing"]);
|
|
5287
5366
|
function processExists(pid) {
|
|
5288
5367
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
@@ -5298,7 +5377,23 @@ function recoverStaleLocalRun(projectRoot, run) {
|
|
|
5298
5377
|
const record = run;
|
|
5299
5378
|
if (run.mode !== "local")
|
|
5300
5379
|
return false;
|
|
5380
|
+
const closeout = closeoutRecord(record);
|
|
5381
|
+
const closeoutStatus = normalizeString(closeout?.status)?.toLowerCase() ?? "";
|
|
5301
5382
|
const status = normalizeString(record.status)?.toLowerCase() ?? "";
|
|
5383
|
+
if (RESUMABLE_SERVER_CLOSEOUT_STATUSES.has(closeoutStatus))
|
|
5384
|
+
return false;
|
|
5385
|
+
if (closeoutStatus === "needs_attention") {
|
|
5386
|
+
if (!ACTIVE_LOCAL_RUN_STATUSES.has(status))
|
|
5387
|
+
return false;
|
|
5388
|
+
const completedAt2 = record.completedAt ?? new Date().toISOString();
|
|
5389
|
+
patchRunRecord(projectRoot, run.runId, {
|
|
5390
|
+
status: "needs_attention",
|
|
5391
|
+
completedAt: completedAt2,
|
|
5392
|
+
errorText: normalizeString(record.errorText) ?? (Array.isArray(closeout?.feedback) ? closeout.feedback.map(String).join(`
|
|
5393
|
+
`) : null)
|
|
5394
|
+
});
|
|
5395
|
+
return true;
|
|
5396
|
+
}
|
|
5302
5397
|
if (!ACTIVE_LOCAL_RUN_STATUSES.has(status))
|
|
5303
5398
|
return false;
|
|
5304
5399
|
const serverPid = typeof record.serverPid === "number" ? record.serverPid : null;
|
|
@@ -5355,15 +5450,7 @@ async function reconcileScheduler(state, reason) {
|
|
|
5355
5450
|
status: "reviewing",
|
|
5356
5451
|
createdAt: new Date().toISOString()
|
|
5357
5452
|
});
|
|
5358
|
-
|
|
5359
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
5360
|
-
patchRunRecord(state.projectRoot, run.runId, {
|
|
5361
|
-
status: "failed",
|
|
5362
|
-
completedAt: new Date().toISOString(),
|
|
5363
|
-
errorText: detail,
|
|
5364
|
-
...closeoutPhasePatch("failed", "failed", { error: detail })
|
|
5365
|
-
});
|
|
5366
|
-
});
|
|
5453
|
+
scheduleServerOwnedPrCloseout(state, run.runId, "auto-resume");
|
|
5367
5454
|
changed = true;
|
|
5368
5455
|
}
|
|
5369
5456
|
if (changed) {
|
|
@@ -5445,7 +5532,7 @@ import { basename, dirname as dirname15, isAbsolute as isAbsolute4, resolve as r
|
|
|
5445
5532
|
import { copyFileSync as copyFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync12 } from "fs";
|
|
5446
5533
|
import {
|
|
5447
5534
|
listAuthorityRuns as listAuthorityRuns5,
|
|
5448
|
-
readAuthorityRun as
|
|
5535
|
+
readAuthorityRun as readAuthorityRun7,
|
|
5449
5536
|
resolveAuthorityPaths,
|
|
5450
5537
|
writeJsonFile as writeJsonFile4
|
|
5451
5538
|
} from "@rig/runtime/control-plane/authority-files";
|
|
@@ -5465,10 +5552,64 @@ import {
|
|
|
5465
5552
|
RemoteWsClient
|
|
5466
5553
|
} from "@rig/runtime/control-plane/remote";
|
|
5467
5554
|
|
|
5555
|
+
// packages/server/src/server-helpers/pi-session-proxy.ts
|
|
5556
|
+
import { readAuthorityRun as readAuthorityRun5 } from "@rig/runtime/control-plane/authority-files";
|
|
5557
|
+
function resolveRunPiSessionProxy(projectRoot, runId) {
|
|
5558
|
+
const run = readAuthorityRun5(projectRoot, runId);
|
|
5559
|
+
if (!run)
|
|
5560
|
+
return null;
|
|
5561
|
+
const privateMetadata = readRunPiSessionMetadata(projectRoot, runId);
|
|
5562
|
+
if (!privateMetadata)
|
|
5563
|
+
return { pending: true, runId, status: typeof run.status === "string" ? run.status : undefined };
|
|
5564
|
+
const connection = privateMetadata.daemonConnection;
|
|
5565
|
+
if (connection.mode !== "http")
|
|
5566
|
+
throw new Error("Only loopback HTTP Rig Pi session daemon connections are supported");
|
|
5567
|
+
const token = tokenFromRef(connection.tokenRef);
|
|
5568
|
+
if (!token)
|
|
5569
|
+
throw new Error("Rig Pi session daemon token is unavailable");
|
|
5570
|
+
return {
|
|
5571
|
+
runId,
|
|
5572
|
+
sessionId: privateMetadata.public.sessionId,
|
|
5573
|
+
metadata: privateMetadata.public,
|
|
5574
|
+
privateMetadata,
|
|
5575
|
+
baseUrl: connection.baseUrl.replace(/\/+$/, ""),
|
|
5576
|
+
token
|
|
5577
|
+
};
|
|
5578
|
+
}
|
|
5579
|
+
async function proxyRunPiHttp(projectRoot, runId, input) {
|
|
5580
|
+
const resolved = resolveRunPiSessionProxy(projectRoot, runId);
|
|
5581
|
+
if (resolved === null)
|
|
5582
|
+
return { status: 404, payload: { ok: false, error: "Run not found" } };
|
|
5583
|
+
if ("pending" in resolved)
|
|
5584
|
+
return { status: 409, payload: { ready: false, runId, status: resolved.status, retryAfterMs: 500 } };
|
|
5585
|
+
const response = await fetch(`${resolved.baseUrl}${input.daemonPath}`, {
|
|
5586
|
+
method: input.method,
|
|
5587
|
+
headers: {
|
|
5588
|
+
authorization: `Bearer ${resolved.token}`,
|
|
5589
|
+
...input.body === undefined ? {} : { "content-type": "application/json" }
|
|
5590
|
+
},
|
|
5591
|
+
body: input.body === undefined ? undefined : JSON.stringify(input.body)
|
|
5592
|
+
});
|
|
5593
|
+
const text = await response.text();
|
|
5594
|
+
const payload = text.trim() ? JSON.parse(text) : null;
|
|
5595
|
+
return { status: response.status, payload };
|
|
5596
|
+
}
|
|
5597
|
+
function buildRunPiDaemonWebSocketUrl(resolved) {
|
|
5598
|
+
const url = new URL(`${resolved.baseUrl}/sessions/${encodeURIComponent(resolved.sessionId)}/events`);
|
|
5599
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
5600
|
+
url.searchParams.set("token", resolved.token);
|
|
5601
|
+
return url.toString();
|
|
5602
|
+
}
|
|
5603
|
+
function tokenFromRef(ref) {
|
|
5604
|
+
if (ref.startsWith("inline:"))
|
|
5605
|
+
return ref.slice("inline:".length) || null;
|
|
5606
|
+
return null;
|
|
5607
|
+
}
|
|
5608
|
+
|
|
5468
5609
|
// packages/server/src/server-helpers/run-steering.ts
|
|
5469
5610
|
import { dirname as dirname10, resolve as resolve15 } from "path";
|
|
5470
5611
|
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync5 } from "fs";
|
|
5471
|
-
import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as
|
|
5612
|
+
import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as readAuthorityRun6, resolveAuthorityRunDir as resolveAuthorityRunDir4 } from "@rig/runtime/control-plane/authority-files";
|
|
5472
5613
|
var steeringSequence = 0;
|
|
5473
5614
|
function runSteeringPath(projectRoot, runId) {
|
|
5474
5615
|
return resolve15(resolveAuthorityRunDir4(projectRoot, runId), "steering.jsonl");
|
|
@@ -5537,7 +5678,7 @@ function markQueuedRunSteeringMessagesDelivered(projectRoot, runId, ids) {
|
|
|
5537
5678
|
return delivered;
|
|
5538
5679
|
}
|
|
5539
5680
|
function queueRunSteeringMessage(projectRoot, runId, input) {
|
|
5540
|
-
const run =
|
|
5681
|
+
const run = readAuthorityRun6(projectRoot, runId);
|
|
5541
5682
|
if (!run)
|
|
5542
5683
|
throw new Error(`Run not found: ${runId}`);
|
|
5543
5684
|
const text = input.message.trim();
|
|
@@ -6525,7 +6666,8 @@ function isAuthorizedInspectorStreamRequest(req, authToken) {
|
|
|
6525
6666
|
}
|
|
6526
6667
|
function buildDeploymentStatus(projectRoot) {
|
|
6527
6668
|
const envCommit = normalizeCommit(process.env.RIG_COMMIT_SHA ?? process.env.GITHUB_SHA ?? process.env.VERCEL_GIT_COMMIT_SHA ?? process.env.RAILWAY_GIT_COMMIT_SHA ?? process.env.COMMIT_SHA);
|
|
6528
|
-
const
|
|
6669
|
+
const deploymentRoot = normalizeString(process.env.RIG_HOST_PROJECT_ROOT) ?? projectRoot;
|
|
6670
|
+
const gitCommit = envCommit ?? readGitHeadCommit(deploymentRoot) ?? readGitHeadCommit(projectRoot);
|
|
6529
6671
|
return {
|
|
6530
6672
|
currentCommit: gitCommit,
|
|
6531
6673
|
commitSource: envCommit ? "env" : gitCommit ? "git" : null,
|
|
@@ -7115,7 +7257,7 @@ function redactSecretFields(value) {
|
|
|
7115
7257
|
return redacted;
|
|
7116
7258
|
}
|
|
7117
7259
|
function validateRemoteLease(deps, state, input) {
|
|
7118
|
-
const run =
|
|
7260
|
+
const run = readAuthorityRun7(state.projectRoot, input.runId);
|
|
7119
7261
|
if (!run) {
|
|
7120
7262
|
return { ok: false, response: deps.jsonResponse({ ok: false, error: "Remote run not found" }, 404) };
|
|
7121
7263
|
}
|
|
@@ -7135,6 +7277,43 @@ function createRigServerFetch(state, deps) {
|
|
|
7135
7277
|
return deps.withServerPathEnv(state.projectRoot, async () => {
|
|
7136
7278
|
const browserOrigin = deps.resolveAllowedBrowserOrigin(req);
|
|
7137
7279
|
const finalizeResponse = (response) => deps.withCorsHeaders(response, req, browserOrigin);
|
|
7280
|
+
const earlyUrl = new URL(req.url);
|
|
7281
|
+
const piEventsWsMatch = earlyUrl.pathname.match(/^\/api\/runs\/([^/]+)\/pi\/events$/);
|
|
7282
|
+
if (piEventsWsMatch && req.headers.get("upgrade")?.toLowerCase() === "websocket") {
|
|
7283
|
+
const queryToken = earlyUrl.searchParams.get("token");
|
|
7284
|
+
const authHeaders = new Headers(req.headers);
|
|
7285
|
+
if (queryToken && !authHeaders.has("authorization")) {
|
|
7286
|
+
authHeaders.set("authorization", `Bearer ${queryToken}`);
|
|
7287
|
+
}
|
|
7288
|
+
const legacyAuthorized = Boolean(state.authToken && queryToken === state.authToken);
|
|
7289
|
+
const requestAuth = authorizeRigHttpRequest({
|
|
7290
|
+
req: new Request(req.url, { method: req.method, headers: authHeaders }),
|
|
7291
|
+
pathname: earlyUrl.pathname,
|
|
7292
|
+
projectRoot: state.projectRoot,
|
|
7293
|
+
serverAuthToken: state.authToken,
|
|
7294
|
+
legacyAuthorized
|
|
7295
|
+
});
|
|
7296
|
+
if (!requestAuth.authorized) {
|
|
7297
|
+
return deps.jsonResponse({ ok: false, error: "Unauthorized WebSocket connection", reason: requestAuth.reason }, 401);
|
|
7298
|
+
}
|
|
7299
|
+
const runId = decodeURIComponent(piEventsWsMatch[1]);
|
|
7300
|
+
const resolved = resolveRunPiSessionProxy(state.projectRoot, runId);
|
|
7301
|
+
if (resolved === null)
|
|
7302
|
+
return deps.jsonResponse({ ok: false, error: "Run not found" }, 404);
|
|
7303
|
+
if ("pending" in resolved)
|
|
7304
|
+
return deps.jsonResponse({ ready: false, runId, status: resolved.status, retryAfterMs: 500 }, 202);
|
|
7305
|
+
if (!server)
|
|
7306
|
+
return deps.jsonResponse({ ok: false, error: "WebSocket upgrade unavailable" }, 400);
|
|
7307
|
+
const upgraded = server.upgrade(req, {
|
|
7308
|
+
data: {
|
|
7309
|
+
kind: "pi-session-proxy",
|
|
7310
|
+
connectedAt: new Date().toISOString(),
|
|
7311
|
+
upstreamUrl: buildRunPiDaemonWebSocketUrl(resolved),
|
|
7312
|
+
runId
|
|
7313
|
+
}
|
|
7314
|
+
});
|
|
7315
|
+
return upgraded ? new Response(null) : deps.jsonResponse({ ok: false, error: "WebSocket upgrade failed" }, 400);
|
|
7316
|
+
}
|
|
7138
7317
|
const upgradeResponse = handleWebSocketUpgrade({
|
|
7139
7318
|
req,
|
|
7140
7319
|
server,
|
|
@@ -8873,6 +9052,49 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
8873
9052
|
return deps.jsonResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);
|
|
8874
9053
|
}
|
|
8875
9054
|
}
|
|
9055
|
+
const runPiMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/pi(?:\/(.*))?$/);
|
|
9056
|
+
if (runPiMatch) {
|
|
9057
|
+
const runId = decodeURIComponent(runPiMatch[1]);
|
|
9058
|
+
const action = runPiMatch[2] || "";
|
|
9059
|
+
const resolved = resolveRunPiSessionProxy(state.projectRoot, runId);
|
|
9060
|
+
if (resolved === null)
|
|
9061
|
+
return deps.notFound();
|
|
9062
|
+
if ("pending" in resolved) {
|
|
9063
|
+
if (action === "" && req.method === "GET") {
|
|
9064
|
+
return deps.jsonResponse({ ready: false, runId, status: resolved.status, retryAfterMs: 500 }, 202);
|
|
9065
|
+
}
|
|
9066
|
+
return deps.jsonResponse({ ready: false, runId, status: resolved.status, retryAfterMs: 500, error: "Pi session is not ready" }, 409);
|
|
9067
|
+
}
|
|
9068
|
+
if (action === "" && req.method === "GET")
|
|
9069
|
+
return deps.jsonResponse({ ready: true, metadata: resolved.metadata });
|
|
9070
|
+
const body = req.method === "GET" ? undefined : await deps.readJsonBody(req);
|
|
9071
|
+
const sessionPath = `/sessions/${encodeURIComponent(resolved.sessionId)}`;
|
|
9072
|
+
const daemonPath = (() => {
|
|
9073
|
+
if (action === "messages" && req.method === "GET")
|
|
9074
|
+
return `${sessionPath}/messages`;
|
|
9075
|
+
if (action === "status" && req.method === "GET")
|
|
9076
|
+
return `${sessionPath}/status`;
|
|
9077
|
+
if (action === "commands" && req.method === "GET")
|
|
9078
|
+
return `${sessionPath}/commands`;
|
|
9079
|
+
if (action === "prompt" && req.method === "POST")
|
|
9080
|
+
return `${sessionPath}/prompt`;
|
|
9081
|
+
if (action === "shell" && req.method === "POST")
|
|
9082
|
+
return `${sessionPath}/shell`;
|
|
9083
|
+
if (action === "commands/run" && req.method === "POST")
|
|
9084
|
+
return `${sessionPath}/commands/run`;
|
|
9085
|
+
if (action === "commands/respond" && req.method === "POST")
|
|
9086
|
+
return `${sessionPath}/commands/respond`;
|
|
9087
|
+
if (action === "extension-ui/respond" && req.method === "POST")
|
|
9088
|
+
return `${sessionPath}/extension-ui/respond`;
|
|
9089
|
+
if (action === "abort" && req.method === "POST")
|
|
9090
|
+
return `${sessionPath}/abort`;
|
|
9091
|
+
return null;
|
|
9092
|
+
})();
|
|
9093
|
+
if (!daemonPath)
|
|
9094
|
+
return deps.notFound();
|
|
9095
|
+
const proxied = await proxyRunPiHttp(state.projectRoot, runId, { method: req.method, daemonPath, body });
|
|
9096
|
+
return deps.jsonResponse(proxied.payload, proxied.status);
|
|
9097
|
+
}
|
|
8876
9098
|
const runSteeringAckMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/steering\/ack$/);
|
|
8877
9099
|
if (runSteeringAckMatch && req.method === "POST") {
|
|
8878
9100
|
const runId = decodeURIComponent(runSteeringAckMatch[1]);
|
|
@@ -8995,7 +9217,7 @@ import {
|
|
|
8995
9217
|
RemoteWsClient as RemoteWsClient2
|
|
8996
9218
|
} from "@rig/runtime/control-plane/remote";
|
|
8997
9219
|
import { deleteRunState } from "@rig/runtime/control-plane/native/run-ops";
|
|
8998
|
-
import { readAuthorityRun as
|
|
9220
|
+
import { readAuthorityRun as readAuthorityRun8 } from "@rig/runtime/control-plane/authority-files";
|
|
8999
9221
|
function redactRemoteEndpoint2(endpoint) {
|
|
9000
9222
|
const { token, ...rest } = endpoint;
|
|
9001
9223
|
return {
|
|
@@ -9209,7 +9431,7 @@ async function routeWebSocketRequest(state, deps, request) {
|
|
|
9209
9431
|
if (!runId || !messageId || text === null) {
|
|
9210
9432
|
throw new Error("runId, messageId, and text are required");
|
|
9211
9433
|
}
|
|
9212
|
-
const run =
|
|
9434
|
+
const run = readAuthorityRun8(state.projectRoot, runId);
|
|
9213
9435
|
if (!run) {
|
|
9214
9436
|
throw new Error(`Run not found: ${runId}`);
|
|
9215
9437
|
}
|
|
@@ -12170,7 +12392,7 @@ import {
|
|
|
12170
12392
|
} from "@rig/runtime/control-plane/native/run-ops";
|
|
12171
12393
|
import {
|
|
12172
12394
|
listAuthorityRuns as listAuthorityRuns6,
|
|
12173
|
-
readAuthorityRun as
|
|
12395
|
+
readAuthorityRun as readAuthorityRun9
|
|
12174
12396
|
} from "@rig/runtime/control-plane/authority-files";
|
|
12175
12397
|
function providerFromRuntimeAdapter(runtimeAdapter) {
|
|
12176
12398
|
if (!runtimeAdapter) {
|
|
@@ -12250,7 +12472,7 @@ function discoverInspectorRuns(options) {
|
|
|
12250
12472
|
discovered.set(surface.runId, existing);
|
|
12251
12473
|
};
|
|
12252
12474
|
for (const authorityEntry of listAuthorityRuns6(options.projectRoot)) {
|
|
12253
|
-
const run =
|
|
12475
|
+
const run = readAuthorityRun9(options.projectRoot, authorityEntry.runId);
|
|
12254
12476
|
if (!run) {
|
|
12255
12477
|
continue;
|
|
12256
12478
|
}
|
|
@@ -14250,6 +14472,26 @@ function startPoller(state, pollMs) {
|
|
|
14250
14472
|
}
|
|
14251
14473
|
}, pollMs);
|
|
14252
14474
|
}
|
|
14475
|
+
function attachPiSessionProxySocket(ws) {
|
|
14476
|
+
if (ws.data.kind !== "pi-session-proxy")
|
|
14477
|
+
return;
|
|
14478
|
+
const upstream = new WebSocket(ws.data.upstreamUrl);
|
|
14479
|
+
ws.__rigPiUpstream = upstream;
|
|
14480
|
+
upstream.addEventListener("message", (event) => {
|
|
14481
|
+
if (ws.readyState === 1)
|
|
14482
|
+
ws.send(typeof event.data === "string" ? event.data : event.data);
|
|
14483
|
+
});
|
|
14484
|
+
upstream.addEventListener("close", () => {
|
|
14485
|
+
try {
|
|
14486
|
+
ws.close();
|
|
14487
|
+
} catch {}
|
|
14488
|
+
});
|
|
14489
|
+
upstream.addEventListener("error", () => {
|
|
14490
|
+
try {
|
|
14491
|
+
ws.close(1011, "Upstream Pi session stream failed");
|
|
14492
|
+
} catch {}
|
|
14493
|
+
});
|
|
14494
|
+
}
|
|
14253
14495
|
async function createRigServer(options, projectRoot = resolveProjectRoot()) {
|
|
14254
14496
|
const state = await createRigServerState(projectRoot, options.eventType, options.authToken, {
|
|
14255
14497
|
upstreamSyncMs: options.upstreamSyncMs,
|
|
@@ -14262,10 +14504,20 @@ async function createRigServer(options, projectRoot = resolveProjectRoot()) {
|
|
|
14262
14504
|
fetch: (req, server2) => createRigServerFetch2(state)(req, server2),
|
|
14263
14505
|
websocket: {
|
|
14264
14506
|
open(ws) {
|
|
14507
|
+
if (ws.data.kind === "pi-session-proxy") {
|
|
14508
|
+
attachPiSessionProxySocket(ws);
|
|
14509
|
+
return;
|
|
14510
|
+
}
|
|
14265
14511
|
state.sockets.add(ws);
|
|
14266
14512
|
sendWebSocketResponse(ws, buildServerWelcomePush(state.projectRoot));
|
|
14267
14513
|
},
|
|
14268
14514
|
async message(ws, raw) {
|
|
14515
|
+
if (ws.data.kind === "pi-session-proxy") {
|
|
14516
|
+
const upstream = ws.__rigPiUpstream;
|
|
14517
|
+
if (upstream?.readyState === WebSocket.OPEN)
|
|
14518
|
+
upstream.send(raw);
|
|
14519
|
+
return;
|
|
14520
|
+
}
|
|
14269
14521
|
const request = parseWebSocketRequestEnvelope(typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8"));
|
|
14270
14522
|
if (!request) {
|
|
14271
14523
|
sendWebSocketResponse(ws, {
|
|
@@ -14288,6 +14540,10 @@ async function createRigServer(options, projectRoot = resolveProjectRoot()) {
|
|
|
14288
14540
|
}
|
|
14289
14541
|
},
|
|
14290
14542
|
close(ws) {
|
|
14543
|
+
if (ws.data.kind === "pi-session-proxy") {
|
|
14544
|
+
ws.__rigPiUpstream?.close();
|
|
14545
|
+
return;
|
|
14546
|
+
}
|
|
14291
14547
|
state.sockets.delete(ws);
|
|
14292
14548
|
}
|
|
14293
14549
|
}
|