@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/server.js
CHANGED
|
@@ -1024,14 +1024,29 @@ function summarizeUsefulRunError(projectRoot, runId, fallback) {
|
|
|
1024
1024
|
const nonGeneric = errorLines.at(-1);
|
|
1025
1025
|
return nonGeneric ?? (typeof fallback === "string" ? fallback : null);
|
|
1026
1026
|
}
|
|
1027
|
+
function readRunPiSessionMetadata(projectRoot, runId) {
|
|
1028
|
+
const run = readAuthorityRun(projectRoot, runId);
|
|
1029
|
+
const metadata = run?.piSessionPrivate;
|
|
1030
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
|
|
1031
|
+
return null;
|
|
1032
|
+
const record = metadata;
|
|
1033
|
+
const publicMetadata = record.public;
|
|
1034
|
+
const daemonConnection = record.daemonConnection;
|
|
1035
|
+
if (!publicMetadata || typeof publicMetadata !== "object" || Array.isArray(publicMetadata))
|
|
1036
|
+
return null;
|
|
1037
|
+
if (!daemonConnection || typeof daemonConnection !== "object" || Array.isArray(daemonConnection))
|
|
1038
|
+
return null;
|
|
1039
|
+
return metadata;
|
|
1040
|
+
}
|
|
1027
1041
|
function readRunDetails(projectRoot, runId) {
|
|
1028
1042
|
const run = readAuthorityRun(projectRoot, runId);
|
|
1029
1043
|
if (!run) {
|
|
1030
1044
|
return null;
|
|
1031
1045
|
}
|
|
1032
1046
|
const usefulErrorText = isGenericRunFailure(run.errorText) ? summarizeUsefulRunError(projectRoot, runId, run.errorText) : null;
|
|
1047
|
+
const { piSessionPrivate: _piSessionPrivate, ...publicRun } = run;
|
|
1033
1048
|
return {
|
|
1034
|
-
run: usefulErrorText ? { ...
|
|
1049
|
+
run: usefulErrorText ? { ...publicRun, errorText: usefulErrorText } : publicRun,
|
|
1035
1050
|
timeline: readJsonlFile(resolve4(resolveAuthorityRunDir(projectRoot, runId), "timeline.jsonl")),
|
|
1036
1051
|
approvals: readApprovals(projectRoot, { runId }),
|
|
1037
1052
|
userInputs: readUserInputs(projectRoot, { runId })
|
|
@@ -2738,6 +2753,12 @@ function patchRunRecord(projectRoot, runId, patch) {
|
|
|
2738
2753
|
writeJsonFile2(resolve11(resolveAuthorityRunDir2(projectRoot, runId), "run.json"), next);
|
|
2739
2754
|
return next;
|
|
2740
2755
|
}
|
|
2756
|
+
function patchRunPiSessionMetadata(projectRoot, runId, metadata) {
|
|
2757
|
+
return patchRunRecord(projectRoot, runId, {
|
|
2758
|
+
piSession: metadata?.public ?? null,
|
|
2759
|
+
piSessionPrivate: metadata
|
|
2760
|
+
});
|
|
2761
|
+
}
|
|
2741
2762
|
function buildRunStartPatch(startedAt) {
|
|
2742
2763
|
return {
|
|
2743
2764
|
status: "preparing",
|
|
@@ -3929,10 +3950,10 @@ function closeoutPhasePatch(phase, status, extra = {}) {
|
|
|
3929
3950
|
const updatedAt = new Date().toISOString();
|
|
3930
3951
|
return {
|
|
3931
3952
|
serverCloseout: {
|
|
3953
|
+
...extra,
|
|
3932
3954
|
phase,
|
|
3933
3955
|
status,
|
|
3934
|
-
updatedAt
|
|
3935
|
-
...extra
|
|
3956
|
+
updatedAt
|
|
3936
3957
|
}
|
|
3937
3958
|
};
|
|
3938
3959
|
}
|
|
@@ -4050,6 +4071,58 @@ async function updateRunTaskSourceLifecycle(projectRoot, run, status, summary, o
|
|
|
4050
4071
|
});
|
|
4051
4072
|
}
|
|
4052
4073
|
}
|
|
4074
|
+
async function markServerOwnedCloseoutFailed(state, runId, error) {
|
|
4075
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
4076
|
+
const current = readAuthorityRun4(state.projectRoot, runId);
|
|
4077
|
+
patchRunRecord(state.projectRoot, runId, {
|
|
4078
|
+
status: "failed",
|
|
4079
|
+
completedAt: new Date().toISOString(),
|
|
4080
|
+
errorText: detail,
|
|
4081
|
+
...closeoutPhasePatch("failed", "failed", { error: detail })
|
|
4082
|
+
});
|
|
4083
|
+
appendRunLogEntryAndBroadcast(state, runId, {
|
|
4084
|
+
id: `log:${runId}:server-closeout-failed`,
|
|
4085
|
+
title: "Server-owned closeout failed",
|
|
4086
|
+
detail,
|
|
4087
|
+
tone: "error",
|
|
4088
|
+
status: "failed",
|
|
4089
|
+
createdAt: new Date().toISOString()
|
|
4090
|
+
}, "server-closeout-failed");
|
|
4091
|
+
if (current?.taskId) {
|
|
4092
|
+
await updateRunTaskSourceLifecycle(state.projectRoot, { ...current, status: "failed", errorText: detail }, "failed", "Rig server-owned closeout failed.", { errorText: detail }).catch((sourceError) => {
|
|
4093
|
+
appendRunLogEntry(state.projectRoot, runId, {
|
|
4094
|
+
id: `log:${runId}:task-source-closeout-failed-update`,
|
|
4095
|
+
title: "Task source closeout failure update failed",
|
|
4096
|
+
detail: sourceError instanceof Error ? sourceError.message : String(sourceError),
|
|
4097
|
+
tone: "error",
|
|
4098
|
+
status: "failed",
|
|
4099
|
+
createdAt: new Date().toISOString()
|
|
4100
|
+
});
|
|
4101
|
+
});
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
function scheduleServerOwnedPrCloseout(state, runId, reason) {
|
|
4105
|
+
const startedAt = new Date().toISOString();
|
|
4106
|
+
state.runProcesses.set(runId, {
|
|
4107
|
+
runId,
|
|
4108
|
+
child: null,
|
|
4109
|
+
startedAt,
|
|
4110
|
+
stopped: false
|
|
4111
|
+
});
|
|
4112
|
+
queueMicrotask(() => {
|
|
4113
|
+
withServerAuthorityEnvIfNeeded(state.projectRoot, async () => {
|
|
4114
|
+
try {
|
|
4115
|
+
await runServerOwnedPrCloseout(state, runId);
|
|
4116
|
+
} catch (error) {
|
|
4117
|
+
await markServerOwnedCloseoutFailed(state, runId, error);
|
|
4118
|
+
} finally {
|
|
4119
|
+
state.runProcesses.delete(runId);
|
|
4120
|
+
broadcastSnapshotInvalidation(state, `server-closeout-${reason}-terminal`);
|
|
4121
|
+
await reconcileScheduler(state, `server-closeout-${reason}-terminal`);
|
|
4122
|
+
}
|
|
4123
|
+
});
|
|
4124
|
+
});
|
|
4125
|
+
}
|
|
4053
4126
|
async function runServerOwnedPrCloseout(state, runId) {
|
|
4054
4127
|
const run = readAuthorityRun4(state.projectRoot, runId);
|
|
4055
4128
|
if (!run)
|
|
@@ -4061,7 +4134,7 @@ async function runServerOwnedPrCloseout(state, runId) {
|
|
|
4061
4134
|
if (!taskId)
|
|
4062
4135
|
throw new Error("Server-owned closeout requires a task id.");
|
|
4063
4136
|
const workspace = normalizeString(closeout.runtimeWorkspace) ?? normalizeString(run.worktreePath) ?? state.projectRoot;
|
|
4064
|
-
|
|
4137
|
+
let branch = normalizeString(closeout.branch) ?? `rig/${taskId}-${runId}`;
|
|
4065
4138
|
const config = await loadRigLifecycleConfig(state.projectRoot);
|
|
4066
4139
|
const runPrMode = normalizeString(run.prMode);
|
|
4067
4140
|
const prMode = runPrMode === "auto" || runPrMode === "ask" || runPrMode === "off" ? runPrMode : config?.pr?.mode ?? "off";
|
|
@@ -4135,6 +4208,12 @@ async function runServerOwnedPrCloseout(state, runId) {
|
|
|
4135
4208
|
const githubEnv = githubToken ? { RIG_GITHUB_TOKEN: githubToken, GITHUB_TOKEN: githubToken, GH_TOKEN: githubToken } : {};
|
|
4136
4209
|
const gitCommand = createCommandRunner("git", githubEnv);
|
|
4137
4210
|
const ghCommand = createCommandRunner("gh", githubEnv);
|
|
4211
|
+
const workspaceBranch = await gitCommand(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: workspace });
|
|
4212
|
+
const currentWorkspaceBranch = workspaceBranch.exitCode === 0 ? normalizeString(workspaceBranch.stdout) : null;
|
|
4213
|
+
if (currentWorkspaceBranch && currentWorkspaceBranch !== "HEAD" && currentWorkspaceBranch !== branch) {
|
|
4214
|
+
appendCloseoutStage(state, runId, "branch", `Using runtime workspace branch ${currentWorkspaceBranch} instead of recorded branch ${branch}.`, "reviewing", "info");
|
|
4215
|
+
branch = currentWorkspaceBranch;
|
|
4216
|
+
}
|
|
4138
4217
|
const setCloseout = (phase, status, extra = {}) => {
|
|
4139
4218
|
const previous = closeoutRecord(readCurrentRun()) ?? closeout;
|
|
4140
4219
|
patchRunRecord(state.projectRoot, runId, {
|
|
@@ -4505,6 +4584,25 @@ async function startLocalRun(state, runId, options) {
|
|
|
4505
4584
|
broadcastSnapshotInvalidation(state);
|
|
4506
4585
|
continue;
|
|
4507
4586
|
}
|
|
4587
|
+
if (line.startsWith("__RIG_WRAPPER_EVENT__")) {
|
|
4588
|
+
try {
|
|
4589
|
+
const wrapperEvent = JSON.parse(line.slice("__RIG_WRAPPER_EVENT__".length));
|
|
4590
|
+
const eventType = normalizeString(wrapperEvent.type);
|
|
4591
|
+
const payload = wrapperEvent.payload && typeof wrapperEvent.payload === "object" && !Array.isArray(wrapperEvent.payload) ? wrapperEvent.payload : {};
|
|
4592
|
+
if (eventType === "pi.session.ready" && payload.privateMetadata && typeof payload.privateMetadata === "object" && !Array.isArray(payload.privateMetadata)) {
|
|
4593
|
+
patchRunPiSessionMetadata(state.projectRoot, runId, payload.privateMetadata);
|
|
4594
|
+
}
|
|
4595
|
+
appendRunTimelineEntry(state.projectRoot, runId, {
|
|
4596
|
+
id: `timeline:${runId}:${Date.now()}:wrapper:${eventType ?? "event"}`,
|
|
4597
|
+
type: "wrapper-event",
|
|
4598
|
+
eventType,
|
|
4599
|
+
payload,
|
|
4600
|
+
createdAt: normalizeString(wrapperEvent.at) ?? new Date().toISOString()
|
|
4601
|
+
});
|
|
4602
|
+
} catch {}
|
|
4603
|
+
broadcastSnapshotInvalidation(state, "wrapper-event");
|
|
4604
|
+
continue;
|
|
4605
|
+
}
|
|
4508
4606
|
appendRunLogEntryAndBroadcast(state, runId, {
|
|
4509
4607
|
id: `log:${runId}:${Date.now()}`,
|
|
4510
4608
|
title,
|
|
@@ -4533,7 +4631,13 @@ async function startLocalRun(state, runId, options) {
|
|
|
4533
4631
|
if (!current) {
|
|
4534
4632
|
return;
|
|
4535
4633
|
}
|
|
4536
|
-
if (
|
|
4634
|
+
if (closeoutRecord(current)?.status === "pending") {
|
|
4635
|
+
try {
|
|
4636
|
+
await runServerOwnedPrCloseout(state, runId);
|
|
4637
|
+
} catch (closeoutError) {
|
|
4638
|
+
await markServerOwnedCloseoutFailed(state, runId, closeoutError);
|
|
4639
|
+
}
|
|
4640
|
+
} else if (exit.code !== 0 && current.status !== "completed" && current.status !== "stopped") {
|
|
4537
4641
|
const completedAt = current.completedAt ?? new Date().toISOString();
|
|
4538
4642
|
const failureSummary = normalizeString(current.errorText) ?? summarizeRunValidationFailure(state.projectRoot, current) ?? `Rig task-run exited with code ${String(exit.code ?? "unknown")}`;
|
|
4539
4643
|
if (current.status !== "failed") {
|
|
@@ -4568,38 +4672,6 @@ ${sourceFailure}` });
|
|
|
4568
4672
|
agent: current.runtimeAdapter,
|
|
4569
4673
|
summary: failureSummary
|
|
4570
4674
|
});
|
|
4571
|
-
} else if (closeoutRecord(current)?.status === "pending") {
|
|
4572
|
-
try {
|
|
4573
|
-
await runServerOwnedPrCloseout(state, runId);
|
|
4574
|
-
} catch (closeoutError) {
|
|
4575
|
-
const closeoutFailure = closeoutError instanceof Error ? closeoutError.message : String(closeoutError);
|
|
4576
|
-
patchRunRecord(state.projectRoot, runId, {
|
|
4577
|
-
status: "failed",
|
|
4578
|
-
completedAt: new Date().toISOString(),
|
|
4579
|
-
errorText: closeoutFailure,
|
|
4580
|
-
...closeoutPhasePatch("failed", "failed", { error: closeoutFailure })
|
|
4581
|
-
});
|
|
4582
|
-
appendRunLogEntryAndBroadcast(state, runId, {
|
|
4583
|
-
id: `log:${runId}:server-closeout-failed`,
|
|
4584
|
-
title: "Server-owned closeout failed",
|
|
4585
|
-
detail: closeoutFailure,
|
|
4586
|
-
tone: "error",
|
|
4587
|
-
status: "failed",
|
|
4588
|
-
createdAt: new Date().toISOString()
|
|
4589
|
-
}, "server-closeout-failed");
|
|
4590
|
-
if (current.taskId) {
|
|
4591
|
-
await updateRunTaskSourceLifecycle(state.projectRoot, { ...current, status: "failed", errorText: closeoutFailure }, "failed", "Rig server-owned closeout failed.", { errorText: closeoutFailure }).catch((error) => {
|
|
4592
|
-
appendRunLogEntry(state.projectRoot, runId, {
|
|
4593
|
-
id: `log:${runId}:task-source-closeout-failed-update`,
|
|
4594
|
-
title: "Task source closeout failure update failed",
|
|
4595
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
4596
|
-
tone: "error",
|
|
4597
|
-
status: "failed",
|
|
4598
|
-
createdAt: new Date().toISOString()
|
|
4599
|
-
});
|
|
4600
|
-
});
|
|
4601
|
-
}
|
|
4602
|
-
}
|
|
4603
4675
|
}
|
|
4604
4676
|
broadcastSnapshotInvalidation(state);
|
|
4605
4677
|
} catch (error) {
|
|
@@ -4688,8 +4760,14 @@ async function resumeRunRecord(state, input) {
|
|
|
4688
4760
|
}
|
|
4689
4761
|
const closeout = closeoutRecord(run);
|
|
4690
4762
|
const closeoutStatus = normalizeString(closeout?.status)?.toLowerCase() ?? "";
|
|
4691
|
-
if (
|
|
4692
|
-
|
|
4763
|
+
if (EXPLICIT_RESUMABLE_SERVER_CLOSEOUT_STATUSES.has(closeoutStatus)) {
|
|
4764
|
+
patchRunRecord(state.projectRoot, input.runId, {
|
|
4765
|
+
status: "reviewing",
|
|
4766
|
+
completedAt: null,
|
|
4767
|
+
errorText: null,
|
|
4768
|
+
...closeoutPhasePatch("queued", "pending", { ...closeout, resumedAt: input.createdAt })
|
|
4769
|
+
});
|
|
4770
|
+
scheduleServerOwnedPrCloseout(state, input.runId, "explicit-resume");
|
|
4693
4771
|
return;
|
|
4694
4772
|
}
|
|
4695
4773
|
await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null, resume: input.restart !== true });
|
|
@@ -4777,6 +4855,7 @@ function removeTaskIdsFromQueueState(projectRoot, taskIds) {
|
|
|
4777
4855
|
return next;
|
|
4778
4856
|
}
|
|
4779
4857
|
var RESUMABLE_SERVER_CLOSEOUT_STATUSES = new Set(["pending", "running"]);
|
|
4858
|
+
var EXPLICIT_RESUMABLE_SERVER_CLOSEOUT_STATUSES = new Set(["pending", "running", "needs_attention"]);
|
|
4780
4859
|
var ACTIVE_LOCAL_RUN_STATUSES = new Set(["created", "preparing", "running", "validating", "reviewing"]);
|
|
4781
4860
|
function processExists(pid) {
|
|
4782
4861
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
@@ -4792,7 +4871,23 @@ function recoverStaleLocalRun(projectRoot, run) {
|
|
|
4792
4871
|
const record = run;
|
|
4793
4872
|
if (run.mode !== "local")
|
|
4794
4873
|
return false;
|
|
4874
|
+
const closeout = closeoutRecord(record);
|
|
4875
|
+
const closeoutStatus = normalizeString(closeout?.status)?.toLowerCase() ?? "";
|
|
4795
4876
|
const status = normalizeString(record.status)?.toLowerCase() ?? "";
|
|
4877
|
+
if (RESUMABLE_SERVER_CLOSEOUT_STATUSES.has(closeoutStatus))
|
|
4878
|
+
return false;
|
|
4879
|
+
if (closeoutStatus === "needs_attention") {
|
|
4880
|
+
if (!ACTIVE_LOCAL_RUN_STATUSES.has(status))
|
|
4881
|
+
return false;
|
|
4882
|
+
const completedAt2 = record.completedAt ?? new Date().toISOString();
|
|
4883
|
+
patchRunRecord(projectRoot, run.runId, {
|
|
4884
|
+
status: "needs_attention",
|
|
4885
|
+
completedAt: completedAt2,
|
|
4886
|
+
errorText: normalizeString(record.errorText) ?? (Array.isArray(closeout?.feedback) ? closeout.feedback.map(String).join(`
|
|
4887
|
+
`) : null)
|
|
4888
|
+
});
|
|
4889
|
+
return true;
|
|
4890
|
+
}
|
|
4796
4891
|
if (!ACTIVE_LOCAL_RUN_STATUSES.has(status))
|
|
4797
4892
|
return false;
|
|
4798
4893
|
const serverPid = typeof record.serverPid === "number" ? record.serverPid : null;
|
|
@@ -4849,15 +4944,7 @@ async function reconcileScheduler(state, reason) {
|
|
|
4849
4944
|
status: "reviewing",
|
|
4850
4945
|
createdAt: new Date().toISOString()
|
|
4851
4946
|
});
|
|
4852
|
-
|
|
4853
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
4854
|
-
patchRunRecord(state.projectRoot, run.runId, {
|
|
4855
|
-
status: "failed",
|
|
4856
|
-
completedAt: new Date().toISOString(),
|
|
4857
|
-
errorText: detail,
|
|
4858
|
-
...closeoutPhasePatch("failed", "failed", { error: detail })
|
|
4859
|
-
});
|
|
4860
|
-
});
|
|
4947
|
+
scheduleServerOwnedPrCloseout(state, run.runId, "auto-resume");
|
|
4861
4948
|
changed = true;
|
|
4862
4949
|
}
|
|
4863
4950
|
if (changed) {
|
|
@@ -4939,7 +5026,7 @@ import { basename, dirname as dirname15, isAbsolute as isAbsolute4, resolve as r
|
|
|
4939
5026
|
import { copyFileSync as copyFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync12 } from "fs";
|
|
4940
5027
|
import {
|
|
4941
5028
|
listAuthorityRuns as listAuthorityRuns5,
|
|
4942
|
-
readAuthorityRun as
|
|
5029
|
+
readAuthorityRun as readAuthorityRun7,
|
|
4943
5030
|
resolveAuthorityPaths,
|
|
4944
5031
|
writeJsonFile as writeJsonFile4
|
|
4945
5032
|
} from "@rig/runtime/control-plane/authority-files";
|
|
@@ -4959,10 +5046,64 @@ import {
|
|
|
4959
5046
|
RemoteWsClient
|
|
4960
5047
|
} from "@rig/runtime/control-plane/remote";
|
|
4961
5048
|
|
|
5049
|
+
// packages/server/src/server-helpers/pi-session-proxy.ts
|
|
5050
|
+
import { readAuthorityRun as readAuthorityRun5 } from "@rig/runtime/control-plane/authority-files";
|
|
5051
|
+
function resolveRunPiSessionProxy(projectRoot, runId) {
|
|
5052
|
+
const run = readAuthorityRun5(projectRoot, runId);
|
|
5053
|
+
if (!run)
|
|
5054
|
+
return null;
|
|
5055
|
+
const privateMetadata = readRunPiSessionMetadata(projectRoot, runId);
|
|
5056
|
+
if (!privateMetadata)
|
|
5057
|
+
return { pending: true, runId, status: typeof run.status === "string" ? run.status : undefined };
|
|
5058
|
+
const connection = privateMetadata.daemonConnection;
|
|
5059
|
+
if (connection.mode !== "http")
|
|
5060
|
+
throw new Error("Only loopback HTTP Rig Pi session daemon connections are supported");
|
|
5061
|
+
const token = tokenFromRef(connection.tokenRef);
|
|
5062
|
+
if (!token)
|
|
5063
|
+
throw new Error("Rig Pi session daemon token is unavailable");
|
|
5064
|
+
return {
|
|
5065
|
+
runId,
|
|
5066
|
+
sessionId: privateMetadata.public.sessionId,
|
|
5067
|
+
metadata: privateMetadata.public,
|
|
5068
|
+
privateMetadata,
|
|
5069
|
+
baseUrl: connection.baseUrl.replace(/\/+$/, ""),
|
|
5070
|
+
token
|
|
5071
|
+
};
|
|
5072
|
+
}
|
|
5073
|
+
async function proxyRunPiHttp(projectRoot, runId, input) {
|
|
5074
|
+
const resolved = resolveRunPiSessionProxy(projectRoot, runId);
|
|
5075
|
+
if (resolved === null)
|
|
5076
|
+
return { status: 404, payload: { ok: false, error: "Run not found" } };
|
|
5077
|
+
if ("pending" in resolved)
|
|
5078
|
+
return { status: 409, payload: { ready: false, runId, status: resolved.status, retryAfterMs: 500 } };
|
|
5079
|
+
const response = await fetch(`${resolved.baseUrl}${input.daemonPath}`, {
|
|
5080
|
+
method: input.method,
|
|
5081
|
+
headers: {
|
|
5082
|
+
authorization: `Bearer ${resolved.token}`,
|
|
5083
|
+
...input.body === undefined ? {} : { "content-type": "application/json" }
|
|
5084
|
+
},
|
|
5085
|
+
body: input.body === undefined ? undefined : JSON.stringify(input.body)
|
|
5086
|
+
});
|
|
5087
|
+
const text = await response.text();
|
|
5088
|
+
const payload = text.trim() ? JSON.parse(text) : null;
|
|
5089
|
+
return { status: response.status, payload };
|
|
5090
|
+
}
|
|
5091
|
+
function buildRunPiDaemonWebSocketUrl(resolved) {
|
|
5092
|
+
const url = new URL(`${resolved.baseUrl}/sessions/${encodeURIComponent(resolved.sessionId)}/events`);
|
|
5093
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
5094
|
+
url.searchParams.set("token", resolved.token);
|
|
5095
|
+
return url.toString();
|
|
5096
|
+
}
|
|
5097
|
+
function tokenFromRef(ref) {
|
|
5098
|
+
if (ref.startsWith("inline:"))
|
|
5099
|
+
return ref.slice("inline:".length) || null;
|
|
5100
|
+
return null;
|
|
5101
|
+
}
|
|
5102
|
+
|
|
4962
5103
|
// packages/server/src/server-helpers/run-steering.ts
|
|
4963
5104
|
import { dirname as dirname10, resolve as resolve15 } from "path";
|
|
4964
5105
|
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync5 } from "fs";
|
|
4965
|
-
import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as
|
|
5106
|
+
import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as readAuthorityRun6, resolveAuthorityRunDir as resolveAuthorityRunDir4 } from "@rig/runtime/control-plane/authority-files";
|
|
4966
5107
|
var steeringSequence = 0;
|
|
4967
5108
|
function runSteeringPath(projectRoot, runId) {
|
|
4968
5109
|
return resolve15(resolveAuthorityRunDir4(projectRoot, runId), "steering.jsonl");
|
|
@@ -5031,7 +5172,7 @@ function markQueuedRunSteeringMessagesDelivered(projectRoot, runId, ids) {
|
|
|
5031
5172
|
return delivered;
|
|
5032
5173
|
}
|
|
5033
5174
|
function queueRunSteeringMessage(projectRoot, runId, input) {
|
|
5034
|
-
const run =
|
|
5175
|
+
const run = readAuthorityRun6(projectRoot, runId);
|
|
5035
5176
|
if (!run)
|
|
5036
5177
|
throw new Error(`Run not found: ${runId}`);
|
|
5037
5178
|
const text = input.message.trim();
|
|
@@ -6019,7 +6160,8 @@ function isAuthorizedInspectorStreamRequest(req, authToken) {
|
|
|
6019
6160
|
}
|
|
6020
6161
|
function buildDeploymentStatus(projectRoot) {
|
|
6021
6162
|
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);
|
|
6022
|
-
const
|
|
6163
|
+
const deploymentRoot = normalizeString(process.env.RIG_HOST_PROJECT_ROOT) ?? projectRoot;
|
|
6164
|
+
const gitCommit = envCommit ?? readGitHeadCommit(deploymentRoot) ?? readGitHeadCommit(projectRoot);
|
|
6023
6165
|
return {
|
|
6024
6166
|
currentCommit: gitCommit,
|
|
6025
6167
|
commitSource: envCommit ? "env" : gitCommit ? "git" : null,
|
|
@@ -6609,7 +6751,7 @@ function redactSecretFields(value) {
|
|
|
6609
6751
|
return redacted;
|
|
6610
6752
|
}
|
|
6611
6753
|
function validateRemoteLease(deps, state, input) {
|
|
6612
|
-
const run =
|
|
6754
|
+
const run = readAuthorityRun7(state.projectRoot, input.runId);
|
|
6613
6755
|
if (!run) {
|
|
6614
6756
|
return { ok: false, response: deps.jsonResponse({ ok: false, error: "Remote run not found" }, 404) };
|
|
6615
6757
|
}
|
|
@@ -6629,6 +6771,43 @@ function createRigServerFetch(state, deps) {
|
|
|
6629
6771
|
return deps.withServerPathEnv(state.projectRoot, async () => {
|
|
6630
6772
|
const browserOrigin = deps.resolveAllowedBrowserOrigin(req);
|
|
6631
6773
|
const finalizeResponse = (response) => deps.withCorsHeaders(response, req, browserOrigin);
|
|
6774
|
+
const earlyUrl = new URL(req.url);
|
|
6775
|
+
const piEventsWsMatch = earlyUrl.pathname.match(/^\/api\/runs\/([^/]+)\/pi\/events$/);
|
|
6776
|
+
if (piEventsWsMatch && req.headers.get("upgrade")?.toLowerCase() === "websocket") {
|
|
6777
|
+
const queryToken = earlyUrl.searchParams.get("token");
|
|
6778
|
+
const authHeaders = new Headers(req.headers);
|
|
6779
|
+
if (queryToken && !authHeaders.has("authorization")) {
|
|
6780
|
+
authHeaders.set("authorization", `Bearer ${queryToken}`);
|
|
6781
|
+
}
|
|
6782
|
+
const legacyAuthorized = Boolean(state.authToken && queryToken === state.authToken);
|
|
6783
|
+
const requestAuth = authorizeRigHttpRequest({
|
|
6784
|
+
req: new Request(req.url, { method: req.method, headers: authHeaders }),
|
|
6785
|
+
pathname: earlyUrl.pathname,
|
|
6786
|
+
projectRoot: state.projectRoot,
|
|
6787
|
+
serverAuthToken: state.authToken,
|
|
6788
|
+
legacyAuthorized
|
|
6789
|
+
});
|
|
6790
|
+
if (!requestAuth.authorized) {
|
|
6791
|
+
return deps.jsonResponse({ ok: false, error: "Unauthorized WebSocket connection", reason: requestAuth.reason }, 401);
|
|
6792
|
+
}
|
|
6793
|
+
const runId = decodeURIComponent(piEventsWsMatch[1]);
|
|
6794
|
+
const resolved = resolveRunPiSessionProxy(state.projectRoot, runId);
|
|
6795
|
+
if (resolved === null)
|
|
6796
|
+
return deps.jsonResponse({ ok: false, error: "Run not found" }, 404);
|
|
6797
|
+
if ("pending" in resolved)
|
|
6798
|
+
return deps.jsonResponse({ ready: false, runId, status: resolved.status, retryAfterMs: 500 }, 202);
|
|
6799
|
+
if (!server)
|
|
6800
|
+
return deps.jsonResponse({ ok: false, error: "WebSocket upgrade unavailable" }, 400);
|
|
6801
|
+
const upgraded = server.upgrade(req, {
|
|
6802
|
+
data: {
|
|
6803
|
+
kind: "pi-session-proxy",
|
|
6804
|
+
connectedAt: new Date().toISOString(),
|
|
6805
|
+
upstreamUrl: buildRunPiDaemonWebSocketUrl(resolved),
|
|
6806
|
+
runId
|
|
6807
|
+
}
|
|
6808
|
+
});
|
|
6809
|
+
return upgraded ? new Response(null) : deps.jsonResponse({ ok: false, error: "WebSocket upgrade failed" }, 400);
|
|
6810
|
+
}
|
|
6632
6811
|
const upgradeResponse = handleWebSocketUpgrade({
|
|
6633
6812
|
req,
|
|
6634
6813
|
server,
|
|
@@ -8367,6 +8546,49 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
8367
8546
|
return deps.jsonResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);
|
|
8368
8547
|
}
|
|
8369
8548
|
}
|
|
8549
|
+
const runPiMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/pi(?:\/(.*))?$/);
|
|
8550
|
+
if (runPiMatch) {
|
|
8551
|
+
const runId = decodeURIComponent(runPiMatch[1]);
|
|
8552
|
+
const action = runPiMatch[2] || "";
|
|
8553
|
+
const resolved = resolveRunPiSessionProxy(state.projectRoot, runId);
|
|
8554
|
+
if (resolved === null)
|
|
8555
|
+
return deps.notFound();
|
|
8556
|
+
if ("pending" in resolved) {
|
|
8557
|
+
if (action === "" && req.method === "GET") {
|
|
8558
|
+
return deps.jsonResponse({ ready: false, runId, status: resolved.status, retryAfterMs: 500 }, 202);
|
|
8559
|
+
}
|
|
8560
|
+
return deps.jsonResponse({ ready: false, runId, status: resolved.status, retryAfterMs: 500, error: "Pi session is not ready" }, 409);
|
|
8561
|
+
}
|
|
8562
|
+
if (action === "" && req.method === "GET")
|
|
8563
|
+
return deps.jsonResponse({ ready: true, metadata: resolved.metadata });
|
|
8564
|
+
const body = req.method === "GET" ? undefined : await deps.readJsonBody(req);
|
|
8565
|
+
const sessionPath = `/sessions/${encodeURIComponent(resolved.sessionId)}`;
|
|
8566
|
+
const daemonPath = (() => {
|
|
8567
|
+
if (action === "messages" && req.method === "GET")
|
|
8568
|
+
return `${sessionPath}/messages`;
|
|
8569
|
+
if (action === "status" && req.method === "GET")
|
|
8570
|
+
return `${sessionPath}/status`;
|
|
8571
|
+
if (action === "commands" && req.method === "GET")
|
|
8572
|
+
return `${sessionPath}/commands`;
|
|
8573
|
+
if (action === "prompt" && req.method === "POST")
|
|
8574
|
+
return `${sessionPath}/prompt`;
|
|
8575
|
+
if (action === "shell" && req.method === "POST")
|
|
8576
|
+
return `${sessionPath}/shell`;
|
|
8577
|
+
if (action === "commands/run" && req.method === "POST")
|
|
8578
|
+
return `${sessionPath}/commands/run`;
|
|
8579
|
+
if (action === "commands/respond" && req.method === "POST")
|
|
8580
|
+
return `${sessionPath}/commands/respond`;
|
|
8581
|
+
if (action === "extension-ui/respond" && req.method === "POST")
|
|
8582
|
+
return `${sessionPath}/extension-ui/respond`;
|
|
8583
|
+
if (action === "abort" && req.method === "POST")
|
|
8584
|
+
return `${sessionPath}/abort`;
|
|
8585
|
+
return null;
|
|
8586
|
+
})();
|
|
8587
|
+
if (!daemonPath)
|
|
8588
|
+
return deps.notFound();
|
|
8589
|
+
const proxied = await proxyRunPiHttp(state.projectRoot, runId, { method: req.method, daemonPath, body });
|
|
8590
|
+
return deps.jsonResponse(proxied.payload, proxied.status);
|
|
8591
|
+
}
|
|
8370
8592
|
const runSteeringAckMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/steering\/ack$/);
|
|
8371
8593
|
if (runSteeringAckMatch && req.method === "POST") {
|
|
8372
8594
|
const runId = decodeURIComponent(runSteeringAckMatch[1]);
|
|
@@ -8489,7 +8711,7 @@ import {
|
|
|
8489
8711
|
RemoteWsClient as RemoteWsClient2
|
|
8490
8712
|
} from "@rig/runtime/control-plane/remote";
|
|
8491
8713
|
import { deleteRunState } from "@rig/runtime/control-plane/native/run-ops";
|
|
8492
|
-
import { readAuthorityRun as
|
|
8714
|
+
import { readAuthorityRun as readAuthorityRun8 } from "@rig/runtime/control-plane/authority-files";
|
|
8493
8715
|
function redactRemoteEndpoint2(endpoint) {
|
|
8494
8716
|
const { token, ...rest } = endpoint;
|
|
8495
8717
|
return {
|
|
@@ -8703,7 +8925,7 @@ async function routeWebSocketRequest(state, deps, request) {
|
|
|
8703
8925
|
if (!runId || !messageId || text === null) {
|
|
8704
8926
|
throw new Error("runId, messageId, and text are required");
|
|
8705
8927
|
}
|
|
8706
|
-
const run =
|
|
8928
|
+
const run = readAuthorityRun8(state.projectRoot, runId);
|
|
8707
8929
|
if (!run) {
|
|
8708
8930
|
throw new Error(`Run not found: ${runId}`);
|
|
8709
8931
|
}
|
|
@@ -11664,7 +11886,7 @@ import {
|
|
|
11664
11886
|
} from "@rig/runtime/control-plane/native/run-ops";
|
|
11665
11887
|
import {
|
|
11666
11888
|
listAuthorityRuns as listAuthorityRuns6,
|
|
11667
|
-
readAuthorityRun as
|
|
11889
|
+
readAuthorityRun as readAuthorityRun9
|
|
11668
11890
|
} from "@rig/runtime/control-plane/authority-files";
|
|
11669
11891
|
function providerFromRuntimeAdapter(runtimeAdapter) {
|
|
11670
11892
|
if (!runtimeAdapter) {
|
|
@@ -11744,7 +11966,7 @@ function discoverInspectorRuns(options) {
|
|
|
11744
11966
|
discovered.set(surface.runId, existing);
|
|
11745
11967
|
};
|
|
11746
11968
|
for (const authorityEntry of listAuthorityRuns6(options.projectRoot)) {
|
|
11747
|
-
const run =
|
|
11969
|
+
const run = readAuthorityRun9(options.projectRoot, authorityEntry.runId);
|
|
11748
11970
|
if (!run) {
|
|
11749
11971
|
continue;
|
|
11750
11972
|
}
|
|
@@ -13744,6 +13966,26 @@ function startPoller(state, pollMs) {
|
|
|
13744
13966
|
}
|
|
13745
13967
|
}, pollMs);
|
|
13746
13968
|
}
|
|
13969
|
+
function attachPiSessionProxySocket(ws) {
|
|
13970
|
+
if (ws.data.kind !== "pi-session-proxy")
|
|
13971
|
+
return;
|
|
13972
|
+
const upstream = new WebSocket(ws.data.upstreamUrl);
|
|
13973
|
+
ws.__rigPiUpstream = upstream;
|
|
13974
|
+
upstream.addEventListener("message", (event) => {
|
|
13975
|
+
if (ws.readyState === 1)
|
|
13976
|
+
ws.send(typeof event.data === "string" ? event.data : event.data);
|
|
13977
|
+
});
|
|
13978
|
+
upstream.addEventListener("close", () => {
|
|
13979
|
+
try {
|
|
13980
|
+
ws.close();
|
|
13981
|
+
} catch {}
|
|
13982
|
+
});
|
|
13983
|
+
upstream.addEventListener("error", () => {
|
|
13984
|
+
try {
|
|
13985
|
+
ws.close(1011, "Upstream Pi session stream failed");
|
|
13986
|
+
} catch {}
|
|
13987
|
+
});
|
|
13988
|
+
}
|
|
13747
13989
|
async function createRigServer(options, projectRoot = resolveProjectRoot()) {
|
|
13748
13990
|
const state = await createRigServerState(projectRoot, options.eventType, options.authToken, {
|
|
13749
13991
|
upstreamSyncMs: options.upstreamSyncMs,
|
|
@@ -13756,10 +13998,20 @@ async function createRigServer(options, projectRoot = resolveProjectRoot()) {
|
|
|
13756
13998
|
fetch: (req, server2) => createRigServerFetch2(state)(req, server2),
|
|
13757
13999
|
websocket: {
|
|
13758
14000
|
open(ws) {
|
|
14001
|
+
if (ws.data.kind === "pi-session-proxy") {
|
|
14002
|
+
attachPiSessionProxySocket(ws);
|
|
14003
|
+
return;
|
|
14004
|
+
}
|
|
13759
14005
|
state.sockets.add(ws);
|
|
13760
14006
|
sendWebSocketResponse(ws, buildServerWelcomePush(state.projectRoot));
|
|
13761
14007
|
},
|
|
13762
14008
|
async message(ws, raw) {
|
|
14009
|
+
if (ws.data.kind === "pi-session-proxy") {
|
|
14010
|
+
const upstream = ws.__rigPiUpstream;
|
|
14011
|
+
if (upstream?.readyState === WebSocket.OPEN)
|
|
14012
|
+
upstream.send(raw);
|
|
14013
|
+
return;
|
|
14014
|
+
}
|
|
13763
14015
|
const request = parseWebSocketRequestEnvelope(typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8"));
|
|
13764
14016
|
if (!request) {
|
|
13765
14017
|
sendWebSocketResponse(ws, {
|
|
@@ -13782,6 +14034,10 @@ async function createRigServer(options, projectRoot = resolveProjectRoot()) {
|
|
|
13782
14034
|
}
|
|
13783
14035
|
},
|
|
13784
14036
|
close(ws) {
|
|
14037
|
+
if (ws.data.kind === "pi-session-proxy") {
|
|
14038
|
+
ws.__rigPiUpstream?.close();
|
|
14039
|
+
return;
|
|
14040
|
+
}
|
|
13785
14041
|
state.sockets.delete(ws);
|
|
13786
14042
|
}
|
|
13787
14043
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-rig/server",
|
|
3
|
-
"version": "0.0.6-alpha.
|
|
3
|
+
"version": "0.0.6-alpha.23",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Rig package",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"rig-server": "./dist/src/server.js"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.
|
|
29
|
-
"@rig/core": "npm:@h-rig/core@0.0.6-alpha.
|
|
30
|
-
"@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.
|
|
28
|
+
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.23",
|
|
29
|
+
"@rig/core": "npm:@h-rig/core@0.0.6-alpha.23",
|
|
30
|
+
"@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.23",
|
|
31
31
|
"effect": "4.0.0-beta.78"
|
|
32
32
|
}
|
|
33
33
|
}
|