@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.
@@ -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 ? { ...run, errorText: usefulErrorText } : run,
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
- const branch = normalizeString(closeout.branch) ?? `rig/${taskId}-${runId}`;
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 (exit.code !== 0 && current.status !== "completed" && current.status !== "stopped") {
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 (RESUMABLE_SERVER_CLOSEOUT_STATUSES.has(closeoutStatus)) {
4692
- await runServerOwnedPrCloseout(state, input.runId);
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
- await runServerOwnedPrCloseout(state, run.runId).catch((error) => {
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 readAuthorityRun6,
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 readAuthorityRun5, resolveAuthorityRunDir as resolveAuthorityRunDir4 } from "@rig/runtime/control-plane/authority-files";
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 = readAuthorityRun5(projectRoot, runId);
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 gitCommit = envCommit ?? readGitHeadCommit(projectRoot);
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 = readAuthorityRun6(state.projectRoot, input.runId);
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 readAuthorityRun7 } from "@rig/runtime/control-plane/authority-files";
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 = readAuthorityRun7(state.projectRoot, runId);
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 readAuthorityRun8
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 = readAuthorityRun8(options.projectRoot, authorityEntry.runId);
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.22",
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.22",
29
- "@rig/core": "npm:@h-rig/core@0.0.6-alpha.22",
30
- "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.22",
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
  }