@h-rig/runtime 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.
Files changed (25) hide show
  1. package/dist/bin/rig-agent-dispatch.js +333 -23
  2. package/dist/src/control-plane/agent-wrapper.js +336 -23
  3. package/dist/src/control-plane/harness-main.js +142 -17
  4. package/dist/src/control-plane/hooks/completion-verification.js +142 -17
  5. package/dist/src/control-plane/native/harness-cli.js +142 -17
  6. package/dist/src/control-plane/native/pr-automation.js +142 -17
  7. package/dist/src/control-plane/native/pr-review-gate.js +142 -17
  8. package/dist/src/control-plane/native/run-ops.js +1 -1
  9. package/dist/src/control-plane/native/task-ops.js +142 -17
  10. package/dist/src/control-plane/native/verifier.js +142 -17
  11. package/dist/src/control-plane/pi-sessiond/bin.js +793 -0
  12. package/dist/src/control-plane/pi-sessiond/client.js +41 -0
  13. package/dist/src/control-plane/pi-sessiond/event-hub.js +59 -0
  14. package/dist/src/control-plane/pi-sessiond/extension-ui-context.js +198 -0
  15. package/dist/src/control-plane/pi-sessiond/launcher.js +163 -0
  16. package/dist/src/control-plane/pi-sessiond/server.js +802 -0
  17. package/dist/src/control-plane/pi-sessiond/session-service.js +540 -0
  18. package/dist/src/control-plane/pi-sessiond/types.js +1 -0
  19. package/dist/src/control-plane/runtime/index.js +17 -0
  20. package/dist/src/control-plane/runtime/isolation/home.js +17 -0
  21. package/dist/src/control-plane/runtime/isolation/index.js +17 -0
  22. package/dist/src/control-plane/runtime/isolation/runner.js +17 -0
  23. package/dist/src/control-plane/runtime/isolation.js +17 -0
  24. package/dist/src/control-plane/runtime/queue.js +17 -0
  25. package/package.json +7 -7
@@ -1775,6 +1775,57 @@ function flattenPaginatedArray(value) {
1775
1775
  }
1776
1776
  return value;
1777
1777
  }
1778
+ function parseConcatenatedJsonValues(value) {
1779
+ const text = value.trim();
1780
+ const docs = [];
1781
+ let start = null;
1782
+ let depth = 0;
1783
+ let inString = false;
1784
+ let escape = false;
1785
+ for (let index = 0;index < text.length; index += 1) {
1786
+ const char = text[index];
1787
+ if (start === null) {
1788
+ if (/\s/.test(char))
1789
+ continue;
1790
+ start = index;
1791
+ }
1792
+ if (inString) {
1793
+ if (escape) {
1794
+ escape = false;
1795
+ } else if (char === "\\") {
1796
+ escape = true;
1797
+ } else if (char === '"') {
1798
+ inString = false;
1799
+ }
1800
+ continue;
1801
+ }
1802
+ if (char === '"') {
1803
+ inString = true;
1804
+ continue;
1805
+ }
1806
+ if (char === "{" || char === "[") {
1807
+ depth += 1;
1808
+ continue;
1809
+ }
1810
+ if (char === "}" || char === "]") {
1811
+ depth -= 1;
1812
+ if (depth < 0)
1813
+ return { value: docs, error: "unexpected JSON close delimiter" };
1814
+ if (depth === 0 && start !== null) {
1815
+ const segment = text.slice(start, index + 1);
1816
+ try {
1817
+ docs.push(JSON.parse(segment));
1818
+ } catch (error) {
1819
+ return { value: docs, error: error instanceof Error ? error.message : String(error) };
1820
+ }
1821
+ start = null;
1822
+ }
1823
+ }
1824
+ }
1825
+ if (inString || depth !== 0 || start !== null)
1826
+ return { value: docs, error: "incomplete JSON stream" };
1827
+ return { value: docs };
1828
+ }
1778
1829
  function parseJsonArray(value) {
1779
1830
  if (!value?.trim())
1780
1831
  return { value: [], error: "empty JSON output" };
@@ -1783,7 +1834,11 @@ function parseJsonArray(value) {
1783
1834
  const flattened = flattenPaginatedArray(parsed);
1784
1835
  return flattened ? { value: flattened } : { value: [], error: "JSON output was not an array" };
1785
1836
  } catch (error) {
1786
- return { value: [], error: error instanceof Error ? error.message : String(error) };
1837
+ const streamed = parseConcatenatedJsonValues(value);
1838
+ if (streamed.error)
1839
+ return { value: [], error: error instanceof Error ? error.message : String(error) };
1840
+ const flattened = streamed.value.flatMap((entry) => flattenPaginatedArray(entry) ?? []);
1841
+ return flattened.length > 0 || streamed.value.length === 0 ? { value: flattened } : { value: [], error: "JSON output was not an array" };
1787
1842
  }
1788
1843
  }
1789
1844
  function parseGithubPrUrl(prUrl) {
@@ -1872,6 +1927,24 @@ function isStrictFiveOfFive(score) {
1872
1927
  function containsConflictingScoreText(input) {
1873
1928
  return parseGreptileScores(input).some((score) => !isStrictFiveOfFive(score));
1874
1929
  }
1930
+ function extractGreptileCommentBlock(input) {
1931
+ const match = input.match(/<!--\s*greptile_comment\s*-->([\s\S]*?)<!--\s*\/greptile_comment\s*-->/i);
1932
+ return match?.[1]?.trim() ?? null;
1933
+ }
1934
+ function extractGreptileBodyReviewedSha(input) {
1935
+ const block = extractGreptileCommentBlock(input);
1936
+ if (!block)
1937
+ return null;
1938
+ const commitLink = block.match(/\/commit\/([0-9a-f]{40})(?:\b|[^0-9a-f])/i);
1939
+ return commitLink?.[1]?.toLowerCase() ?? null;
1940
+ }
1941
+ function isoAtOrAfter(value, floor) {
1942
+ if (!value || !floor)
1943
+ return false;
1944
+ const valueMs = Date.parse(value);
1945
+ const floorMs = Date.parse(floor);
1946
+ return Number.isFinite(valueMs) && Number.isFinite(floorMs) && valueMs >= floorMs;
1947
+ }
1875
1948
  function greptileStatusVerdict(status) {
1876
1949
  const normalized = String(status ?? "").trim().toUpperCase().replace(/[\s-]+/g, "_");
1877
1950
  if (!normalized)
@@ -2246,9 +2319,18 @@ function commentAuthorLogin(comment) {
2246
2319
  }
2247
2320
  function collectGreptileSignals(evidence) {
2248
2321
  const signals = [];
2322
+ const greptileBodyReviewedSha = extractGreptileBodyReviewedSha(evidence.body);
2323
+ const trustedGreptileBody = Boolean(greptileBodyReviewedSha && isGreptileGithubLogin(evidence.bodyEditorLogin) && isoAtOrAfter(evidence.bodyLastEditedAt, evidence.headCommittedDate));
2249
2324
  const contextSources = [
2250
2325
  { source: "pr-title", body: evidence.title ?? "" },
2251
- { source: "pr-body", body: evidence.body }
2326
+ {
2327
+ source: "pr-body",
2328
+ body: evidence.body,
2329
+ trusted: trustedGreptileBody,
2330
+ authorLogin: trustedGreptileBody ? evidence.bodyEditorLogin ?? null : null,
2331
+ reviewedSha: greptileBodyReviewedSha,
2332
+ verdict: trustedGreptileBody ? "completed" : null
2333
+ }
2252
2334
  ];
2253
2335
  for (const context of contextSources) {
2254
2336
  if (!context.body.trim())
@@ -2260,7 +2342,10 @@ function collectGreptileSignals(evidence) {
2260
2342
  source: context.source,
2261
2343
  body: context.body,
2262
2344
  currentHeadSha: evidence.currentHeadSha,
2263
- trusted: false,
2345
+ trusted: context.trusted === true,
2346
+ authorLogin: context.authorLogin,
2347
+ reviewedSha: context.reviewedSha,
2348
+ verdict: context.verdict,
2264
2349
  blocker: contextBlocker,
2265
2350
  actionable: contextBlocker
2266
2351
  }));
@@ -2456,7 +2541,7 @@ function deriveGreptileEvidence(input) {
2456
2541
  const hasGreptileEvidence = trustedSignals.length > 0 || signals.some((signal) => /greptile/i.test(signal.body));
2457
2542
  const approved = fresh && completed && !blockers.length && !unresolvedComments.length && currentPendingApiSignals.length === 0 && (approvedByScore || approvedByExplicitMapping);
2458
2543
  const mapping = !hasGreptileEvidence ? "missing" : staleSignals.length > 0 && !approvingSignal ? "stale" : approvedByScore ? "score-5-of-5" : approvedByExplicitMapping ? "explicit-approved" : "unproven";
2459
- const source = approvingSignal?.source === "api" ? "api" : approvingSignal?.source === "github-review" ? "github-review" : approvingSignal?.source === "changed-file-comment" || approvingSignal?.source === "issue-comment" || approvingSignal?.source === "review-thread" ? "github-comment" : greptileReviews.length > 0 && greptileChecks.length > 0 ? "combined" : greptileReviews.length > 0 ? "github-review" : greptileChecks.length > 0 ? "github-check" : signals.some((signal) => signal.source === "pr-body" || signal.source === "pr-title") ? "pr-body" : "missing";
2544
+ const source = approvingSignal?.source === "api" ? "api" : approvingSignal?.source === "github-review" ? "github-review" : approvingSignal?.source === "pr-body" || approvingSignal?.source === "pr-title" ? "pr-body" : approvingSignal?.source === "changed-file-comment" || approvingSignal?.source === "issue-comment" || approvingSignal?.source === "review-thread" ? "github-comment" : greptileReviews.length > 0 && greptileChecks.length > 0 ? "combined" : greptileReviews.length > 0 ? "github-review" : greptileChecks.length > 0 ? "github-check" : signals.some((signal) => signal.source === "pr-body" || signal.source === "pr-title") ? "pr-body" : "missing";
2460
2545
  return {
2461
2546
  source,
2462
2547
  currentHeadSha: input.currentHeadSha,
@@ -2477,17 +2562,48 @@ function isGreptileCheckDetail(check) {
2477
2562
  return isGreptileLabel(checkName(check)) || isGreptileGithubLogin(check.app?.slug) || isGreptileGithubLogin(check.app?.owner?.login) || isGreptileLabel(check.app?.name);
2478
2563
  }
2479
2564
  async function collectGreptileCheckDetails(input) {
2480
- const checkRunsRead = await runJsonArray(input.command, [
2565
+ const checkRunsRead = await runJsonObject(input.command, [
2481
2566
  "api",
2482
2567
  `repos/${input.repoName}/commits/${input.headSha}/check-runs`,
2483
- "--paginate",
2484
- "--slurp",
2485
- "--jq",
2486
- "map(.check_runs // []) | add // []"
2568
+ "-F",
2569
+ "per_page=100"
2487
2570
  ], input.projectRoot);
2488
- const checkRuns = checkRunsRead.value.map(normalizeStatusCheck).filter((entry) => !!entry).filter(isGreptileCheckDetail);
2571
+ const checkRuns = arrayField(checkRunsRead.value, "check_runs").map(normalizeStatusCheck).filter((entry) => !!entry).filter(isGreptileCheckDetail);
2489
2572
  return checkRunsRead.error ? { value: checkRuns, error: checkRunsRead.error } : { value: checkRuns };
2490
2573
  }
2574
+ async function collectPullRequestProvenance(input) {
2575
+ const response = await runJsonObject(input.command, [
2576
+ "api",
2577
+ "graphql",
2578
+ "-F",
2579
+ `owner=${input.owner}`,
2580
+ "-F",
2581
+ `name=${input.name}`,
2582
+ "-F",
2583
+ `prNumber=${input.prNumber}`,
2584
+ "-f",
2585
+ "query=query($owner: String!, $name: String!, $prNumber: Int!) { repository(owner:$owner, name:$name) { pullRequest(number:$prNumber) { lastEditedAt editor { login } commits(last: 1) { nodes { commit { oid committedDate } } } } } }"
2586
+ ], input.projectRoot);
2587
+ if (response.error)
2588
+ return { value: {}, error: response.error };
2589
+ const data = response.value.data;
2590
+ const repository = data?.repository;
2591
+ const pullRequest = repository?.pullRequest;
2592
+ if (!pullRequest)
2593
+ return { value: {}, error: "GitHub pullRequest provenance response did not include a pullRequest object" };
2594
+ const editor = pullRequest.editor;
2595
+ const commits = pullRequest.commits;
2596
+ const nodes = Array.isArray(commits?.nodes) ? commits.nodes : [];
2597
+ const latestCommitNode = nodes[nodes.length - 1];
2598
+ const latestCommit = latestCommitNode?.commit;
2599
+ return {
2600
+ value: {
2601
+ bodyEditorLogin: typeof editor?.login === "string" ? editor.login : null,
2602
+ bodyLastEditedAt: typeof pullRequest.lastEditedAt === "string" ? pullRequest.lastEditedAt : null,
2603
+ headCommittedDate: typeof latestCommit?.committedDate === "string" ? latestCommit.committedDate : null
2604
+ }
2605
+ };
2606
+ }
2491
2607
  async function collectReviewThreads(input) {
2492
2608
  const reviewThreads = [];
2493
2609
  let afterCursor = null;
@@ -2565,11 +2681,19 @@ async function collectPrReviewEvidence(input) {
2565
2681
  const baseRefName = firstString(view, ["baseRefName"]);
2566
2682
  const statusCheckRollup = arrayField(view, "statusCheckRollup").map(normalizeStatusCheck).filter((entry) => !!entry);
2567
2683
  const reviews = arrayField(view, "reviews").map(normalizeReview).filter((entry) => !!entry);
2568
- const reviewCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/pulls/${parsed.prNumber}/comments`, "--paginate", "--slurp"], input.projectRoot);
2684
+ const provenanceRead = await collectPullRequestProvenance({
2685
+ command: input.command,
2686
+ projectRoot: input.projectRoot,
2687
+ owner: parsed.owner,
2688
+ name: parsed.repo,
2689
+ prNumber: parsed.prNumber
2690
+ });
2691
+ const provenance = provenanceRead.value;
2692
+ const reviewCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/pulls/${parsed.prNumber}/comments`, "--paginate"], input.projectRoot);
2569
2693
  if (reviewCommentsRead.error)
2570
2694
  readErrors.push(reviewCommentsRead.error);
2571
2695
  const reviewComments = reviewCommentsRead.value.map(normalizeReviewComment).filter((entry) => !!entry);
2572
- const issueCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/issues/${parsed.prNumber}/comments`, "--paginate", "--slurp"], input.projectRoot);
2696
+ const issueCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/issues/${parsed.prNumber}/comments`, "--paginate"], input.projectRoot);
2573
2697
  if (issueCommentsRead.error)
2574
2698
  readErrors.push(issueCommentsRead.error);
2575
2699
  const issueComments = issueCommentsRead.value.map(normalizeIssueComment).filter((entry) => !!entry).filter(relevantIssueComment);
@@ -2592,12 +2716,7 @@ async function collectPrReviewEvidence(input) {
2592
2716
  repoName: parsed.repoName,
2593
2717
  headSha
2594
2718
  });
2595
- if (checkDetailsRead.error)
2596
- readErrors.push(checkDetailsRead.error);
2597
2719
  greptileCheckDetails = checkDetailsRead.value;
2598
- if (!checkDetailsRead.error && greptileCheckDetails.length === 0) {
2599
- readErrors.push("Greptile check details could not be found for the current PR head");
2600
- }
2601
2720
  }
2602
2721
  const checksWithGreptileDetails = [...statusCheckRollup, ...greptileCheckDetails];
2603
2722
  const shouldCollectConfiguredGreptileApi = input.greptileApi?.enabled !== false;
@@ -2616,6 +2735,9 @@ async function collectPrReviewEvidence(input) {
2616
2735
  const evidenceBase = {
2617
2736
  title: firstString(view, ["title"]),
2618
2737
  body: firstString(view, ["body"]),
2738
+ bodyEditorLogin: provenance.bodyEditorLogin ?? null,
2739
+ bodyLastEditedAt: provenance.bodyLastEditedAt ?? null,
2740
+ headCommittedDate: provenance.headCommittedDate ?? null,
2619
2741
  reviews,
2620
2742
  changedFileReviewComments: reviewComments,
2621
2743
  relevantIssueComments: issueComments,
@@ -2631,6 +2753,9 @@ async function collectPrReviewEvidence(input) {
2631
2753
  repoName: parsed.repoName,
2632
2754
  title: evidenceBase.title,
2633
2755
  body: evidenceBase.body,
2756
+ bodyEditorLogin: evidenceBase.bodyEditorLogin,
2757
+ bodyLastEditedAt: evidenceBase.bodyLastEditedAt,
2758
+ headCommittedDate: evidenceBase.headCommittedDate,
2634
2759
  headSha,
2635
2760
  headRefName: firstString(view, ["headRefName"]),
2636
2761
  baseRefName,