@qodeca/xezar 0.13.0 → 0.14.0

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 (250) hide show
  1. package/README.md +72 -39
  2. package/dist/agent-config/account-identity.d.ts +2 -8
  3. package/dist/agent-config/account-identity.js +54 -10
  4. package/dist/agent-config/account-identity.js.map +1 -1
  5. package/dist/agent-config/catalog.d.ts +49 -7
  6. package/dist/agent-config/catalog.js +107 -0
  7. package/dist/agent-config/catalog.js.map +1 -1
  8. package/dist/agent-config/model-settings/pi.d.ts +12 -6
  9. package/dist/agent-config/model-settings/pi.js +12 -6
  10. package/dist/agent-config/model-settings/pi.js.map +1 -1
  11. package/dist/agent-config/seed.js +10 -0
  12. package/dist/agent-config/seed.js.map +1 -1
  13. package/dist/contract/github.d.ts +15 -0
  14. package/dist/contract/index.d.ts +9 -0
  15. package/dist/contract/index.js +1527 -895
  16. package/dist/contract/mcp-api-reference.d.ts +174 -0
  17. package/dist/contract/mcp-audit.d.ts +103 -0
  18. package/dist/contract/mcp-discovery.d.ts +257 -0
  19. package/dist/contract/mcp-event-catalog.d.ts +121 -0
  20. package/dist/contract/mcp-event-catalog.test.d.ts +1 -0
  21. package/dist/contract/mcp-idempotency.d.ts +200 -0
  22. package/dist/contract/mcp-journal.d.ts +268 -0
  23. package/dist/contract/mcp-leader.d.ts +88 -0
  24. package/dist/contract/mcp-ownership.d.ts +78 -0
  25. package/dist/contract/mcp-versioning.d.ts +139 -0
  26. package/dist/contract/runs.d.ts +81 -0
  27. package/dist/core/agent-profiles.d.ts +15 -4
  28. package/dist/core/agent-profiles.js +22 -8
  29. package/dist/core/agent-profiles.js.map +1 -1
  30. package/dist/core/opencode-server-runner.js +71 -11
  31. package/dist/core/opencode-server-runner.js.map +1 -1
  32. package/dist/core/secret-redaction.d.ts +7 -1
  33. package/dist/core/secret-redaction.js +44 -10
  34. package/dist/core/secret-redaction.js.map +1 -1
  35. package/dist/index.js +138 -27
  36. package/dist/index.js.map +1 -1
  37. package/dist/machine-identity.d.ts +66 -0
  38. package/dist/machine-identity.js +96 -0
  39. package/dist/machine-identity.js.map +1 -0
  40. package/dist/mcp/adapters/claude-code.d.ts +41 -0
  41. package/dist/mcp/adapters/claude-code.js +40 -0
  42. package/dist/mcp/adapters/claude-code.js.map +1 -0
  43. package/dist/mcp/adapters/codex.d.ts +140 -0
  44. package/dist/mcp/adapters/codex.js +323 -0
  45. package/dist/mcp/adapters/codex.js.map +1 -0
  46. package/dist/mcp/adapters/opencode.d.ts +148 -0
  47. package/dist/mcp/adapters/opencode.js +528 -0
  48. package/dist/mcp/adapters/opencode.js.map +1 -0
  49. package/dist/mcp/adapters/pi-link.d.ts +102 -0
  50. package/dist/mcp/adapters/pi-link.js +235 -0
  51. package/dist/mcp/adapters/pi-link.js.map +1 -0
  52. package/dist/mcp/adapters/pi.d.ts +188 -0
  53. package/dist/mcp/adapters/pi.js +418 -0
  54. package/dist/mcp/adapters/pi.js.map +1 -0
  55. package/dist/mcp/api-reference.d.ts +26 -0
  56. package/dist/mcp/api-reference.js +133 -0
  57. package/dist/mcp/api-reference.js.map +1 -0
  58. package/dist/mcp/audit-trail.d.ts +162 -0
  59. package/dist/mcp/audit-trail.js +306 -0
  60. package/dist/mcp/audit-trail.js.map +1 -0
  61. package/dist/mcp/bridge.d.ts +81 -0
  62. package/dist/mcp/bridge.js +407 -0
  63. package/dist/mcp/bridge.js.map +1 -0
  64. package/dist/mcp/connection-file.d.ts +49 -0
  65. package/dist/mcp/connection-file.js +63 -0
  66. package/dist/mcp/connection-file.js.map +1 -0
  67. package/dist/mcp/echo-guard.d.ts +94 -0
  68. package/dist/mcp/echo-guard.js +160 -0
  69. package/dist/mcp/echo-guard.js.map +1 -0
  70. package/dist/mcp/event-catalog.d.ts +120 -0
  71. package/dist/mcp/event-catalog.js +306 -0
  72. package/dist/mcp/event-catalog.js.map +1 -0
  73. package/dist/mcp/event-controller.d.ts +230 -0
  74. package/dist/mcp/event-controller.js +596 -0
  75. package/dist/mcp/event-controller.js.map +1 -0
  76. package/dist/mcp/event-journal.d.ts +57 -0
  77. package/dist/mcp/event-journal.js +423 -0
  78. package/dist/mcp/event-journal.js.map +1 -0
  79. package/dist/mcp/index.d.ts +63 -0
  80. package/dist/mcp/index.js +386 -0
  81. package/dist/mcp/index.js.map +1 -0
  82. package/dist/mcp/ipc.d.ts +202 -0
  83. package/dist/mcp/ipc.js +206 -0
  84. package/dist/mcp/ipc.js.map +1 -0
  85. package/dist/mcp/leader-delivery.d.ts +119 -0
  86. package/dist/mcp/leader-delivery.js +450 -0
  87. package/dist/mcp/leader-delivery.js.map +1 -0
  88. package/dist/mcp/operation-receipts.d.ts +210 -0
  89. package/dist/mcp/operation-receipts.js +723 -0
  90. package/dist/mcp/operation-receipts.js.map +1 -0
  91. package/dist/mcp/project-catalogs.d.ts +23 -0
  92. package/dist/mcp/project-catalogs.js +25 -0
  93. package/dist/mcp/project-catalogs.js.map +1 -0
  94. package/dist/mcp/project-leaders.d.ts +26 -0
  95. package/dist/mcp/project-leaders.js +17 -0
  96. package/dist/mcp/project-leaders.js.map +1 -0
  97. package/dist/mcp/protocol.d.ts +50 -0
  98. package/dist/mcp/protocol.js +47 -0
  99. package/dist/mcp/protocol.js.map +1 -0
  100. package/dist/mcp/reconnect.d.ts +297 -0
  101. package/dist/mcp/reconnect.js +465 -0
  102. package/dist/mcp/reconnect.js.map +1 -0
  103. package/dist/mcp/resource-ownership.d.ts +228 -0
  104. package/dist/mcp/resource-ownership.js +390 -0
  105. package/dist/mcp/resource-ownership.js.map +1 -0
  106. package/dist/mcp/service-adapter.d.ts +16488 -0
  107. package/dist/mcp/service-adapter.js +148 -0
  108. package/dist/mcp/service-adapter.js.map +1 -0
  109. package/dist/mcp/service.d.ts +83 -0
  110. package/dist/mcp/service.js +233 -0
  111. package/dist/mcp/service.js.map +1 -0
  112. package/dist/mcp/session-binding.d.ts +105 -0
  113. package/dist/mcp/session-binding.js +163 -0
  114. package/dist/mcp/session-binding.js.map +1 -0
  115. package/dist/mcp/stale-write.d.ts +145 -0
  116. package/dist/mcp/stale-write.js +219 -0
  117. package/dist/mcp/stale-write.js.map +1 -0
  118. package/dist/mcp/tool.d.ts +48 -0
  119. package/dist/mcp/tool.js +24 -0
  120. package/dist/mcp/tool.js.map +1 -0
  121. package/dist/mcp/tools/discovery.d.ts +40 -0
  122. package/dist/mcp/tools/discovery.js +232 -0
  123. package/dist/mcp/tools/discovery.js.map +1 -0
  124. package/dist/mcp/tools/execution-control.d.ts +171 -0
  125. package/dist/mcp/tools/execution-control.js +513 -0
  126. package/dist/mcp/tools/execution-control.js.map +1 -0
  127. package/dist/mcp/tools/handoff-git.d.ts +26 -0
  128. package/dist/mcp/tools/handoff-git.js +544 -0
  129. package/dist/mcp/tools/handoff-git.js.map +1 -0
  130. package/dist/mcp/tools/index.d.ts +2 -0
  131. package/dist/mcp/tools/index.js +23 -0
  132. package/dist/mcp/tools/index.js.map +1 -0
  133. package/dist/mcp/tools/leader-events.d.ts +46 -0
  134. package/dist/mcp/tools/leader-events.js +126 -0
  135. package/dist/mcp/tools/leader-events.js.map +1 -0
  136. package/dist/mcp/tools/local-handoff.d.ts +102 -0
  137. package/dist/mcp/tools/local-handoff.js +306 -0
  138. package/dist/mcp/tools/local-handoff.js.map +1 -0
  139. package/dist/mcp/tools/project-config.d.ts +340 -0
  140. package/dist/mcp/tools/project-config.js +1075 -0
  141. package/dist/mcp/tools/project-config.js.map +1 -0
  142. package/dist/mcp/tools/results-evidence.d.ts +112 -0
  143. package/dist/mcp/tools/results-evidence.js +1045 -0
  144. package/dist/mcp/tools/results-evidence.js.map +1 -0
  145. package/dist/mcp/tools/task-create.d.ts +70 -0
  146. package/dist/mcp/tools/task-create.js +473 -0
  147. package/dist/mcp/tools/task-create.js.map +1 -0
  148. package/dist/mcp/tools/task-reads.d.ts +102 -0
  149. package/dist/mcp/tools/task-reads.js +689 -0
  150. package/dist/mcp/tools/task-reads.js.map +1 -0
  151. package/dist/mcp/tools/work-organisation.d.ts +2 -0
  152. package/dist/mcp/tools/work-organisation.js +502 -0
  153. package/dist/mcp/tools/work-organisation.js.map +1 -0
  154. package/dist/paths.d.ts +17 -7
  155. package/dist/paths.js +18 -8
  156. package/dist/paths.js.map +1 -1
  157. package/dist/runs/project-writer.js +76 -11
  158. package/dist/runs/project-writer.js.map +1 -1
  159. package/dist/runs/retention.d.ts +25 -2
  160. package/dist/runs/retention.js +63 -2
  161. package/dist/runs/retention.js.map +1 -1
  162. package/dist/server/forge/github.js +44 -0
  163. package/dist/server/forge/github.js.map +1 -1
  164. package/dist/server/forge/types.d.ts +16 -0
  165. package/dist/server/project-context.d.ts +81 -8
  166. package/dist/server/project-context.js +143 -35
  167. package/dist/server/project-context.js.map +1 -1
  168. package/dist/server/server.d.ts +1050 -53
  169. package/dist/server/server.js +326 -120
  170. package/dist/server/server.js.map +1 -1
  171. package/dist/server/validators.d.ts +26 -6
  172. package/dist/server/validators.js +17 -0
  173. package/dist/server/validators.js.map +1 -1
  174. package/dist/skills-remote.js +64 -6
  175. package/dist/skills-remote.js.map +1 -1
  176. package/dist/workflows/run.d.ts +277 -12
  177. package/dist/workflows/run.js +599 -79
  178. package/dist/workflows/run.js.map +1 -1
  179. package/dist/workspace/agent-profiles.d.ts +30 -3
  180. package/dist/workspace/agent-profiles.js +39 -4
  181. package/dist/workspace/agent-profiles.js.map +1 -1
  182. package/dist/workspace/config.js +25 -4
  183. package/dist/workspace/config.js.map +1 -1
  184. package/dist/workspace/project-owner.d.ts +210 -0
  185. package/dist/workspace/project-owner.js +518 -0
  186. package/dist/workspace/project-owner.js.map +1 -0
  187. package/package.json +5 -3
  188. package/scripts/pi-leader-extension.ts +420 -0
  189. package/scripts/sync-readme.mjs +83 -2
  190. package/web/dist/assets/{alert-dialog-BVsVcbFy.js → alert-dialog-D6rP6d39.js} +1 -1
  191. package/web/dist/assets/arrow-down-DMOfFIzW.js +1 -0
  192. package/web/dist/assets/arrow-left-C4Ec2BUg.js +1 -0
  193. package/web/dist/assets/{centered-state-Djgly6Ni.js → centered-state-B0ZqrPaa.js} +12 -12
  194. package/web/dist/assets/chevron-right-mHJ-crvn.js +1 -0
  195. package/web/dist/assets/{collapsible-DZ4D-DHG.js → collapsible-DchEHgUM.js} +1 -1
  196. package/web/dist/assets/{commit-list-B-z1JL9G.js → commit-list-BpMim1Rv.js} +1 -1
  197. package/web/dist/assets/{compare-variants-CdS8r8E2.js → compare-variants-D5ItQbAq.js} +1 -1
  198. package/web/dist/assets/{diff-BKew9rQX.js → diff-HqqZ_WDR.js} +2 -2
  199. package/web/dist/assets/{diff-stat-D0mc5V6Z.js → diff-stat-NsU-3ecr.js} +1 -1
  200. package/web/dist/assets/{diff-view-xpIcuLIC.js → diff-view-B7c11Xux.js} +1 -1
  201. package/web/dist/assets/{dropdown-menu-CEQziH7X.js → dropdown-menu-CtTpnyod.js} +1 -1
  202. package/web/dist/assets/{editable-title-BCX3T2BY.js → editable-title-BXtuGQry.js} +1 -1
  203. package/web/dist/assets/{ellipsis-vertical-ByVdDc4z.js → ellipsis-vertical-MHtGZSH6.js} +1 -1
  204. package/web/dist/assets/{file-xBSB5hXO.js → file-DKLw5SOb.js} +1 -1
  205. package/web/dist/assets/{folder-CldgbZ5A.js → folder-Drw6-ufH.js} +1 -1
  206. package/web/dist/assets/{git-toolbar-BYlnf4qf.js → git-toolbar-BQH8bORV.js} +1 -1
  207. package/web/dist/assets/github-ynnpKFCJ.js +1 -0
  208. package/web/dist/assets/{image-preview-Cv5mZr0n.js → image-preview-DLhMN_R9.js} +1 -1
  209. package/web/dist/assets/index-BEsrNdXt.js +29 -0
  210. package/web/dist/assets/index-Da8DOBau.css +2 -0
  211. package/web/dist/assets/{markdown-CgEEE2ND.js → markdown-q365eN6Y.js} +1 -1
  212. package/web/dist/assets/{new-task-form-1r2bgsdD.js → new-task-form-CW-hNIWQ.js} +1 -1
  213. package/web/dist/assets/{pill-q-x22_ej.js → pill-B_TFulXa.js} +1 -1
  214. package/web/dist/assets/{project-router-VDN4jQM8.js → project-router-CLPc-ImZ.js} +1 -1
  215. package/web/dist/assets/{prompt-templates-BmZtCe-L.js → prompt-templates-CLc4RxyO.js} +1 -1
  216. package/web/dist/assets/{repo-git-C2On8SlI.js → repo-git-Is7R6-Tj.js} +1 -1
  217. package/web/dist/assets/{run-diff-vguEMJzh.js → run-diff-DHMvDlVo.js} +2 -2
  218. package/web/dist/assets/run-header-DvZJbQ8I.js +1 -0
  219. package/web/dist/assets/{search-x-QmhaAbg5.js → search-x-Ce2MMetu.js} +1 -1
  220. package/web/dist/assets/{skill-empty-hint-BrzUwByZ.js → skill-empty-hint-C_xCSvzB.js} +1 -1
  221. package/web/dist/assets/skills-CivqD4si.js +1 -0
  222. package/web/dist/assets/{sparkles-CP8QDOgX.js → sparkles-BCqPoj5g.js} +1 -1
  223. package/web/dist/assets/{square-terminal-CGypshSD.js → square-terminal-ikj5g-oK.js} +1 -1
  224. package/web/dist/assets/{tab-link-D4lFzzI1.js → tab-link-VsEjFV1Y.js} +1 -1
  225. package/web/dist/assets/{task-changes-6aopqAmV.js → task-changes-CH-E81-_.js} +1 -1
  226. package/web/dist/assets/{task-commits-u-dwaCyw.js → task-commits-DqfkMFxp.js} +1 -1
  227. package/web/dist/assets/{task-files-IMOZnIUK.js → task-files-D_nkHZ_a.js} +2 -2
  228. package/web/dist/assets/task-thread-bxQompxJ.js +9 -0
  229. package/web/dist/assets/{textarea-CyBCyKIk.js → textarea-xajuW1L_.js} +1 -1
  230. package/web/dist/assets/{thread-loading-Cuq2jchC.js → thread-loading-C3JQZbIE.js} +1 -1
  231. package/web/dist/assets/{trash-2-B6Dp3ZNs.js → trash-2-hJrBhpUD.js} +1 -1
  232. package/web/dist/assets/{triangle-alert-xBya8FUp.js → triangle-alert-D1u82y1a.js} +1 -1
  233. package/web/dist/assets/{upload-D6MXEB9p.js → upload-Bn6FBZ59.js} +1 -1
  234. package/web/dist/assets/{use-desktop-fLDKEJoB.js → use-desktop-DOIMfHfY.js} +1 -1
  235. package/web/dist/assets/{use-submit-shortcut-JTpNzU-u.js → use-submit-shortcut-Dj4DHTdE.js} +1 -1
  236. package/web/dist/assets/utils-YwBpOoRN.js +64 -0
  237. package/web/dist/assets/{workflows-BY3KTkFw.js → workflows-CTaAKz2m.js} +3 -3
  238. package/web/dist/assets/{zoomable-image-GTYwLtkS.js → zoomable-image-C1-6P3tg.js} +1 -1
  239. package/web/dist/index.html +23 -23
  240. package/web/dist/assets/arrow-down-CYgAlZtW.js +0 -1
  241. package/web/dist/assets/arrow-left-BOqE0a3k.js +0 -1
  242. package/web/dist/assets/chevron-right-Cc9BWnbN.js +0 -1
  243. package/web/dist/assets/github-CqGK62Lw.js +0 -1
  244. package/web/dist/assets/index-3nxVmdn5.css +0 -2
  245. package/web/dist/assets/index-qX7n11FX.js +0 -9
  246. package/web/dist/assets/refresh-cw-DcNCj2nu.js +0 -1
  247. package/web/dist/assets/run-header-DM7MiKj9.js +0 -1
  248. package/web/dist/assets/skills-Bj-uG_Hz.js +0 -1
  249. package/web/dist/assets/task-thread-CbMPmrWq.js +0 -9
  250. package/web/dist/assets/utils--z9hRtl3.js +0 -64
@@ -0,0 +1,230 @@
1
+ import type { McpJournalRow, McpProjectOccupiedError, McpSessionExpiredError } from '../contract/index.js';
2
+ import { type ProjectOwnership } from '../workspace/project-owner.ts';
3
+ import { type EventJournal } from './event-journal.ts';
4
+ /**
5
+ * The non-model event controller (#107): it follows ONE project's event journal (#103) on behalf
6
+ * of the logical MCP session that owns the project (#99), and hands significant events to a
7
+ * client-specific reaction adapter (#108–#110). It never starts a model turn of its own.
8
+ *
9
+ * WHAT IT CONSUMES, AND DOES NOT DECIDE. The event identity, order, replay and acknowledgement
10
+ * model are D-05 (`docs/features/mcp-server/mcp-d05-async-event-contract-decision.md` § 6); the
11
+ * numbers are D-09 (`mcp-d09-limits-retention-packaging-decision.md` § 3). Every constant below
12
+ * names the one it reuses.
13
+ *
14
+ * - **Source.** The project journal only (`EventJournal`), never the workspace stream — requirements
15
+ * § 8 forbids an unfiltered workspace stream reaching MCP, and the journal already holds exactly
16
+ * E-01–E-06 for exactly one project. The journal is also the QUEUE: undelivered rows are the
17
+ * journal between this controller's position and its head, so the queue is durable, gapless and
18
+ * bounded by B-19 retention, and no event lives only in this object's memory.
19
+ * - **Coalescing.** D-05 N3 decided *no coalescing window*: rows are never merged, delayed or
20
+ * dropped. What coalesces is DISPATCHES: at most one is in flight, and every row that arrived
21
+ * before it starts rides in it, up to one journal page (B-20, 100 rows; B-01, 40 000 bytes).
22
+ * So a burst of N rows appended in one tick reaches the adapter as `ceil(N / 100)` dispatches,
23
+ * each row intact and in `journalSeq` order, and rows that arrive while a dispatch is in flight
24
+ * go out together in the next one.
25
+ * WHOSE FACTS ARE WHOSE. This controller serves one SESSION, and its cursors belong to the project's
26
+ * journal. Whether the leader is reachable is a fact about the LEADER, so it is not kept here: the
27
+ * adapter records it (`LeaderDelivery`), and it therefore survives a session change and is never
28
+ * inherited by a leader that has just been attached (QA on #311, round five, both directions).
29
+ *
30
+ * - **Delivery is not reaction (F-20).** `deliver` hands rows to the client application and is
31
+ * non-model. Whether a model turn then starts is the adapter's own, client-specific decision, and
32
+ * it reports that separately through `recordReaction`. The three D-05 § 6.6 cursors are kept
33
+ * apart: `deliveredSeq` (this controller, on a resolved `deliver`), `ackedSeq` (the leader,
34
+ * through an explicit tool call — `ack`) and `reactedSeq` (the adapter, when a turn carrying the
35
+ * rows really started). `reactedSeq` lagging `deliveredSeq` is a normal state, not a fault.
36
+ * - **Non-model traffic only (N-06).** The heartbeat, a transport retry and an acknowledgement
37
+ * never call anything that can start a turn: the heartbeat calls the adapter's optional
38
+ * `heartbeat`, a retry re-sends the SAME undelivered rows, and `ack` touches only a number.
39
+ * Rows already delivered in this session are never dispatched again.
40
+ * - **At-least-once (D-05 § 6.6).** A new session resumes after the leader's last ACK, not after
41
+ * the last delivery: a row handed to a previous client that the leader never acknowledged is
42
+ * outstanding, and F-21 says reconnect delivers it. In production the leader acknowledges through
43
+ * the pull tool (`leader_events ack`), so that record is the ACK read here (`acknowledged`, #332):
44
+ * one source of truth, not two cursors that can disagree. The leader deduplicates on `eventId`. The
45
+ * echo guard (drop a `leader` row whose `causedBy` is the adapter's own operation) is the
46
+ * adapter's, per D-05 § 6.3: rows reach it whole.
47
+ *
48
+ * ONE LOGICAL OWNER. This controller and the MCP bridge are one client, not two (compatibility
49
+ * report, "Recommended architecture"; F-18). So the controller never acquires: it is handed the
50
+ * project's `ProjectOwnership` and the session key the transport acquired with, starts only while
51
+ * that session is the live owner, and re-checks it before every dispatch and at every heartbeat.
52
+ * It never calls `acquire` or `release` — the lease is the transport's, released on connection
53
+ * close (D-02.4) — so it can neither create a second claim nor end the session it serves. A
54
+ * second controller for the same project is refused: a competing session with project-occupied,
55
+ * and a second controller for the SAME session too, because two dispatchers over one journal are
56
+ * the "two independent streams delivering duplicates" risk of requirements § 12.
57
+ *
58
+ * INERT WITHOUT A CLIENT (N-07). With no adapter the controller subscribes to nothing, starts no
59
+ * timer, reads no ownership and writes no file. The journal keeps accumulating rows exactly as it
60
+ * does with no controller at all.
61
+ *
62
+ * ## Every state, and who fires each exit
63
+ *
64
+ * No exit below is a human action, and no state holds a queued row without an on-by-default path
65
+ * to the adapter.
66
+ *
67
+ * | State | Exit | Who fires it | If nobody fires it |
68
+ * | --- | --- | --- | --- |
69
+ * | inert | — (terminal for this object) | — | correct: no client, nothing is owed; rows stay in the journal |
70
+ * | idle | → dispatching | a journal append (subscription) or the heartbeat tick | the heartbeat tick (B-17, 30 s) re-reads the journal, so a missed wakeup costs at most one interval |
71
+ * | idle | → disconnected | a failed heartbeat probe | stays idle — the correct resting state |
72
+ * | dispatching | → idle | `deliver` resolving | the attempt is aborted after one heartbeat interval and counts as a transport drop |
73
+ * | dispatching | → recovering | `deliver` rejecting, or the attempt timing out | — (the timeout above always fires) |
74
+ * | recovering | → idle / → disconnected | the round itself: at most 5 attempts, full-jitter backoff ≤ 200 ms | — (bounded) |
75
+ * | disconnected | → dispatching | the heartbeat tick (one new bounded round per interval) or the adapter's `wake` on reconnect | the heartbeat timer is on by default, so rows are retried every 30 s for as long as the session owns the project |
76
+ * | any active state | → ended | `close` (the transport, on connection close), or losing ownership, found at the next dispatch or heartbeat | lease expiry (30 s, B-14) makes the owner check fail at the next heartbeat |
77
+ * | ended | — (terminal) | — | rows stay in the journal and the persisted cursors; the next session's controller resumes after the last ack |
78
+ *
79
+ * What reaches a terminal state BECAUSE of this controller: nothing but this object. It holds no
80
+ * run, queue slot, lease or worktree, and ending it touches none.
81
+ *
82
+ * ## Where its state lives
83
+ *
84
+ * `<dataDir>/mcp/event-controller.json`, beside the journal: `{ v, projectId, epoch, deliveredSeq,
85
+ * ackedSeq, reactedSeq, floorSeq }`, written by atomic tmp+rename after every change (D-05 N4:
86
+ * persisted per delivery, not batched). Written, never required. With a leader record (production)
87
+ * the file keeps only real history — what was pushed and what reacted — and WHERE pushing starts
88
+ * comes from the leader's record. Standalone, no file means a first session that starts at the
89
+ * journal head (nothing was ever owed to a leader that did not exist; it reads current state, F-21),
90
+ * and an unreadable file starts at the head WITH a stated gap. Either way no reported cursor is ever
91
+ * set to a position nothing reached (QA on #311): a start is a floor, not a delivery. An unwritable
92
+ * directory keeps the cursors in memory with one warning.
93
+ */
94
+ /** Heartbeat and liveness period. B-17 / D-05 N6, reusing the hub's `HEARTBEAT_MS` (`server/ws.ts`). */
95
+ export declare const EVENT_CONTROLLER_HEARTBEAT_MS = 30000;
96
+ /**
97
+ * Delivery attempts per recovery round, and the cap on the doubling full-jitter backoff between them.
98
+ * D-01, D-05 and D-09 fix no delivery-retry numbers (B-16 is the bridge's own "no retry" on a dead
99
+ * socket, which this is not), so the bounded-recovery shape is the one D-02 measured and D-09 adopted
100
+ * as B-15 for the same kind of local contention, rather than a new one.
101
+ */
102
+ export declare const EVENT_DELIVERY_ATTEMPTS = 5;
103
+ export declare const EVENT_DELIVERY_BACKOFF_CAP_MS = 200;
104
+ /** Stated to the adapter when rows this session was owed are gone (D-05 § 6.5, A-21). */
105
+ export interface EventRecovery {
106
+ readonly required: 'current-state';
107
+ readonly oldestSeq: number | null;
108
+ readonly latestSeq: number;
109
+ readonly message: string;
110
+ }
111
+ /** One dispatch: consecutive journal rows, oldest first, never merged, never rewritten. */
112
+ export interface EventDispatch {
113
+ readonly projectId: string;
114
+ readonly events: readonly McpJournalRow[];
115
+ /** Present when a gap was detected: read current state before acting on `events`. */
116
+ readonly recovery?: EventRecovery;
117
+ }
118
+ /**
119
+ * The client-specific half (#108 Claude Code, #109 Codex, #110 OpenCode). Nothing here can start a
120
+ * model turn on the controller's behalf: that is the adapter's decision, reported back through
121
+ * `EventController.recordReaction`.
122
+ */
123
+ export interface ReactionAdapter {
124
+ /**
125
+ * Hand `dispatch` to the client application — NON-MODEL. Resolve once the client's transport
126
+ * accepted it; reject, or honour `signal`, when it did not. Resolving is delivery, not reaction.
127
+ * Resolve with a `DeliveryReceipt` when not every row was really handed over (the leader's own
128
+ * echoes are not); resolving with nothing means all of them were.
129
+ */
130
+ deliver(dispatch: EventDispatch, signal: AbortSignal): Promise<void | DeliveryReceipt>;
131
+ /** Optional non-model liveness probe (N-06). A rejection marks the transport disconnected. */
132
+ heartbeat?(signal: AbortSignal): Promise<void>;
133
+ }
134
+ /**
135
+ * What an adapter says about a dispatch it settled: the newest row it REALLY handed to the client,
136
+ * or `null` for none (every row was one the leader caused itself). `deliveredSeq` counts only
137
+ * these, so it never claims a row reached the leader when it did not (QA on #311).
138
+ */
139
+ export interface DeliveryReceipt {
140
+ readonly handedThrough: number | null;
141
+ }
142
+ export type EventControllerState = 'inert' | 'idle' | 'dispatching' | 'recovering' | 'disconnected' | 'ended';
143
+ export interface EventControllerStatus {
144
+ state: EventControllerState;
145
+ deliveredSeq: number;
146
+ ackedSeq: number;
147
+ reactedSeq: number;
148
+ latestSeq: number;
149
+ }
150
+ /** What `ack` and `recordReaction` answer. `ahead` changes nothing; neither ever rewinds. */
151
+ export interface CursorAdvance {
152
+ status: 'advanced' | 'unchanged' | 'ahead' | 'inactive';
153
+ seq: number;
154
+ }
155
+ /** The leader's acknowledgement record as the controller reads it. `LeaderCursors` implements it. */
156
+ export interface LeaderRecord {
157
+ /**
158
+ * Rows after `seq` are owed to the leader: its last acknowledgement, or — for a leader with no
159
+ * acknowledgement yet — where its record began (#251: the journal head the first time xezar kept
160
+ * one). `sameEpoch` false: the record counts in a journal that has since been recreated.
161
+ */
162
+ owedAfter(): {
163
+ seq: number;
164
+ sameEpoch: boolean;
165
+ };
166
+ /** The last row the leader acknowledged with an explicit tool call; 0 when it never has. */
167
+ acknowledged(): number;
168
+ }
169
+ export type EventControllerStart = {
170
+ outcome: 'started' | 'inert';
171
+ controller: EventController;
172
+ } | {
173
+ outcome: 'refused';
174
+ error: McpProjectOccupiedError | McpSessionExpiredError;
175
+ };
176
+ export interface EventControllerOptions {
177
+ /** The bound project's journal — the one `EventJournal.open` returned for it. */
178
+ journal: Pick<EventJournal, 'projectId' | 'rowsPath' | 'epoch' | 'latestSeq' | 'oldestSeq' | 'headCursor' | 'read' | 'subscribe'>;
179
+ /** The bound project's owner slot. Read, never acquired or released, by this controller. */
180
+ ownership: Pick<ProjectOwnership, 'projectId' | 'sessionToken' | 'state'>;
181
+ /** The key the transport acquired the lease with — the logical session this controller serves. */
182
+ sessionKey: string;
183
+ /** The client's reaction adapter. Absent: no client is configured, and the controller is inert. */
184
+ adapter?: ReactionAdapter;
185
+ /**
186
+ * The leader's own record of what it has taken into account — in production the pull tool's
187
+ * `LeaderCursors` (#251), the ONE acknowledgement (#332). When given, the controller keeps no rival
188
+ * cursor: it pushes only rows after `owedAfter()`, read when a session starts AND before every
189
+ * dispatch, and reports `acknowledged()` as `ackedSeq`. Absent: the controller's own cursor file
190
+ * and `ack()` stand in (#107's standalone shape, and its tests).
191
+ */
192
+ leaderRecord?: LeaderRecord;
193
+ /** Test seams. Production uses the defaults. */
194
+ heartbeatMs?: number;
195
+ random?: () => number;
196
+ sleep?: (ms: number) => Promise<void>;
197
+ warn?: (message: string) => void;
198
+ }
199
+ export declare class EventController {
200
+ #private;
201
+ readonly projectId: string;
202
+ private constructor();
203
+ /**
204
+ * Start the controller for the session `sessionKey`, which must already own the project through
205
+ * the transport's own acquisition. Throws only on a caller bug (journal and owner slot of two
206
+ * different projects); every runtime condition is an answer.
207
+ */
208
+ static start(opts: EventControllerOptions): EventControllerStart;
209
+ get state(): EventControllerState;
210
+ status(): EventControllerStatus;
211
+ /**
212
+ * The leader's acknowledgement (D-05 § 6.6): monotonic and idempotent. At or below the current
213
+ * ack it is a successful no-op, never a rewind; past the journal head it changes nothing. It
214
+ * calls no adapter, so it can never cost a model turn.
215
+ */
216
+ ack(journalSeq: number): CursorAdvance;
217
+ /**
218
+ * The adapter's report that a model turn carrying rows up to `journalSeq` really started — the
219
+ * reaction half of F-20, recorded apart from delivery. Monotonic; never past a row the adapter was
220
+ * handed. A turn may start before `deliver` has resolved, so a row still in flight counts as handed.
221
+ */
222
+ recordReaction(journalSeq: number): CursorAdvance;
223
+ /** The adapter's transport came back: start a recovery round now instead of at the next tick. */
224
+ wake(): void;
225
+ /**
226
+ * Confirmed end of the session's connection — fired by the transport where it releases the lease.
227
+ * Ends this controller only: the lease, the journal and every run are untouched.
228
+ */
229
+ close(): void;
230
+ }