@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.
- package/README.md +72 -39
- package/dist/agent-config/account-identity.d.ts +2 -8
- package/dist/agent-config/account-identity.js +54 -10
- package/dist/agent-config/account-identity.js.map +1 -1
- package/dist/agent-config/catalog.d.ts +49 -7
- package/dist/agent-config/catalog.js +107 -0
- package/dist/agent-config/catalog.js.map +1 -1
- package/dist/agent-config/model-settings/pi.d.ts +12 -6
- package/dist/agent-config/model-settings/pi.js +12 -6
- package/dist/agent-config/model-settings/pi.js.map +1 -1
- package/dist/agent-config/seed.js +10 -0
- package/dist/agent-config/seed.js.map +1 -1
- package/dist/contract/github.d.ts +15 -0
- package/dist/contract/index.d.ts +9 -0
- package/dist/contract/index.js +1527 -895
- package/dist/contract/mcp-api-reference.d.ts +174 -0
- package/dist/contract/mcp-audit.d.ts +103 -0
- package/dist/contract/mcp-discovery.d.ts +257 -0
- package/dist/contract/mcp-event-catalog.d.ts +121 -0
- package/dist/contract/mcp-event-catalog.test.d.ts +1 -0
- package/dist/contract/mcp-idempotency.d.ts +200 -0
- package/dist/contract/mcp-journal.d.ts +268 -0
- package/dist/contract/mcp-leader.d.ts +88 -0
- package/dist/contract/mcp-ownership.d.ts +78 -0
- package/dist/contract/mcp-versioning.d.ts +139 -0
- package/dist/contract/runs.d.ts +81 -0
- package/dist/core/agent-profiles.d.ts +15 -4
- package/dist/core/agent-profiles.js +22 -8
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/opencode-server-runner.js +71 -11
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/secret-redaction.d.ts +7 -1
- package/dist/core/secret-redaction.js +44 -10
- package/dist/core/secret-redaction.js.map +1 -1
- package/dist/index.js +138 -27
- package/dist/index.js.map +1 -1
- package/dist/machine-identity.d.ts +66 -0
- package/dist/machine-identity.js +96 -0
- package/dist/machine-identity.js.map +1 -0
- package/dist/mcp/adapters/claude-code.d.ts +41 -0
- package/dist/mcp/adapters/claude-code.js +40 -0
- package/dist/mcp/adapters/claude-code.js.map +1 -0
- package/dist/mcp/adapters/codex.d.ts +140 -0
- package/dist/mcp/adapters/codex.js +323 -0
- package/dist/mcp/adapters/codex.js.map +1 -0
- package/dist/mcp/adapters/opencode.d.ts +148 -0
- package/dist/mcp/adapters/opencode.js +528 -0
- package/dist/mcp/adapters/opencode.js.map +1 -0
- package/dist/mcp/adapters/pi-link.d.ts +102 -0
- package/dist/mcp/adapters/pi-link.js +235 -0
- package/dist/mcp/adapters/pi-link.js.map +1 -0
- package/dist/mcp/adapters/pi.d.ts +188 -0
- package/dist/mcp/adapters/pi.js +418 -0
- package/dist/mcp/adapters/pi.js.map +1 -0
- package/dist/mcp/api-reference.d.ts +26 -0
- package/dist/mcp/api-reference.js +133 -0
- package/dist/mcp/api-reference.js.map +1 -0
- package/dist/mcp/audit-trail.d.ts +162 -0
- package/dist/mcp/audit-trail.js +306 -0
- package/dist/mcp/audit-trail.js.map +1 -0
- package/dist/mcp/bridge.d.ts +81 -0
- package/dist/mcp/bridge.js +407 -0
- package/dist/mcp/bridge.js.map +1 -0
- package/dist/mcp/connection-file.d.ts +49 -0
- package/dist/mcp/connection-file.js +63 -0
- package/dist/mcp/connection-file.js.map +1 -0
- package/dist/mcp/echo-guard.d.ts +94 -0
- package/dist/mcp/echo-guard.js +160 -0
- package/dist/mcp/echo-guard.js.map +1 -0
- package/dist/mcp/event-catalog.d.ts +120 -0
- package/dist/mcp/event-catalog.js +306 -0
- package/dist/mcp/event-catalog.js.map +1 -0
- package/dist/mcp/event-controller.d.ts +230 -0
- package/dist/mcp/event-controller.js +596 -0
- package/dist/mcp/event-controller.js.map +1 -0
- package/dist/mcp/event-journal.d.ts +57 -0
- package/dist/mcp/event-journal.js +423 -0
- package/dist/mcp/event-journal.js.map +1 -0
- package/dist/mcp/index.d.ts +63 -0
- package/dist/mcp/index.js +386 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/ipc.d.ts +202 -0
- package/dist/mcp/ipc.js +206 -0
- package/dist/mcp/ipc.js.map +1 -0
- package/dist/mcp/leader-delivery.d.ts +119 -0
- package/dist/mcp/leader-delivery.js +450 -0
- package/dist/mcp/leader-delivery.js.map +1 -0
- package/dist/mcp/operation-receipts.d.ts +210 -0
- package/dist/mcp/operation-receipts.js +723 -0
- package/dist/mcp/operation-receipts.js.map +1 -0
- package/dist/mcp/project-catalogs.d.ts +23 -0
- package/dist/mcp/project-catalogs.js +25 -0
- package/dist/mcp/project-catalogs.js.map +1 -0
- package/dist/mcp/project-leaders.d.ts +26 -0
- package/dist/mcp/project-leaders.js +17 -0
- package/dist/mcp/project-leaders.js.map +1 -0
- package/dist/mcp/protocol.d.ts +50 -0
- package/dist/mcp/protocol.js +47 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/reconnect.d.ts +297 -0
- package/dist/mcp/reconnect.js +465 -0
- package/dist/mcp/reconnect.js.map +1 -0
- package/dist/mcp/resource-ownership.d.ts +228 -0
- package/dist/mcp/resource-ownership.js +390 -0
- package/dist/mcp/resource-ownership.js.map +1 -0
- package/dist/mcp/service-adapter.d.ts +16488 -0
- package/dist/mcp/service-adapter.js +148 -0
- package/dist/mcp/service-adapter.js.map +1 -0
- package/dist/mcp/service.d.ts +83 -0
- package/dist/mcp/service.js +233 -0
- package/dist/mcp/service.js.map +1 -0
- package/dist/mcp/session-binding.d.ts +105 -0
- package/dist/mcp/session-binding.js +163 -0
- package/dist/mcp/session-binding.js.map +1 -0
- package/dist/mcp/stale-write.d.ts +145 -0
- package/dist/mcp/stale-write.js +219 -0
- package/dist/mcp/stale-write.js.map +1 -0
- package/dist/mcp/tool.d.ts +48 -0
- package/dist/mcp/tool.js +24 -0
- package/dist/mcp/tool.js.map +1 -0
- package/dist/mcp/tools/discovery.d.ts +40 -0
- package/dist/mcp/tools/discovery.js +232 -0
- package/dist/mcp/tools/discovery.js.map +1 -0
- package/dist/mcp/tools/execution-control.d.ts +171 -0
- package/dist/mcp/tools/execution-control.js +513 -0
- package/dist/mcp/tools/execution-control.js.map +1 -0
- package/dist/mcp/tools/handoff-git.d.ts +26 -0
- package/dist/mcp/tools/handoff-git.js +544 -0
- package/dist/mcp/tools/handoff-git.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +2 -0
- package/dist/mcp/tools/index.js +23 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/leader-events.d.ts +46 -0
- package/dist/mcp/tools/leader-events.js +126 -0
- package/dist/mcp/tools/leader-events.js.map +1 -0
- package/dist/mcp/tools/local-handoff.d.ts +102 -0
- package/dist/mcp/tools/local-handoff.js +306 -0
- package/dist/mcp/tools/local-handoff.js.map +1 -0
- package/dist/mcp/tools/project-config.d.ts +340 -0
- package/dist/mcp/tools/project-config.js +1075 -0
- package/dist/mcp/tools/project-config.js.map +1 -0
- package/dist/mcp/tools/results-evidence.d.ts +112 -0
- package/dist/mcp/tools/results-evidence.js +1045 -0
- package/dist/mcp/tools/results-evidence.js.map +1 -0
- package/dist/mcp/tools/task-create.d.ts +70 -0
- package/dist/mcp/tools/task-create.js +473 -0
- package/dist/mcp/tools/task-create.js.map +1 -0
- package/dist/mcp/tools/task-reads.d.ts +102 -0
- package/dist/mcp/tools/task-reads.js +689 -0
- package/dist/mcp/tools/task-reads.js.map +1 -0
- package/dist/mcp/tools/work-organisation.d.ts +2 -0
- package/dist/mcp/tools/work-organisation.js +502 -0
- package/dist/mcp/tools/work-organisation.js.map +1 -0
- package/dist/paths.d.ts +17 -7
- package/dist/paths.js +18 -8
- package/dist/paths.js.map +1 -1
- package/dist/runs/project-writer.js +76 -11
- package/dist/runs/project-writer.js.map +1 -1
- package/dist/runs/retention.d.ts +25 -2
- package/dist/runs/retention.js +63 -2
- package/dist/runs/retention.js.map +1 -1
- package/dist/server/forge/github.js +44 -0
- package/dist/server/forge/github.js.map +1 -1
- package/dist/server/forge/types.d.ts +16 -0
- package/dist/server/project-context.d.ts +81 -8
- package/dist/server/project-context.js +143 -35
- package/dist/server/project-context.js.map +1 -1
- package/dist/server/server.d.ts +1050 -53
- package/dist/server/server.js +326 -120
- package/dist/server/server.js.map +1 -1
- package/dist/server/validators.d.ts +26 -6
- package/dist/server/validators.js +17 -0
- package/dist/server/validators.js.map +1 -1
- package/dist/skills-remote.js +64 -6
- package/dist/skills-remote.js.map +1 -1
- package/dist/workflows/run.d.ts +277 -12
- package/dist/workflows/run.js +599 -79
- package/dist/workflows/run.js.map +1 -1
- package/dist/workspace/agent-profiles.d.ts +30 -3
- package/dist/workspace/agent-profiles.js +39 -4
- package/dist/workspace/agent-profiles.js.map +1 -1
- package/dist/workspace/config.js +25 -4
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/project-owner.d.ts +210 -0
- package/dist/workspace/project-owner.js +518 -0
- package/dist/workspace/project-owner.js.map +1 -0
- package/package.json +5 -3
- package/scripts/pi-leader-extension.ts +420 -0
- package/scripts/sync-readme.mjs +83 -2
- package/web/dist/assets/{alert-dialog-BVsVcbFy.js → alert-dialog-D6rP6d39.js} +1 -1
- package/web/dist/assets/arrow-down-DMOfFIzW.js +1 -0
- package/web/dist/assets/arrow-left-C4Ec2BUg.js +1 -0
- package/web/dist/assets/{centered-state-Djgly6Ni.js → centered-state-B0ZqrPaa.js} +12 -12
- package/web/dist/assets/chevron-right-mHJ-crvn.js +1 -0
- package/web/dist/assets/{collapsible-DZ4D-DHG.js → collapsible-DchEHgUM.js} +1 -1
- package/web/dist/assets/{commit-list-B-z1JL9G.js → commit-list-BpMim1Rv.js} +1 -1
- package/web/dist/assets/{compare-variants-CdS8r8E2.js → compare-variants-D5ItQbAq.js} +1 -1
- package/web/dist/assets/{diff-BKew9rQX.js → diff-HqqZ_WDR.js} +2 -2
- package/web/dist/assets/{diff-stat-D0mc5V6Z.js → diff-stat-NsU-3ecr.js} +1 -1
- package/web/dist/assets/{diff-view-xpIcuLIC.js → diff-view-B7c11Xux.js} +1 -1
- package/web/dist/assets/{dropdown-menu-CEQziH7X.js → dropdown-menu-CtTpnyod.js} +1 -1
- package/web/dist/assets/{editable-title-BCX3T2BY.js → editable-title-BXtuGQry.js} +1 -1
- package/web/dist/assets/{ellipsis-vertical-ByVdDc4z.js → ellipsis-vertical-MHtGZSH6.js} +1 -1
- package/web/dist/assets/{file-xBSB5hXO.js → file-DKLw5SOb.js} +1 -1
- package/web/dist/assets/{folder-CldgbZ5A.js → folder-Drw6-ufH.js} +1 -1
- package/web/dist/assets/{git-toolbar-BYlnf4qf.js → git-toolbar-BQH8bORV.js} +1 -1
- package/web/dist/assets/github-ynnpKFCJ.js +1 -0
- package/web/dist/assets/{image-preview-Cv5mZr0n.js → image-preview-DLhMN_R9.js} +1 -1
- package/web/dist/assets/index-BEsrNdXt.js +29 -0
- package/web/dist/assets/index-Da8DOBau.css +2 -0
- package/web/dist/assets/{markdown-CgEEE2ND.js → markdown-q365eN6Y.js} +1 -1
- package/web/dist/assets/{new-task-form-1r2bgsdD.js → new-task-form-CW-hNIWQ.js} +1 -1
- package/web/dist/assets/{pill-q-x22_ej.js → pill-B_TFulXa.js} +1 -1
- package/web/dist/assets/{project-router-VDN4jQM8.js → project-router-CLPc-ImZ.js} +1 -1
- package/web/dist/assets/{prompt-templates-BmZtCe-L.js → prompt-templates-CLc4RxyO.js} +1 -1
- package/web/dist/assets/{repo-git-C2On8SlI.js → repo-git-Is7R6-Tj.js} +1 -1
- package/web/dist/assets/{run-diff-vguEMJzh.js → run-diff-DHMvDlVo.js} +2 -2
- package/web/dist/assets/run-header-DvZJbQ8I.js +1 -0
- package/web/dist/assets/{search-x-QmhaAbg5.js → search-x-Ce2MMetu.js} +1 -1
- package/web/dist/assets/{skill-empty-hint-BrzUwByZ.js → skill-empty-hint-C_xCSvzB.js} +1 -1
- package/web/dist/assets/skills-CivqD4si.js +1 -0
- package/web/dist/assets/{sparkles-CP8QDOgX.js → sparkles-BCqPoj5g.js} +1 -1
- package/web/dist/assets/{square-terminal-CGypshSD.js → square-terminal-ikj5g-oK.js} +1 -1
- package/web/dist/assets/{tab-link-D4lFzzI1.js → tab-link-VsEjFV1Y.js} +1 -1
- package/web/dist/assets/{task-changes-6aopqAmV.js → task-changes-CH-E81-_.js} +1 -1
- package/web/dist/assets/{task-commits-u-dwaCyw.js → task-commits-DqfkMFxp.js} +1 -1
- package/web/dist/assets/{task-files-IMOZnIUK.js → task-files-D_nkHZ_a.js} +2 -2
- package/web/dist/assets/task-thread-bxQompxJ.js +9 -0
- package/web/dist/assets/{textarea-CyBCyKIk.js → textarea-xajuW1L_.js} +1 -1
- package/web/dist/assets/{thread-loading-Cuq2jchC.js → thread-loading-C3JQZbIE.js} +1 -1
- package/web/dist/assets/{trash-2-B6Dp3ZNs.js → trash-2-hJrBhpUD.js} +1 -1
- package/web/dist/assets/{triangle-alert-xBya8FUp.js → triangle-alert-D1u82y1a.js} +1 -1
- package/web/dist/assets/{upload-D6MXEB9p.js → upload-Bn6FBZ59.js} +1 -1
- package/web/dist/assets/{use-desktop-fLDKEJoB.js → use-desktop-DOIMfHfY.js} +1 -1
- package/web/dist/assets/{use-submit-shortcut-JTpNzU-u.js → use-submit-shortcut-Dj4DHTdE.js} +1 -1
- package/web/dist/assets/utils-YwBpOoRN.js +64 -0
- package/web/dist/assets/{workflows-BY3KTkFw.js → workflows-CTaAKz2m.js} +3 -3
- package/web/dist/assets/{zoomable-image-GTYwLtkS.js → zoomable-image-C1-6P3tg.js} +1 -1
- package/web/dist/index.html +23 -23
- package/web/dist/assets/arrow-down-CYgAlZtW.js +0 -1
- package/web/dist/assets/arrow-left-BOqE0a3k.js +0 -1
- package/web/dist/assets/chevron-right-Cc9BWnbN.js +0 -1
- package/web/dist/assets/github-CqGK62Lw.js +0 -1
- package/web/dist/assets/index-3nxVmdn5.css +0 -2
- package/web/dist/assets/index-qX7n11FX.js +0 -9
- package/web/dist/assets/refresh-cw-DcNCj2nu.js +0 -1
- package/web/dist/assets/run-header-DM7MiKj9.js +0 -1
- package/web/dist/assets/skills-Bj-uG_Hz.js +0 -1
- package/web/dist/assets/task-thread-CbMPmrWq.js +0 -9
- 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
|
+
}
|