@oscharko-dev/keiko-server 0.2.8 → 0.2.9

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 (281) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/chat-handlers.d.ts +18 -2
  3. package/dist/chat-handlers.d.ts.map +1 -1
  4. package/dist/chat-handlers.js +185 -3
  5. package/dist/command-runner-errors.d.ts +17 -0
  6. package/dist/command-runner-errors.d.ts.map +1 -0
  7. package/dist/command-runner-errors.js +37 -0
  8. package/dist/command-runner-evidence.d.ts +23 -0
  9. package/dist/command-runner-evidence.d.ts.map +1 -0
  10. package/dist/command-runner-evidence.js +69 -0
  11. package/dist/command-runner-routes.d.ts +7 -0
  12. package/dist/command-runner-routes.d.ts.map +1 -0
  13. package/dist/command-runner-routes.js +175 -0
  14. package/dist/command-runner.d.ts +29 -0
  15. package/dist/command-runner.d.ts.map +1 -0
  16. package/dist/command-runner.js +348 -0
  17. package/dist/conversation-prompt.d.ts +2 -2
  18. package/dist/conversation-prompt.d.ts.map +1 -1
  19. package/dist/conversation-prompt.js +17 -1
  20. package/dist/csp.d.ts.map +1 -1
  21. package/dist/csp.js +3 -0
  22. package/dist/deps.d.ts +27 -1
  23. package/dist/deps.d.ts.map +1 -1
  24. package/dist/deps.js +288 -13
  25. package/dist/discussion-prompt.d.ts +4 -0
  26. package/dist/discussion-prompt.d.ts.map +1 -0
  27. package/dist/discussion-prompt.js +19 -0
  28. package/dist/editor/agentActionAudit.d.ts +18 -0
  29. package/dist/editor/agentActionAudit.d.ts.map +1 -0
  30. package/dist/editor/agentActionAudit.js +80 -0
  31. package/dist/editor/agentRoutes.d.ts +1 -0
  32. package/dist/editor/agentRoutes.d.ts.map +1 -1
  33. package/dist/editor/agentRoutes.js +292 -55
  34. package/dist/editor/agentSessionRegistry.d.ts +35 -0
  35. package/dist/editor/agentSessionRegistry.d.ts.map +1 -0
  36. package/dist/editor/agentSessionRegistry.js +243 -0
  37. package/dist/editor/completionRoutes.d.ts.map +1 -1
  38. package/dist/editor/completionRoutes.js +5 -10
  39. package/dist/editor/languageRoutes.d.ts +12 -1
  40. package/dist/editor/languageRoutes.d.ts.map +1 -1
  41. package/dist/editor/languageRoutes.js +71 -8
  42. package/dist/editor/languageService.d.ts +3 -2
  43. package/dist/editor/languageService.d.ts.map +1 -1
  44. package/dist/editor/languageService.js +41 -3
  45. package/dist/editor/languageServiceHost.d.ts.map +1 -1
  46. package/dist/editor/languageServiceHost.js +2 -2
  47. package/dist/editor/lsp/hostLanguageOperation.d.ts +17 -0
  48. package/dist/editor/lsp/hostLanguageOperation.d.ts.map +1 -0
  49. package/dist/editor/lsp/hostLanguageOperation.js +436 -0
  50. package/dist/editor/lsp/hostLanguageProviders.d.ts +26 -0
  51. package/dist/editor/lsp/hostLanguageProviders.d.ts.map +1 -0
  52. package/dist/editor/lsp/hostLanguageProviders.js +161 -0
  53. package/dist/editor/lsp/lspFrameCodec.d.ts +13 -0
  54. package/dist/editor/lsp/lspFrameCodec.d.ts.map +1 -0
  55. package/dist/editor/lsp/lspFrameCodec.js +164 -0
  56. package/dist/editor/lsp/lspJsonRpcClient.d.ts +34 -0
  57. package/dist/editor/lsp/lspJsonRpcClient.d.ts.map +1 -0
  58. package/dist/editor/lsp/lspJsonRpcClient.js +173 -0
  59. package/dist/editor/lsp/lspLanguageProvider.d.ts +7 -0
  60. package/dist/editor/lsp/lspLanguageProvider.d.ts.map +1 -0
  61. package/dist/editor/lsp/lspLanguageProvider.js +29 -0
  62. package/dist/editor/lsp/lspLifecycleLedger.d.ts +5 -0
  63. package/dist/editor/lsp/lspLifecycleLedger.d.ts.map +1 -0
  64. package/dist/editor/lsp/lspLifecycleLedger.js +37 -0
  65. package/dist/editor/lsp/lspNodeAdapter.d.ts +31 -0
  66. package/dist/editor/lsp/lspNodeAdapter.d.ts.map +1 -0
  67. package/dist/editor/lsp/lspNodeAdapter.js +230 -0
  68. package/dist/editor/lsp/lspProcessManager.d.ts +24 -0
  69. package/dist/editor/lsp/lspProcessManager.d.ts.map +1 -0
  70. package/dist/editor/lsp/lspProcessManager.js +255 -0
  71. package/dist/editor/lsp/lspRestartThrottle.d.ts +6 -0
  72. package/dist/editor/lsp/lspRestartThrottle.d.ts.map +1 -0
  73. package/dist/editor/lsp/lspRestartThrottle.js +24 -0
  74. package/dist/editor/lsp/lspStatusRoute.d.ts +8 -0
  75. package/dist/editor/lsp/lspStatusRoute.d.ts.map +1 -0
  76. package/dist/editor/lsp/lspStatusRoute.js +22 -0
  77. package/dist/editor/lsp/lspTransport.d.ts +19 -0
  78. package/dist/editor/lsp/lspTransport.d.ts.map +1 -0
  79. package/dist/editor/lsp/lspTransport.js +55 -0
  80. package/dist/editor/lsp/testing/fakeLspProcess.d.ts +23 -0
  81. package/dist/editor/lsp/testing/fakeLspProcess.d.ts.map +1 -0
  82. package/dist/editor/lsp/testing/fakeLspProcess.js +132 -0
  83. package/dist/files.d.ts +45 -0
  84. package/dist/files.d.ts.map +1 -1
  85. package/dist/files.js +631 -7
  86. package/dist/gateway-readiness.js +3 -3
  87. package/dist/gateway-setup.d.ts +2 -0
  88. package/dist/gateway-setup.d.ts.map +1 -1
  89. package/dist/gateway-setup.js +275 -11
  90. package/dist/gitDelivery/actionSheetProjection.d.ts +30 -0
  91. package/dist/gitDelivery/actionSheetProjection.d.ts.map +1 -0
  92. package/dist/gitDelivery/actionSheetProjection.js +206 -0
  93. package/dist/gitDelivery/actionSheetRoutes.d.ts +29 -0
  94. package/dist/gitDelivery/actionSheetRoutes.d.ts.map +1 -0
  95. package/dist/gitDelivery/actionSheetRoutes.js +293 -0
  96. package/dist/gitDelivery/agentOperationsRoutes.d.ts +33 -0
  97. package/dist/gitDelivery/agentOperationsRoutes.d.ts.map +1 -0
  98. package/dist/gitDelivery/agentOperationsRoutes.js +405 -0
  99. package/dist/gitDelivery/commitRoutes.d.ts +23 -0
  100. package/dist/gitDelivery/commitRoutes.d.ts.map +1 -0
  101. package/dist/gitDelivery/commitRoutes.js +204 -0
  102. package/dist/gitDelivery/evidenceRoutes.d.ts +9 -0
  103. package/dist/gitDelivery/evidenceRoutes.d.ts.map +1 -0
  104. package/dist/gitDelivery/evidenceRoutes.js +101 -0
  105. package/dist/gitDelivery/execution.d.ts +38 -0
  106. package/dist/gitDelivery/execution.d.ts.map +1 -0
  107. package/dist/gitDelivery/execution.js +117 -0
  108. package/dist/gitDelivery/localMutationRoutes.d.ts +30 -0
  109. package/dist/gitDelivery/localMutationRoutes.d.ts.map +1 -0
  110. package/dist/gitDelivery/localMutationRoutes.js +165 -0
  111. package/dist/gitDelivery/mergeExecution.d.ts +63 -0
  112. package/dist/gitDelivery/mergeExecution.d.ts.map +1 -0
  113. package/dist/gitDelivery/mergeExecution.js +168 -0
  114. package/dist/gitDelivery/mergeRoutes.d.ts +12 -0
  115. package/dist/gitDelivery/mergeRoutes.d.ts.map +1 -0
  116. package/dist/gitDelivery/mergeRoutes.js +218 -0
  117. package/dist/gitDelivery/mutationEvidenceLedger.d.ts +23 -0
  118. package/dist/gitDelivery/mutationEvidenceLedger.d.ts.map +1 -0
  119. package/dist/gitDelivery/mutationEvidenceLedger.js +87 -0
  120. package/dist/gitDelivery/prExecution.d.ts +54 -0
  121. package/dist/gitDelivery/prExecution.d.ts.map +1 -0
  122. package/dist/gitDelivery/prExecution.js +192 -0
  123. package/dist/gitDelivery/prRoutes.d.ts +12 -0
  124. package/dist/gitDelivery/prRoutes.d.ts.map +1 -0
  125. package/dist/gitDelivery/prRoutes.js +256 -0
  126. package/dist/gitDelivery/pushExecution.d.ts +43 -0
  127. package/dist/gitDelivery/pushExecution.d.ts.map +1 -0
  128. package/dist/gitDelivery/pushExecution.js +124 -0
  129. package/dist/gitDelivery/pushRoutes.d.ts +12 -0
  130. package/dist/gitDelivery/pushRoutes.d.ts.map +1 -0
  131. package/dist/gitDelivery/pushRoutes.js +200 -0
  132. package/dist/gitDelivery/requestGuards.d.ts +15 -0
  133. package/dist/gitDelivery/requestGuards.d.ts.map +1 -0
  134. package/dist/gitDelivery/requestGuards.js +97 -0
  135. package/dist/gitDelivery/syncEvidence.d.ts +37 -0
  136. package/dist/gitDelivery/syncEvidence.d.ts.map +1 -0
  137. package/dist/gitDelivery/syncEvidence.js +85 -0
  138. package/dist/gitDelivery/syncExecution.d.ts +30 -0
  139. package/dist/gitDelivery/syncExecution.d.ts.map +1 -0
  140. package/dist/gitDelivery/syncExecution.js +266 -0
  141. package/dist/gitDelivery/syncRoutes.d.ts +13 -0
  142. package/dist/gitDelivery/syncRoutes.d.ts.map +1 -0
  143. package/dist/gitDelivery/syncRoutes.js +200 -0
  144. package/dist/gitPorcelainStatus.d.ts +15 -0
  145. package/dist/gitPorcelainStatus.d.ts.map +1 -0
  146. package/dist/gitPorcelainStatus.js +104 -0
  147. package/dist/gitRepositoryReads.d.ts +10 -0
  148. package/dist/gitRepositoryReads.d.ts.map +1 -0
  149. package/dist/gitRepositoryReads.js +314 -0
  150. package/dist/gitRepositoryRoutes.d.ts +7 -0
  151. package/dist/gitRepositoryRoutes.d.ts.map +1 -0
  152. package/dist/gitRepositoryRoutes.js +221 -0
  153. package/dist/gitRoutes.d.ts +66 -0
  154. package/dist/gitRoutes.d.ts.map +1 -0
  155. package/dist/gitRoutes.js +543 -0
  156. package/dist/governed-workflow.d.ts +2 -0
  157. package/dist/governed-workflow.d.ts.map +1 -1
  158. package/dist/governed-workflow.js +4 -0
  159. package/dist/grounded-qa.d.ts +11 -0
  160. package/dist/grounded-qa.d.ts.map +1 -1
  161. package/dist/grounded-qa.js +13 -4
  162. package/dist/headers.d.ts +4 -1
  163. package/dist/headers.d.ts.map +1 -1
  164. package/dist/headers.js +11 -4
  165. package/dist/index.d.ts +8 -1
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +9 -1
  168. package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts +1 -1
  169. package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts.map +1 -1
  170. package/dist/qualityIntelligence/figmaSnapshotRoutes.js +1 -1
  171. package/dist/read-handlers.d.ts +5 -0
  172. package/dist/read-handlers.d.ts.map +1 -1
  173. package/dist/read-handlers.js +57 -1
  174. package/dist/routes.d.ts.map +1 -1
  175. package/dist/routes.js +259 -6
  176. package/dist/run-engine.d.ts.map +1 -1
  177. package/dist/run-engine.js +3 -0
  178. package/dist/run-handlers.d.ts.map +1 -1
  179. package/dist/run-handlers.js +74 -4
  180. package/dist/run-request.d.ts +11 -0
  181. package/dist/run-request.d.ts.map +1 -1
  182. package/dist/run-request.js +158 -10
  183. package/dist/runtime/capabilityDetector.d.ts +38 -0
  184. package/dist/runtime/capabilityDetector.d.ts.map +1 -0
  185. package/dist/runtime/capabilityDetector.js +443 -0
  186. package/dist/runtime/capabilityRoutes.d.ts +9 -0
  187. package/dist/runtime/capabilityRoutes.d.ts.map +1 -0
  188. package/dist/runtime/capabilityRoutes.js +45 -0
  189. package/dist/runtime/containerEngineDetector.d.ts +17 -0
  190. package/dist/runtime/containerEngineDetector.d.ts.map +1 -0
  191. package/dist/runtime/containerEngineDetector.js +222 -0
  192. package/dist/runtime/containerRoutes.d.ts +8 -0
  193. package/dist/runtime/containerRoutes.d.ts.map +1 -0
  194. package/dist/runtime/containerRoutes.js +207 -0
  195. package/dist/runtime/containerRunner-errors.d.ts +18 -0
  196. package/dist/runtime/containerRunner-errors.d.ts.map +1 -0
  197. package/dist/runtime/containerRunner-errors.js +42 -0
  198. package/dist/runtime/containerRunner-evidence.d.ts +24 -0
  199. package/dist/runtime/containerRunner-evidence.d.ts.map +1 -0
  200. package/dist/runtime/containerRunner-evidence.js +74 -0
  201. package/dist/runtime/containerRunner.d.ts +37 -0
  202. package/dist/runtime/containerRunner.d.ts.map +1 -0
  203. package/dist/runtime/containerRunner.js +443 -0
  204. package/dist/server.d.ts.map +1 -1
  205. package/dist/server.js +24 -4
  206. package/dist/store/schema.d.ts +1 -1
  207. package/dist/store/schema.d.ts.map +1 -1
  208. package/dist/store/schema.js +62 -1
  209. package/dist/task-workspace/active-store.d.ts +21 -0
  210. package/dist/task-workspace/active-store.d.ts.map +1 -0
  211. package/dist/task-workspace/active-store.js +55 -0
  212. package/dist/task-workspace/authorization.d.ts +7 -0
  213. package/dist/task-workspace/authorization.d.ts.map +1 -0
  214. package/dist/task-workspace/authorization.js +54 -0
  215. package/dist/task-workspace/binding.d.ts +3 -0
  216. package/dist/task-workspace/binding.d.ts.map +1 -0
  217. package/dist/task-workspace/binding.js +22 -0
  218. package/dist/task-workspace/cleanup.d.ts +4 -0
  219. package/dist/task-workspace/cleanup.d.ts.map +1 -0
  220. package/dist/task-workspace/cleanup.js +428 -0
  221. package/dist/task-workspace/errors.d.ts +14 -0
  222. package/dist/task-workspace/errors.d.ts.map +1 -0
  223. package/dist/task-workspace/errors.js +81 -0
  224. package/dist/task-workspace/evidence.d.ts +32 -0
  225. package/dist/task-workspace/evidence.d.ts.map +1 -0
  226. package/dist/task-workspace/evidence.js +52 -0
  227. package/dist/task-workspace/field-safety.d.ts +3 -0
  228. package/dist/task-workspace/field-safety.d.ts.map +1 -0
  229. package/dist/task-workspace/field-safety.js +42 -0
  230. package/dist/task-workspace/health.d.ts +4 -0
  231. package/dist/task-workspace/health.d.ts.map +1 -0
  232. package/dist/task-workspace/health.js +163 -0
  233. package/dist/task-workspace/lifecycle.d.ts +3 -0
  234. package/dist/task-workspace/lifecycle.d.ts.map +1 -0
  235. package/dist/task-workspace/lifecycle.js +248 -0
  236. package/dist/task-workspace/locks.d.ts +13 -0
  237. package/dist/task-workspace/locks.d.ts.map +1 -0
  238. package/dist/task-workspace/locks.js +44 -0
  239. package/dist/task-workspace/managed-root.d.ts +7 -0
  240. package/dist/task-workspace/managed-root.d.ts.map +1 -0
  241. package/dist/task-workspace/managed-root.js +98 -0
  242. package/dist/task-workspace/mutex.d.ts +8 -0
  243. package/dist/task-workspace/mutex.d.ts.map +1 -0
  244. package/dist/task-workspace/mutex.js +82 -0
  245. package/dist/task-workspace/naming.d.ts +15 -0
  246. package/dist/task-workspace/naming.d.ts.map +1 -0
  247. package/dist/task-workspace/naming.js +0 -0
  248. package/dist/task-workspace/provisioning.d.ts +3 -0
  249. package/dist/task-workspace/provisioning.d.ts.map +1 -0
  250. package/dist/task-workspace/provisioning.js +528 -0
  251. package/dist/task-workspace/reconciliation.d.ts +15 -0
  252. package/dist/task-workspace/reconciliation.d.ts.map +1 -0
  253. package/dist/task-workspace/reconciliation.js +274 -0
  254. package/dist/task-workspace/repair.d.ts +3 -0
  255. package/dist/task-workspace/repair.d.ts.map +1 -0
  256. package/dist/task-workspace/repair.js +286 -0
  257. package/dist/task-workspace/routes.d.ts +19 -0
  258. package/dist/task-workspace/routes.d.ts.map +1 -0
  259. package/dist/task-workspace/routes.js +481 -0
  260. package/dist/task-workspace/store.d.ts +12 -0
  261. package/dist/task-workspace/store.d.ts.map +1 -0
  262. package/dist/task-workspace/store.js +128 -0
  263. package/dist/task-workspace/types.d.ts +170 -0
  264. package/dist/task-workspace/types.d.ts.map +1 -0
  265. package/dist/task-workspace/types.js +5 -0
  266. package/dist/voice-action-governance.d.ts +23 -0
  267. package/dist/voice-action-governance.d.ts.map +1 -0
  268. package/dist/voice-action-governance.js +126 -0
  269. package/dist/voice-handlers.d.ts +6 -0
  270. package/dist/voice-handlers.d.ts.map +1 -0
  271. package/dist/voice-handlers.js +570 -0
  272. package/dist/voice-realtime-grounded-tool.d.ts +31 -0
  273. package/dist/voice-realtime-grounded-tool.d.ts.map +1 -0
  274. package/dist/voice-realtime-grounded-tool.js +322 -0
  275. package/dist/voice-realtime.d.ts +69 -0
  276. package/dist/voice-realtime.d.ts.map +1 -0
  277. package/dist/voice-realtime.js +787 -0
  278. package/dist/workspace-state-handlers.d.ts +5 -0
  279. package/dist/workspace-state-handlers.d.ts.map +1 -0
  280. package/dist/workspace-state-handlers.js +106 -0
  281. package/package.json +20 -19
@@ -0,0 +1,243 @@
1
+ // Issue #1392 — live editor-agent session registry, bounded action queue, and SSE event bus.
2
+ //
3
+ // The server COORDINATES; the browser bridge OWNS live editor mutation (ADR-0060). This module is the
4
+ // single owner of the in-memory, non-persistent control-plane state that the BFF routes operate over:
5
+ //
6
+ // 1. Session registry — the latest snapshot each browser bridge has published (bounded).
7
+ // 2. Bridge liveness — which sessions currently hold at least one live SSE connection. A queued
8
+ // action is only meaningful when a live bridge can execute it; otherwise the
9
+ // route answers a structured NO_ACTIVE_BRIDGE conflict (AC1).
10
+ // 3. Bounded action queue — in-flight actions awaiting a browser result, each armed with a timeout so
11
+ // a silent bridge cannot strand the queue (AC2). The queue is keyed PER
12
+ // SESSION so a result, timeout, or re-queue for one session can never touch
13
+ // another session's slots; the per-session depth is bounded (perf).
14
+ // 4. Event bus — fan-out of session/action/result events, scoped per session so an action
15
+ // for one session never reaches another session's bridge (bounded fan-out).
16
+ //
17
+ // The registry NEVER mutates editor/React state and NEVER executes an action — it records, bounds,
18
+ // times out, and fans out (AC5). It produces only post-admission *lifecycle* failures (TIMED_OUT /
19
+ // QUEUE_FULL); preflight *conflicts* are the route's policy. No raw source content (snapshot text,
20
+ // edits, patch bodies) is logged here. The factory mirrors the `createRunRegistry` idiom: a small
21
+ // state record plus module-level operations, so each operation stays short and explicitly typed.
22
+ import { EDITOR_AGENT_SCHEMA_VERSION, } from "@oscharko-dev/keiko-contracts";
23
+ // A queued action the bridge never acknowledges within ACTION_TIMEOUT_MS is failed and evicted so the
24
+ // bounded queue self-heals (AC2). MAX_QUEUED_PER_SESSION bounds the in-flight depth per session;
25
+ // MAX_SESSIONS bounds the snapshot registry so a long-lived server cannot grow it without limit.
26
+ export const EDITOR_AGENT_ACTION_TIMEOUT_MS = 15_000;
27
+ export const EDITOR_AGENT_MAX_QUEUED_PER_SESSION = 64;
28
+ export const EDITOR_AGENT_MAX_SESSIONS = 256;
29
+ function defaultSetTimer(handler, ms) {
30
+ const handle = setTimeout(handler, ms);
31
+ handle.unref?.();
32
+ return handle;
33
+ }
34
+ function defaultClearTimer(handle) {
35
+ clearTimeout(handle);
36
+ }
37
+ function lifecycleFailure(action, code, message) {
38
+ return {
39
+ schemaVersion: EDITOR_AGENT_SCHEMA_VERSION,
40
+ actionId: action.actionId,
41
+ sessionId: action.sessionId,
42
+ status: "failed",
43
+ message,
44
+ failure: { code, message },
45
+ };
46
+ }
47
+ function eventSessionId(event) {
48
+ switch (event.type) {
49
+ case "session":
50
+ return event.snapshot.sessionId;
51
+ case "action":
52
+ return event.action.sessionId;
53
+ case "result":
54
+ return event.result.sessionId;
55
+ case "heartbeat":
56
+ return undefined;
57
+ }
58
+ }
59
+ // Bounded fan-out: a session-scoped event reaches the global observers plus that session's own bridge
60
+ // connections, never every bridge. A session-less event (heartbeat) reaches everyone.
61
+ function deliver(state, event) {
62
+ for (const send of state.observers)
63
+ send(event);
64
+ const sessionId = eventSessionId(event);
65
+ if (sessionId === undefined) {
66
+ for (const set of state.bridges.values()) {
67
+ for (const send of set)
68
+ send(event);
69
+ }
70
+ return;
71
+ }
72
+ const set = state.bridges.get(sessionId);
73
+ if (set === undefined)
74
+ return;
75
+ for (const send of set)
76
+ send(event);
77
+ }
78
+ function emit(state, payload) {
79
+ state.eventSeq += 1;
80
+ deliver(state, {
81
+ schemaVersion: EDITOR_AGENT_SCHEMA_VERSION,
82
+ eventId: `editor-agent-${String(state.eventSeq)}`,
83
+ ...payload,
84
+ });
85
+ }
86
+ function clearPending(state, sessionId, actionId) {
87
+ const inner = state.pending.get(sessionId);
88
+ const entry = inner?.get(actionId);
89
+ if (inner === undefined || entry === undefined)
90
+ return false;
91
+ state.clearTimer(entry.timer);
92
+ inner.delete(actionId);
93
+ if (inner.size === 0)
94
+ state.pending.delete(sessionId);
95
+ return true;
96
+ }
97
+ function onTimeout(state, action) {
98
+ const inner = state.pending.get(action.sessionId);
99
+ if (inner?.get(action.actionId) === undefined)
100
+ return;
101
+ inner.delete(action.actionId);
102
+ if (inner.size === 0)
103
+ state.pending.delete(action.sessionId);
104
+ emit(state, {
105
+ type: "result",
106
+ result: lifecycleFailure(action, "TIMED_OUT", "The browser bridge did not report a result before the deadline."),
107
+ });
108
+ }
109
+ function connectImpl(state, sessionId, send) {
110
+ if (sessionId === undefined) {
111
+ state.observers.add(send);
112
+ return () => {
113
+ state.observers.delete(send);
114
+ };
115
+ }
116
+ let set = state.bridges.get(sessionId);
117
+ if (set === undefined) {
118
+ set = new Set();
119
+ state.bridges.set(sessionId, set);
120
+ }
121
+ set.add(send);
122
+ return () => {
123
+ const current = state.bridges.get(sessionId);
124
+ if (current === undefined)
125
+ return;
126
+ current.delete(send);
127
+ if (current.size === 0)
128
+ state.bridges.delete(sessionId);
129
+ };
130
+ }
131
+ function queueActionImpl(state, action, emitAction) {
132
+ const inner = state.pending.get(action.sessionId);
133
+ if (inner?.get(action.actionId) !== undefined) {
134
+ // A second admit for an actionId already in flight would otherwise strand the first action's
135
+ // deadline; reject it instead so the first action still self-heals (AC2). Pure rejection — the
136
+ // existing slot/timer are untouched.
137
+ return {
138
+ kind: "rejected",
139
+ result: {
140
+ schemaVersion: EDITOR_AGENT_SCHEMA_VERSION,
141
+ actionId: action.actionId,
142
+ sessionId: action.sessionId,
143
+ status: "failed",
144
+ message: "An action with this id is already in flight for this session.",
145
+ },
146
+ };
147
+ }
148
+ if ((inner?.size ?? 0) >= state.maxQueuedPerSession) {
149
+ return {
150
+ kind: "rejected",
151
+ result: lifecycleFailure(action, "QUEUE_FULL", "The editor action queue for this session is full; retry once an action completes."),
152
+ };
153
+ }
154
+ const slots = inner ?? new Map();
155
+ if (inner === undefined)
156
+ state.pending.set(action.sessionId, slots);
157
+ const timer = state.setTimer(() => {
158
+ onTimeout(state, action);
159
+ }, state.actionTimeoutMs);
160
+ slots.set(action.actionId, { timer });
161
+ emit(state, { type: "action", action: emitAction });
162
+ return {
163
+ kind: "queued",
164
+ result: {
165
+ schemaVersion: EDITOR_AGENT_SCHEMA_VERSION,
166
+ actionId: action.actionId,
167
+ sessionId: action.sessionId,
168
+ status: "queued",
169
+ },
170
+ };
171
+ }
172
+ function reportResultImpl(state, result) {
173
+ // Correlate strictly by (sessionId, actionId): a result can only resolve its own session's slot, so
174
+ // a mismatched or cross-session result is fanned out for visibility without disturbing the queue.
175
+ clearPending(state, result.sessionId, result.actionId);
176
+ emit(state, { type: "result", result });
177
+ }
178
+ // Bound the snapshot registry: when over the cap, evict the oldest session that is neither bridged nor
179
+ // has in-flight actions (so active work is never disrupted), preferring the just-registered session
180
+ // last. If every other session is busy the registry is left slightly over the soft cap.
181
+ function evictOldestIdleSession(state, keepSessionId) {
182
+ for (const sessionId of state.sessions.keys()) {
183
+ if (sessionId === keepSessionId)
184
+ continue;
185
+ if ((state.bridges.get(sessionId)?.size ?? 0) > 0)
186
+ continue;
187
+ if ((state.pending.get(sessionId)?.size ?? 0) > 0)
188
+ continue;
189
+ state.sessions.delete(sessionId);
190
+ return;
191
+ }
192
+ }
193
+ function resetImpl(state) {
194
+ for (const inner of state.pending.values()) {
195
+ for (const entry of inner.values())
196
+ state.clearTimer(entry.timer);
197
+ }
198
+ state.sessions.clear();
199
+ state.bridges.clear();
200
+ state.observers.clear();
201
+ state.pending.clear();
202
+ state.eventSeq = 0;
203
+ }
204
+ export function createEditorAgentRegistry(options = {}) {
205
+ const state = {
206
+ sessions: new Map(),
207
+ bridges: new Map(),
208
+ observers: new Set(),
209
+ pending: new Map(),
210
+ actionTimeoutMs: options.actionTimeoutMs ?? EDITOR_AGENT_ACTION_TIMEOUT_MS,
211
+ maxQueuedPerSession: options.maxQueuedPerSession ?? EDITOR_AGENT_MAX_QUEUED_PER_SESSION,
212
+ maxSessions: options.maxSessions ?? EDITOR_AGENT_MAX_SESSIONS,
213
+ setTimer: options.setTimer ?? defaultSetTimer,
214
+ clearTimer: options.clearTimer ?? defaultClearTimer,
215
+ eventSeq: 0,
216
+ };
217
+ return {
218
+ registerSnapshot: (snapshot) => {
219
+ state.sessions.set(snapshot.sessionId, snapshot);
220
+ if (state.sessions.size > state.maxSessions) {
221
+ evictOldestIdleSession(state, snapshot.sessionId);
222
+ }
223
+ emit(state, { type: "session", snapshot });
224
+ },
225
+ listSessions: () => [...state.sessions.values()],
226
+ snapshotFor: (sessionId) => state.sessions.get(sessionId),
227
+ selectSnapshot: (sessionId) => sessionId === undefined ? [...state.sessions.values()][0] : state.sessions.get(sessionId),
228
+ hasLiveBridge: (sessionId) => (state.bridges.get(sessionId)?.size ?? 0) > 0,
229
+ liveBridgeCount: (sessionId) => state.bridges.get(sessionId)?.size ?? 0,
230
+ connect: (sessionId, send) => connectImpl(state, sessionId, send),
231
+ queueAction: (action, emitAction) => queueActionImpl(state, action, emitAction),
232
+ reportResult: (result) => {
233
+ reportResultImpl(state, result);
234
+ },
235
+ pendingCount: (sessionId) => state.pending.get(sessionId)?.size ?? 0,
236
+ reset: () => {
237
+ resetImpl(state);
238
+ },
239
+ };
240
+ }
241
+ // Process-wide singleton used by the BFF routes. Unit tests construct isolated instances with injected
242
+ // timers; the route integration/security tests share this singleton and reset it between cases.
243
+ export const editorAgentRegistry = createEditorAgentRegistry();
@@ -1 +1 @@
1
- {"version":3,"file":"completionRoutes.d.ts","sourceRoot":"","sources":["../../src/editor/completionRoutes.ts"],"names":[],"mappings":"AAkBA,OAAO,EAqBL,KAAK,qBAAqB,EAG3B,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAEvE,OAAO,EAAa,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAOtE,OAAO,EAIL,KAAK,WAAW,EAEjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAIL,KAAK,sBAAsB,EAE5B,MAAM,6BAA6B,CAAC;AASrC,eAAO,MAAM,kCAAkC;;;;;;;;;;;;;;;CAGrC,CAAC;AAGX,mGAAmG;AACnG,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,KAAK,WAAW,CAAC;AAE5F,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACzD,kGAAkG;IAClG,QAAQ,CAAC,WAAW,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAC1D,qEAAqE;IACrE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,mGAAmG;IACnG,QAAQ,CAAC,qBAAqB,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACnE,4FAA4F;IAC5F,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;CAC1D;AAufD,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,WAAW,CAAC,CA6CtB"}
1
+ {"version":3,"file":"completionRoutes.d.ts","sourceRoot":"","sources":["../../src/editor/completionRoutes.ts"],"names":[],"mappings":"AAkBA,OAAO,EAqBL,KAAK,qBAAqB,EAG3B,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAa,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAWtE,OAAO,EAIL,KAAK,WAAW,EAEjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAIL,KAAK,sBAAsB,EAE5B,MAAM,6BAA6B,CAAC;AASrC,eAAO,MAAM,kCAAkC;;;;;;;;;;;;;;;CAGrC,CAAC;AAGX,mGAAmG;AACnG,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,KAAK,WAAW,CAAC;AAE5F,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACzD,kGAAkG;IAClG,QAAQ,CAAC,WAAW,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAC1D,qEAAqE;IACrE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,mGAAmG;IACnG,QAAQ,CAAC,qBAAqB,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACnE,4FAA4F;IAC5F,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;CAC1D;AA2fD,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,WAAW,CAAC,CA8CtB"}
@@ -17,15 +17,13 @@
17
17
  // Model Gateway, retrieval, or any provider directly (Acceptance Criterion 5).
18
18
  import { CODING_CONTEXT_SCHEMA_VERSION, CODING_CONTEXT_BUDGETS, DEFAULT_LANGUAGE_SERVICE_LIMITS, EDITOR_COMPLETION_SCHEMA_VERSION, isValidScopePath, parseEditorCompletionRequest, stripUnsafeFormatChars, toCodingContextWirePack, } from "@oscharko-dev/keiko-contracts";
19
19
  import { Gateway, selectCompletionModel } from "@oscharko-dev/keiko-model-gateway";
20
- import { nodeWorkspaceFs } from "@oscharko-dev/keiko-workspace/internal/fs";
21
20
  import { errorBody } from "../routes.js";
22
21
  import { currentGatewayConfig } from "../deps.js";
23
22
  import { readJsonObject, resolveRoot, runFilesHandler } from "../files.js";
24
23
  import { assembleCodingContext } from "./codingContext.js";
25
24
  import { recordCodingContextEvidence } from "./codingContextEvidence.js";
26
25
  import { recordEditorCompletionModelEvidence } from "./completionModelEvidence.js";
27
- import { clientAbortSignal, resolveOverlayPath, STATUS_BY_CODE } from "./languageRoutes.js";
28
- import { runLanguageOperation } from "./languageService.js";
26
+ import { clientAbortSignal, resolveOverlayPath, runEditorLanguageOperation, STATUS_BY_CODE, } from "./languageRoutes.js";
29
27
  import { buildModelCompletionPrompt, generateModelCompletions, } from "./editorCompletionModel.js";
30
28
  import { estimateEditorModelReservationTokens, settleModelUsage, sharedEditorModelTokenBudget, } from "./editorModelTokenBudget.js";
31
29
  // The overlay buffer may be up to the document-size cap; allow 64 KiB of JSON envelope on top.
@@ -347,18 +345,14 @@ function buildWireResponse(deterministic, model) {
347
345
  provenance: buildProvenance(model, producedModelItems),
348
346
  };
349
347
  }
350
- function runDeterministicCompletion(input) {
348
+ async function runDeterministicCompletion(input) {
351
349
  const langRequest = {
352
350
  operation: "completion",
353
351
  root: input.request.root,
354
352
  document: input.request.document,
355
353
  position: input.request.position,
356
354
  };
357
- const outcome = runLanguageOperation(langRequest, {
358
- fs: nodeWorkspaceFs,
359
- realRoot: input.realRoot,
360
- overlayAbsolutePath: input.overlayAbsolutePath,
361
- signal: input.signal,
355
+ const outcome = await runEditorLanguageOperation(langRequest, input.deps, input.realRoot, input.overlayAbsolutePath, input.signal, {
362
356
  limits: input.limits ?? COMPLETION_LANGUAGE_SERVICE_LIMITS,
363
357
  now: input.now,
364
358
  });
@@ -393,8 +387,9 @@ export async function handleEditorCompletion(ctx, deps, options = {}) {
393
387
  }
394
388
  const signal = clientAbortSignal(ctx);
395
389
  // Tier 1: deterministic language-service completion (always).
396
- const deterministic = runDeterministicCompletion({
390
+ const deterministic = await runDeterministicCompletion({
397
391
  request: sanitizedRequest,
392
+ deps,
398
393
  realRoot: root.realRoot,
399
394
  overlayAbsolutePath,
400
395
  signal,
@@ -1,15 +1,26 @@
1
- import { type LanguageServiceErrorCode, type LanguageServiceLimits } from "@oscharko-dev/keiko-contracts";
1
+ import { type LanguageProviderDescriptor, type LanguageServiceErrorCode, type LanguageServiceLimits, type LanguageServiceRequest } from "@oscharko-dev/keiko-contracts";
2
+ import type { CommandRule } from "@oscharko-dev/keiko-tools";
2
3
  import { type RouteContext, type RouteResult } from "../routes.js";
3
4
  import type { UiHandlerDeps } from "../deps.js";
5
+ import type { LanguageServiceOutcome } from "./languageService.js";
6
+ import type { LspSpawnFn } from "./lsp/lspNodeAdapter.js";
4
7
  export interface EditorLanguageRouteOptions {
5
8
  /** Test-only deterministic limits seam; production keeps the default deadline. */
6
9
  readonly limits?: LanguageServiceLimits | undefined;
7
10
  /** Test-only deterministic clock seam; production keeps the real clock. */
8
11
  readonly now?: (() => number) | undefined;
12
+ /** Test-only provider descriptor override seam for capabilities route coverage. */
13
+ readonly capabilityDescriptorOverrides?: readonly LanguageProviderDescriptor[] | undefined;
14
+ /** Test-only command-policy seam for host-provider detection. */
15
+ readonly hostLanguageCommandRules?: readonly CommandRule[] | undefined;
16
+ /** Test-only LSP spawn seam for host-provider operation coverage. */
17
+ readonly hostLanguageSpawn?: LspSpawnFn | undefined;
9
18
  }
10
19
  export declare const STATUS_BY_CODE: Readonly<Record<LanguageServiceErrorCode, number>>;
11
20
  export declare function resolveOverlayPath(realRoot: string, relativePath: string): string;
12
21
  export declare function clientAbortSignal(ctx: RouteContext): AbortSignal;
13
22
  export declare function handleEditorLanguage(ctx: RouteContext, deps: UiHandlerDeps, options?: EditorLanguageRouteOptions): Promise<RouteResult>;
14
23
  export declare function handleEditorLanguageCapabilities(): RouteResult;
24
+ export declare function runEditorLanguageOperation(request: LanguageServiceRequest, deps: UiHandlerDeps, realRoot: string, overlayAbsolutePath: string, signal: AbortSignal, options?: EditorLanguageRouteOptions): Promise<LanguageServiceOutcome>;
25
+ export declare function handleEditorLanguageCapabilitiesForRoute(ctx: RouteContext, deps: UiHandlerDeps, options?: EditorLanguageRouteOptions): Promise<RouteResult>;
15
26
  //# sourceMappingURL=languageRoutes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"languageRoutes.d.ts","sourceRoot":"","sources":["../../src/editor/languageRoutes.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC3B,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAAa,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAShD,MAAM,WAAW,0BAA0B;IACzC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACpD,2EAA2E;IAC3E,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;CAC3C;AAID,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAQ7E,CAAC;AAaF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAWjF;AAID,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,WAAW,CAahE;AAiBD,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,WAAW,CAAC,CAuBtB;AAED,wBAAgB,gCAAgC,IAAI,WAAW,CAE9D"}
1
+ {"version":3,"file":"languageRoutes.d.ts","sourceRoot":"","sources":["../../src/editor/languageRoutes.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC5B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAG7D,OAAO,EAAa,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAMnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAK1D,MAAM,WAAW,0BAA0B;IACzC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACpD,2EAA2E;IAC3E,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,mFAAmF;IACnF,QAAQ,CAAC,6BAA6B,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAC;IAC3F,iEAAiE;IACjE,QAAQ,CAAC,wBAAwB,CAAC,EAAE,SAAS,WAAW,EAAE,GAAG,SAAS,CAAC;IACvE,qEAAqE;IACrE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACrD;AAID,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAQ7E,CAAC;AAaF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAWjF;AAID,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,WAAW,CAahE;AAiBD,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,WAAW,CAAC,CAuBtB;AAED,wBAAgB,gCAAgC,IAAI,WAAW,CAE9D;AA0BD,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,sBAAsB,EAC/B,IAAI,EAAE,aAAa,EACnB,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAC3B,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,sBAAsB,CAAC,CAsBjC;AAED,wBAAsB,wCAAwC,CAC5D,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,WAAW,CAAC,CAuBtB"}
@@ -13,6 +13,8 @@ import { errorBody } from "../routes.js";
13
13
  import { FilesError, readJsonObject, resolveRoot, runFilesHandler } from "../files.js";
14
14
  import { DENIED_MESSAGE, pathIsDenied } from "../files-deny.js";
15
15
  import { describeLanguageCapabilities, runLanguageOperation } from "./languageService.js";
16
+ import { detectHostLanguageProviderDescriptors, HOST_LANGUAGE_PROVIDER_SPECS, } from "./lsp/hostLanguageProviders.js";
17
+ import { runHostLanguageOperation } from "./lsp/hostLanguageOperation.js";
16
18
  // The overlay buffer may be up to the document-size cap; allow 64 KiB of JSON envelope on top.
17
19
  const MAX_LANGUAGE_BODY_BYTES = DEFAULT_LANGUAGE_SERVICE_LIMITS.maxDocumentBytes + 64 * 1024;
18
20
  // Exported for reuse by the completion route (#1199), which maps the same deterministic
@@ -90,17 +92,78 @@ export async function handleEditorLanguage(ctx, deps, options = {}) {
90
92
  return runFilesHandler(async () => {
91
93
  const root = await resolveRoot(deps.store, request.root, deps.redactor);
92
94
  const overlayAbsolutePath = resolveOverlayPath(root.realRoot, request.document.path);
93
- const outcome = runLanguageOperation(request, {
94
- fs: nodeWorkspaceFs,
95
- realRoot: root.realRoot,
96
- overlayAbsolutePath,
97
- signal: clientAbortSignal(ctx),
98
- limits: options.limits,
99
- now: options.now,
100
- });
95
+ const outcome = await runEditorLanguageOperation(request, deps, root.realRoot, overlayAbsolutePath, clientAbortSignal(ctx), options);
101
96
  return outcomeToResult(outcome, deps);
102
97
  });
103
98
  }
104
99
  export function handleEditorLanguageCapabilities() {
105
100
  return { status: 200, body: describeLanguageCapabilities() };
106
101
  }
102
+ function defaultHostLanguageCommandRules() {
103
+ const names = new Set();
104
+ for (const spec of HOST_LANGUAGE_PROVIDER_SPECS) {
105
+ names.add(spec.executableName);
106
+ for (const executable of spec.requiredExecutables)
107
+ names.add(executable);
108
+ }
109
+ return [...names].sort().map((executable) => ({ executable }));
110
+ }
111
+ function workspaceForRoot(realRoot) {
112
+ return {
113
+ root: realRoot,
114
+ name: undefined,
115
+ version: undefined,
116
+ testFramework: "unknown",
117
+ sourceDirs: [],
118
+ testDirs: [],
119
+ languages: [],
120
+ ignoreLines: [],
121
+ };
122
+ }
123
+ export async function runEditorLanguageOperation(request, deps, realRoot, overlayAbsolutePath, signal, options = {}) {
124
+ const hostOutcome = await runHostLanguageOperation(request, {
125
+ workspace: workspaceForRoot(realRoot),
126
+ processEnv: deps.env,
127
+ commandRules: options.hostLanguageCommandRules ?? defaultHostLanguageCommandRules(),
128
+ overlayAbsolutePath,
129
+ signal,
130
+ limits: options.limits,
131
+ now: options.now,
132
+ ...(options.hostLanguageSpawn !== undefined ? { spawn: options.hostLanguageSpawn } : {}),
133
+ });
134
+ if (hostOutcome !== undefined) {
135
+ return hostOutcome;
136
+ }
137
+ return runLanguageOperation(request, {
138
+ fs: nodeWorkspaceFs,
139
+ realRoot,
140
+ overlayAbsolutePath,
141
+ signal,
142
+ limits: options.limits,
143
+ now: options.now,
144
+ });
145
+ }
146
+ export async function handleEditorLanguageCapabilitiesForRoute(ctx, deps, options = {}) {
147
+ const root = ctx.url.searchParams.get("root")?.trim() ?? "";
148
+ if (root.length === 0) {
149
+ return {
150
+ status: 200,
151
+ body: describeLanguageCapabilities(undefined, options.capabilityDescriptorOverrides),
152
+ };
153
+ }
154
+ return runFilesHandler(async () => {
155
+ const resolved = await resolveRoot(deps.store, root, deps.redactor);
156
+ const detected = detectHostLanguageProviderDescriptors({
157
+ workspace: workspaceForRoot(resolved.realRoot),
158
+ processEnv: deps.env,
159
+ commandRules: options.hostLanguageCommandRules ?? defaultHostLanguageCommandRules(),
160
+ });
161
+ return {
162
+ status: 200,
163
+ body: describeLanguageCapabilities(undefined, [
164
+ ...detected,
165
+ ...(options.capabilityDescriptorOverrides ?? []),
166
+ ]),
167
+ };
168
+ });
169
+ }
@@ -1,8 +1,9 @@
1
- import { type LanguageCompletionResult, type LanguageDiagnosticsResult, type LanguageFormattingResult, type LanguageHoverResult, type LanguageServiceCapabilities, type LanguageServiceErrorCode, type LanguageServiceLimits, type LanguageServiceRequest, type LanguageSymbolResult } from "@oscharko-dev/keiko-contracts";
1
+ import { type LanguageCompletionResult, type LanguageDiagnosticsResult, type LanguageFormattingResult, type LanguageHoverResult, type LanguageServiceCapabilities, type LanguageServiceErrorCode, type LanguageProviderDescriptor, type LanguageServiceLimits, type LanguageServiceRequest, type LanguageSymbolResult } from "@oscharko-dev/keiko-contracts";
2
2
  import type { WorkspaceFs } from "@oscharko-dev/keiko-workspace";
3
3
  import { type LanguageProviderRegistry } from "./languageProvider.js";
4
4
  export declare function languageServiceRegistry(): LanguageProviderRegistry;
5
- export declare function describeLanguageCapabilities(registry?: LanguageProviderRegistry): LanguageServiceCapabilities;
5
+ export declare const NO_PROVIDER_UNAVAILABLE_REASON: "No language provider is configured for this language.";
6
+ export declare function describeLanguageCapabilities(registry?: LanguageProviderRegistry, descriptorOverrides?: readonly LanguageProviderDescriptor[]): LanguageServiceCapabilities;
6
7
  export type LanguageServiceOutcome = {
7
8
  readonly kind: "diagnostics";
8
9
  readonly result: LanguageDiagnosticsResult;
@@ -1 +1 @@
1
- {"version":3,"file":"languageService.d.ts","sourceRoot":"","sources":["../../src/editor/languageService.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EAE1B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,uBAAuB,CAAC;AAsB/B,wBAAgB,uBAAuB,IAAI,wBAAwB,CAElE;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,GAAE,wBAA0C,GACnD,2BAA2B,CAK7B;AAED,MAAM,MAAM,sBAAsB,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAA;CAAE,GAC5E;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,GAChE;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,GACnE;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IAEzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,wBAAwB,GAAG,SAAS,CAAC;CAC1D;AAsCD,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,2BAA2B,GACnC,sBAAsB,CAsCxB"}
1
+ {"version":3,"file":"languageService.d.ts","sourceRoot":"","sources":["../../src/editor/languageService.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAC/B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EAG1B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,uBAAuB,CAAC;AAsB/B,wBAAgB,uBAAuB,IAAI,wBAAwB,CAElE;AAKD,eAAO,MAAM,8BAA8B,EACzC,uDAAgE,CAAC;AASnE,wBAAgB,4BAA4B,CAC1C,QAAQ,GAAE,wBAA0C,EACpD,mBAAmB,GAAE,SAAS,0BAA0B,EAAO,GAC9D,2BAA2B,CA+B7B;AAED,MAAM,MAAM,sBAAsB,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAA;CAAE,GAC5E;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,GAChE;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,GACnE;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IAEzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,wBAAwB,GAAG,SAAS,CAAC;CAC1D;AAsCD,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,2BAA2B,GACnC,sBAAsB,CAsCxB"}
@@ -4,7 +4,7 @@
4
4
  // cancellation token, and sanitises every result for browser display. It never calls a model and
5
5
  // never routes through the Model Gateway. Provider registration is the only thing that changes to
6
6
  // add a language (#1213), so this orchestrator stays language-agnostic.
7
- import { DEFAULT_LANGUAGE_SERVICE_LIMITS, LANGUAGE_SERVICE_SCHEMA_VERSION, } from "@oscharko-dev/keiko-contracts";
7
+ import { DEFAULT_LANGUAGE_SERVICE_LIMITS, EDITOR_LANGUAGE_MODE_IDS, LANGUAGE_SERVICE_SCHEMA_VERSION, } from "@oscharko-dev/keiko-contracts";
8
8
  import { createDeadlineCancellation, isCancellation } from "./languageCancellation.js";
9
9
  import { createLanguageProviderRegistry, } from "./languageProvider.js";
10
10
  import { createTypescriptLanguageProvider } from "./typescriptLanguageProvider.js";
@@ -16,10 +16,48 @@ const defaultRegistry = createLanguageProviderRegistry([createTypescriptLanguage
16
16
  export function languageServiceRegistry() {
17
17
  return defaultRegistry;
18
18
  }
19
- export function describeLanguageCapabilities(registry = defaultRegistry) {
19
+ // Issue #1379 AC2 (ADR-0067 D3) — the content-free reason advertised for a known mode-map language
20
+ // that no registered provider serves. Distinct from the external-LSP "no executable configured"
21
+ // reason: this language has no provider at all, real or unavailable.
22
+ export const NO_PROVIDER_UNAVAILABLE_REASON = "No language provider is configured for this language.";
23
+ // Issue #1379 AC2 (ADR-0067 D3) — exhaustive over the canonical mode-map universe. We project the
24
+ // registered descriptors (available providers + unavailable external LSP descriptors), then append a
25
+ // synthetic `unavailable` descriptor for every known mode-map language that no descriptor already
26
+ // covers. After this, `providerForLanguage` returns a descriptor for EVERY known language id, never
27
+ // null, so AC2 holds at the registry layer rather than by UI patching. The synthesis is additive and
28
+ // read-only — it changes no operation-execution path. `runLanguageOperation` is untouched: an
29
+ // unmatched/unavailable language still yields UNSUPPORTED_LANGUAGE (AC3).
30
+ export function describeLanguageCapabilities(registry = defaultRegistry, descriptorOverrides = []) {
31
+ const overridesById = new Map(descriptorOverrides.map((descriptor) => [descriptor.id, descriptor]));
32
+ const descriptors = registry
33
+ .describe()
34
+ .map((descriptor) => overridesById.get(descriptor.id) ?? descriptor);
35
+ for (const descriptor of descriptorOverrides) {
36
+ if (!descriptors.some((entry) => entry.id === descriptor.id)) {
37
+ descriptors.push(descriptor);
38
+ }
39
+ }
40
+ const covered = new Set();
41
+ for (const descriptor of descriptors) {
42
+ for (const language of descriptor.languages) {
43
+ covered.add(language);
44
+ }
45
+ }
46
+ const synthesized = [];
47
+ for (const languageId of EDITOR_LANGUAGE_MODE_IDS) {
48
+ if (covered.has(languageId))
49
+ continue;
50
+ synthesized.push({
51
+ id: "none",
52
+ languages: [languageId],
53
+ operations: [],
54
+ availability: "unavailable",
55
+ unavailableReason: NO_PROVIDER_UNAVAILABLE_REASON,
56
+ });
57
+ }
20
58
  return {
21
59
  schemaVersion: LANGUAGE_SERVICE_SCHEMA_VERSION,
22
- providers: registry.describe(),
60
+ providers: [...descriptors, ...synthesized],
23
61
  };
24
62
  }
25
63
  function errorOutcome(code, message) {
@@ -1 +1 @@
1
- {"version":3,"file":"languageServiceHost.d.ts","sourceRoot":"","sources":["../../src/editor/languageServiceHost.ts"],"names":[],"mappings":"AAUA,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,+BAA+B,CAAC;AAEhF,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5B,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,EAAE,CAAC,qBAAqB,CAAC;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;CACrD;AAgPD,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,oBAAoB,GAC5B,EAAE,CAAC,mBAAmB,CAiDxB"}
1
+ {"version":3,"file":"languageServiceHost.d.ts","sourceRoot":"","sources":["../../src/editor/languageServiceHost.ts"],"names":[],"mappings":"AAUA,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,+BAA+B,CAAC;AAEhF,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5B,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,EAAE,CAAC,qBAAqB,CAAC;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;CACrD;AA8OD,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,oBAAoB,GAC5B,EAAE,CAAC,mBAAmB,CAiDxB"}
@@ -153,7 +153,7 @@ function createContainedReaders(fs, realRoot, overlayPath, overlayText, libDir,
153
153
  }
154
154
  // Resolves the compiler options for the overlay: the nearest tsconfig inside the root (following
155
155
  // `extends` through the contained reader, never enumerating files) merged over safe defaults.
156
- function resolveCompilerOptions(fs, realRoot, overlayPath, readers) {
156
+ function resolveCompilerOptions(overlayPath, readers) {
157
157
  const defaults = defaultCompilerOptions();
158
158
  const configPath = ts.findConfigFile(dirname(overlayPath), readers.fileExists, "tsconfig.json");
159
159
  if (configPath === undefined || readers.containedReal(configPath) === undefined) {
@@ -207,7 +207,7 @@ export function createContainedLanguageServiceHost(options) {
207
207
  const limits = options.limits ?? DEFAULT_LANGUAGE_SERVICE_LIMITS;
208
208
  const libDir = normalizeSlashes(dirname(ts.getDefaultLibFilePath(defaultCompilerOptions())));
209
209
  const readers = createContainedReaders(fs, realRoot, overlayPath, overlayText, libDir, cancellation, limits);
210
- const compilerOptions = resolveCompilerOptions(fs, realRoot, overlayPath, readers);
210
+ const compilerOptions = resolveCompilerOptions(overlayPath, readers);
211
211
  const overlay = normalizeSlashes(overlayPath);
212
212
  return {
213
213
  getCompilationSettings: () => compilerOptions,
@@ -0,0 +1,17 @@
1
+ import { type LanguageServiceLimits, type LanguageServiceRequest } from "@oscharko-dev/keiko-contracts";
2
+ import type { CommandRule } from "@oscharko-dev/keiko-tools";
3
+ import type { WorkspaceInfo } from "@oscharko-dev/keiko-workspace";
4
+ import type { LanguageServiceOutcome } from "../languageService.js";
5
+ import { type LspSpawnFn } from "./lspNodeAdapter.js";
6
+ export interface HostLanguageOperationOptions {
7
+ readonly workspace: WorkspaceInfo;
8
+ readonly processEnv: NodeJS.ProcessEnv;
9
+ readonly commandRules: readonly CommandRule[];
10
+ readonly overlayAbsolutePath: string;
11
+ readonly signal: AbortSignal;
12
+ readonly limits?: LanguageServiceLimits | undefined;
13
+ readonly now?: (() => number) | undefined;
14
+ readonly spawn?: LspSpawnFn | undefined;
15
+ }
16
+ export declare function runHostLanguageOperation(request: LanguageServiceRequest, options: HostLanguageOperationOptions): Promise<LanguageServiceOutcome | undefined>;
17
+ //# sourceMappingURL=hostLanguageOperation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hostLanguageOperation.d.ts","sourceRoot":"","sources":["../../../src/editor/lsp/hostLanguageOperation.ts"],"names":[],"mappings":"AACA,OAAO,EAWL,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAI5B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AASpE,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAOvE,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IACvC,QAAQ,CAAC,YAAY,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9C,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACzC;AA8cD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CA+C7C"}