@qodeca/xezar 0.13.1 → 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 +45 -15
- 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 +24 -1
- package/dist/runs/retention.js +62 -1
- 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 +554 -59
- 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-D62m4j3B.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-DJANqg7Y.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-btG0sdbO.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-BtZId14z.js → task-changes-CH-E81-_.js} +1 -1
- package/web/dist/assets/{task-commits-BoeyJXrv.js → task-commits-DqfkMFxp.js} +1 -1
- package/web/dist/assets/{task-files-DIy2oUnj.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-CPBwzQ7M.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-DG115scU.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-D6vK0jFn.js +0 -1
- package/web/dist/assets/index-CkmR6h7s.css +0 -2
- package/web/dist/assets/index-DVDqF4bM.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-DMfxQtrD.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,596 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { OWNER_ACQUIRE_ATTEMPTS, OWNER_BACKOFF_CAP_MS, projectOccupiedError, sessionExpiredError, } from '../workspace/project-owner.js';
|
|
5
|
+
import { McpJournalCursorError } from './event-journal.js';
|
|
6
|
+
/**
|
|
7
|
+
* The non-model event controller (#107): it follows ONE project's event journal (#103) on behalf
|
|
8
|
+
* of the logical MCP session that owns the project (#99), and hands significant events to a
|
|
9
|
+
* client-specific reaction adapter (#108–#110). It never starts a model turn of its own.
|
|
10
|
+
*
|
|
11
|
+
* WHAT IT CONSUMES, AND DOES NOT DECIDE. The event identity, order, replay and acknowledgement
|
|
12
|
+
* model are D-05 (`docs/features/mcp-server/mcp-d05-async-event-contract-decision.md` § 6); the
|
|
13
|
+
* numbers are D-09 (`mcp-d09-limits-retention-packaging-decision.md` § 3). Every constant below
|
|
14
|
+
* names the one it reuses.
|
|
15
|
+
*
|
|
16
|
+
* - **Source.** The project journal only (`EventJournal`), never the workspace stream — requirements
|
|
17
|
+
* § 8 forbids an unfiltered workspace stream reaching MCP, and the journal already holds exactly
|
|
18
|
+
* E-01–E-06 for exactly one project. The journal is also the QUEUE: undelivered rows are the
|
|
19
|
+
* journal between this controller's position and its head, so the queue is durable, gapless and
|
|
20
|
+
* bounded by B-19 retention, and no event lives only in this object's memory.
|
|
21
|
+
* - **Coalescing.** D-05 N3 decided *no coalescing window*: rows are never merged, delayed or
|
|
22
|
+
* dropped. What coalesces is DISPATCHES: at most one is in flight, and every row that arrived
|
|
23
|
+
* before it starts rides in it, up to one journal page (B-20, 100 rows; B-01, 40 000 bytes).
|
|
24
|
+
* So a burst of N rows appended in one tick reaches the adapter as `ceil(N / 100)` dispatches,
|
|
25
|
+
* each row intact and in `journalSeq` order, and rows that arrive while a dispatch is in flight
|
|
26
|
+
* go out together in the next one.
|
|
27
|
+
* WHOSE FACTS ARE WHOSE. This controller serves one SESSION, and its cursors belong to the project's
|
|
28
|
+
* journal. Whether the leader is reachable is a fact about the LEADER, so it is not kept here: the
|
|
29
|
+
* adapter records it (`LeaderDelivery`), and it therefore survives a session change and is never
|
|
30
|
+
* inherited by a leader that has just been attached (QA on #311, round five, both directions).
|
|
31
|
+
*
|
|
32
|
+
* - **Delivery is not reaction (F-20).** `deliver` hands rows to the client application and is
|
|
33
|
+
* non-model. Whether a model turn then starts is the adapter's own, client-specific decision, and
|
|
34
|
+
* it reports that separately through `recordReaction`. The three D-05 § 6.6 cursors are kept
|
|
35
|
+
* apart: `deliveredSeq` (this controller, on a resolved `deliver`), `ackedSeq` (the leader,
|
|
36
|
+
* through an explicit tool call — `ack`) and `reactedSeq` (the adapter, when a turn carrying the
|
|
37
|
+
* rows really started). `reactedSeq` lagging `deliveredSeq` is a normal state, not a fault.
|
|
38
|
+
* - **Non-model traffic only (N-06).** The heartbeat, a transport retry and an acknowledgement
|
|
39
|
+
* never call anything that can start a turn: the heartbeat calls the adapter's optional
|
|
40
|
+
* `heartbeat`, a retry re-sends the SAME undelivered rows, and `ack` touches only a number.
|
|
41
|
+
* Rows already delivered in this session are never dispatched again.
|
|
42
|
+
* - **At-least-once (D-05 § 6.6).** A new session resumes after the leader's last ACK, not after
|
|
43
|
+
* the last delivery: a row handed to a previous client that the leader never acknowledged is
|
|
44
|
+
* outstanding, and F-21 says reconnect delivers it. In production the leader acknowledges through
|
|
45
|
+
* the pull tool (`leader_events ack`), so that record is the ACK read here (`acknowledged`, #332):
|
|
46
|
+
* one source of truth, not two cursors that can disagree. The leader deduplicates on `eventId`. The
|
|
47
|
+
* echo guard (drop a `leader` row whose `causedBy` is the adapter's own operation) is the
|
|
48
|
+
* adapter's, per D-05 § 6.3: rows reach it whole.
|
|
49
|
+
*
|
|
50
|
+
* ONE LOGICAL OWNER. This controller and the MCP bridge are one client, not two (compatibility
|
|
51
|
+
* report, "Recommended architecture"; F-18). So the controller never acquires: it is handed the
|
|
52
|
+
* project's `ProjectOwnership` and the session key the transport acquired with, starts only while
|
|
53
|
+
* that session is the live owner, and re-checks it before every dispatch and at every heartbeat.
|
|
54
|
+
* It never calls `acquire` or `release` — the lease is the transport's, released on connection
|
|
55
|
+
* close (D-02.4) — so it can neither create a second claim nor end the session it serves. A
|
|
56
|
+
* second controller for the same project is refused: a competing session with project-occupied,
|
|
57
|
+
* and a second controller for the SAME session too, because two dispatchers over one journal are
|
|
58
|
+
* the "two independent streams delivering duplicates" risk of requirements § 12.
|
|
59
|
+
*
|
|
60
|
+
* INERT WITHOUT A CLIENT (N-07). With no adapter the controller subscribes to nothing, starts no
|
|
61
|
+
* timer, reads no ownership and writes no file. The journal keeps accumulating rows exactly as it
|
|
62
|
+
* does with no controller at all.
|
|
63
|
+
*
|
|
64
|
+
* ## Every state, and who fires each exit
|
|
65
|
+
*
|
|
66
|
+
* No exit below is a human action, and no state holds a queued row without an on-by-default path
|
|
67
|
+
* to the adapter.
|
|
68
|
+
*
|
|
69
|
+
* | State | Exit | Who fires it | If nobody fires it |
|
|
70
|
+
* | --- | --- | --- | --- |
|
|
71
|
+
* | inert | — (terminal for this object) | — | correct: no client, nothing is owed; rows stay in the journal |
|
|
72
|
+
* | 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 |
|
|
73
|
+
* | idle | → disconnected | a failed heartbeat probe | stays idle — the correct resting state |
|
|
74
|
+
* | dispatching | → idle | `deliver` resolving | the attempt is aborted after one heartbeat interval and counts as a transport drop |
|
|
75
|
+
* | dispatching | → recovering | `deliver` rejecting, or the attempt timing out | — (the timeout above always fires) |
|
|
76
|
+
* | recovering | → idle / → disconnected | the round itself: at most 5 attempts, full-jitter backoff ≤ 200 ms | — (bounded) |
|
|
77
|
+
* | 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 |
|
|
78
|
+
* | 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 |
|
|
79
|
+
* | ended | — (terminal) | — | rows stay in the journal and the persisted cursors; the next session's controller resumes after the last ack |
|
|
80
|
+
*
|
|
81
|
+
* What reaches a terminal state BECAUSE of this controller: nothing but this object. It holds no
|
|
82
|
+
* run, queue slot, lease or worktree, and ending it touches none.
|
|
83
|
+
*
|
|
84
|
+
* ## Where its state lives
|
|
85
|
+
*
|
|
86
|
+
* `<dataDir>/mcp/event-controller.json`, beside the journal: `{ v, projectId, epoch, deliveredSeq,
|
|
87
|
+
* ackedSeq, reactedSeq, floorSeq }`, written by atomic tmp+rename after every change (D-05 N4:
|
|
88
|
+
* persisted per delivery, not batched). Written, never required. With a leader record (production)
|
|
89
|
+
* the file keeps only real history — what was pushed and what reacted — and WHERE pushing starts
|
|
90
|
+
* comes from the leader's record. Standalone, no file means a first session that starts at the
|
|
91
|
+
* journal head (nothing was ever owed to a leader that did not exist; it reads current state, F-21),
|
|
92
|
+
* and an unreadable file starts at the head WITH a stated gap. Either way no reported cursor is ever
|
|
93
|
+
* set to a position nothing reached (QA on #311): a start is a floor, not a delivery. An unwritable
|
|
94
|
+
* directory keeps the cursors in memory with one warning.
|
|
95
|
+
*/
|
|
96
|
+
/** Heartbeat and liveness period. B-17 / D-05 N6, reusing the hub's `HEARTBEAT_MS` (`server/ws.ts`). */
|
|
97
|
+
export const EVENT_CONTROLLER_HEARTBEAT_MS = 30_000;
|
|
98
|
+
/**
|
|
99
|
+
* Delivery attempts per recovery round, and the cap on the doubling full-jitter backoff between them.
|
|
100
|
+
* D-01, D-05 and D-09 fix no delivery-retry numbers (B-16 is the bridge's own "no retry" on a dead
|
|
101
|
+
* socket, which this is not), so the bounded-recovery shape is the one D-02 measured and D-09 adopted
|
|
102
|
+
* as B-15 for the same kind of local contention, rather than a new one.
|
|
103
|
+
*/
|
|
104
|
+
export const EVENT_DELIVERY_ATTEMPTS = OWNER_ACQUIRE_ATTEMPTS;
|
|
105
|
+
export const EVENT_DELIVERY_BACKOFF_CAP_MS = OWNER_BACKOFF_CAP_MS;
|
|
106
|
+
/** The doubling start `project-owner.ts` uses (25, 50, 100, 200 ms). */
|
|
107
|
+
const BACKOFF_BASE_MS = 25;
|
|
108
|
+
const savedSchema = z.object({
|
|
109
|
+
v: z.literal(1),
|
|
110
|
+
projectId: z.string(),
|
|
111
|
+
epoch: z.string(),
|
|
112
|
+
deliveredSeq: z.number().int().nonnegative(),
|
|
113
|
+
ackedSeq: z.number().int().nonnegative(),
|
|
114
|
+
reactedSeq: z.number().int().nonnegative(),
|
|
115
|
+
/** Where pushing resumes. Absent in files written before #332's fix: the ack stood in for it. */
|
|
116
|
+
floorSeq: z.number().int().nonnegative().optional(),
|
|
117
|
+
});
|
|
118
|
+
/** Journal files that have a live controller in this process. One dispatcher per journal. */
|
|
119
|
+
const liveControllers = new Set();
|
|
120
|
+
export class EventController {
|
|
121
|
+
projectId;
|
|
122
|
+
#journal;
|
|
123
|
+
#ownership;
|
|
124
|
+
#sessionKey;
|
|
125
|
+
#adapter;
|
|
126
|
+
#leaderRecord;
|
|
127
|
+
#statePath;
|
|
128
|
+
#heartbeatMs;
|
|
129
|
+
#random;
|
|
130
|
+
#sleep;
|
|
131
|
+
#warn;
|
|
132
|
+
#closed = new AbortController();
|
|
133
|
+
#state;
|
|
134
|
+
/** Read strictly after this cursor next; `undefined` reads from the oldest retained row. */
|
|
135
|
+
#position;
|
|
136
|
+
/** The newest row really handed to the client, in this or an earlier session of this epoch. */
|
|
137
|
+
#delivered = 0;
|
|
138
|
+
/** An explicit acknowledgement through `ack()` — only when there is no leader record. */
|
|
139
|
+
#acked = 0;
|
|
140
|
+
/** The newest row a model turn was really seen to carry. */
|
|
141
|
+
#reacted = 0;
|
|
142
|
+
/**
|
|
143
|
+
* Rows at or below this are not owed to this session, so they are never pushed. It is WHERE PUSHING
|
|
144
|
+
* STARTS, not something that happened, so no status field ever reports it (#332, QA on #311).
|
|
145
|
+
*/
|
|
146
|
+
#floor = 0;
|
|
147
|
+
/** The newest row this session has handed to `deliver`, delivered or still in flight. */
|
|
148
|
+
#handedOut = 0;
|
|
149
|
+
#recovery;
|
|
150
|
+
#busy = false;
|
|
151
|
+
/** A wakeup arrived while busy; whoever clears `#busy` re-schedules, so no row is stranded. */
|
|
152
|
+
#again = false;
|
|
153
|
+
#scheduled = false;
|
|
154
|
+
#unsubscribe;
|
|
155
|
+
#timer;
|
|
156
|
+
#warnedWrite = false;
|
|
157
|
+
#warnedDelivery = false;
|
|
158
|
+
#warnedFault = false;
|
|
159
|
+
constructor(opts, state) {
|
|
160
|
+
this.projectId = opts.journal.projectId;
|
|
161
|
+
this.#journal = opts.journal;
|
|
162
|
+
this.#ownership = opts.ownership;
|
|
163
|
+
this.#sessionKey = opts.sessionKey;
|
|
164
|
+
this.#adapter = opts.adapter;
|
|
165
|
+
this.#leaderRecord = opts.leaderRecord;
|
|
166
|
+
this.#statePath = join(dirname(opts.journal.rowsPath), 'event-controller.json');
|
|
167
|
+
this.#heartbeatMs = opts.heartbeatMs ?? EVENT_CONTROLLER_HEARTBEAT_MS;
|
|
168
|
+
this.#random = opts.random ?? Math.random;
|
|
169
|
+
this.#sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
170
|
+
this.#warn = opts.warn ?? ((message) => console.warn(message));
|
|
171
|
+
this.#state = state;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Start the controller for the session `sessionKey`, which must already own the project through
|
|
175
|
+
* the transport's own acquisition. Throws only on a caller bug (journal and owner slot of two
|
|
176
|
+
* different projects); every runtime condition is an answer.
|
|
177
|
+
*/
|
|
178
|
+
static start(opts) {
|
|
179
|
+
if (opts.journal.projectId !== opts.ownership.projectId) {
|
|
180
|
+
throw new Error('event controller: the journal and the owner slot belong to different projects');
|
|
181
|
+
}
|
|
182
|
+
if (opts.adapter === undefined)
|
|
183
|
+
return { outcome: 'inert', controller: new EventController(opts, 'inert') };
|
|
184
|
+
const projectId = opts.journal.projectId;
|
|
185
|
+
if (liveControllers.has(opts.journal.rowsPath))
|
|
186
|
+
return { outcome: 'refused', error: projectOccupiedError(projectId) };
|
|
187
|
+
if (opts.ownership.sessionToken(opts.sessionKey) === undefined) {
|
|
188
|
+
const error = opts.ownership.state() === 'owned' ? projectOccupiedError(projectId) : sessionExpiredError(projectId);
|
|
189
|
+
return { outcome: 'refused', error };
|
|
190
|
+
}
|
|
191
|
+
liveControllers.add(opts.journal.rowsPath);
|
|
192
|
+
const controller = new EventController(opts, 'idle');
|
|
193
|
+
controller.#resume();
|
|
194
|
+
controller.#unsubscribe = opts.journal.subscribe(() => controller.#schedule());
|
|
195
|
+
controller.#timer = setInterval(() => void controller.#tick().catch((err) => controller.#fault(err)), controller.#heartbeatMs);
|
|
196
|
+
controller.#timer.unref?.();
|
|
197
|
+
controller.#schedule();
|
|
198
|
+
return { outcome: 'started', controller };
|
|
199
|
+
}
|
|
200
|
+
get state() {
|
|
201
|
+
return this.#state;
|
|
202
|
+
}
|
|
203
|
+
status() {
|
|
204
|
+
return {
|
|
205
|
+
state: this.#state,
|
|
206
|
+
deliveredSeq: this.#delivered,
|
|
207
|
+
// Only an explicit acknowledgement, as its owner records it now (#332).
|
|
208
|
+
ackedSeq: this.#ackedNow(),
|
|
209
|
+
reactedSeq: this.#reacted,
|
|
210
|
+
latestSeq: this.#journal.latestSeq,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The leader's acknowledgement (D-05 § 6.6): monotonic and idempotent. At or below the current
|
|
215
|
+
* ack it is a successful no-op, never a rewind; past the journal head it changes nothing. It
|
|
216
|
+
* calls no adapter, so it can never cost a model turn.
|
|
217
|
+
*/
|
|
218
|
+
ack(journalSeq) {
|
|
219
|
+
if (!this.#active())
|
|
220
|
+
return { status: 'inactive', seq: this.#acked };
|
|
221
|
+
assertSeq(journalSeq);
|
|
222
|
+
if (journalSeq <= this.#acked)
|
|
223
|
+
return { status: 'unchanged', seq: this.#acked };
|
|
224
|
+
if (journalSeq > this.#journal.latestSeq)
|
|
225
|
+
return { status: 'ahead', seq: this.#acked };
|
|
226
|
+
this.#acked = journalSeq;
|
|
227
|
+
this.#floor = Math.max(this.#floor, journalSeq);
|
|
228
|
+
this.#persist();
|
|
229
|
+
return { status: 'advanced', seq: this.#acked };
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* The adapter's report that a model turn carrying rows up to `journalSeq` really started — the
|
|
233
|
+
* reaction half of F-20, recorded apart from delivery. Monotonic; never past a row the adapter was
|
|
234
|
+
* handed. A turn may start before `deliver` has resolved, so a row still in flight counts as handed.
|
|
235
|
+
*/
|
|
236
|
+
recordReaction(journalSeq) {
|
|
237
|
+
if (!this.#active())
|
|
238
|
+
return { status: 'inactive', seq: this.#reacted };
|
|
239
|
+
assertSeq(journalSeq);
|
|
240
|
+
if (journalSeq <= this.#reacted)
|
|
241
|
+
return { status: 'unchanged', seq: this.#reacted };
|
|
242
|
+
if (journalSeq > Math.max(this.#delivered, this.#handedOut))
|
|
243
|
+
return { status: 'ahead', seq: this.#reacted };
|
|
244
|
+
this.#reacted = journalSeq;
|
|
245
|
+
this.#persist();
|
|
246
|
+
return { status: 'advanced', seq: this.#reacted };
|
|
247
|
+
}
|
|
248
|
+
/** The adapter's transport came back: start a recovery round now instead of at the next tick. */
|
|
249
|
+
wake() {
|
|
250
|
+
if (this.#state === 'disconnected')
|
|
251
|
+
this.#state = 'idle';
|
|
252
|
+
this.#schedule();
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Confirmed end of the session's connection — fired by the transport where it releases the lease.
|
|
256
|
+
* Ends this controller only: the lease, the journal and every run are untouched.
|
|
257
|
+
*/
|
|
258
|
+
close() {
|
|
259
|
+
this.#end();
|
|
260
|
+
}
|
|
261
|
+
#active() {
|
|
262
|
+
return this.#state !== 'inert' && this.#state !== 'ended';
|
|
263
|
+
}
|
|
264
|
+
#end() {
|
|
265
|
+
if (!this.#active())
|
|
266
|
+
return;
|
|
267
|
+
this.#state = 'ended';
|
|
268
|
+
this.#closed.abort();
|
|
269
|
+
if (this.#timer)
|
|
270
|
+
clearInterval(this.#timer);
|
|
271
|
+
this.#timer = undefined;
|
|
272
|
+
this.#unsubscribe?.();
|
|
273
|
+
this.#unsubscribe = undefined;
|
|
274
|
+
liveControllers.delete(this.#journal.rowsPath);
|
|
275
|
+
}
|
|
276
|
+
/** Ownership is xezar's to enforce, so it is re-read, never remembered. Losing it ends us. */
|
|
277
|
+
#stillOwner() {
|
|
278
|
+
if (!this.#active())
|
|
279
|
+
return false;
|
|
280
|
+
if (this.#ownership.sessionToken(this.#sessionKey) !== undefined)
|
|
281
|
+
return true;
|
|
282
|
+
this.#end();
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
#schedule() {
|
|
286
|
+
if (this.#scheduled || !this.#active())
|
|
287
|
+
return;
|
|
288
|
+
this.#scheduled = true;
|
|
289
|
+
// A microtask, not a timer: rows appended in the same tick share a dispatch, and nothing waits.
|
|
290
|
+
queueMicrotask(() => {
|
|
291
|
+
this.#scheduled = false;
|
|
292
|
+
void this.#drain().catch((err) => this.#fault(err));
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/** A bug inside a background step is logged once, never thrown into the cockpit (N-07). The next
|
|
296
|
+
* tick tries again, because every step re-reads the journal and the owner from scratch. */
|
|
297
|
+
#fault(err) {
|
|
298
|
+
if (this.#warnedFault)
|
|
299
|
+
return;
|
|
300
|
+
this.#warnedFault = true;
|
|
301
|
+
this.#warn(`[xez] MCP event controller for project ${this.projectId} hit an internal error: ${err instanceof Error ? err.message : String(err)}`);
|
|
302
|
+
}
|
|
303
|
+
#release() {
|
|
304
|
+
this.#busy = false;
|
|
305
|
+
if (this.#again) {
|
|
306
|
+
this.#again = false;
|
|
307
|
+
this.#schedule();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async #drain() {
|
|
311
|
+
if (this.#busy) {
|
|
312
|
+
this.#again = true;
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
// A disconnected transport waits for the tick or `wake`, so a burst cannot fire rounds back to back.
|
|
316
|
+
if (this.#state !== 'idle')
|
|
317
|
+
return;
|
|
318
|
+
this.#busy = true;
|
|
319
|
+
try {
|
|
320
|
+
for (;;) {
|
|
321
|
+
if (!this.#stillOwner())
|
|
322
|
+
return;
|
|
323
|
+
// The leader may have acknowledged through the pull tool since the last dispatch (#332).
|
|
324
|
+
this.#syncFloor();
|
|
325
|
+
const next = this.#next();
|
|
326
|
+
if (next === undefined)
|
|
327
|
+
return;
|
|
328
|
+
if (next.lastSeq !== undefined)
|
|
329
|
+
this.#handedOut = Math.max(this.#handedOut, next.lastSeq);
|
|
330
|
+
const delivered = await this.#deliverBounded(next.dispatch);
|
|
331
|
+
if (!this.#active())
|
|
332
|
+
return;
|
|
333
|
+
if (delivered === false) {
|
|
334
|
+
this.#state = 'disconnected';
|
|
335
|
+
if (!this.#warnedDelivery) {
|
|
336
|
+
this.#warnedDelivery = true;
|
|
337
|
+
this.#warn(`[xez] MCP event delivery for project ${this.projectId} failed ${EVENT_DELIVERY_ATTEMPTS} times — retrying every ${this.#heartbeatMs / 1000}s while the session owns the project`);
|
|
338
|
+
}
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
this.#warnedDelivery = false;
|
|
342
|
+
this.#state = 'idle';
|
|
343
|
+
this.#position = next.nextCursor;
|
|
344
|
+
this.#recovery = undefined;
|
|
345
|
+
if (next.lastSeq !== undefined) {
|
|
346
|
+
// Only rows REALLY handed over count as delivered; a receipt says which (the leader's own
|
|
347
|
+
// echoes are settled but never delivered). A redelivery never lowers the count.
|
|
348
|
+
const handed = delivered.receipt === undefined ? next.lastSeq : delivered.receipt.handedThrough;
|
|
349
|
+
if (handed !== null)
|
|
350
|
+
this.#delivered = Math.max(this.#delivered, handed);
|
|
351
|
+
}
|
|
352
|
+
this.#persist();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
finally {
|
|
356
|
+
this.#release();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/** The next dispatch, read from the journal itself. `undefined` when nothing is outstanding. */
|
|
360
|
+
#next() {
|
|
361
|
+
for (;;) {
|
|
362
|
+
let page;
|
|
363
|
+
try {
|
|
364
|
+
page = this.#journal.read(this.#position === undefined ? {} : { cursor: this.#position });
|
|
365
|
+
}
|
|
366
|
+
catch (err) {
|
|
367
|
+
if (!(err instanceof McpJournalCursorError))
|
|
368
|
+
throw err;
|
|
369
|
+
// Our own cursor refused: state the gap and start again from the oldest retained row.
|
|
370
|
+
this.#position = undefined;
|
|
371
|
+
this.#recovery = this.#gap();
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (page.status === 'cursor_too_old') {
|
|
375
|
+
this.#position = page.resumeCursor;
|
|
376
|
+
this.#recovery = this.#gap();
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
// Rows at or below the floor are not owed (the leader acknowledged them, or they predate it).
|
|
380
|
+
const events = page.events.filter((row) => row.journalSeq > this.#floor);
|
|
381
|
+
if (events.length === 0 && page.events.length > 0) {
|
|
382
|
+
this.#position = page.nextCursor;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (events.length === 0 && this.#recovery === undefined)
|
|
386
|
+
return undefined;
|
|
387
|
+
const dispatch = {
|
|
388
|
+
projectId: this.projectId,
|
|
389
|
+
events,
|
|
390
|
+
...(this.#recovery === undefined ? {} : { recovery: this.#recovery }),
|
|
391
|
+
};
|
|
392
|
+
const lastSeq = events.at(-1)?.journalSeq;
|
|
393
|
+
return { dispatch, nextCursor: page.nextCursor, ...(lastSeq === undefined ? {} : { lastSeq }) };
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* One bounded recovery round over the SAME rows. No attempt can start a turn on its own. Every
|
|
398
|
+
* attempt's outcome is a fact about the LEADER, and the adapter records it (`LeaderDelivery`).
|
|
399
|
+
*/
|
|
400
|
+
async #deliverBounded(dispatch) {
|
|
401
|
+
const adapter = this.#adapter;
|
|
402
|
+
for (let attempt = 0; attempt < EVENT_DELIVERY_ATTEMPTS; attempt++) {
|
|
403
|
+
if (!this.#active())
|
|
404
|
+
return false;
|
|
405
|
+
this.#state = attempt === 0 ? 'dispatching' : 'recovering';
|
|
406
|
+
const outcome = await this.#attempt((signal) => adapter.deliver(dispatch, signal));
|
|
407
|
+
if (outcome.ok)
|
|
408
|
+
return { receipt: outcome.value === undefined ? undefined : outcome.value };
|
|
409
|
+
if (attempt < EVENT_DELIVERY_ATTEMPTS - 1) {
|
|
410
|
+
await this.#sleep(this.#random() * Math.min(EVENT_DELIVERY_BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** attempt));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* One call into the adapter, bounded by one heartbeat interval: an attempt that has not settled in
|
|
417
|
+
* a whole liveness period is a dead transport, and it must not hold the queue behind it.
|
|
418
|
+
*/
|
|
419
|
+
async #attempt(call) {
|
|
420
|
+
const attempt = new AbortController();
|
|
421
|
+
const abort = () => attempt.abort();
|
|
422
|
+
this.#closed.signal.addEventListener('abort', abort, { once: true });
|
|
423
|
+
const timer = setTimeout(abort, this.#heartbeatMs);
|
|
424
|
+
timer.unref?.();
|
|
425
|
+
const gaveUp = new Promise((_, reject) => {
|
|
426
|
+
attempt.signal.addEventListener('abort', () => reject(new Error('aborted')), { once: true });
|
|
427
|
+
});
|
|
428
|
+
const work = Promise.resolve().then(() => call(attempt.signal));
|
|
429
|
+
// Whichever loses the race settles later with nobody listening; neither may surface as unhandled.
|
|
430
|
+
work.catch(() => { });
|
|
431
|
+
gaveUp.catch(() => { });
|
|
432
|
+
try {
|
|
433
|
+
return { ok: true, value: await Promise.race([work, gaveUp]) };
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
return { ok: false };
|
|
437
|
+
}
|
|
438
|
+
finally {
|
|
439
|
+
clearTimeout(timer);
|
|
440
|
+
this.#closed.signal.removeEventListener('abort', abort);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
/** The heartbeat: owner check, a recovery round when disconnected, then a non-model liveness probe. */
|
|
444
|
+
async #tick() {
|
|
445
|
+
if (!this.#stillOwner() || this.#busy)
|
|
446
|
+
return;
|
|
447
|
+
if (this.#state === 'disconnected')
|
|
448
|
+
this.#state = 'idle';
|
|
449
|
+
// Re-reading the journal here is also the backstop for a wakeup that never arrived.
|
|
450
|
+
await this.#drain();
|
|
451
|
+
const probe = this.#adapter?.heartbeat;
|
|
452
|
+
if (probe === undefined || this.#state !== 'idle' || this.#busy)
|
|
453
|
+
return;
|
|
454
|
+
this.#busy = true;
|
|
455
|
+
const alive = (await this.#attempt((signal) => probe.call(this.#adapter, signal))).ok;
|
|
456
|
+
if (this.#active() && !alive)
|
|
457
|
+
this.#state = 'disconnected';
|
|
458
|
+
this.#release();
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Where a starting controller reads from, and what it reports. The two are kept apart, because
|
|
462
|
+
* conflating them is how a first session once reported rows as delivered, acknowledged and
|
|
463
|
+
* reacted to that never reached anyone (QA on #311, D-05 § 6.6):
|
|
464
|
+
* - REPORTED cursors come only from things that happened: `deliveredSeq` and `reactedSeq` from
|
|
465
|
+
* this file's record of real pushes and turns in the SAME journal epoch (0 otherwise), and
|
|
466
|
+
* `ackedSeq` from an explicit acknowledgement.
|
|
467
|
+
* - The FLOOR is where pushing resumes: with a leader record, after what the leader is owed
|
|
468
|
+
* (`owedAfter`); standalone (#107), after the file's last ack, or the head for a first session.
|
|
469
|
+
*/
|
|
470
|
+
#resume() {
|
|
471
|
+
const journal = this.#journal;
|
|
472
|
+
const saved = this.#load();
|
|
473
|
+
const own = typeof saved === 'object' && saved.epoch === journal.epoch ? saved : undefined;
|
|
474
|
+
this.#delivered = own ? Math.min(own.deliveredSeq, journal.latestSeq) : 0;
|
|
475
|
+
this.#reacted = own ? Math.min(own.reactedSeq, this.#delivered) : 0;
|
|
476
|
+
this.#acked = own && this.#leaderRecord === undefined ? Math.min(own.ackedSeq, journal.latestSeq) : 0;
|
|
477
|
+
const firstRetained = journal.oldestSeq ?? journal.latestSeq + 1;
|
|
478
|
+
this.#position = undefined;
|
|
479
|
+
let gap;
|
|
480
|
+
if (this.#leaderRecord !== undefined) {
|
|
481
|
+
// One acknowledgement (#332): owed is what the leader's own record says, never this file.
|
|
482
|
+
const owed = this.#owedAfter();
|
|
483
|
+
this.#floor = owed.sameEpoch ? owed.seq : 0;
|
|
484
|
+
gap = !owed.sameEpoch || this.#floor < firstRetained - 1;
|
|
485
|
+
}
|
|
486
|
+
else if (saved === 'absent' || saved === 'unreadable') {
|
|
487
|
+
// Standalone first session: nothing was owed to a leader that did not exist; it reads current state (F-21).
|
|
488
|
+
this.#floor = journal.latestSeq;
|
|
489
|
+
this.#position = journal.headCursor();
|
|
490
|
+
gap = saved === 'unreadable';
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
this.#floor = own ? Math.max(own.ackedSeq, own.floorSeq ?? own.ackedSeq) : 0;
|
|
494
|
+
gap = own === undefined || this.#floor > journal.latestSeq || this.#floor < firstRetained - 1;
|
|
495
|
+
}
|
|
496
|
+
if (gap) {
|
|
497
|
+
// Rows this session was owed are gone (journal recreated, or evicted unacknowledged): say so, and
|
|
498
|
+
// resume at the oldest row that still exists — or, for an unreadable file, at the head.
|
|
499
|
+
if (this.#position === undefined)
|
|
500
|
+
this.#floor = Math.min(Math.max(this.#floor, 0), firstRetained - 1);
|
|
501
|
+
this.#recovery = this.#gap();
|
|
502
|
+
}
|
|
503
|
+
this.#persist();
|
|
504
|
+
}
|
|
505
|
+
/** The leader record's owed-after position, clamped to this journal; nothing readable owes everything. */
|
|
506
|
+
#owedAfter() {
|
|
507
|
+
try {
|
|
508
|
+
const owed = this.#leaderRecord.owedAfter();
|
|
509
|
+
const seq = Number.isSafeInteger(owed.seq) && owed.seq > 0 ? Math.min(owed.seq, this.#journal.latestSeq) : 0;
|
|
510
|
+
return { seq, sameEpoch: owed.sameEpoch };
|
|
511
|
+
}
|
|
512
|
+
catch {
|
|
513
|
+
return { seq: 0, sameEpoch: true }; // an unreadable record owes everything retained: at-least-once, never a skipped row
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
/** The explicit acknowledgement as its owner records it now; 0 when there is none. */
|
|
517
|
+
#ackedNow() {
|
|
518
|
+
if (this.#leaderRecord === undefined)
|
|
519
|
+
return this.#acked;
|
|
520
|
+
try {
|
|
521
|
+
const seq = this.#leaderRecord.acknowledged();
|
|
522
|
+
return Number.isSafeInteger(seq) && seq > 0 ? Math.min(seq, this.#journal.latestSeq) : 0;
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
return 0;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/** Raise the floor to what the leader's record says it is owed after. Monotonic; reports nothing. */
|
|
529
|
+
#syncFloor() {
|
|
530
|
+
if (this.#leaderRecord === undefined)
|
|
531
|
+
return;
|
|
532
|
+
const owed = this.#owedAfter();
|
|
533
|
+
if (!owed.sameEpoch || owed.seq <= this.#floor)
|
|
534
|
+
return;
|
|
535
|
+
this.#floor = owed.seq;
|
|
536
|
+
this.#persist();
|
|
537
|
+
}
|
|
538
|
+
#gap() {
|
|
539
|
+
return {
|
|
540
|
+
required: 'current-state',
|
|
541
|
+
oldestSeq: this.#journal.oldestSeq,
|
|
542
|
+
latestSeq: this.#journal.latestSeq,
|
|
543
|
+
message: 'Some events this session was owed are no longer retained. Read the current state before acting on these events.',
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
#load() {
|
|
547
|
+
let raw;
|
|
548
|
+
try {
|
|
549
|
+
raw = readFileSync(this.#statePath, 'utf8');
|
|
550
|
+
}
|
|
551
|
+
catch (err) {
|
|
552
|
+
if (err.code === 'ENOENT')
|
|
553
|
+
return 'absent';
|
|
554
|
+
raw = '';
|
|
555
|
+
}
|
|
556
|
+
try {
|
|
557
|
+
const parsed = savedSchema.safeParse(JSON.parse(raw));
|
|
558
|
+
if (parsed.success && parsed.data.projectId === this.projectId)
|
|
559
|
+
return parsed.data;
|
|
560
|
+
}
|
|
561
|
+
catch {
|
|
562
|
+
/* reported below */
|
|
563
|
+
}
|
|
564
|
+
this.#warn(`[xez] MCP event controller state for project ${this.projectId} is unreadable — its cursors restart at 0${this.#leaderRecord ? '' : ', pushing from the journal head with a stated gap'}`);
|
|
565
|
+
return 'unreadable';
|
|
566
|
+
}
|
|
567
|
+
#persist() {
|
|
568
|
+
const body = {
|
|
569
|
+
v: 1,
|
|
570
|
+
projectId: this.projectId,
|
|
571
|
+
epoch: this.#journal.epoch,
|
|
572
|
+
deliveredSeq: this.#delivered,
|
|
573
|
+
ackedSeq: this.#acked,
|
|
574
|
+
reactedSeq: this.#reacted,
|
|
575
|
+
floorSeq: this.#floor,
|
|
576
|
+
};
|
|
577
|
+
const tmp = `${this.#statePath}.tmp`;
|
|
578
|
+
try {
|
|
579
|
+
mkdirSync(dirname(this.#statePath), { recursive: true, mode: 0o700 });
|
|
580
|
+
writeFileSync(tmp, `${JSON.stringify(body)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
581
|
+
renameSync(tmp, this.#statePath);
|
|
582
|
+
}
|
|
583
|
+
catch (err) {
|
|
584
|
+
if (this.#warnedWrite)
|
|
585
|
+
return;
|
|
586
|
+
this.#warnedWrite = true;
|
|
587
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
588
|
+
this.#warn(`[xez] MCP event controller state for project ${this.projectId} cannot be written (${message}) — cursors are kept in memory only`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function assertSeq(seq) {
|
|
593
|
+
if (!Number.isSafeInteger(seq) || seq < 0)
|
|
594
|
+
throw new RangeError('a journal sequence number is a non-negative integer');
|
|
595
|
+
}
|
|
596
|
+
//# sourceMappingURL=event-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-controller.js","sourceRoot":"","sources":["../../src/mcp/event-controller.ts"],"names":[],"mappings":"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"}
|