@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
package/dist/mcp/ipc.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { mcpProjectOccupiedErrorSchema, mcpSessionExpiredErrorSchema } from '../contract/index.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { xezarHomeDir } from '../paths.js';
|
|
6
|
+
/**
|
|
7
|
+
* The IPC leg between `xez mcp` (the bridge) and the running xezar service — D-01
|
|
8
|
+
* (docs/features/mcp-server/mcp-d01-transport-decision.md) § 1.2–1.5.
|
|
9
|
+
*
|
|
10
|
+
* One Unix domain socket per registered project, owned by the service, at
|
|
11
|
+
* `<xezarHomeDir()>/ipc/<projectId>.sock` (dir 0700, socket 0600). Frames are
|
|
12
|
+
* newline-delimited JSON, the same framing as the stdio leg. The socket IS the
|
|
13
|
+
* project binding (§ 1.5): no frame carries a project field, and the service answers
|
|
14
|
+
* only for the project it opened the socket for.
|
|
15
|
+
*
|
|
16
|
+
* Both sides import this file, so the path rule and the frame shapes cannot drift.
|
|
17
|
+
* They may still come from different installed versions (`npx` vs a global
|
|
18
|
+
* install), which is what `IPC_PROTOCOL_VERSION` is for.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Bumped only when a frame shape changes incompatibly. Carried on every request.
|
|
22
|
+
*
|
|
23
|
+
* 2 (#302): a connection is a SESSION. The bridge keeps one connection for its whole life, opens
|
|
24
|
+
* the session on it with `session/open` (which acquires the project's owner slot, D-02), and every
|
|
25
|
+
* `health` / `tools/call` needs that live session. A version-1 bridge — one connection per call,
|
|
26
|
+
* no session — would be refused on every call anyway; the version check refuses it legibly first.
|
|
27
|
+
*/
|
|
28
|
+
export const IPC_PROTOCOL_VERSION = 2;
|
|
29
|
+
/**
|
|
30
|
+
* Largest frame either leg accepts, in bytes: 32 MiB + 64 KiB. Taken from D-09 B-08
|
|
31
|
+
* (qodeca/xezar#206, not yet merged at the time of writing) so the bridge does not
|
|
32
|
+
* invent a second number. A larger frame is refused with an explicit error and
|
|
33
|
+
* closes nothing else.
|
|
34
|
+
*/
|
|
35
|
+
export const MAX_FRAME_BYTES = 33_619_968;
|
|
36
|
+
/**
|
|
37
|
+
* How long the bridge waits for the service to answer one request — D-09 B-09
|
|
38
|
+
* (qodeca/xezar#206): 55 s. On expiry the bridge answers with a readable result;
|
|
39
|
+
* the service operation is not cancelled. There is no retry (D-01 § 5, D-09 B-16).
|
|
40
|
+
*/
|
|
41
|
+
export const IPC_REQUEST_TIMEOUT_MS = 55_000;
|
|
42
|
+
/**
|
|
43
|
+
* How long the bridge waits to connect and open its session. Acquisition itself is bounded far
|
|
44
|
+
* below this (D-02.2: 5 attempts, at most 375 ms of backoff), so only a hung service reaches it.
|
|
45
|
+
* It is short because `initialize` waits on it, and a client gives up on a server whose handshake
|
|
46
|
+
* is slow — Codex's documented default startup timeout is 10 s (not re-measured here). On expiry
|
|
47
|
+
* the handshake still succeeds with no session (N-07) and the next call tries again.
|
|
48
|
+
*/
|
|
49
|
+
export const IPC_SESSION_OPEN_TIMEOUT_MS = 5_000;
|
|
50
|
+
// ---- socket path ---------------------------------------------------------------
|
|
51
|
+
/**
|
|
52
|
+
* The longest socket path the OS accepts, in bytes. An operating-system constant
|
|
53
|
+
* (`sun_path`), not a xezar choice: macOS/BSD measured 104 OK and 105 EINVAL
|
|
54
|
+
* (D-01 E5); Linux reserves 108 bytes including the terminating NUL.
|
|
55
|
+
*/
|
|
56
|
+
function maxSocketPathBytes(platform) {
|
|
57
|
+
return platform === 'linux' ? 107 : 104;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Where the service listens for `projectId`, and where the bridge connects. Pure:
|
|
61
|
+
* both sides compute it from the same registry entry, so they always agree.
|
|
62
|
+
*
|
|
63
|
+
* D-01 § 1.4: when `<home>/ipc/<projectId>.sock` is longer than the OS allows, the
|
|
64
|
+
* first 12 hex characters of SHA-256(project root) replace the id. When even that
|
|
65
|
+
* is too long (a very long `XEZ_HOME`), there is no socket — the caller degrades.
|
|
66
|
+
* Windows named pipes are untested and their naming is undecided (D-01 § 10.2), so
|
|
67
|
+
* Windows is reported as unavailable rather than guessed.
|
|
68
|
+
*/
|
|
69
|
+
export function mcpSocketLocation(project, env = process.env, platform = process.platform) {
|
|
70
|
+
if (platform === 'win32') {
|
|
71
|
+
return { kind: 'unavailable', reason: 'the xezar MCP bridge is not supported on Windows yet' };
|
|
72
|
+
}
|
|
73
|
+
const dir = mcpSocketDir(env);
|
|
74
|
+
const limit = maxSocketPathBytes(platform);
|
|
75
|
+
const primary = join(dir, `${project.id}.sock`);
|
|
76
|
+
if (Buffer.byteLength(primary) <= limit)
|
|
77
|
+
return { kind: 'socket', path: primary };
|
|
78
|
+
const hashed = join(dir, `${createHash('sha256').update(project.root).digest('hex').slice(0, 12)}.sock`);
|
|
79
|
+
if (Buffer.byteLength(hashed) <= limit)
|
|
80
|
+
return { kind: 'socket', path: hashed };
|
|
81
|
+
return {
|
|
82
|
+
kind: 'unavailable',
|
|
83
|
+
reason: `the xezar home path is too long for a local socket on this system (limit ${limit} bytes) — point XEZ_HOME at a shorter directory`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** `<xezarHomeDir()>/ipc` — the only directory the service creates for MCP. */
|
|
87
|
+
export function mcpSocketDir(env = process.env) {
|
|
88
|
+
return join(xezarHomeDir(env), 'ipc');
|
|
89
|
+
}
|
|
90
|
+
// ---- frames --------------------------------------------------------------------
|
|
91
|
+
/** A tool result exactly as MCP carries it: the text block is authoritative. */
|
|
92
|
+
export const toolResultSchema = z.object({
|
|
93
|
+
content: z.array(z.object({ type: z.literal('text'), text: z.string() })),
|
|
94
|
+
structuredContent: z.record(z.string(), z.unknown()).optional(),
|
|
95
|
+
isError: z.boolean().optional(),
|
|
96
|
+
});
|
|
97
|
+
/** What `health` answers. No account identity and no path — F-12, N-01, F-15. */
|
|
98
|
+
export const healthResultSchema = z.object({
|
|
99
|
+
ipcVersion: z.number().int(),
|
|
100
|
+
xezarVersion: z.string().max(64),
|
|
101
|
+
project: z.object({ id: z.string().max(64), name: z.string().max(200) }),
|
|
102
|
+
});
|
|
103
|
+
export const toolCallParamsSchema = z.object({
|
|
104
|
+
name: z.string().min(1).max(128),
|
|
105
|
+
arguments: z.record(z.string(), z.unknown()).optional(),
|
|
106
|
+
});
|
|
107
|
+
/**
|
|
108
|
+
* Bridge → service. `v` is checked BEFORE the method is looked at, so a service
|
|
109
|
+
* can refuse a bridge from another protocol version legibly. `bridgeVersion` is the
|
|
110
|
+
* bridge's xezar version, for diagnostics only.
|
|
111
|
+
*/
|
|
112
|
+
export const ipcRequestSchema = z.object({
|
|
113
|
+
v: z.number().int(),
|
|
114
|
+
id: z.number().int().nonnegative(),
|
|
115
|
+
bridgeVersion: z.string().max(64).optional(),
|
|
116
|
+
method: z.string().min(1).max(64),
|
|
117
|
+
params: z.unknown().optional(),
|
|
118
|
+
});
|
|
119
|
+
export const ipcErrorCodeSchema = z.enum([
|
|
120
|
+
'bad-frame',
|
|
121
|
+
'version-mismatch',
|
|
122
|
+
'unknown-method',
|
|
123
|
+
'invalid-params',
|
|
124
|
+
'unknown-tool',
|
|
125
|
+
'internal',
|
|
126
|
+
/** `session/open` while another session owns the project (D-02 § 4, `-32080`). */
|
|
127
|
+
'project-occupied',
|
|
128
|
+
/** A call on a connection whose session does not own the project (D-02 § 4, `-32081`). */
|
|
129
|
+
'session-expired',
|
|
130
|
+
]);
|
|
131
|
+
/**
|
|
132
|
+
* The JSON-RPC error the bridge hands the client verbatim for the two ownership refusals. The
|
|
133
|
+
* contract's schemas are `.strict()`, so nothing about the competing owner can ride along (N-01).
|
|
134
|
+
*/
|
|
135
|
+
export const ownershipRpcErrorSchema = z.union([mcpProjectOccupiedErrorSchema, mcpSessionExpiredErrorSchema]);
|
|
136
|
+
/** What `session/open` answers: this connection's session now owns the project. */
|
|
137
|
+
export const sessionOpenResultSchema = z.object({ owner: z.literal(true) });
|
|
138
|
+
/** Service → bridge. */
|
|
139
|
+
export const ipcResponseSchema = z.discriminatedUnion('ok', [
|
|
140
|
+
z.object({ v: z.number().int(), id: z.number().int().nullable(), ok: z.literal(true), result: z.unknown() }),
|
|
141
|
+
z.object({
|
|
142
|
+
v: z.number().int(),
|
|
143
|
+
id: z.number().int().nullable(),
|
|
144
|
+
ok: z.literal(false),
|
|
145
|
+
error: z.object({ code: ipcErrorCodeSchema, message: z.string().max(2000) }),
|
|
146
|
+
/** The service's xezar version, so a mismatch message can name both sides. */
|
|
147
|
+
serviceVersion: z.string().max(64).optional(),
|
|
148
|
+
/** Only on `project-occupied` / `session-expired`: the D-02 § 4 error the client receives. */
|
|
149
|
+
rpcError: ownershipRpcErrorSchema.optional(),
|
|
150
|
+
}),
|
|
151
|
+
]);
|
|
152
|
+
// ---- framing -------------------------------------------------------------------
|
|
153
|
+
/**
|
|
154
|
+
* Split a byte stream into newline-delimited frames. A frame over `maxBytes` is
|
|
155
|
+
* reported once through `onOversize` and skipped up to its newline; the stream
|
|
156
|
+
* keeps working (D-09 B-08: "closes nothing else").
|
|
157
|
+
*/
|
|
158
|
+
export class LineFramer {
|
|
159
|
+
onFrame;
|
|
160
|
+
onOversize;
|
|
161
|
+
maxBytes;
|
|
162
|
+
chunks = [];
|
|
163
|
+
size = 0;
|
|
164
|
+
discarding = false;
|
|
165
|
+
constructor(onFrame, onOversize, maxBytes = MAX_FRAME_BYTES) {
|
|
166
|
+
this.onFrame = onFrame;
|
|
167
|
+
this.onOversize = onOversize;
|
|
168
|
+
this.maxBytes = maxBytes;
|
|
169
|
+
}
|
|
170
|
+
push(chunk) {
|
|
171
|
+
let start = 0;
|
|
172
|
+
for (let nl = chunk.indexOf(0x0a, start); nl !== -1; nl = chunk.indexOf(0x0a, start)) {
|
|
173
|
+
this.append(chunk.subarray(start, nl));
|
|
174
|
+
this.flushFrame();
|
|
175
|
+
start = nl + 1;
|
|
176
|
+
}
|
|
177
|
+
this.append(chunk.subarray(start));
|
|
178
|
+
}
|
|
179
|
+
append(part) {
|
|
180
|
+
if (this.discarding || part.length === 0)
|
|
181
|
+
return;
|
|
182
|
+
if (this.size + part.length > this.maxBytes) {
|
|
183
|
+
this.chunks = [];
|
|
184
|
+
this.size = 0;
|
|
185
|
+
this.discarding = true;
|
|
186
|
+
this.onOversize();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
this.chunks.push(part);
|
|
190
|
+
this.size += part.length;
|
|
191
|
+
}
|
|
192
|
+
flushFrame() {
|
|
193
|
+
const wasDiscarding = this.discarding;
|
|
194
|
+
const line = Buffer.concat(this.chunks, this.size).toString('utf8').replace(/\r$/, '');
|
|
195
|
+
this.chunks = [];
|
|
196
|
+
this.size = 0;
|
|
197
|
+
this.discarding = false;
|
|
198
|
+
if (!wasDiscarding && line.trim() !== '')
|
|
199
|
+
this.onFrame(line);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/** One frame on the wire. `JSON.stringify` never emits a raw newline. */
|
|
203
|
+
export function encodeFrame(value) {
|
|
204
|
+
return `${JSON.stringify(value)}\n`;
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=ipc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ipc.js","sourceRoot":"","sources":["../../src/mcp/ipc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { McpLeaderActionInput, McpLeaderStatus } from '../contract/index.js';
|
|
2
|
+
import type { ProjectOwnership } from '../workspace/project-owner.ts';
|
|
3
|
+
import { type PiLeaderDescriptor, type PiLeaderLink, readPiLeaderDescriptor } from './adapters/pi-link.ts';
|
|
4
|
+
import type { EchoGuard } from './echo-guard.ts';
|
|
5
|
+
import { type DeliveryReceipt, type EventDispatch, type LeaderRecord, type ReactionAdapter } from './event-controller.ts';
|
|
6
|
+
import type { EventJournal } from './event-journal.ts';
|
|
7
|
+
import type { LeaderActResult, ProjectLeaderPort } from './project-leaders.ts';
|
|
8
|
+
/**
|
|
9
|
+
* Push delivery, connected (#309, Phase 6 of #73). Until this module, `EventController` (#107) and
|
|
10
|
+
* the reaction adapters (#108–#110) were complete, tested — and constructed by nothing outside their
|
|
11
|
+
* own tests, so no event ever reached a client. This is the one place the running service builds
|
|
12
|
+
* the controller, composed once per project by `startMcpService`.
|
|
13
|
+
*
|
|
14
|
+
* WHAT IS ON BY DEFAULT. Every MCP session that becomes the project's owner (`session/open`,
|
|
15
|
+
* D-02.2) gets an event controller at once, with this object as its adapter; the controller ends
|
|
16
|
+
* when that session's connection closes (D-02.4). No flag, no setting: an owner session always has
|
|
17
|
+
* a dispatcher following the journal for it.
|
|
18
|
+
*
|
|
19
|
+
* WHO IT CAN REACH, AND WHY ONLY THEM. A dispatch must reach a MODEL, and generic MCP notifications
|
|
20
|
+
* start no turn in Claude Code, Codex, OpenCode or pi (D-05 § 4; #330 run A for pi; each adapter's
|
|
21
|
+
* evidence record). xezar NEVER starts an agent process for a leader (owner decision on #311): the
|
|
22
|
+
* person runs their own leader and connects it to xezar over MCP. So an event reaches a model only
|
|
23
|
+
* through a session the person runs AND tells xezar where to find — `attach`. Two clients can be
|
|
24
|
+
* told today:
|
|
25
|
+
*
|
|
26
|
+
* - **OpenCode** names an `opencode serve` session by URL and session id (#110).
|
|
27
|
+
* - **pi** names nothing, because it cannot: pi's RPC is stdio-only and spawn-only, so a pi the
|
|
28
|
+
* person started has no address. The address therefore comes from INSIDE it — xezar's pi leader
|
|
29
|
+
* extension opens a socket and announces it in the project's data directory, and `#piTarget()`
|
|
30
|
+
* dials that (#330 WP2, `adapters/pi-link.ts`). With no extension running there is no descriptor
|
|
31
|
+
* and the attach is refused with pi's own recoverable reason, which is exactly the behaviour that
|
|
32
|
+
* existed before the extension did.
|
|
33
|
+
*
|
|
34
|
+
* A Claude Code or Codex session in a terminal still has no address at all: for it the controller
|
|
35
|
+
* keeps the rows in the journal, reports `disconnected`, retries at its heartbeat, and
|
|
36
|
+
* `status().blocker` says so. Nothing is lost either way — such a leader reads its events with the
|
|
37
|
+
* `leader_events` tool (#251), and the next session resumes after its last acknowledgement.
|
|
38
|
+
*
|
|
39
|
+
* THE ECHO GUARD HOLDS HERE, FOR EVERY CLIENT. The door records each mutation's operation id as the
|
|
40
|
+
* leader's own before it runs (`EchoGuard.issue`, #106), including the ids it mints for tools that
|
|
41
|
+
* carry no `operationId`. A `leader` row caused by one of them is dropped before the adapter sees it,
|
|
42
|
+
* so a client's own guard — which knows only the ids it saw — cannot miss a door-minted one. Only the
|
|
43
|
+
* echo rule: the guard's `duplicate` rule must NOT be applied, because redelivery after a reconnect
|
|
44
|
+
* is at-least-once by contract.
|
|
45
|
+
*
|
|
46
|
+
* ## Every state this relies on, and who fires each exit
|
|
47
|
+
*
|
|
48
|
+
* Controller states (`EventControllerState`), as wired here:
|
|
49
|
+
*
|
|
50
|
+
* | State | Exit | Who fires it (on by default) |
|
|
51
|
+
* | --- | --- | --- |
|
|
52
|
+
* | inert | — | never entered: this object is always the adapter |
|
|
53
|
+
* | idle | → dispatching | a journal append, or the 30 s heartbeat tick |
|
|
54
|
+
* | dispatching | → idle / → recovering | `deliver` resolving / rejecting or timing out (one heartbeat) |
|
|
55
|
+
* | recovering | → idle / → disconnected | the bounded round itself (5 attempts) |
|
|
56
|
+
* | disconnected | → dispatching | the heartbeat tick, every 30 s; or `wake()`, which `attach` fires at once |
|
|
57
|
+
* | any | → ended | the session's connection closing (`sessionClosed`), another session taking the project (`sessionOpened`), the lease lapsing (found at the next tick), or the service closing |
|
|
58
|
+
*
|
|
59
|
+
* `disconnected` with nothing attached is the one state that waits on something outside xezar, and
|
|
60
|
+
* it does not wait on a human alone: the heartbeat retries it for as long as the session owns the
|
|
61
|
+
* project, at no model cost (N-06). It is the recoverable blocker the requirements ask for.
|
|
62
|
+
*
|
|
63
|
+
* Leader states: none → attached (`act` attach); attached → none (`act` stop, or the service
|
|
64
|
+
* closing). An attached OpenCode session that goes away is the adapter's own recoverable blocker,
|
|
65
|
+
* reported by `status()`; the controller retries it at the heartbeat. A pi leader that goes away
|
|
66
|
+
* takes its socket with it, which closes the link and reports the same way.
|
|
67
|
+
*
|
|
68
|
+
* What xezar OPENS to reach a leader is released on every one of those exits: `#detach` closes the
|
|
69
|
+
* adapter and then its `dispose` (pi's socket today), and `close()` goes through `#detach`.
|
|
70
|
+
*
|
|
71
|
+
* What reaches a terminal state BECAUSE of this module: its controllers. No process, run, lease,
|
|
72
|
+
* queue slot or worktree — it starts nothing and stops nothing but its own objects.
|
|
73
|
+
*/
|
|
74
|
+
/** xezar's base role, sent with every event to an attached leader. Per-project customisation is not built yet. */
|
|
75
|
+
export declare const LEADER_ROLE_INSTRUCTION: string;
|
|
76
|
+
export interface LeaderDeliveryOptions {
|
|
77
|
+
readonly projectId: string;
|
|
78
|
+
readonly projectRoot: string;
|
|
79
|
+
readonly journal: EventJournal;
|
|
80
|
+
readonly ownership: Pick<ProjectOwnership, 'projectId' | 'sessionToken' | 'state'>;
|
|
81
|
+
/** The door's echo guard. Absent (it could not be built): no row is dropped as an echo. */
|
|
82
|
+
readonly guard: Pick<EchoGuard, 'isOwn'> | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* The leader's acknowledgement record (`LeaderCursors`, #251) — the ONE acknowledgement the
|
|
85
|
+
* controller resumes by, filters by and reports (#332). Absent: the controller's own.
|
|
86
|
+
*/
|
|
87
|
+
readonly leaderRecord?: LeaderRecord;
|
|
88
|
+
readonly warn: (message: string) => void;
|
|
89
|
+
/** Test seam. Production uses the controller's 30 s. */
|
|
90
|
+
readonly heartbeatMs?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Where this project's state lives, for the pi leader descriptor (`adapters/pi-link.ts`). Absent
|
|
93
|
+
* it is DERIVED from `projectRoot`, which is what the service already does, so nothing upstream
|
|
94
|
+
* has to pass it for pi attach to work. A store with a relocated `dataDir` may pass its own.
|
|
95
|
+
*/
|
|
96
|
+
readonly dataDir?: string;
|
|
97
|
+
/** Test seams for the pi link, so a case never touches a real socket. Production uses the module. */
|
|
98
|
+
readonly piLeader?: {
|
|
99
|
+
read?: (dataDir: string) => ReturnType<typeof readPiLeaderDescriptor>;
|
|
100
|
+
connect?: (descriptor: PiLeaderDescriptor, opts: {
|
|
101
|
+
warn?: (message: string) => void;
|
|
102
|
+
}) => PiLeaderLink;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export declare class LeaderDelivery implements ReactionAdapter, ProjectLeaderPort {
|
|
106
|
+
#private;
|
|
107
|
+
readonly projectId: string;
|
|
108
|
+
constructor(opts: LeaderDeliveryOptions);
|
|
109
|
+
/** `session/open` made `sessionKey` the owner. Never throws into the transport (N-07). */
|
|
110
|
+
sessionOpened(sessionKey: string): void;
|
|
111
|
+
/** The session's connection closed (D-02.4): its controller ends. The journal keeps every row. */
|
|
112
|
+
sessionClosed(sessionKey: string): void;
|
|
113
|
+
deliver(dispatch: EventDispatch, signal: AbortSignal): Promise<DeliveryReceipt>;
|
|
114
|
+
heartbeat(signal: AbortSignal): Promise<void>;
|
|
115
|
+
status(): McpLeaderStatus;
|
|
116
|
+
act(input: McpLeaderActionInput): Promise<LeaderActResult>;
|
|
117
|
+
/** The service is stopping: every controller ends and the attached session is let go. */
|
|
118
|
+
close(): void;
|
|
119
|
+
}
|