dsh-plugin-dev-kb 1.8.0 → 1.8.1

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 (229) hide show
  1. package/CHANGELOG.md +186 -178
  2. package/README.en.md +172 -172
  3. package/README.md +183 -183
  4. package/kb/INDEX.md +236 -234
  5. package/kb/README.md +88 -88
  6. package/kb/extra/AGENTS.md +77 -77
  7. package/kb/extra/deepseek-llm-api-wire-extensions.md +162 -162
  8. package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +162 -162
  9. package/kb/extra/defensive-patterns/package-surface.md +233 -232
  10. package/kb/extra/defensive-patterns/package-surface.zh.md +233 -232
  11. package/kb/extra/defensive-patterns/remote-and-api.md +117 -117
  12. package/kb/extra/defensive-patterns/remote-and-api.zh.md +117 -117
  13. package/kb/extra/development.md +221 -221
  14. package/kb/extra/development.zh.md +228 -228
  15. package/kb/extra/event-producer-consumer.md +133 -123
  16. package/kb/extra/event-producer-consumer.zh.md +135 -125
  17. package/kb/extra/module-graph.md +1961 -1888
  18. package/kb/extra/module-graph.zh.md +1963 -1890
  19. package/kb/extra/persistence-changes/2026-11-05-timeline-priority-notice.md +60 -0
  20. package/kb/extra/persistence-changes/2026-11-05-timeline-priority-notice.zh.md +60 -0
  21. package/kb/extra/persistence-changes/README.md +92 -92
  22. package/kb/extra/persistence-changes/README.zh.md +92 -92
  23. package/kb/extra/persistence-changes/historical-formats/README.md +75 -75
  24. package/kb/extra/persistence-changes/historical-formats/README.zh.md +75 -75
  25. package/kb/extra/persistence-changes/releases/README.md +103 -103
  26. package/kb/extra/persistence-changes/releases/README.zh.md +103 -103
  27. package/kb/extra/rescope.md +55 -55
  28. package/kb/extra/rescope.zh.md +55 -55
  29. package/kb/extra/subsystems/boot.md +631 -621
  30. package/kb/extra/subsystems/boot.zh.md +631 -621
  31. package/kb/extra/subsystems/computer-use.md +179 -137
  32. package/kb/extra/subsystems/computer-use.zh.md +179 -137
  33. package/kb/extra/subsystems/extensions.md +387 -384
  34. package/kb/extra/subsystems/extensions.zh.md +387 -384
  35. package/kb/extra/subsystems/feedback.md +418 -400
  36. package/kb/extra/subsystems/feedback.zh.md +418 -400
  37. package/kb/extra/subsystems/forge.md +2169 -1226
  38. package/kb/extra/subsystems/forge.zh.md +2171 -1226
  39. package/kb/extra/subsystems/liveshare.md +167 -158
  40. package/kb/extra/subsystems/liveshare.zh.md +167 -158
  41. package/kb/extra/subsystems/mcp.md +237 -234
  42. package/kb/extra/subsystems/mcp.zh.md +237 -234
  43. package/kb/extra/subsystems/office-kit.md +122 -0
  44. package/kb/extra/subsystems/office-kit.zh.md +122 -0
  45. package/kb/extra/subsystems/working-directory.md +95 -64
  46. package/kb/extra/subsystems/working-directory.zh.md +95 -64
  47. package/kb/extra/subsystems/worktrees.md +187 -127
  48. package/kb/extra/subsystems/worktrees.zh.md +187 -127
  49. package/kb/extra/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.md +21 -21
  50. package/kb/extra/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.zh.md +21 -21
  51. package/kb/extra/upgrade-guide/v0.2.1-rc.4/sandbox-unsupported-policy-error/guide.md +18 -0
  52. package/kb/extra/upgrade-guide/v0.2.1-rc.4/sandbox-unsupported-policy-error/guide.zh.md +18 -0
  53. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/llm-error-builtin-codes/guide.md +18 -0
  54. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/llm-error-builtin-codes/guide.zh.md +18 -0
  55. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-kit-package/guide.md +34 -0
  56. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-kit-package/guide.zh.md +34 -0
  57. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-operation-tools/guide.md +49 -0
  58. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-operation-tools/guide.zh.md +49 -0
  59. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-skill-resources/guide.md +36 -0
  60. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-skill-resources/guide.zh.md +36 -0
  61. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-notification-fields/guide.md +26 -0
  62. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-notification-fields/guide.zh.md +26 -0
  63. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-remote-provider/guide.md +22 -0
  64. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-remote-provider/guide.zh.md +22 -0
  65. package/kb/extra/upgrade-guide/v0.2.2-rc.1/archive-worktrees/guide.md +21 -0
  66. package/kb/extra/upgrade-guide/v0.2.2-rc.1/archive-worktrees/guide.zh.md +21 -0
  67. package/kb/extra/upgrade-guide/v0.2.2-rc.1/attachment-error-codes/guide.md +23 -0
  68. package/kb/extra/upgrade-guide/v0.2.2-rc.1/attachment-error-codes/guide.zh.md +23 -0
  69. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-ax-read-limits/guide.md +20 -0
  70. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-ax-read-limits/guide.zh.md +20 -0
  71. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-hover-ax-only/guide.md +18 -0
  72. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-hover-ax-only/guide.zh.md +18 -0
  73. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-native-platform/guide.md +23 -0
  74. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-native-platform/guide.zh.md +23 -0
  75. package/kb/extra/upgrade-guide/v0.2.2-rc.1/dsh-doc-skill/guide.md +35 -0
  76. package/kb/extra/upgrade-guide/v0.2.2-rc.1/dsh-doc-skill/guide.zh.md +35 -0
  77. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-change-request-overview/guide.md +26 -0
  78. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-change-request-overview/guide.zh.md +26 -0
  79. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-pinned-diff/guide.md +26 -0
  80. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-pinned-diff/guide.zh.md +26 -0
  81. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-review-reads/guide.md +19 -0
  82. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-review-reads/guide.zh.md +19 -0
  83. package/kb/extra/upgrade-guide/v0.2.2-rc.1/package-export-cleanup/guide.md +26 -0
  84. package/kb/extra/upgrade-guide/v0.2.2-rc.1/package-export-cleanup/guide.zh.md +26 -0
  85. package/kb/extra/upgrade-guide/v0.2.2-rc.1/plugin-update-method/guide.md +22 -0
  86. package/kb/extra/upgrade-guide/v0.2.2-rc.1/plugin-update-method/guide.zh.md +22 -0
  87. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-bootstrap-protocol/guide.md +25 -0
  88. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-bootstrap-protocol/guide.zh.md +25 -0
  89. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-inspect-output/guide.md +48 -0
  90. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-inspect-output/guide.zh.md +48 -0
  91. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-scoped-repl-default/guide.md +45 -0
  92. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-scoped-repl-default/guide.zh.md +45 -0
  93. package/kb/extra/upgrade-guide/v0.2.2-rc.1/runtime-context-reminders/guide.md +19 -0
  94. package/kb/extra/upgrade-guide/v0.2.2-rc.1/runtime-context-reminders/guide.zh.md +19 -0
  95. package/kb/extra/upgrade-guide/v0.2.2-rc.1/session-append-overloads/guide.md +17 -0
  96. package/kb/extra/upgrade-guide/v0.2.2-rc.1/session-append-overloads/guide.zh.md +17 -0
  97. package/kb/extra/upgrade-guide/v0.2.2-rc.1/subagent-interrupt-default/guide.md +27 -0
  98. package/kb/extra/upgrade-guide/v0.2.2-rc.1/subagent-interrupt-default/guide.zh.md +27 -0
  99. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-backend-cleanup-error/guide.md +20 -0
  100. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-backend-cleanup-error/guide.zh.md +20 -0
  101. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-bundle-sidebar-backend/guide.md +19 -0
  102. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-bundle-sidebar-backend/guide.zh.md +19 -0
  103. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-fetch-transport-error-codes/guide.md +18 -0
  104. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-fetch-transport-error-codes/guide.zh.md +18 -0
  105. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-search-billing/guide.md +41 -0
  106. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-search-billing/guide.zh.md +41 -0
  107. package/kb/extra/upgrade-guide/v0.2.2-rc.1/window-drag-regions/guide.md +21 -0
  108. package/kb/extra/upgrade-guide/v0.2.2-rc.1/window-drag-regions/guide.zh.md +21 -0
  109. package/kb/extra/upgrade-guide/v0.2.2-rc.2/client-module-metadata/guide.md +29 -0
  110. package/kb/extra/upgrade-guide/v0.2.2-rc.2/client-module-metadata/guide.zh.md +29 -0
  111. package/kb/extra/upgrade-guide/v0.2.2-rc.2/connection-page-peer/guide.md +25 -0
  112. package/kb/extra/upgrade-guide/v0.2.2-rc.2/connection-page-peer/guide.zh.md +25 -0
  113. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cordis-root-and-intercept-metadata/guide.md +31 -0
  114. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cordis-root-and-intercept-metadata/guide.zh.md +31 -0
  115. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-agents-without-preset/guide.md +24 -0
  116. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-agents-without-preset/guide.zh.md +24 -0
  117. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-macos-provider/guide.md +24 -0
  118. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-macos-provider/guide.zh.md +24 -0
  119. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-partial-observations/guide.md +19 -0
  120. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-partial-observations/guide.zh.md +19 -0
  121. package/kb/extra/upgrade-guide/v0.2.2-rc.2/deepseek-files-timeout/guide.md +15 -0
  122. package/kb/extra/upgrade-guide/v0.2.2-rc.2/deepseek-files-timeout/guide.zh.md +15 -0
  123. package/kb/extra/upgrade-guide/v0.2.2-rc.2/experimental-tool-error-codes/guide.md +28 -0
  124. package/kb/extra/upgrade-guide/v0.2.2-rc.2/experimental-tool-error-codes/guide.zh.md +28 -0
  125. package/kb/extra/upgrade-guide/v0.2.2-rc.2/package-export-cleanup/guide.md +36 -0
  126. package/kb/extra/upgrade-guide/v0.2.2-rc.2/package-export-cleanup/guide.zh.md +36 -0
  127. package/kb/extra/upgrade-guide/v0.2.2-rc.2/python-ptc-removal/guide.md +22 -0
  128. package/kb/extra/upgrade-guide/v0.2.2-rc.2/python-ptc-removal/guide.zh.md +22 -0
  129. package/kb/extra/upgrade-guide/v0.2.2-rc.2/session-status-package/guide.md +31 -0
  130. package/kb/extra/upgrade-guide/v0.2.2-rc.2/session-status-package/guide.zh.md +31 -0
  131. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shared-search-filter/guide.md +18 -0
  132. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shared-search-filter/guide.zh.md +18 -0
  133. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shell-fs-tool-error-codes/guide.md +30 -0
  134. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shell-fs-tool-error-codes/guide.zh.md +30 -0
  135. package/kb/extra/web-styling.md +57 -57
  136. package/kb/extra/web-styling.zh.md +57 -57
  137. package/kb/meta/search-index.json +4508 -3980
  138. package/kb/meta/source.json +18 -18
  139. package/kb/site/develop/practice/llm-adapter.md +192 -192
  140. package/kb/site/en/develop/practice/llm-adapter.md +192 -192
  141. package/kb/site/en/guide/desktop-uninstall-cleanup.md +63 -0
  142. package/kb/site/en/guide/plugin-market.md +153 -151
  143. package/kb/site/en/guide/providers.md +234 -234
  144. package/kb/site/en/guide/quickstart.md +35 -34
  145. package/kb/site/en/reference/capability-seams.md +789 -767
  146. package/kb/site/en/reference/config-catalog.md +5432 -5128
  147. package/kb/site/en/reference/cookbook/adding-a-package.md +175 -175
  148. package/kb/site/en/reference/cookbook/adding-a-tool.md +105 -104
  149. package/kb/site/en/reference/cookbook/extension-cookbook.md +178 -135
  150. package/kb/site/en/reference/cordis-api/context.md +373 -369
  151. package/kb/site/en/reference/cordis-api/fiber.md +457 -433
  152. package/kb/site/en/reference/cordis-api/inherited.md +44 -44
  153. package/kb/site/en/reference/cordis-api/service.md +118 -107
  154. package/kb/site/en/reference/experimental-persistence-catalog.md +36 -23
  155. package/kb/site/en/reference/index.md +174 -174
  156. package/kb/site/en/reference/persistence-catalog.md +9453 -9432
  157. package/kb/site/en/reference/subsystems/compaction.md +288 -288
  158. package/kb/site/en/reference/subsystems/core.md +1172 -1171
  159. package/kb/site/en/reference/subsystems/credentials.md +526 -497
  160. package/kb/site/en/reference/subsystems/filesystem.md +708 -708
  161. package/kb/site/en/reference/subsystems/index.md +82 -82
  162. package/kb/site/en/reference/subsystems/llm-streaming.md +1290 -1288
  163. package/kb/site/en/reference/subsystems/lsp.md +230 -206
  164. package/kb/site/en/reference/subsystems/persistence.md +451 -432
  165. package/kb/site/en/reference/subsystems/plan.md +91 -91
  166. package/kb/site/en/reference/subsystems/ptc-runtime.md +295 -261
  167. package/kb/site/en/reference/subsystems/sandbox.md +250 -232
  168. package/kb/site/en/reference/subsystems/session-query.md +525 -515
  169. package/kb/site/en/reference/subsystems/session-reference.md +231 -229
  170. package/kb/site/en/reference/subsystems/session.md +1478 -1431
  171. package/kb/site/en/reference/subsystems/sidebar-right.md +165 -165
  172. package/kb/site/en/reference/subsystems/skills.md +356 -356
  173. package/kb/site/en/reference/subsystems/slots.md +235 -231
  174. package/kb/site/en/reference/subsystems/terminal.md +188 -188
  175. package/kb/site/en/reference/subsystems/tools.md +902 -891
  176. package/kb/site/en/reference/subsystems/user-questions.md +330 -327
  177. package/kb/site/en/reference/subsystems/web-client.md +161 -104
  178. package/kb/site/en/reference/subsystems/web-server.md +434 -351
  179. package/kb/site/en/reference/subsystems/web.md +268 -268
  180. package/kb/site/en/reference/subsystems/workflow.md +287 -282
  181. package/kb/site/en/reference/subsystems/workspace.md +743 -651
  182. package/kb/site/en/reference/tool-catalog.md +2867 -2704
  183. package/kb/site/guide/desktop-uninstall-cleanup.md +63 -0
  184. package/kb/site/guide/plugin-market.md +153 -151
  185. package/kb/site/guide/providers.md +234 -234
  186. package/kb/site/guide/quickstart.md +35 -34
  187. package/kb/site/reference/capability-seams.md +789 -767
  188. package/kb/site/reference/config-catalog.md +5432 -5128
  189. package/kb/site/reference/cookbook/adding-a-package.md +175 -175
  190. package/kb/site/reference/cookbook/adding-a-tool.md +107 -106
  191. package/kb/site/reference/cookbook/extension-cookbook.md +182 -139
  192. package/kb/site/reference/cordis-api/context.md +373 -369
  193. package/kb/site/reference/cordis-api/fiber.md +459 -435
  194. package/kb/site/reference/cordis-api/inherited.md +44 -44
  195. package/kb/site/reference/cordis-api/service.md +118 -107
  196. package/kb/site/reference/experimental-persistence-catalog.md +36 -23
  197. package/kb/site/reference/index.md +178 -178
  198. package/kb/site/reference/persistence-catalog.md +9453 -9432
  199. package/kb/site/reference/subsystems/compaction.md +288 -288
  200. package/kb/site/reference/subsystems/core.md +1184 -1183
  201. package/kb/site/reference/subsystems/credentials.md +526 -497
  202. package/kb/site/reference/subsystems/filesystem.md +708 -708
  203. package/kb/site/reference/subsystems/index.md +82 -82
  204. package/kb/site/reference/subsystems/llm-streaming.md +1296 -1294
  205. package/kb/site/reference/subsystems/lsp.md +230 -206
  206. package/kb/site/reference/subsystems/persistence.md +451 -432
  207. package/kb/site/reference/subsystems/plan.md +91 -91
  208. package/kb/site/reference/subsystems/ptc-runtime.md +295 -261
  209. package/kb/site/reference/subsystems/sandbox.md +250 -232
  210. package/kb/site/reference/subsystems/session-query.md +525 -515
  211. package/kb/site/reference/subsystems/session-reference.md +231 -229
  212. package/kb/site/reference/subsystems/session.md +1482 -1435
  213. package/kb/site/reference/subsystems/sidebar-right.md +165 -165
  214. package/kb/site/reference/subsystems/skills.md +356 -356
  215. package/kb/site/reference/subsystems/slots.md +239 -235
  216. package/kb/site/reference/subsystems/terminal.md +188 -188
  217. package/kb/site/reference/subsystems/tools.md +902 -891
  218. package/kb/site/reference/subsystems/user-questions.md +330 -327
  219. package/kb/site/reference/subsystems/web-client.md +161 -104
  220. package/kb/site/reference/subsystems/web-server.md +434 -351
  221. package/kb/site/reference/subsystems/web.md +268 -268
  222. package/kb/site/reference/subsystems/workflow.md +287 -282
  223. package/kb/site/reference/subsystems/workspace.md +743 -651
  224. package/kb/site/reference/tool-catalog.md +2872 -2709
  225. package/lib/index.js +359 -359
  226. package/package.json +68 -68
  227. package/skills/dsh-plugin-dev-kb.md +71 -70
  228. package/kb/extra/subsystems/office-to-pdf.md +0 -87
  229. package/kb/extra/subsystems/office-to-pdf.zh.md +0 -87
@@ -1,1226 +1,2169 @@
1
- # Forges
2
-
3
- English | [中文](forge.zh.md)
4
-
5
- The experimental forge capability seam reads change requests, meaning pull requests and merge requests, from code hosts. [`dsh-experimental-forge`](../../packages/experimental/forge/README.md) is the Service Definition and provides `ctx.forge`; [`dsh-experimental-forge-github`](../../packages/experimental/forge-github/README.md) is the Service Provider for GitHub hosts; [`dsh-experimental-forge-session`](../../packages/experimental/forge-session/README.md) is the Consumer that binds change requests to Sessions and provides `ctx.forgeSession`; [`dsh-experimental-client-ui-forge`](../../packages/experimental/client-ui-forge/README.md) is the GUI Consumer that provides `ctx.forgePanel`. The default-disabled [forge bundle](../../packages/experimental/forge-bundle/README.md) composes all four, with the [worktree model tool](../../packages/experimental/tool-worktree/README.md) and an isolated [worktree service](../../packages/experimental/worktree/README.md) for the panel, from the Plugins page; otherwise they load only through explicit composition.
6
-
7
- ## Providers and change requests
8
-
9
- A provider serves one forge family, such as `github`. It maps Git remote URLs to repositories and change request URLs to references, builds canonical change request URLs, finds open change requests by head branch, and reads status in batches. `changeRequestKey` derives one case-insensitive key from a reference, so watchers of differently spelled URLs share one polling loop.
10
-
11
- ```ts type-equiv
12
- /** One repository on a code host, identified the way its provider addresses it. */
13
- interface ForgeRepository {
14
- /** Provider family that serves the repository, such as `github`. */
15
- readonly forge: string
16
- /** Lowercase host name, such as `github.com`. */
17
- readonly host: string
18
- /** Owner or namespace path as the host spells it. */
19
- readonly owner: string
20
- /** Repository name as the host spells it. */
21
- readonly name: string
22
- }
23
- ```
24
-
25
- ```ts type-equiv
26
- /** One change request (pull request or merge request) in a repository. */
27
- interface ChangeRequestRef {
28
- /** Repository that holds the change request. */
29
- readonly repository: ForgeRepository
30
- /** Host-assigned change request number. */
31
- readonly number: number
32
- }
33
- ```
34
-
35
- ```ts type-equiv
36
- /** Canonical identity of a change request across providers; built by `changeRequestKey`. */
37
- type ChangeRequestKey = Branded<'ChangeRequestKey'>
38
- ```
39
-
40
- ```ts type-equiv
41
- /**
42
- * Host-specific access registered with `ctx.forge`. One provider serves one
43
- * forge family; methods that take a signal must stop work when it aborts.
44
- */
45
- interface ForgeProvider {
46
- /** Forge family name stored in every repository this provider returns. */
47
- readonly forge: string
48
- /** Publish a comment or completed review. Omission declares that this provider cannot write reviews. */
49
- submitReview?(ref: ChangeRequestRef, request: ForgeReviewRequest, signal: AbortSignal): Promise<ForgeReviewResult>
50
- /** Map a Git remote URL to its repository, or undefined when this provider does not serve it. */
51
- repositoryFromRemote(url: string): ForgeRepository | undefined
52
- /** Map a change request web URL to its reference, or undefined when this provider does not serve it. */
53
- changeRequestFromUrl(url: string): ChangeRequestRef | undefined
54
- /** Build the canonical web URL of a change request this provider serves. */
55
- changeRequestUrl(ref: ChangeRequestRef): string
56
- /** Read the status of several change requests, returning one result per requested key. */
57
- fetch(
58
- refs: readonly ChangeRequestRef[],
59
- signal: AbortSignal,
60
- ): Promise<ReadonlyMap<ChangeRequestKey, ChangeRequestSnapshot | ForgeFailure>>
61
- /** Read all checks for one change request on demand; omission declares that only the polling preview is available. */
62
- fetchChecks?(ref: ChangeRequestRef, signal: AbortSignal): Promise<ChangeRequestSnapshot | ForgeFailure>
63
- /** Find open change requests whose head is `branch` in `repository`, excluding the default branch and forks. */
64
- findByHead(repository: ForgeRepository, branch: string, signal: AbortSignal): Promise<readonly ChangeRequestRef[]>
65
- /** Report whether the provider can reach `host` with current credentials. */
66
- readiness(host: string, signal: AbortSignal): Promise<ForgeReadiness>
67
- /** Mark a draft change request ready for review, resolving to undefined once the host accepted it. */
68
- markReady(ref: ChangeRequestRef, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
69
- /** Merge a change request while its head is still `request.headSha`, resolving to undefined once merged. */
70
- merge(ref: ChangeRequestRef, request: MergeRequest, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
71
- /** Read the changes of a change request as the host computes them, as one unified patch with `a/` and `b/` path prefixes. */
72
- diff(ref: ChangeRequestRef, signal: AbortSignal): Promise<string | ForgeFailure>
73
- /** Read the text of `path` at `commit` in the change request's repository. */
74
- fileText(ref: ChangeRequestRef, commit: string, path: string, signal: AbortSignal): Promise<string | ForgeFailure>
75
- /** Build the web page of `path` at `commit` in the change request's repository. */
76
- fileUrl(ref: ChangeRequestRef, commit: string, path: string): string
77
- }
78
- ```
79
-
80
- ```ts type-equiv
81
- /** Whether a provider can reach one host with the current credentials. */
82
- interface ForgeReadiness {
83
- /** `ready`, `unauthenticated` (sign-in needed), or `unavailable` (tool missing or host unreachable). */
84
- readonly state: 'ready' | 'unauthenticated' | 'unavailable'
85
- /** Human-readable detail, or null when ready. */
86
- readonly message: string | null
87
- }
88
- ```
89
-
90
- ## Status polling
91
-
92
- `ctx.forge.watch` shares one polling loop among the watchers of a change request and delivers every result as an update. Each forge has at most one fetch in flight. The [registry README](../../packages/experimental/forge/README.md#use-this-package) owns the poll cadence, backoff, and timeout rules.
93
-
94
- ```ts type-equiv
95
- /** Lifecycle state of a change request. */
96
- type ChangeRequestState = 'open' | 'closed' | 'merged'
97
- ```
98
-
99
- ```ts type-equiv
100
- /** Normalized outcome of one status check. */
101
- type CheckState = 'pending' | 'success' | 'failure' | 'neutral' | 'skipped' | 'cancelled'
102
- ```
103
-
104
- ```ts type-equiv
105
- /** One status check reported for the head commit of a change request. */
106
- interface ChangeRequestCheck {
107
- /** Check name as the host displays it. */
108
- readonly name: string
109
- /** Normalized check outcome. */
110
- readonly state: CheckState
111
- /** Web page for the check, or null when the host reports none. */
112
- readonly url: string | null
113
- /** Workflow that produced the check, or null when the host reports none. */
114
- readonly workflow: string | null
115
- /** ISO 8601 time the check started, or null before it starts and when the host reports none. */
116
- readonly startedAt: string | null
117
- /** ISO 8601 time the check completed, or null while it runs and when the host reports none. */
118
- readonly completedAt: string | null
119
- }
120
- ```
121
-
122
- ```ts type-equiv
123
- /** Combined outcome of all checks on the head commit. */
124
- type ChecksRollup = 'none' | 'pending' | 'success' | 'failure'
125
- ```
126
-
127
- ```ts type-equiv
128
- /** Whether the change request can merge into its base without conflicts. */
129
- type Mergeability = 'mergeable' | 'conflicting' | 'unknown'
130
- ```
131
-
132
- ```ts type-equiv
133
- /** Aggregate review outcome required by the host's merge rules. */
134
- type ReviewDecision = 'approved' | 'changes-requested' | 'review-required' | 'none'
135
- ```
136
-
137
- ```ts type-equiv
138
- /** Status of one change request at the time its provider read it. */
139
- interface ChangeRequestSnapshot {
140
- /** Change request the snapshot describes. */
141
- readonly ref: ChangeRequestRef
142
- /** Web page of the change request. */
143
- readonly url: string
144
- /** Title as the author wrote it. */
145
- readonly title: string
146
- /** Lifecycle state. */
147
- readonly state: ChangeRequestState
148
- /** Whether the author marked the change request as a draft. */
149
- readonly draft: boolean
150
- /** Author login, or null when the host hides it. */
151
- readonly author: string | null
152
- /** Branch that carries the proposed commits. */
153
- readonly headBranch: string
154
- /** Commit object name at the head of the change request. */
155
- readonly headSha: string
156
- /** Branch the change request merges into. */
157
- readonly baseBranch: string
158
- /** Lines the change request adds. */
159
- readonly additions: number
160
- /** Lines the change request deletes. */
161
- readonly deletions: number
162
- /** Merge conflict status; `unknown` while the host computes it. */
163
- readonly mergeability: Mergeability
164
- /**
165
- * Whether the host would merge the change request now for the signed-in
166
- * user: open, not a draft, and meeting the base branch's merge rules.
167
- */
168
- readonly mergeReady: boolean
169
- /** Merge methods the repository accepts, the signed-in user's usual one first. */
170
- readonly mergeMethods: readonly MergeMethod[]
171
- /** Aggregate review outcome. */
172
- readonly reviewDecision: ReviewDecision
173
- /** Checks on the head commit in host order. */
174
- readonly checks: readonly ChangeRequestCheck[]
175
- /** Combined check outcome. */
176
- readonly checksRollup: ChecksRollup
177
- /** Whether the host reported more checks than `checks` contains. */
178
- readonly checksTruncated: boolean
179
- /** Number of review threads that are not resolved. */
180
- readonly unresolvedThreads: number
181
- /** ISO 8601 time of the host's last update to the change request. */
182
- readonly updatedAt: string
183
- /** ISO 8601 time the change request was merged or closed, or null while it is open. */
184
- readonly closedAt: string | null
185
- }
186
- ```
187
-
188
- ```ts type-equiv
189
- /** Why a provider could not read a change request. */
190
- interface ForgeFailure {
191
- /**
192
- * `not-found`: the host does not expose it; `unauthenticated`: credentials are missing or rejected;
193
- * `too-large`: the host or the provider refuses an answer over its size limit, so reading again fails
194
- * the same way; `unavailable`: any other failure.
195
- */
196
- readonly code: 'not-found' | 'unauthenticated' | 'too-large' | 'unavailable'
197
- /** Human-readable cause. */
198
- readonly message: string
199
- }
200
- ```
201
-
202
- ```ts type-equiv
203
- /** Latest polling result delivered to watchers of one change request. */
204
- type ChangeRequestUpdate = {
205
- /** Canonical identity of the watched change request. */
206
- readonly key: ChangeRequestKey
207
- /** Epoch milliseconds when the result settled. */
208
- readonly fetchedAt: number
209
- } & ({
210
- /** Status read from the host. */
211
- readonly snapshot: ChangeRequestSnapshot
212
- } | {
213
- /** Reason the status could not be read. */
214
- readonly failure: ForgeFailure
215
- })
216
- ```
217
-
218
- ```ts type-equiv
219
- /**
220
- * Receives every polling result for one watched change request.
221
- * @param update - the settled result.
222
- */
223
- type ChangeRequestListener = (update: ChangeRequestUpdate) => void
224
- ```
225
-
226
- ## Change request actions
227
-
228
- `ctx.forge.markReady` marks a draft ready for review, and `ctx.forge.merge` merges a change request only while its head is still the commit the user confirmed. Both resolve to a `ForgeActionFailure` instead of throwing when the host refuses, and poll a watched change request at once afterwards. A snapshot's `mergeReady` and `mergeMethods` tell a consumer when to offer a merge and with which methods; the [GitHub provider README](../../packages/experimental/forge-github/README.md#use-this-package) owns how GitHub answers both.
229
-
230
- ```ts type-equiv
231
- /** A merge the user confirmed for the head commit they saw. */
232
- interface MergeRequest {
233
- /** How the commits enter the base branch. */
234
- readonly method: MergeMethod
235
- /** Commit object name the head must still have; the merge is refused once the head moves. */
236
- readonly headSha: string
237
- }
238
- ```
239
-
240
- ```ts type-equiv
241
- /** Why a provider could not change a change request. */
242
- interface ForgeActionFailure {
243
- /**
244
- * `unauthenticated`: credentials are missing or rejected; `stale`: the head
245
- * moved past the confirmed commit; `failed`: the host refused or could not be reached.
246
- */
247
- readonly code: 'unauthenticated' | 'stale' | 'failed'
248
- /** Human-readable cause, usually the host's own message. */
249
- readonly message: string
250
- }
251
- ```
252
-
253
- `ctx.forge.submitReview` delegates an explicit comment or completed review to the provider. A provider that omits this capability returns unsupported. Consumers retain pending drafts and reconcile unknown write outcomes before resubmitting.
254
-
255
- ```ts type-equiv
256
- /** One inline comment addressed to the old or new side of a pull request diff. */
257
- interface ForgeLineComment {
258
- readonly path: string
259
- readonly side: 'L' | 'R'
260
- readonly startLine: number
261
- readonly endLine: number
262
- readonly body: string
263
- }
264
- ```
265
-
266
- ```ts type-equiv
267
- /** A single published comment or one completed review containing all pending comments. */
268
- type ForgeReviewRequest = {
269
- /** Commit the user reviewed; providers must check it before writing and address that commit explicitly. */
270
- readonly headSha: string
271
- } & (
272
- | { readonly kind: 'comment'; readonly comment: ForgeLineComment }
273
- | { readonly kind: 'review'; readonly event: 'comment' | 'approve' | 'request-changes'; readonly body: string; readonly comments: readonly ForgeLineComment[] }
274
- )
275
- ```
276
-
277
- ```ts type-equiv
278
- /** Result of a review write; unknown outcomes must be checked on the host before another submission. */
279
- type ForgeReviewResult =
280
- | { readonly ok: true; readonly url: string }
281
- | { readonly ok: false; readonly code: 'unsupported' | 'stale' | 'failed' | 'unknown'; readonly message: string }
282
- ```
283
-
284
- ## Session bindings
285
-
286
- `ctx.forgeSession` derives each Session's bindings from its own log: `gh pr create` results bind the URLs they print, `gh pr view`, `gh pr edit`, and `gh pr checkout` calls bind the change request they act on, and `git push` results trigger head-branch discovery through `ctx.forge`; the panel also reports the branch its checkout tracks for the same discovery. Track, dismiss, and discovery actions are `plugin:forge-session/binding` records, which stay outside the model-visible conversation. The `forgeChangeRequests` projection's client view, which the Session summary carries, lists the bindings without their status. The [Session binding README](../../packages/experimental/forge-session/README.md#use-this-package) owns the recognition and discovery rules.
287
-
288
- ```ts type-equiv
289
- /**
290
- * How a change request became bound to a Session: `created` when a
291
- * `gh pr create` result printed its URL, `referenced` when a `gh pr view`,
292
- * `gh pr edit`, or `gh pr checkout` call named it or its result printed its
293
- * own URL, `discovered` when a lookup found it for a branch the Session pushed
294
- * or works on, `tracked` when a caller bound it by URL.
295
- */
296
- type BindingOrigin = 'created' | 'referenced' | 'discovered' | 'tracked'
297
- ```
298
-
299
- ```ts type-equiv
300
- /** Payload of the `plugin:forge-session/binding` record. */
301
- interface BindingRecord {
302
- /**
303
- * `track` binds or restores, `dismiss` hides, `discover` binds a branch lookup,
304
- * and `reference` binds a CLI selector lookup without restoring dismissed entries.
305
- */
306
- readonly action: 'track' | 'dismiss' | 'discover' | 'reference'
307
- /** Web URL of the change request. */
308
- readonly url: string
309
- }
310
- ```
311
-
312
- ```ts type-equiv
313
- /** One change request bound to a Session and not dismissed, with its latest polling result. */
314
- interface SessionChangeRequest {
315
- /** Web URL as first recorded. */
316
- readonly url: string
317
- /** How the change request became bound. */
318
- readonly origin: BindingOrigin
319
- /** Parsed reference, or null when no registered provider serves the URL. */
320
- readonly ref: ChangeRequestRef | null
321
- /** Latest polling result, or null before the first poll and without a provider. */
322
- readonly update: ChangeRequestUpdate | null
323
- }
324
- ```
325
-
326
- ```ts type-equiv
327
- /**
328
- * Receives the complete visible list after a binding or provider change, or a polling result.
329
- * @param changeRequests - every visible binding of the watched Session, in first-recorded order.
330
- */
331
- type SessionChangeRequestsListener = (changeRequests: readonly SessionChangeRequest[]) => void
332
- ```
333
-
334
- ## Panel
335
-
336
- `ctx.forgePanel` is the Host Remote of the [forge panel](../../packages/experimental/client-ui-forge/README.md#use-this-package). Following a Session streams a `ForgePanelState`: a read-only Git inspection of the Session's current directory in its execution world, the code host recognized from the upstream remote or `origin`, the sign-in state of that host, the Session's bindings with their polled status and last snapshot, and its worktree request. Its actions list base branches, bind and hide URLs, refresh, mark a bound draft ready or merge a bound change request, request or withdraw a worktree, stop a running creation, and restore a removed worktree or leave it for the source checkout. A request is a `plugin:client-ui-forge/worktree` record. When a message enters a Session's inbox while its Agent is idle, the panel holds the message's turn with maintenance of the [Agent](../../packages/core/agent/README.md), started before the delivery wakes the Agent: it creates the worktree of an open request through `ctx.worktrees` and, after a failed or stopped attempt, waits for another attempt or the request's withdrawal; for a Session that works in its created worktree it checks that worktree and, when it is gone, waits until it is restored or left. The panel state names what a message waits for and the last failed attempt. The first step proposal creates the worktree for a message no hold took. Requests after the first turn starts are ignored. Changes to a Session's checkout run as maintenance of its Agent, so a message from another client waits for them.
337
-
338
- ```ts type-equiv
339
- /** Git facts for the checkout that contains the Session's current directory. */
340
- interface ForgeCheckout {
341
- /** Top-level directory of the checkout. */
342
- readonly root: string
343
- /** Checked-out branch, or null when HEAD is detached. */
344
- readonly branch: string | null
345
- /** Whether the checkout is a linked worktree of another checkout's repository. */
346
- readonly linked: boolean
347
- /** Abbreviated commit HEAD names, or null while the checked-out branch has no commit. */
348
- readonly commit: string | null
349
- }
350
- ```
351
-
352
- ```ts type-equiv
353
- /** Inspection result for the Session's current directory. */
354
- type ForgeCheckoutState =
355
- /** The first inspection has not finished. */
356
- | { readonly state: 'pending' }
357
- /** The current directory is not inside a Git checkout. */
358
- | { readonly state: 'none' }
359
- /** Git could not run or reported an error. */
360
- | { readonly state: 'failed'; readonly message: string }
361
- /** The checkout facts. */
362
- | { readonly state: 'ready'; readonly checkout: ForgeCheckout }
363
- ```
364
-
365
- ```ts type-equiv
366
- /** The worktree the panel created and entered for a Session before its first step. */
367
- interface ForgeSessionWorktree {
368
- /** Canonical checkout directory. */
369
- readonly path: string
370
- /** Branch created for the checkout. */
371
- readonly branch: string
372
- /** Requested base branch, or null when the checkout started from HEAD of the source checkout. */
373
- readonly from: string | null
374
- /** Commit object name the branch started from. */
375
- readonly baseCommit: string
376
- /** Canonical root of the source checkout. */
377
- readonly repositoryRoot: string
378
- }
379
- ```
380
-
381
- ```ts type-equiv
382
- /** Payload of the `plugin:client-ui-forge/worktree` record. */
383
- type WorktreeRecord =
384
- /** Create a worktree before the Session's first step, starting from `from`, or from HEAD when it is null. */
385
- | { readonly action: 'request'; readonly from: string | null }
386
- /** Withdraw the open request. */
387
- | { readonly action: 'cancel' }
388
- /** The requested worktree was created and became the Session's working directory. */
389
- | { readonly action: 'created'; readonly worktree: ForgeSessionWorktree }
390
- /** Restoration started; the checkout stays unavailable until restoration completes. */
391
- | { readonly action: 'restoring' }
392
- /** The recorded worktree was completely restored. */
393
- | { readonly action: 'restored' }
394
- ```
395
-
396
- ```ts type-equiv
397
- /** One open worktree request. */
398
- interface ForgeWorktreeRequest {
399
- /** Base branch, or null for HEAD of the source checkout. */
400
- readonly from: string | null
401
- }
402
- ```
403
-
404
- ```ts type-equiv
405
- /** Why the Session's checkout cannot host a new worktree. */
406
- type ForgeWorktreeBlocker =
407
- /** The current directory is not inside a Git checkout. */
408
- | 'not-git'
409
- /** The Session's sandbox cannot write the checkout or its Git directory, as for a linked worktree outside the writable root. */
410
- | 'outside-writable-root'
411
- /** The Session runs read-only. */
412
- | 'read-only'
413
- ```
414
-
415
- ```ts type-equiv
416
- /** One running worktree creation. */
417
- interface ForgeWorktreeCreation {
418
- /** Running step: `prepare` validates and creates the branch, then `checkout` writes the files. */
419
- readonly phase: 'prepare' | 'checkout'
420
- /** Branch the attempt creates. */
421
- readonly branch: string
422
- /** Base branch, or null for HEAD. */
423
- readonly from: string | null
424
- /**
425
- * Whether a message waits for the creation before its turn starts. Only
426
- * such a creation can be stopped, after which the message keeps waiting; a
427
- * creation at the first step serves a message the Session already holds.
428
- */
429
- readonly held: boolean
430
- }
431
- ```
432
-
433
- ```ts type-equiv
434
- /** One running restoration of the removed worktree a Session works in. */
435
- interface ForgeWorktreeRestoration {
436
- /** Running step: `prepare` registers the checkout again, creating a missing branch, then `checkout` writes the files. */
437
- readonly phase: 'prepare' | 'checkout'
438
- }
439
- ```
440
-
441
- ```ts type-equiv
442
- /** The last attempt to create the requested worktree for a message, which failed. */
443
- interface ForgeWorktreeFailure {
444
- /** Branch the attempt created, or null when it failed before naming one. */
445
- readonly branch: string | null
446
- /** Base branch of the attempt, or null for HEAD. */
447
- readonly from: string | null
448
- /** Step that failed. */
449
- readonly phase: 'prepare' | 'checkout'
450
- /** Failure text. */
451
- readonly message: string
452
- }
453
- ```
454
-
455
- ```ts type-equiv
456
- /**
457
- * What a message waits for before its turn starts: the creation of the
458
- * requested worktree, or the restoration or leaving of the removed worktree
459
- * the Session works in.
460
- */
461
- type ForgeWorktreeWaiting = 'creation' | 'restoration'
462
- ```
463
-
464
- ```ts type-equiv
465
- /** The recorded worktree whose directory is missing, invalid, or incompletely restored. */
466
- interface ForgeMissingWorktree {
467
- /** Whether its branch exists, or null when an incomplete restoration cannot inspect it. */
468
- readonly branchExists: boolean | null
469
- }
470
- ```
471
-
472
- ```ts type-equiv
473
- /** Worktree facts of one Session in the panel state. */
474
- interface ForgePanelWorktree {
475
- /**
476
- * Whether the Session can still request or withdraw a worktree: a worktree
477
- * service is mounted, no turn has started, and none was created.
478
- */
479
- readonly editable: boolean
480
- /** Why the checkout cannot host a worktree, or null when it can, before the first inspection, and after a failed one. */
481
- readonly blocker: ForgeWorktreeBlocker | null
482
- /** Open request, or null without one. */
483
- readonly request: ForgeWorktreeRequest | null
484
- /** Created worktree, or null before creation. */
485
- readonly created: ForgeSessionWorktree | null
486
- /** Running creation, or null. */
487
- readonly creation: ForgeWorktreeCreation | null
488
- /**
489
- * The last failed attempt of the creation a message waits for, or null. It
490
- * clears when another attempt starts, when the request changes or is
491
- * withdrawn, and when the wait ends.
492
- */
493
- readonly failure: ForgeWorktreeFailure | null
494
- /** The created worktree the Session works in when its directory is gone, or null. */
495
- readonly missing: ForgeMissingWorktree | null
496
- /** Running restoration of the removed worktree, or null. */
497
- readonly restoration: ForgeWorktreeRestoration | null
498
- /** What a message of the Session waits for before its turn starts, or null while none waits. */
499
- readonly waiting: ForgeWorktreeWaiting | null
500
- }
501
- ```
502
-
503
- ```ts type-equiv
504
- /** One bound change request in the panel state. */
505
- interface ForgePanelChangeRequest extends SessionChangeRequest {
506
- /** Latest snapshot a poll returned, kept while later polls fail, or null before the first successful poll. */
507
- readonly snapshot: ChangeRequestSnapshot | null
508
- }
509
- ```
510
-
511
- ```ts type-equiv
512
- /** Everything the panel shows for one Session. */
513
- interface ForgePanelState {
514
- /** Current working directory of the Session. */
515
- readonly directory: string
516
- /** Inspection result for `directory`. */
517
- readonly checkout: ForgeCheckoutState
518
- /** Code host repository of the checkout's upstream remote or `origin`, or null when no provider serves them. */
519
- readonly repository: ForgeRepository | null
520
- /** Sign-in state for the repository's host, or null before the first probe and without a repository. */
521
- readonly readiness: ForgeReadiness | null
522
- /** Visible change request bindings with their latest polling results, in first-recorded order. */
523
- readonly changeRequests: readonly ForgePanelChangeRequest[]
524
- /** Worktree request and creation state. */
525
- readonly worktree: ForgePanelWorktree
526
- /**
527
- * Files the `branch` scope changes, or null before the first inspection,
528
- * outside a checkout, and without a fork point.
529
- */
530
- readonly local: ForgeLocalChanges | null
531
- }
532
- ```
533
-
534
- ```ts type-equiv
535
- /** Local branches offered as the base of a new worktree. */
536
- interface ForgeBranches {
537
- /** Checked-out branch, or null when HEAD is detached. */
538
- readonly current: string | null
539
- /** Local branch names, `current` first and then most recently committed first, at most `maxBranches`. */
540
- readonly branches: readonly string[]
541
- }
542
- ```
543
-
544
- The Session list reads through the same Remote. `changeRequestStates` streams `ForgeChangeRequestStatesFrame` values: a `baseline` with the last known `ForgeChangeRequestListState` of every change request that a listed Session the caller may read binds, then the states that change. One Host run serves every stream; it takes the bindings of loaded Sessions from `ctx.forgeSession` and those of other Sessions from their cached Session summary without loading them, keeps each bound change request subscribed, including closed requests polled at the idle cadence to detect reopening, and stores every change, so a restart shows the last known states before the next poll. The Session summary also carries the `forgeWorktree` projection's client view: the created worktree while the Session's directory is that worktree or lies below it, else null.
545
-
546
- ```ts type-equiv
547
- /**
548
- * The last polled state of one change request that a listed Session binds, as
549
- * the Session list shows it.
550
- */
551
- type ForgeChangeRequestListState = Pick<
552
- ChangeRequestSnapshot, 'url' | 'title' | 'state' | 'draft' | 'checksRollup' | 'reviewDecision' | 'mergeability'
553
- > & {
554
- /** Change request number on its code host. */
555
- readonly number: number
556
- }
557
- ```
558
-
559
- ```ts type-equiv
560
- /**
561
- * One frame of `forgePanel.changeRequestStates`: the known state of every
562
- * change request the reader may see first, then the states that changed or
563
- * became visible since the previous frame.
564
- */
565
- type ForgeChangeRequestStatesFrame =
566
- | { readonly type: 'baseline'; readonly states: readonly ForgeChangeRequestListState[] }
567
- | { readonly type: 'changed'; readonly states: readonly ForgeChangeRequestListState[] }
568
- ```
569
-
570
- The changes view reads through the same Remote. `localDiff` reads one `ForgeLocalScope` of the Session's checkout, and `changeRequestDiff` reads a bound change request at the head of its latest status through the provider's `diff`; both return files that the Host parsed into `ForgeDiffFile` hunks. `localText` and `changeRequestText` read the new side of one file, which supplies the unchanged lines between hunks. Each inspection also sizes the `branch` scope as `ForgePanelState.local` for the panel's local changes card, always against the automatic fork point. A `branch` scope whose `base` names a ref compares with the merge base of HEAD and that branch instead; `diffBases` lists the branches the view offers, with the automatic fork point.
571
-
572
- ```ts type-equiv
573
- /** Which local changes a diff covers; each reads the Session's checkout through Git. */
574
- type ForgeLocalScope =
575
- /**
576
- * The working tree and untracked files against a fork point: the merge base of HEAD with the
577
- * `base` ref, or while `base` is null, the base commit of the worktree the Session created,
578
- * else the merge base of HEAD with the default branch of the code host remote.
579
- */
580
- | { readonly kind: 'branch'; readonly base: string | null }
581
- /** The working tree and untracked files against HEAD. */
582
- | { readonly kind: 'uncommitted' }
583
- /** The working tree and untracked files against the index. */
584
- | { readonly kind: 'unstaged' }
585
- /** The index against HEAD. */
586
- | { readonly kind: 'staged' }
587
- ```
588
-
589
- ```ts type-equiv
590
- /** A branch the `branch` scope can compare with. */
591
- interface ForgeBaseBranch {
592
- /** Full ref name: `refs/heads/<branch>` or `refs/remotes/<remote>/<branch>`. */
593
- readonly ref: string
594
- /** The ref without `refs/heads/` or `refs/remotes/`, such as `master` or `origin/master`. */
595
- readonly name: string
596
- }
597
- ```
598
-
599
- ```ts type-equiv
600
- /** What the `branch` scope compares with while no base branch is chosen. */
601
- interface ForgeAutomaticBase {
602
- /** The created worktree's base branch or abbreviated base commit, or the default branch of the code host remote. */
603
- readonly name: string
604
- /** Whether the fork point is the base commit of the worktree the Session created rather than a merge base. */
605
- readonly worktree: boolean
606
- }
607
- ```
608
-
609
- ```ts type-equiv
610
- /** Branches the `branch` scope can compare with, whose names contain a query. */
611
- interface ForgeBaseBranches {
612
- /** The fork point used while no base branch is chosen, whatever the query; null without one. */
613
- readonly automatic: ForgeAutomaticBase | null
614
- /** Remote-tracking branches without symbolic refs such as `origin/HEAD`, most recently committed first, at most `maxBranches`. */
615
- readonly remote: readonly ForgeBaseBranch[]
616
- /** Local branches other than the checked-out one, most recently committed first, at most `maxBranches`. */
617
- readonly local: readonly ForgeBaseBranch[]
618
- }
619
- ```
620
-
621
- ```ts type-equiv
622
- /** One hunk of a file diff. */
623
- interface ForgeDiffHunk {
624
- /** First old line the hunk covers, or the old line it comes before when it covers none. */
625
- readonly oldStart: number
626
- /** Old lines the hunk covers. */
627
- readonly oldLines: number
628
- /** First new line the hunk covers, or the new line it comes before when it covers none. */
629
- readonly newStart: number
630
- /** New lines the hunk covers. */
631
- readonly newLines: number
632
- /** Lines in patch form: a leading space for context, `+` for added, `-` for deleted, and `\` for a missing final newline. */
633
- readonly lines: readonly string[]
634
- }
635
- ```
636
-
637
- ```ts type-equiv
638
- /** How one file changed. */
639
- type ForgeDiffStatus = 'added' | 'deleted' | 'modified' | 'renamed'
640
- ```
641
-
642
- ```ts type-equiv
643
- /** One changed file of a diff. */
644
- interface ForgeDiffFile {
645
- /** Path relative to the repository root after the change, or before it for a deleted file. */
646
- readonly path: string
647
- /** Path before a rename, else null. */
648
- readonly previousPath: string | null
649
- /** How the file changed. */
650
- readonly status: ForgeDiffStatus
651
- /** Whether the content is binary, which carries no hunks. */
652
- readonly binary: boolean
653
- /** Whether an untracked file exceeds `maxDiffBytes`, so it carries no hunks. */
654
- readonly oversized: boolean
655
- /** Whether Git does not track the file yet; only local diffs list such files. */
656
- readonly untracked: boolean
657
- /** Lines added. */
658
- readonly additions: number
659
- /** Lines deleted. */
660
- readonly deletions: number
661
- /** Hunks in file order; none for a binary file or a change of mode or name only. */
662
- readonly hunks: readonly ForgeDiffHunk[]
663
- }
664
- ```
665
-
666
- ```ts type-equiv
667
- /** Local changes of the Session's checkout in one scope. */
668
- interface ForgeLocalDiff {
669
- /** Ref or abbreviated commit the `branch` scope compares with, such as `origin/master`; null for other scopes. */
670
- readonly base: string | null
671
- /** Changed files in Git's path order, untracked files last. */
672
- readonly files: readonly ForgeDiffFile[]
673
- /** Untracked files left out beyond `maxUntrackedFiles`. */
674
- readonly omitted: number
675
- }
676
- ```
677
-
678
- ```ts type-equiv
679
- /** Changes of a change request as its code host computes them. */
680
- interface ForgeChangeRequestDiff {
681
- /** Head commit the diff describes. */
682
- readonly headSha: string
683
- /** Changed files in the host's order. */
684
- readonly files: readonly ForgeDiffFile[]
685
- /**
686
- * Commits the checkout's branch has beyond the head when that branch is the change request's head
687
- * branch and the head commit is present locally; null otherwise.
688
- */
689
- readonly localAhead: number | null
690
- /** Web page of each changed file at the head commit, by path. */
691
- readonly fileUrls: Readonly<Record<string, string>>
692
- }
693
- ```
694
-
695
- ```ts type-equiv
696
- /** One file the `branch` scope changes, without its hunks. */
697
- interface ForgeChangedFile {
698
- /** Path relative to the repository root after the change. */
699
- readonly path: string
700
- /** Lines added, or 0 for a binary file. */
701
- readonly additions: number
702
- /** Lines deleted, or 0 for a binary file. */
703
- readonly deletions: number
704
- /** Whether the content is binary. */
705
- readonly binary: boolean
706
- }
707
- ```
708
-
709
- ```ts type-equiv
710
- /** Size of the `branch` scope, which the panel's local changes card shows. */
711
- interface ForgeLocalChanges {
712
- /** Ref or abbreviated commit the scope compares with. */
713
- readonly base: string
714
- /** Changed files in Git's path order, untracked files last, at most `maxUntrackedFiles` of those. */
715
- readonly files: readonly ForgeChangedFile[]
716
- }
717
- ```
718
-
719
- <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
720
-
721
- <a id="cordis-surface"></a>
722
-
723
- ## Cordis API
724
-
725
- Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
726
-
727
- <a id="ctxforge--forgeservice"></a>
728
-
729
- ### `ctx.forge` — `ForgeService`
730
-
731
- Registry of forge providers. Watchers of one change request share a single polling loop; each forge has at most one fetch in flight, which batches every due change request of that forge.
732
-
733
- ```ts cordis-catalog
734
- /**
735
- * Register the provider of one forge family as an effect on the calling
736
- * fiber. Watches of that forge receive an `unavailable` failure when the
737
- * registration ends. Unregistering cancels and awaits its in-flight calls;
738
- * results from the withdrawn registration are discarded.
739
- * @param provider - provider to serve `provider.forge`.
740
- * @returns the disposer that unregisters the provider and waits for its calls to finish.
741
- * @throws when another provider already serves the same forge.
742
- */
743
- register(provider: ForgeProvider): () => Promise<void>
744
-
745
- /**
746
- * Map a Git remote URL to a repository through the first provider that serves it.
747
- * @param url - fetch or push URL as Git reports it.
748
- * @returns the repository, or undefined when no provider serves the URL.
749
- */
750
- repositoryFromRemote(url: string): ForgeRepository | undefined
751
-
752
- /**
753
- * Map a change request web URL through the first provider that serves it.
754
- * @param url - web URL of a pull or merge request.
755
- * @returns the change request, or undefined when no provider serves the URL.
756
- */
757
- changeRequestFromUrl(url: string): ChangeRequestRef | undefined
758
-
759
- /**
760
- * Build the canonical web URL of a change request.
761
- * @param ref - change request served by a registered provider.
762
- * @returns the provider's canonical URL.
763
- * @throws when no provider serves the change request's forge.
764
- */
765
- changeRequestUrl(ref: ChangeRequestRef): string
766
-
767
- /**
768
- * Find open change requests whose head is `branch`, excluding the default branch and forks.
769
- * @param repository - repository served by a registered provider.
770
- * @param branch - head branch name on the host.
771
- * @param signal - cancellation for the lookup.
772
- * @returns matching change requests, newest first as the provider reports them.
773
- * @throws when no provider serves the repository's forge or the lookup fails.
774
- */
775
- async findByHead(repository: ForgeRepository, branch: string, signal: AbortSignal): Promise<readonly ChangeRequestRef[]>
776
-
777
- /**
778
- * Report whether the provider can reach the repository's host with current credentials.
779
- * @param repository - repository served by a registered provider.
780
- * @param signal - cancellation for the probe.
781
- * @returns the provider's readiness for the host.
782
- * @throws when no provider serves the repository's forge.
783
- */
784
- async readiness(repository: ForgeRepository, signal: AbortSignal): Promise<ForgeReadiness>
785
-
786
- /**
787
- * Mark a draft change request ready for review, then poll it at once when it is watched.
788
- * @param ref - change request served by a registered provider.
789
- * @param signal - cancellation of the request.
790
- * @returns undefined once the host accepted the change, or why it did not.
791
- * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
792
- */
793
- async markReady(ref: ChangeRequestRef, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
794
-
795
- /**
796
- * Merge a change request while its head is still `request.headSha`, then
797
- * poll it at once when it is watched.
798
- * @param ref - change request served by a registered provider.
799
- * @param request - merge method and the head commit the user confirmed.
800
- * @param signal - cancellation of the request.
801
- * @returns undefined once the host merged it, or why it did not.
802
- * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
803
- */
804
- async merge(ref: ChangeRequestRef, request: MergeRequest, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
805
-
806
- /**
807
- * Read complete check details independently of periodic polling.
808
- * @param ref - change request served by a registered provider.
809
- * @param signal - cancellation of the detail view.
810
- * @returns a complete snapshot or the provider's failure; unsupported providers return unavailable.
811
- * @throws on cancellation, provider disposal, or expiry of this read's own `fetchTimeoutMs` deadline.
812
- */
813
- async fetchChecks(ref: ChangeRequestRef, signal: AbortSignal): Promise<ChangeRequestSnapshot | ForgeFailure>
814
-
815
- /**
816
- * Read the changes of a change request as its host computes them.
817
- * @param ref - change request served by a registered provider.
818
- * @param signal - cancellation of the read.
819
- * @returns one unified patch with `a/` and `b/` path prefixes, or why the host did not provide it.
820
- * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
821
- */
822
- async diff(ref: ChangeRequestRef, signal: AbortSignal): Promise<string | ForgeFailure>
823
-
824
- /**
825
- * Publish one comment or a completed review, then refresh the watched status.
826
- * @param ref - change request served by a registered provider.
827
- * @param request - explicitly selected commit, comments, and review decision.
828
- * @param signal - request cancellation.
829
- * @returns a publication URL or a failure, including an unknown write outcome.
830
- */
831
- async submitReview(ref: ChangeRequestRef, request: ForgeReviewRequest, signal: AbortSignal): Promise<ForgeReviewResult>
832
-
833
- /**
834
- * Read the text of one file at a commit of a change request's repository.
835
- * @param ref - change request served by a registered provider.
836
- * @param commit - commit object name.
837
- * @param path - path relative to the repository root.
838
- * @param signal - cancellation of the read.
839
- * @returns the text, or why the host did not provide it.
840
- * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
841
- */
842
- async fileText(ref: ChangeRequestRef, commit: string, path: string, signal: AbortSignal): Promise<string | ForgeFailure>
843
-
844
- /**
845
- * Build the web page of one file at a commit of a change request's repository.
846
- * @param ref - change request served by a registered provider.
847
- * @param commit - commit object name.
848
- * @param path - path relative to the repository root.
849
- * @returns the provider's URL.
850
- * @throws when no provider serves the change request's forge.
851
- */
852
- fileUrl(ref: ChangeRequestRef, commit: string, path: string): string
853
-
854
- /**
855
- * Watch one change request as an effect on the calling fiber. The first
856
- * watcher starts polling at once; listeners receive every later result,
857
- * and `latest` returns the current one. Polling stops when the last watcher
858
- * leaves. Listeners must not throw; a thrown error is logged and skipped.
859
- * @param ref - change request to poll.
860
- * @param listener - receives each polling result.
861
- * @returns the disposer that stops this watch.
862
- */
863
- watch(ref: ChangeRequestRef, listener: ChangeRequestListener): () => void
864
-
865
- /**
866
- * Poll a watched change request at once, including after a terminal state.
867
- * A fetch already in flight is followed immediately by another fetch; an
868
- * unwatched change request is unchanged.
869
- * @param ref - change request to poll.
870
- */
871
- refresh(ref: ChangeRequestRef): void
872
-
873
- /**
874
- * Read the latest polling result of a watched change request.
875
- * @param ref - change request to read.
876
- * @returns the latest result, or undefined before the first result or without a watch.
877
- */
878
- latest(ref: ChangeRequestRef): ChangeRequestUpdate | undefined
879
- ```
880
-
881
- Source: [`packages/experimental/forge/src/index.ts`](../../packages/experimental/forge/src/index.ts)
882
-
883
- <a id="ctxforgepanel--forgepanelcontroller"></a>
884
-
885
- ### `ctx.forgePanel` — `ForgePanelController`
886
-
887
- Serves the forge panel. Following a Session inspects the checkout of its current directory using the Session's permissions and execution world, recognizes the code host of the upstream remote or `origin`, probes sign-in for that host, and polls the Session's bound change requests through `ctx.forge` until the last follower of that Session leaves. A worktree request makes the panel create a branch and checkout through an optional `ctx.worktrees` and enter it while the Session's first message waits in the inbox, or at the first step proposal of a message no wait took. After a failed or stopped attempt the message keeps waiting for another attempt or for the request to be withdrawn. A message also waits while the worktree the Session works in is gone, until that worktree is checked out again or left for the source checkout. Changes to a Session's checkout run as Agent maintenance, so a message that arrives meanwhile waits for them.
888
-
889
- ```ts cordis-catalog
890
- /**
891
- * Stream the panel state of a Session: the current state first, then each
892
- * later state. Intermediate states may be skipped when the reader is slower
893
- * than the updates. The stream ends when `signal` aborts or the Remote stops.
894
- * @param session - Session to follow.
895
- * @param signal - ends the stream.
896
- * @returns panel states in publication order.
897
- */
898
- @Remote({ mode: 'stream' }) async *follow(session: Session, signal: AbortSignal): AsyncIterable<ForgePanelState>
899
-
900
- /**
901
- * Stream the last known state of every change request that a listed
902
- * Session the caller may read binds: the stored states first, then each
903
- * state a poll changes or a new binding reveals. Listing reads no Session
904
- * log: loaded Sessions report their bindings, others the cached Session
905
- * summary, and a Session without one shows nothing until it is opened.
906
- * Closed requests are polled at the idle cadence so reopening is observed. The
907
- * stream ends when `signal` aborts or the Remote stops.
908
- * @param signal - ends the stream.
909
- * @returns frames in publication order.
910
- * @throws when the Host lacks the Session query, projection cache, storage, or Session store service.
911
- */
912
- @Remote({ mode: 'stream' }) async *changeRequestStates(signal: AbortSignal): AsyncIterable<ForgeChangeRequestStatesFrame>
913
-
914
- /**
915
- * List the local branches that can start a worktree of the Session's current checkout.
916
- * @param session - Session whose current directory is listed.
917
- * @param signal - request cancellation.
918
- * @returns the checked-out branch and at most `maxBranches` local branches, the checked-out one first.
919
- * @throws when the current directory is not inside a checkout or Git fails.
920
- */
921
- @Remote async branches(session: Session, signal: AbortSignal): Promise<ForgeBranches>
922
-
923
- /**
924
- * List the branches the `branch` scope of the Session's current checkout can compare with.
925
- * @param session - Session whose checkout is read.
926
- * @param query - text the branch names must contain, ignoring case; empty for every branch.
927
- * @param signal - request cancellation.
928
- * @returns the automatic fork point and at most `maxBranches` remote-tracking and local branches each,
929
- * most recently committed first, without the checked-out branch.
930
- * @throws `forgePanel/not-a-checkout` outside a checkout; the Git error when Git fails or the listed refs exceed `maxDiffBytes`.
931
- */
932
- @Remote async diffBases(session: Session, query: string, signal: AbortSignal): Promise<ForgeBaseBranches>
933
-
934
- /**
935
- * Bind a change request to a Session by URL, or restore a dismissed binding.
936
- * @param session - Session that receives the binding.
937
- * @param url - web URL of the change request.
938
- * @param signal - request cancellation.
939
- * @throws `forgePanel/unrecognized-url` when no registered provider serves the URL.
940
- */
941
- @Remote async track(session: Session, url: string, signal: AbortSignal): Promise<void>
942
-
943
- /**
944
- * Hide a visible binding of a Session; other URLs leave the Session unchanged.
945
- * @param session - Session that owns the binding.
946
- * @param url - web URL of the change request.
947
- * @param signal - request cancellation.
948
- */
949
- @Remote async dismiss(session: Session, url: string, signal: AbortSignal): Promise<void>
950
-
951
- /**
952
- * Reinspect a followed Session's checkout, probe sign-in again, and poll its
953
- * change requests at once; an unfollowed Session is unchanged.
954
- * @param session - Session whose panel refreshes.
955
- * @param signal - request cancellation.
956
- * @returns settles once the inspection and the sign-in probe have ended; poll results reach the panel stream later.
957
- */
958
- @Remote async refresh(session: Session, signal: AbortSignal): Promise<void>
959
-
960
- /**
961
- * Read the changes one scope covers in the checkout of the Session's
962
- * current directory, with Git using the Session's sandbox policy.
963
- * @param session - Session whose checkout is read.
964
- * @param scope - which changes to read.
965
- * @param signal - request cancellation.
966
- * @returns the files, the base of the `branch` scope, and the untracked files left out.
967
- * @throws `forgePanel/not-a-checkout` outside a checkout; `forgePanel/base-missing` when the base branch
968
- * chosen for the `branch` scope does not exist; `forgePanel/no-fork-point` for the `branch` scope without
969
- * a fork point; `forgePanel/filter-config-failed` when filter configuration cannot be read completely, before
970
- * any comparison; `forgePanel/diff-too-large` over `maxDiffBytes`; the Git error when Git fails.
971
- */
972
- @Remote async localDiff(session: Session, scope: ForgeLocalScope, signal: AbortSignal): Promise<ForgeLocalDiff>
973
-
974
- /**
975
- * Read the new side of one changed file in a scope, which supplies the
976
- * unchanged lines between its hunks: the index for `staged`, else the working tree.
977
- * @param session - Session whose checkout is read.
978
- * @param scope - the scope the diff was read in.
979
- * @param path - path relative to the checkout's top-level directory, as the diff names it.
980
- * @param signal - request cancellation.
981
- * @returns the text.
982
- * @throws `forgePanel/not-a-checkout` outside a checkout; `forgePanel/diff-too-large` over `maxDiffBytes`;
983
- * an error for a path outside the checkout, binary content, or a failed read.
984
- */
985
- @Remote async localText(session: Session, scope: ForgeLocalScope, path: string, signal: AbortSignal): Promise<string>
986
-
987
- /**
988
- * Load complete checks for the change request whose list the user opened.
989
- * @param session - Session retaining the visible binding.
990
- * @param url - binding URL.
991
- * @param headSha - commit displayed by the card; a different head rejects the result.
992
- * @param signal - cancellation when the list closes.
993
- * @returns the complete snapshot at the displayed head.
994
- * @throws `forgePanel/not-bound` for an invisible binding, `forgePanel/stale-head` for a moved head,
995
- * or `forgePanel/checks-failed` when the provider cannot complete the read.
996
- */
997
- @Remote async changeRequestChecks(session: Session, url: string, headSha: string, signal: AbortSignal): Promise<ChangeRequestSnapshot>
998
-
999
- /**
1000
- * Read the changes of a change request of a Session as its code host
1001
- * computes them, at the head commit of its latest status. A patch already
1002
- * read at the same head commit and base branch is reused from memory, for
1003
- * any Session, until `maxCachedDiffBytes` evicts it.
1004
- * @param session - Session the change request is bound to.
1005
- * @param url - binding URL of the change request.
1006
- * @param signal - request cancellation.
1007
- * @returns the files, the head commit, each file's web page, and the commits the checkout has beyond the head.
1008
- * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1009
- * `forgePanel/diff-too-large` when the host or its provider refuses the diff over a size limit;
1010
- * `forgePanel/diff-failed` before its status was read and when the host does not provide the diff otherwise.
1011
- */
1012
- @Remote async changeRequestDiff(session: Session, url: string, signal: AbortSignal): Promise<ForgeChangeRequestDiff>
1013
-
1014
- /**
1015
- * Read one file of a change request's repository at a commit, which
1016
- * supplies the unchanged lines between the hunks of its diff.
1017
- * @param session - Session the change request is bound to.
1018
- * @param url - binding URL of the change request.
1019
- * @param commit - commit object name, the head commit of the diff.
1020
- * @param path - path relative to the repository root.
1021
- * @param signal - request cancellation.
1022
- * @returns the text.
1023
- * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1024
- * `forgePanel/diff-too-large` when the host or its provider refuses the file over a size limit;
1025
- * `forgePanel/diff-failed` when the host does not provide the file otherwise.
1026
- */
1027
- @Remote async changeRequestText(session: Session, url: string, commit: string, path: string, signal: AbortSignal): Promise<string>
1028
-
1029
- /**
1030
- * Publish a line comment or completed review for a visible Session binding.
1031
- * @param session - Session retaining the change request.
1032
- * @param url - visible binding URL.
1033
- * @param request - reviewed head, ranges, plain text, and explicit review decision.
1034
- * @param signal - request cancellation.
1035
- * @returns confirmed publication or a typed failure; unknown outcomes require reconciliation on the host.
1036
- */
1037
- @Remote async submitReview(session: Session, url: string, request: ForgeReviewRequest, signal: AbortSignal): Promise<ForgeReviewResult>
1038
-
1039
- /**
1040
- * Mark a draft change request of a Session ready for review; the change
1041
- * request is polled again once the host answers.
1042
- * @param session - Session the change request is bound to.
1043
- * @param url - binding URL of the change request.
1044
- * @param signal - request cancellation.
1045
- * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1046
- * `forgePanel/action-failed` with the host's reason when the host refuses or cannot be reached.
1047
- */
1048
- @Remote async markReady(session: Session, url: string, signal: AbortSignal): Promise<void>
1049
-
1050
- /**
1051
- * Merge a change request of a Session while its head is still `headSha`;
1052
- * the change request is polled again once the host answers.
1053
- * @param session - Session the change request is bound to.
1054
- * @param url - binding URL of the change request.
1055
- * @param method - how the commits enter the base branch.
1056
- * @param headSha - head commit the user confirmed.
1057
- * @param signal - request cancellation.
1058
- * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1059
- * `forgePanel/stale-head` when the head moved past `headSha`; `forgePanel/action-failed` with the host's
1060
- * reason when the host refuses otherwise or cannot be reached.
1061
- */
1062
- @Remote async merge(session: Session, url: string, method: MergeMethod, headSha: string, signal: AbortSignal): Promise<void>
1063
-
1064
- /**
1065
- * Ask for a new worktree before the Session's first turn, replacing an open
1066
- * request and forgetting a failed attempt. While the next message waits in
1067
- * the inbox, or else at the first step proposal, the panel creates a branch
1068
- * from `from`, or from HEAD of the current checkout when it is null, and
1069
- * enters its checkout. A message that already waits after a failed or
1070
- * stopped attempt keeps waiting for {@link ForgePanelController.retryWorktree}.
1071
- * @param session - Session without a started turn.
1072
- * @param from - local base branch, or null for HEAD.
1073
- * @param signal - request cancellation.
1074
- * @throws `forgePanel/invalid-branch` when `from` is empty; `forgePanel/worktrees-unavailable`
1075
- * without a worktree service; `forgePanel/session-started` after a turn started or the worktree was
1076
- * created; `forgePanel/session-busy` while the worktree is being created.
1077
- */
1078
- @Remote async requestWorktree(session: Session, from: string | null, signal: AbortSignal): Promise<void>
1079
-
1080
- /**
1081
- * Withdraw the open worktree request of a Session with its failed attempt,
1082
- * which lets a waiting message go on in the current checkout; without a
1083
- * request the Session is unchanged. Withdrawing needs no worktree service.
1084
- * @param session - Session without a started turn.
1085
- * @param signal - request cancellation.
1086
- * @throws `forgePanel/session-started` after a turn started or the worktree was created;
1087
- * `forgePanel/session-busy` while the worktree is being created.
1088
- */
1089
- @Remote async cancelWorktree(session: Session, signal: AbortSignal): Promise<void>
1090
-
1091
- /**
1092
- * Stop the creation a message of the Session waits for; the message keeps
1093
- * waiting with the request still open. Without such a creation the Session
1094
- * is unchanged.
1095
- * @param session - Session whose creation stops.
1096
- * @param signal - request cancellation.
1097
- */
1098
- @Remote async stopCreation(session: Session, signal: AbortSignal): Promise<void>
1099
-
1100
- /**
1101
- * Make another attempt, with a new branch name, at the worktree a message
1102
- * of the Session waits for after a failed or stopped attempt.
1103
- * @param session - Session whose message waits.
1104
- * @param signal - request cancellation.
1105
- * @throws `forgePanel/not-waiting` when no message waits or an attempt is running.
1106
- */
1107
- @Remote async retryWorktree(session: Session, signal: AbortSignal): Promise<void>
1108
-
1109
- /**
1110
- * Check out the created worktree of a Session again at its recorded
1111
- * directory when that directory is gone: its branch when the branch still
1112
- * exists, otherwise a new branch of the same name at the base commit. Changes
1113
- * that were never committed are not recovered. Runs as Agent maintenance,
1114
- * inside the wait of a message that waits for the restoration, which then
1115
- * goes on; without a missing directory the Session is unchanged.
1116
- * @param session - Session whose worktree directory is gone.
1117
- * @param signal - request cancellation, which aborts the restoration.
1118
- * @throws `forgePanel/no-worktree` without a created worktree; `forgePanel/worktrees-unavailable`
1119
- * without a worktree service; `forgePanel/session-busy` while the Session's Agent is not loaded or
1120
- * has other work; `forgePanel/restore-failed` when Git fails.
1121
- */
1122
- @Remote async restoreWorktree(session: Session, signal: AbortSignal): Promise<void>
1123
-
1124
- /**
1125
- * Move a Session from its created worktree to the source checkout the
1126
- * worktree came from; the Session no longer works in the worktree. Runs as
1127
- * Agent maintenance, inside the wait of a message that waits for the removed
1128
- * worktree, which then goes on.
1129
- * @param session - Session with a created worktree.
1130
- * @param signal - request cancellation.
1131
- * @throws `forgePanel/no-worktree` without a created worktree; `forgePanel/session-busy` while the
1132
- * Session's Agent is not loaded or has other work; the working-directory service's error when the
1133
- * source checkout no longer exists.
1134
- */
1135
- @Remote async continueInProject(session: Session, signal: AbortSignal): Promise<void>
1136
- ```
1137
-
1138
- Types: [Session](session.md)
1139
-
1140
- Source: [`packages/experimental/client-ui-forge/src/index.ts`](../../packages/experimental/client-ui-forge/src/index.ts)
1141
-
1142
- <a id="ctxforgesession--forgesessionservice"></a>
1143
-
1144
- ### `ctx.forgeSession` — `ForgeSessionService`
1145
-
1146
- Session-scoped change request bindings. The `forgeChangeRequests` projection derives them from `gh pr create`, `gh pr view`, `gh pr edit`, `gh pr checkout`, and `git push` results and from `plugin:forge-session/binding` records. After each push result, and when a caller reports the branch a Session works on, the service looks up open change requests whose head is that branch and records the unbound ones as discovered. Watching a Session polls its visible change requests through `ctx.forge`.
1147
-
1148
- ```ts cordis-catalog
1149
- /**
1150
- * List the change requests bound to a Session and not dismissed.
1151
- * @param session - Session whose bindings are read.
1152
- * @returns visible bindings in first-recorded order, each with its latest polling result.
1153
- */
1154
- changeRequests(session: Session): SessionChangeRequest[]
1155
-
1156
- /**
1157
- * Bind a change request to a Session by URL, or restore a dismissed binding.
1158
- * Appends a `track` record with the provider's canonical URL unless the
1159
- * change request is already visible.
1160
- * @param session - Session that receives the binding.
1161
- * @param url - web URL of the change request.
1162
- * @throws when no registered provider recognizes the URL.
1163
- */
1164
- track(session: Session, url: string): void
1165
-
1166
- /**
1167
- * Look up open change requests whose head is a branch the Session works on,
1168
- * and record the unbound ones as discovered, as after a push of that branch.
1169
- * A remote that no provider recognizes changes nothing; a failed lookup is
1170
- * logged and changes nothing.
1171
- * @param session - Session that receives the bindings.
1172
- * @param remote - URL of the remote that holds the branch.
1173
- * @param branch - branch name on that remote.
1174
- * @returns resolves once the lookup has recorded its bindings or failed.
1175
- */
1176
- discoverBranch(session: Session, remote: string, branch: string): Promise<void>
1177
-
1178
- /**
1179
- * Hide a visible binding. Appends a `dismiss` record; a URL that is not
1180
- * visibly bound leaves the log unchanged. Discovery never restores a
1181
- * dismissed binding; `track` does.
1182
- * @param session - Session that owns the binding.
1183
- * @param url - web URL of the change request.
1184
- */
1185
- dismiss(session: Session, url: string): void
1186
-
1187
- /**
1188
- * Watch a Session's visible change requests as an effect on the calling
1189
- * fiber. Every visible change request that a provider recognizes is polled
1190
- * through `ctx.forge` while the Session has a watcher. Listeners receive the
1191
- * complete list after each later binding or provider change, or polling result; read the
1192
- * current list with {@link changeRequests}. Listeners must not throw; a
1193
- * thrown error is logged and skipped.
1194
- * @param session - Session to watch.
1195
- * @param listener - receives the complete visible list.
1196
- * @returns the disposer that stops this watch.
1197
- */
1198
- watch(session: Session, listener: SessionChangeRequestsListener): () => void
1199
- ```
1200
-
1201
- Types: [Session](session.md)
1202
-
1203
- Source: [`packages/experimental/forge-session/src/index.ts`](../../packages/experimental/forge-session/src/index.ts)
1204
-
1205
- <a id="forge-events"></a>
1206
-
1207
- ### `forge/*` events
1208
-
1209
- <a id="forgeproviders-changed--parallel"></a>
1210
-
1211
- #### `forge/providers-changed` — parallel
1212
-
1213
- Announces a provider registration or withdrawal after the registry changes. Listener failures are logged without preventing other listeners from running.
1214
-
1215
- ```ts cordis-catalog
1216
- /**
1217
- * Announces a provider registration or withdrawal after the registry changes.
1218
- * Listener failures are logged without preventing other listeners from running.
1219
- * @param forge - family whose provider changed.
1220
- * @mode parallel
1221
- */
1222
- 'forge/providers-changed'(forge: string): void | Promise<void>
1223
- ```
1224
-
1225
- Source: [`packages/experimental/forge/src/index.ts`](../../packages/experimental/forge/src/index.ts)
1226
- <!-- END GENERATED cordis-surface -->
1
+ # Forges
2
+
3
+ English | [中文](forge.zh.md)
4
+
5
+ The experimental forge capability seam reads change requests, meaning pull requests and merge requests, from code hosts. [`dsh-experimental-forge`](../../packages/experimental/forge/README.md) is the Service Definition and provides `ctx.forge`; [`dsh-experimental-forge-github`](../../packages/experimental/forge-github/README.md) is the Service Provider for GitHub hosts; [`dsh-experimental-forge-session`](../../packages/experimental/forge-session/README.md) is the Consumer that binds change requests to Sessions and provides `ctx.forgeSession`; [`dsh-experimental-client-ui-forge`](../../packages/experimental/client-ui-forge/README.md) is the GUI Consumer that provides `ctx.forgePanel`. The default-disabled [forge bundle](../../packages/experimental/forge-bundle/README.md) composes all four, with the [worktree model tool](../../packages/experimental/tool-worktree/README.md) and one shared [worktree service](../../packages/experimental/worktree/README.md), from the Plugins page; otherwise they load only through explicit composition.
6
+
7
+ ## Providers and change requests
8
+
9
+ A provider serves one forge family, such as `github`. It maps Git remote URLs to repositories and change request URLs to references, builds canonical change request URLs, finds open change requests by head branch, and reads status in batches. `changeRequestKey` derives one case-insensitive key from a reference, so watchers of differently spelled URLs share one polling loop.
10
+
11
+ ```ts type-equiv
12
+ /** One repository on a code host, identified the way its provider addresses it. */
13
+ interface ForgeRepository {
14
+ /** Provider family that serves the repository, such as `github`. */
15
+ readonly forge: string
16
+ /** Lowercase host name, such as `github.com`. */
17
+ readonly host: string
18
+ /** Owner or namespace path as the host spells it. */
19
+ readonly owner: string
20
+ /** Repository name as the host spells it. */
21
+ readonly name: string
22
+ }
23
+ ```
24
+
25
+ ```ts type-equiv
26
+ /** One change request (pull request or merge request) in a repository. */
27
+ interface ChangeRequestRef {
28
+ /** Repository that holds the change request. */
29
+ readonly repository: ForgeRepository
30
+ /** Host-assigned change request number. */
31
+ readonly number: number
32
+ }
33
+ ```
34
+
35
+ ```ts type-equiv
36
+ /** Canonical identity of a change request across providers; built by `changeRequestKey`. */
37
+ type ChangeRequestKey = Branded<'ChangeRequestKey'>
38
+ ```
39
+
40
+ ```ts type-equiv
41
+ /**
42
+ * Host-specific access registered with `ctx.forge`. One provider serves one
43
+ * forge family; methods that take a signal must stop work when it aborts.
44
+ */
45
+ interface ForgeProvider {
46
+ /** Forge family name stored in every repository this provider returns. */
47
+ readonly forge: string
48
+ /** Publish a comment or completed review. Omission declares that this provider cannot write reviews. */
49
+ submitReview?(ref: ChangeRequestRef, request: ForgeReviewRequest, signal: AbortSignal): Promise<ForgeReviewResult>
50
+ /**
51
+ * Read the review threads of a change request at head commit `headSha`. The threads are returned only when the
52
+ * read names `headSha` as the head; once the head is another commit, the read resolves to a `stale` failure.
53
+ * Omission declares that this provider cannot read reviews.
54
+ */
55
+ readReview?(ref: ChangeRequestRef, headSha: string, signal: AbortSignal): Promise<ForgeReviewData | ForgeFailure>
56
+ /**
57
+ * Write to the review threads or the pending review of a change request while its head is `write.headSha`; once the
58
+ * head is another commit, the write resolves to `stale` without writing. A write is never retried. Omission declares
59
+ * that this provider cannot write reviews, and its `readReview` then reports no viewer permission.
60
+ */
61
+ writeReview?(ref: ChangeRequestRef, write: ForgeReviewWrite, signal: AbortSignal): Promise<ForgeReviewWriteResult>
62
+ /** Map a Git remote URL to its repository, or undefined when this provider does not serve it. */
63
+ repositoryFromRemote(url: string): ForgeRepository | undefined
64
+ /** Map a change request web URL to its reference, or undefined when this provider does not serve it. */
65
+ changeRequestFromUrl(url: string): ChangeRequestRef | undefined
66
+ /** Build the canonical web URL of a change request this provider serves. */
67
+ changeRequestUrl(ref: ChangeRequestRef): string
68
+ /** Read the status of several change requests, returning one result per requested key. */
69
+ fetch(
70
+ refs: readonly ChangeRequestRef[],
71
+ signal: AbortSignal,
72
+ ): Promise<ReadonlyMap<ChangeRequestKey, ChangeRequestSnapshot | ForgeFailure>>
73
+ /** Read all checks for one change request on demand; omission declares that only the polling preview is available. */
74
+ fetchChecks?(ref: ChangeRequestRef, signal: AbortSignal): Promise<ChangeRequestSnapshot | ForgeFailure>
75
+ /** Find open change requests whose head is `branch` in `repository`, excluding the default branch and forks. */
76
+ findByHead(repository: ForgeRepository, branch: string, signal: AbortSignal): Promise<readonly ChangeRequestRef[]>
77
+ /** Report whether the provider can reach `host` with current credentials. */
78
+ readiness(host: string, signal: AbortSignal): Promise<ForgeReadiness>
79
+ /** Mark a draft change request ready for review, resolving to undefined once the host accepted it. */
80
+ markReady(ref: ChangeRequestRef, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
81
+ /** Merge a change request while its head is still `request.headSha`, resolving to undefined once merged. */
82
+ merge(ref: ChangeRequestRef, request: MergeRequest, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
83
+ /**
84
+ * Read the changes of a change request at head commit `headSha` as the host computes them, as one unified
85
+ * patch with `a/` and `b/` path prefixes. A patch is returned only when it describes `headSha`; once the
86
+ * head is another commit, the read resolves to a `stale` failure. `base`, when given, is the `baseSha` the
87
+ * latest status recorded with `headSha`; a provider may start reading from it before it confirms the head.
88
+ */
89
+ diff(ref: ChangeRequestRef, headSha: string, signal: AbortSignal, base?: string): Promise<string | ForgeFailure>
90
+ /** Read the text of `path` at `commit` in the change request's repository. */
91
+ fileText(ref: ChangeRequestRef, commit: string, path: string, signal: AbortSignal): Promise<string | ForgeFailure>
92
+ /** Build the web page of `path` at `commit` in the change request's repository. */
93
+ fileUrl(ref: ChangeRequestRef, commit: string, path: string): string
94
+ /**
95
+ * List the commits of a change request at its current head. A provider implements `commits` and `compare`
96
+ * together; omitting either declares that the change request can be read only as a whole.
97
+ */
98
+ commits?(ref: ChangeRequestRef, signal: AbortSignal): Promise<ForgeCommitList | ForgeFailure>
99
+ /**
100
+ * Read the changes from commit `base` to commit `head` in the change request's repository, as one unified patch
101
+ * with `a/` and `b/` path prefixes. Both are full object names; `head` descends from `base`.
102
+ */
103
+ compare?(ref: ChangeRequestRef, base: string, head: string, signal: AbortSignal): Promise<string | ForgeFailure>
104
+ /**
105
+ * Find the merge base of commits `base` and `head` in the change request's repository, as a full object name: the
106
+ * commit whose files the old side of a change request's diff shows. Omission declares that the provider cannot name
107
+ * it, so readers of the old side get `unsupported`.
108
+ */
109
+ mergeBase?(ref: ChangeRequestRef, base: string, head: string, signal: AbortSignal): Promise<string | ForgeFailure>
110
+ /**
111
+ * Read the signed-in user's viewed marks on the change request's files at head commit `headSha`. The marks are
112
+ * returned only when the read names `headSha` as the head; once the head is another commit, the read resolves to a
113
+ * `stale` failure. A provider implements `viewedFiles` and `setViewed` together; omitting either declares that the
114
+ * host keeps no viewed marks.
115
+ */
116
+ viewedFiles?(ref: ChangeRequestRef, headSha: string, signal: AbortSignal): Promise<ForgeViewedFiles | ForgeFailure>
117
+ /**
118
+ * Set or clear the signed-in user's viewed mark on one file while the head is still `request.headSha`, resolving to
119
+ * undefined once the host saved it.
120
+ */
121
+ setViewed?(ref: ChangeRequestRef, request: ForgeViewedRequest, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
122
+ /**
123
+ * Rerun the failed checks of the head commit that name a `rerun` none of whose checks is pending, while the head is
124
+ * still `request.headSha`, resolving to undefined once the host accepted every rerun. Omission declares that the
125
+ * provider reruns no checks, and its snapshots then name none.
126
+ */
127
+ rerunChecks?(ref: ChangeRequestRef, request: ForgeRerunRequest, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
128
+ /**
129
+ * Request reviews from more reviewers, resolving to undefined once the host accepted the request. A provider implements
130
+ * `requestReviews` and `reviewerCandidates` together; omitting either declares that it requests no reviews, and its
131
+ * snapshots then deny `canRequestReviews`.
132
+ */
133
+ requestReviews?(ref: ChangeRequestRef, request: ForgeReviewersRequest, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
134
+ /** List accounts whose login or name matches `query`, the host's suggestions first, for a review request. */
135
+ reviewerCandidates?(ref: ChangeRequestRef, query: string, signal: AbortSignal): Promise<readonly ForgeReviewerCandidate[] | ForgeFailure>
136
+ /**
137
+ * Bring the base branch's new commits into the head branch while its head is still `request.headSha`, resolving to
138
+ * undefined once the host accepted the update. Omission declares that the provider updates no branches, and its
139
+ * snapshots then offer no `branchUpdates`.
140
+ */
141
+ updateBranch?(ref: ChangeRequestRef, request: BranchUpdateRequest, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
142
+ }
143
+ ```
144
+
145
+ ```ts type-equiv
146
+ /** One commit of a change request. */
147
+ interface ForgeCommit {
148
+ /** Full commit object name. */
149
+ readonly sha: string
150
+ /** Full object name of the first parent, or null for a root commit. */
151
+ readonly parent: string | null
152
+ /** First line of the commit message. */
153
+ readonly title: string
154
+ /** Author name or login as the host reports it, or null when it reports none. */
155
+ readonly author: string | null
156
+ /** ISO 8601 time the commit was authored. */
157
+ readonly authoredAt: string
158
+ }
159
+ ```
160
+
161
+ ```ts type-equiv
162
+ /** The commits of a change request at one head commit. */
163
+ interface ForgeCommitList {
164
+ /** Head commit the list was read at. */
165
+ readonly headSha: string
166
+ /** Commits oldest first, ending with the head commit unless `truncated`. */
167
+ readonly commits: readonly ForgeCommit[]
168
+ /** Whether the change request has more commits than the provider lists; the listed ones are the oldest. */
169
+ readonly truncated: boolean
170
+ }
171
+ ```
172
+
173
+ `ctx.forge.commits` and `ctx.forge.compare` delegate to a provider that implements both; otherwise `commits` resolves to undefined and `compare` to an `unavailable` failure, so the forge panel offers its commit picker only where the provider reads commits.
174
+
175
+ ```ts type-equiv
176
+ /** Whether a provider can reach one host with the current credentials. */
177
+ interface ForgeReadiness {
178
+ /** `ready`, `unauthenticated` (sign-in needed), or `unavailable` (tool missing or host unreachable). */
179
+ readonly state: 'ready' | 'unauthenticated' | 'unavailable'
180
+ /** Human-readable detail, or null when ready. */
181
+ readonly message: string | null
182
+ }
183
+ ```
184
+
185
+ ## Status polling
186
+
187
+ `ctx.forge.watch` shares one polling loop among the watchers of a change request and delivers every result as an update. Each forge has at most one fetch in flight. The [registry README](../../packages/experimental/forge/README.md#use-this-package) owns the poll cadence, backoff, and timeout rules.
188
+
189
+ ```ts type-equiv
190
+ /** Lifecycle state of a change request. */
191
+ type ChangeRequestState = 'open' | 'closed' | 'merged'
192
+ ```
193
+
194
+ ```ts type-equiv
195
+ /** Normalized outcome of one status check. */
196
+ type CheckState = 'pending' | 'success' | 'failure' | 'neutral' | 'skipped' | 'cancelled'
197
+ ```
198
+
199
+ ```ts type-equiv
200
+ /** One status check reported for the head commit of a change request. */
201
+ interface ChangeRequestCheck {
202
+ /** Check name as the host displays it. */
203
+ readonly name: string
204
+ /** Normalized check outcome. */
205
+ readonly state: CheckState
206
+ /** Web page for the check, or null when the host reports none. */
207
+ readonly url: string | null
208
+ /** Workflow that produced the check, or null when the host reports none. */
209
+ readonly workflow: string | null
210
+ /** ISO 8601 time the check started, or null before it starts and when the host reports none. */
211
+ readonly startedAt: string | null
212
+ /** ISO 8601 time the check completed, or null while it runs and when the host reports none. */
213
+ readonly completedAt: string | null
214
+ /**
215
+ * Identity a rerun of this check names, or null when the provider cannot rerun it, as for a commit status. Checks that
216
+ * name one identity rerun together, and their failed checks can be rerun once none of them is pending.
217
+ */
218
+ readonly rerun: ForgeCheckId | null
219
+ }
220
+ ```
221
+
222
+ ```ts type-equiv
223
+ /** Combined outcome of all checks on the head commit. */
224
+ type ChecksRollup = 'none' | 'pending' | 'success' | 'failure'
225
+ ```
226
+
227
+ ```ts type-equiv
228
+ /** Whether the change request can merge into its base without conflicts. */
229
+ type Mergeability = 'mergeable' | 'conflicting' | 'unknown'
230
+ ```
231
+
232
+ ```ts type-equiv
233
+ /** Aggregate review outcome required by the host's merge rules. */
234
+ type ReviewDecision = 'approved' | 'changes-requested' | 'review-required' | 'none'
235
+ ```
236
+
237
+ ```ts type-equiv
238
+ /** Status of one change request at the time its provider read it. */
239
+ interface ChangeRequestSnapshot {
240
+ /** Change request the snapshot describes. */
241
+ readonly ref: ChangeRequestRef
242
+ /** Web page of the change request. */
243
+ readonly url: string
244
+ /** Title as the author wrote it. */
245
+ readonly title: string
246
+ /** Lifecycle state. */
247
+ readonly state: ChangeRequestState
248
+ /** Whether the author marked the change request as a draft. */
249
+ readonly draft: boolean
250
+ /** Author login, or null when the host hides it. */
251
+ readonly author: string | null
252
+ /** Branch that carries the proposed commits. */
253
+ readonly headBranch: string
254
+ /** Commit object name at the head of the change request. */
255
+ readonly headSha: string
256
+ /** Branch the change request merges into. */
257
+ readonly baseBranch: string
258
+ /**
259
+ * Base commit the host recorded with `headSha`, whose merge base with the head starts the change request's diff;
260
+ * omitted when the provider does not report it.
261
+ */
262
+ readonly baseSha?: string
263
+ /** Lines the change request adds. */
264
+ readonly additions: number
265
+ /** Lines the change request deletes. */
266
+ readonly deletions: number
267
+ /** Merge conflict status; `unknown` while the host computes it. */
268
+ readonly mergeability: Mergeability
269
+ /**
270
+ * Whether the host would merge the change request now for the signed-in
271
+ * user: open, not a draft, and meeting the base branch's merge rules.
272
+ */
273
+ readonly mergeReady: boolean
274
+ /** Merge methods the repository accepts, the signed-in user's usual one first. */
275
+ readonly mergeMethods: readonly MergeMethod[]
276
+ /**
277
+ * Ways the signed-in user can bring the base branch's new commits into the head branch now; empty while the head is
278
+ * up to date, while the branches conflict, when the user cannot update the head branch, or when the provider cannot,
279
+ * so a head behind its base cannot be told apart from a current one for a user without the right to update it.
280
+ */
281
+ readonly branchUpdates: readonly BranchUpdateMethod[]
282
+ /** Aggregate review outcome. */
283
+ readonly reviewDecision: ReviewDecision
284
+ /** Reviewers whose review is requested, in host order, then other accounts that reviewed, each once; the author is never listed. */
285
+ readonly reviewers: readonly ChangeRequestReviewer[]
286
+ /** Whether the host reported more review requests or reviews than the provider read, so `reviewers` may be incomplete. */
287
+ readonly reviewersTruncated: boolean
288
+ /** Whether the signed-in user may request reviews of the change request through its provider. */
289
+ readonly canRequestReviews: boolean
290
+ /** Checks on the head commit in host order. */
291
+ readonly checks: readonly ChangeRequestCheck[]
292
+ /** Combined check outcome. */
293
+ readonly checksRollup: ChecksRollup
294
+ /** Whether the host reported more checks than `checks` contains. */
295
+ readonly checksTruncated: boolean
296
+ /** Whether the signed-in user may rerun the failed checks that name a `rerun`. */
297
+ readonly canRerunChecks: boolean
298
+ /** Number of counted review threads that are not resolved. */
299
+ readonly unresolvedThreads: number
300
+ /** Whether the change request has more review threads than the provider counted, so `unresolvedThreads` is a lower bound. */
301
+ readonly threadsTruncated: boolean
302
+ /** Issues linked to the change request, which the host closes once it merges, in host order. */
303
+ readonly closingIssues: readonly ChangeRequestIssue[]
304
+ /** Whether the host reported more closing issues than `closingIssues` holds. */
305
+ readonly closingIssuesTruncated: boolean
306
+ /** ISO 8601 time of the host's last update to the change request. */
307
+ readonly updatedAt: string
308
+ /** ISO 8601 time the change request was merged or closed, or null while it is open. */
309
+ readonly closedAt: string | null
310
+ }
311
+ ```
312
+
313
+ ```ts type-equiv
314
+ /** Why a provider could not read a change request. */
315
+ interface ForgeFailure {
316
+ /**
317
+ * `not-found`: the host does not expose it; `unauthenticated`: credentials are missing or rejected;
318
+ * `too-large`: the host or the provider refuses an answer over its size limit, so reading again fails
319
+ * the same way; `stale`: the head is no longer the commit the read named, so reading again at the head of
320
+ * a later status can succeed; `unsupported`: the provider does not offer the read; `unavailable`: any other failure.
321
+ */
322
+ readonly code: 'not-found' | 'unauthenticated' | 'too-large' | 'stale' | 'unsupported' | 'unavailable'
323
+ /** Human-readable cause. */
324
+ readonly message: string
325
+ }
326
+ ```
327
+
328
+ ```ts type-equiv
329
+ /** Latest polling result delivered to watchers of one change request. */
330
+ type ChangeRequestUpdate = {
331
+ /** Canonical identity of the watched change request. */
332
+ readonly key: ChangeRequestKey
333
+ /** Epoch milliseconds when the result settled. */
334
+ readonly fetchedAt: number
335
+ } & ({
336
+ /** Status read from the host. */
337
+ readonly snapshot: ChangeRequestSnapshot
338
+ } | {
339
+ /** Reason the status could not be read. */
340
+ readonly failure: ForgeFailure
341
+ })
342
+ ```
343
+
344
+ ```ts type-equiv
345
+ /**
346
+ * Receives every polling result for one watched change request.
347
+ * @param update - the settled result.
348
+ */
349
+ type ChangeRequestListener = (update: ChangeRequestUpdate) => void
350
+ ```
351
+
352
+ ## Change request actions
353
+
354
+ `ctx.forge.markReady` marks a draft ready for review, and `ctx.forge.merge` merges a change request only while its head is still the commit the user confirmed. Both resolve to a `ForgeActionFailure` instead of throwing when the host refuses, and poll a watched change request at once afterwards. A snapshot's `mergeReady` and `mergeMethods` tell a consumer when to offer a merge and with which methods; the [GitHub provider README](../../packages/experimental/forge-github/README.md#use-this-package) owns how GitHub answers both.
355
+
356
+ ```ts type-equiv
357
+ /** A merge the user confirmed for the head commit they saw. */
358
+ interface MergeRequest {
359
+ /** How the commits enter the base branch. */
360
+ readonly method: MergeMethod
361
+ /** Commit object name the head must still have; the merge is refused once the head moves. */
362
+ readonly headSha: string
363
+ }
364
+ ```
365
+
366
+ ```ts type-equiv
367
+ /** Why a provider could not change a change request, or could not confirm that it did. */
368
+ interface ForgeActionFailure {
369
+ /**
370
+ * `unauthenticated`: credentials are missing or rejected; `stale`: the head
371
+ * moved past the confirmed commit; `failed`: the host refused or could not be reached, so nothing changed;
372
+ * `unknown`: the request reached the host but its answer did not confirm the outcome, so the change may have been
373
+ * applied and must be checked on the host before it is tried again.
374
+ */
375
+ readonly code: 'unauthenticated' | 'stale' | 'failed' | 'unknown'
376
+ /** Human-readable cause, usually the host's own message. */
377
+ readonly message: string
378
+ }
379
+ ```
380
+
381
+ `ctx.forge.submitReview` delegates an explicit comment or completed review to the provider. A provider that omits this capability returns unsupported. Consumers reconcile unknown write outcomes before resubmitting; comments kept for a later review go to the code host's pending review through `ctx.forge.writeReview`.
382
+
383
+ ```ts type-equiv
384
+ /** One inline comment addressed to the old or new side of a pull request diff. */
385
+ interface ForgeLineComment {
386
+ readonly path: string
387
+ readonly side: 'L' | 'R'
388
+ readonly startLine: number
389
+ readonly endLine: number
390
+ readonly body: string
391
+ }
392
+ ```
393
+
394
+ ```ts type-equiv
395
+ /** A single published comment or one completed review containing all pending comments. */
396
+ type ForgeReviewRequest = {
397
+ /** Commit the user reviewed; providers must check it before writing and address that commit explicitly. */
398
+ readonly headSha: string
399
+ } & (
400
+ | { readonly kind: 'comment'; readonly comment: ForgeLineComment }
401
+ | { readonly kind: 'review'; readonly event: ForgeReviewEvent; readonly body: string; readonly comments: readonly ForgeLineComment[] }
402
+ )
403
+ ```
404
+
405
+ ```ts type-equiv
406
+ /** Result of a review submission; unknown outcomes must be checked on the host before another submission. */
407
+ type ForgeReviewResult =
408
+ | { readonly ok: true; readonly url: string }
409
+ | { readonly ok: false; readonly code: 'unsupported' | 'stale' | 'failed' | 'unknown'; readonly message: string }
410
+ ```
411
+
412
+ ## Review threads
413
+
414
+ `ctx.forge.readReview(ref, headSha, signal)` reads the review threads of a change request at one head commit. The provider returns `ForgeReviewData` only when the read names `headSha` as the head; otherwise it returns `stale`, which polls a watched change request again at once, as a stale diff read does. For a provider without `readReview` the service returns `unsupported`, so consumers show no threads instead of a failure. A line thread has two positions: `original` on the commit it was written on, and `current` on the head of the read, which is null once the host no longer places the thread on that commit. A consumer that shows the head's diff places a thread at `current` and shows the others with their `diffHunk`; reading the file at `original.commit` through `fileText` shows the whole file the thread was written on. The [GitHub provider README](../../packages/experimental/forge-github/README.md#use-this-package) owns how GitHub's fields map onto these positions.
415
+
416
+ ```ts type-equiv
417
+ /** Opaque host identity of one review thread. */
418
+ type ForgeThreadId = Branded<'ForgeThreadId'>
419
+ ```
420
+
421
+ ```ts type-equiv
422
+ /** Opaque host identity of one review comment. */
423
+ type ForgeCommentId = Branded<'ForgeCommentId'>
424
+ ```
425
+
426
+ ```ts type-equiv
427
+ /** Consecutive lines on one side of a diff whose new side is one commit. */
428
+ interface ForgeAnchor {
429
+ /**
430
+ * Full object name of the commit the diff's new side shows: new-side (`R`) lines number this commit's file, and
431
+ * old-side (`L`) lines number the file at the diff's base.
432
+ */
433
+ readonly commit: string
434
+ /** First line, inclusive. */
435
+ readonly startLine: number
436
+ /** Last line, inclusive. */
437
+ readonly endLine: number
438
+ }
439
+ ```
440
+
441
+ ```ts type-equiv
442
+ /** Account that wrote a review comment. */
443
+ interface ForgeCommentAuthor {
444
+ /** Login as the host spells it. */
445
+ readonly login: string
446
+ /** Avatar image URL, or null when the host reports none. */
447
+ readonly avatarUrl: string | null
448
+ /** Whether the account is an app or bot rather than a person. */
449
+ readonly bot: boolean
450
+ }
451
+ ```
452
+
453
+ ```ts type-equiv
454
+ /** One comment of a review thread. */
455
+ interface ForgeReviewComment {
456
+ /** Host identity of the comment. */
457
+ readonly id: ForgeCommentId
458
+ /** Author, or null when the account no longer exists. */
459
+ readonly author: ForgeCommentAuthor | null
460
+ /** Markdown source as the author wrote it; it may contain raw HTML, which readers must not render as HTML. */
461
+ readonly body: string
462
+ /** ISO 8601 time the comment was created. */
463
+ readonly createdAt: string
464
+ /** Whether the comment belongs to the signed-in user's review that is not submitted yet, which only that user sees. */
465
+ readonly pending: boolean
466
+ /** Web page of the comment, or null when the host reports none. */
467
+ readonly url: string | null
468
+ /** Whether the signed-in user can replace the comment's text through `writeReview`; false for a provider without it. */
469
+ readonly viewerCanEdit: boolean
470
+ /** Whether the signed-in user can delete the comment through `writeReview`; false for a provider without it. */
471
+ readonly viewerCanDelete: boolean
472
+ }
473
+ ```
474
+
475
+ ```ts type-equiv
476
+ /**
477
+ * One review thread on a file of a change request. A line thread has two positions: `original` on the commit it was
478
+ * written on, and `current` on the head commit of the read. `current` is null once the host no longer places the
479
+ * thread on the head commit, as after later commits changed its lines.
480
+ */
481
+ interface ForgeReviewThread {
482
+ /** Host identity of the thread. */
483
+ readonly id: ForgeThreadId
484
+ /** Repository-relative path the thread is on. */
485
+ readonly path: string
486
+ /** Old (`L`) or new (`R`) side of the diff the lines number. */
487
+ readonly side: 'L' | 'R'
488
+ /** Whether the thread is on lines or on the whole file. */
489
+ readonly subject: 'line' | 'file'
490
+ /** Lines in the diff that ends at the head commit of the read, whose `commit` is that head; null for an outdated or file thread. */
491
+ readonly current: ForgeAnchor | null
492
+ /** Lines in the diff that ended at the commit the thread was written on; null for a file thread. */
493
+ readonly original: ForgeAnchor | null
494
+ /** Diff hunk the host recorded when the thread was written, ending at its last line; null when it recorded none. */
495
+ readonly diffHunk: string | null
496
+ /** Whether the thread is marked resolved. */
497
+ readonly resolved: boolean
498
+ /** Whether the signed-in user can reply to the thread through `writeReview`; false for a provider without it. */
499
+ readonly viewerCanReply: boolean
500
+ /**
501
+ * Whether the signed-in user can mark the thread resolved, or unresolved when `resolved`, through `writeReview`; false
502
+ * for a provider without it.
503
+ */
504
+ readonly viewerCanResolve: boolean
505
+ /** Comments oldest first; the first starts the thread. */
506
+ readonly comments: readonly ForgeReviewComment[]
507
+ }
508
+ ```
509
+
510
+ ```ts type-equiv
511
+ /** Review threads of a change request read at one head commit. */
512
+ interface ForgeReviewData {
513
+ /** Head commit the threads were read at; `current` positions number its files. */
514
+ readonly headSha: string
515
+ /** Login of the signed-in user the provider read as, or null when the host does not name one. */
516
+ readonly viewer: string | null
517
+ /** Threads in host order, at most the provider's limit. */
518
+ readonly threads: readonly ForgeReviewThread[]
519
+ /** Whether the change request has more threads than `threads` holds. */
520
+ readonly truncated: boolean
521
+ /** The signed-in user's pending review, or null without one. */
522
+ readonly pendingReview: ForgePendingReview | null
523
+ /**
524
+ * Whether the signed-in user can add comments to a pending review and submit or discard it through `writeReview`;
525
+ * false for a provider without it.
526
+ */
527
+ readonly viewerCanReview: boolean
528
+ }
529
+ ```
530
+
531
+ `ctx.forge.writeReview(ref, write, signal)` writes to the review threads and the signed-in user's pending review: a reply, at once or with `pending` inside the pending review; resolving or unresolving a thread; editing or deleting a comment; a line comment at the head added to the pending review; and submitting or discarding the pending review. A pending reply or comment starts a pending review when the user has none. Every write carries the head commit the user saw; the provider checks it first and returns `stale` without writing once the head moved, which polls a watched change request as a stale read does, and the service polls a watched change request at once after any other result. Writes are never retried, and an `unknown` result must be checked on the host before the write is tried again. For a provider without `writeReview` the service returns `unsupported`. `ForgeReviewData` carries the pending review and the user's permissions for each thread, comment, and the pending review, which a provider without `writeReview` reports as absent, so consumers offer only the writes that can succeed. The [GitHub provider README](../../packages/experimental/forge-github/README.md#use-this-package) owns the GraphQL mutations and how their outcomes map onto these results.
532
+
533
+ ```ts type-equiv
534
+ /** Opaque host identity of one review. */
535
+ type ForgeReviewId = Branded<'ForgeReviewId'>
536
+ ```
537
+
538
+ ```ts type-equiv
539
+ /** The signed-in user's review of a change request that is not submitted yet, which only that user sees. */
540
+ interface ForgePendingReview {
541
+ /** Host identity of the review. */
542
+ readonly id: ForgeReviewId
543
+ /** Comments the review holds. */
544
+ readonly comments: number
545
+ }
546
+ ```
547
+
548
+ ```ts type-equiv
549
+ /** Decision a submitted review records. */
550
+ type ForgeReviewEvent = 'comment' | 'approve' | 'request-changes'
551
+ ```
552
+
553
+ ```ts type-equiv
554
+ /**
555
+ * One write to the review threads and the pending review of a change request. `pending` replies and `add-pending`
556
+ * comments join the signed-in user's pending review, which is started when the user has none. A host that adds every
557
+ * reply to an existing pending review, as GitHub does, refuses a reply with `pending: false` while one exists.
558
+ */
559
+ type ForgeReviewWrite = {
560
+ /** Head commit the user saw; providers check it before writing and refuse with `stale` once the head is another commit. */
561
+ readonly headSha: string
562
+ } & (
563
+ | { readonly kind: 'reply'; readonly thread: ForgeThreadId; readonly body: string; readonly pending: boolean }
564
+ | { readonly kind: 'resolve'; readonly thread: ForgeThreadId; readonly resolved: boolean }
565
+ | { readonly kind: 'edit-comment'; readonly comment: ForgeCommentId; readonly body: string }
566
+ /** Deleting the only comment of a thread deletes the thread. */
567
+ | { readonly kind: 'delete-comment'; readonly comment: ForgeCommentId }
568
+ /** A comment on lines of `headSha`. */
569
+ | { readonly kind: 'add-pending'; readonly comment: ForgeLineComment }
570
+ | { readonly kind: 'submit-pending'; readonly review: ForgeReviewId; readonly event: ForgeReviewEvent; readonly body: string }
571
+ /** Delete the pending review with every comment it holds. */
572
+ | { readonly kind: 'discard-pending'; readonly review: ForgeReviewId }
573
+ )
574
+ ```
575
+
576
+ ```ts type-equiv
577
+ /** Result of a review write; an `unknown` outcome must be checked on the host before the write is tried again. */
578
+ type ForgeReviewWriteResult =
579
+ | { readonly ok: true }
580
+ | { readonly ok: false; readonly code: 'unsupported' | 'stale' | 'failed' | 'unknown'; readonly message: string }
581
+ ```
582
+
583
+ ## Overview and viewed marks
584
+
585
+ A snapshot also carries the overview of a change request: its `reviewers` with each one's latest verdict, the `closingIssues` the host closes once it merges, the `branchUpdates` that would bring a head behind its base up to date, whether the signed-in user may request reviews or rerun checks, and the `rerun` each check names. `ctx.forge.rerunChecks`, `ctx.forge.requestReviews`, and `ctx.forge.updateBranch` act on it and resolve to a `ForgeActionFailure` as the actions above do, `unknown` when the host's answer did not confirm the outcome, polling a watched change request once the provider answered; a rerun and an update name the head commit the user saw and are refused with `stale` once the head moved. A provider implements `requestReviews` together with `reviewerCandidates`, which `ctx.forge.reviewerCandidates` uses to list the accounts a review can be requested from; an action the provider lacks fails with `failed`, and `reviewerCandidates` returns `unsupported`. The [GitHub provider README](../../packages/experimental/forge-github/README.md#use-this-package) owns how GitHub fills and acts on each field.
586
+
587
+ ```ts type-equiv
588
+ /** Opaque host identity of a status check that a rerun names. */
589
+ type ForgeCheckId = Branded<'ForgeCheckId'>
590
+ ```
591
+
592
+ ```ts type-equiv
593
+ /** How an update brings the base branch's new commits into the head branch: a merge commit, or a rebase of the head's commits. */
594
+ type BranchUpdateMethod = 'merge' | 'rebase'
595
+ ```
596
+
597
+ ```ts type-equiv
598
+ /** Outcome of a reviewer's latest submitted review. */
599
+ type ReviewVerdict = 'approved' | 'changes-requested' | 'commented' | 'dismissed'
600
+ ```
601
+
602
+ ```ts type-equiv
603
+ /** An account or team asked to review a change request, or an account that reviewed it. */
604
+ interface ChangeRequestReviewer {
605
+ /** Whether the reviewer is a person's account, an app or bot account, or a team. */
606
+ readonly kind: 'user' | 'bot' | 'team'
607
+ /** Login, or `organization/slug` for a team. */
608
+ readonly login: string
609
+ /** Avatar image URL, or null when the host reports none. */
610
+ readonly avatarUrl: string | null
611
+ /** Whether a review from the reviewer is requested now. */
612
+ readonly requested: boolean
613
+ /** Whether the host requested the review because the reviewer owns changed paths. */
614
+ readonly codeOwner: boolean
615
+ /** Outcome of the reviewer's latest submitted review, or null before the first. */
616
+ readonly verdict: ReviewVerdict | null
617
+ /** Commit the latest submitted review was on, or null before the first or when the host reports none. */
618
+ readonly reviewedSha: string | null
619
+ }
620
+ ```
621
+
622
+ ```ts type-equiv
623
+ /** One issue linked to a change request, which the host closes once the change request merges. */
624
+ interface ChangeRequestIssue {
625
+ /** Repository that holds the issue. */
626
+ readonly repository: ForgeRepository
627
+ /** Host-assigned issue number. */
628
+ readonly number: number
629
+ /** Title as the author wrote it. */
630
+ readonly title: string
631
+ /** Whether the issue is still open. */
632
+ readonly state: 'open' | 'closed'
633
+ /** Web page of the issue. */
634
+ readonly url: string
635
+ }
636
+ ```
637
+
638
+ ```ts type-equiv
639
+ /** A rerun of the failed checks of the head commit whose checks the user saw. */
640
+ interface ForgeRerunRequest {
641
+ /** Head commit whose checks the user saw; the provider refuses with `stale` once the head is another commit. */
642
+ readonly headSha: string
643
+ }
644
+ ```
645
+
646
+ ```ts type-equiv
647
+ /** Reviewers to ask in addition to those whose review is requested now; one who already reviewed is asked again. */
648
+ interface ForgeReviewersRequest {
649
+ /** The reviewers by kind and login, a team by `organization/slug`. */
650
+ readonly reviewers: readonly Pick<ChangeRequestReviewer, 'kind' | 'login'>[]
651
+ }
652
+ ```
653
+
654
+ ```ts type-equiv
655
+ /** An account a review of the change request can be requested from. */
656
+ interface ForgeReviewerCandidate {
657
+ /** Login as the host spells it. */
658
+ readonly login: string
659
+ /** Display name, or null when the account shows none. */
660
+ readonly name: string | null
661
+ /** Avatar image URL, or null when the host reports none. */
662
+ readonly avatarUrl: string | null
663
+ /** Whether the host suggests the account for this change request, as from the history of its changed files. */
664
+ readonly suggested: boolean
665
+ }
666
+ ```
667
+
668
+ ```ts type-equiv
669
+ /** A branch update the user confirmed for the head commit they saw. */
670
+ interface BranchUpdateRequest {
671
+ /** How the base branch's new commits enter the head branch. */
672
+ readonly method: BranchUpdateMethod
673
+ /** Commit object name the head must still have; the update is refused once the head moves. */
674
+ readonly headSha: string
675
+ }
676
+ ```
677
+
678
+ `ctx.forge.viewedFiles` and `ctx.forge.setViewed` read and change the signed-in user's viewed marks on the files of a change request at one head commit. A provider keeps viewed marks only when it implements both methods; otherwise `viewedFiles` returns undefined and `setViewed` fails, so a consumer keeps marks itself. A `stale` answer polls a watched change request as a stale diff read does.
679
+
680
+ ```ts type-equiv
681
+ /**
682
+ * The signed-in user's review progress on one changed file: `viewed`, `unviewed`, or `dismissed` once the file
683
+ * changed after the user marked it viewed.
684
+ */
685
+ type ForgeViewedState = 'viewed' | 'unviewed' | 'dismissed'
686
+ ```
687
+
688
+ ```ts type-equiv
689
+ /** The signed-in user's viewed marks on the files of a change request, read at one head commit. */
690
+ interface ForgeViewedFiles {
691
+ /** Head commit the marks were read at. */
692
+ readonly headSha: string
693
+ /** State of each changed file the host listed, by repository-relative path. */
694
+ readonly files: Readonly<Record<string, ForgeViewedState>>
695
+ /** Whether the change request has more files than `files` lists. */
696
+ readonly truncated: boolean
697
+ }
698
+ ```
699
+
700
+ ```ts type-equiv
701
+ /** A viewed mark the user set or cleared on a file of the head commit they saw. */
702
+ interface ForgeViewedRequest {
703
+ /** Repository-relative path of the changed file. */
704
+ readonly path: string
705
+ /** Whether the file is viewed. */
706
+ readonly viewed: boolean
707
+ /** Head commit whose changes the user saw; the provider refuses with `stale` once the head is another commit. */
708
+ readonly headSha: string
709
+ }
710
+ ```
711
+
712
+ ## Session bindings
713
+
714
+ `ctx.forgeSession` derives each Session's bindings from its own log: `gh pr create` results bind the URLs they print, `gh pr view`, `gh pr edit`, and `gh pr checkout` calls bind the change request they act on, and `git push` results trigger head-branch discovery through `ctx.forge`; the panel also reports the branch its checkout tracks for the same discovery. Track, dismiss, and discovery actions are `plugin:forge-session/binding` records, which stay outside the model-visible conversation. The `forgeChangeRequests` projection's client view, which the Session summary carries, lists the bindings without their status. The [Session binding README](../../packages/experimental/forge-session/README.md#use-this-package) owns the recognition and discovery rules.
715
+
716
+ ```ts type-equiv
717
+ /**
718
+ * How a change request became bound to a Session: `created` when a
719
+ * `gh pr create` result printed its URL, `referenced` when a `gh pr view`,
720
+ * `gh pr edit`, or `gh pr checkout` call named it or its result printed its
721
+ * own URL, `discovered` when a lookup found it for a branch the Session pushed
722
+ * or works on, `tracked` when a caller bound it by URL.
723
+ */
724
+ type BindingOrigin = 'created' | 'referenced' | 'discovered' | 'tracked'
725
+ ```
726
+
727
+ ```ts type-equiv
728
+ /** Payload of the `plugin:forge-session/binding` record. */
729
+ interface BindingRecord {
730
+ /**
731
+ * `track` binds or restores, `dismiss` hides, `discover` binds a branch lookup,
732
+ * and `reference` binds a CLI selector lookup without restoring dismissed entries.
733
+ */
734
+ readonly action: 'track' | 'dismiss' | 'discover' | 'reference'
735
+ /** Web URL of the change request. */
736
+ readonly url: string
737
+ }
738
+ ```
739
+
740
+ ```ts type-equiv
741
+ /** One change request bound to a Session and not dismissed, with its latest polling result. */
742
+ interface SessionChangeRequest {
743
+ /** Web URL as first recorded. */
744
+ readonly url: string
745
+ /** How the change request became bound. */
746
+ readonly origin: BindingOrigin
747
+ /** Parsed reference, or null when no registered provider serves the URL. */
748
+ readonly ref: ChangeRequestRef | null
749
+ /** Latest polling result, or null before the first poll and without a provider. */
750
+ readonly update: ChangeRequestUpdate | null
751
+ }
752
+ ```
753
+
754
+ ```ts type-equiv
755
+ /**
756
+ * Receives the complete visible list after a binding or provider change, or a polling result.
757
+ * @param changeRequests - every visible binding of the watched Session, in first-recorded order.
758
+ */
759
+ type SessionChangeRequestsListener = (changeRequests: readonly SessionChangeRequest[]) => void
760
+ ```
761
+
762
+ ## Panel
763
+
764
+ `ctx.forgePanel` is the Host Remote of the [forge panel](../../packages/experimental/client-ui-forge/README.md#use-this-package). Following a Session streams a `ForgePanelState`: a read-only Git inspection of the Session's current directory in its execution world, the code host recognized from the upstream remote or `origin`, the sign-in state of that host, the Session's bindings with their polled status and last snapshot, and its worktree request. Its actions list base branches, bind and hide URLs, refresh, mark a bound draft ready or merge a bound change request, request or withdraw a worktree, stop a running creation, and restore a removed worktree or leave it for the source checkout. A request is a `plugin:client-ui-forge/worktree` record. When a message enters a Session's inbox while its Agent is idle, the panel holds the message's turn with maintenance of the [Agent](../../packages/core/agent/README.md), started before the delivery wakes the Agent: it creates the worktree of an open request through `ctx.worktrees` and, after a failed or stopped attempt, waits for another attempt or the request's withdrawal; for a Session that works in its created worktree it checks that worktree and, when it is gone, waits until it is restored or left. The panel state names what a message waits for and the last failed attempt. The first step proposal creates the worktree for a message no hold took. Requests after the first turn starts are ignored. Changes to a Session's checkout run as maintenance of its Agent, so a message from another client waits for them.
765
+
766
+ ```ts type-equiv
767
+ /** Git facts for the checkout that contains the Session's current directory. */
768
+ interface ForgeCheckout {
769
+ /** Top-level directory of the checkout. */
770
+ readonly root: string
771
+ /** Checked-out branch, or null when HEAD is detached. */
772
+ readonly branch: string | null
773
+ /** Whether the checkout is a linked worktree of another checkout's repository. */
774
+ readonly linked: boolean
775
+ /** Abbreviated commit HEAD names, or null while the checked-out branch has no commit. */
776
+ readonly commit: string | null
777
+ }
778
+ ```
779
+
780
+ ```ts type-equiv
781
+ /** Inspection result for the Session's current directory. */
782
+ type ForgeCheckoutState =
783
+ /** The first inspection has not finished. */
784
+ | { readonly state: 'pending' }
785
+ /** The current directory is not inside a Git checkout. */
786
+ | { readonly state: 'none' }
787
+ /** Git could not run or reported an error. */
788
+ | { readonly state: 'failed'; readonly message: string }
789
+ /** The checkout facts. */
790
+ | { readonly state: 'ready'; readonly checkout: ForgeCheckout }
791
+ ```
792
+
793
+ ```ts type-equiv
794
+ /** The worktree the panel created and entered for a Session before its first step. */
795
+ interface ForgeSessionWorktree {
796
+ /** Canonical checkout directory. */
797
+ readonly path: string
798
+ /** Branch created for the checkout. */
799
+ readonly branch: string
800
+ /** Requested base branch, or null when the checkout started from HEAD of the source checkout. */
801
+ readonly from: string | null
802
+ /** Commit object name the branch started from. */
803
+ readonly baseCommit: string
804
+ /** Canonical root of the source checkout. */
805
+ readonly repositoryRoot: string
806
+ }
807
+ ```
808
+
809
+
810
+ ```ts type-equiv
811
+ /** Payload of the `plugin:client-ui-forge/worktree` record. */
812
+ type WorktreeRecord =
813
+ /** Create a worktree before the Session's first step, starting from `from`, or from HEAD when it is null. */
814
+ | { readonly action: 'request'; readonly from: string | null }
815
+ /** Withdraw the open request. */
816
+ | { readonly action: 'cancel' }
817
+ /** The requested worktree was created and became the Session's working directory. */
818
+ | { readonly action: 'created'; readonly worktree: ForgeSessionWorktree }
819
+ /** Restoration started; the checkout stays unavailable until restoration completes. */
820
+ | { readonly action: 'restoring' }
821
+ /** The recorded worktree was completely restored. */
822
+ | { readonly action: 'restored' }
823
+ ```
824
+
825
+ ```ts type-equiv
826
+ /** One open worktree request. */
827
+ interface ForgeWorktreeRequest {
828
+ /** Base branch, or null for HEAD of the source checkout. */
829
+ readonly from: string | null
830
+ }
831
+ ```
832
+
833
+ ```ts type-equiv
834
+ /** Why the Session's checkout cannot host a new worktree. */
835
+ type ForgeWorktreeBlocker =
836
+ /** The current directory is not inside a Git checkout. */
837
+ | 'not-git'
838
+ /** The Session's sandbox cannot write the checkout or its Git directory, as for a linked worktree outside the writable root. */
839
+ | 'outside-writable-root'
840
+ /** The Session runs read-only. */
841
+ | 'read-only'
842
+ ```
843
+
844
+ ```ts type-equiv
845
+ /** One running worktree creation. */
846
+ interface ForgeWorktreeCreation {
847
+ /** Running step: `prepare` validates and creates the branch, then `checkout` writes the files. */
848
+ readonly phase: 'prepare' | 'checkout'
849
+ /** Branch the attempt creates. */
850
+ readonly branch: string
851
+ /** Base branch, or null for HEAD. */
852
+ readonly from: string | null
853
+ /**
854
+ * Whether a message waits for the creation before its turn starts. Only
855
+ * such a creation can be stopped, after which the message keeps waiting; a
856
+ * creation at the first step serves a message the Session already holds.
857
+ */
858
+ readonly held: boolean
859
+ }
860
+ ```
861
+
862
+ ```ts type-equiv
863
+ /** One running restoration of the removed worktree a Session works in. */
864
+ interface ForgeWorktreeRestoration {
865
+ /** Running step: `prepare` registers the checkout again, creating a missing branch, then `checkout` writes the files. */
866
+ readonly phase: 'prepare' | 'checkout'
867
+ }
868
+ ```
869
+
870
+ ```ts type-equiv
871
+ /** The last attempt to create the requested worktree for a message, which failed. */
872
+ interface ForgeWorktreeFailure {
873
+ /** Branch the attempt created, or null when it failed before naming one. */
874
+ readonly branch: string | null
875
+ /** Base branch of the attempt, or null for HEAD. */
876
+ readonly from: string | null
877
+ /** Step that failed. */
878
+ readonly phase: 'prepare' | 'checkout'
879
+ /** Failure text. */
880
+ readonly message: string
881
+ }
882
+ ```
883
+
884
+ ```ts type-equiv
885
+ /**
886
+ * What a message waits for before its turn starts: the creation of the
887
+ * requested worktree, or the restoration or leaving of the removed worktree
888
+ * the Session works in.
889
+ */
890
+ type ForgeWorktreeWaiting = 'creation' | 'restoration'
891
+ ```
892
+
893
+ ```ts type-equiv
894
+ /** The recorded worktree whose directory is missing, invalid, or incompletely restored. */
895
+ interface ForgeMissingWorktree {
896
+ /** Whether its branch exists, or null when an incomplete restoration cannot inspect it. */
897
+ readonly branchExists: boolean | null
898
+ }
899
+ ```
900
+
901
+ ```ts type-equiv
902
+ /** Worktree facts of one Session in the panel state. */
903
+ interface ForgePanelWorktree {
904
+ /**
905
+ * Whether the Session can still request or withdraw a worktree: a worktree
906
+ * service is mounted, no turn has started, and none was created.
907
+ */
908
+ readonly editable: boolean
909
+ /** Why the checkout cannot host a worktree, or null when it can, before the first inspection, and after a failed one. */
910
+ readonly blocker: ForgeWorktreeBlocker | null
911
+ /** Open request, or null without one. */
912
+ readonly request: ForgeWorktreeRequest | null
913
+ /** Created worktree, or null before creation. */
914
+ readonly created: ForgeSessionWorktree | null
915
+ /** Running creation, or null. */
916
+ readonly creation: ForgeWorktreeCreation | null
917
+ /**
918
+ * The last failed attempt of the creation a message waits for, or null. It
919
+ * clears when another attempt starts, when the request changes or is
920
+ * withdrawn, and when the wait ends.
921
+ */
922
+ readonly failure: ForgeWorktreeFailure | null
923
+ /** The created worktree the Session works in when its directory is gone, or null. */
924
+ readonly missing: ForgeMissingWorktree | null
925
+ /** Running restoration of the removed worktree, or null. */
926
+ readonly restoration: ForgeWorktreeRestoration | null
927
+ /** What a message of the Session waits for before its turn starts, or null while none waits. */
928
+ readonly waiting: ForgeWorktreeWaiting | null
929
+ }
930
+ ```
931
+
932
+ ```ts type-equiv
933
+ /** One bound change request in the panel state. */
934
+ interface ForgePanelChangeRequest extends SessionChangeRequest {
935
+ /** Latest snapshot a poll returned, kept while later polls fail, or null before the first successful poll. */
936
+ readonly snapshot: ChangeRequestSnapshot | null
937
+ /**
938
+ * Number of review changes `forge/review-changed` announced for the change request since the panel host started; it
939
+ * grows when any plugin's write is accepted, so a reader shows threads and viewed marks read after it.
940
+ */
941
+ readonly reviewRevision: number
942
+ }
943
+ ```
944
+
945
+ ```ts type-equiv
946
+ /** Everything the panel shows for one Session. */
947
+ interface ForgePanelState {
948
+ /** Current working directory of the Session. */
949
+ readonly directory: string
950
+ /** Inspection result for `directory`. */
951
+ readonly checkout: ForgeCheckoutState
952
+ /** Code host repository of the checkout's upstream remote or `origin`, or null when no provider serves them. */
953
+ readonly repository: ForgeRepository | null
954
+ /** Sign-in state for the repository's host, or null before the first probe and without a repository. */
955
+ readonly readiness: ForgeReadiness | null
956
+ /** Visible change request bindings with their latest polling results, in first-recorded order. */
957
+ readonly changeRequests: readonly ForgePanelChangeRequest[]
958
+ /** Worktree request and creation state. */
959
+ readonly worktree: ForgePanelWorktree
960
+ /**
961
+ * Files the `branch` scope changes, or null before the first inspection,
962
+ * outside a checkout, and without a fork point.
963
+ */
964
+ readonly local: ForgeLocalChanges | null
965
+ }
966
+ ```
967
+
968
+ ```ts type-equiv
969
+ /** Local branches offered as the base of a new worktree. */
970
+ interface ForgeBranches {
971
+ /** Checked-out branch, or null when HEAD is detached. */
972
+ readonly current: string | null
973
+ /** Local branch names, `current` first and then most recently committed first, at most `maxBranches`. */
974
+ readonly branches: readonly string[]
975
+ }
976
+ ```
977
+
978
+ The Session list reads through the same Remote. `changeRequestStates` streams `ForgeChangeRequestStatesFrame` values: a `baseline` with the last known `ForgeChangeRequestListState` of every change request that a listed Session the caller may read binds, then the states that change. One Host run serves every stream; it takes the bindings of loaded Sessions from `ctx.forgeSession` and those of other Sessions from their cached Session summary without loading them, keeps each bound change request subscribed, including closed requests polled at the idle cadence to detect reopening, and stores every change, so a restart shows the last known states before the next poll. The Session summary also carries the `forgeWorktree` projection's client view: the created worktree while the Session's directory is that worktree or lies below it, else null.
979
+
980
+ ```ts type-equiv
981
+ /**
982
+ * The last polled state of one change request that a listed Session binds, as
983
+ * the Session list shows it.
984
+ */
985
+ type ForgeChangeRequestListState = Pick<
986
+ ChangeRequestSnapshot, 'url' | 'title' | 'state' | 'draft' | 'checksRollup' | 'reviewDecision' | 'mergeability'
987
+ > & {
988
+ /** Change request number on its code host. */
989
+ readonly number: number
990
+ }
991
+ ```
992
+
993
+ ```ts type-equiv
994
+ /**
995
+ * One frame of `forgePanel.changeRequestStates`: the known state of every
996
+ * change request the reader may see first, then the states that changed or
997
+ * became visible since the previous frame.
998
+ */
999
+ type ForgeChangeRequestStatesFrame =
1000
+ | { readonly type: 'baseline'; readonly states: readonly ForgeChangeRequestListState[] }
1001
+ | { readonly type: 'changed'; readonly states: readonly ForgeChangeRequestListState[] }
1002
+ ```
1003
+
1004
+ The changes view reads through the same Remote. `localDiff` reads one `ForgeLocalScope` of the Session's checkout, and `changeRequestDiff` reads a bound change request at the head of its latest status through the provider's `diff`, which returns a patch only for that head commit, or the `ForgeCommitRange` it names through the provider's `compare`; both return files that the Host parsed into `ForgeDiffFile` hunks, each with its review layer and change digest. `localCommits` and `changeRequestCommits` list the commits a range can name, the latter as null when the provider does not read commits. `localText` and `changeRequestText` read the new side of one file, which supplies the unchanged lines between hunks. Each inspection also sizes the `branch` scope as `ForgePanelState.local` for the panel's local changes card, always against the automatic fork point. A `branch` scope whose `base` names a ref compares with the merge base of HEAD and that branch instead; `diffBases` lists the branches the view offers, with the automatic fork point. When the head moved past the latest status, the provider returns `stale` and `ctx.forge.diff` polls the change request at once, once per head commit; `changeRequestDiff` rejects with `forgePanel/stale-head` and caches nothing, and the changes view keeps loading until a later status arrives, then reads again at that status's head. Review comments therefore always address the commit whose lines the view shows.
1005
+
1006
+ `changeRequestReview(session, url, headSha, fresh)` reads the threads of a bound change request at the head of the diff the view shows and refuses another head with `forgePanel/stale-head`, `forgePanel/review-unsupported` for a provider without review reads, and `forgePanel/review-failed` otherwise. It keeps one read per change request in memory, keyed by head commit and the status's update time and bounded by `maxCachedReviewBytes`, and reads again once the kept read is `reviewRefreshMs` old or when `fresh` asks for it, as the first read after the reader refreshes the view does: on GitHub a new comment or reply changes a pull request's update time, but resolving a thread does not. The changes view asks again at every status it receives while visible, and overlays the threads on the files the [panel README](../../packages/experimental/client-ui-forge/README.md#use-this-package) describes.
1007
+
1008
+ `changeRequestViewed(session, url, headSha)` reads the viewed marks of a bound change request at the head of the diff the view shows, returns null when the provider keeps none, and refuses another head with `forgePanel/stale-head` and any other failure with `forgePanel/viewed-failed`; `setFileViewed` saves one file's mark at that head. `rerunChecks`, `requestReviews`, and `updateBranch` act on a bound change request and refuse an outcome the host did not confirm with `forgePanel/action-unknown`, and `reviewerCandidates` lists accounts for a review request, refusing with `forgePanel/reviewers-failed` when the host lists none. The changes view reads the marks of a whole change request beside its diff and keeps the marks of local changes and chosen commits in the browser; the [panel README](../../packages/experimental/client-ui-forge/README.md#use-this-package) describes both and the card rows that offer the actions.
1009
+
1010
+ `changeRequestReviewWrite(session, url, write)` passes one `ForgeReviewWrite` for a bound change request to `ctx.forge.writeReview` after checking its head commit, ids, text, and line range, and returns the `ForgeReviewWriteResult`. Once the provider answers, whatever the answer, it drops the kept threads of that change request and every read that started before, so the next `changeRequestReview` reads the host; `submitReview` drops them as well. The changes view sends one write at a time and reads the threads again after each write that may have changed them.
1011
+
1012
+ ```ts type-equiv
1013
+ /**
1014
+ * Consecutive commits a changes view shows instead of its whole comparison, named by full object names in the order
1015
+ * the commit list shows them, oldest first; the view shows the changes from the first commit's first parent to the last.
1016
+ */
1017
+ interface ForgeCommitRange {
1018
+ /** First, oldest commit shown. */
1019
+ readonly from: string
1020
+ /** Last, newest commit shown; equal to `from` for one commit. */
1021
+ readonly to: string
1022
+ }
1023
+ ```
1024
+
1025
+ ```ts type-equiv
1026
+ /** Which local changes a diff covers; each reads the Session's checkout through Git. */
1027
+ type ForgeLocalScope =
1028
+ /**
1029
+ * The working tree and untracked files against a fork point: the merge base of HEAD with the
1030
+ * `base` ref, or while `base` is null, the base commit of the worktree the Session created,
1031
+ * else the merge base of HEAD with the default branch of the code host remote. With `commits`, only the changes
1032
+ * of those commits between the fork point and HEAD, without uncommitted or untracked files.
1033
+ */
1034
+ | { readonly kind: 'branch'; readonly base: string | null; readonly commits: ForgeCommitRange | null }
1035
+ /** The working tree and untracked files against HEAD. */
1036
+ | { readonly kind: 'uncommitted' }
1037
+ /** The working tree and untracked files against the index. */
1038
+ | { readonly kind: 'unstaged' }
1039
+ /** The index against HEAD. */
1040
+ | { readonly kind: 'staged' }
1041
+ ```
1042
+
1043
+ ```ts type-equiv
1044
+ /** A branch the `branch` scope can compare with. */
1045
+ interface ForgeBaseBranch {
1046
+ /** Full ref name: `refs/heads/<branch>` or `refs/remotes/<remote>/<branch>`. */
1047
+ readonly ref: string
1048
+ /** The ref without `refs/heads/` or `refs/remotes/`, such as `master` or `origin/master`. */
1049
+ readonly name: string
1050
+ }
1051
+ ```
1052
+
1053
+ ```ts type-equiv
1054
+ /** What the `branch` scope compares with while no base branch is chosen. */
1055
+ interface ForgeAutomaticBase {
1056
+ /** The created worktree's base branch or abbreviated base commit, or the default branch of the code host remote. */
1057
+ readonly name: string
1058
+ /** Whether the fork point is the base commit of the worktree the Session created rather than a merge base. */
1059
+ readonly worktree: boolean
1060
+ }
1061
+ ```
1062
+
1063
+ ```ts type-equiv
1064
+ /** Branches the `branch` scope can compare with, whose names contain a query. */
1065
+ interface ForgeBaseBranches {
1066
+ /** The fork point used while no base branch is chosen, whatever the query; null without one. */
1067
+ readonly automatic: ForgeAutomaticBase | null
1068
+ /** Remote-tracking branches without symbolic refs such as `origin/HEAD`, most recently committed first, at most `maxBranches`. */
1069
+ readonly remote: readonly ForgeBaseBranch[]
1070
+ /** Local branches other than the checked-out one, most recently committed first, at most `maxBranches`. */
1071
+ readonly local: readonly ForgeBaseBranch[]
1072
+ }
1073
+ ```
1074
+
1075
+ ```ts type-equiv
1076
+ /** One hunk of a file diff. */
1077
+ interface ForgeDiffHunk {
1078
+ /** First old line the hunk covers, or the old line it comes before when it covers none. */
1079
+ readonly oldStart: number
1080
+ /** Old lines the hunk covers. */
1081
+ readonly oldLines: number
1082
+ /** First new line the hunk covers, or the new line it comes before when it covers none. */
1083
+ readonly newStart: number
1084
+ /** New lines the hunk covers. */
1085
+ readonly newLines: number
1086
+ /** Lines in patch form: a leading space for context, `+` for added, `-` for deleted, and `\` for a missing final newline. */
1087
+ readonly lines: readonly string[]
1088
+ }
1089
+ ```
1090
+
1091
+ ```ts type-equiv
1092
+ /** How one file changed. */
1093
+ type ForgeDiffStatus = 'added' | 'deleted' | 'modified' | 'renamed'
1094
+ ```
1095
+
1096
+ ```ts type-equiv
1097
+ /**
1098
+ * Review layer of a changed file: the layer the repository's root `.gitattributes` names with `dsh-review-layer`, else
1099
+ * `test` for a file it marks `linguist-generated`, else the first of `testPaths`, `interfacePaths`, and the built-in
1100
+ * test and interface rules that the path matches, else `implementation`.
1101
+ */
1102
+ type ForgeFileLayer = 'interface' | 'implementation' | 'test'
1103
+ ```
1104
+
1105
+ ```ts type-equiv
1106
+ /** One changed file of a diff. */
1107
+ interface ForgeDiffFile {
1108
+ /** Path relative to the repository root after the change, or before it for a deleted file. */
1109
+ readonly path: string
1110
+ /** Path before a rename, else null. */
1111
+ readonly previousPath: string | null
1112
+ /** How the file changed. */
1113
+ readonly status: ForgeDiffStatus
1114
+ /** Whether the content is binary, which carries no hunks. */
1115
+ readonly binary: boolean
1116
+ /** Whether an untracked file exceeds `maxDiffBytes`, so it carries no hunks. */
1117
+ readonly oversized: boolean
1118
+ /** Whether Git does not track the file yet; only local diffs list such files. */
1119
+ readonly untracked: boolean
1120
+ /** Lines added. */
1121
+ readonly additions: number
1122
+ /** Lines deleted. */
1123
+ readonly deletions: number
1124
+ /** Hunks in file order; none for a binary file or a change of mode or name only. */
1125
+ readonly hunks: readonly ForgeDiffHunk[]
1126
+ /** Review layer the path falls in. */
1127
+ readonly layer: ForgeFileLayer
1128
+ /**
1129
+ * Digest of the file's change: its paths, status, and hunks, plus the blob names of a binary change, or the content
1130
+ * of an untracked binary file. Equal digests describe the same change; reading the same comparison again keeps it.
1131
+ */
1132
+ readonly digest: string
1133
+ }
1134
+ ```
1135
+
1136
+ ```ts type-equiv
1137
+ /** Local changes of the Session's checkout in one scope. */
1138
+ interface ForgeLocalDiff {
1139
+ /** Ref or abbreviated commit the `branch` scope compares with, such as `origin/master`; null for other scopes. */
1140
+ readonly base: string | null
1141
+ /** Changed files in Git's path order, untracked files last. */
1142
+ readonly files: readonly ForgeDiffFile[]
1143
+ /** Untracked files left out beyond `maxUntrackedFiles`. */
1144
+ readonly omitted: number
1145
+ }
1146
+ ```
1147
+
1148
+ ```ts type-equiv
1149
+ /** Changes of a change request, or of consecutive commits of it, as its code host computes them. */
1150
+ interface ForgeChangeRequestDiff {
1151
+ /** Commit whose new side the diff describes: the head, or the last chosen commit. */
1152
+ readonly headSha: string
1153
+ /** Changed files in the host's order. */
1154
+ readonly files: readonly ForgeDiffFile[]
1155
+ /**
1156
+ * Commits the checkout's branch has beyond the head when that branch is the change request's head
1157
+ * branch and the head commit is present locally; null otherwise and for chosen commits.
1158
+ */
1159
+ readonly localAhead: number | null
1160
+ /** Web page of each changed file at `headSha`, by path. */
1161
+ readonly fileUrls: Readonly<Record<string, string>>
1162
+ }
1163
+ ```
1164
+
1165
+ ```ts type-equiv
1166
+ /** Commits of the `branch` scope at one HEAD commit, which the changes view can show one or several of. */
1167
+ interface ForgeLocalCommits {
1168
+ /** HEAD commit the list was read at. */
1169
+ readonly headSha: string
1170
+ /** Commits oldest first, ending with HEAD's commit. */
1171
+ readonly commits: readonly ForgeCommit[]
1172
+ /** Whether the branch has more commits than `maxCommits`; the listed ones are the newest. */
1173
+ readonly truncated: boolean
1174
+ }
1175
+ ```
1176
+
1177
+ ```ts type-equiv
1178
+ /** One file the `branch` scope changes, without its hunks. */
1179
+ interface ForgeChangedFile {
1180
+ /** Path relative to the repository root after the change. */
1181
+ readonly path: string
1182
+ /** Lines added, or 0 for a binary file. */
1183
+ readonly additions: number
1184
+ /** Lines deleted, or 0 for a binary file. */
1185
+ readonly deletions: number
1186
+ /** Whether the content is binary. */
1187
+ readonly binary: boolean
1188
+ }
1189
+ ```
1190
+
1191
+ ```ts type-equiv
1192
+ /** Size of the `branch` scope, which the panel's local changes card shows. */
1193
+ interface ForgeLocalChanges {
1194
+ /** Ref or abbreviated commit the scope compares with. */
1195
+ readonly base: string
1196
+ /** Changed files in Git's path order, untracked files last, at most `maxUntrackedFiles` of those. */
1197
+ readonly files: readonly ForgeChangedFile[]
1198
+ }
1199
+ ```
1200
+
1201
+ <a id="extension-api"></a>
1202
+
1203
+ ## Extension API
1204
+
1205
+ Other plugins extend change request review through the services above and five more extension points; each registration returns its disposer and ends with the plugin that made it. The surface is experimental and pre-stable like the rest of this capability. Adding an optional field, method, slot, event, error code, or defaulted Config key is compatible; removing or renaming a type, method, slot, owner prop, event, Config key, or the `dsh-resource://forge-changes/` address, or narrowing what one means, needs an [upgrade guide](../../.agents/skills/dsh-create-upgrade-guide/SKILL.md). None of these extension points sends anything to a model: a plugin that shows forge data, lens results, or annotations to a model does so through its own tool results or `agent.inject()`, which the Session log records.
1206
+
1207
+ | Extension point | Service Definition | Service Provider | Consumer |
1208
+ |---|---|---|---|
1209
+ | Review data | `ctx.forge` and the `forge/review-changed` event | The forge providers | The panel host and other Host plugins |
1210
+ | Filter lenses | `ctx.forgePanel.registerLens` | Host plugins | The changes view through `changesLens` |
1211
+ | Changes view face | `ctx.forgeChanges` | The forge panel's Client plugin | Client plugins that open views, name lenses, and add annotations |
1212
+ | Slots | `forge.changes.toolbar`, `forge.changes.file.menu`, `forge.diff.line.annotation`, `forge.diff.comment.actions`, `forge.change-request.card.actions` | Client plugins | The changes view and the change request card |
1213
+ | Session data | The `forgeChangeRequests` and `forgeWorktree` projections | forge-session and the forge panel | Client plugins through `useProjection`, Host plugins through `ctx.forgeSession.changeRequests` or `ctx.sessionProjections.snapshot` |
1214
+
1215
+ `ctx.forge` announces `forge/review-changed` after a review write, a submitted review, or a viewed mark the host accepted, whichever plugin made it; an outcome that is unknown, refused, stale, or unsupported is not announced. The panel host then drops the threads it keeps for the change request and raises `reviewRevision` on its entries, so an open changes view reads the threads and the viewed marks again without a refresh.
1216
+
1217
+ ```ts type-equiv
1218
+ /**
1219
+ * One review change the host accepted, which `forge/review-changed` announces: a write to the threads or the pending
1220
+ * review, a submitted review, or a viewed mark. Each names the head commit the change was made against.
1221
+ */
1222
+ type ForgeReviewChange =
1223
+ | { readonly kind: 'write'; readonly write: ForgeReviewWrite['kind']; readonly headSha: string }
1224
+ | { readonly kind: 'submit'; readonly headSha: string }
1225
+ | { readonly kind: 'viewed'; readonly path: string; readonly viewed: boolean; readonly headSha: string }
1226
+ ```
1227
+
1228
+ A Host plugin registers a filter lens with `ctx.forgePanel.registerLens(lens)`, and a Client plugin names it with `ctx.forgeChanges.describeLens({ id, title })`; a lens without a description shows its identity. A lens filters the shown files and does not place them in layers: the changes view shows the files that both the chosen layer and the chosen lens keep, the test switch still hides test files, and a lens cannot move a file to another layer. Hunks that meet none of a kept file's `ranges` fold into one row the reader can expand. The view asks for the chosen lens only, through the streaming Remote `changesLens`, which reads the same files as the view, refuses other files with `forgePanel/lens-stale`, and runs `classify` within `lensTimeoutMs` and `maxLensReadBytes`; results are kept up to `maxCachedLensBytes`. `text` reads a local old side at the fork point, HEAD, the index, or the parent of the first chosen commit, and a change request's old side at the chosen commits' parent or at the merge base its provider names through the optional `mergeBase`.
1229
+
1230
+ ```ts type-equiv
1231
+ /** A filter over the files of a changes view, which a plugin registers with `ctx.forgePanel.registerLens()`. */
1232
+ interface ForgeLens {
1233
+ /** Identity the plugin chooses; one registration holds an id at a time. */
1234
+ readonly id: ForgeLensId
1235
+ /** Changes whenever `classify` may answer differently for the same input, which drops results kept under another version. */
1236
+ readonly version: string
1237
+ /**
1238
+ * Decide which files the lens keeps and which lines it is about. The signal aborts when the reader stops waiting, the
1239
+ * registration ends, or `lensTimeoutMs` passes; a rejection fails the request without a partial result.
1240
+ */
1241
+ classify(input: ForgeLensInput, signal: AbortSignal): Promise<ForgeLensResult>
1242
+ }
1243
+ ```
1244
+
1245
+ ```ts type-equiv
1246
+ /** What one `classify` call receives. */
1247
+ interface ForgeLensInput {
1248
+ /** What the view shows. */
1249
+ readonly source: ForgeLensSource
1250
+ /** Every file of the diff in display order, with the layer the panel gave it. */
1251
+ readonly files: readonly ForgeLensFile[]
1252
+ /**
1253
+ * Read one side of a file of `files`, by its `path`: the old side reads `previousPath` for a renamed file. Resolves to
1254
+ * null when the side does not exist, as the old side of an added file, or holds binary content. Rejects for a path not
1255
+ * in `files`, once the reads of one call exceed the panel's `maxLensReadBytes`, for an old side the provider cannot
1256
+ * locate, and when the read fails.
1257
+ */
1258
+ text(side: 'old' | 'new', path: string, signal: AbortSignal): Promise<string | null>
1259
+ /** Report how many of `total` units the lens finished; the changes view shows it while the lens runs. */
1260
+ progress(done: number, total: number): void
1261
+ }
1262
+ ```
1263
+
1264
+ ```ts type-equiv
1265
+ /** The files a lens keeps, by path; a file the result does not name is filtered out. */
1266
+ interface ForgeLensResult {
1267
+ readonly files: Readonly<Record<string, ForgeLensKeep>>
1268
+ }
1269
+ ```
1270
+
1271
+ ```ts type-equiv
1272
+ /** What a lens keeps of one file. */
1273
+ interface ForgeLensKeep {
1274
+ /**
1275
+ * Lines the lens is about on each side; hunks that touch none of them fold into one row the reader can expand.
1276
+ * Omitted: every hunk of the file shows.
1277
+ */
1278
+ readonly ranges?: { readonly old: readonly ForgeLineRange[]; readonly new: readonly ForgeLineRange[] }
1279
+ }
1280
+ ```
1281
+
1282
+ `ctx.forgeChanges` also mints and reads changes addresses, names the tab `kinds`, and opens a view with `open(target, { path, lens })` in the Session the sidebar shows. `registerAnnotations(source)` adds line annotations: the source observes each view's annotations by path, and its `forge.diff.line.annotation` entry, keyed by the source's identity, draws each one inside a frame the view draws. Annotations stack on the row of their last line after the comment form, the browser drafts, and the host threads, in the column of their side; whole-file annotations come first, and those written on another `digest` follow the file in their own section. A file shows at most `maxAnnotationsPerFile`, and a file that draws its lines draws no annotation before its section comes within reach of the scrollport. The owner props of every slot live in [`src/client/slots.ts`](../../packages/experimental/client-ui-forge/src/client/slots.ts).
1283
+
1284
+ ```ts type-equiv
1285
+ /** Where one line annotation sits in a file of a changes view. */
1286
+ interface ForgeLineAnnotation {
1287
+ /** Identity within its source, stable across updates; it keys the drawn entry. */
1288
+ readonly key: string
1289
+ /** Inclusive 1-based lines on the old (`L`) or new (`R`) side, or `file` for the whole file. */
1290
+ readonly place: { readonly side: 'L' | 'R'; readonly startLine: number; readonly endLine: number } | 'file'
1291
+ /** Digest of the file change it was written on; a file whose `digest` differs shows it in its outdated section. */
1292
+ readonly digest?: string
1293
+ /** Changes whenever the entry should draw again; equal keys and versions keep the drawn entry's props. */
1294
+ readonly version: string | number
1295
+ }
1296
+ ```
1297
+
1298
+ ```ts type-equiv
1299
+ /** A Client plugin's line annotations, drawn through its `forge.diff.line.annotation` entry. */
1300
+ interface ForgeAnnotationSource {
1301
+ /** Unique source identity, also the key of its `forge.diff.line.annotation` entry. */
1302
+ readonly id: string
1303
+ /** Position among sources in one stack, lower first; equal positions keep registration order. Default 0. */
1304
+ readonly order?: number
1305
+ /**
1306
+ * Observe the annotations of one view, by path. The changes view calls it once per view and source registration;
1307
+ * the source republishes through the same observable whenever the annotations change.
1308
+ * @param target - the view's Session and source.
1309
+ * @returns annotations by repository-relative path.
1310
+ */
1311
+ observe(target: ChangesTarget): HostObservable<ReadonlyMap<string, readonly ForgeLineAnnotation[]>>
1312
+ }
1313
+ ```
1314
+
1315
+ <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
1316
+
1317
+ <a id="cordis-surface"></a>
1318
+
1319
+ ## Cordis API
1320
+
1321
+ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
1322
+
1323
+ <a id="ctxforge--forgeservice"></a>
1324
+
1325
+ ### `ctx.forge` — `ForgeService`
1326
+
1327
+ Registry of forge providers. Watchers of one change request share a single polling loop; each forge has at most one fetch in flight, which batches every due change request of that forge.
1328
+
1329
+ ```ts cordis-catalog
1330
+ /**
1331
+ * Register the provider of one forge family as an effect on the calling
1332
+ * fiber. Watches of that forge receive an `unavailable` failure when the
1333
+ * registration ends. Unregistering cancels and awaits its in-flight calls;
1334
+ * results from the withdrawn registration are discarded.
1335
+ * @param provider - provider to serve `provider.forge`.
1336
+ * @returns the disposer that unregisters the provider and waits for its calls to finish.
1337
+ * @throws when another provider already serves the same forge.
1338
+ */
1339
+ register(provider: ForgeProvider): () => Promise<void>
1340
+
1341
+ /**
1342
+ * Map a Git remote URL to a repository through the first provider that serves it.
1343
+ * @param url - fetch or push URL as Git reports it.
1344
+ * @returns the repository, or undefined when no provider serves the URL.
1345
+ */
1346
+ repositoryFromRemote(url: string): ForgeRepository | undefined
1347
+
1348
+ /**
1349
+ * Map a change request web URL through the first provider that serves it.
1350
+ * @param url - web URL of a pull or merge request.
1351
+ * @returns the change request, or undefined when no provider serves the URL.
1352
+ */
1353
+ changeRequestFromUrl(url: string): ChangeRequestRef | undefined
1354
+
1355
+ /**
1356
+ * Build the canonical web URL of a change request.
1357
+ * @param ref - change request served by a registered provider.
1358
+ * @returns the provider's canonical URL.
1359
+ * @throws when no provider serves the change request's forge.
1360
+ */
1361
+ changeRequestUrl(ref: ChangeRequestRef): string
1362
+
1363
+ /**
1364
+ * Find open change requests whose head is `branch`, excluding the default branch and forks.
1365
+ * @param repository - repository served by a registered provider.
1366
+ * @param branch - head branch name on the host.
1367
+ * @param signal - cancellation for the lookup.
1368
+ * @returns matching change requests, newest first as the provider reports them.
1369
+ * @throws when no provider serves the repository's forge or the lookup fails.
1370
+ */
1371
+ async findByHead(repository: ForgeRepository, branch: string, signal: AbortSignal): Promise<readonly ChangeRequestRef[]>
1372
+
1373
+ /**
1374
+ * Report whether the provider can reach the repository's host with current credentials.
1375
+ * @param repository - repository served by a registered provider.
1376
+ * @param signal - cancellation for the probe.
1377
+ * @returns the provider's readiness for the host.
1378
+ * @throws when no provider serves the repository's forge.
1379
+ */
1380
+ async readiness(repository: ForgeRepository, signal: AbortSignal): Promise<ForgeReadiness>
1381
+
1382
+ /**
1383
+ * Mark a draft change request ready for review, then poll it at once when it is watched.
1384
+ * @param ref - change request served by a registered provider.
1385
+ * @param signal - cancellation of the request.
1386
+ * @returns undefined once the host accepted the change, or why it did not.
1387
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1388
+ */
1389
+ async markReady(ref: ChangeRequestRef, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
1390
+
1391
+ /**
1392
+ * Merge a change request while its head is still `request.headSha`, then
1393
+ * poll it at once when it is watched.
1394
+ * @param ref - change request served by a registered provider.
1395
+ * @param request - merge method and the head commit the user confirmed.
1396
+ * @param signal - cancellation of the request.
1397
+ * @returns undefined once the host merged it, or why it did not.
1398
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1399
+ */
1400
+ async merge(ref: ChangeRequestRef, request: MergeRequest, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
1401
+
1402
+ /**
1403
+ * Rerun the failed checks of a change request's head commit while the head is still `request.headSha`, then poll it
1404
+ * at once when it is watched.
1405
+ * @param ref - change request served by a registered provider.
1406
+ * @param request - the head commit whose checks the user saw.
1407
+ * @param signal - cancellation of the request.
1408
+ * @returns undefined once the host accepted the rerun, or why it did not; a provider that reruns no checks fails.
1409
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1410
+ */
1411
+ async rerunChecks(ref: ChangeRequestRef, request: ForgeRerunRequest, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
1412
+
1413
+ /**
1414
+ * Request reviews of a change request from more reviewers, then poll it at once when it is watched.
1415
+ * @param ref - change request served by a registered provider.
1416
+ * @param request - the reviewers to ask.
1417
+ * @param signal - cancellation of the request.
1418
+ * @returns undefined once the host accepted the request, or why it did not; a provider that requests no reviews fails.
1419
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1420
+ */
1421
+ async requestReviews( ref: ChangeRequestRef, request: ForgeReviewersRequest, signal: AbortSignal, ): Promise<ForgeActionFailure | undefined>
1422
+
1423
+ /**
1424
+ * List accounts a review of a change request can be requested from.
1425
+ * @param ref - change request served by a registered provider.
1426
+ * @param query - text the login or name contains; empty for the host's suggestions and first accounts.
1427
+ * @param signal - cancellation of the read.
1428
+ * @returns the accounts, the host's suggestions first, or why the host did not list them; a provider that requests no
1429
+ * reviews returns `unsupported`.
1430
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1431
+ */
1432
+ async reviewerCandidates( ref: ChangeRequestRef, query: string, signal: AbortSignal, ): Promise<readonly ForgeReviewerCandidate[] | ForgeFailure>
1433
+
1434
+ /**
1435
+ * Bring the base branch's new commits into a change request's head branch while its head is still
1436
+ * `request.headSha`, then poll it at once when it is watched.
1437
+ * @param ref - change request served by a registered provider.
1438
+ * @param request - update method and the head commit the user confirmed.
1439
+ * @param signal - cancellation of the request.
1440
+ * @returns undefined once the host accepted the update, or why it did not; a provider that updates no branches fails.
1441
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1442
+ */
1443
+ async updateBranch(ref: ChangeRequestRef, request: BranchUpdateRequest, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
1444
+
1445
+ /**
1446
+ * Read complete check details independently of periodic polling.
1447
+ * @param ref - change request served by a registered provider.
1448
+ * @param signal - cancellation of the detail view.
1449
+ * @returns a complete snapshot or the provider's failure; unsupported providers return unavailable.
1450
+ * @throws on cancellation, provider disposal, or expiry of this read's own `fetchTimeoutMs` deadline.
1451
+ */
1452
+ async fetchChecks(ref: ChangeRequestRef, signal: AbortSignal): Promise<ChangeRequestSnapshot | ForgeFailure>
1453
+
1454
+ /**
1455
+ * Read the changes of a change request at one head commit as its host
1456
+ * computes them. The first `stale` failure at a head commit polls a watched
1457
+ * change request again at once, so its next status names the current head;
1458
+ * later stale reads at the same commit wait for the regular poll, which
1459
+ * bounds the reads while the host's answers disagree. When the latest status of a watched change request names
1460
+ * `headSha`, its `baseSha` goes to the provider, which may then read the patch while it confirms the head.
1461
+ * @param ref - change request served by a registered provider.
1462
+ * @param headSha - head commit the patch must describe, usually the head of the latest status.
1463
+ * @param signal - cancellation of the read.
1464
+ * @returns one unified patch with `a/` and `b/` path prefixes, or why the host did not provide it at `headSha`.
1465
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1466
+ */
1467
+ async diff(ref: ChangeRequestRef, headSha: string, signal: AbortSignal): Promise<string | ForgeFailure>
1468
+
1469
+ /**
1470
+ * Read the review threads of a change request at one head commit. A
1471
+ * `stale` failure polls a watched change request again as {@link ForgeService.diff} does.
1472
+ * @param ref - change request served by a registered provider.
1473
+ * @param headSha - head commit the threads must be read at, usually the head of the displayed diff.
1474
+ * @param signal - cancellation of the read.
1475
+ * @returns the threads read at `headSha`, or why the host did not provide them; `unsupported` when the provider cannot read reviews.
1476
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1477
+ */
1478
+ async readReview(ref: ChangeRequestRef, headSha: string, signal: AbortSignal): Promise<ForgeReviewData | ForgeFailure>
1479
+
1480
+ /**
1481
+ * Write to the review threads or the pending review of a change request while its head is still `write.headSha`, then
1482
+ * poll the change request at once when it is watched, unless the provider cannot write reviews. A `stale` result polls
1483
+ * it as a stale read does; a write the host took is announced with `forge/review-changed`.
1484
+ * @param ref - change request served by a registered provider.
1485
+ * @param write - the write and the head commit the user saw.
1486
+ * @param signal - cancellation of the write.
1487
+ * @returns whether the host took the write; `unsupported` when the provider cannot write reviews, and `unknown` when the
1488
+ * outcome must be checked on the host before the write is tried again.
1489
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1490
+ */
1491
+ async writeReview(ref: ChangeRequestRef, write: ForgeReviewWrite, signal: AbortSignal): Promise<ForgeReviewWriteResult>
1492
+
1493
+ /**
1494
+ * Read the signed-in user's viewed marks on the files of a change request at one head commit. A `stale` failure
1495
+ * polls a watched change request again as {@link ForgeService.diff} does.
1496
+ * @param ref - change request served by a registered provider.
1497
+ * @param headSha - head commit the marks must be read at, usually the head of the displayed diff.
1498
+ * @param signal - cancellation of the read.
1499
+ * @returns the marks read at `headSha`, why the host did not provide them, or undefined when the provider keeps no
1500
+ * viewed marks.
1501
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1502
+ */
1503
+ async viewedFiles(ref: ChangeRequestRef, headSha: string, signal: AbortSignal): Promise<ForgeViewedFiles | ForgeFailure | undefined>
1504
+
1505
+ /**
1506
+ * Set or clear the signed-in user's viewed mark on one file of a change request while its head is still
1507
+ * `request.headSha`. A `stale` failure polls a watched change request again as {@link ForgeService.diff} does; a saved
1508
+ * mark is announced with `forge/review-changed`.
1509
+ * @param ref - change request served by a registered provider.
1510
+ * @param request - the file, its mark, and the head commit the user saw.
1511
+ * @param signal - cancellation of the request.
1512
+ * @returns undefined once the host saved the mark, or why it did not; a provider that keeps no viewed marks fails.
1513
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1514
+ */
1515
+ async setViewed(ref: ChangeRequestRef, request: ForgeViewedRequest, signal: AbortSignal): Promise<ForgeActionFailure | undefined>
1516
+
1517
+ /**
1518
+ * List the commits of a change request at its current head.
1519
+ * @param ref - change request served by a registered provider.
1520
+ * @param signal - cancellation of the read.
1521
+ * @returns the commits, why the host did not provide them, or undefined when the provider does not read commits.
1522
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1523
+ */
1524
+ async commits(ref: ChangeRequestRef, signal: AbortSignal): Promise<ForgeCommitList | ForgeFailure | undefined>
1525
+
1526
+ /**
1527
+ * Read the changes between two commits of a change request's repository.
1528
+ * @param ref - change request served by a registered provider.
1529
+ * @param base - full object name of the commit compared from.
1530
+ * @param head - full object name of a commit that descends from `base`.
1531
+ * @param signal - cancellation of the read.
1532
+ * @returns one unified patch with `a/` and `b/` path prefixes, or why the host did not provide it; a provider that
1533
+ * does not read commits returns `unavailable`.
1534
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1535
+ */
1536
+ async compare(ref: ChangeRequestRef, base: string, head: string, signal: AbortSignal): Promise<string | ForgeFailure>
1537
+
1538
+ /**
1539
+ * Find the merge base of two commits of a change request's repository: the commit whose files the old side of the
1540
+ * change request's diff shows when `base` is the snapshot's `baseSha` and `head` its `headSha`.
1541
+ * @param ref - change request served by a registered provider.
1542
+ * @param base - full object name of the base commit.
1543
+ * @param head - full object name of the head commit.
1544
+ * @param signal - cancellation of the read.
1545
+ * @returns the merge base's full object name, or why the host did not provide it; `unsupported` when the provider
1546
+ * cannot name merge bases.
1547
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1548
+ */
1549
+ async mergeBase(ref: ChangeRequestRef, base: string, head: string, signal: AbortSignal): Promise<string | ForgeFailure>
1550
+
1551
+ /**
1552
+ * Publish one comment or a completed review, then refresh the watched status; a publication the host took is announced
1553
+ * with `forge/review-changed`.
1554
+ * @param ref - change request served by a registered provider.
1555
+ * @param request - explicitly selected commit, comments, and review decision.
1556
+ * @param signal - request cancellation.
1557
+ * @returns a publication URL or a failure, including an unknown write outcome.
1558
+ */
1559
+ async submitReview(ref: ChangeRequestRef, request: ForgeReviewRequest, signal: AbortSignal): Promise<ForgeReviewResult>
1560
+
1561
+ /**
1562
+ * Read the text of one file at a commit of a change request's repository.
1563
+ * @param ref - change request served by a registered provider.
1564
+ * @param commit - commit object name.
1565
+ * @param path - path relative to the repository root.
1566
+ * @param signal - cancellation of the read.
1567
+ * @returns the text, or why the host did not provide it.
1568
+ * @throws when no provider serves the change request's forge, or with `signal.reason` when it aborts.
1569
+ */
1570
+ async fileText(ref: ChangeRequestRef, commit: string, path: string, signal: AbortSignal): Promise<string | ForgeFailure>
1571
+
1572
+ /**
1573
+ * Build the web page of one file at a commit of a change request's repository.
1574
+ * @param ref - change request served by a registered provider.
1575
+ * @param commit - commit object name.
1576
+ * @param path - path relative to the repository root.
1577
+ * @returns the provider's URL.
1578
+ * @throws when no provider serves the change request's forge.
1579
+ */
1580
+ fileUrl(ref: ChangeRequestRef, commit: string, path: string): string
1581
+
1582
+ /**
1583
+ * Watch one change request as an effect on the calling fiber. The first
1584
+ * watcher starts polling at once; listeners receive every later result,
1585
+ * and `latest` returns the current one. Polling stops when the last watcher
1586
+ * leaves. Listeners must not throw; a thrown error is logged and skipped.
1587
+ * @param ref - change request to poll.
1588
+ * @param listener - receives each polling result.
1589
+ * @returns the disposer that stops this watch.
1590
+ */
1591
+ watch(ref: ChangeRequestRef, listener: ChangeRequestListener): () => void
1592
+
1593
+ /**
1594
+ * Poll a watched change request at once, including after a terminal state.
1595
+ * A fetch already in flight is followed immediately by another fetch; an
1596
+ * unwatched change request is unchanged.
1597
+ * @param ref - change request to poll.
1598
+ */
1599
+ refresh(ref: ChangeRequestRef): void
1600
+
1601
+ /**
1602
+ * Read the latest polling result of a watched change request.
1603
+ * @param ref - change request to read.
1604
+ * @returns the latest result, or undefined before the first result or without a watch.
1605
+ */
1606
+ latest(ref: ChangeRequestRef): ChangeRequestUpdate | undefined
1607
+ ```
1608
+
1609
+ Source: [`packages/experimental/forge/src/index.ts`](../../packages/experimental/forge/src/index.ts)
1610
+
1611
+ <a id="ctxforgepanel--forgepanelcontroller"></a>
1612
+
1613
+ ### `ctx.forgePanel` — `ForgePanelController`
1614
+
1615
+ Serves the forge panel. Following a Session inspects the checkout of its current directory using the Session's permissions and execution world, recognizes the code host of the upstream remote or `origin`, probes sign-in for that host, and polls the Session's bound change requests through `ctx.forge` until the last follower of that Session leaves. A worktree request makes the panel create a branch and checkout through an optional `ctx.worktrees` and enter it while the Session's first message waits in the inbox, or at the first step proposal of a message no wait took. After a failed or stopped attempt the message keeps waiting for another attempt or for the request to be withdrawn. A message also waits while the worktree the Session works in is gone, until that worktree is checked out again or left for the source checkout. Changes to a Session's checkout run as Agent maintenance, so a message that arrives meanwhile waits for them.
1616
+
1617
+ ```ts cordis-catalog
1618
+ /**
1619
+ * Stream the panel state of a Session: the current state first, then each
1620
+ * later state. Intermediate states may be skipped when the reader is slower
1621
+ * than the updates. The stream ends when `signal` aborts or the Remote stops.
1622
+ * @param session - Session to follow.
1623
+ * @param signal - ends the stream.
1624
+ * @returns panel states in publication order.
1625
+ */
1626
+ @Remote({ mode: 'stream' }) async *follow(session: Session, signal: AbortSignal): AsyncIterable<ForgePanelState>
1627
+
1628
+ /**
1629
+ * Stream the last known state of every change request that a listed
1630
+ * Session the caller may read binds: the stored states first, then each
1631
+ * state a poll changes or a new binding reveals. Listing reads no Session
1632
+ * log: loaded Sessions report their bindings, others the cached Session
1633
+ * summary, and a Session without one shows nothing until it is opened.
1634
+ * Closed requests are polled at the idle cadence so reopening is observed. The
1635
+ * stream ends when `signal` aborts or the Remote stops.
1636
+ * @param signal - ends the stream.
1637
+ * @returns frames in publication order.
1638
+ * @throws when the Host lacks the Session query, projection cache, storage, or Session store service.
1639
+ */
1640
+ @Remote({ mode: 'stream' }) async *changeRequestStates(signal: AbortSignal): AsyncIterable<ForgeChangeRequestStatesFrame>
1641
+
1642
+ /**
1643
+ * Read or await the worktree service's archive cleanup of the Session's checkout.
1644
+ * @param session - Session whose cleanup is visible to the caller.
1645
+ * @param revision - null reads now; a matching revision waits for completion.
1646
+ * @returns the cleanup state, or null without a worktree service, an attempt, or a current revision.
1647
+ */
1648
+ @Remote async worktreeCleanup(session: Session, revision: number | null): Promise<WorktreeCleanup | null>
1649
+
1650
+ /**
1651
+ * List the local branches that can start a worktree of the Session's current checkout.
1652
+ * @param session - Session whose current directory is listed.
1653
+ * @param signal - request cancellation.
1654
+ * @returns the checked-out branch and at most `maxBranches` local branches, the checked-out one first.
1655
+ * @throws when the current directory is not inside a checkout or Git fails.
1656
+ */
1657
+ @Remote async branches(session: Session, signal: AbortSignal): Promise<ForgeBranches>
1658
+
1659
+ /**
1660
+ * List the branches the `branch` scope of the Session's current checkout can compare with.
1661
+ * @param session - Session whose checkout is read.
1662
+ * @param query - text the branch names must contain, ignoring case; empty for every branch.
1663
+ * @param signal - request cancellation.
1664
+ * @returns the automatic fork point and at most `maxBranches` remote-tracking and local branches each,
1665
+ * most recently committed first, without the checked-out branch.
1666
+ * @throws `forgePanel/not-a-checkout` outside a checkout; the Git error when Git fails or the listed refs exceed `maxDiffBytes`.
1667
+ */
1668
+ @Remote async diffBases(session: Session, query: string, signal: AbortSignal): Promise<ForgeBaseBranches>
1669
+
1670
+ /**
1671
+ * Bind a change request to a Session by URL, or restore a dismissed binding.
1672
+ * @param session - Session that receives the binding.
1673
+ * @param url - web URL of the change request.
1674
+ * @param signal - request cancellation.
1675
+ * @throws `forgePanel/unrecognized-url` when no registered provider serves the URL.
1676
+ */
1677
+ @Remote async track(session: Session, url: string, signal: AbortSignal): Promise<void>
1678
+
1679
+ /**
1680
+ * Hide a visible binding of a Session; other URLs leave the Session unchanged.
1681
+ * @param session - Session that owns the binding.
1682
+ * @param url - web URL of the change request.
1683
+ * @param signal - request cancellation.
1684
+ */
1685
+ @Remote async dismiss(session: Session, url: string, signal: AbortSignal): Promise<void>
1686
+
1687
+ /**
1688
+ * Reinspect a followed Session's checkout, probe sign-in again, and poll its
1689
+ * change requests at once; an unfollowed Session is unchanged.
1690
+ * @param session - Session whose panel refreshes.
1691
+ * @param signal - request cancellation.
1692
+ * @returns settles once the inspection and the sign-in probe have ended; poll results reach the panel stream later.
1693
+ */
1694
+ @Remote async refresh(session: Session, signal: AbortSignal): Promise<void>
1695
+
1696
+ /**
1697
+ * Read the changes one scope covers in the checkout of the Session's
1698
+ * current directory, with Git using the Session's sandbox policy.
1699
+ * @param session - Session whose checkout is read.
1700
+ * @param scope - which changes to read.
1701
+ * @param signal - request cancellation.
1702
+ * @returns the files, the base of the `branch` scope, and the untracked files left out.
1703
+ * @throws `forgePanel/not-a-checkout` outside a checkout; `forgePanel/base-missing` when the base branch
1704
+ * chosen for the `branch` scope does not exist; `forgePanel/no-fork-point` for the `branch` scope without
1705
+ * a fork point; `forgePanel/commits-missing` when chosen commits are not between the fork point and HEAD in order;
1706
+ * `forgePanel/commits-not-linear` when a chosen commit's first parent is not the commit listed before it;
1707
+ * `forgePanel/filter-config-failed` when filter configuration cannot be read completely, before
1708
+ * any comparison; `forgePanel/diff-too-large` over `maxDiffBytes`; the Git error when Git fails.
1709
+ */
1710
+ @Remote async localDiff(session: Session, scope: ForgeLocalScope, signal: AbortSignal): Promise<ForgeLocalDiff>
1711
+
1712
+ /**
1713
+ * List the commits of the `branch` scope of the Session's current checkout, which the changes view can show one or
1714
+ * several of instead of the whole scope.
1715
+ * @param session - Session whose checkout is read.
1716
+ * @param base - the base ref of the `branch` scope, or null for its automatic fork point.
1717
+ * @param signal - request cancellation.
1718
+ * @returns the newest `maxCommits` commits between the fork point and HEAD, oldest first, and HEAD's commit.
1719
+ * @throws `forgePanel/not-a-checkout` outside a checkout; `forgePanel/base-missing` when the base branch does not
1720
+ * exist; `forgePanel/no-fork-point` without a fork point; the Git error when Git fails or the listing exceeds `maxDiffBytes`.
1721
+ */
1722
+ @Remote async localCommits(session: Session, base: string | null, signal: AbortSignal): Promise<ForgeLocalCommits>
1723
+
1724
+ /**
1725
+ * Read the new side of one changed file in a scope, which supplies the
1726
+ * unchanged lines between its hunks: the index for `staged`, the last chosen commit for chosen commits of
1727
+ * the `branch` scope, else the working tree.
1728
+ * @param session - Session whose checkout is read.
1729
+ * @param scope - the scope the diff was read in.
1730
+ * @param path - path relative to the checkout's top-level directory, as the diff names it.
1731
+ * @param signal - request cancellation.
1732
+ * @returns the text.
1733
+ * @throws `forgePanel/not-a-checkout` outside a checkout; `forgePanel/diff-too-large` over `maxDiffBytes`;
1734
+ * `forgePanel/commits-missing` for a chosen commit that is not a full object name; an error for a path outside
1735
+ * the checkout, binary content, or a failed read.
1736
+ */
1737
+ @Remote async localText(session: Session, scope: ForgeLocalScope, path: string, signal: AbortSignal): Promise<string>
1738
+
1739
+ /**
1740
+ * Load complete checks for the change request whose list the user opened.
1741
+ * @param session - Session retaining the visible binding.
1742
+ * @param url - binding URL.
1743
+ * @param headSha - commit displayed by the card; a different head rejects the result.
1744
+ * @param signal - cancellation when the list closes.
1745
+ * @returns the complete snapshot at the displayed head.
1746
+ * @throws `forgePanel/not-bound` for an invisible binding, `forgePanel/stale-head` for a moved head,
1747
+ * or `forgePanel/checks-failed` when the provider cannot complete the read.
1748
+ */
1749
+ @Remote async changeRequestChecks(session: Session, url: string, headSha: string, signal: AbortSignal): Promise<ChangeRequestSnapshot>
1750
+
1751
+ /**
1752
+ * List the commits of a change request of a Session at its current head, which the changes view can show one or
1753
+ * several of instead of the whole change request.
1754
+ * @param session - Session the change request is bound to.
1755
+ * @param url - binding URL of the change request.
1756
+ * @param signal - request cancellation.
1757
+ * @returns the commits oldest first with the head they were read at, or null when the provider does not read commits.
1758
+ * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1759
+ * `forgePanel/diff-failed` when the host does not list them.
1760
+ */
1761
+ @Remote async changeRequestCommits(session: Session, url: string, signal: AbortSignal): Promise<ForgeCommitList | null>
1762
+
1763
+ /**
1764
+ * Read the changes of a change request of a Session as its code host
1765
+ * computes them, with each file's layer following the root `.gitattributes`
1766
+ * of the commit the diff ends at. Without `commits` the diff covers the change
1767
+ * request at the head commit of its latest status; a patch already read at the
1768
+ * same head commit and base branch is reused from memory with its attributes, for
1769
+ * any Session, until `maxCachedDiffBytes` evicts it. With `commits`, it covers those
1770
+ * commits, from the first one's first parent to the last, and is read every time.
1771
+ * @param session - Session the change request is bound to.
1772
+ * @param url - binding URL of the change request.
1773
+ * @param commits - consecutive commits of the change request, or null for all of it.
1774
+ * @param signal - request cancellation.
1775
+ * @returns the files, the commit they end at, each file's web page, and the commits the checkout has beyond the head.
1776
+ * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1777
+ * `forgePanel/commits-missing` when chosen commits are not among the change request's commits in order;
1778
+ * `forgePanel/commits-not-linear` when a chosen commit's first parent is not the commit listed before it;
1779
+ * `forgePanel/stale-head` without `commits` when the head moved past the latest status, which is then polled again;
1780
+ * `forgePanel/diff-too-large` when the host or its provider refuses the diff over a size limit;
1781
+ * `forgePanel/diff-failed` before its status was read, for a first chosen commit without a parent, and when the host
1782
+ * does not provide the commits, the diff, or an existing `.gitattributes` otherwise.
1783
+ */
1784
+ @Remote async changeRequestDiff( session: Session, url: string, commits: ForgeCommitRange | null, signal: AbortSignal, ): Promise<ForgeChangeRequestDiff>
1785
+
1786
+ /**
1787
+ * Read one file of a change request's repository at a commit, which
1788
+ * supplies the unchanged lines between the hunks of its diff.
1789
+ * @param session - Session the change request is bound to.
1790
+ * @param url - binding URL of the change request.
1791
+ * @param commit - commit object name, the head commit of the diff.
1792
+ * @param path - path relative to the repository root.
1793
+ * @param signal - request cancellation.
1794
+ * @returns the text.
1795
+ * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1796
+ * `forgePanel/diff-too-large` when the host or its provider refuses the file over a size limit;
1797
+ * `forgePanel/diff-failed` when the host does not provide the file otherwise.
1798
+ */
1799
+ @Remote async changeRequestText(session: Session, url: string, commit: string, path: string, signal: AbortSignal): Promise<string>
1800
+
1801
+ /**
1802
+ * Register a filter lens as an effect on the calling fiber. The changes views list it while it is registered; ending
1803
+ * the registration aborts its running classifications and waits for them.
1804
+ * @param lens - the lens.
1805
+ * @returns the disposer that unregisters the lens and waits for its classifications to stop.
1806
+ * @throws when another registration holds `lens.id`.
1807
+ */
1808
+ registerLens(lens: ForgeLens): () => Promise<void>
1809
+
1810
+ /**
1811
+ * Stream the facts every changes view reads about the panel's extensions: the current facts first, then the facts
1812
+ * after each lens registration or withdrawal. The stream ends when `signal` aborts or the Remote stops.
1813
+ * @param signal - ends the stream.
1814
+ * @returns the registered lenses and the annotation limit, in publication order.
1815
+ */
1816
+ @Remote({ mode: 'stream' }) async *extensions(signal: AbortSignal): AsyncIterable<ForgeExtensionState>
1817
+
1818
+ /**
1819
+ * Classify the files a changes view of a Session shows with one registered lens: progress frames while the lens
1820
+ * runs, then the files it keeps. Results for the same files, lens, and lens version are reused from memory until
1821
+ * `maxCachedLensBytes` evicts them. Local changes are read with the Session's permissions; a change request's files
1822
+ * are those `changeRequestDiff` reads.
1823
+ * @param session - Session whose checkout or binding the view shows.
1824
+ * @param request - the view's source, the version of the files it shows, and the lens.
1825
+ * @param signal - ends the classification.
1826
+ * @returns progress frames, then one result frame.
1827
+ * @throws `forgePanel/lens-missing` while no lens holds the identity or once its registration ends;
1828
+ * `forgePanel/lens-stale` before a change request's status was read, when the files differ from `request.version`, and
1829
+ * when local files changed while the lens ran;
1830
+ * `forgePanel/lens-failed` when the lens fails, runs past `lensTimeoutMs`, reads past `maxLensReadBytes`, or answers
1831
+ * over `maxCachedLensBytes`; the errors of `localDiff` and `changeRequestDiff` for the files.
1832
+ */
1833
+ @Remote({ mode: 'stream' }) async *changesLens(session: Session, request: ForgeLensRequest, signal: AbortSignal): AsyncIterable<ForgeLensFrame>
1834
+
1835
+ /**
1836
+ * Read the signed-in user's viewed marks on the files of a change request of a Session, which the host keeps for the
1837
+ * whole change request.
1838
+ * @param session - Session the change request is bound to.
1839
+ * @param url - binding URL of the change request.
1840
+ * @param headSha - head commit of the shown diff; marks read at another head are refused.
1841
+ * @param signal - request cancellation.
1842
+ * @returns the marks at `headSha`, or null when the provider keeps no viewed marks.
1843
+ * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1844
+ * `forgePanel/stale-head` when the head moved past `headSha`, which is then polled again; `forgePanel/viewed-failed`
1845
+ * when the host does not provide the marks otherwise.
1846
+ */
1847
+ @Remote async changeRequestViewed(session: Session, url: string, headSha: string, signal: AbortSignal): Promise<ForgeViewedFiles | null>
1848
+
1849
+ /**
1850
+ * Set or clear the signed-in user's viewed mark on one file of a change request of a Session.
1851
+ * @param session - Session the change request is bound to.
1852
+ * @param url - binding URL of the change request.
1853
+ * @param request - the file, its mark, and the head commit of the shown diff.
1854
+ * @param signal - request cancellation.
1855
+ * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1856
+ * `forgePanel/stale-head` when the head moved past `request.headSha`; `forgePanel/action-failed` with the host's
1857
+ * reason when the host refuses, cannot be reached, or keeps no viewed marks; `forgePanel/action-unknown` when the host
1858
+ * did not confirm whether it saved the mark.
1859
+ */
1860
+ @Remote async setFileViewed(session: Session, url: string, request: ForgeViewedRequest, signal: AbortSignal): Promise<void>
1861
+
1862
+ /**
1863
+ * Read the review threads of a change request of a Session at the head commit of its displayed diff. Threads read
1864
+ * at the same head commit and update time are reused from memory, for any Session, until they are
1865
+ * `reviewRefreshMs` old, `maxCachedReviewBytes` evicts them, or a review write through this panel drops them; a read
1866
+ * again after that age, or a `fresh` read, shows threads resolved elsewhere since, which do not change a GitHub pull
1867
+ * request's update time.
1868
+ * @param session - Session the change request is bound to.
1869
+ * @param url - binding URL of the change request.
1870
+ * @param headSha - head commit of the displayed diff.
1871
+ * @param fresh - whether to read from the host even when threads at this head and update time are kept, as after the
1872
+ * reader refreshes the view; the result replaces the kept threads.
1873
+ * @param signal - request cancellation.
1874
+ * @returns the threads at `headSha`, with `current` positions numbering the files of that diff.
1875
+ * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1876
+ * `forgePanel/stale-head` when the latest status or the host names another head, which a host answer polls again;
1877
+ * `forgePanel/review-unsupported` when the provider cannot read reviews; `forgePanel/review-failed` before the
1878
+ * status was read and when the host does not provide the threads otherwise.
1879
+ */
1880
+ @Remote async changeRequestReview(session: Session, url: string, headSha: string, fresh: boolean, signal: AbortSignal): Promise<ForgeReviewData>
1881
+
1882
+ /**
1883
+ * Publish a line comment or completed review for a visible Session binding. The threads kept for the change request
1884
+ * are dropped once the host answers, so the next read shows the comments.
1885
+ * @param session - Session retaining the change request.
1886
+ * @param url - visible binding URL.
1887
+ * @param request - reviewed head, ranges, plain text, and explicit review decision.
1888
+ * @param signal - request cancellation.
1889
+ * @returns confirmed publication or a typed failure; unknown outcomes require reconciliation on the host.
1890
+ */
1891
+ @Remote async submitReview(session: Session, url: string, request: ForgeReviewRequest, signal: AbortSignal): Promise<ForgeReviewResult>
1892
+
1893
+ /**
1894
+ * Write to the review threads or the pending review of a change request of a Session while its head is still the
1895
+ * head the user saw. The threads kept for the change request are dropped once the host answers, so the next read
1896
+ * shows the write, which a resolve does not show through a GitHub pull request's update time.
1897
+ * @param session - Session the change request is bound to.
1898
+ * @param url - binding URL of the change request.
1899
+ * @param write - the write and the head commit of the threads the user saw.
1900
+ * @param signal - request cancellation.
1901
+ * @returns whether the host took the write; `stale` once the head moved, which polls the change request again, and
1902
+ * `unknown` when the outcome must be checked on the host before the write is tried again.
1903
+ * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves; an error
1904
+ * for a write without text, a head that is not a commit object name, or an invalid line range.
1905
+ */
1906
+ @Remote async changeRequestReviewWrite( session: Session, url: string, write: ForgeReviewWrite, signal: AbortSignal, ): Promise<ForgeReviewWriteResult>
1907
+
1908
+ /**
1909
+ * Mark a draft change request of a Session ready for review; the change
1910
+ * request is polled again once the host answers.
1911
+ * @param session - Session the change request is bound to.
1912
+ * @param url - binding URL of the change request.
1913
+ * @param signal - request cancellation.
1914
+ * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1915
+ * `forgePanel/action-failed` with the host's reason when the host refuses or cannot be reached.
1916
+ */
1917
+ @Remote async markReady(session: Session, url: string, signal: AbortSignal): Promise<void>
1918
+
1919
+ /**
1920
+ * Merge a change request of a Session while its head is still `headSha`;
1921
+ * the change request is polled again once the host answers.
1922
+ * @param session - Session the change request is bound to.
1923
+ * @param url - binding URL of the change request.
1924
+ * @param method - how the commits enter the base branch.
1925
+ * @param headSha - head commit the user confirmed.
1926
+ * @param signal - request cancellation.
1927
+ * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1928
+ * `forgePanel/stale-head` when the head moved past `headSha`; `forgePanel/action-failed` with the host's
1929
+ * reason when the host refuses otherwise or cannot be reached.
1930
+ */
1931
+ @Remote async merge(session: Session, url: string, method: MergeMethod, headSha: string, signal: AbortSignal): Promise<void>
1932
+
1933
+ /**
1934
+ * Rerun the failed checks of a change request of a Session while its head is still the commit whose checks the user
1935
+ * saw; the change request is polled again once the host answers.
1936
+ * @param session - Session the change request is bound to.
1937
+ * @param url - binding URL of the change request.
1938
+ * @param request - the head commit whose checks the user saw.
1939
+ * @param signal - request cancellation.
1940
+ * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1941
+ * `forgePanel/stale-head` when the head moved past `request.headSha`; `forgePanel/action-failed` with the host's
1942
+ * reason when the host refuses, cannot be reached, or reruns no checks; `forgePanel/action-unknown` when the host did
1943
+ * not confirm whether it started the rerun.
1944
+ */
1945
+ @Remote async rerunChecks(session: Session, url: string, request: ForgeRerunRequest, signal: AbortSignal): Promise<void>
1946
+
1947
+ /**
1948
+ * Request reviews of a change request of a Session from more reviewers; the change request is polled again once the
1949
+ * host answers.
1950
+ * @param session - Session the change request is bound to.
1951
+ * @param url - binding URL of the change request.
1952
+ * @param reviewers - the reviewers to ask, by kind and login.
1953
+ * @param signal - request cancellation.
1954
+ * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1955
+ * `forgePanel/action-failed` with the host's reason when the host refuses, cannot be reached, or requests no reviews;
1956
+ * `forgePanel/action-unknown` when the host did not confirm whether it requested the reviews.
1957
+ */
1958
+ @Remote async requestReviews( session: Session, url: string, reviewers: readonly Pick<ChangeRequestReviewer, 'kind' | 'login'>[], signal: AbortSignal, ): Promise<void>
1959
+
1960
+ /**
1961
+ * List accounts a review of a change request of a Session can be requested from.
1962
+ * @param session - Session the change request is bound to.
1963
+ * @param url - binding URL of the change request.
1964
+ * @param query - text the login or name contains; empty for the host's suggestions and first accounts.
1965
+ * @param signal - request cancellation.
1966
+ * @returns the accounts, the host's suggestions first.
1967
+ * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1968
+ * `forgePanel/reviewers-failed` when the host does not list them.
1969
+ */
1970
+ @Remote async reviewerCandidates(session: Session, url: string, query: string, signal: AbortSignal): Promise<readonly ForgeReviewerCandidate[]>
1971
+
1972
+ /**
1973
+ * Bring the base branch's new commits into the head branch of a change request of a Session while its head is still
1974
+ * `headSha`; the change request is polled again once the host answers.
1975
+ * @param session - Session the change request is bound to.
1976
+ * @param url - binding URL of the change request.
1977
+ * @param method - merge commit or rebase.
1978
+ * @param headSha - head commit the user confirmed.
1979
+ * @param signal - request cancellation.
1980
+ * @throws `forgePanel/not-bound` unless the URL is a visible binding of the Session that a provider serves;
1981
+ * `forgePanel/stale-head` when the head moved past `headSha`; `forgePanel/action-failed` with the host's reason when
1982
+ * the host refuses, cannot be reached, or updates no branches; `forgePanel/action-unknown` when the host did not
1983
+ * confirm whether it started the update.
1984
+ */
1985
+ @Remote async updateBranch(session: Session, url: string, method: BranchUpdateMethod, headSha: string, signal: AbortSignal): Promise<void>
1986
+
1987
+ /**
1988
+ * Ask for a new worktree before the Session's first turn, replacing an open
1989
+ * request and forgetting a failed attempt. While the next message waits in
1990
+ * the inbox, or else at the first step proposal, the panel creates a branch
1991
+ * from `from`, or from HEAD of the current checkout when it is null, and
1992
+ * enters its checkout. A message that already waits after a failed or
1993
+ * stopped attempt keeps waiting for {@link ForgePanelController.retryWorktree}.
1994
+ * @param session - Session without a started turn.
1995
+ * @param from - local base branch, or null for HEAD.
1996
+ * @param signal - request cancellation.
1997
+ * @throws `forgePanel/invalid-branch` when `from` is empty; `forgePanel/worktrees-unavailable`
1998
+ * without a worktree service; `forgePanel/session-started` after a turn started or the worktree was
1999
+ * created; `forgePanel/session-busy` while the worktree is being created.
2000
+ */
2001
+ @Remote async requestWorktree(session: Session, from: string | null, signal: AbortSignal): Promise<void>
2002
+
2003
+ /**
2004
+ * Withdraw the open worktree request of a Session with its failed attempt,
2005
+ * which lets a waiting message go on in the current checkout; without a
2006
+ * request the Session is unchanged. Withdrawing needs no worktree service.
2007
+ * @param session - Session without a started turn.
2008
+ * @param signal - request cancellation.
2009
+ * @throws `forgePanel/session-started` after a turn started or the worktree was created;
2010
+ * `forgePanel/session-busy` while the worktree is being created.
2011
+ */
2012
+ @Remote async cancelWorktree(session: Session, signal: AbortSignal): Promise<void>
2013
+
2014
+ /**
2015
+ * Stop the creation a message of the Session waits for; the message keeps
2016
+ * waiting with the request still open. Without such a creation the Session
2017
+ * is unchanged.
2018
+ * @param session - Session whose creation stops.
2019
+ * @param signal - request cancellation.
2020
+ */
2021
+ @Remote async stopCreation(session: Session, signal: AbortSignal): Promise<void>
2022
+
2023
+ /**
2024
+ * Make another attempt, with a new branch name, at the worktree a message
2025
+ * of the Session waits for after a failed or stopped attempt.
2026
+ * @param session - Session whose message waits.
2027
+ * @param signal - request cancellation.
2028
+ * @throws `forgePanel/not-waiting` when no message waits or an attempt is running.
2029
+ */
2030
+ @Remote async retryWorktree(session: Session, signal: AbortSignal): Promise<void>
2031
+
2032
+ /**
2033
+ * Check out the created worktree of a Session again at its recorded
2034
+ * directory when that directory is gone: its branch when the branch still
2035
+ * exists, otherwise a new branch of the same name at the base commit. Changes
2036
+ * that were never committed are not recovered. Runs as Agent maintenance,
2037
+ * inside the wait of a message that waits for the restoration, which then
2038
+ * goes on; without a missing directory the Session is unchanged.
2039
+ * @param session - Session whose worktree directory is gone.
2040
+ * @param signal - request cancellation, which aborts the restoration.
2041
+ * @throws `forgePanel/no-worktree` without a created worktree; `forgePanel/worktrees-unavailable`
2042
+ * without a worktree service; `forgePanel/session-busy` while the Session's Agent is not loaded or
2043
+ * has other work; `forgePanel/restore-failed` when Git fails.
2044
+ */
2045
+ @Remote async restoreWorktree(session: Session, signal: AbortSignal): Promise<void>
2046
+
2047
+ /**
2048
+ * Move a Session from its created worktree to the source checkout the
2049
+ * worktree came from; the Session no longer works in the worktree. Runs as
2050
+ * Agent maintenance, inside the wait of a message that waits for the removed
2051
+ * worktree, which then goes on.
2052
+ * @param session - Session with a created worktree.
2053
+ * @param signal - request cancellation.
2054
+ * @throws `forgePanel/no-worktree` without a created worktree; `forgePanel/session-busy` while the
2055
+ * Session's Agent is not loaded or has other work; the working-directory service's error when the
2056
+ * source checkout no longer exists.
2057
+ */
2058
+ @Remote async continueInProject(session: Session, signal: AbortSignal): Promise<void>
2059
+ ```
2060
+
2061
+ Types: [Session](session.md) · [WorktreeCleanup](worktrees.md)
2062
+
2063
+ Source: [`packages/experimental/client-ui-forge/src/index.ts`](../../packages/experimental/client-ui-forge/src/index.ts)
2064
+
2065
+ <a id="ctxforgesession--forgesessionservice"></a>
2066
+
2067
+ ### `ctx.forgeSession` — `ForgeSessionService`
2068
+
2069
+ Session-scoped change request bindings. The `forgeChangeRequests` projection derives them from `gh pr create`, `gh pr view`, `gh pr edit`, `gh pr checkout`, and `git push` results, including those of shell calls inside PTC programs, and from `plugin:forge-session/binding` records. After each push result, and when a caller reports the branch a Session works on, the service looks up open change requests whose head is that branch and records the unbound ones as discovered. Watching a Session polls its visible change requests through `ctx.forge`.
2070
+
2071
+ ```ts cordis-catalog
2072
+ /**
2073
+ * List the change requests bound to a Session and not dismissed.
2074
+ * @param session - Session whose bindings are read.
2075
+ * @returns visible bindings in first-recorded order, each with its latest polling result.
2076
+ */
2077
+ changeRequests(session: Session): SessionChangeRequest[]
2078
+
2079
+ /**
2080
+ * Bind a change request to a Session by URL, or restore a dismissed binding.
2081
+ * Appends a `track` record with the provider's canonical URL unless the
2082
+ * change request is already visible.
2083
+ * @param session - Session that receives the binding.
2084
+ * @param url - web URL of the change request.
2085
+ * @throws when no registered provider recognizes the URL.
2086
+ */
2087
+ track(session: Session, url: string): void
2088
+
2089
+ /**
2090
+ * Look up open change requests whose head is a branch the Session works on,
2091
+ * and record the unbound ones as discovered, as after a push of that branch.
2092
+ * A remote that no provider recognizes changes nothing; a failed lookup is
2093
+ * logged and changes nothing.
2094
+ * @param session - Session that receives the bindings.
2095
+ * @param remote - URL of the remote that holds the branch.
2096
+ * @param branch - branch name on that remote.
2097
+ * @returns resolves once the lookup has recorded its bindings or failed.
2098
+ */
2099
+ discoverBranch(session: Session, remote: string, branch: string): Promise<void>
2100
+
2101
+ /**
2102
+ * Hide a visible binding. Appends a `dismiss` record; a URL that is not
2103
+ * visibly bound leaves the log unchanged. Discovery never restores a
2104
+ * dismissed binding; `track` does.
2105
+ * @param session - Session that owns the binding.
2106
+ * @param url - web URL of the change request.
2107
+ */
2108
+ dismiss(session: Session, url: string): void
2109
+
2110
+ /**
2111
+ * Watch a Session's visible change requests as an effect on the calling
2112
+ * fiber. Every visible change request that a provider recognizes is polled
2113
+ * through `ctx.forge` while the Session has a watcher. Listeners receive the
2114
+ * complete list after each later binding or provider change, or polling result; read the
2115
+ * current list with {@link changeRequests}. Listeners must not throw; a
2116
+ * thrown error is logged and skipped.
2117
+ * @param session - Session to watch.
2118
+ * @param listener - receives the complete visible list.
2119
+ * @returns the disposer that stops this watch.
2120
+ */
2121
+ watch(session: Session, listener: SessionChangeRequestsListener): () => void
2122
+ ```
2123
+
2124
+ Types: [Session](session.md)
2125
+
2126
+ Source: [`packages/experimental/forge-session/src/index.ts`](../../packages/experimental/forge-session/src/index.ts)
2127
+
2128
+ <a id="forge-events"></a>
2129
+
2130
+ ### `forge/*` events
2131
+
2132
+ <a id="forgeproviders-changed--parallel"></a>
2133
+
2134
+ #### `forge/providers-changed` — parallel
2135
+
2136
+ Announces a provider registration or withdrawal after the registry changes. Listener failures are logged without preventing other listeners from running.
2137
+
2138
+ ```ts cordis-catalog
2139
+ /**
2140
+ * Announces a provider registration or withdrawal after the registry changes.
2141
+ * Listener failures are logged without preventing other listeners from running.
2142
+ * @param forge - family whose provider changed.
2143
+ * @mode parallel
2144
+ */
2145
+ 'forge/providers-changed'(forge: string): void | Promise<void>
2146
+ ```
2147
+
2148
+ Source: [`packages/experimental/forge/src/index.ts`](../../packages/experimental/forge/src/index.ts)
2149
+
2150
+ <a id="forgereview-changed--parallel"></a>
2151
+
2152
+ #### `forge/review-changed` — parallel
2153
+
2154
+ Announces a review change the host accepted through this service: a thread or pending-review write, a submitted review, or a viewed mark. Changes whose outcome is unknown, refused, stale, or unsupported are not announced. Listener failures are logged without preventing other listeners from running.
2155
+
2156
+ ```ts cordis-catalog
2157
+ /**
2158
+ * Announces a review change the host accepted through this service: a thread or pending-review write, a submitted
2159
+ * review, or a viewed mark. Changes whose outcome is unknown, refused, stale, or unsupported are not announced.
2160
+ * Listener failures are logged without preventing other listeners from running.
2161
+ * @param ref - change request the change went to.
2162
+ * @param change - what changed and the head commit it was made against.
2163
+ * @mode parallel
2164
+ */
2165
+ 'forge/review-changed'(ref: ChangeRequestRef, change: ForgeReviewChange): void | Promise<void>
2166
+ ```
2167
+
2168
+ Source: [`packages/experimental/forge/src/index.ts`](../../packages/experimental/forge/src/index.ts)
2169
+ <!-- END GENERATED cordis-surface -->