@a9n-shoji/rvw 0.3.0 → 0.3.2

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 (153) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +29 -10
  3. package/dist/cli.mjs +974 -111
  4. package/dist/cli.mjs.map +4 -4
  5. package/dist/web/assets/CommentThread-CMXtuZ98.js +33 -0
  6. package/dist/web/assets/DocumentViewer-D-UGIxCU.js +1417 -0
  7. package/dist/web/assets/MarkdownTable-7Sm_sj1E.js +7 -0
  8. package/dist/web/assets/MermaidExpandedView-CyWRss2q.js +4 -0
  9. package/dist/web/assets/MermaidSurface-Dt2_Hu6n.js +344 -0
  10. package/dist/web/assets/WalkthroughViewer-CuJMrKOW.js +43 -0
  11. package/dist/web/assets/abnfDiagram-N423BO3Z-DjhFeHQj.js +1 -0
  12. package/dist/web/assets/architecture-TIHT7OUA-CsHfPErM.js +1 -0
  13. package/dist/web/assets/architectureDiagram-T3A2C74G-4zQ-4s9L.js +36 -0
  14. package/dist/web/assets/{blockDiagram-VBNYF7ZC-BBG7PXeg.js → blockDiagram-VBNYF7ZC-Bh91Tm8T.js} +1 -1
  15. package/dist/web/assets/{c4Diagram-5PPSVZJV-oLErQeqr.js → c4Diagram-5PPSVZJV-BHgLhSdB.js} +1 -1
  16. package/dist/web/assets/ccount-DFmD81E1.js +1 -0
  17. package/dist/web/assets/channel-BmC0cAv-.js +1 -0
  18. package/dist/web/assets/{chunk-2GRJ4B5K-Bknsyt9u.js → chunk-2GRJ4B5K-sTPzFBtk.js} +1 -1
  19. package/dist/web/assets/chunk-3NCLNEKW-k0AW8Qyp.js +1 -0
  20. package/dist/web/assets/{chunk-4I5QYGJK-D-Icvgxq.js → chunk-4I5QYGJK-q_8xEFmv.js} +1 -1
  21. package/dist/web/assets/chunk-5FCAYU7R-Dh5nylU8.js +1 -0
  22. package/dist/web/assets/chunk-5HE753X5-Bw-yg4kO.js +1 -0
  23. package/dist/web/assets/chunk-5JV3BV7I-Cy5pj3QY.js +1 -0
  24. package/dist/web/assets/{chunk-5RXB4S5H-B1iIyQwd.js → chunk-5RXB4S5H-BcuYQqlM.js} +1 -1
  25. package/dist/web/assets/chunk-5TONJI2A-DQOY3q1H.js +2 -0
  26. package/dist/web/assets/{chunk-6Q2QTUOP-C09OFw2-.js → chunk-6Q2QTUOP-DDI96JMg.js} +1 -1
  27. package/dist/web/assets/{chunk-7Z6QIM7H-BqCAUqX5.js → chunk-7Z6QIM7H-B8QX_5g4.js} +1 -1
  28. package/dist/web/assets/chunk-BIQX33UG-DZEsGuSn.js +1 -0
  29. package/dist/web/assets/chunk-CQNSW5MT-B8S09OIh.js +1 -0
  30. package/dist/web/assets/chunk-CYSBUYHQ-C1Rf4art.js +1 -0
  31. package/dist/web/assets/chunk-EMLP6XTP-RLD4nXpI.js +1 -0
  32. package/dist/web/assets/{chunk-GF5L2VYU-BHy3jGGE.js → chunk-GF5L2VYU-CJin2HQO.js} +1 -1
  33. package/dist/web/assets/{chunk-I66GZJ75-BBhE3jWF.js → chunk-I66GZJ75-CEntV2nF.js} +1 -1
  34. package/dist/web/assets/chunk-J7OUQ5F2-XLdTvjUm.js +2 -0
  35. package/dist/web/assets/chunk-JG7HCLWE-BYfiSaVt.js +2 -0
  36. package/dist/web/assets/{chunk-JQJVKLGR-DNslGDt7.js → chunk-JQJVKLGR-Bnum3gum.js} +3 -3
  37. package/dist/web/assets/{chunk-KBJHAD2P-J7tWEBhK.js → chunk-KBJHAD2P-DwPs20tJ.js} +1 -1
  38. package/dist/web/assets/chunk-MOZMSUNE-XVucA-n6.js +1 -0
  39. package/dist/web/assets/{chunk-NSK5VX7P-retcSoJp.js → chunk-NSK5VX7P-AmYQxFLu.js} +1 -1
  40. package/dist/web/assets/chunk-OSBZ3O6U-CrKZOUOr.js +1 -0
  41. package/dist/web/assets/chunk-QBLGF6JB-CxAyS2mW.js +1 -0
  42. package/dist/web/assets/{chunk-QR6OTTB3-DvFlp4db.js → chunk-QR6OTTB3-VIu9ZqqS.js} +1 -1
  43. package/dist/web/assets/chunk-R7FJI6CG-Cnn3gE-Y.js +1 -0
  44. package/dist/web/assets/chunk-U6XO7XAA-BFxjP5U3.js +2 -0
  45. package/dist/web/assets/{chunk-UBXNYLIW-gyX_iwb1.js → chunk-UBXNYLIW-D4XAD8w6.js} +1 -1
  46. package/dist/web/assets/{chunk-W5SLKNZC-BGNgHuYL.js → chunk-W5SLKNZC-B8QG_JAG.js} +1 -1
  47. package/dist/web/assets/{chunk-WRU74C26-66KXN6Rn.js → chunk-WRU74C26-CS8zeNym.js} +1 -1
  48. package/dist/web/assets/chunk-YOTPTUD7-D0vRKqYp.js +1 -0
  49. package/dist/web/assets/chunk-ZIRB5QZD-O39ZBUk3.js +32 -0
  50. package/dist/web/assets/classDiagram-JCYQIIEL-DyY7Kt3N.js +1 -0
  51. package/dist/web/assets/classDiagram-v2-OCEON4UE-DyY7Kt3N.js +1 -0
  52. package/dist/web/assets/cynefin-VYW2F7L2-Ds2Fw2Y0.js +1 -0
  53. package/dist/web/assets/cynefinDiagram-MW4NZA55-DaNC4kY7.js +62 -0
  54. package/dist/web/assets/{dagre-VZM6K2ZE-BTedug-5.js → dagre-VZM6K2ZE-DJ845MyA.js} +1 -1
  55. package/dist/web/assets/{diagram-7IWD3JNH-kSc1kTCk.js → diagram-7IWD3JNH-98E00vZR.js} +3 -3
  56. package/dist/web/assets/diagram-B4RE2ZJO-BpMQ8ico.js +3 -0
  57. package/dist/web/assets/diagram-LBJQPF4R-BZ8RNQpZ.js +24 -0
  58. package/dist/web/assets/diagram-Q27KOJAE-Bn1wtc7g.js +24 -0
  59. package/dist/web/assets/diagram-UB23O5K3-B314M-wb.js +41 -0
  60. package/dist/web/assets/dist-DCGa16l5.js +145 -0
  61. package/dist/web/assets/ebnfDiagram-BXEA7PRR-CqKM-cL0.js +1 -0
  62. package/dist/web/assets/{erDiagram-JOGREHBK-jm3RTuJX.js → erDiagram-JOGREHBK-CVMcNMlx.js} +1 -1
  63. package/dist/web/assets/eventmodeling-45OFAUF4-CLEiVx5s.js +1 -0
  64. package/dist/web/assets/flowDiagram-UKHOOZJN-8dur6GAS.js +1 -0
  65. package/dist/web/assets/{ganttDiagram-PKOTCBZU--GwSY0BM.js → ganttDiagram-PKOTCBZU-DALoGLHl.js} +1 -1
  66. package/dist/web/assets/gitGraph-TEB2WS4Q-CZFBqwW-.js +1 -0
  67. package/dist/web/assets/gitGraphDiagram-DS77QQ5N-BY_jOOb-.js +106 -0
  68. package/dist/web/assets/index-BNcvGFlf.css +1 -0
  69. package/dist/web/assets/index-BwIKbLJ1.js +11 -0
  70. package/dist/web/assets/info-DKCQHKI2--1InqQeT.js +1 -0
  71. package/dist/web/assets/infoDiagram-6WML65LV-CNMbrlEJ.js +2 -0
  72. package/dist/web/assets/{ishikawaDiagram-WSZJBQD7-ErnJ_7Sr.js → ishikawaDiagram-WSZJBQD7-NODY49Zb.js} +2 -2
  73. package/dist/web/assets/{journeyDiagram-NVQOT4AX-CWvooGdp.js → journeyDiagram-NVQOT4AX-v9CJWB8Y.js} +1 -1
  74. package/dist/web/assets/{kanban-definition-27J2QSJJ-DsIS6ipP.js → kanban-definition-27J2QSJJ-Cv2iL7No.js} +3 -3
  75. package/dist/web/assets/lib-p2IERBh0.js +9 -0
  76. package/dist/web/assets/{line-DCcVjraC.js → line-Dah7RomF.js} +1 -1
  77. package/dist/web/assets/mermaid-parser.core-BcQae2Lb.js +4 -0
  78. package/dist/web/assets/mermaid.core-BVyLgPb6.js +11 -0
  79. package/dist/web/assets/{mindmap-definition-FAOFIHXS-DdsB9lmP.js → mindmap-definition-FAOFIHXS-BD9fkU-f.js} +1 -1
  80. package/dist/web/assets/packet-7NZHBO7P-BGMXB0XL.js +1 -0
  81. package/dist/web/assets/pegDiagram-VL7TDLO6-D-HL9Pvp.js +1 -0
  82. package/dist/web/assets/pie-RZYD4A2V-DgYg3tCt.js +1 -0
  83. package/dist/web/assets/pieDiagram-7S7Q4E2Y-Sam5BEo-.js +39 -0
  84. package/dist/web/assets/preload-helper-Czpn1I53.js +1 -0
  85. package/dist/web/assets/{quadrantDiagram-CIZ2JOQS-CryFsz7r.js → quadrantDiagram-CIZ2JOQS-BNXhvOJb.js} +1 -1
  86. package/dist/web/assets/radar-I7S5WNFK-Ag-zp_Rq.js +1 -0
  87. package/dist/web/assets/railroad-3IZDKUUU-aibJDIDV.js +1 -0
  88. package/dist/web/assets/railroad-abnf-AHOZXSZD-BBMEuvIl.js +1 -0
  89. package/dist/web/assets/railroad-ebnf-EBAXGLYW-Be1nseej.js +1 -0
  90. package/dist/web/assets/railroad-peg-LSFZ7HO6-DIvD-Eyb.js +1 -0
  91. package/dist/web/assets/railroadDiagram-AXF67PYL-C6-GuR01.js +1 -0
  92. package/dist/web/assets/{requirementDiagram-LRYGKXZP-D2-9KlK1.js → requirementDiagram-LRYGKXZP-Dew1VZVc.js} +1 -1
  93. package/dist/web/assets/{sankeyDiagram-W5VNT64P-DSOFAzno.js → sankeyDiagram-W5VNT64P-BNlHXupx.js} +1 -1
  94. package/dist/web/assets/{sequenceDiagram-SI44F4Z6-BHEKSYJt.js → sequenceDiagram-SI44F4Z6-CHZhbxWH.js} +1 -1
  95. package/dist/web/assets/stateDiagram-OKZ733FA-BP7TOkRY.js +1 -0
  96. package/dist/web/assets/stateDiagram-v2-UEYNNEHI-BoFgs-SF.js +1 -0
  97. package/dist/web/assets/swimlanes-SLNWSIFB-DfMga4nB.js +2 -0
  98. package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-CpfunQZj.js +8 -0
  99. package/dist/web/assets/{timeline-definition-Z64GVDOM-C7eeBnPC.js → timeline-definition-Z64GVDOM-DjC-PWK6.js} +1 -1
  100. package/dist/web/assets/treeView-QDETBFTQ-uHZ-3Fz8.js +1 -0
  101. package/dist/web/assets/treemap-6X3UGDF4-BnUKyy3l.js +1 -0
  102. package/dist/web/assets/{vennDiagram-T6HMQDX7-DTIq1ec9.js → vennDiagram-T6HMQDX7-CJzy1Vav.js} +2 -2
  103. package/dist/web/assets/wardley-OPB4EBWU-Da7TLC2F.js +1 -0
  104. package/dist/web/assets/{wardleyDiagram-T6FBY63Y-BmzhEoo_.js → wardleyDiagram-T6FBY63Y-C9BlVo6M.js} +3 -3
  105. package/dist/web/assets/{xychartDiagram-ELKLHX3M-CcCGC0u-.js → xychartDiagram-ELKLHX3M-B0WUxsYy.js} +3 -3
  106. package/dist/web/index.html +6 -2
  107. package/package.json +1 -1
  108. package/skills/rvw-walkthrough/SKILL.md +4 -4
  109. package/skills/rvw-walkthrough/agents/openai.yaml +1 -1
  110. package/skills/rvw-walkthrough/references/walkthrough-authoring.md +11 -0
  111. package/dist/web/assets/DocumentViewer-DtusMl0r.js +0 -1561
  112. package/dist/web/assets/MarkdownTable-BjwiOHQe.js +0 -15
  113. package/dist/web/assets/WalkthroughViewer-BNwKsKzk.js +0 -42
  114. package/dist/web/assets/abnfDiagram-N423BO3Z-DtH8B7Z7.js +0 -1
  115. package/dist/web/assets/architecture-TIHT7OUA-P_K7H_mW.js +0 -1
  116. package/dist/web/assets/architectureDiagram-T3A2C74G-5Fq3Xylv.js +0 -36
  117. package/dist/web/assets/channel-wXu0iczQ.js +0 -1
  118. package/dist/web/assets/classDiagram-JCYQIIEL-DOHE4q2M.js +0 -1
  119. package/dist/web/assets/classDiagram-v2-OCEON4UE-DOHE4q2M.js +0 -1
  120. package/dist/web/assets/cynefin-VYW2F7L2-Dj_jqvQa.js +0 -1
  121. package/dist/web/assets/cynefinDiagram-MW4NZA55-DSnUS3JS.js +0 -62
  122. package/dist/web/assets/diagram-B4RE2ZJO-DNtXohxd.js +0 -3
  123. package/dist/web/assets/diagram-LBJQPF4R-CVD-8DKW.js +0 -24
  124. package/dist/web/assets/diagram-Q27KOJAE-DBEnUsLJ.js +0 -24
  125. package/dist/web/assets/diagram-UB23O5K3-DiPHfrk7.js +0 -41
  126. package/dist/web/assets/ebnfDiagram-BXEA7PRR-CE06v37i.js +0 -1
  127. package/dist/web/assets/eventmodeling-45OFAUF4-C344zcDl.js +0 -1
  128. package/dist/web/assets/flowDiagram-UKHOOZJN-BAHsb6fm.js +0 -1
  129. package/dist/web/assets/gitGraph-TEB2WS4Q-C6oA29oA.js +0 -1
  130. package/dist/web/assets/gitGraphDiagram-DS77QQ5N-DR1mGQ6a.js +0 -106
  131. package/dist/web/assets/index-BiPhui1B.js +0 -384
  132. package/dist/web/assets/index-CHlqmhif.css +0 -1
  133. package/dist/web/assets/info-DKCQHKI2-BT4oYSnk.js +0 -1
  134. package/dist/web/assets/infoDiagram-6WML65LV-D0mnXRu0.js +0 -2
  135. package/dist/web/assets/mermaid-parser.core-BmDn4VB6.js +0 -7
  136. package/dist/web/assets/mermaid.core-Cus_gjGs.js +0 -42
  137. package/dist/web/assets/packet-7NZHBO7P-BXuujfeh.js +0 -1
  138. package/dist/web/assets/pegDiagram-VL7TDLO6-CBF6Uic6.js +0 -1
  139. package/dist/web/assets/pie-RZYD4A2V-DgssvIaB.js +0 -1
  140. package/dist/web/assets/pieDiagram-7S7Q4E2Y-DejhR8Wh.js +0 -39
  141. package/dist/web/assets/radar-I7S5WNFK-CoiIzjHC.js +0 -1
  142. package/dist/web/assets/railroad-3IZDKUUU-CWq6IkAh.js +0 -1
  143. package/dist/web/assets/railroad-abnf-AHOZXSZD-D7Iqnp88.js +0 -1
  144. package/dist/web/assets/railroad-ebnf-EBAXGLYW-Dp2g2TtT.js +0 -1
  145. package/dist/web/assets/railroad-peg-LSFZ7HO6-DTqKNJKF.js +0 -1
  146. package/dist/web/assets/railroadDiagram-AXF67PYL-DkePHtcR.js +0 -1
  147. package/dist/web/assets/stateDiagram-OKZ733FA-f1GttDrB.js +0 -1
  148. package/dist/web/assets/stateDiagram-v2-UEYNNEHI-CEn2YfQh.js +0 -1
  149. package/dist/web/assets/swimlanes-SLNWSIFB-Dx7QlDaA.js +0 -2
  150. package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-CX2pXGLK.js +0 -8
  151. package/dist/web/assets/treeView-QDETBFTQ-BPsqqMsK.js +0 -1
  152. package/dist/web/assets/treemap-6X3UGDF4-CmBU76N7.js +0 -1
  153. package/dist/web/assets/wardley-OPB4EBWU-DLOnGzPl.js +0 -1
package/dist/cli.mjs CHANGED
@@ -7,6 +7,7 @@ var __export = (target, all4) => {
7
7
 
8
8
  // src/cli/main.ts
9
9
  import { fork } from "node:child_process";
10
+ import { randomUUID as randomUUID6 } from "node:crypto";
10
11
  import { existsSync as existsSync6 } from "node:fs";
11
12
  import path10 from "node:path";
12
13
  import { fileURLToPath as fileURLToPath5, pathToFileURL } from "node:url";
@@ -19093,6 +19094,11 @@ var RvwDatabase = class {
19093
19094
  listPullRequests() {
19094
19095
  return this.database.prepare("SELECT * FROM pull_requests ORDER BY updated_at DESC").all().map(mapPullRequest);
19095
19096
  }
19097
+ listPullRequestsNeedingStatusRefresh() {
19098
+ return this.database.prepare(
19099
+ "SELECT * FROM pull_requests WHERE github_state = 'OPEN' OR github_state IS NULL ORDER BY updated_at DESC"
19100
+ ).all().map(mapPullRequest);
19101
+ }
19096
19102
  listPullRequestSummaries(offset, limit, hideClosedOrMerged = true) {
19097
19103
  const hideClosedOrMergedValue = hideClosedOrMerged ? 1 : 0;
19098
19104
  const rows = this.database.prepare(
@@ -19165,6 +19171,25 @@ var RvwDatabase = class {
19165
19171
  total: numberValue(totalRow, "total")
19166
19172
  };
19167
19173
  }
19174
+ updatePullRequestGitHubStatuses(updates) {
19175
+ if (updates.length === 0) return;
19176
+ this.immediateTransaction(() => {
19177
+ const statement = this.database.prepare(
19178
+ "UPDATE pull_requests SET github_state = ?, github_is_draft = ? WHERE id = ?"
19179
+ );
19180
+ for (const update of updates) {
19181
+ const result = statement.run(update.state, update.isDraft ? 1 : 0, update.pullRequestId);
19182
+ if (Number(result.changes) !== 1) {
19183
+ throw new RvwError(
19184
+ "PR_NOT_FOUND",
19185
+ `Pull Request\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${update.pullRequestId}`,
19186
+ { status: 404 }
19187
+ );
19188
+ }
19189
+ }
19190
+ this.incrementChangeSequence();
19191
+ });
19192
+ }
19168
19193
  updateRepositoryLocation(id, repository) {
19169
19194
  this.immediateTransaction(() => {
19170
19195
  const result = this.database.prepare(
@@ -19870,9 +19895,11 @@ import path5 from "node:path";
19870
19895
  import { TextDecoder as TextDecoder2 } from "node:util";
19871
19896
 
19872
19897
  // src/shared/constants.ts
19873
- var APP_VERSION = "0.3.0";
19898
+ var APP_VERSION = "0.3.2";
19874
19899
  var PROTOCOL_VERSION = 4;
19875
19900
  var VIEWER_ID_HEADER = "x-rvw-viewer-id";
19901
+ var VIEWER_OPEN_LEASE_HEADER = "x-rvw-viewer-open-lease";
19902
+ var VIEWER_OPEN_LEASE_QUERY = "viewerLease";
19876
19903
  var GIT_OBJECT_ID_PATTERN = /^[0-9a-f]{40,64}$/i;
19877
19904
  var MAX_TEXT_DOCUMENT_BYTES = 1024 * 1024;
19878
19905
  var MAX_MARKDOWN_ASSET_BYTES = 5 * 1024 * 1024;
@@ -20369,6 +20396,10 @@ var GitClient = class {
20369
20396
  );
20370
20397
  return parseCommitLog(output);
20371
20398
  }
20399
+ async firstParent(cwd, oid) {
20400
+ const parents = await runText("git", ["show", "-s", "--format=%P", oid], { cwd });
20401
+ return parents.split(" ").filter(Boolean)[0] ?? null;
20402
+ }
20372
20403
  async tree(cwd, oid) {
20373
20404
  const result = await runProcess("git", ["ls-tree", "-r", "-z", "--long", oid], { cwd });
20374
20405
  return parseLsTree(result.stdout);
@@ -20381,6 +20412,23 @@ var GitClient = class {
20381
20412
  );
20382
20413
  return parseNameStatus(result.stdout);
20383
20414
  }
20415
+ async changedFilesWithCopies(cwd, oldOid, newOid) {
20416
+ const result = await runProcess(
20417
+ "git",
20418
+ [
20419
+ "diff",
20420
+ "--name-status",
20421
+ "-z",
20422
+ "--find-renames",
20423
+ "--find-copies",
20424
+ "--find-copies-harder",
20425
+ oldOid,
20426
+ newOid
20427
+ ],
20428
+ { cwd }
20429
+ );
20430
+ return parseNameStatus(result.stdout);
20431
+ }
20384
20432
  async readDocument(cwd, sourceOid, filePath) {
20385
20433
  assertGitPath(filePath);
20386
20434
  const result = await runProcess("git", ["ls-tree", "-z", "--long", sourceOid, "--", filePath], {
@@ -20643,6 +20691,10 @@ var ghPullRequestSchema = external_exports.object({
20643
20691
  headRefOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN),
20644
20692
  createdAt: external_exports.string()
20645
20693
  });
20694
+ var ghPullRequestStatusSchema = external_exports.object({
20695
+ state: external_exports.enum(["OPEN", "CLOSED", "MERGED"]),
20696
+ isDraft: external_exports.boolean()
20697
+ });
20646
20698
  function parsePullRequestUrl(url2) {
20647
20699
  const match2 = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)(?:\/.*)?$/.exec(url2);
20648
20700
  if (!match2?.[1] || !match2[2] || !match2[3]) {
@@ -20663,7 +20715,7 @@ var GitHubClient = class {
20663
20715
  return { version: version2.split("\n")[0] ?? version2, authenticated: auth.exitCode === 0 };
20664
20716
  }
20665
20717
  async assertAuthenticated() {
20666
- const status = await runProcess("gh", ["auth", "status", "--hostname", "github.com"], {
20718
+ const status = await this.processRunner("gh", ["auth", "status", "--hostname", "github.com"], {
20667
20719
  allowExitCodes: [1]
20668
20720
  });
20669
20721
  if (status.exitCode !== 0) {
@@ -20672,6 +20724,77 @@ var GitHubClient = class {
20672
20724
  });
20673
20725
  }
20674
20726
  }
20727
+ async getPullRequestStatus(reference) {
20728
+ let output;
20729
+ try {
20730
+ const result = await this.processRunner(
20731
+ "gh",
20732
+ ["pr", "view", reference, "--json", "state,isDraft"],
20733
+ { timeoutMs: 6e4 }
20734
+ );
20735
+ output = result.stdout.toString("utf8").trimEnd();
20736
+ } catch (error51) {
20737
+ if (error51 instanceof RvwError && error51.code === "PROCESS_FAILED") {
20738
+ throw new RvwError("GITHUB_ERROR", "Pull Request\u72B6\u614B\u3092GitHub\u304B\u3089\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002", {
20739
+ cause: error51,
20740
+ details: error51.details,
20741
+ suggestions: ["PR URL\u3068gh\u8A8D\u8A3C\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"]
20742
+ });
20743
+ }
20744
+ throw error51;
20745
+ }
20746
+ let decoded;
20747
+ try {
20748
+ decoded = JSON.parse(output);
20749
+ } catch (error51) {
20750
+ throw new RvwError("GITHUB_ERROR", "GitHub CLI\u306EPull Request\u72B6\u614B\u5FDC\u7B54\u304C\u4E0D\u6B63\u3067\u3059\u3002", {
20751
+ cause: error51
20752
+ });
20753
+ }
20754
+ const parsed = ghPullRequestStatusSchema.safeParse(decoded);
20755
+ if (!parsed.success) {
20756
+ throw new RvwError("GITHUB_ERROR", "GitHub CLI\u306EPull Request\u72B6\u614B\u5FDC\u7B54\u304C\u4E0D\u6B63\u3067\u3059\u3002", {
20757
+ details: parsed.error.flatten()
20758
+ });
20759
+ }
20760
+ return parsed.data;
20761
+ }
20762
+ async getPullRequestStatuses(references) {
20763
+ if (references.length === 0) return [];
20764
+ await this.assertAuthenticated();
20765
+ const outcomes = Array.from(
20766
+ { length: references.length },
20767
+ () => void 0
20768
+ );
20769
+ let nextIndex = 0;
20770
+ const workerCount = Math.min(4, references.length);
20771
+ await Promise.all(
20772
+ Array.from({ length: workerCount }, async () => {
20773
+ while (nextIndex < references.length) {
20774
+ const index2 = nextIndex;
20775
+ nextIndex += 1;
20776
+ const reference = references[index2];
20777
+ if (!reference) continue;
20778
+ try {
20779
+ outcomes[index2] = {
20780
+ status: "fulfilled",
20781
+ value: await this.getPullRequestStatus(reference)
20782
+ };
20783
+ } catch (error51) {
20784
+ outcomes[index2] = { status: "rejected", error: error51 };
20785
+ }
20786
+ }
20787
+ })
20788
+ );
20789
+ return outcomes.map((outcome) => {
20790
+ if (!outcome) {
20791
+ throw new RvwError("INTERNAL_ERROR", "Pull Request\u72B6\u614B\u306E\u4E00\u62EC\u53D6\u5F97\u7D50\u679C\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059\u3002", {
20792
+ status: 500
20793
+ });
20794
+ }
20795
+ return outcome;
20796
+ });
20797
+ }
20675
20798
  async getPullRequest(reference, cwd, options = {}) {
20676
20799
  await this.assertAuthenticated();
20677
20800
  const fields = [
@@ -27969,11 +28092,22 @@ function selectedLineText(value, startLine, endLine) {
27969
28092
  function lines(text5) {
27970
28093
  return normalizeLf(text5).split("\n");
27971
28094
  }
28095
+ function countLineRangeOccurrences(documentLines, selected) {
28096
+ let occurrences = 0;
28097
+ for (let index2 = 0; index2 <= documentLines.length - selected.length; index2 += 1) {
28098
+ if (selected.every((line, offset) => documentLines[index2 + offset] === line)) {
28099
+ occurrences += 1;
28100
+ }
28101
+ }
28102
+ return occurrences;
28103
+ }
27972
28104
  function mapUnchangedLineRange(sourceText, destinationText, startLine, endLine) {
27973
28105
  if (startLine < 1 || endLine < startLine) return null;
27974
28106
  const sourceLines = lines(sourceText);
27975
28107
  if (endLine > sourceLines.length) return null;
27976
28108
  if (sourceText === destinationText) return { startLine, endLine };
28109
+ const selected = sourceLines.slice(startLine - 1, endLine);
28110
+ if (countLineRangeOccurrences(sourceLines, selected) !== 1) return null;
27977
28111
  const mapping = /* @__PURE__ */ new Map();
27978
28112
  let sourceIndex = 1;
27979
28113
  let destinationIndex = 1;
@@ -27999,14 +28133,8 @@ function mapUnchangedLineRange(sourceText, destinationText, startLine, endLine)
27999
28133
  for (let line = startLine; line <= endLine; line += 1) {
28000
28134
  if (mapping.get(line) !== mappedStart + line - startLine) return null;
28001
28135
  }
28002
- const selected = sourceLines.slice(startLine - 1, endLine);
28003
- let occurrences = 0;
28004
28136
  const destinationLines = lines(destinationText);
28005
- for (let index2 = 0; index2 <= destinationLines.length - selected.length; index2 += 1) {
28006
- if (selected.every((line, offset) => destinationLines[index2 + offset] === line))
28007
- occurrences += 1;
28008
- if (occurrences > 1) return null;
28009
- }
28137
+ if (countLineRangeOccurrences(destinationLines, selected) !== 1) return null;
28010
28138
  return { startLine: mappedStart, endLine: mappedEnd };
28011
28139
  }
28012
28140
  function findUniqueQuotedLineRange(quotedText, destinationText) {
@@ -28034,6 +28162,11 @@ function placeMutableDocumentComment(anchor, currentText) {
28034
28162
  return range ? { outdated: false, range } : { outdated: true, range: null };
28035
28163
  }
28036
28164
 
28165
+ // src/domain/walkthrough-reference.ts
28166
+ function walkthroughReferenceFingerprint(sourceOid, reference) {
28167
+ return JSON.stringify([sourceOid, reference.path, reference.startLine, reference.endLine]);
28168
+ }
28169
+
28037
28170
  // src/domain/source-excerpt.ts
28038
28171
  var SOURCE_EXCERPT_CONTEXT_LINES = 20;
28039
28172
  var MAX_SOURCE_EXCERPT_LINES = 200;
@@ -41003,7 +41136,8 @@ var codeReferenceIdPattern = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
41003
41136
  var walkthroughDiagramNodePattern = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
41004
41137
  var mermaidIdentifierPattern = /[A-Za-z][A-Za-z0-9_-]{0,63}/g;
41005
41138
  var mermaidEdgePattern = /[<|o*x]*[-.=~]{2,}[|o*x>]*/g;
41006
- var mermaidKeywords = /* @__PURE__ */ new Set([
41139
+ var mermaidClassShorthandPattern = /:::[A-Za-z][A-Za-z0-9_-]*(?:,[A-Za-z][A-Za-z0-9_-]*)*/g;
41140
+ var flowchartAndClassKeywords = /* @__PURE__ */ new Set([
41007
41141
  "class",
41008
41142
  "classDef",
41009
41143
  "classDiagram",
@@ -41015,12 +41149,19 @@ var mermaidKeywords = /* @__PURE__ */ new Set([
41015
41149
  "style",
41016
41150
  "subgraph"
41017
41151
  ]);
41018
- function mermaidIdentifiers(value) {
41019
- return [...value.matchAll(mermaidIdentifierPattern)].map(([identifier]) => identifier).filter((identifier) => !mermaidKeywords.has(identifier));
41152
+ var noMermaidKeywords = /* @__PURE__ */ new Set();
41153
+ function mermaidIdentifiers(value, excludedKeywords) {
41154
+ return [...value.matchAll(mermaidIdentifierPattern)].map(([identifier]) => identifier).filter((identifier) => !excludedKeywords.has(identifier));
41020
41155
  }
41021
- function mermaidLineWithoutLabels(value) {
41156
+ function mermaidLineWithoutComment(value) {
41022
41157
  const commentIndex = value.indexOf("%%");
41023
- const uncommented = commentIndex === -1 ? value : value.slice(0, commentIndex);
41158
+ return commentIndex === -1 ? value : value.slice(0, commentIndex);
41159
+ }
41160
+ function mermaidLineWithoutClassShorthand(value) {
41161
+ return value.replace(mermaidClassShorthandPattern, "");
41162
+ }
41163
+ function mermaidLineWithoutLabels(value) {
41164
+ const uncommented = mermaidLineWithoutComment(value);
41024
41165
  const withoutQuotedLabels = uncommented.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").replace(/\|[^|]*\|/g, " ").replace(/--\s+[^-\n]+\s+-->/g, " -->").replace(/-\.\s+[^.\n]+\s+\.->/g, " -.->").replace(/==\s+[^=\n]+\s+==>/g, " ==>");
41025
41166
  let result = "";
41026
41167
  const closingDelimiters = [];
@@ -41045,33 +41186,105 @@ function mermaidLineWithoutLabels(value) {
41045
41186
  }
41046
41187
  return result;
41047
41188
  }
41048
- function addMermaidEdgeEndpoints(line, nodeIds) {
41049
- const edges = [...line.matchAll(mermaidEdgePattern)];
41189
+ function addMermaidEdgeEndpoints(line, nodeIds, excludedKeywords) {
41190
+ const endpointLine = mermaidLineWithoutClassShorthand(line);
41191
+ const edges = [...endpointLine.matchAll(mermaidEdgePattern)];
41050
41192
  for (let index2 = 0; index2 < edges.length; index2 += 1) {
41051
41193
  const edge = edges[index2];
41052
41194
  const edgeStart = edge.index;
41053
41195
  const edgeEnd = edgeStart + edge[0].length;
41054
41196
  const leftStart = index2 === 0 ? 0 : edges[index2 - 1].index + edges[index2 - 1][0].length;
41055
- const rightEnd = index2 + 1 === edges.length ? line.length : edges[index2 + 1].index;
41056
- const left = line.slice(leftStart, edgeStart);
41057
- const right = line.slice(edgeEnd, rightEnd);
41058
- const leftIdentifiers = mermaidIdentifiers(left);
41059
- const rightIdentifiers = mermaidIdentifiers(right);
41197
+ const rightEnd = index2 + 1 === edges.length ? endpointLine.length : edges[index2 + 1].index;
41198
+ const left = endpointLine.slice(leftStart, edgeStart);
41199
+ const right = endpointLine.slice(edgeEnd, rightEnd);
41200
+ const leftIdentifiers = mermaidIdentifiers(left, excludedKeywords);
41201
+ const rightIdentifiers = mermaidIdentifiers(right, excludedKeywords);
41060
41202
  const leftEndpoints = left.includes("&") ? leftIdentifiers : leftIdentifiers.slice(-1);
41061
41203
  const rightEndpoints = right.includes("&") ? rightIdentifiers : rightIdentifiers.slice(0, 1);
41062
41204
  for (const identifier of [...leftEndpoints, ...rightEndpoints]) nodeIds.add(identifier);
41063
41205
  }
41064
41206
  }
41207
+ function addSequenceMessageParticipants(line, nodeIds) {
41208
+ const message = line.match(
41209
+ /^([A-Za-z][A-Za-z0-9_-]{0,63})\s*<*[-=]+[)>x]+\s*[+-]?([A-Za-z][A-Za-z0-9_-]{0,63})\b/
41210
+ );
41211
+ if (!message) return;
41212
+ nodeIds.add(message[1]);
41213
+ nodeIds.add(message[2]);
41214
+ }
41215
+ var erCardinalitySource = [
41216
+ "one\\s+or\\s+zero",
41217
+ "one\\s+or\\s+(?:more|many)",
41218
+ "zero\\s+or\\s+(?:one|more|many)",
41219
+ "only\\s+one",
41220
+ "many\\([01]\\)",
41221
+ "[01]\\+",
41222
+ "many",
41223
+ "one",
41224
+ "\\d+(?:\\.\\d+)?"
41225
+ ].join("|");
41226
+ var erTextualRelationshipPattern = new RegExp(
41227
+ `^([A-Za-z][A-Za-z0-9_-]{0,63})\\s+(?:${erCardinalitySource})\\s+(?:to|optionally\\s+to)\\s+(?:${erCardinalitySource})\\s+([A-Za-z][A-Za-z0-9_-]{0,63})\\b`,
41228
+ "i"
41229
+ );
41230
+ function addErRelationshipEntities(line, nodeIds) {
41231
+ const relationship = line.match(
41232
+ /^([A-Za-z][A-Za-z0-9_-]{0,63})\s+[|o}{]+[.-]{2}[|o}{]+\s+([A-Za-z][A-Za-z0-9_-]{0,63})\b/
41233
+ ) ?? line.match(erTextualRelationshipPattern);
41234
+ if (!relationship) return;
41235
+ nodeIds.add(relationship[1]);
41236
+ nodeIds.add(relationship[2]);
41237
+ }
41065
41238
  function addMermaidDiagramNodes(source, nodeIds) {
41066
41239
  const lines2 = source.split("\n");
41067
41240
  const header = lines2.map((line) => mermaidLineWithoutLabels(line).trim()).find((line) => line.length > 0);
41068
- const diagramType = header?.match(/^(flowchart|graph|classDiagram)\b/)?.[1];
41241
+ const diagramType = header?.match(
41242
+ /^(flowchart|graph|classDiagram|sequenceDiagram|stateDiagram-v2|erDiagram|architecture-beta)\b/
41243
+ )?.[1];
41069
41244
  if (!diagramType) return;
41070
41245
  for (const rawLine of lines2) {
41071
- for (const statement of mermaidLineWithoutLabels(rawLine).split(";")) {
41246
+ const diagramLine = diagramType === "erDiagram" || diagramType === "sequenceDiagram" ? mermaidLineWithoutComment(rawLine) : mermaidLineWithoutLabels(rawLine);
41247
+ for (const statement of diagramLine.split(";")) {
41072
41248
  const line = statement.trim();
41073
- if (!line || /^(flowchart|graph|classDiagram)\b/.test(line)) continue;
41074
- addMermaidEdgeEndpoints(line, nodeIds);
41249
+ if (!line || /^(flowchart|graph|classDiagram|sequenceDiagram|stateDiagram-v2|erDiagram|architecture-beta)\b/.test(
41250
+ line
41251
+ )) {
41252
+ continue;
41253
+ }
41254
+ if (diagramType === "sequenceDiagram") {
41255
+ const participant = line.match(
41256
+ /^(?:participant|actor)\s+([A-Za-z][A-Za-z0-9_-]{0,63})(?:\s*@\s*\{|\s+as\b|\s*$)/
41257
+ )?.[1];
41258
+ if (participant) nodeIds.add(participant);
41259
+ else addSequenceMessageParticipants(line, nodeIds);
41260
+ continue;
41261
+ }
41262
+ if (diagramType === "architecture-beta") {
41263
+ const service = line.match(/^service\s+([A-Za-z][A-Za-z0-9_-]{0,63})(?:\s|\(|$)/)?.[1];
41264
+ if (service) nodeIds.add(service);
41265
+ continue;
41266
+ }
41267
+ if (diagramType === "stateDiagram-v2") {
41268
+ addMermaidEdgeEndpoints(line, nodeIds, noMermaidKeywords);
41269
+ const alias = line.match(/^state\s+.+?\s+as\s+([A-Za-z][A-Za-z0-9_-]{0,63})\b/)?.[1];
41270
+ const declaration = alias ? void 0 : line.match(/^state\s+([A-Za-z][A-Za-z0-9_-]{0,63})(?:\s|\{|$)/)?.[1];
41271
+ const describedState = line.match(/^([A-Za-z][A-Za-z0-9_-]{0,63})\s*:/)?.[1];
41272
+ const standaloneState = line.match(/^([A-Za-z][A-Za-z0-9_-]{0,63})$/)?.[1];
41273
+ for (const identifier of [alias, declaration, describedState, standaloneState]) {
41274
+ if (identifier) nodeIds.add(identifier);
41275
+ }
41276
+ continue;
41277
+ }
41278
+ if (diagramType === "erDiagram") {
41279
+ const entityLine = mermaidLineWithoutClassShorthand(line);
41280
+ addErRelationshipEntities(entityLine, nodeIds);
41281
+ const entity = entityLine.match(
41282
+ /^([A-Za-z][A-Za-z0-9_-]{0,63})(?:\s*\[[^\]\n]+\])?\s*(?:\{|$)/
41283
+ )?.[1];
41284
+ if (entity) nodeIds.add(entity);
41285
+ continue;
41286
+ }
41287
+ addMermaidEdgeEndpoints(line, nodeIds, flowchartAndClassKeywords);
41075
41288
  if (diagramType === "classDiagram") {
41076
41289
  const declaration = line.match(/^class\s+([A-Za-z][A-Za-z0-9_-]{0,63})\b/)?.[1];
41077
41290
  const memberOwner = line.match(/^([A-Za-z][A-Za-z0-9_-]{0,63})\s*:/)?.[1];
@@ -41282,6 +41495,42 @@ var RvwService = class {
41282
41495
  }
41283
41496
  };
41284
41497
  }
41498
+ async refreshPullRequestStatuses() {
41499
+ const pullRequests = this.database.listPullRequestsNeedingStatusRefresh();
41500
+ if (pullRequests.length === 0) {
41501
+ return { attempted: 0, updated: 0, failures: [] };
41502
+ }
41503
+ const outcomes = await this.github.getPullRequestStatuses(
41504
+ pullRequests.map((pullRequest) => pullRequest.url)
41505
+ );
41506
+ if (outcomes.length !== pullRequests.length) {
41507
+ throw new RvwError("GITHUB_ERROR", "Pull Request\u72B6\u614B\u306E\u4E00\u62EC\u53D6\u5F97\u4EF6\u6570\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002", {
41508
+ status: 502
41509
+ });
41510
+ }
41511
+ const successful = outcomes.flatMap((outcome, index2) => {
41512
+ const pullRequest = pullRequests[index2];
41513
+ return outcome.status === "fulfilled" && pullRequest ? [{ pullRequestId: pullRequest.id, ...outcome.value }] : [];
41514
+ });
41515
+ this.database.updatePullRequestGitHubStatuses(successful);
41516
+ const failures = outcomes.flatMap((outcome, index2) => {
41517
+ const pullRequest = pullRequests[index2];
41518
+ return outcome.status === "rejected" && pullRequest ? [
41519
+ {
41520
+ pullRequestId: pullRequest.id,
41521
+ owner: pullRequest.owner,
41522
+ repository: pullRequest.repository,
41523
+ number: pullRequest.number,
41524
+ error: asRvwError(outcome.error).toJSON()
41525
+ }
41526
+ ] : [];
41527
+ });
41528
+ return {
41529
+ attempted: pullRequests.length,
41530
+ updated: successful.length,
41531
+ failures
41532
+ };
41533
+ }
41285
41534
  resolveStoredPullRequest(reference) {
41286
41535
  if (/^https:\/\/github\.com\//.test(reference)) {
41287
41536
  const parsed = parsePullRequestUrl(reference);
@@ -41953,6 +42202,133 @@ var RvwService = class {
41953
42202
  }
41954
42203
  return walkthrough;
41955
42204
  }
42205
+ async walkthroughReferenceFallbackTarget(pullRequest, sourceOid, filePath) {
42206
+ const diffBaseOid = await this.git.firstParent(pullRequest.localRepositoryPath, sourceOid);
42207
+ if (!diffBaseOid) {
42208
+ return {
42209
+ sourceOid,
42210
+ path: filePath,
42211
+ diffBaseOid: null,
42212
+ oldPath: filePath,
42213
+ newPath: filePath,
42214
+ hasDiff: false
42215
+ };
42216
+ }
42217
+ const change = (await this.git.changedFiles(pullRequest.localRepositoryPath, diffBaseOid, sourceOid)).find((candidate) => candidate.newPath === filePath);
42218
+ return {
42219
+ sourceOid,
42220
+ path: filePath,
42221
+ diffBaseOid,
42222
+ oldPath: change?.oldPath ?? filePath,
42223
+ newPath: change?.newPath ?? filePath,
42224
+ hasDiff: change !== void 0
42225
+ };
42226
+ }
42227
+ async resolveWalkthroughReference(pullRequestId, walkthroughId, referenceId) {
42228
+ const pullRequest = this.getPullRequest(pullRequestId);
42229
+ const walkthrough = this.getWalkthrough(pullRequestId, walkthroughId);
42230
+ const reference = walkthrough.references.find((candidate) => candidate.id === referenceId);
42231
+ if (!reference) {
42232
+ throw new RvwError("NOT_FOUND", "Walkthrough\u306Ecode reference\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002", {
42233
+ status: 404
42234
+ });
42235
+ }
42236
+ const referenceFingerprint = walkthroughReferenceFingerprint(walkthrough.sourceOid, reference);
42237
+ const sourceDocument = await this.getDocument({
42238
+ kind: "repository-file",
42239
+ pullRequestId,
42240
+ sourceOid: walkthrough.sourceOid,
42241
+ path: reference.path
42242
+ });
42243
+ const latestHeadOid = pullRequest.latestHeadOid;
42244
+ let latestPath = reference.path;
42245
+ let latestDocument;
42246
+ if (walkthrough.sourceOid === latestHeadOid) {
42247
+ latestDocument = sourceDocument;
42248
+ } else {
42249
+ latestDocument = await this.getDocument({
42250
+ kind: "repository-file",
42251
+ pullRequestId,
42252
+ sourceOid: latestHeadOid,
42253
+ path: reference.path
42254
+ });
42255
+ if (latestDocument.availability === "missing") {
42256
+ const successorPaths = [
42257
+ ...new Set(
42258
+ (await this.git.changedFilesWithCopies(
42259
+ pullRequest.localRepositoryPath,
42260
+ walkthrough.sourceOid,
42261
+ latestHeadOid
42262
+ )).filter(
42263
+ (candidate) => (candidate.status.startsWith("R") || candidate.status.startsWith("C")) && candidate.oldPath === reference.path && candidate.newPath !== null
42264
+ ).map((candidate) => candidate.newPath)
42265
+ )
42266
+ ];
42267
+ latestPath = successorPaths.length === 1 ? successorPaths[0] : null;
42268
+ latestDocument = latestPath === null ? null : await this.getDocument({
42269
+ kind: "repository-file",
42270
+ pullRequestId,
42271
+ sourceOid: latestHeadOid,
42272
+ path: latestPath
42273
+ });
42274
+ }
42275
+ }
42276
+ const latestFileExists = latestDocument !== null && latestDocument.availability !== "missing";
42277
+ const latestFileDisplayable = latestDocument?.availability === "available";
42278
+ const mappedRange = reference.startLine === null || reference.endLine === null ? latestFileDisplayable ? null : void 0 : sourceDocument.availability === "available" && latestDocument?.availability === "available" ? mapUnchangedLineRange(
42279
+ sourceDocument.text ?? "",
42280
+ latestDocument.text ?? "",
42281
+ reference.startLine,
42282
+ reference.endLine
42283
+ ) : null;
42284
+ const resolvedToLatest = latestFileDisplayable && (reference.startLine === null || reference.endLine === null || mappedRange !== null);
42285
+ if (resolvedToLatest && latestPath !== null && latestDocument !== null) {
42286
+ return {
42287
+ outcome: "latest",
42288
+ anchorSourceOid: walkthrough.sourceOid,
42289
+ latestHeadOid,
42290
+ referenceFingerprint,
42291
+ target: {
42292
+ sourceOid: latestHeadOid,
42293
+ path: latestPath,
42294
+ diffBaseOid: null,
42295
+ oldPath: latestPath,
42296
+ newPath: latestPath,
42297
+ hasDiff: false,
42298
+ startLine: mappedRange?.startLine ?? reference.startLine,
42299
+ endLine: mappedRange?.endLine ?? reference.endLine
42300
+ },
42301
+ latestFile: null,
42302
+ document: latestDocument
42303
+ };
42304
+ }
42305
+ const targetFile = await this.walkthroughReferenceFallbackTarget(
42306
+ pullRequest,
42307
+ walkthrough.sourceOid,
42308
+ reference.path
42309
+ );
42310
+ const latestFile = latestFileExists && latestPath !== null ? {
42311
+ sourceOid: latestHeadOid,
42312
+ path: latestPath,
42313
+ diffBaseOid: null,
42314
+ oldPath: latestPath,
42315
+ newPath: latestPath,
42316
+ hasDiff: false
42317
+ } : null;
42318
+ return {
42319
+ outcome: "source-fallback",
42320
+ anchorSourceOid: walkthrough.sourceOid,
42321
+ latestHeadOid,
42322
+ referenceFingerprint,
42323
+ target: {
42324
+ ...targetFile,
42325
+ startLine: reference.startLine,
42326
+ endLine: reference.endLine
42327
+ },
42328
+ latestFile,
42329
+ document: sourceDocument
42330
+ };
42331
+ }
41956
42332
  getWalkthroughByUri(uri) {
41957
42333
  const walkthrough = this.database.getWalkthrough(parseWalkthroughUri(uri));
41958
42334
  if (!walkthrough) {
@@ -42001,7 +42377,7 @@ var RvwService = class {
42001
42377
  if (!markdown.mermaidNodeIds.has(nodeId)) {
42002
42378
  throw new RvwError(
42003
42379
  "INVALID_INPUT",
42004
- `Mermaid node\u304C\u672C\u6587\u306Eflowchart\u307E\u305F\u306FclassDiagram\u306B\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${nodeId}`
42380
+ `Mermaid binding\u5BFE\u8C61\u304C\u672C\u6587\u306E\u5BFE\u5FDCdiagram\u306B\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${nodeId}`
42005
42381
  );
42006
42382
  }
42007
42383
  }
@@ -42639,6 +43015,9 @@ var SkillInstaller = class {
42639
43015
  }
42640
43016
  };
42641
43017
 
43018
+ // src/server/start-server.ts
43019
+ import { randomUUID as randomUUID4 } from "node:crypto";
43020
+
42642
43021
  // node_modules/.pnpm/@hono+node-server@2.1.0_hono@4.13.1/node_modules/@hono/node-server/dist/constants-BLSFu_RU.mjs
42643
43022
  var X_ALREADY_SENT = "x-hono-already-sent";
42644
43023
 
@@ -46532,7 +46911,11 @@ function createApp(service, options) {
46532
46911
  app.get("/api/meta/change-sequence", (context) => {
46533
46912
  const rawViewerId = context.req.header(VIEWER_ID_HEADER);
46534
46913
  if (rawViewerId !== void 0) {
46535
- options.viewerLifecycle?.heartbeat(viewerIdSchema.parse(rawViewerId));
46914
+ const rawViewerLease = context.req.header(VIEWER_OPEN_LEASE_HEADER);
46915
+ options.viewerLifecycle?.heartbeat(
46916
+ viewerIdSchema.parse(rawViewerId),
46917
+ rawViewerLease === void 0 ? void 0 : viewerIdSchema.parse(rawViewerLease)
46918
+ );
46536
46919
  }
46537
46920
  return context.json({ ok: true, changeSequence: service.database.getChangeSequence() });
46538
46921
  });
@@ -46560,6 +46943,10 @@ function createApp(service, options) {
46560
46943
  });
46561
46944
  return context.json({ ok: true, ...service.listPullRequests(query) });
46562
46945
  });
46946
+ app.post(
46947
+ "/api/pull-requests/refresh-statuses",
46948
+ async (context) => context.json({ ok: true, ...await service.refreshPullRequestStatuses() })
46949
+ );
46563
46950
  app.get(
46564
46951
  "/api/pull-requests/:id",
46565
46952
  async (context) => context.json({ ok: true, ...await service.getPullRequestView(context.req.param("id")) })
@@ -46691,6 +47078,17 @@ function createApp(service, options) {
46691
47078
  walkthroughs: service.listWalkthroughs(context.req.param("id"))
46692
47079
  })
46693
47080
  );
47081
+ app.get(
47082
+ "/api/pull-requests/:id/walkthroughs/:walkthroughId/references/:referenceId/resolve",
47083
+ async (context) => context.json({
47084
+ ok: true,
47085
+ resolution: await service.resolveWalkthroughReference(
47086
+ context.req.param("id"),
47087
+ context.req.param("walkthroughId"),
47088
+ context.req.param("referenceId")
47089
+ )
47090
+ })
47091
+ );
46694
47092
  app.get(
46695
47093
  "/api/pull-requests/:id/walkthroughs/:walkthroughId",
46696
47094
  (context) => context.json({
@@ -46837,14 +47235,17 @@ var ViewerLifecycle = class {
46837
47235
  constructor(options) {
46838
47236
  this.options = options;
46839
47237
  this.leaseTimeoutMs = options.leaseTimeoutMs ?? DEFAULT_VIEWER_LEASE_TIMEOUT_MS;
47238
+ this.startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_VIEWER_STARTUP_TIMEOUT_MS;
46840
47239
  this.closeGraceMs = options.closeGraceMs ?? DEFAULT_VIEWER_CLOSE_GRACE_MS;
46841
47240
  this.timerLatenessToleranceMs = options.timerLatenessToleranceMs ?? DEFAULT_TIMER_LATENESS_TOLERANCE_MS;
46842
47241
  }
46843
47242
  options;
46844
47243
  viewers = /* @__PURE__ */ new Map();
47244
+ pendingViewers = /* @__PURE__ */ new Map();
46845
47245
  // A poll already in flight can arrive after its pagehide release. Keep that document closed.
46846
47246
  releasedViewers = /* @__PURE__ */ new Set();
46847
47247
  leaseTimeoutMs;
47248
+ startupTimeoutMs;
46848
47249
  closeGraceMs;
46849
47250
  timerLatenessToleranceMs;
46850
47251
  timer = null;
@@ -46853,35 +47254,72 @@ var ViewerLifecycle = class {
46853
47254
  get activeViewerCount() {
46854
47255
  return this.viewers.size;
46855
47256
  }
46856
- heartbeat(viewerId) {
47257
+ get pendingViewerCount() {
47258
+ return this.pendingViewers.size;
47259
+ }
47260
+ reserveViewer(leaseId) {
47261
+ if (this.stopped) return false;
47262
+ this.pendingViewers.set(leaseId, { phase: "opening" });
47263
+ this.scheduleActivityCheck();
47264
+ return true;
47265
+ }
47266
+ armViewerReservation(leaseId) {
47267
+ if (this.stopped || !this.pendingViewers.has(leaseId)) return false;
47268
+ this.pendingViewers.set(leaseId, {
47269
+ phase: "startup",
47270
+ deadline: Date.now() + this.startupTimeoutMs,
47271
+ timeoutMs: this.startupTimeoutMs
47272
+ });
47273
+ this.scheduleActivityCheck();
47274
+ return true;
47275
+ }
47276
+ cancelViewerReservation(leaseId) {
47277
+ if (this.stopped || !this.pendingViewers.delete(leaseId)) return;
47278
+ this.scheduleNext();
47279
+ }
47280
+ heartbeat(viewerId, pendingLeaseId) {
46857
47281
  if (this.stopped || this.releasedViewers.has(viewerId)) return;
47282
+ if (pendingLeaseId !== void 0) this.pendingViewers.delete(pendingLeaseId);
46858
47283
  if (!this.viewerHasConnected) {
46859
47284
  this.viewerHasConnected = true;
46860
47285
  this.options.onFirstViewerConnected?.();
46861
47286
  }
46862
47287
  this.viewers.set(viewerId, Date.now());
46863
- this.scheduleLeaseCheck();
47288
+ this.scheduleActivityCheck();
46864
47289
  }
46865
47290
  release(viewerId) {
46866
47291
  if (this.stopped) return;
46867
47292
  this.releasedViewers.add(viewerId);
46868
47293
  if (!this.viewers.delete(viewerId)) return;
46869
- if (this.viewers.size === 0) this.scheduleEmptyShutdown();
46870
- else this.scheduleLeaseCheck();
47294
+ this.scheduleNext();
46871
47295
  }
46872
47296
  close() {
46873
47297
  this.stopped = true;
46874
47298
  this.clearTimer();
46875
47299
  this.viewers.clear();
47300
+ this.pendingViewers.clear();
46876
47301
  this.releasedViewers.clear();
46877
47302
  }
46878
- scheduleLeaseCheck() {
46879
- if (this.stopped || this.viewers.size === 0) return;
46880
- const earliestHeartbeat = Math.min(...this.viewers.values());
46881
- this.schedule("lease", earliestHeartbeat + this.leaseTimeoutMs);
47303
+ scheduleNext() {
47304
+ if (this.viewers.size > 0 || this.pendingViewers.size > 0) this.scheduleActivityCheck();
47305
+ else this.scheduleEmptyShutdown();
47306
+ }
47307
+ scheduleActivityCheck() {
47308
+ if (this.stopped) return;
47309
+ const deadlines = [
47310
+ ...Array.from(this.viewers.values(), (heartbeat) => heartbeat + this.leaseTimeoutMs),
47311
+ ...Array.from(this.pendingViewers.values()).filter(
47312
+ (pending) => pending.phase === "startup"
47313
+ ).map((pending) => pending.deadline)
47314
+ ];
47315
+ if (deadlines.length === 0) {
47316
+ this.clearTimer();
47317
+ return;
47318
+ }
47319
+ this.schedule("activity", Math.min(...deadlines));
46882
47320
  }
46883
47321
  scheduleEmptyShutdown() {
46884
- if (this.stopped || this.viewers.size > 0) return;
47322
+ if (this.stopped || this.viewers.size > 0 || this.pendingViewers.size > 0) return;
46885
47323
  this.schedule("empty", Date.now() + this.closeGraceMs);
46886
47324
  }
46887
47325
  schedule(kind, deadline) {
@@ -46895,23 +47333,33 @@ var ViewerLifecycle = class {
46895
47333
  const now = Date.now();
46896
47334
  if (now - deadline > this.timerLatenessToleranceMs) {
46897
47335
  for (const viewerId of this.viewers.keys()) this.viewers.set(viewerId, now);
46898
- if (this.viewers.size === 0) this.scheduleEmptyShutdown();
46899
- else this.scheduleLeaseCheck();
47336
+ for (const [leaseId, pending] of this.pendingViewers) {
47337
+ if (pending.phase !== "startup") continue;
47338
+ this.pendingViewers.set(leaseId, {
47339
+ phase: "startup",
47340
+ deadline: now + pending.timeoutMs,
47341
+ timeoutMs: pending.timeoutMs
47342
+ });
47343
+ }
47344
+ this.scheduleNext();
46900
47345
  return;
46901
47346
  }
46902
- if (kind === "lease") {
47347
+ if (kind === "activity") {
46903
47348
  for (const [viewerId, lastHeartbeat] of this.viewers) {
46904
47349
  if (now - lastHeartbeat >= this.leaseTimeoutMs) this.viewers.delete(viewerId);
46905
47350
  }
46906
- if (this.viewers.size > 0) this.scheduleLeaseCheck();
46907
- else this.scheduleEmptyShutdown();
47351
+ for (const [leaseId, pending] of this.pendingViewers) {
47352
+ if (pending.phase !== "startup") continue;
47353
+ if (now >= pending.deadline) this.pendingViewers.delete(leaseId);
47354
+ }
47355
+ this.scheduleNext();
46908
47356
  return;
46909
47357
  }
46910
- if (this.viewers.size === 0) {
47358
+ if (this.viewers.size === 0 && this.pendingViewers.size === 0) {
46911
47359
  this.stopped = true;
46912
47360
  this.options.onAllViewersClosed();
46913
47361
  } else {
46914
- this.scheduleLeaseCheck();
47362
+ this.scheduleActivityCheck();
46915
47363
  }
46916
47364
  }
46917
47365
  clearTimer() {
@@ -46921,6 +47369,7 @@ var ViewerLifecycle = class {
46921
47369
  };
46922
47370
 
46923
47371
  // src/server/start-server.ts
47372
+ var RUNTIME_STOPPING_REASON = "runtime-stopping";
46924
47373
  async function startServer(service, options = {}) {
46925
47374
  const host = "127.0.0.1";
46926
47375
  const security = { expectedHost: null, expectedOrigin: null };
@@ -46960,6 +47409,33 @@ async function startServer(service, options = {}) {
46960
47409
  origin,
46961
47410
  firstViewerConnected,
46962
47411
  allViewersClosed,
47412
+ reserveViewer: () => {
47413
+ if (!viewerLifecycle) return null;
47414
+ const leaseId = randomUUID4();
47415
+ if (!viewerLifecycle.reserveViewer(leaseId)) {
47416
+ throw new RvwError(
47417
+ "PROCESS_FAILED",
47418
+ "rvw runtime\u306F\u505C\u6B62\u51E6\u7406\u4E2D\u306E\u305F\u3081viewer\u3092\u8FFD\u52A0\u3067\u304D\u307E\u305B\u3093\u3002",
47419
+ {
47420
+ details: { reason: RUNTIME_STOPPING_REASON },
47421
+ suggestions: ["rvw open\u3092\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"]
47422
+ }
47423
+ );
47424
+ }
47425
+ return leaseId;
47426
+ },
47427
+ armViewerReservation: (leaseId) => {
47428
+ if (!viewerLifecycle || viewerLifecycle.armViewerReservation(leaseId)) return;
47429
+ throw new RvwError(
47430
+ "PROCESS_FAILED",
47431
+ "rvw runtime\u306F\u505C\u6B62\u51E6\u7406\u4E2D\u306E\u305F\u3081viewer\u3092\u8FFD\u52A0\u3067\u304D\u307E\u305B\u3093\u3002",
47432
+ {
47433
+ details: { reason: RUNTIME_STOPPING_REASON },
47434
+ suggestions: ["rvw open\u3092\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"]
47435
+ }
47436
+ );
47437
+ },
47438
+ cancelViewerReservation: (leaseId) => viewerLifecycle?.cancelViewerReservation(leaseId),
46963
47439
  close: () => closePromise ??= (async () => {
46964
47440
  viewerLifecycle?.close();
46965
47441
  await new Promise((closeResolve, closeReject) => {
@@ -46974,7 +47450,7 @@ async function startServer(service, options = {}) {
46974
47450
  }
46975
47451
 
46976
47452
  // src/server/agent-socket.ts
46977
- import { createHash as createHash5, randomUUID as randomUUID4 } from "node:crypto";
47453
+ import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
46978
47454
  import {
46979
47455
  chmodSync as chmodSync2,
46980
47456
  existsSync as existsSync5,
@@ -46990,7 +47466,8 @@ import os4 from "node:os";
46990
47466
  import path9 from "node:path";
46991
47467
  var MAX_CLI_STDIN_BYTES = 40 * 1024 * 1024;
46992
47468
  var MAX_AGENT_MESSAGE_BYTES = MAX_CLI_STDIN_BYTES + 64 * 1024;
46993
- var AGENT_SOCKET_PROTOCOL_VERSION = 2;
47469
+ var AGENT_SOCKET_PROTOCOL_VERSION = 4;
47470
+ var RUNTIME_VIEWER_OPEN_OPERATION = "viewer.open";
46994
47471
  var AGENT_SOCKET_RESTART_SUGGESTION = "\u8D77\u52D5\u4E2D\u306Ervw viewer\u3092\u505C\u6B62\u3057\u3001\u66F4\u65B0\u5F8C\u306Ervw open\u3067\u518D\u8D77\u52D5\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
46995
47472
  var DEFAULT_CONNECT_TIMEOUT_MS = 2e3;
46996
47473
  var DEFAULT_OPERATION_TIMEOUT_MS = 18e4;
@@ -47003,6 +47480,19 @@ var agentRequestEnvelopeSchema = external_exports.object({
47003
47480
  input: external_exports.unknown(),
47004
47481
  expectedDatabasePath: external_exports.string().min(1).optional()
47005
47482
  }).strict();
47483
+ var runtimeViewerOpenInputSchema = external_exports.object({
47484
+ reference: external_exports.string().min(1).optional(),
47485
+ cwd: external_exports.string().min(1),
47486
+ requestedPort: external_exports.number().int().min(0).max(65535)
47487
+ }).strict();
47488
+ var runtimeViewerOpenResultSchema = external_exports.object({
47489
+ url: external_exports.string().url(),
47490
+ origin: external_exports.string().url(),
47491
+ port: external_exports.number().int().min(1).max(65535),
47492
+ pullRequestId: external_exports.string().min(1),
47493
+ ownerPid: external_exports.number().int().positive(),
47494
+ viewerLeaseId: external_exports.uuid().optional()
47495
+ }).strict();
47006
47496
  function explicitAgentSocketPath() {
47007
47497
  const configuredPath = process.env.RVW_AGENT_SOCKET_PATH;
47008
47498
  if (configuredPath === void 0) return null;
@@ -47058,7 +47548,10 @@ function parseOperationInput(operation, value) {
47058
47548
  if (!parsed.success) throw invalidSocketInput(parsed.error);
47059
47549
  return parsed.data;
47060
47550
  }
47061
- async function dispatchAgentSocketRequest(service, rawRequest) {
47551
+ function resolveDatabaseIdentity(databaseFilePath) {
47552
+ return databaseFilePath === ":memory:" ? databaseFilePath : path9.resolve(databaseFilePath);
47553
+ }
47554
+ function validateAgentRequest(rawRequest, databaseFilePath) {
47062
47555
  const request = parseRequest(rawRequest);
47063
47556
  if (request.protocolVersion !== AGENT_SOCKET_PROTOCOL_VERSION) {
47064
47557
  throw new RvwError("STALE_PROTOCOL", "Agent socket protocol version\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002", {
@@ -47069,23 +47562,30 @@ async function dispatchAgentSocketRequest(service, rawRequest) {
47069
47562
  suggestions: [AGENT_SOCKET_RESTART_SUGGESTION]
47070
47563
  });
47071
47564
  }
47072
- if (request.expectedDatabasePath !== void 0 && path9.resolve(request.expectedDatabasePath) !== path9.resolve(service.database.filePath)) {
47565
+ if (request.expectedDatabasePath !== void 0 && databaseFilePath !== void 0 && resolveDatabaseIdentity(request.expectedDatabasePath) !== resolveDatabaseIdentity(databaseFilePath)) {
47073
47566
  throw new RvwError("DATABASE_ERROR", "Agent socket\u304C\u5225\u306Edatabase\u3092\u4F7F\u7528\u3057\u3066\u3044\u307E\u3059\u3002", {
47074
47567
  details: {
47075
47568
  agentSocketDatabaseMismatch: true,
47076
- expectedDatabasePath: path9.resolve(request.expectedDatabasePath),
47077
- actualDatabasePath: path9.resolve(service.database.filePath)
47569
+ expectedDatabasePath: resolveDatabaseIdentity(request.expectedDatabasePath),
47570
+ actualDatabasePath: resolveDatabaseIdentity(databaseFilePath)
47078
47571
  }
47079
47572
  });
47080
47573
  }
47574
+ return request;
47575
+ }
47576
+ function pingResult(databaseFilePath) {
47577
+ return {
47578
+ protocolVersion: AGENT_SOCKET_PROTOCOL_VERSION,
47579
+ databasePath: resolveDatabaseIdentity(databaseFilePath),
47580
+ ownerPid: process.pid
47581
+ };
47582
+ }
47583
+ async function dispatchAgentSocketRequest(service, rawRequest) {
47584
+ const request = validateAgentRequest(rawRequest, service.database?.filePath);
47081
47585
  if (request.operation === "ping") {
47082
47586
  const ping = external_exports.object({}).strict().safeParse(request.input);
47083
47587
  if (!ping.success) throw invalidSocketInput(ping.error);
47084
- return {
47085
- protocolVersion: AGENT_SOCKET_PROTOCOL_VERSION,
47086
- databasePath: path9.resolve(service.database.filePath),
47087
- ownerPid: process.pid
47088
- };
47588
+ return pingResult(service.database.filePath);
47089
47589
  }
47090
47590
  if (!Object.hasOwn(agentCommandInputSchemas, request.operation)) {
47091
47591
  throw new RvwError("INVALID_INPUT", `\u672A\u5BFE\u5FDC\u306EAgent socket operation\u3067\u3059: ${request.operation}`);
@@ -47390,6 +47890,38 @@ async function tryAgentSocketRequest(operation, input, options = {}) {
47390
47890
  });
47391
47891
  });
47392
47892
  }
47893
+ async function tryRuntimeViewerOpen(input, databaseFilePath = databasePathConfiguration().filePath, options = {}) {
47894
+ const parsedInput = runtimeViewerOpenInputSchema.safeParse(input);
47895
+ if (!parsedInput.success) throw invalidSocketInput(parsedInput.error);
47896
+ const response = await tryAgentSocketRequest(
47897
+ RUNTIME_VIEWER_OPEN_OPERATION,
47898
+ parsedInput.data,
47899
+ {
47900
+ expectedDatabasePath: databaseFilePath,
47901
+ requireSocket: false,
47902
+ ...options
47903
+ }
47904
+ );
47905
+ if (!response.available) return response;
47906
+ const parsedResult = runtimeViewerOpenResultSchema.safeParse(response.result);
47907
+ if (!parsedResult.success) {
47908
+ throw new RvwError("PROCESS_FAILED", "\u8D77\u52D5\u4E2D\u306Ervw runtime\u304B\u3089\u4E0D\u6B63\u306A\u5FDC\u7B54\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F\u3002", {
47909
+ details: { issues: parsedResult.error.issues },
47910
+ suggestions: [AGENT_SOCKET_RESTART_SUGGESTION]
47911
+ });
47912
+ }
47913
+ return {
47914
+ available: true,
47915
+ result: {
47916
+ url: parsedResult.data.url,
47917
+ origin: parsedResult.data.origin,
47918
+ port: parsedResult.data.port,
47919
+ pullRequestId: parsedResult.data.pullRequestId,
47920
+ ownerPid: parsedResult.data.ownerPid,
47921
+ ...parsedResult.data.viewerLeaseId === void 0 ? {} : { viewerLeaseId: parsedResult.data.viewerLeaseId }
47922
+ }
47923
+ };
47924
+ }
47393
47925
  async function inspectAgentTransport(databaseFilePath = databasePathConfiguration().filePath) {
47394
47926
  const expectedDatabasePath = databaseFilePath === ":memory:" ? databaseFilePath : path9.resolve(databaseFilePath);
47395
47927
  const configuredSocketPath = explicitAgentSocketPath();
@@ -47492,7 +48024,7 @@ function socketOwnerPid(lockPath) {
47492
48024
  function tryAcquireSocketOwnerLock(socketPath) {
47493
48025
  const lockPath = `${socketPath}${OWNER_LOCK_SUFFIX}`;
47494
48026
  for (let attempt = 0; attempt < 2; attempt += 1) {
47495
- const stagingPath = `${lockPath}.${process.pid}.${randomUUID4()}.tmp`;
48027
+ const stagingPath = `${lockPath}.${process.pid}.${randomUUID5()}.tmp`;
47496
48028
  try {
47497
48029
  writeFileSync2(stagingPath, `${JSON.stringify({ pid: process.pid })}
47498
48030
  `, {
@@ -47536,7 +48068,7 @@ function releaseSocketOwnerLock(lock) {
47536
48068
  if (!lock) return;
47537
48069
  unlinkIfOwned(lock.path, lock.identity);
47538
48070
  }
47539
- function createAgentServer(service) {
48071
+ function createAgentServer(dispatch) {
47540
48072
  const server = net.createServer((socket) => {
47541
48073
  const chunks = [];
47542
48074
  let bytes = 0;
@@ -47561,7 +48093,7 @@ function createAgentServer(service) {
47561
48093
  const request = JSON.parse(Buffer.concat(chunks).toString("utf8"));
47562
48094
  response = {
47563
48095
  ok: true,
47564
- result: await dispatchAgentSocketRequest(service, parseRequest(request))
48096
+ result: await dispatch(parseRequest(request))
47565
48097
  };
47566
48098
  } catch (error51) {
47567
48099
  response = { ok: false, error: asRvwError(error51).toJSON() };
@@ -47589,8 +48121,8 @@ async function listen(server, socketPath) {
47589
48121
  server.listen(socketPath);
47590
48122
  });
47591
48123
  }
47592
- async function startAgentSocket(service, options = {}) {
47593
- const socketPath = agentSocketPath(service.database.filePath);
48124
+ async function startAgentSocketListener(databaseFilePath, dispatch, options) {
48125
+ const socketPath = agentSocketPath(databaseFilePath);
47594
48126
  ensurePrivateSocketDirectory(socketPath);
47595
48127
  const retryDelay = options.takeoverRetryMs ?? DEFAULT_TAKEOVER_RETRY_MS;
47596
48128
  let server = null;
@@ -47598,9 +48130,12 @@ async function startAgentSocket(service, options = {}) {
47598
48130
  let ownerLock = null;
47599
48131
  let retryTimer;
47600
48132
  let acquisition = null;
48133
+ let listenerDrain = null;
48134
+ let stopPromise = null;
48135
+ let releasePromise = null;
47601
48136
  let closed = false;
47602
48137
  const scheduleTakeover = () => {
47603
- if (closed || server || retryTimer) return;
48138
+ if (!options.retryOwnership || closed || server || retryTimer) return;
47604
48139
  retryTimer = setTimeout(() => {
47605
48140
  retryTimer = void 0;
47606
48141
  void acquire().catch(() => scheduleTakeover());
@@ -47629,7 +48164,7 @@ async function startAgentSocket(service, options = {}) {
47629
48164
  throw error51;
47630
48165
  }
47631
48166
  }
47632
- const candidate = createAgentServer(service);
48167
+ const candidate = createAgentServer(dispatch);
47633
48168
  try {
47634
48169
  await listen(candidate, socketPath);
47635
48170
  } catch (error51) {
@@ -47661,12 +48196,16 @@ async function startAgentSocket(service, options = {}) {
47661
48196
  ownerLock = candidateLock;
47662
48197
  candidate.on("error", () => {
47663
48198
  if (server !== candidate) return;
47664
- const failedIdentity = identity;
47665
- const failedOwnerLock = ownerLock;
47666
48199
  server = null;
48200
+ listenerDrain = new Promise((resolve) => candidate.close(() => resolve()));
48201
+ if (!options.retryOwnership) {
48202
+ return;
48203
+ }
48204
+ const failedIdentity = identity;
47667
48205
  identity = null;
48206
+ const failedOwnerLock = ownerLock;
47668
48207
  ownerLock = null;
47669
- candidate.close(() => {
48208
+ void listenerDrain.then(() => {
47670
48209
  unlinkIfOwned(socketPath, failedIdentity);
47671
48210
  releaseSocketOwnerLock(failedOwnerLock);
47672
48211
  scheduleTakeover();
@@ -47680,33 +48219,128 @@ async function startAgentSocket(service, options = {}) {
47680
48219
  }
47681
48220
  };
47682
48221
  await acquire();
47683
- return {
47684
- path: socketPath,
47685
- get owned() {
47686
- return server !== null;
47687
- },
47688
- close: async () => {
48222
+ const stopAccepting = async () => {
48223
+ await (stopPromise ??= (async () => {
47689
48224
  closed = true;
47690
48225
  if (retryTimer) clearTimeout(retryTimer);
47691
48226
  await acquisition;
48227
+ if (listenerDrain) await listenerDrain;
47692
48228
  const ownedServer = server;
47693
- const ownedIdentity = identity;
47694
- const ownedOwnerLock = ownerLock;
47695
48229
  server = null;
47696
- identity = null;
47697
- ownerLock = null;
47698
- if (!ownedServer) {
47699
- releaseSocketOwnerLock(ownedOwnerLock);
47700
- return;
47701
- }
48230
+ if (!ownedServer) return;
48231
+ listenerDrain = new Promise(
48232
+ (resolve, reject) => ownedServer.close((error51) => error51 ? reject(error51) : resolve())
48233
+ );
48234
+ await listenerDrain;
48235
+ })());
48236
+ };
48237
+ const releaseOwnership = async () => {
48238
+ await (releasePromise ??= (async () => {
47702
48239
  try {
47703
- await new Promise(
47704
- (resolve, reject) => ownedServer.close((error51) => error51 ? reject(error51) : resolve())
47705
- );
48240
+ await stopAccepting();
47706
48241
  } finally {
47707
- unlinkIfOwned(socketPath, ownedIdentity);
47708
- releaseSocketOwnerLock(ownedOwnerLock);
48242
+ const ownedIdentity = identity;
48243
+ const ownedOwnerLock = ownerLock;
48244
+ identity = null;
48245
+ ownerLock = null;
48246
+ try {
48247
+ unlinkIfOwned(socketPath, ownedIdentity);
48248
+ } finally {
48249
+ releaseSocketOwnerLock(ownedOwnerLock);
48250
+ }
48251
+ }
48252
+ })());
48253
+ };
48254
+ return {
48255
+ path: socketPath,
48256
+ get owned() {
48257
+ return ownerLock !== null;
48258
+ },
48259
+ stopAccepting,
48260
+ releaseOwnership,
48261
+ close: releaseOwnership
48262
+ };
48263
+ }
48264
+ async function startRuntimeAgentSocket(databaseFilePath) {
48265
+ let handler = null;
48266
+ let resolveHandler;
48267
+ let rejectHandler;
48268
+ const handlerPromise = new Promise((resolve, reject) => {
48269
+ resolveHandler = resolve;
48270
+ rejectHandler = reject;
48271
+ });
48272
+ void handlerPromise.catch(() => {
48273
+ });
48274
+ let accepting = true;
48275
+ const rejectUninitializedHandler = () => {
48276
+ if (handler) return;
48277
+ rejectHandler?.(new RvwError("PROCESS_FAILED", "rvw runtime\u304C\u521D\u671F\u5316\u3055\u308C\u308B\u524D\u306B\u505C\u6B62\u3057\u307E\u3057\u305F\u3002"));
48278
+ resolveHandler = void 0;
48279
+ rejectHandler = void 0;
48280
+ };
48281
+ const running = await startAgentSocketListener(
48282
+ databaseFilePath,
48283
+ async (rawRequest) => {
48284
+ const request = validateAgentRequest(rawRequest, databaseFilePath);
48285
+ if (request.operation === "ping") {
48286
+ const ping = external_exports.object({}).strict().safeParse(request.input);
48287
+ if (!ping.success) throw invalidSocketInput(ping.error);
48288
+ return pingResult(databaseFilePath);
48289
+ }
48290
+ if (request.operation === RUNTIME_VIEWER_OPEN_OPERATION) {
48291
+ const parsed = runtimeViewerOpenInputSchema.safeParse(request.input);
48292
+ if (!parsed.success) throw invalidSocketInput(parsed.error);
48293
+ return await (await handlerPromise).openViewer({
48294
+ ...parsed.data.reference === void 0 ? {} : { reference: parsed.data.reference },
48295
+ cwd: parsed.data.cwd,
48296
+ requestedPort: parsed.data.requestedPort
48297
+ });
48298
+ }
48299
+ return await dispatchAgentSocketRequest((await handlerPromise).service, request);
48300
+ },
48301
+ { retryOwnership: false }
48302
+ );
48303
+ return {
48304
+ path: running.path,
48305
+ get owned() {
48306
+ return running.owned;
48307
+ },
48308
+ setHandler(value) {
48309
+ if (!running.owned || !accepting) {
48310
+ throw new RvwError("PROCESS_FAILED", "\u6240\u6709\u3057\u3066\u3044\u306A\u3044runtime socket\u306F\u521D\u671F\u5316\u3067\u304D\u307E\u305B\u3093\u3002");
48311
+ }
48312
+ if (handler) {
48313
+ throw new RvwError("INTERNAL_ERROR", "runtime socket\u306F\u521D\u671F\u5316\u6E08\u307F\u3067\u3059\u3002", {
48314
+ status: 500
48315
+ });
48316
+ }
48317
+ if (resolveDatabaseIdentity(value.service.database.filePath) !== resolveDatabaseIdentity(databaseFilePath)) {
48318
+ throw new RvwError("DATABASE_ERROR", "runtime\u3068Agent socket\u306Edatabase\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002", {
48319
+ details: {
48320
+ socketDatabasePath: resolveDatabaseIdentity(databaseFilePath),
48321
+ runtimeDatabasePath: resolveDatabaseIdentity(value.service.database.filePath)
48322
+ }
48323
+ });
47709
48324
  }
48325
+ handler = value;
48326
+ resolveHandler?.(value);
48327
+ resolveHandler = void 0;
48328
+ rejectHandler = void 0;
48329
+ },
48330
+ stopAccepting: async () => {
48331
+ accepting = false;
48332
+ rejectUninitializedHandler();
48333
+ await running.stopAccepting();
48334
+ },
48335
+ releaseOwnership: async () => {
48336
+ accepting = false;
48337
+ rejectUninitializedHandler();
48338
+ await running.releaseOwnership();
48339
+ },
48340
+ close: async () => {
48341
+ accepting = false;
48342
+ rejectUninitializedHandler();
48343
+ await running.releaseOwnership();
47710
48344
  }
47711
48345
  };
47712
48346
  }
@@ -48067,6 +48701,9 @@ function formatCommentListOutput(result, state) {
48067
48701
  var MAX_STDIN_BYTES = MAX_CLI_STDIN_BYTES;
48068
48702
  var OPEN_WORKER_READY_TIMEOUT_MS = 12e4;
48069
48703
  var OPEN_WORKER_PARENT_TIMEOUT_PADDING_MS = 5e3;
48704
+ var RUNTIME_REUSE_RETRY_MS = 50;
48705
+ var RUNTIME_VIEWER_HEARTBEAT_INTERVAL_MS = 3e4;
48706
+ var RUNTIME_VIEWER_REQUEST_TIMEOUT_MS = 5e3;
48070
48707
  function writeJson(value) {
48071
48708
  process.stdout.write(`${JSON.stringify(value)}
48072
48709
  `);
@@ -48162,6 +48799,11 @@ function errorFromOpenWorker(error51) {
48162
48799
  ...error51.details === void 0 ? {} : { details: error51.details }
48163
48800
  });
48164
48801
  }
48802
+ function isRuntimeStoppingError(error51) {
48803
+ if (!(error51 instanceof RvwError) || error51.code !== "PROCESS_FAILED") return false;
48804
+ if (!error51.details || typeof error51.details !== "object") return false;
48805
+ return error51.details.reason === RUNTIME_STOPPING_REASON;
48806
+ }
48165
48807
  async function completeBackgroundOpen(child, launchBrowser, options = {}) {
48166
48808
  const readyTimeoutMs = options.readyTimeoutMs ?? OPEN_WORKER_READY_TIMEOUT_MS;
48167
48809
  const viewerTimeoutMs = options.viewerTimeoutMs ?? DEFAULT_VIEWER_STARTUP_TIMEOUT_MS + OPEN_WORKER_PARENT_TIMEOUT_PADDING_MS;
@@ -48285,11 +48927,31 @@ function forkOpenWorker(reference, port) {
48285
48927
  });
48286
48928
  }
48287
48929
  async function startBackgroundOpen(reference, port, dependencies = {}) {
48930
+ const input = {
48931
+ ...reference === void 0 ? {} : { reference },
48932
+ cwd: process.cwd(),
48933
+ requestedPort: port
48934
+ };
48935
+ const launchBrowser = dependencies.launchBrowser ?? (async (url2) => await open_default(url2));
48936
+ let existing = null;
48937
+ try {
48938
+ existing = await (dependencies.tryRuntimeOpen ?? tryRuntimeViewerOpen)(input);
48939
+ } catch (error51) {
48940
+ if (!isRuntimeStoppingError(error51)) throw error51;
48941
+ }
48942
+ if (existing?.available) {
48943
+ await launchBrowser(existing.result.url);
48944
+ return existing.result.url;
48945
+ }
48946
+ if (existing?.reason === "database-mismatch") {
48947
+ throw new RvwError(
48948
+ "AGENT_SOCKET_UNAVAILABLE",
48949
+ "\u8D77\u52D5\u4E2D\u306Ervw runtime\u304C\u5225\u306Edatabase\u3092\u4F7F\u7528\u3057\u3066\u3044\u307E\u3059\u3002",
48950
+ { details: existing.details }
48951
+ );
48952
+ }
48288
48953
  const child = (dependencies.forkWorker ?? forkOpenWorker)(reference, port);
48289
- return await completeBackgroundOpen(
48290
- child,
48291
- dependencies.launchBrowser ?? (async (url2) => await open_default(url2))
48292
- );
48954
+ return await completeBackgroundOpen(child, launchBrowser);
48293
48955
  }
48294
48956
  async function sendOpenWorkerMessage(message) {
48295
48957
  if (!process.send || !process.connected) return false;
@@ -48321,17 +48983,208 @@ async function waitForInitialViewer(firstViewerConnected, timeoutMs = DEFAULT_VI
48321
48983
  void firstViewerConnected.then(() => finish("connected"));
48322
48984
  });
48323
48985
  }
48324
- async function runOpenServer(activeRuntime, reference, port, openAutomatically, useAgentSocket) {
48986
+ function runtimeViewerOpenInput(reference, cwd, requestedPort) {
48987
+ return {
48988
+ ...reference === void 0 ? {} : { reference },
48989
+ cwd,
48990
+ requestedPort
48991
+ };
48992
+ }
48993
+ function createRuntimeAgentSocketHandler(activeRuntime, running) {
48994
+ return {
48995
+ service: activeRuntime.service,
48996
+ openViewer: async (input) => {
48997
+ if (input.requestedPort !== 0 && input.requestedPort !== running.port) {
48998
+ throw new RvwError(
48999
+ "INVALID_INPUT",
49000
+ `\u540C\u3058database\u306Ervw runtime\u306Fport ${running.port} \u3067\u8D77\u52D5\u6E08\u307F\u3067\u3059\u3002`,
49001
+ {
49002
+ details: { activePort: running.port, requestedPort: input.requestedPort },
49003
+ suggestions: [
49004
+ "\u65E2\u5B58runtime\u3092\u505C\u6B62\u3057\u3066\u304B\u3089\u6307\u5B9Aport\u3067\u518D\u8D77\u52D5\u3059\u308B\u304B\u3001--port\u3092\u7701\u7565\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
49005
+ ]
49006
+ }
49007
+ );
49008
+ }
49009
+ const viewerLeaseId = running.reserveViewer();
49010
+ try {
49011
+ const opened = await activeRuntime.service.openPullRequest(input.reference, input.cwd);
49012
+ if (viewerLeaseId !== null) running.armViewerReservation(viewerLeaseId);
49013
+ const url2 = new URL(running.origin);
49014
+ url2.searchParams.set("pullRequestId", opened.pullRequest.id);
49015
+ if (viewerLeaseId !== null) url2.searchParams.set(VIEWER_OPEN_LEASE_QUERY, viewerLeaseId);
49016
+ return {
49017
+ url: url2.toString(),
49018
+ origin: running.origin,
49019
+ port: running.port,
49020
+ pullRequestId: opened.pullRequest.id,
49021
+ ownerPid: process.pid,
49022
+ ...viewerLeaseId === null ? {} : { viewerLeaseId }
49023
+ };
49024
+ } catch (error51) {
49025
+ if (viewerLeaseId !== null) running.cancelViewerReservation(viewerLeaseId);
49026
+ throw error51;
49027
+ }
49028
+ }
49029
+ };
49030
+ }
49031
+ async function holdRuntimeViewerLease(result, dependencies = {}) {
49032
+ const viewerLeaseId = result.viewerLeaseId;
49033
+ if (viewerLeaseId === void 0) return;
49034
+ const fetchRequest = dependencies.fetch ?? fetch;
49035
+ const viewerId = randomUUID6();
49036
+ const requestTimeoutMs = dependencies.requestTimeoutMs ?? RUNTIME_VIEWER_REQUEST_TIMEOUT_MS;
49037
+ const heartbeatUrl = new URL("/api/meta/change-sequence", result.origin);
49038
+ const releaseUrl = new URL("/api/meta/viewers/release", result.origin);
49039
+ const heartbeat = async () => {
49040
+ const response = await fetchRequest(heartbeatUrl, {
49041
+ signal: AbortSignal.timeout(requestTimeoutMs),
49042
+ headers: {
49043
+ [VIEWER_ID_HEADER]: viewerId,
49044
+ [VIEWER_OPEN_LEASE_HEADER]: viewerLeaseId
49045
+ }
49046
+ });
49047
+ if (!response.ok) {
49048
+ throw new RvwError("PROCESS_FAILED", "rvw runtime\u306Eviewer lease\u3092\u66F4\u65B0\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002", {
49049
+ details: { status: response.status, origin: result.origin }
49050
+ });
49051
+ }
49052
+ };
49053
+ const stopped = Promise.withResolvers();
49054
+ let heartbeatError;
49055
+ let heartbeatInFlight = false;
49056
+ const onSignal = () => stopped.resolve("signal");
49057
+ process.once("SIGINT", onSignal);
49058
+ process.once("SIGTERM", onSignal);
49059
+ let interval;
49060
+ let registered = false;
49061
+ try {
49062
+ await heartbeat();
49063
+ registered = true;
49064
+ interval = setInterval(() => {
49065
+ if (heartbeatInFlight) return;
49066
+ heartbeatInFlight = true;
49067
+ void heartbeat().catch((error51) => {
49068
+ heartbeatError = error51;
49069
+ stopped.resolve("heartbeat-failed");
49070
+ }).finally(() => {
49071
+ heartbeatInFlight = false;
49072
+ });
49073
+ }, dependencies.heartbeatIntervalMs ?? RUNTIME_VIEWER_HEARTBEAT_INTERVAL_MS);
49074
+ interval.unref();
49075
+ const reason = await stopped.promise;
49076
+ if (reason === "heartbeat-failed") throw heartbeatError;
49077
+ } finally {
49078
+ if (interval) clearInterval(interval);
49079
+ process.removeListener("SIGINT", onSignal);
49080
+ process.removeListener("SIGTERM", onSignal);
49081
+ if (registered) {
49082
+ await fetchRequest(releaseUrl, {
49083
+ method: "POST",
49084
+ signal: AbortSignal.timeout(requestTimeoutMs),
49085
+ headers: {
49086
+ origin: result.origin,
49087
+ "content-type": "application/json"
49088
+ },
49089
+ body: JSON.stringify({ viewerId })
49090
+ }).catch(() => void 0);
49091
+ }
49092
+ }
49093
+ }
49094
+ async function closeOwnedRuntime(agentSocket, running, activeRuntime) {
49095
+ try {
49096
+ await agentSocket?.stopAccepting();
49097
+ } finally {
49098
+ try {
49099
+ await running?.close();
49100
+ } finally {
49101
+ try {
49102
+ activeRuntime?.close();
49103
+ } finally {
49104
+ await agentSocket?.releaseOwnership();
49105
+ }
49106
+ }
49107
+ }
49108
+ }
49109
+ async function acquireRuntimeOrReuseExisting(input, databaseFilePath, timeoutMs = OPEN_WORKER_READY_TIMEOUT_MS, dependencies = {}) {
49110
+ const startSocket = dependencies.startSocket ?? startRuntimeAgentSocket;
49111
+ const tryRuntimeOpen = dependencies.tryRuntimeOpen ?? tryRuntimeViewerOpen;
49112
+ const wait = dependencies.wait ?? (async (milliseconds) => {
49113
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
49114
+ });
49115
+ const now = dependencies.now ?? Date.now;
49116
+ const deadline = now() + timeoutMs;
49117
+ do {
49118
+ const candidate = await startSocket(databaseFilePath);
49119
+ if (candidate.owned) return { kind: "owned", agentSocket: candidate };
49120
+ await candidate.close();
49121
+ let response = null;
49122
+ try {
49123
+ response = await tryRuntimeOpen(input, databaseFilePath);
49124
+ } catch (error51) {
49125
+ if (!isRuntimeStoppingError(error51)) throw error51;
49126
+ }
49127
+ if (response?.available) return { kind: "reused", result: response.result };
49128
+ if (response?.reason === "database-mismatch") {
49129
+ throw new RvwError(
49130
+ "AGENT_SOCKET_UNAVAILABLE",
49131
+ "\u8D77\u52D5\u4E2D\u306Ervw runtime\u304C\u5225\u306Edatabase\u3092\u4F7F\u7528\u3057\u3066\u3044\u307E\u3059\u3002",
49132
+ { details: response.details }
49133
+ );
49134
+ }
49135
+ await wait(RUNTIME_REUSE_RETRY_MS);
49136
+ } while (now() < deadline);
49137
+ throw new RvwError("PROCESS_TIMEOUT", "\u8D77\u52D5\u4E2D\u306Ervw runtime\u3078\u306E\u63A5\u7D9A\u304C\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3057\u307E\u3057\u305F\u3002", {
49138
+ details: { timeoutMs }
49139
+ });
49140
+ }
49141
+ async function waitForWorkerParentDisconnect() {
49142
+ if (!process.connected) return;
49143
+ await new Promise((resolve) => process.once("disconnect", resolve));
49144
+ }
49145
+ async function runOpenServer(runtimeFactory, reference, port, openAutomatically, useAgentSocket, reuseExisting) {
49146
+ let activeRuntime;
48325
49147
  let running;
48326
49148
  let agentSocket;
48327
49149
  try {
49150
+ if (useAgentSocket) {
49151
+ const databaseFilePath = databasePathConfiguration().filePath;
49152
+ if (reuseExisting) {
49153
+ const startup = await acquireRuntimeOrReuseExisting(
49154
+ runtimeViewerOpenInput(reference, process.cwd(), port),
49155
+ databaseFilePath
49156
+ );
49157
+ if (startup.kind === "reused") {
49158
+ const result = startup.result;
49159
+ process.stdout.write(`rvw: ${result.url}
49160
+ `);
49161
+ if (!openAutomatically) await holdRuntimeViewerLease(result);
49162
+ return;
49163
+ }
49164
+ agentSocket = startup.agentSocket;
49165
+ } else {
49166
+ agentSocket = await startRuntimeAgentSocket(databaseFilePath);
49167
+ if (!agentSocket.owned) {
49168
+ throw new RvwError(
49169
+ "PROCESS_FAILED",
49170
+ "\u540C\u3058database\u306Ervw runtime\u304C\u65E2\u306B\u8D77\u52D5\u4E2D\u306E\u305F\u3081foreground server\u3092\u958B\u59CB\u3067\u304D\u307E\u305B\u3093\u3002",
49171
+ {
49172
+ suggestions: [
49173
+ "\u901A\u5E38\u306Ervw open\u3067\u65E2\u5B58runtime\u3092\u5229\u7528\u3059\u308B\u304B\u3001\u65E2\u5B58runtime\u3092\u505C\u6B62\u3057\u3066\u304B\u3089\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
49174
+ ]
49175
+ }
49176
+ );
49177
+ }
49178
+ }
49179
+ }
49180
+ activeRuntime = runtimeFactory();
48328
49181
  const opened = await activeRuntime.service.openPullRequest(reference, process.cwd());
48329
49182
  running = await startServer(activeRuntime.service, {
48330
49183
  port,
48331
49184
  staticDirectory: staticDirectory(),
48332
49185
  autoCloseWhenNoViewers: openAutomatically
48333
49186
  });
48334
- if (useAgentSocket) agentSocket = await startAgentSocket(activeRuntime.service);
49187
+ agentSocket?.setHandler(createRuntimeAgentSocketHandler(activeRuntime, running));
48335
49188
  const url2 = new URL(running.origin);
48336
49189
  url2.searchParams.set("pullRequestId", opened.pullRequest.id);
48337
49190
  process.stdout.write(`rvw: ${url2.toString()}
@@ -48342,31 +49195,38 @@ async function runOpenServer(activeRuntime, reference, port, openAutomatically,
48342
49195
  process.stdout.write("rvw: viewer\u3092\u9589\u3058\u305F\u305F\u3081server\u3092\u505C\u6B62\u3057\u307E\u3059\u3002\n");
48343
49196
  }
48344
49197
  } finally {
48345
- try {
48346
- await agentSocket?.close();
48347
- } finally {
48348
- try {
48349
- await running?.close();
48350
- } finally {
48351
- activeRuntime.close();
48352
- }
48353
- }
49198
+ await closeOwnedRuntime(agentSocket, running, activeRuntime);
48354
49199
  }
48355
49200
  }
48356
- async function runOpenWorker(activeRuntime, reference, port) {
49201
+ async function runOpenWorker(runtimeFactory, reference, port) {
49202
+ let activeRuntime;
48357
49203
  let running;
48358
49204
  let agentSocket;
48359
49205
  try {
48360
49206
  if (!process.send || !process.connected) {
48361
49207
  throw new RvwError("INVALID_INPUT", "\u5185\u90E8viewer worker\u306F\u89AACLI\u304B\u3089\u8D77\u52D5\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
48362
49208
  }
49209
+ const databaseFilePath = databasePathConfiguration().filePath;
49210
+ const startup = await acquireRuntimeOrReuseExisting(
49211
+ runtimeViewerOpenInput(reference, process.cwd(), port),
49212
+ databaseFilePath
49213
+ );
49214
+ if (startup.kind === "reused") {
49215
+ const result = startup.result;
49216
+ if (!await sendOpenWorkerMessage({ type: "ready", url: result.url })) return;
49217
+ if (!await sendOpenWorkerMessage({ type: "viewer-connected" })) return;
49218
+ await waitForWorkerParentDisconnect();
49219
+ return;
49220
+ }
49221
+ agentSocket = startup.agentSocket;
49222
+ activeRuntime = runtimeFactory();
48363
49223
  const opened = await activeRuntime.service.openPullRequest(reference, process.cwd());
48364
49224
  running = await startServer(activeRuntime.service, {
48365
49225
  port,
48366
49226
  staticDirectory: staticDirectory(),
48367
49227
  autoCloseWhenNoViewers: true
48368
49228
  });
48369
- agentSocket = await startAgentSocket(activeRuntime.service);
49229
+ agentSocket.setHandler(createRuntimeAgentSocketHandler(activeRuntime, running));
48370
49230
  const url2 = new URL(running.origin);
48371
49231
  url2.searchParams.set("pullRequestId", opened.pullRequest.id);
48372
49232
  if (!await sendOpenWorkerMessage({ type: "ready", url: url2.toString() })) return;
@@ -48386,15 +49246,7 @@ async function runOpenWorker(activeRuntime, reference, port) {
48386
49246
  await sendOpenWorkerMessage({ type: "viewer-connected" });
48387
49247
  await waitForServerShutdown(running.allViewersClosed);
48388
49248
  } finally {
48389
- try {
48390
- await agentSocket?.close();
48391
- } finally {
48392
- try {
48393
- await running?.close();
48394
- } finally {
48395
- activeRuntime.close();
48396
- }
48397
- }
49249
+ await closeOwnedRuntime(agentSocket, running, activeRuntime);
48398
49250
  }
48399
49251
  }
48400
49252
  var defaultRuntimeFactory = () => createRuntime();
@@ -48495,12 +49347,19 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
48495
49347
  `);
48496
49348
  return;
48497
49349
  }
48498
- await runOpenServer(getRuntime(), reference, options.port, options.open, useAgentSocket);
49350
+ await runOpenServer(
49351
+ getRuntime,
49352
+ reference,
49353
+ options.port,
49354
+ options.open,
49355
+ useAgentSocket,
49356
+ !options.foreground
49357
+ );
48499
49358
  }
48500
49359
  );
48501
49360
  program2.command("__open-worker", { hidden: true }).option("--reference <pull-request>").requiredOption("--port <port>", "listen port", parsePort).action(async (options) => {
48502
49361
  try {
48503
- await runOpenWorker(getRuntime(), options.reference, options.port);
49362
+ await runOpenWorker(getRuntime, options.reference, options.port);
48504
49363
  } catch (error51) {
48505
49364
  const rvwError = asRvwError(error51);
48506
49365
  const sent = await sendOpenWorkerMessage({
@@ -48861,8 +49720,12 @@ if (bundledCli || process.argv[1] && import.meta.url === pathToFileURL(process.a
48861
49720
  await runCli();
48862
49721
  }
48863
49722
  export {
49723
+ acquireRuntimeOrReuseExisting,
49724
+ closeOwnedRuntime,
48864
49725
  completeBackgroundOpen,
48865
49726
  createProgram,
49727
+ createRuntimeAgentSocketHandler,
49728
+ holdRuntimeViewerLease,
48866
49729
  runCli,
48867
49730
  startBackgroundOpen,
48868
49731
  waitForServerShutdown