@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.
Files changed (250) hide show
  1. package/README.md +45 -15
  2. package/dist/agent-config/account-identity.d.ts +2 -8
  3. package/dist/agent-config/account-identity.js +54 -10
  4. package/dist/agent-config/account-identity.js.map +1 -1
  5. package/dist/agent-config/catalog.d.ts +49 -7
  6. package/dist/agent-config/catalog.js +107 -0
  7. package/dist/agent-config/catalog.js.map +1 -1
  8. package/dist/agent-config/model-settings/pi.d.ts +12 -6
  9. package/dist/agent-config/model-settings/pi.js +12 -6
  10. package/dist/agent-config/model-settings/pi.js.map +1 -1
  11. package/dist/agent-config/seed.js +10 -0
  12. package/dist/agent-config/seed.js.map +1 -1
  13. package/dist/contract/github.d.ts +15 -0
  14. package/dist/contract/index.d.ts +9 -0
  15. package/dist/contract/index.js +1527 -895
  16. package/dist/contract/mcp-api-reference.d.ts +174 -0
  17. package/dist/contract/mcp-audit.d.ts +103 -0
  18. package/dist/contract/mcp-discovery.d.ts +257 -0
  19. package/dist/contract/mcp-event-catalog.d.ts +121 -0
  20. package/dist/contract/mcp-event-catalog.test.d.ts +1 -0
  21. package/dist/contract/mcp-idempotency.d.ts +200 -0
  22. package/dist/contract/mcp-journal.d.ts +268 -0
  23. package/dist/contract/mcp-leader.d.ts +88 -0
  24. package/dist/contract/mcp-ownership.d.ts +78 -0
  25. package/dist/contract/mcp-versioning.d.ts +139 -0
  26. package/dist/contract/runs.d.ts +81 -0
  27. package/dist/core/agent-profiles.d.ts +15 -4
  28. package/dist/core/agent-profiles.js +22 -8
  29. package/dist/core/agent-profiles.js.map +1 -1
  30. package/dist/core/opencode-server-runner.js +71 -11
  31. package/dist/core/opencode-server-runner.js.map +1 -1
  32. package/dist/core/secret-redaction.d.ts +7 -1
  33. package/dist/core/secret-redaction.js +44 -10
  34. package/dist/core/secret-redaction.js.map +1 -1
  35. package/dist/index.js +138 -27
  36. package/dist/index.js.map +1 -1
  37. package/dist/machine-identity.d.ts +66 -0
  38. package/dist/machine-identity.js +96 -0
  39. package/dist/machine-identity.js.map +1 -0
  40. package/dist/mcp/adapters/claude-code.d.ts +41 -0
  41. package/dist/mcp/adapters/claude-code.js +40 -0
  42. package/dist/mcp/adapters/claude-code.js.map +1 -0
  43. package/dist/mcp/adapters/codex.d.ts +140 -0
  44. package/dist/mcp/adapters/codex.js +323 -0
  45. package/dist/mcp/adapters/codex.js.map +1 -0
  46. package/dist/mcp/adapters/opencode.d.ts +148 -0
  47. package/dist/mcp/adapters/opencode.js +528 -0
  48. package/dist/mcp/adapters/opencode.js.map +1 -0
  49. package/dist/mcp/adapters/pi-link.d.ts +102 -0
  50. package/dist/mcp/adapters/pi-link.js +235 -0
  51. package/dist/mcp/adapters/pi-link.js.map +1 -0
  52. package/dist/mcp/adapters/pi.d.ts +188 -0
  53. package/dist/mcp/adapters/pi.js +418 -0
  54. package/dist/mcp/adapters/pi.js.map +1 -0
  55. package/dist/mcp/api-reference.d.ts +26 -0
  56. package/dist/mcp/api-reference.js +133 -0
  57. package/dist/mcp/api-reference.js.map +1 -0
  58. package/dist/mcp/audit-trail.d.ts +162 -0
  59. package/dist/mcp/audit-trail.js +306 -0
  60. package/dist/mcp/audit-trail.js.map +1 -0
  61. package/dist/mcp/bridge.d.ts +81 -0
  62. package/dist/mcp/bridge.js +407 -0
  63. package/dist/mcp/bridge.js.map +1 -0
  64. package/dist/mcp/connection-file.d.ts +49 -0
  65. package/dist/mcp/connection-file.js +63 -0
  66. package/dist/mcp/connection-file.js.map +1 -0
  67. package/dist/mcp/echo-guard.d.ts +94 -0
  68. package/dist/mcp/echo-guard.js +160 -0
  69. package/dist/mcp/echo-guard.js.map +1 -0
  70. package/dist/mcp/event-catalog.d.ts +120 -0
  71. package/dist/mcp/event-catalog.js +306 -0
  72. package/dist/mcp/event-catalog.js.map +1 -0
  73. package/dist/mcp/event-controller.d.ts +230 -0
  74. package/dist/mcp/event-controller.js +596 -0
  75. package/dist/mcp/event-controller.js.map +1 -0
  76. package/dist/mcp/event-journal.d.ts +57 -0
  77. package/dist/mcp/event-journal.js +423 -0
  78. package/dist/mcp/event-journal.js.map +1 -0
  79. package/dist/mcp/index.d.ts +63 -0
  80. package/dist/mcp/index.js +386 -0
  81. package/dist/mcp/index.js.map +1 -0
  82. package/dist/mcp/ipc.d.ts +202 -0
  83. package/dist/mcp/ipc.js +206 -0
  84. package/dist/mcp/ipc.js.map +1 -0
  85. package/dist/mcp/leader-delivery.d.ts +119 -0
  86. package/dist/mcp/leader-delivery.js +450 -0
  87. package/dist/mcp/leader-delivery.js.map +1 -0
  88. package/dist/mcp/operation-receipts.d.ts +210 -0
  89. package/dist/mcp/operation-receipts.js +723 -0
  90. package/dist/mcp/operation-receipts.js.map +1 -0
  91. package/dist/mcp/project-catalogs.d.ts +23 -0
  92. package/dist/mcp/project-catalogs.js +25 -0
  93. package/dist/mcp/project-catalogs.js.map +1 -0
  94. package/dist/mcp/project-leaders.d.ts +26 -0
  95. package/dist/mcp/project-leaders.js +17 -0
  96. package/dist/mcp/project-leaders.js.map +1 -0
  97. package/dist/mcp/protocol.d.ts +50 -0
  98. package/dist/mcp/protocol.js +47 -0
  99. package/dist/mcp/protocol.js.map +1 -0
  100. package/dist/mcp/reconnect.d.ts +297 -0
  101. package/dist/mcp/reconnect.js +465 -0
  102. package/dist/mcp/reconnect.js.map +1 -0
  103. package/dist/mcp/resource-ownership.d.ts +228 -0
  104. package/dist/mcp/resource-ownership.js +390 -0
  105. package/dist/mcp/resource-ownership.js.map +1 -0
  106. package/dist/mcp/service-adapter.d.ts +16488 -0
  107. package/dist/mcp/service-adapter.js +148 -0
  108. package/dist/mcp/service-adapter.js.map +1 -0
  109. package/dist/mcp/service.d.ts +83 -0
  110. package/dist/mcp/service.js +233 -0
  111. package/dist/mcp/service.js.map +1 -0
  112. package/dist/mcp/session-binding.d.ts +105 -0
  113. package/dist/mcp/session-binding.js +163 -0
  114. package/dist/mcp/session-binding.js.map +1 -0
  115. package/dist/mcp/stale-write.d.ts +145 -0
  116. package/dist/mcp/stale-write.js +219 -0
  117. package/dist/mcp/stale-write.js.map +1 -0
  118. package/dist/mcp/tool.d.ts +48 -0
  119. package/dist/mcp/tool.js +24 -0
  120. package/dist/mcp/tool.js.map +1 -0
  121. package/dist/mcp/tools/discovery.d.ts +40 -0
  122. package/dist/mcp/tools/discovery.js +232 -0
  123. package/dist/mcp/tools/discovery.js.map +1 -0
  124. package/dist/mcp/tools/execution-control.d.ts +171 -0
  125. package/dist/mcp/tools/execution-control.js +513 -0
  126. package/dist/mcp/tools/execution-control.js.map +1 -0
  127. package/dist/mcp/tools/handoff-git.d.ts +26 -0
  128. package/dist/mcp/tools/handoff-git.js +544 -0
  129. package/dist/mcp/tools/handoff-git.js.map +1 -0
  130. package/dist/mcp/tools/index.d.ts +2 -0
  131. package/dist/mcp/tools/index.js +23 -0
  132. package/dist/mcp/tools/index.js.map +1 -0
  133. package/dist/mcp/tools/leader-events.d.ts +46 -0
  134. package/dist/mcp/tools/leader-events.js +126 -0
  135. package/dist/mcp/tools/leader-events.js.map +1 -0
  136. package/dist/mcp/tools/local-handoff.d.ts +102 -0
  137. package/dist/mcp/tools/local-handoff.js +306 -0
  138. package/dist/mcp/tools/local-handoff.js.map +1 -0
  139. package/dist/mcp/tools/project-config.d.ts +340 -0
  140. package/dist/mcp/tools/project-config.js +1075 -0
  141. package/dist/mcp/tools/project-config.js.map +1 -0
  142. package/dist/mcp/tools/results-evidence.d.ts +112 -0
  143. package/dist/mcp/tools/results-evidence.js +1045 -0
  144. package/dist/mcp/tools/results-evidence.js.map +1 -0
  145. package/dist/mcp/tools/task-create.d.ts +70 -0
  146. package/dist/mcp/tools/task-create.js +473 -0
  147. package/dist/mcp/tools/task-create.js.map +1 -0
  148. package/dist/mcp/tools/task-reads.d.ts +102 -0
  149. package/dist/mcp/tools/task-reads.js +689 -0
  150. package/dist/mcp/tools/task-reads.js.map +1 -0
  151. package/dist/mcp/tools/work-organisation.d.ts +2 -0
  152. package/dist/mcp/tools/work-organisation.js +502 -0
  153. package/dist/mcp/tools/work-organisation.js.map +1 -0
  154. package/dist/paths.d.ts +17 -7
  155. package/dist/paths.js +18 -8
  156. package/dist/paths.js.map +1 -1
  157. package/dist/runs/project-writer.js +76 -11
  158. package/dist/runs/project-writer.js.map +1 -1
  159. package/dist/runs/retention.d.ts +24 -1
  160. package/dist/runs/retention.js +62 -1
  161. package/dist/runs/retention.js.map +1 -1
  162. package/dist/server/forge/github.js +44 -0
  163. package/dist/server/forge/github.js.map +1 -1
  164. package/dist/server/forge/types.d.ts +16 -0
  165. package/dist/server/project-context.d.ts +81 -8
  166. package/dist/server/project-context.js +143 -35
  167. package/dist/server/project-context.js.map +1 -1
  168. package/dist/server/server.d.ts +1050 -53
  169. package/dist/server/server.js +326 -120
  170. package/dist/server/server.js.map +1 -1
  171. package/dist/server/validators.d.ts +26 -6
  172. package/dist/server/validators.js +17 -0
  173. package/dist/server/validators.js.map +1 -1
  174. package/dist/skills-remote.js +64 -6
  175. package/dist/skills-remote.js.map +1 -1
  176. package/dist/workflows/run.d.ts +277 -12
  177. package/dist/workflows/run.js +554 -59
  178. package/dist/workflows/run.js.map +1 -1
  179. package/dist/workspace/agent-profiles.d.ts +30 -3
  180. package/dist/workspace/agent-profiles.js +39 -4
  181. package/dist/workspace/agent-profiles.js.map +1 -1
  182. package/dist/workspace/config.js +25 -4
  183. package/dist/workspace/config.js.map +1 -1
  184. package/dist/workspace/project-owner.d.ts +210 -0
  185. package/dist/workspace/project-owner.js +518 -0
  186. package/dist/workspace/project-owner.js.map +1 -0
  187. package/package.json +5 -3
  188. package/scripts/pi-leader-extension.ts +420 -0
  189. package/scripts/sync-readme.mjs +83 -2
  190. package/web/dist/assets/{alert-dialog-BVsVcbFy.js → alert-dialog-D6rP6d39.js} +1 -1
  191. package/web/dist/assets/arrow-down-DMOfFIzW.js +1 -0
  192. package/web/dist/assets/arrow-left-C4Ec2BUg.js +1 -0
  193. package/web/dist/assets/{centered-state-Djgly6Ni.js → centered-state-B0ZqrPaa.js} +12 -12
  194. package/web/dist/assets/chevron-right-mHJ-crvn.js +1 -0
  195. package/web/dist/assets/{collapsible-DZ4D-DHG.js → collapsible-DchEHgUM.js} +1 -1
  196. package/web/dist/assets/{commit-list-B-z1JL9G.js → commit-list-BpMim1Rv.js} +1 -1
  197. package/web/dist/assets/{compare-variants-D62m4j3B.js → compare-variants-D5ItQbAq.js} +1 -1
  198. package/web/dist/assets/{diff-BKew9rQX.js → diff-HqqZ_WDR.js} +2 -2
  199. package/web/dist/assets/{diff-stat-D0mc5V6Z.js → diff-stat-NsU-3ecr.js} +1 -1
  200. package/web/dist/assets/{diff-view-xpIcuLIC.js → diff-view-B7c11Xux.js} +1 -1
  201. package/web/dist/assets/{dropdown-menu-CEQziH7X.js → dropdown-menu-CtTpnyod.js} +1 -1
  202. package/web/dist/assets/{editable-title-BCX3T2BY.js → editable-title-BXtuGQry.js} +1 -1
  203. package/web/dist/assets/{ellipsis-vertical-ByVdDc4z.js → ellipsis-vertical-MHtGZSH6.js} +1 -1
  204. package/web/dist/assets/{file-xBSB5hXO.js → file-DKLw5SOb.js} +1 -1
  205. package/web/dist/assets/{folder-CldgbZ5A.js → folder-Drw6-ufH.js} +1 -1
  206. package/web/dist/assets/{git-toolbar-DJANqg7Y.js → git-toolbar-BQH8bORV.js} +1 -1
  207. package/web/dist/assets/github-ynnpKFCJ.js +1 -0
  208. package/web/dist/assets/{image-preview-Cv5mZr0n.js → image-preview-DLhMN_R9.js} +1 -1
  209. package/web/dist/assets/index-BEsrNdXt.js +29 -0
  210. package/web/dist/assets/index-Da8DOBau.css +2 -0
  211. package/web/dist/assets/{markdown-CgEEE2ND.js → markdown-q365eN6Y.js} +1 -1
  212. package/web/dist/assets/{new-task-form-1r2bgsdD.js → new-task-form-CW-hNIWQ.js} +1 -1
  213. package/web/dist/assets/{pill-q-x22_ej.js → pill-B_TFulXa.js} +1 -1
  214. package/web/dist/assets/{project-router-VDN4jQM8.js → project-router-CLPc-ImZ.js} +1 -1
  215. package/web/dist/assets/{prompt-templates-BmZtCe-L.js → prompt-templates-CLc4RxyO.js} +1 -1
  216. package/web/dist/assets/{repo-git-btG0sdbO.js → repo-git-Is7R6-Tj.js} +1 -1
  217. package/web/dist/assets/{run-diff-vguEMJzh.js → run-diff-DHMvDlVo.js} +2 -2
  218. package/web/dist/assets/run-header-DvZJbQ8I.js +1 -0
  219. package/web/dist/assets/{search-x-QmhaAbg5.js → search-x-Ce2MMetu.js} +1 -1
  220. package/web/dist/assets/{skill-empty-hint-BrzUwByZ.js → skill-empty-hint-C_xCSvzB.js} +1 -1
  221. package/web/dist/assets/skills-CivqD4si.js +1 -0
  222. package/web/dist/assets/{sparkles-CP8QDOgX.js → sparkles-BCqPoj5g.js} +1 -1
  223. package/web/dist/assets/{square-terminal-CGypshSD.js → square-terminal-ikj5g-oK.js} +1 -1
  224. package/web/dist/assets/{tab-link-D4lFzzI1.js → tab-link-VsEjFV1Y.js} +1 -1
  225. package/web/dist/assets/{task-changes-BtZId14z.js → task-changes-CH-E81-_.js} +1 -1
  226. package/web/dist/assets/{task-commits-BoeyJXrv.js → task-commits-DqfkMFxp.js} +1 -1
  227. package/web/dist/assets/{task-files-DIy2oUnj.js → task-files-D_nkHZ_a.js} +2 -2
  228. package/web/dist/assets/task-thread-bxQompxJ.js +9 -0
  229. package/web/dist/assets/{textarea-CyBCyKIk.js → textarea-xajuW1L_.js} +1 -1
  230. package/web/dist/assets/{thread-loading-Cuq2jchC.js → thread-loading-C3JQZbIE.js} +1 -1
  231. package/web/dist/assets/{trash-2-B6Dp3ZNs.js → trash-2-hJrBhpUD.js} +1 -1
  232. package/web/dist/assets/{triangle-alert-xBya8FUp.js → triangle-alert-D1u82y1a.js} +1 -1
  233. package/web/dist/assets/{upload-D6MXEB9p.js → upload-Bn6FBZ59.js} +1 -1
  234. package/web/dist/assets/{use-desktop-CPBwzQ7M.js → use-desktop-DOIMfHfY.js} +1 -1
  235. package/web/dist/assets/{use-submit-shortcut-JTpNzU-u.js → use-submit-shortcut-Dj4DHTdE.js} +1 -1
  236. package/web/dist/assets/utils-YwBpOoRN.js +64 -0
  237. package/web/dist/assets/{workflows-DG115scU.js → workflows-CTaAKz2m.js} +3 -3
  238. package/web/dist/assets/{zoomable-image-GTYwLtkS.js → zoomable-image-C1-6P3tg.js} +1 -1
  239. package/web/dist/index.html +23 -23
  240. package/web/dist/assets/arrow-down-CYgAlZtW.js +0 -1
  241. package/web/dist/assets/arrow-left-BOqE0a3k.js +0 -1
  242. package/web/dist/assets/chevron-right-Cc9BWnbN.js +0 -1
  243. package/web/dist/assets/github-D6vK0jFn.js +0 -1
  244. package/web/dist/assets/index-CkmR6h7s.css +0 -2
  245. package/web/dist/assets/index-DVDqF4bM.js +0 -9
  246. package/web/dist/assets/refresh-cw-DcNCj2nu.js +0 -1
  247. package/web/dist/assets/run-header-DM7MiKj9.js +0 -1
  248. package/web/dist/assets/skills-DMfxQtrD.js +0 -1
  249. package/web/dist/assets/task-thread-CbMPmrWq.js +0 -9
  250. package/web/dist/assets/utils--z9hRtl3.js +0 -64
@@ -0,0 +1,163 @@
1
+ import { realpathSync, statSync } from 'node:fs';
2
+ import { runIdParamSchema } from '../contract/index.js';
3
+ import { ProjectContextError, } from '../server/project-context.js';
4
+ import { PROJECT_ID_RE } from '../workspace/config.js';
5
+ import { RESERVED_PROJECT_IDS } from '../workspace/projects.js';
6
+ /** Fixed, identifier-free sentences — the rejection text is part of the surface (N-01). */
7
+ const MESSAGES = {
8
+ 'unknown-project': 'this MCP session is not bound to a registered xezar project; reconnect from the project folder',
9
+ 'missing-root': 'the project folder this MCP session is bound to is missing; restore it or reconnect from the project folder',
10
+ unavailable: 'the project this MCP session is bound to cannot be opened right now; the cockpit log has the details',
11
+ 'not-in-project': 'no such resource in this project',
12
+ };
13
+ /** The one error this module throws. Carries no client-supplied value. */
14
+ export class McpScopeError extends Error {
15
+ reason;
16
+ projectId;
17
+ constructor(reason,
18
+ /** The BOUND project's id — never a foreign or client-supplied one. */
19
+ projectId,
20
+ /** The underlying failure, for the service's own log only — never sent to a client. */
21
+ cause) {
22
+ super(MESSAGES[reason], cause === undefined ? undefined : { cause });
23
+ this.reason = reason;
24
+ this.projectId = projectId;
25
+ this.name = 'McpScopeError';
26
+ }
27
+ }
28
+ /**
29
+ * The live binding a connection holds. Construct only through `bindMcpSession`.
30
+ * Every read and write an MCP operation makes goes through `project()`, `run()` or
31
+ * `withRun()`; there is no other door to a `ProjectContext` for it.
32
+ */
33
+ export class McpSessionBinding {
34
+ // Runtime-private (`#`), not TypeScript `readonly`: a `readonly` property is still
35
+ // writable at runtime, and the binding must not be re-pointable by any code holding it.
36
+ #projectId;
37
+ #contexts;
38
+ #root;
39
+ constructor(projectId, contexts, root) {
40
+ this.#projectId = projectId;
41
+ this.#contexts = contexts;
42
+ this.#root = root;
43
+ }
44
+ /** The bound registry id. Fixed for the life of the session; there is no setter. */
45
+ get projectId() {
46
+ return this.#projectId;
47
+ }
48
+ /**
49
+ * Bind a session to `trustedProjectId` — the registry id of the project whose socket
50
+ * accepted this connection, as the SERVICE knows it. Never pass anything read from a
51
+ * client frame, a tool argument, a path or a URL here: that value is untrusted by
52
+ * definition, and binding to it is exactly the escape F-01 forbids.
53
+ *
54
+ * Fails closed with `McpScopeError`: `default` and every other reserved alias, a
55
+ * malformed id, an unregistered project and a missing root are all refused — there
56
+ * is no fallback to the boot project. The constructor is private, so this is the
57
+ * only way a binding comes into existence.
58
+ */
59
+ static async bind(contexts, trustedProjectId) {
60
+ if (typeof trustedProjectId !== 'string' ||
61
+ RESERVED_PROJECT_IDS.has(trustedProjectId) ||
62
+ !PROJECT_ID_RE.test(trustedProjectId)) {
63
+ // Not echoed: a malformed binding id is a caller bug, and the message stays fixed.
64
+ throw new McpScopeError('unknown-project', '');
65
+ }
66
+ const ctx = await resolveContext(contexts, trustedProjectId);
67
+ if (ctx.id !== trustedProjectId)
68
+ throw new McpScopeError('unknown-project', trustedProjectId);
69
+ const root = rootIdentity(ctx.root);
70
+ // The registry stores realpath'd roots. A root that no longer IS its own realpath
71
+ // was swapped for a symlink (or moved) after registration; pinning what it points
72
+ // at now would bind the session to whatever the link names — possibly another
73
+ // project's folder. Refuse instead of pinning.
74
+ if (!root || root.realpath !== ctx.root)
75
+ throw new McpScopeError('missing-root', trustedProjectId);
76
+ return new McpSessionBinding(trustedProjectId, contexts, root);
77
+ }
78
+ /**
79
+ * The bound project's context, re-validated on every call. Throws `McpScopeError`
80
+ * when the project is no longer registered, its folder is gone or replaced, or
81
+ * another process owns its data. Never returns any other project's context.
82
+ */
83
+ async project() {
84
+ const ctx = await resolveContext(this.#contexts, this.#projectId);
85
+ // Defence in depth: the map is keyed by id, so this holds today. A context whose
86
+ // id is not the bound id must never be handed to an operation, whatever changes.
87
+ if (ctx.id !== this.#projectId)
88
+ throw new McpScopeError('unknown-project', this.#projectId);
89
+ const current = rootIdentity(ctx.root);
90
+ if (!current ||
91
+ current.realpath !== this.#root.realpath ||
92
+ current.dev !== this.#root.dev ||
93
+ current.ino !== this.#root.ino) {
94
+ throw new McpScopeError('missing-root', this.#projectId);
95
+ }
96
+ return ctx;
97
+ }
98
+ /**
99
+ * One task of the BOUND project, by id. The id is client data: it is shape-checked
100
+ * against the contract's run-id rule and looked up in the bound project's own store
101
+ * only, so another project's id — or any string naming a project, alias, path or
102
+ * URL — reads as `not-in-project`, identically to an id that exists nowhere. The
103
+ * error never repeats the id.
104
+ */
105
+ async run(runId) {
106
+ const ctx = await this.project();
107
+ return lookupRun(ctx, runId, this.#projectId);
108
+ }
109
+ /**
110
+ * Gate for an operation on one task: resolves the task in the bound project first
111
+ * and only then runs `operation` with the bound context and that task. A foreign or
112
+ * unknown id is refused before `operation` is called, so it causes no side effect.
113
+ */
114
+ async withRun(runId, operation) {
115
+ const ctx = await this.project();
116
+ const run = lookupRun(ctx, runId, this.#projectId);
117
+ return operation(run, ctx);
118
+ }
119
+ }
120
+ /** Bind a session — see `McpSessionBinding.bind`. */
121
+ export function bindMcpSession(contexts, trustedProjectId) {
122
+ return McpSessionBinding.bind(contexts, trustedProjectId);
123
+ }
124
+ /** Resolve the bound id through the context map, translating its failures. Only ever
125
+ * called with the bound id — the one place a `ProjectContexts` lookup happens. */
126
+ async function resolveContext(contexts, projectId) {
127
+ try {
128
+ return await contexts.context(projectId);
129
+ }
130
+ catch (err) {
131
+ if (err instanceof ProjectContextError)
132
+ throw new McpScopeError(err.reason, projectId, err);
133
+ // Anything else — the writer error, or a raw fs error from building the context —
134
+ // can carry a data directory, a path or a pid in its message; none may reach a
135
+ // client. The original stays on `cause` for the service's own log.
136
+ throw new McpScopeError('unavailable', projectId, err);
137
+ }
138
+ }
139
+ function lookupRun(ctx, runId, projectId) {
140
+ // Dot segments pass the character class but are never run ids; refuse them here so
141
+ // no caller can build a path or URL out of one.
142
+ if (typeof runId !== 'string' || runId === '.' || runId === '..' || !runIdParamSchema.safeParse({ id: runId }).success) {
143
+ throw new McpScopeError('not-in-project', projectId);
144
+ }
145
+ const run = ctx.store.getRun(runId);
146
+ if (!run)
147
+ throw new McpScopeError('not-in-project', projectId);
148
+ return run;
149
+ }
150
+ /** Realpath + device/inode of a directory, or null when it is gone or not a directory. */
151
+ function rootIdentity(root) {
152
+ try {
153
+ const realpath = realpathSync(root);
154
+ const stats = statSync(realpath);
155
+ if (!stats.isDirectory())
156
+ return null;
157
+ return { realpath, dev: stats.dev, ino: stats.ino };
158
+ }
159
+ catch {
160
+ return null;
161
+ }
162
+ }
163
+ //# sourceMappingURL=session-binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-binding.js","sourceRoot":"","sources":["../../src/mcp/session-binding.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,145 @@
1
+ import { type ExecutionFailed, type McpVersionedResourceRef, type StaleVersionRejection, type VersionedMutationDone } from '../contract/index.js';
2
+ import type { RunRecord, RunStore } from '../runs/store.ts';
3
+ /**
4
+ * Stale-write rejection for leader mutations (#100) — the mechanism D-06 chose for N-03
5
+ * (`docs/features/mcp-server/mcp-d06-versioning-idempotency-audit-decision.md` § 4). N-03 is an
6
+ * agreed outcome and nothing here makes it optional: a leader mutation based on state a human has
7
+ * changed since is REFUSED, before any effect. It is never merged, never retried with the fresh
8
+ * token, and never offered to anyone for approval (§ 4.4 rule 3). There is no switch that turns
9
+ * the check off, and there must never be one.
10
+ *
11
+ * Two pieces, deliberately tool-agnostic — this is infrastructure a tool calls, not a tool:
12
+ *
13
+ * - `versionToken` computes `rev1:<kind>:<id>:<seq>:<digest12>` (§ 4.2) from a resource's
14
+ * DECISION PROJECTION (§ 4.3): the fields the server reads to judge a mutation plus the fields
15
+ * it writes, and nothing that moves on its own. A token that included a token counter would go
16
+ * stale within seconds of an agent emitting anything, and a check that always fails is an outage
17
+ * people learn to switch off.
18
+ * - `guardedMutation` is the compare-and-swap (§ 4.5). It reads the current token, compares, and
19
+ * applies — all in ONE synchronous stretch. Node runs one stretch of synchronous code at a time,
20
+ * and `ownProjectData` (`runs/project-writer.ts`) keeps a project's state to one writing process,
21
+ * so no other write can land between the check and the effect. That is also why `apply` must
22
+ * not be async: an `await` inside it would open exactly the gap the check exists to close.
23
+ *
24
+ * N-02: the effect `apply` performs is the SAME store call the cockpit's route makes — this module
25
+ * wraps the shared service, it never writes JSON or NDJSON itself. The cockpit keeps calling those
26
+ * methods exactly as it does today — requiring a token there would break every existing client —
27
+ * and the leader's write reaches the very same methods, behind this check.
28
+ *
29
+ * U-M05: the result tells the three endings apart — `done`, `conflict` (rejected, NOTHING applied)
30
+ * and `failed` (the effect ran and failed, so the state may have moved). See
31
+ * `packages/contract/src/mcp-versioning.ts` for the shapes.
32
+ */
33
+ /** One resource, as the check sees it. Build it from live state on every call — never cache one. */
34
+ export interface VersionedSnapshot {
35
+ readonly ref: McpVersionedResourceRef;
36
+ /**
37
+ * For a run-scoped resource, its highest event `seq`; absent for a resource with no event stream
38
+ * (a config file, an automation). This half catches A→B→A — a human who changes something and
39
+ * changes it back leaves the projection byte-identical, but not the event counter, because `seq`
40
+ * is monotonic and never reused, even across a restart (`RunStore.rehydrateSeq`).
41
+ */
42
+ readonly seq?: number;
43
+ /** The decision projection. Canonicalized and digested; its content never leaves this module. */
44
+ readonly projection: unknown;
45
+ }
46
+ /**
47
+ * JSON with object keys sorted by UTF-16 code unit at every level, arrays kept in order, no
48
+ * whitespace, and `undefined`-valued keys dropped — D-06 § 5.4 step 4, so a token does not depend
49
+ * on the order in which a record's keys happened to be assigned.
50
+ *
51
+ * Serialized by hand rather than by rebuilding an object for `JSON.stringify`: an engine iterates
52
+ * integer-like keys (`"10"`, `"9"`) numerically whatever order they were inserted in, which is not
53
+ * code-unit order.
54
+ */
55
+ export declare function canonicalJson(value: unknown): string;
56
+ /** The token a read hands out and a mutation must echo back. Opaque to every client. */
57
+ export declare function versionToken(snapshot: VersionedSnapshot): string;
58
+ /** What `guardedMutation` answers when the change went through: the contract shape plus the value
59
+ * `apply` returned, for the calling tool to project onto its own result. */
60
+ export type GuardedMutationDone<T> = VersionedMutationDone & {
61
+ readonly value: T;
62
+ };
63
+ export type GuardedMutationResult<T> = GuardedMutationDone<T> | StaleVersionRejection | ExecutionFailed;
64
+ /**
65
+ * `apply` has to finish inside the critical section, so an async one is a compile error — this
66
+ * rest parameter demands an impossible extra argument when `apply` returns a promise — and, for a
67
+ * caller that got past the types, a `failed` result at run time.
68
+ */
69
+ type SyncOnly<T> = [T] extends [never] ? [] : [T] extends [PromiseLike<unknown>] ? [asyncApplyIsNotAllowed: never] : [];
70
+ export interface GuardedMutationInput<T> {
71
+ readonly resource: McpVersionedResourceRef;
72
+ /**
73
+ * The token the leader read. A missing one is REFUSED, never waved through: "no token" and "token
74
+ * matches" must not share a branch (D-06 § 4.4 rule 4). The tool schema already requires it; this
75
+ * is the same guarantee for a caller that bypassed the schema.
76
+ */
77
+ readonly expectedVersion: string | undefined;
78
+ /** Reads the resource as it is NOW; `undefined` when it no longer exists. */
79
+ readonly read: () => VersionedSnapshot | undefined;
80
+ /** The effect: the shared store call. Synchronous — see `SyncOnly`. */
81
+ readonly apply: () => T;
82
+ }
83
+ /**
84
+ * Check the leader's token against the resource's current one and apply the effect only on a match.
85
+ *
86
+ * Never throws for a stale token or a failed effect — both are ANSWERS the leader must reason
87
+ * about, which is why D-05 returns them as ordinary results rather than tool errors.
88
+ */
89
+ export declare function guardedMutation<T>(input: GuardedMutationInput<T>, ..._sync: SyncOnly<T>): GuardedMutationResult<T>;
90
+ export declare const RUN_RESOURCE_KIND = "run";
91
+ /**
92
+ * A run's decision projection (D-06 § 4.3).
93
+ *
94
+ * D-06 proposed `status`, `archived`, `pinned`, `title`, `titleOrigin`, `autoResumeAt`,
95
+ * `queuedMessages[].id`, `steps[].id`/`steps[].status`, `branch` and `workflow`, and left the list
96
+ * to be reviewed against `runRecordSchema`. Reviewed against it, two additions follow from the
97
+ * rule itself — every field a leader mutation of a run WRITES:
98
+ *
99
+ * - `task`: a queued run's brief stays editable until the scheduler picks it up, and a human
100
+ * editing it is the "edits its brief" case § 4.1 names;
101
+ * - `queuedMessages[].text`, not only the id: a queued follow-up is editable in the same way.
102
+ *
103
+ * Left out on purpose: telemetry (`tokensUsed`, `inputTokens`, `outputTokens`, `costUsd`,
104
+ * `peakRssBytes`, `peakProcCount`, `diffStat`, the per-step counters), presentation (`seenAt`, and
105
+ * the `archivedAt`/`pinnedAt` stamps whose flags ARE covered), and everything derived by the server
106
+ * for display (`titleSummary`, the referenced PR/issue tiers).
107
+ */
108
+ export declare function runDecisionProjection(run: RunRecord): unknown;
109
+ /** A run as the check sees it right now, or `undefined` when there is no such run. */
110
+ export declare function runVersionSnapshot(store: RunStore, runId: string): VersionedSnapshot | undefined;
111
+ /** The `version` a read of this run hands the leader, or `undefined` when there is no such run. */
112
+ export declare function runVersion(store: RunStore, runId: string): string | undefined;
113
+ /**
114
+ * The route half of the check (#250): the rejection a run-mutating route answers with, or
115
+ * `undefined` when its effect may go ahead.
116
+ *
117
+ * WHY THE ROUTE, NOT THE TOOL. An MCP tool reaches the services through in-process HTTP dispatch
118
+ * (N-02, `service-adapter.ts`), which is async, so a tool cannot hold `guardedMutation`'s
119
+ * synchronous critical section around the store call — only the route handler can. So the route
120
+ * accepts an OPTIONAL `expectedVersion` and calls this RIGHT BEFORE its effect, with nothing but
121
+ * synchronous code between the two: the check and the store call then run in one stretch, exactly
122
+ * as `guardedMutation` would run them (§ 4.5). A handler must never put an `await` between this
123
+ * call and its effect; each call site says so.
124
+ *
125
+ * ABSENT MEANS "NOT ASKED", ON PURPOSE. `undefined` is the cockpit, which has never sent a token and
126
+ * keeps its behaviour byte-for-byte. The leader can never reach that branch: every MCP tool that
127
+ * mutates a run REQUIRES `expectedVersion` in its own schema (D-06 § 4.4 rule 4), and the tool
128
+ * tests pin that each action sends it. Present-but-wrong — empty, foreign, unknown tag, another
129
+ * run's — is always a rejection, never a pass.
130
+ *
131
+ * AN ASYNC EFFECT (a worktree removal, a push, `gh pr create`) cannot sit inside a synchronous
132
+ * stretch at all. Those routes call this right before the effect STARTS, so a stale decision never
133
+ * begins; a change that lands while the effect is already running is outside what a version check
134
+ * can see, and each such effect keeps its own guards (`isActive`, the worktree checks).
135
+ */
136
+ export declare function staleRunWrite(store: RunStore, runId: string, expectedVersion: string | undefined): StaleVersionRejection | undefined;
137
+ /** The rejection a route answered with, when the body is one — how a tool tells a stale-version
138
+ * 409 from every other 409 (`run already started`, `session closed`, …). */
139
+ export declare function staleRejectionIn(body: unknown): StaleVersionRejection | undefined;
140
+ /**
141
+ * `guardedMutation` for one run: `apply` receives the CURRENT record and performs the shared store
142
+ * call on it (`setPinned`, `updateRun`, …). It is never called when the token is stale.
143
+ */
144
+ export declare function guardedRunMutation<T>(store: RunStore, runId: string, expectedVersion: string | undefined, apply: (run: RunRecord) => T, ..._sync: SyncOnly<T>): GuardedMutationResult<T>;
145
+ export {};
@@ -0,0 +1,219 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { MCP_EXECUTION_FAILED_GUIDANCE, MCP_STALE_VERSION_GUIDANCE, MCP_VERSION_TOKEN_TAG, staleVersionRejectionSchema, } from '../contract/index.js';
3
+ /**
4
+ * JSON with object keys sorted by UTF-16 code unit at every level, arrays kept in order, no
5
+ * whitespace, and `undefined`-valued keys dropped — D-06 § 5.4 step 4, so a token does not depend
6
+ * on the order in which a record's keys happened to be assigned.
7
+ *
8
+ * Serialized by hand rather than by rebuilding an object for `JSON.stringify`: an engine iterates
9
+ * integer-like keys (`"10"`, `"9"`) numerically whatever order they were inserted in, which is not
10
+ * code-unit order.
11
+ */
12
+ export function canonicalJson(value) {
13
+ if (Array.isArray(value)) {
14
+ return `[${value.map((item) => (item === undefined ? 'null' : canonicalJson(item))).join(',')}]`;
15
+ }
16
+ if (value !== null && typeof value === 'object') {
17
+ const parts = [];
18
+ for (const key of Object.keys(value).sort()) {
19
+ const item = value[key];
20
+ if (item !== undefined)
21
+ parts.push(`${JSON.stringify(key)}:${canonicalJson(item)}`);
22
+ }
23
+ return `{${parts.join(',')}}`;
24
+ }
25
+ return JSON.stringify(value) ?? 'null';
26
+ }
27
+ /** The token a read hands out and a mutation must echo back. Opaque to every client. */
28
+ export function versionToken(snapshot) {
29
+ const digest = createHash('sha256').update(canonicalJson(snapshot.projection)).digest('hex').slice(0, 12);
30
+ const seq = snapshot.seq === undefined ? '-' : String(snapshot.seq);
31
+ return `${MCP_VERSION_TOKEN_TAG}:${snapshot.ref.kind}:${snapshot.ref.id}:${seq}:${digest}`;
32
+ }
33
+ /**
34
+ * Check the leader's token against the resource's current one and apply the effect only on a match.
35
+ *
36
+ * Never throws for a stale token or a failed effect — both are ANSWERS the leader must reason
37
+ * about, which is why D-05 returns them as ordinary results rather than tool errors.
38
+ */
39
+ export function guardedMutation(input, ..._sync) {
40
+ const before = input.read();
41
+ const currentVersion = before ? versionToken(before) : undefined;
42
+ // Verbatim comparison is the whole rule, and it covers D-06 § 4.4 rule 5 without parsing: an
43
+ // unparseable, foreign or unknown-tag token can never equal a token this server just minted.
44
+ if (!input.expectedVersion || currentVersion === undefined || input.expectedVersion !== currentVersion) {
45
+ return staleVersionRejection(input.resource, currentVersion);
46
+ }
47
+ let value;
48
+ try {
49
+ value = input.apply();
50
+ }
51
+ catch (error) {
52
+ return executionFailed(input.resource, error);
53
+ }
54
+ if (isThenable(value)) {
55
+ // The effect escaped the critical section. Swallow a later rejection so it cannot crash the
56
+ // process as an unhandled one, and report what is actually known: it ran, and it is not done.
57
+ value.then(undefined, () => undefined);
58
+ return executionFailed(input.resource, new Error('apply returned a promise; a guarded mutation must be synchronous'));
59
+ }
60
+ const after = input.read();
61
+ return {
62
+ status: 'done',
63
+ resource: input.resource,
64
+ ...(after ? { version: versionToken(after) } : {}),
65
+ value,
66
+ };
67
+ }
68
+ function staleVersionRejection(resource, currentVersion) {
69
+ return {
70
+ status: 'conflict',
71
+ applied: false,
72
+ error: 'stale_version',
73
+ resource,
74
+ // Spread, not `currentVersion: undefined` — a key that is always present in the type but
75
+ // dropped by `JSON.stringify` is exactly the drift the contract-parity rule warns about.
76
+ ...(currentVersion === undefined ? {} : { currentVersion }),
77
+ changedSince: true,
78
+ guidance: MCP_STALE_VERSION_GUIDANCE,
79
+ };
80
+ }
81
+ function executionFailed(resource, error) {
82
+ // The message stays in the cockpit's own log: it can quote a path or a command line, and nothing
83
+ // secret may reach a tool response (F-15).
84
+ const message = error instanceof Error ? error.message : String(error);
85
+ console.warn(`[xez] MCP mutation of ${resource.kind} ${resource.id} failed after execution: ${message}`);
86
+ return {
87
+ status: 'failed',
88
+ executed: true,
89
+ error: 'execution_failed',
90
+ resource,
91
+ guidance: MCP_EXECUTION_FAILED_GUIDANCE,
92
+ };
93
+ }
94
+ function isThenable(value) {
95
+ return (value !== null &&
96
+ (typeof value === 'object' || typeof value === 'function') &&
97
+ typeof value.then === 'function');
98
+ }
99
+ // ---- runs -------------------------------------------------------------------------------------
100
+ export const RUN_RESOURCE_KIND = 'run';
101
+ /**
102
+ * A run's decision projection (D-06 § 4.3).
103
+ *
104
+ * D-06 proposed `status`, `archived`, `pinned`, `title`, `titleOrigin`, `autoResumeAt`,
105
+ * `queuedMessages[].id`, `steps[].id`/`steps[].status`, `branch` and `workflow`, and left the list
106
+ * to be reviewed against `runRecordSchema`. Reviewed against it, two additions follow from the
107
+ * rule itself — every field a leader mutation of a run WRITES:
108
+ *
109
+ * - `task`: a queued run's brief stays editable until the scheduler picks it up, and a human
110
+ * editing it is the "edits its brief" case § 4.1 names;
111
+ * - `queuedMessages[].text`, not only the id: a queued follow-up is editable in the same way.
112
+ *
113
+ * Left out on purpose: telemetry (`tokensUsed`, `inputTokens`, `outputTokens`, `costUsd`,
114
+ * `peakRssBytes`, `peakProcCount`, `diffStat`, the per-step counters), presentation (`seenAt`, and
115
+ * the `archivedAt`/`pinnedAt` stamps whose flags ARE covered), and everything derived by the server
116
+ * for display (`titleSummary`, the referenced PR/issue tiers).
117
+ */
118
+ export function runDecisionProjection(run) {
119
+ return {
120
+ status: run.status,
121
+ archived: run.archived,
122
+ pinned: run.pinned,
123
+ title: run.title,
124
+ titleOrigin: run.titleOrigin,
125
+ autoResumeAt: run.autoResumeAt,
126
+ task: run.task,
127
+ queuedMessages: run.queuedMessages?.map((message) => ({ id: message.id, text: message.text })),
128
+ steps: run.steps.map((step) => ({ id: step.id, status: step.status })),
129
+ branch: run.branch,
130
+ workflow: run.workflow,
131
+ };
132
+ }
133
+ /**
134
+ * The run's highest event `seq` on disk.
135
+ *
136
+ * D-06 names the highest ALLOCATED seq. The allocation counter is private to the store, so this
137
+ * reads the persisted one: it moves on every persisted event and never goes backwards, which is the
138
+ * property the token needs. The only allocations it cannot see are `emitEphemeral`'s, which are
139
+ * presentation-only and never persisted — § 4.3 would exclude them anyway.
140
+ */
141
+ function highestEventSeq(store, runId) {
142
+ let max = 0;
143
+ for (const event of store.readEvents(runId)) {
144
+ if (typeof event.seq === 'number' && event.seq > max)
145
+ max = event.seq;
146
+ }
147
+ return max;
148
+ }
149
+ /** A run as the check sees it right now, or `undefined` when there is no such run. */
150
+ export function runVersionSnapshot(store, runId) {
151
+ const run = store.getRun(runId);
152
+ if (!run)
153
+ return undefined;
154
+ return {
155
+ ref: { kind: RUN_RESOURCE_KIND, id: runId },
156
+ seq: highestEventSeq(store, runId),
157
+ projection: runDecisionProjection(run),
158
+ };
159
+ }
160
+ /** The `version` a read of this run hands the leader, or `undefined` when there is no such run. */
161
+ export function runVersion(store, runId) {
162
+ const snapshot = runVersionSnapshot(store, runId);
163
+ return snapshot ? versionToken(snapshot) : undefined;
164
+ }
165
+ /**
166
+ * The route half of the check (#250): the rejection a run-mutating route answers with, or
167
+ * `undefined` when its effect may go ahead.
168
+ *
169
+ * WHY THE ROUTE, NOT THE TOOL. An MCP tool reaches the services through in-process HTTP dispatch
170
+ * (N-02, `service-adapter.ts`), which is async, so a tool cannot hold `guardedMutation`'s
171
+ * synchronous critical section around the store call — only the route handler can. So the route
172
+ * accepts an OPTIONAL `expectedVersion` and calls this RIGHT BEFORE its effect, with nothing but
173
+ * synchronous code between the two: the check and the store call then run in one stretch, exactly
174
+ * as `guardedMutation` would run them (§ 4.5). A handler must never put an `await` between this
175
+ * call and its effect; each call site says so.
176
+ *
177
+ * ABSENT MEANS "NOT ASKED", ON PURPOSE. `undefined` is the cockpit, which has never sent a token and
178
+ * keeps its behaviour byte-for-byte. The leader can never reach that branch: every MCP tool that
179
+ * mutates a run REQUIRES `expectedVersion` in its own schema (D-06 § 4.4 rule 4), and the tool
180
+ * tests pin that each action sends it. Present-but-wrong — empty, foreign, unknown tag, another
181
+ * run's — is always a rejection, never a pass.
182
+ *
183
+ * AN ASYNC EFFECT (a worktree removal, a push, `gh pr create`) cannot sit inside a synchronous
184
+ * stretch at all. Those routes call this right before the effect STARTS, so a stale decision never
185
+ * begins; a change that lands while the effect is already running is outside what a version check
186
+ * can see, and each such effect keeps its own guards (`isActive`, the worktree checks).
187
+ */
188
+ export function staleRunWrite(store, runId, expectedVersion) {
189
+ if (expectedVersion === undefined)
190
+ return undefined;
191
+ const checked = guardedRunMutation(store, runId, expectedVersion, () => undefined);
192
+ return checked.status === 'conflict' ? checked : undefined;
193
+ }
194
+ /** The rejection a route answered with, when the body is one — how a tool tells a stale-version
195
+ * 409 from every other 409 (`run already started`, `session closed`, …). */
196
+ export function staleRejectionIn(body) {
197
+ const parsed = staleVersionRejectionSchema.safeParse(body);
198
+ return parsed.success ? parsed.data : undefined;
199
+ }
200
+ /**
201
+ * `guardedMutation` for one run: `apply` receives the CURRENT record and performs the shared store
202
+ * call on it (`setPinned`, `updateRun`, …). It is never called when the token is stale.
203
+ */
204
+ export function guardedRunMutation(store, runId, expectedVersion, apply, ..._sync) {
205
+ return guardedMutation({
206
+ resource: { kind: RUN_RESOURCE_KIND, id: runId },
207
+ expectedVersion,
208
+ read: () => runVersionSnapshot(store, runId),
209
+ apply: () => {
210
+ const run = store.getRun(runId);
211
+ // Unreachable after a passing check — the read above found it in this same synchronous
212
+ // stretch — but a missing run must fail loudly rather than hand `apply` an undefined.
213
+ if (!run)
214
+ throw new Error(`run ${runId} vanished inside the critical section`);
215
+ return apply(run);
216
+ },
217
+ }, ..._sync);
218
+ }
219
+ //# sourceMappingURL=stale-write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stale-write.js","sourceRoot":"","sources":["../../src/mcp/stale-write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,6BAA6B,EAC7B,0BAA0B,EAC1B,qBAAqB,EACrB,2BAA2B,GAK5B,MAAM,wBAAwB,CAAC;AAgDhC;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACnG,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAI,KAAiC,CAAC,GAAG,CAAC,CAAC;YACrD,IAAI,IAAI,KAAK,SAAS;gBAAE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtF,CAAC;QACD,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAChC,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC;AACzC,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,YAAY,CAAC,QAA2B;IACtD,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1G,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACpE,OAAO,GAAG,qBAAqB,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,IAAI,MAAM,EAAE,CAAC;AAC7F,CAAC;AAiCD;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAI,KAA8B,EAAE,GAAG,KAAkB;IACtF,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC5B,MAAM,cAAc,GAAG,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACjE,6FAA6F;IAC7F,6FAA6F;IAC7F,IAAI,CAAC,KAAK,CAAC,eAAe,IAAI,cAAc,KAAK,SAAS,IAAI,KAAK,CAAC,eAAe,KAAK,cAAc,EAAE,CAAC;QACvG,OAAO,qBAAqB,CAAC,KAAK,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,KAAQ,CAAC;IACb,IAAI,CAAC;QACH,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,eAAe,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACtB,4FAA4F;QAC5F,8FAA8F;QAC9F,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACvC,OAAO,eAAe,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC,CAAC;IACxH,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC3B,OAAO;QACL,MAAM,EAAE,MAAM;QACd,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,KAAK;KACN,CAAC;AACJ,CAAC;AAED,SAAS,qBAAqB,CAAC,QAAiC,EAAE,cAAkC;IAClG,OAAO;QACL,MAAM,EAAE,UAAU;QAClB,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,eAAe;QACtB,QAAQ;QACR,yFAAyF;QACzF,yFAAyF;QACzF,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC;QAC3D,YAAY,EAAE,IAAI;QAClB,QAAQ,EAAE,0BAA0B;KACrC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,QAAiC,EAAE,KAAc;IACxE,iGAAiG;IACjG,2CAA2C;IAC3C,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,IAAI,CAAC,yBAAyB,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,EAAE,4BAA4B,OAAO,EAAE,CAAC,CAAC;IACzG,OAAO;QACL,MAAM,EAAE,QAAQ;QAChB,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,kBAAkB;QACzB,QAAQ;QACR,QAAQ,EAAE,6BAA6B;KACxC,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,CACL,KAAK,KAAK,IAAI;QACd,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,UAAU,CAAC;QAC1D,OAAQ,KAA4B,CAAC,IAAI,KAAK,UAAU,CACzD,CAAC;AACJ,CAAC;AAED,kGAAkG;AAElG,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEvC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAc;IAClD,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,cAAc,EAAE,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9F,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACtE,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ;KACvB,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,eAAe,CAAC,KAAe,EAAE,KAAa;IACrD,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5C,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,GAAG,GAAG,GAAG;YAAE,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;IACxE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,kBAAkB,CAAC,KAAe,EAAE,KAAa;IAC/D,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,OAAO;QACL,GAAG,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,EAAE,KAAK,EAAE;QAC3C,GAAG,EAAE,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC;QAClC,UAAU,EAAE,qBAAqB,CAAC,GAAG,CAAC;KACvC,CAAC;AACJ,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,UAAU,CAAC,KAAe,EAAE,KAAa;IACvD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAClD,OAAO,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,aAAa,CAAC,KAAe,EAAE,KAAa,EAAE,eAAmC;IAC/F,IAAI,eAAe,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACpD,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACnF,OAAO,OAAO,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7D,CAAC;AAED;6EAC6E;AAC7E,MAAM,UAAU,gBAAgB,CAAC,IAAa;IAC5C,MAAM,MAAM,GAAG,2BAA2B,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC3D,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAClD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAe,EACf,KAAa,EACb,eAAmC,EACnC,KAA4B,EAC5B,GAAG,KAAkB;IAErB,OAAO,eAAe,CAAI;QACxB,QAAQ,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,EAAE,KAAK,EAAE;QAChD,eAAe;QACf,IAAI,EAAE,GAAG,EAAE,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC;QAC5C,KAAK,EAAE,GAAG,EAAE;YACV,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAChC,uFAAuF;YACvF,sFAAsF;YACtF,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,KAAK,CAAC,OAAO,KAAK,uCAAuC,CAAC,CAAC;YAC/E,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;KACF,EAAE,GAAG,KAAK,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,48 @@
1
+ import { z } from 'zod';
2
+ import type { McpToolResult } from './ipc.ts';
3
+ export type { McpToolResult } from './ipc.ts';
4
+ /**
5
+ * What a tool runs with. Tools execute INSIDE the running xezar service, never in
6
+ * the bridge: the bridge only lists them and forwards `tools/call` over the
7
+ * project's socket (D-01 § 3.5–3.6 — the bridge is a transport adapter, not
8
+ * another business engine). So `project` is the project that socket belongs to,
9
+ * and no argument a client sends can change it (D-01 § 1.5, F-01).
10
+ *
11
+ * Deliberately small. The session binding (#87) and the shared business-service
12
+ * adapter (#89) widen it; widening an interface is additive for every tool.
13
+ */
14
+ export interface McpToolContext {
15
+ readonly project: {
16
+ readonly id: string;
17
+ readonly name: string;
18
+ readonly root: string;
19
+ };
20
+ readonly xezarVersion: string;
21
+ }
22
+ /**
23
+ * One MCP tool. `inputSchema` is the only definition of its arguments: the JSON
24
+ * Schema clients see is derived from it, and the service validates every call
25
+ * against it before `call` runs. Results follow D-05: the text block is
26
+ * authoritative and `structuredContent` may only add to it.
27
+ */
28
+ export interface McpTool<S extends z.ZodObject = z.ZodObject> {
29
+ /** `snake_case`, unique across the registry — `tools/index.test.ts` enforces both. */
30
+ readonly name: string;
31
+ readonly title?: string;
32
+ readonly description: string;
33
+ readonly inputSchema: S;
34
+ readonly annotations?: {
35
+ readonly readOnlyHint?: boolean;
36
+ readonly destructiveHint?: boolean;
37
+ readonly idempotentHint?: boolean;
38
+ readonly openWorldHint?: boolean;
39
+ };
40
+ call(args: z.output<S>, ctx: McpToolContext): Promise<McpToolResult>;
41
+ }
42
+ /** Keeps a tool's argument type inferred from its own schema. */
43
+ export declare function defineTool<S extends z.ZodObject>(tool: McpTool<S>): McpTool;
44
+ export declare const TOOL_NAME_RE: RegExp;
45
+ export declare function textResult(text: string, structuredContent?: Record<string, unknown>): McpToolResult;
46
+ export declare function errorResult(text: string, structuredContent?: Record<string, unknown>): McpToolResult;
47
+ /** The tool as `tools/list` sends it. */
48
+ export declare function toolListing(tool: McpTool): Record<string, unknown>;
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ /** Keeps a tool's argument type inferred from its own schema. */
3
+ export function defineTool(tool) {
4
+ return tool;
5
+ }
6
+ export const TOOL_NAME_RE = /^[a-z][a-z0-9_]{0,63}$/;
7
+ export function textResult(text, structuredContent) {
8
+ return { content: [{ type: 'text', text }], ...(structuredContent ? { structuredContent } : {}) };
9
+ }
10
+ export function errorResult(text, structuredContent) {
11
+ return { ...textResult(text, structuredContent), isError: true };
12
+ }
13
+ /** The tool as `tools/list` sends it. */
14
+ export function toolListing(tool) {
15
+ const { $schema: _dialect, ...inputSchema } = z.toJSONSchema(tool.inputSchema, { io: 'input' });
16
+ return {
17
+ name: tool.name,
18
+ ...(tool.title ? { title: tool.title } : {}),
19
+ description: tool.description,
20
+ inputSchema,
21
+ ...(tool.annotations ? { annotations: tool.annotations } : {}),
22
+ };
23
+ }
24
+ //# sourceMappingURL=tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.js","sourceRoot":"","sources":["../../src/mcp/tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAyCxB,iEAAiE;AACjE,MAAM,UAAU,UAAU,CAAwB,IAAgB;IAChE,OAAO,IAA0B,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,wBAAwB,CAAC;AAErD,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,iBAA2C;IAClF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACpG,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,iBAA2C;IACnF,OAAO,EAAE,GAAG,UAAU,CAAC,IAAI,EAAE,iBAAiB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACnE,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,WAAW,CAAC,IAAa;IACvC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,WAAW,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAChG,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,WAAW;QACX,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/D,CAAC;AACJ,CAAC"}