@ashlr/hub 3.4.0 → 3.5.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 (213) hide show
  1. package/CHANGELOG.md +56 -1
  2. package/README.md +3 -0
  3. package/dist/build-identity.json +2 -2
  4. package/dist/cli/index.js +11 -0
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/cli/local-runtime.js +40 -9
  7. package/dist/cli/local-runtime.js.map +1 -1
  8. package/dist/core/local-eval/classify.d.ts +57 -0
  9. package/dist/core/local-eval/classify.js +121 -0
  10. package/dist/core/local-eval/classify.js.map +1 -0
  11. package/dist/core/local-eval/configuration.d.ts +37 -0
  12. package/dist/core/local-eval/configuration.js +171 -0
  13. package/dist/core/local-eval/configuration.js.map +1 -0
  14. package/dist/core/local-eval/index.d.ts +15 -0
  15. package/dist/core/local-eval/index.js +16 -0
  16. package/dist/core/local-eval/index.js.map +1 -0
  17. package/dist/core/local-eval/main.d.ts +31 -0
  18. package/dist/core/local-eval/main.js +174 -0
  19. package/dist/core/local-eval/main.js.map +1 -0
  20. package/dist/core/local-eval/report.d.ts +25 -0
  21. package/dist/core/local-eval/report.js +157 -0
  22. package/dist/core/local-eval/report.js.map +1 -0
  23. package/dist/core/local-eval/runner.d.ts +55 -0
  24. package/dist/core/local-eval/runner.js +290 -0
  25. package/dist/core/local-eval/runner.js.map +1 -0
  26. package/dist/core/local-eval/tasks.d.ts +22 -0
  27. package/dist/core/local-eval/tasks.js +303 -0
  28. package/dist/core/local-eval/tasks.js.map +1 -0
  29. package/dist/core/local-eval/trace.d.ts +67 -0
  30. package/dist/core/local-eval/trace.js +309 -0
  31. package/dist/core/local-eval/trace.js.map +1 -0
  32. package/dist/core/local-eval/types.d.ts +262 -0
  33. package/dist/core/local-eval/types.js +23 -0
  34. package/dist/core/local-eval/types.js.map +1 -0
  35. package/dist/core/local-runtime/llama/anthropic-proxy-host.d.ts +68 -0
  36. package/dist/core/local-runtime/llama/anthropic-proxy-host.js +120 -0
  37. package/dist/core/local-runtime/llama/anthropic-proxy-host.js.map +1 -0
  38. package/dist/core/local-runtime/llama/anthropic-proxy-process.d.ts +15 -0
  39. package/dist/core/local-runtime/llama/anthropic-proxy-process.js +21 -0
  40. package/dist/core/local-runtime/llama/anthropic-proxy-process.js.map +1 -0
  41. package/dist/core/local-runtime/llama/anthropic-proxy.d.ts +2 -1
  42. package/dist/core/local-runtime/llama/anthropic-proxy.js +14 -6
  43. package/dist/core/local-runtime/llama/anthropic-proxy.js.map +1 -1
  44. package/dist/core/local-runtime/llama/anthropic-shim.d.ts +33 -0
  45. package/dist/core/local-runtime/llama/anthropic-shim.js +59 -0
  46. package/dist/core/local-runtime/llama/anthropic-shim.js.map +1 -1
  47. package/dist/core/local-runtime/llama/config.d.ts +101 -6
  48. package/dist/core/local-runtime/llama/config.js +174 -6
  49. package/dist/core/local-runtime/llama/config.js.map +1 -1
  50. package/dist/core/local-runtime/llama/index.d.ts +15 -0
  51. package/dist/core/local-runtime/llama/index.js +13 -0
  52. package/dist/core/local-runtime/llama/index.js.map +1 -1
  53. package/dist/core/local-runtime/llama/paths.d.ts +17 -0
  54. package/dist/core/local-runtime/llama/paths.js +25 -0
  55. package/dist/core/local-runtime/llama/paths.js.map +1 -1
  56. package/dist/core/local-runtime/llama/proxy-invocation.d.ts +101 -0
  57. package/dist/core/local-runtime/llama/proxy-invocation.js +134 -0
  58. package/dist/core/local-runtime/llama/proxy-invocation.js.map +1 -0
  59. package/dist/core/local-runtime/llama/proxy-launchd.d.ts +83 -0
  60. package/dist/core/local-runtime/llama/proxy-launchd.js +216 -0
  61. package/dist/core/local-runtime/llama/proxy-launchd.js.map +1 -0
  62. package/dist/core/local-runtime/llama/shapes.d.ts +2 -1
  63. package/dist/core/local-runtime/llama/shapes.js +2 -1
  64. package/dist/core/local-runtime/llama/shapes.js.map +1 -1
  65. package/dist/core/verse/adapters/claude.js +16 -0
  66. package/dist/core/verse/adapters/claude.js.map +1 -1
  67. package/dist/core/verse/adapters/codex.js +60 -2
  68. package/dist/core/verse/adapters/codex.js.map +1 -1
  69. package/dist/core/verse/adapters/grok.js +13 -0
  70. package/dist/core/verse/adapters/grok.js.map +1 -1
  71. package/dist/core/verse/control-api.d.ts +8 -9
  72. package/dist/core/verse/control-api.js +14 -114
  73. package/dist/core/verse/control-api.js.map +1 -1
  74. package/dist/core/verse/github-api.d.ts +50 -0
  75. package/dist/core/verse/github-api.js +211 -0
  76. package/dist/core/verse/github-api.js.map +1 -0
  77. package/dist/core/verse/github-proposal.d.ts +64 -0
  78. package/dist/core/verse/github-proposal.js +186 -0
  79. package/dist/core/verse/github-proposal.js.map +1 -0
  80. package/dist/core/verse/github-repo.d.ts +104 -0
  81. package/dist/core/verse/github-repo.js +476 -0
  82. package/dist/core/verse/github-repo.js.map +1 -0
  83. package/dist/core/verse/github-types.d.ts +174 -0
  84. package/dist/core/verse/github-types.js +31 -0
  85. package/dist/core/verse/github-types.js.map +1 -0
  86. package/dist/core/verse/mcp-cli-health.d.ts +149 -0
  87. package/dist/core/verse/mcp-cli-health.js +227 -0
  88. package/dist/core/verse/mcp-cli-health.js.map +1 -0
  89. package/dist/core/verse/mcp-control-api.d.ts +68 -0
  90. package/dist/core/verse/mcp-control-api.js +322 -0
  91. package/dist/core/verse/mcp-control-api.js.map +1 -0
  92. package/dist/core/verse/mcp-mutation.d.ts +187 -0
  93. package/dist/core/verse/mcp-mutation.js +471 -0
  94. package/dist/core/verse/mcp-mutation.js.map +1 -0
  95. package/dist/core/verse/mcp-scope.d.ts +114 -0
  96. package/dist/core/verse/mcp-scope.js +149 -0
  97. package/dist/core/verse/mcp-scope.js.map +1 -0
  98. package/dist/core/verse/mcp-seat-view.d.ts +197 -0
  99. package/dist/core/verse/mcp-seat-view.js +322 -0
  100. package/dist/core/verse/mcp-seat-view.js.map +1 -0
  101. package/dist/core/verse/path-guard.d.ts +115 -0
  102. package/dist/core/verse/path-guard.js +171 -0
  103. package/dist/core/verse/path-guard.js.map +1 -0
  104. package/dist/core/verse/seats.js +30 -2
  105. package/dist/core/verse/seats.js.map +1 -1
  106. package/dist/core/verse/session-engine.js +60 -1
  107. package/dist/core/verse/session-engine.js.map +1 -1
  108. package/dist/core/verse/session-store.js +20 -0
  109. package/dist/core/verse/session-store.js.map +1 -1
  110. package/dist/core/verse/types.d.ts +225 -1
  111. package/dist/core/verse/types.js +26 -0
  112. package/dist/core/verse/types.js.map +1 -1
  113. package/dist/core/verse/verse-api.d.ts +16 -1
  114. package/dist/core/verse/verse-api.js +338 -16
  115. package/dist/core/verse/verse-api.js.map +1 -1
  116. package/dist/core/verse/workspaces.d.ts +130 -0
  117. package/dist/core/verse/workspaces.js +501 -0
  118. package/dist/core/verse/workspaces.js.map +1 -0
  119. package/dist/core/web/api.js +23 -0
  120. package/dist/core/web/api.js.map +1 -1
  121. package/dist/core/web/public/next/assets/{App-LuILXjWG.js → App-CsbPYQqy.js} +2 -2
  122. package/dist/core/web/public/next/assets/{App-LuILXjWG.js.map → App-CsbPYQqy.js.map} +1 -1
  123. package/dist/core/web/public/next/assets/{ApprovalsSection-B_Xor8JH.js → ApprovalsSection-CBZquyLK.js} +2 -2
  124. package/dist/core/web/public/next/assets/{ApprovalsSection-B_Xor8JH.js.map → ApprovalsSection-CBZquyLK.js.map} +1 -1
  125. package/dist/core/web/public/next/assets/AutonomySection-CQVpEdCE.js +2 -0
  126. package/dist/core/web/public/next/assets/{AutonomySection-BEwITtPJ.js.map → AutonomySection-CQVpEdCE.js.map} +1 -1
  127. package/dist/core/web/public/next/assets/AutonomySection-DTN6m6QE.css +1 -0
  128. package/dist/core/web/public/next/assets/ChatSection-DiRvNX3Q.css +1 -0
  129. package/dist/core/web/public/next/assets/ChatSection-DmE9K0Tg.js +77 -0
  130. package/dist/core/web/public/next/assets/ChatSection-DmE9K0Tg.js.map +1 -0
  131. package/dist/core/web/public/next/assets/{ConfirmDialog-BqkZdPs_.js → ConfirmDialog-deO0C0iY.js} +2 -2
  132. package/dist/core/web/public/next/assets/{ConfirmDialog-BqkZdPs_.js.map → ConfirmDialog-deO0C0iY.js.map} +1 -1
  133. package/dist/core/web/public/next/assets/{Dialog-CPmedg4S.js → Dialog-CNd7q89m.js} +2 -2
  134. package/dist/core/web/public/next/assets/{Dialog-CPmedg4S.js.map → Dialog-CNd7q89m.js.map} +1 -1
  135. package/dist/core/web/public/next/assets/{DiffViewer-DmPJbeM_.js → DiffViewer-DTO0KRuG.js} +2 -2
  136. package/dist/core/web/public/next/assets/{DiffViewer-DmPJbeM_.js.map → DiffViewer-DTO0KRuG.js.map} +1 -1
  137. package/dist/core/web/public/next/assets/{Epistemic-M3SxLHjq.js → Epistemic-53PikaUI.js} +2 -2
  138. package/dist/core/web/public/next/assets/{Epistemic-M3SxLHjq.js.map → Epistemic-53PikaUI.js.map} +1 -1
  139. package/dist/core/web/public/next/assets/{Meter-D_X7c9U3.js → Meter-DRWZQgr1.js} +2 -2
  140. package/dist/core/web/public/next/assets/{Meter-D_X7c9U3.js.map → Meter-DRWZQgr1.js.map} +1 -1
  141. package/dist/core/web/public/next/assets/{MutationTokenDialog-Ba5ZHu-8.js → MutationTokenDialog-DYF8n_RD.js} +2 -2
  142. package/dist/core/web/public/next/assets/{MutationTokenDialog-Ba5ZHu-8.js.map → MutationTokenDialog-DYF8n_RD.js.map} +1 -1
  143. package/dist/core/web/public/next/assets/{RefreshIndicator-vHGpmyzb.js → RefreshIndicator-DXJQXKyA.js} +2 -2
  144. package/dist/core/web/public/next/assets/{RefreshIndicator-vHGpmyzb.js.map → RefreshIndicator-DXJQXKyA.js.map} +1 -1
  145. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-B5g6JEVk.js → ResourcePoolConsoleApp-hDSEFdLU.js} +2 -2
  146. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-B5g6JEVk.js.map → ResourcePoolConsoleApp-hDSEFdLU.js.map} +1 -1
  147. package/dist/core/web/public/next/assets/{RouteErrorBoundary-CroxuKoi.js → RouteErrorBoundary-CZd7ln8v.js} +2 -2
  148. package/dist/core/web/public/next/assets/{RouteErrorBoundary-CroxuKoi.js.map → RouteErrorBoundary-CZd7ln8v.js.map} +1 -1
  149. package/dist/core/web/public/next/assets/{SettingsSection-p-7FCGfn.js → SettingsSection-C9Qs9JWW.js} +2 -2
  150. package/dist/core/web/public/next/assets/{SettingsSection-p-7FCGfn.js.map → SettingsSection-C9Qs9JWW.js.map} +1 -1
  151. package/dist/core/web/public/next/assets/{Skeleton-4qq4HuJO.js → Skeleton-YkcOpgFb.js} +2 -2
  152. package/dist/core/web/public/next/assets/{Skeleton-4qq4HuJO.js.map → Skeleton-YkcOpgFb.js.map} +1 -1
  153. package/dist/core/web/public/next/assets/{StatusBadge-BbEdqEGn.js → StatusBadge-DJw0LSPg.js} +2 -2
  154. package/dist/core/web/public/next/assets/{StatusBadge-BbEdqEGn.js.map → StatusBadge-DJw0LSPg.js.map} +1 -1
  155. package/dist/core/web/public/next/assets/{TableView-nyYs7Sv1.js → TableView-Bj-w1O6o.js} +2 -2
  156. package/dist/core/web/public/next/assets/{TableView-nyYs7Sv1.js.map → TableView-Bj-w1O6o.js.map} +1 -1
  157. package/dist/core/web/public/next/assets/{UniverseConsoleApp-DjTpQUrA.js → UniverseConsoleApp-Bs2fxaen.js} +2 -2
  158. package/dist/core/web/public/next/assets/{UniverseConsoleApp-DjTpQUrA.js.map → UniverseConsoleApp-Bs2fxaen.js.map} +1 -1
  159. package/dist/core/web/public/next/assets/{UniverseView-BtApwayT.js → UniverseView-B6b2hw-8.js} +2 -2
  160. package/dist/core/web/public/next/assets/{UniverseView-BtApwayT.js.map → UniverseView-B6b2hw-8.js.map} +1 -1
  161. package/dist/core/web/public/next/assets/{UsageSection-Db99N6ao.js → UsageSection-C__ziNkV.js} +2 -2
  162. package/dist/core/web/public/next/assets/{UsageSection-Db99N6ao.js.map → UsageSection-C__ziNkV.js.map} +1 -1
  163. package/dist/core/web/public/next/assets/{VerseConsoleApp-COa3J2dO.js → VerseConsoleApp-Cu5aG6J6.js} +3 -3
  164. package/dist/core/web/public/next/assets/VerseConsoleApp-Cu5aG6J6.js.map +1 -0
  165. package/dist/core/web/public/next/assets/{global-CNVapoV1.js → global-BHjd6fVZ.js} +2 -2
  166. package/dist/core/web/public/next/assets/{global-CNVapoV1.js.map → global-BHjd6fVZ.js.map} +1 -1
  167. package/dist/core/web/public/next/assets/index-CJ-e8YA9.js +2 -0
  168. package/dist/core/web/public/next/assets/index-CJ-e8YA9.js.map +1 -0
  169. package/dist/core/web/public/next/assets/{index-BkUlX2Se.js → index-CpgkRaTi.js} +3 -3
  170. package/dist/core/web/public/next/assets/{index-BkUlX2Se.js.map → index-CpgkRaTi.js.map} +1 -1
  171. package/dist/core/web/public/next/assets/{queries-C9XS4SYq.js → queries-856qUN3Y.js} +2 -2
  172. package/dist/core/web/public/next/assets/{queries-C9XS4SYq.js.map → queries-856qUN3Y.js.map} +1 -1
  173. package/dist/core/web/public/next/assets/{use-guarded-action-Bql1nA-z.js → use-guarded-action-DwaESKVy.js} +2 -2
  174. package/dist/core/web/public/next/assets/{use-guarded-action-Bql1nA-z.js.map → use-guarded-action-DwaESKVy.js.map} +1 -1
  175. package/dist/core/web/public/next/assets/verse-model-Dllo8rRt.js +2 -0
  176. package/dist/core/web/public/next/assets/{verse-model-IrNQKtER.js.map → verse-model-Dllo8rRt.js.map} +1 -1
  177. package/dist/core/web/public/next/assets/workspace-model-JgmWDKt4.js +2 -0
  178. package/dist/core/web/public/next/assets/workspace-model-JgmWDKt4.js.map +1 -0
  179. package/dist/core/web/public/next/index.html +1 -1
  180. package/dist/release-dependency-inventory.json +1 -1
  181. package/docs/README.md +4 -0
  182. package/package.json +1 -1
  183. package/dist/cli/activation.d.ts +0 -41
  184. package/dist/cli/activation.js +0 -485
  185. package/dist/cli/activation.js.map +0 -1
  186. package/dist/core/fleet/generated-repair-resolution.d.ts +0 -36
  187. package/dist/core/fleet/generated-repair-resolution.js +0 -245
  188. package/dist/core/fleet/generated-repair-resolution.js.map +0 -1
  189. package/dist/core/local-runtime/llama/port.d.ts +0 -17
  190. package/dist/core/local-runtime/llama/port.js +0 -47
  191. package/dist/core/local-runtime/llama/port.js.map +0 -1
  192. package/dist/core/local-runtime/llama/proxy-host.d.ts +0 -54
  193. package/dist/core/local-runtime/llama/proxy-host.js +0 -159
  194. package/dist/core/local-runtime/llama/proxy-host.js.map +0 -1
  195. package/dist/core/local-runtime/llama/proxy-process.d.ts +0 -148
  196. package/dist/core/local-runtime/llama/proxy-process.js +0 -250
  197. package/dist/core/local-runtime/llama/proxy-process.js.map +0 -1
  198. package/dist/core/local-runtime/llama/proxy-record.d.ts +0 -40
  199. package/dist/core/local-runtime/llama/proxy-record.js +0 -133
  200. package/dist/core/local-runtime/llama/proxy-record.js.map +0 -1
  201. package/dist/core/local-runtime/llama/proxy-supervisor.d.ts +0 -118
  202. package/dist/core/local-runtime/llama/proxy-supervisor.js +0 -511
  203. package/dist/core/local-runtime/llama/proxy-supervisor.js.map +0 -1
  204. package/dist/core/web/public/next/assets/AutonomySection-BB8RD-tv.css +0 -1
  205. package/dist/core/web/public/next/assets/AutonomySection-BEwITtPJ.js +0 -2
  206. package/dist/core/web/public/next/assets/ChatSection-DrxqhBPK.css +0 -1
  207. package/dist/core/web/public/next/assets/ChatSection-bRAzlMth.js +0 -77
  208. package/dist/core/web/public/next/assets/ChatSection-bRAzlMth.js.map +0 -1
  209. package/dist/core/web/public/next/assets/VerseConsoleApp-COa3J2dO.js.map +0 -1
  210. package/dist/core/web/public/next/assets/index-YNTgDG2b.js +0 -2
  211. package/dist/core/web/public/next/assets/index-YNTgDG2b.js.map +0 -1
  212. package/dist/core/web/public/next/assets/verse-model-IrNQKtER.js +0 -2
  213. package/node_modules/which/CHANGELOG.md +0 -166
@@ -0,0 +1,322 @@
1
+ /**
2
+ * core/verse/mcp-control-api.ts — the HTTP surface for MCP and per-account
3
+ * CLI management.
4
+ *
5
+ * Deliberately a SEPARATE handler from `control-api.ts` rather than new arms
6
+ * on its `if`-chain: that file is owned by another work stream, and this one
7
+ * carries a mutation that installs arbitrary code. Keeping them apart means
8
+ * the review surface for "can the hub run new code" is one file.
9
+ *
10
+ * ── Route shape ─────────────────────────────────────────────────────────────
11
+ *
12
+ * GET /api/verse/mcp which servers each seat would load
13
+ * GET /api/verse/mcp/cli-health per-account auth, plan and version drift
14
+ * POST /api/verse/mcp/cli-probe read the installed CLI versions (SPAWNS)
15
+ * POST /api/verse/mcp/proposal validate + disclose a write (writes nothing)
16
+ * POST /api/verse/mcp/apply perform the write the digest confirms
17
+ *
18
+ * The two GETs are free: no subprocess, no provider call, no write. Everything
19
+ * that costs something or changes something is a POST behind
20
+ * `passesMutationGate`, including `cli-probe` — it launches up to four vendor
21
+ * binaries, and a read route that does that is a side effect in disguise.
22
+ *
23
+ * `proposal` is a POST for the same reason, even though it writes nothing: it
24
+ * carries an operator-supplied command in its body, and it is the step that
25
+ * must be authenticated for the resulting digest to mean anything.
26
+ *
27
+ * ── Ordering requirement ────────────────────────────────────────────────────
28
+ *
29
+ * These paths live under `/api/verse/*`, which `isVerseApiPath()` matches
30
+ * wholesale and 404s when it does not recognise. So the dispatch call in
31
+ * `src/core/web/api.ts` MUST come before both `isVerseControlPath` and
32
+ * `isVerseApiPath`. See the comment at that call site.
33
+ */
34
+ import { passesMutationGate, readBody, sendJson } from '../web/api.js';
35
+ import { audit } from '../sandbox/audit.js';
36
+ import { buildVerseAccountsSnapshot, readVerseAccountIdentities } from './accounts.js';
37
+ import { getVerseAccountCollector } from './accounts.js';
38
+ import { defaultAccountsRoot, resolveAccountsRoot } from './seats.js';
39
+ import { buildVerseMcpSnapshot } from './mcp-seat-view.js';
40
+ import { readVerseMcpScopeGate } from './mcp-scope.js';
41
+ import { buildVerseCliHealth, probeVerseAccountCliVersions, } from './mcp-cli-health.js';
42
+ import { applyVerseMcpProposal, buildVerseMcpProposal, } from './mcp-mutation.js';
43
+ const MCP_PREFIX = '/api/verse/mcp';
44
+ const MCP_ROUTES = new Set([
45
+ MCP_PREFIX,
46
+ `${MCP_PREFIX}/cli-health`,
47
+ `${MCP_PREFIX}/cli-probe`,
48
+ `${MCP_PREFIX}/proposal`,
49
+ `${MCP_PREFIX}/apply`,
50
+ ]);
51
+ /** True for exactly the five MCP-management routes. */
52
+ export function isVerseMcpPath(path) {
53
+ return MCP_ROUTES.has(path);
54
+ }
55
+ const ERROR_STATUS = {
56
+ VERSE_INVALID: 400,
57
+ VERSE_REFUSED: 409,
58
+ VERSE_TOO_LARGE: 413,
59
+ VERSE_UNAVAILABLE: 503,
60
+ };
61
+ function sendError(res, code, error) {
62
+ sendJson(res, ERROR_STATUS[code], { code, error });
63
+ }
64
+ function sendInvalid(res, error) {
65
+ sendError(res, 'VERSE_INVALID', error);
66
+ }
67
+ function isRecord(value) {
68
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
69
+ }
70
+ async function readJsonBody(req, res) {
71
+ let raw;
72
+ try {
73
+ raw = await readBody(req);
74
+ }
75
+ catch {
76
+ sendError(res, 'VERSE_TOO_LARGE', 'request body too large');
77
+ return null;
78
+ }
79
+ let parsed;
80
+ try {
81
+ parsed = raw.length === 0 ? {} : JSON.parse(raw);
82
+ }
83
+ catch {
84
+ sendInvalid(res, 'invalid JSON body');
85
+ return null;
86
+ }
87
+ if (!isRecord(parsed)) {
88
+ sendInvalid(res, 'body must be a JSON object');
89
+ return null;
90
+ }
91
+ return parsed;
92
+ }
93
+ function passesPostGate(ctx, req, res) {
94
+ if (!ctx.allowDispatch) {
95
+ sendJson(res, 404, { error: 'not found' });
96
+ return false;
97
+ }
98
+ return passesMutationGate(req, res, ctx.token);
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // Shared reads
102
+ // ---------------------------------------------------------------------------
103
+ function accountsRootFor(ctx) {
104
+ if (ctx.accountsRoot !== undefined)
105
+ return ctx.accountsRoot;
106
+ try {
107
+ return resolveAccountsRoot(ctx.cfg);
108
+ }
109
+ catch {
110
+ return defaultAccountsRoot();
111
+ }
112
+ }
113
+ /**
114
+ * Seat inputs for the MCP view, WITHOUT running `discoverSeats()`.
115
+ *
116
+ * `discoverSeats` probes Ollama over HTTP and builds telemetry — none of which
117
+ * this view needs, and all of which would make a cheap read expensive. What
118
+ * the MCP question turns on is (engine, account), and that is exactly what
119
+ * `readVerseAccountIdentities` publishes. Seat ids for native seats ARE the
120
+ * account ids (see VerseSeat.id's own documentation).
121
+ *
122
+ * Local seats are collapsed into ONE row on purpose: every Ollama tag is
123
+ * launched through the same `buildClaudeLaunch` argv, so they are all isolated
124
+ * identically and listing twelve identical rows would be noise, not detail.
125
+ */
126
+ export function verseMcpSeatInputs(accountsRoot) {
127
+ const seats = readVerseAccountIdentities(accountsRoot).map((identity) => ({
128
+ id: identity.id,
129
+ label: identity.label,
130
+ engine: identity.provider,
131
+ accountId: identity.id,
132
+ }));
133
+ seats.push({
134
+ id: 'local',
135
+ label: 'Local seats (every Ollama tag)',
136
+ engine: 'local',
137
+ accountId: 'local',
138
+ });
139
+ return seats;
140
+ }
141
+ // ---------------------------------------------------------------------------
142
+ // Handler
143
+ // ---------------------------------------------------------------------------
144
+ /**
145
+ * Handle one MCP-management request. Returns true when a response was
146
+ * written (including errors); false when `path` is not one of these routes.
147
+ */
148
+ export async function handleVerseMcpApi(ctx, req, res, path, method) {
149
+ if (!isVerseMcpPath(path))
150
+ return false;
151
+ try {
152
+ const accountsRoot = accountsRootFor(ctx);
153
+ // ── GET /api/verse/mcp ────────────────────────────────────────────────
154
+ if (path === MCP_PREFIX) {
155
+ if (method !== 'GET') {
156
+ sendJson(res, 404, { error: `not found: ${method} ${path}` });
157
+ return true;
158
+ }
159
+ sendJson(res, 200, buildVerseMcpSnapshot({
160
+ accountsRoot,
161
+ seats: verseMcpSeatInputs(accountsRoot),
162
+ }));
163
+ return true;
164
+ }
165
+ // ── GET /api/verse/mcp/cli-health ─────────────────────────────────────
166
+ //
167
+ // Free: derived entirely from the collector's existing records plus the
168
+ // pinned-version constant. No subprocess. A version NUMBER requires the
169
+ // probe below; its absence is reported as `unverified`, never faked.
170
+ if (path === `${MCP_PREFIX}/cli-health`) {
171
+ if (method !== 'GET') {
172
+ sendJson(res, 404, { error: `not found: ${method} ${path}` });
173
+ return true;
174
+ }
175
+ const snapshot = buildVerseAccountsSnapshot({
176
+ accountsRoot,
177
+ collector: getVerseAccountCollector(),
178
+ });
179
+ sendJson(res, 200, buildVerseCliHealth({ accounts: snapshot.accounts }));
180
+ return true;
181
+ }
182
+ // ── POST /api/verse/mcp/cli-probe ─────────────────────────────────────
183
+ //
184
+ // Gated because it SPAWNS: help/version invocations only, but four vendor
185
+ // binaries is a real cost and a real side effect on this machine.
186
+ if (path === `${MCP_PREFIX}/cli-probe`) {
187
+ if (method !== 'POST') {
188
+ sendJson(res, 404, { error: `not found: ${method} ${path}` });
189
+ return true;
190
+ }
191
+ if (!passesPostGate(ctx, req, res))
192
+ return true;
193
+ const body = await readJsonBody(req, res);
194
+ if (!body)
195
+ return true;
196
+ for (const key of Object.keys(body)) {
197
+ sendInvalid(res, `unknown key: ${key}`);
198
+ return true;
199
+ }
200
+ let readings;
201
+ try {
202
+ readings = await probeVerseAccountCliVersions({ accountsRoot });
203
+ }
204
+ catch {
205
+ sendError(res, 'VERSE_UNAVAILABLE', 'the CLI version probe could not be run');
206
+ return true;
207
+ }
208
+ const snapshot = buildVerseAccountsSnapshot({
209
+ accountsRoot,
210
+ collector: getVerseAccountCollector(),
211
+ });
212
+ const health = buildVerseCliHealth({ accounts: snapshot.accounts, readings });
213
+ audit({
214
+ action: 'verse.mcp.cli-probe',
215
+ repo: null,
216
+ sandboxId: null,
217
+ summary: `read CLI versions for ${readings.size} account(s); drift=${health.driftDetected}`,
218
+ result: 'ok',
219
+ });
220
+ sendJson(res, 200, health);
221
+ return true;
222
+ }
223
+ // ── POST /api/verse/mcp/proposal ──────────────────────────────────────
224
+ //
225
+ // Writes NOTHING. Returns the full command, args and env KEYS (values
226
+ // redacted), the warnings, the Locus scope, and the digest that the apply
227
+ // step demands back.
228
+ if (path === `${MCP_PREFIX}/proposal` || path === `${MCP_PREFIX}/apply`) {
229
+ const applying = path === `${MCP_PREFIX}/apply`;
230
+ if (method !== 'POST') {
231
+ sendJson(res, 404, { error: `not found: ${method} ${path}` });
232
+ return true;
233
+ }
234
+ if (!passesPostGate(ctx, req, res))
235
+ return true;
236
+ const body = await readJsonBody(req, res);
237
+ if (!body)
238
+ return true;
239
+ const allowed = applying
240
+ ? new Set(['target', 'server', 'digest', 'confirm'])
241
+ : new Set(['target', 'server']);
242
+ for (const key of Object.keys(body)) {
243
+ if (!allowed.has(key)) {
244
+ sendInvalid(res, `unknown key: ${key}`);
245
+ return true;
246
+ }
247
+ }
248
+ const target = body['target'];
249
+ if (typeof target !== 'string' || target.length === 0 || target.length > 128) {
250
+ sendInvalid(res, 'target is required');
251
+ return true;
252
+ }
253
+ if (body['server'] === undefined) {
254
+ sendInvalid(res, 'server is required');
255
+ return true;
256
+ }
257
+ const scope = readVerseMcpScopeGate();
258
+ if (!applying) {
259
+ const proposal = buildVerseMcpProposal({
260
+ targetId: target,
261
+ server: body['server'],
262
+ accountsRoot,
263
+ scope,
264
+ });
265
+ sendRefusableResult(res, proposal);
266
+ return true;
267
+ }
268
+ // An explicit `confirm: true` on top of the digest. The digest is the
269
+ // real control; this is the second half of "never a one-click install"
270
+ // being visible in the request itself.
271
+ if (body['confirm'] !== true) {
272
+ sendInvalid(res, 'confirm must be true to apply a proposal');
273
+ return true;
274
+ }
275
+ const supplied = body['digest'];
276
+ if (typeof supplied !== 'string' || supplied.length === 0 || supplied.length > 128) {
277
+ sendInvalid(res, 'digest is required to apply a proposal');
278
+ return true;
279
+ }
280
+ const result = applyVerseMcpProposal({
281
+ targetId: target,
282
+ server: body['server'],
283
+ accountsRoot,
284
+ scope,
285
+ digest: supplied,
286
+ });
287
+ // The audit summary names the target and the server NAME. It never
288
+ // carries the command, args or env — `stripSecrets` is a backstop, not
289
+ // a licence to hand it a secret.
290
+ const serverName = isRecord(body['server']) && typeof body['server']['name'] === 'string'
291
+ ? body['server']['name']
292
+ : '(unnamed)';
293
+ audit({
294
+ action: 'verse.mcp.apply',
295
+ repo: null,
296
+ sandboxId: null,
297
+ summary: result.ok
298
+ ? `${result.action} MCP server ${serverName} in ${result.target.ref}`
299
+ : `refused MCP server ${serverName} for ${target}: ${result.refusal}`,
300
+ result: result.ok ? 'ok' : 'refused',
301
+ });
302
+ sendRefusableResult(res, result);
303
+ return true;
304
+ }
305
+ /* c8 ignore next 2 -- isVerseMcpPath already narrowed to the five routes. */
306
+ sendJson(res, 404, { error: `not found: ${method} ${path}` });
307
+ return true;
308
+ }
309
+ catch {
310
+ sendError(res, 'VERSE_UNAVAILABLE', 'MCP management is unavailable on this server');
311
+ return true;
312
+ }
313
+ }
314
+ /**
315
+ * A refusal is a 409 carrying the full result body with a plain-language
316
+ * `note` — the same convention `VerseScopeResult` established, which the web
317
+ * client relies on when it reads `error` and falls back to `note`.
318
+ */
319
+ function sendRefusableResult(res, result) {
320
+ sendJson(res, result.ok ? 200 : 409, result);
321
+ }
322
+ //# sourceMappingURL=mcp-control-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-control-api.js","sourceRoot":"","sources":["../../../src/core/verse/mcp-control-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,187 @@
1
+ /**
2
+ * core/verse/mcp-mutation.ts — adding or editing an MCP server, deliberately
3
+ * made difficult.
4
+ *
5
+ * docs/VERSE-WORKSPACES.md §3: "an MCP server is arbitrary code with the
6
+ * agent's privileges"; "Adding or editing a server is a privileged,
7
+ * outward-facing change. It belongs behind the same mutation gate as the other
8
+ * control-plane writes, and it should show the full command and environment
9
+ * (redacted) before it is saved — never a one-click install of something the
10
+ * operator has not read."
11
+ *
12
+ * ── How "never one-click" is enforced, rather than just intended ────────────
13
+ *
14
+ * A write takes TWO requests, and the second cannot be forged from the first
15
+ * by a client that did not render it:
16
+ *
17
+ * 1. {@link buildVerseMcpProposal} validates the spec, resolves the target,
18
+ * and returns a PROPOSAL containing the full command and args verbatim,
19
+ * the env KEYS with every value redacted to '<set>', the warnings, and a
20
+ * `digest` over exactly what was shown plus the target file's current
21
+ * contents. Nothing is written.
22
+ * 2. {@link applyVerseMcpProposal} refuses unless it is handed that same
23
+ * digest back AND the target file is still byte-identical to what the
24
+ * digest was taken over. A stale digest is a refusal, not a merge.
25
+ *
26
+ * The digest binds the decision to the disclosure. An operator who never saw
27
+ * the command cannot produce it, and a file that changed underneath the
28
+ * proposal invalidates it.
29
+ *
30
+ * ── What this refuses to do ─────────────────────────────────────────────────
31
+ *
32
+ * - Rewrite TOML. Codex and Grok keep their MCP servers in `config.toml`.
33
+ * `src/core/integrations/editors.ts` already established the rule for
34
+ * this repo — Codex "is intentionally delegated to its official CLI so
35
+ * this module never attempts to parse and rewrite a user's TOML
36
+ * configuration" — and a half-understood TOML rewrite of an operator's
37
+ * config is a worse outcome than a refusal.
38
+ * - Touch ~/.ashlr/config.json, ~/.ashlr/enrollment.json or ~/.ashlr/KILL.
39
+ * Those are not MCP registries and are explicitly out of bounds.
40
+ * - Write anywhere the caller names freely. Targets are an enumerated set.
41
+ *
42
+ * ── What may leave this module ──────────────────────────────────────────────
43
+ *
44
+ * Env VALUES never leave, in any form, on any path: not in the proposal, not
45
+ * in the apply result, not in the audit summary, not in an error message. The
46
+ * proposal carries `redactEnv`'d specs only. The real values live in the
47
+ * request body and go straight to disk.
48
+ */
49
+ import type { McpServerSpec } from '../types.js';
50
+ import { type VerseMcpServerView } from './mcp-seat-view.js';
51
+ import type { VerseMcpScopeGate } from './mcp-scope.js';
52
+ /**
53
+ * Where a server may be written.
54
+ *
55
+ * `hub` ~/.ashlr/settings.json — the hub's own aggregated
56
+ * gateway registry (M66; `knownConfigPaths()` scans it).
57
+ * `account:<id>` that account's private JSON registry. Only accounts
58
+ * whose native config is JSON qualify; Codex and Grok keep
59
+ * TOML and are refused by name below.
60
+ */
61
+ export type VerseMcpTargetId = 'hub' | `account:${string}`;
62
+ export type VerseMcpRefusal = 'mcp-target-unknown' | 'mcp-target-not-json' | 'mcp-target-unresolved' | 'mcp-target-forbidden' | 'mcp-spec-invalid' | 'mcp-digest-mismatch' | 'mcp-target-changed' | 'mcp-scope-refused' | 'mcp-write-failed';
63
+ /** Files that must never be written by this module, whatever a caller asks. */
64
+ export declare const VERSE_MCP_FORBIDDEN_FILES: readonly string[];
65
+ export interface VerseMcpTarget {
66
+ id: VerseMcpTargetId;
67
+ /** Absolute path. SERVER-SIDE ONLY — projected to `ref` for transport. */
68
+ path: string;
69
+ /** Path relative to $HOME. What the operator is shown. */
70
+ ref: string;
71
+ /** Human label for the surface. */
72
+ label: string;
73
+ }
74
+ /**
75
+ * Resolve a target id to a file, or explain why not.
76
+ *
77
+ * `accountsRoot` is only consulted for `account:` targets. Account state
78
+ * directories are private and never published — only `ref` crosses out.
79
+ */
80
+ export declare function resolveVerseMcpTarget(id: string, options: {
81
+ accountsRoot: string;
82
+ home?: string;
83
+ }): {
84
+ ok: true;
85
+ target: VerseMcpTarget;
86
+ } | {
87
+ ok: false;
88
+ refusal: VerseMcpRefusal;
89
+ error: string;
90
+ };
91
+ export type VerseMcpSpecParse = {
92
+ ok: true;
93
+ spec: McpServerSpec;
94
+ warnings: string[];
95
+ } | {
96
+ ok: false;
97
+ error: string;
98
+ };
99
+ /**
100
+ * Validate a caller-supplied server spec.
101
+ *
102
+ * Strict by construction: unknown keys are rejected, every string is bounded
103
+ * and printable, and nothing is coerced. Warnings are returned rather than
104
+ * silently swallowed — they are part of what the operator must read.
105
+ */
106
+ export declare function parseVerseMcpSpec(body: unknown): VerseMcpSpecParse;
107
+ export interface VerseMcpProposal {
108
+ ok: true;
109
+ /** What the operator must read before confirming. Env values are '<set>'. */
110
+ server: VerseMcpServerView;
111
+ target: {
112
+ id: VerseMcpTargetId;
113
+ ref: string;
114
+ label: string;
115
+ };
116
+ /** 'add' when the name is new in the target, 'replace' when it is not. */
117
+ action: 'add' | 'replace';
118
+ /** The server this would overwrite, redacted. Null on an add. */
119
+ replaces: VerseMcpServerView | null;
120
+ /** Everything the operator should weigh. Never empty for a real install. */
121
+ warnings: string[];
122
+ /** The scope this write lands under, and whether Locus permits it. */
123
+ scope: VerseMcpScopeGate;
124
+ /**
125
+ * Binds this exact disclosure to the target's current contents. `apply`
126
+ * refuses without it, and refuses again if the file moved underneath.
127
+ */
128
+ digest: string;
129
+ note: string;
130
+ }
131
+ export interface VerseMcpRefusalResult {
132
+ ok: false;
133
+ refusal: VerseMcpRefusal;
134
+ error: string;
135
+ note: string;
136
+ }
137
+ export declare const VERSE_MCP_PROPOSAL_NOTE: string;
138
+ /**
139
+ * The digest the operator confirms against.
140
+ *
141
+ * Covers the REDACTED spec (what was shown), the target, and a hash of the
142
+ * target file's current bytes. Env values are deliberately outside it: the
143
+ * digest must be reproducible from what was displayed.
144
+ */
145
+ export declare function verseMcpProposalDigest(input: {
146
+ server: VerseMcpServerView;
147
+ targetId: string;
148
+ targetBytes: string;
149
+ }): string;
150
+ export interface VerseMcpProposalOptions {
151
+ targetId: string;
152
+ server: unknown;
153
+ accountsRoot: string;
154
+ scope: VerseMcpScopeGate;
155
+ home?: string;
156
+ }
157
+ /**
158
+ * Validate, disclose, and hand back a digest. Writes nothing.
159
+ */
160
+ export declare function buildVerseMcpProposal(options: VerseMcpProposalOptions): VerseMcpProposal | VerseMcpRefusalResult;
161
+ export interface VerseMcpApplyResult {
162
+ ok: true;
163
+ action: 'add' | 'replace';
164
+ target: {
165
+ id: VerseMcpTargetId;
166
+ ref: string;
167
+ };
168
+ /** Re-read from disk after the write, redacted. What was actually persisted. */
169
+ server: VerseMcpServerView;
170
+ note: string;
171
+ }
172
+ export declare const VERSE_MCP_APPLIED_NOTE: string;
173
+ export interface VerseMcpApplyOptions extends VerseMcpProposalOptions {
174
+ /** The digest returned by the proposal. A mismatch is a refusal. */
175
+ digest: string;
176
+ }
177
+ /**
178
+ * Apply a proposal the operator confirmed.
179
+ *
180
+ * Refuses unless the supplied digest reproduces from (a) the redacted spec it
181
+ * is about to write and (b) the target file's CURRENT bytes. That second half
182
+ * is what makes a stale confirmation fail instead of quietly clobbering a
183
+ * change made in between.
184
+ *
185
+ * The write is atomic: a sibling temp file, mode 0600, then rename.
186
+ */
187
+ export declare function applyVerseMcpProposal(options: VerseMcpApplyOptions): VerseMcpApplyResult | VerseMcpRefusalResult;