@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.
@@ -174,14 +174,29 @@ function summarizeUsefulRunError(projectRoot, runId, fallback) {
174
174
  const nonGeneric = errorLines.at(-1);
175
175
  return nonGeneric ?? (typeof fallback === "string" ? fallback : null);
176
176
  }
177
+ function readRunPiSessionMetadata(projectRoot, runId) {
178
+ const run = readAuthorityRun(projectRoot, runId);
179
+ const metadata = run?.piSessionPrivate;
180
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
181
+ return null;
182
+ const record = metadata;
183
+ const publicMetadata = record.public;
184
+ const daemonConnection = record.daemonConnection;
185
+ if (!publicMetadata || typeof publicMetadata !== "object" || Array.isArray(publicMetadata))
186
+ return null;
187
+ if (!daemonConnection || typeof daemonConnection !== "object" || Array.isArray(daemonConnection))
188
+ return null;
189
+ return metadata;
190
+ }
177
191
  function readRunDetails(projectRoot, runId) {
178
192
  const run = readAuthorityRun(projectRoot, runId);
179
193
  if (!run) {
180
194
  return null;
181
195
  }
182
196
  const usefulErrorText = isGenericRunFailure(run.errorText) ? summarizeUsefulRunError(projectRoot, runId, run.errorText) : null;
197
+ const { piSessionPrivate: _piSessionPrivate, ...publicRun } = run;
183
198
  return {
184
- run: usefulErrorText ? { ...run, errorText: usefulErrorText } : run,
199
+ run: usefulErrorText ? { ...publicRun, errorText: usefulErrorText } : publicRun,
185
200
  timeline: readJsonlFile(resolve(resolveAuthorityRunDir(projectRoot, runId), "timeline.jsonl")),
186
201
  approvals: readApprovals(projectRoot, { runId }),
187
202
  userInputs: readUserInputs(projectRoot, { runId })
@@ -1275,6 +1290,7 @@ var TERMINAL_RUN_STATUSES2 = new Set([
1275
1290
  "stopped"
1276
1291
  ]);
1277
1292
  var RESUMABLE_SERVER_CLOSEOUT_STATUSES = new Set(["pending", "running"]);
1293
+ var EXPLICIT_RESUMABLE_SERVER_CLOSEOUT_STATUSES = new Set(["pending", "running", "needs_attention"]);
1278
1294
  var ACTIVE_LOCAL_RUN_STATUSES = new Set(["created", "preparing", "running", "validating", "reviewing"]);
1279
1295
 
1280
1296
  // packages/server/src/server-helpers/ws-router.ts
@@ -1436,7 +1452,7 @@ if (false) {}
1436
1452
  // packages/server/src/server-helpers/http-router.ts
1437
1453
  import {
1438
1454
  listAuthorityRuns as listAuthorityRuns7,
1439
- readAuthorityRun as readAuthorityRun8,
1455
+ readAuthorityRun as readAuthorityRun9,
1440
1456
  resolveAuthorityPaths,
1441
1457
  writeJsonFile as writeJsonFile4
1442
1458
  } from "@rig/runtime/control-plane/authority-files";
@@ -1456,10 +1472,64 @@ import {
1456
1472
  RemoteWsClient as RemoteWsClient2
1457
1473
  } from "@rig/runtime/control-plane/remote";
1458
1474
 
1475
+ // packages/server/src/server-helpers/pi-session-proxy.ts
1476
+ import { readAuthorityRun as readAuthorityRun7 } from "@rig/runtime/control-plane/authority-files";
1477
+ function resolveRunPiSessionProxy(projectRoot, runId) {
1478
+ const run = readAuthorityRun7(projectRoot, runId);
1479
+ if (!run)
1480
+ return null;
1481
+ const privateMetadata = readRunPiSessionMetadata(projectRoot, runId);
1482
+ if (!privateMetadata)
1483
+ return { pending: true, runId, status: typeof run.status === "string" ? run.status : undefined };
1484
+ const connection = privateMetadata.daemonConnection;
1485
+ if (connection.mode !== "http")
1486
+ throw new Error("Only loopback HTTP Rig Pi session daemon connections are supported");
1487
+ const token = tokenFromRef(connection.tokenRef);
1488
+ if (!token)
1489
+ throw new Error("Rig Pi session daemon token is unavailable");
1490
+ return {
1491
+ runId,
1492
+ sessionId: privateMetadata.public.sessionId,
1493
+ metadata: privateMetadata.public,
1494
+ privateMetadata,
1495
+ baseUrl: connection.baseUrl.replace(/\/+$/, ""),
1496
+ token
1497
+ };
1498
+ }
1499
+ async function proxyRunPiHttp(projectRoot, runId, input) {
1500
+ const resolved = resolveRunPiSessionProxy(projectRoot, runId);
1501
+ if (resolved === null)
1502
+ return { status: 404, payload: { ok: false, error: "Run not found" } };
1503
+ if ("pending" in resolved)
1504
+ return { status: 409, payload: { ready: false, runId, status: resolved.status, retryAfterMs: 500 } };
1505
+ const response = await fetch(`${resolved.baseUrl}${input.daemonPath}`, {
1506
+ method: input.method,
1507
+ headers: {
1508
+ authorization: `Bearer ${resolved.token}`,
1509
+ ...input.body === undefined ? {} : { "content-type": "application/json" }
1510
+ },
1511
+ body: input.body === undefined ? undefined : JSON.stringify(input.body)
1512
+ });
1513
+ const text = await response.text();
1514
+ const payload = text.trim() ? JSON.parse(text) : null;
1515
+ return { status: response.status, payload };
1516
+ }
1517
+ function buildRunPiDaemonWebSocketUrl(resolved) {
1518
+ const url = new URL(`${resolved.baseUrl}/sessions/${encodeURIComponent(resolved.sessionId)}/events`);
1519
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
1520
+ url.searchParams.set("token", resolved.token);
1521
+ return url.toString();
1522
+ }
1523
+ function tokenFromRef(ref) {
1524
+ if (ref.startsWith("inline:"))
1525
+ return ref.slice("inline:".length) || null;
1526
+ return null;
1527
+ }
1528
+
1459
1529
  // packages/server/src/server-helpers/run-steering.ts
1460
1530
  import { dirname as dirname4, resolve as resolve8 } from "path";
1461
1531
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync3 } from "fs";
1462
- import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as readAuthorityRun7, resolveAuthorityRunDir as resolveAuthorityRunDir4 } from "@rig/runtime/control-plane/authority-files";
1532
+ import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as readAuthorityRun8, resolveAuthorityRunDir as resolveAuthorityRunDir4 } from "@rig/runtime/control-plane/authority-files";
1463
1533
  var steeringSequence = 0;
1464
1534
  function runSteeringPath(projectRoot, runId) {
1465
1535
  return resolve8(resolveAuthorityRunDir4(projectRoot, runId), "steering.jsonl");
@@ -1528,7 +1598,7 @@ function markQueuedRunSteeringMessagesDelivered(projectRoot, runId, ids) {
1528
1598
  return delivered;
1529
1599
  }
1530
1600
  function queueRunSteeringMessage(projectRoot, runId, input) {
1531
- const run = readAuthorityRun7(projectRoot, runId);
1601
+ const run = readAuthorityRun8(projectRoot, runId);
1532
1602
  if (!run)
1533
1603
  throw new Error(`Run not found: ${runId}`);
1534
1604
  const text = input.message.trim();
@@ -2416,7 +2486,8 @@ function isAuthorizedInspectorStreamRequest(req, authToken) {
2416
2486
  }
2417
2487
  function buildDeploymentStatus(projectRoot) {
2418
2488
  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);
2419
- const gitCommit = envCommit ?? readGitHeadCommit(projectRoot);
2489
+ const deploymentRoot = normalizeString(process.env.RIG_HOST_PROJECT_ROOT) ?? projectRoot;
2490
+ const gitCommit = envCommit ?? readGitHeadCommit(deploymentRoot) ?? readGitHeadCommit(projectRoot);
2420
2491
  return {
2421
2492
  currentCommit: gitCommit,
2422
2493
  commitSource: envCommit ? "env" : gitCommit ? "git" : null,
@@ -3006,7 +3077,7 @@ function redactSecretFields(value) {
3006
3077
  return redacted;
3007
3078
  }
3008
3079
  function validateRemoteLease(deps, state, input) {
3009
- const run = readAuthorityRun8(state.projectRoot, input.runId);
3080
+ const run = readAuthorityRun9(state.projectRoot, input.runId);
3010
3081
  if (!run) {
3011
3082
  return { ok: false, response: deps.jsonResponse({ ok: false, error: "Remote run not found" }, 404) };
3012
3083
  }
@@ -3026,6 +3097,43 @@ function createRigServerFetch(state, deps) {
3026
3097
  return deps.withServerPathEnv(state.projectRoot, async () => {
3027
3098
  const browserOrigin = deps.resolveAllowedBrowserOrigin(req);
3028
3099
  const finalizeResponse = (response) => deps.withCorsHeaders(response, req, browserOrigin);
3100
+ const earlyUrl = new URL(req.url);
3101
+ const piEventsWsMatch = earlyUrl.pathname.match(/^\/api\/runs\/([^/]+)\/pi\/events$/);
3102
+ if (piEventsWsMatch && req.headers.get("upgrade")?.toLowerCase() === "websocket") {
3103
+ const queryToken = earlyUrl.searchParams.get("token");
3104
+ const authHeaders = new Headers(req.headers);
3105
+ if (queryToken && !authHeaders.has("authorization")) {
3106
+ authHeaders.set("authorization", `Bearer ${queryToken}`);
3107
+ }
3108
+ const legacyAuthorized = Boolean(state.authToken && queryToken === state.authToken);
3109
+ const requestAuth = authorizeRigHttpRequest({
3110
+ req: new Request(req.url, { method: req.method, headers: authHeaders }),
3111
+ pathname: earlyUrl.pathname,
3112
+ projectRoot: state.projectRoot,
3113
+ serverAuthToken: state.authToken,
3114
+ legacyAuthorized
3115
+ });
3116
+ if (!requestAuth.authorized) {
3117
+ return deps.jsonResponse({ ok: false, error: "Unauthorized WebSocket connection", reason: requestAuth.reason }, 401);
3118
+ }
3119
+ const runId = decodeURIComponent(piEventsWsMatch[1]);
3120
+ const resolved = resolveRunPiSessionProxy(state.projectRoot, runId);
3121
+ if (resolved === null)
3122
+ return deps.jsonResponse({ ok: false, error: "Run not found" }, 404);
3123
+ if ("pending" in resolved)
3124
+ return deps.jsonResponse({ ready: false, runId, status: resolved.status, retryAfterMs: 500 }, 202);
3125
+ if (!server)
3126
+ return deps.jsonResponse({ ok: false, error: "WebSocket upgrade unavailable" }, 400);
3127
+ const upgraded = server.upgrade(req, {
3128
+ data: {
3129
+ kind: "pi-session-proxy",
3130
+ connectedAt: new Date().toISOString(),
3131
+ upstreamUrl: buildRunPiDaemonWebSocketUrl(resolved),
3132
+ runId
3133
+ }
3134
+ });
3135
+ return upgraded ? new Response(null) : deps.jsonResponse({ ok: false, error: "WebSocket upgrade failed" }, 400);
3136
+ }
3029
3137
  const upgradeResponse = handleWebSocketUpgrade({
3030
3138
  req,
3031
3139
  server,
@@ -4764,6 +4872,49 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
4764
4872
  return deps.jsonResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }, 404);
4765
4873
  }
4766
4874
  }
4875
+ const runPiMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/pi(?:\/(.*))?$/);
4876
+ if (runPiMatch) {
4877
+ const runId = decodeURIComponent(runPiMatch[1]);
4878
+ const action = runPiMatch[2] || "";
4879
+ const resolved = resolveRunPiSessionProxy(state.projectRoot, runId);
4880
+ if (resolved === null)
4881
+ return deps.notFound();
4882
+ if ("pending" in resolved) {
4883
+ if (action === "" && req.method === "GET") {
4884
+ return deps.jsonResponse({ ready: false, runId, status: resolved.status, retryAfterMs: 500 }, 202);
4885
+ }
4886
+ return deps.jsonResponse({ ready: false, runId, status: resolved.status, retryAfterMs: 500, error: "Pi session is not ready" }, 409);
4887
+ }
4888
+ if (action === "" && req.method === "GET")
4889
+ return deps.jsonResponse({ ready: true, metadata: resolved.metadata });
4890
+ const body = req.method === "GET" ? undefined : await deps.readJsonBody(req);
4891
+ const sessionPath = `/sessions/${encodeURIComponent(resolved.sessionId)}`;
4892
+ const daemonPath = (() => {
4893
+ if (action === "messages" && req.method === "GET")
4894
+ return `${sessionPath}/messages`;
4895
+ if (action === "status" && req.method === "GET")
4896
+ return `${sessionPath}/status`;
4897
+ if (action === "commands" && req.method === "GET")
4898
+ return `${sessionPath}/commands`;
4899
+ if (action === "prompt" && req.method === "POST")
4900
+ return `${sessionPath}/prompt`;
4901
+ if (action === "shell" && req.method === "POST")
4902
+ return `${sessionPath}/shell`;
4903
+ if (action === "commands/run" && req.method === "POST")
4904
+ return `${sessionPath}/commands/run`;
4905
+ if (action === "commands/respond" && req.method === "POST")
4906
+ return `${sessionPath}/commands/respond`;
4907
+ if (action === "extension-ui/respond" && req.method === "POST")
4908
+ return `${sessionPath}/extension-ui/respond`;
4909
+ if (action === "abort" && req.method === "POST")
4910
+ return `${sessionPath}/abort`;
4911
+ return null;
4912
+ })();
4913
+ if (!daemonPath)
4914
+ return deps.notFound();
4915
+ const proxied = await proxyRunPiHttp(state.projectRoot, runId, { method: req.method, daemonPath, body });
4916
+ return deps.jsonResponse(proxied.payload, proxied.status);
4917
+ }
4767
4918
  const runSteeringAckMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/steering\/ack$/);
4768
4919
  if (runSteeringAckMatch && req.method === "POST") {
4769
4920
  const runId = decodeURIComponent(runSteeringAckMatch[1]);
@@ -0,0 +1,84 @@
1
+ // @bun
2
+ // packages/server/src/server-helpers/pi-session-proxy.ts
3
+ import { readAuthorityRun as readAuthorityRun2 } from "@rig/runtime/control-plane/authority-files";
4
+
5
+ // packages/server/src/server-helpers/run-io.ts
6
+ import {
7
+ listAuthorityRuns,
8
+ readAuthorityRun,
9
+ readJsonlFile,
10
+ resolveAuthorityRunDir
11
+ } from "@rig/runtime/control-plane/authority-files";
12
+ function readRunPiSessionMetadata(projectRoot, runId) {
13
+ const run = readAuthorityRun(projectRoot, runId);
14
+ const metadata = run?.piSessionPrivate;
15
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
16
+ return null;
17
+ const record = metadata;
18
+ const publicMetadata = record.public;
19
+ const daemonConnection = record.daemonConnection;
20
+ if (!publicMetadata || typeof publicMetadata !== "object" || Array.isArray(publicMetadata))
21
+ return null;
22
+ if (!daemonConnection || typeof daemonConnection !== "object" || Array.isArray(daemonConnection))
23
+ return null;
24
+ return metadata;
25
+ }
26
+ var INITIAL_RUN_LOG_TAIL_MAX_BYTES = 8 * 1024 * 1024;
27
+
28
+ // packages/server/src/server-helpers/pi-session-proxy.ts
29
+ function resolveRunPiSessionProxy(projectRoot, runId) {
30
+ const run = readAuthorityRun2(projectRoot, runId);
31
+ if (!run)
32
+ return null;
33
+ const privateMetadata = readRunPiSessionMetadata(projectRoot, runId);
34
+ if (!privateMetadata)
35
+ return { pending: true, runId, status: typeof run.status === "string" ? run.status : undefined };
36
+ const connection = privateMetadata.daemonConnection;
37
+ if (connection.mode !== "http")
38
+ throw new Error("Only loopback HTTP Rig Pi session daemon connections are supported");
39
+ const token = tokenFromRef(connection.tokenRef);
40
+ if (!token)
41
+ throw new Error("Rig Pi session daemon token is unavailable");
42
+ return {
43
+ runId,
44
+ sessionId: privateMetadata.public.sessionId,
45
+ metadata: privateMetadata.public,
46
+ privateMetadata,
47
+ baseUrl: connection.baseUrl.replace(/\/+$/, ""),
48
+ token
49
+ };
50
+ }
51
+ async function proxyRunPiHttp(projectRoot, runId, input) {
52
+ const resolved = resolveRunPiSessionProxy(projectRoot, runId);
53
+ if (resolved === null)
54
+ return { status: 404, payload: { ok: false, error: "Run not found" } };
55
+ if ("pending" in resolved)
56
+ return { status: 409, payload: { ready: false, runId, status: resolved.status, retryAfterMs: 500 } };
57
+ const response = await fetch(`${resolved.baseUrl}${input.daemonPath}`, {
58
+ method: input.method,
59
+ headers: {
60
+ authorization: `Bearer ${resolved.token}`,
61
+ ...input.body === undefined ? {} : { "content-type": "application/json" }
62
+ },
63
+ body: input.body === undefined ? undefined : JSON.stringify(input.body)
64
+ });
65
+ const text = await response.text();
66
+ const payload = text.trim() ? JSON.parse(text) : null;
67
+ return { status: response.status, payload };
68
+ }
69
+ function buildRunPiDaemonWebSocketUrl(resolved) {
70
+ const url = new URL(`${resolved.baseUrl}/sessions/${encodeURIComponent(resolved.sessionId)}/events`);
71
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
72
+ url.searchParams.set("token", resolved.token);
73
+ return url.toString();
74
+ }
75
+ function tokenFromRef(ref) {
76
+ if (ref.startsWith("inline:"))
77
+ return ref.slice("inline:".length) || null;
78
+ return null;
79
+ }
80
+ export {
81
+ resolveRunPiSessionProxy,
82
+ proxyRunPiHttp,
83
+ buildRunPiDaemonWebSocketUrl
84
+ };
@@ -114,14 +114,29 @@ function summarizeUsefulRunError(projectRoot, runId, fallback) {
114
114
  const nonGeneric = errorLines.at(-1);
115
115
  return nonGeneric ?? (typeof fallback === "string" ? fallback : null);
116
116
  }
117
+ function readRunPiSessionMetadata(projectRoot, runId) {
118
+ const run = readAuthorityRun(projectRoot, runId);
119
+ const metadata = run?.piSessionPrivate;
120
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
121
+ return null;
122
+ const record = metadata;
123
+ const publicMetadata = record.public;
124
+ const daemonConnection = record.daemonConnection;
125
+ if (!publicMetadata || typeof publicMetadata !== "object" || Array.isArray(publicMetadata))
126
+ return null;
127
+ if (!daemonConnection || typeof daemonConnection !== "object" || Array.isArray(daemonConnection))
128
+ return null;
129
+ return metadata;
130
+ }
117
131
  function readRunDetails(projectRoot, runId) {
118
132
  const run = readAuthorityRun(projectRoot, runId);
119
133
  if (!run) {
120
134
  return null;
121
135
  }
122
136
  const usefulErrorText = isGenericRunFailure(run.errorText) ? summarizeUsefulRunError(projectRoot, runId, run.errorText) : null;
137
+ const { piSessionPrivate: _piSessionPrivate, ...publicRun } = run;
123
138
  return {
124
- run: usefulErrorText ? { ...run, errorText: usefulErrorText } : run,
139
+ run: usefulErrorText ? { ...publicRun, errorText: usefulErrorText } : publicRun,
125
140
  timeline: readJsonlFile(resolve(resolveAuthorityRunDir(projectRoot, runId), "timeline.jsonl")),
126
141
  approvals: readApprovals(projectRoot, { runId }),
127
142
  userInputs: readUserInputs(projectRoot, { runId })
@@ -265,6 +280,7 @@ export {
265
280
  readUserInputsForRuns,
266
281
  readUserInputs,
267
282
  readRunTimelinePage,
283
+ readRunPiSessionMetadata,
268
284
  readRunLogsPage,
269
285
  readRunDetails,
270
286
  readRawRunLogs,