@oscharko-dev/keiko-server 0.2.7 → 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 (302) 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 +28 -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 +63 -0
  84. package/dist/files.d.ts.map +1 -1
  85. package/dist/files.js +799 -1
  86. package/dist/gateway-readiness.d.ts +6 -0
  87. package/dist/gateway-readiness.d.ts.map +1 -0
  88. package/dist/gateway-readiness.js +624 -0
  89. package/dist/gateway-setup.d.ts +2 -0
  90. package/dist/gateway-setup.d.ts.map +1 -1
  91. package/dist/gateway-setup.js +275 -11
  92. package/dist/gitDelivery/actionSheetProjection.d.ts +30 -0
  93. package/dist/gitDelivery/actionSheetProjection.d.ts.map +1 -0
  94. package/dist/gitDelivery/actionSheetProjection.js +206 -0
  95. package/dist/gitDelivery/actionSheetRoutes.d.ts +29 -0
  96. package/dist/gitDelivery/actionSheetRoutes.d.ts.map +1 -0
  97. package/dist/gitDelivery/actionSheetRoutes.js +293 -0
  98. package/dist/gitDelivery/agentOperationsRoutes.d.ts +33 -0
  99. package/dist/gitDelivery/agentOperationsRoutes.d.ts.map +1 -0
  100. package/dist/gitDelivery/agentOperationsRoutes.js +405 -0
  101. package/dist/gitDelivery/commitRoutes.d.ts +23 -0
  102. package/dist/gitDelivery/commitRoutes.d.ts.map +1 -0
  103. package/dist/gitDelivery/commitRoutes.js +204 -0
  104. package/dist/gitDelivery/evidenceRoutes.d.ts +9 -0
  105. package/dist/gitDelivery/evidenceRoutes.d.ts.map +1 -0
  106. package/dist/gitDelivery/evidenceRoutes.js +101 -0
  107. package/dist/gitDelivery/execution.d.ts +38 -0
  108. package/dist/gitDelivery/execution.d.ts.map +1 -0
  109. package/dist/gitDelivery/execution.js +117 -0
  110. package/dist/gitDelivery/localMutationRoutes.d.ts +30 -0
  111. package/dist/gitDelivery/localMutationRoutes.d.ts.map +1 -0
  112. package/dist/gitDelivery/localMutationRoutes.js +165 -0
  113. package/dist/gitDelivery/mergeExecution.d.ts +63 -0
  114. package/dist/gitDelivery/mergeExecution.d.ts.map +1 -0
  115. package/dist/gitDelivery/mergeExecution.js +168 -0
  116. package/dist/gitDelivery/mergeRoutes.d.ts +12 -0
  117. package/dist/gitDelivery/mergeRoutes.d.ts.map +1 -0
  118. package/dist/gitDelivery/mergeRoutes.js +218 -0
  119. package/dist/gitDelivery/mutationEvidenceLedger.d.ts +23 -0
  120. package/dist/gitDelivery/mutationEvidenceLedger.d.ts.map +1 -0
  121. package/dist/gitDelivery/mutationEvidenceLedger.js +87 -0
  122. package/dist/gitDelivery/prExecution.d.ts +54 -0
  123. package/dist/gitDelivery/prExecution.d.ts.map +1 -0
  124. package/dist/gitDelivery/prExecution.js +192 -0
  125. package/dist/gitDelivery/prRoutes.d.ts +12 -0
  126. package/dist/gitDelivery/prRoutes.d.ts.map +1 -0
  127. package/dist/gitDelivery/prRoutes.js +256 -0
  128. package/dist/gitDelivery/pushExecution.d.ts +43 -0
  129. package/dist/gitDelivery/pushExecution.d.ts.map +1 -0
  130. package/dist/gitDelivery/pushExecution.js +124 -0
  131. package/dist/gitDelivery/pushRoutes.d.ts +12 -0
  132. package/dist/gitDelivery/pushRoutes.d.ts.map +1 -0
  133. package/dist/gitDelivery/pushRoutes.js +200 -0
  134. package/dist/gitDelivery/requestGuards.d.ts +15 -0
  135. package/dist/gitDelivery/requestGuards.d.ts.map +1 -0
  136. package/dist/gitDelivery/requestGuards.js +97 -0
  137. package/dist/gitDelivery/syncEvidence.d.ts +37 -0
  138. package/dist/gitDelivery/syncEvidence.d.ts.map +1 -0
  139. package/dist/gitDelivery/syncEvidence.js +85 -0
  140. package/dist/gitDelivery/syncExecution.d.ts +30 -0
  141. package/dist/gitDelivery/syncExecution.d.ts.map +1 -0
  142. package/dist/gitDelivery/syncExecution.js +266 -0
  143. package/dist/gitDelivery/syncRoutes.d.ts +13 -0
  144. package/dist/gitDelivery/syncRoutes.d.ts.map +1 -0
  145. package/dist/gitDelivery/syncRoutes.js +200 -0
  146. package/dist/gitPorcelainStatus.d.ts +15 -0
  147. package/dist/gitPorcelainStatus.d.ts.map +1 -0
  148. package/dist/gitPorcelainStatus.js +104 -0
  149. package/dist/gitRepositoryReads.d.ts +10 -0
  150. package/dist/gitRepositoryReads.d.ts.map +1 -0
  151. package/dist/gitRepositoryReads.js +314 -0
  152. package/dist/gitRepositoryRoutes.d.ts +7 -0
  153. package/dist/gitRepositoryRoutes.d.ts.map +1 -0
  154. package/dist/gitRepositoryRoutes.js +221 -0
  155. package/dist/gitRoutes.d.ts +66 -0
  156. package/dist/gitRoutes.d.ts.map +1 -0
  157. package/dist/gitRoutes.js +543 -0
  158. package/dist/governed-workflow.d.ts +2 -0
  159. package/dist/governed-workflow.d.ts.map +1 -1
  160. package/dist/governed-workflow.js +4 -0
  161. package/dist/grounded-qa-hybrid.d.ts.map +1 -1
  162. package/dist/grounded-qa-hybrid.js +2 -0
  163. package/dist/grounded-qa-multi-source.d.ts.map +1 -1
  164. package/dist/grounded-qa-multi-source.js +1 -0
  165. package/dist/grounded-qa.d.ts +11 -0
  166. package/dist/grounded-qa.d.ts.map +1 -1
  167. package/dist/grounded-qa.js +14 -4
  168. package/dist/headers.d.ts +4 -1
  169. package/dist/headers.d.ts.map +1 -1
  170. package/dist/headers.js +11 -4
  171. package/dist/index.d.ts +8 -1
  172. package/dist/index.d.ts.map +1 -1
  173. package/dist/index.js +9 -1
  174. package/dist/local-knowledge-grounded-qa.d.ts.map +1 -1
  175. package/dist/local-knowledge-grounded-qa.js +11 -2
  176. package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts +1 -1
  177. package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts.map +1 -1
  178. package/dist/qualityIntelligence/figmaSnapshotRoutes.js +1 -1
  179. package/dist/read-handlers.d.ts +5 -0
  180. package/dist/read-handlers.d.ts.map +1 -1
  181. package/dist/read-handlers.js +57 -1
  182. package/dist/routes.d.ts.map +1 -1
  183. package/dist/routes.js +260 -12
  184. package/dist/run-engine.d.ts.map +1 -1
  185. package/dist/run-engine.js +3 -0
  186. package/dist/run-handlers.d.ts +0 -1
  187. package/dist/run-handlers.d.ts.map +1 -1
  188. package/dist/run-handlers.js +64 -211
  189. package/dist/run-request.d.ts +11 -0
  190. package/dist/run-request.d.ts.map +1 -1
  191. package/dist/run-request.js +158 -10
  192. package/dist/runtime/capabilityDetector.d.ts +38 -0
  193. package/dist/runtime/capabilityDetector.d.ts.map +1 -0
  194. package/dist/runtime/capabilityDetector.js +443 -0
  195. package/dist/runtime/capabilityRoutes.d.ts +9 -0
  196. package/dist/runtime/capabilityRoutes.d.ts.map +1 -0
  197. package/dist/runtime/capabilityRoutes.js +45 -0
  198. package/dist/runtime/containerEngineDetector.d.ts +17 -0
  199. package/dist/runtime/containerEngineDetector.d.ts.map +1 -0
  200. package/dist/runtime/containerEngineDetector.js +222 -0
  201. package/dist/runtime/containerRoutes.d.ts +8 -0
  202. package/dist/runtime/containerRoutes.d.ts.map +1 -0
  203. package/dist/runtime/containerRoutes.js +207 -0
  204. package/dist/runtime/containerRunner-errors.d.ts +18 -0
  205. package/dist/runtime/containerRunner-errors.d.ts.map +1 -0
  206. package/dist/runtime/containerRunner-errors.js +42 -0
  207. package/dist/runtime/containerRunner-evidence.d.ts +24 -0
  208. package/dist/runtime/containerRunner-evidence.d.ts.map +1 -0
  209. package/dist/runtime/containerRunner-evidence.js +74 -0
  210. package/dist/runtime/containerRunner.d.ts +37 -0
  211. package/dist/runtime/containerRunner.d.ts.map +1 -0
  212. package/dist/runtime/containerRunner.js +443 -0
  213. package/dist/server.d.ts.map +1 -1
  214. package/dist/server.js +24 -4
  215. package/dist/store/db.d.ts.map +1 -1
  216. package/dist/store/db.js +2 -1
  217. package/dist/store/index.d.ts +1 -1
  218. package/dist/store/index.d.ts.map +1 -1
  219. package/dist/store/messages.d.ts +2 -1
  220. package/dist/store/messages.d.ts.map +1 -1
  221. package/dist/store/messages.js +46 -4
  222. package/dist/store/schema.d.ts +1 -1
  223. package/dist/store/schema.d.ts.map +1 -1
  224. package/dist/store/schema.js +68 -1
  225. package/dist/store/types.d.ts +3 -2
  226. package/dist/store/types.d.ts.map +1 -1
  227. package/dist/task-workspace/active-store.d.ts +21 -0
  228. package/dist/task-workspace/active-store.d.ts.map +1 -0
  229. package/dist/task-workspace/active-store.js +55 -0
  230. package/dist/task-workspace/authorization.d.ts +7 -0
  231. package/dist/task-workspace/authorization.d.ts.map +1 -0
  232. package/dist/task-workspace/authorization.js +54 -0
  233. package/dist/task-workspace/binding.d.ts +3 -0
  234. package/dist/task-workspace/binding.d.ts.map +1 -0
  235. package/dist/task-workspace/binding.js +22 -0
  236. package/dist/task-workspace/cleanup.d.ts +4 -0
  237. package/dist/task-workspace/cleanup.d.ts.map +1 -0
  238. package/dist/task-workspace/cleanup.js +428 -0
  239. package/dist/task-workspace/errors.d.ts +14 -0
  240. package/dist/task-workspace/errors.d.ts.map +1 -0
  241. package/dist/task-workspace/errors.js +81 -0
  242. package/dist/task-workspace/evidence.d.ts +32 -0
  243. package/dist/task-workspace/evidence.d.ts.map +1 -0
  244. package/dist/task-workspace/evidence.js +52 -0
  245. package/dist/task-workspace/field-safety.d.ts +3 -0
  246. package/dist/task-workspace/field-safety.d.ts.map +1 -0
  247. package/dist/task-workspace/field-safety.js +42 -0
  248. package/dist/task-workspace/health.d.ts +4 -0
  249. package/dist/task-workspace/health.d.ts.map +1 -0
  250. package/dist/task-workspace/health.js +163 -0
  251. package/dist/task-workspace/lifecycle.d.ts +3 -0
  252. package/dist/task-workspace/lifecycle.d.ts.map +1 -0
  253. package/dist/task-workspace/lifecycle.js +248 -0
  254. package/dist/task-workspace/locks.d.ts +13 -0
  255. package/dist/task-workspace/locks.d.ts.map +1 -0
  256. package/dist/task-workspace/locks.js +44 -0
  257. package/dist/task-workspace/managed-root.d.ts +7 -0
  258. package/dist/task-workspace/managed-root.d.ts.map +1 -0
  259. package/dist/task-workspace/managed-root.js +98 -0
  260. package/dist/task-workspace/mutex.d.ts +8 -0
  261. package/dist/task-workspace/mutex.d.ts.map +1 -0
  262. package/dist/task-workspace/mutex.js +82 -0
  263. package/dist/task-workspace/naming.d.ts +15 -0
  264. package/dist/task-workspace/naming.d.ts.map +1 -0
  265. package/dist/task-workspace/naming.js +0 -0
  266. package/dist/task-workspace/provisioning.d.ts +3 -0
  267. package/dist/task-workspace/provisioning.d.ts.map +1 -0
  268. package/dist/task-workspace/provisioning.js +528 -0
  269. package/dist/task-workspace/reconciliation.d.ts +15 -0
  270. package/dist/task-workspace/reconciliation.d.ts.map +1 -0
  271. package/dist/task-workspace/reconciliation.js +274 -0
  272. package/dist/task-workspace/repair.d.ts +3 -0
  273. package/dist/task-workspace/repair.d.ts.map +1 -0
  274. package/dist/task-workspace/repair.js +286 -0
  275. package/dist/task-workspace/routes.d.ts +19 -0
  276. package/dist/task-workspace/routes.d.ts.map +1 -0
  277. package/dist/task-workspace/routes.js +481 -0
  278. package/dist/task-workspace/store.d.ts +12 -0
  279. package/dist/task-workspace/store.d.ts.map +1 -0
  280. package/dist/task-workspace/store.js +128 -0
  281. package/dist/task-workspace/types.d.ts +170 -0
  282. package/dist/task-workspace/types.d.ts.map +1 -0
  283. package/dist/task-workspace/types.js +5 -0
  284. package/dist/voice-action-governance.d.ts +23 -0
  285. package/dist/voice-action-governance.d.ts.map +1 -0
  286. package/dist/voice-action-governance.js +126 -0
  287. package/dist/voice-handlers.d.ts +6 -0
  288. package/dist/voice-handlers.d.ts.map +1 -0
  289. package/dist/voice-handlers.js +570 -0
  290. package/dist/voice-realtime-grounded-tool.d.ts +31 -0
  291. package/dist/voice-realtime-grounded-tool.d.ts.map +1 -0
  292. package/dist/voice-realtime-grounded-tool.js +322 -0
  293. package/dist/voice-realtime.d.ts +69 -0
  294. package/dist/voice-realtime.d.ts.map +1 -0
  295. package/dist/voice-realtime.js +787 -0
  296. package/dist/workspace-state-handlers.d.ts +5 -0
  297. package/dist/workspace-state-handlers.d.ts.map +1 -0
  298. package/dist/workspace-state-handlers.js +106 -0
  299. package/package.json +20 -19
  300. package/dist/grounded-handoff.d.ts +0 -4
  301. package/dist/grounded-handoff.d.ts.map +0 -1
  302. package/dist/grounded-handoff.js +0 -445
@@ -1,28 +1,49 @@
1
- import { EDITOR_AGENT_SCHEMA_VERSION, isEditorAgentAction, parseEditorAgentActionsPostBody, parseEditorAgentSnapshotRequest, } from "@oscharko-dev/keiko-contracts";
2
- import { errorBody, STREAMING } from "../routes.js";
1
+ // Issue #1392 BFF routes for the editor-agent control plane: session discovery, snapshot read/write,
2
+ // action queueing, browser result reporting, and the SSE event stream. The durable control-plane state
3
+ // (session registry, live bridge tracking, bounded action queue, timeouts, fan-out) lives in
4
+ // agentSessionRegistry.ts; this module is the HTTP edge that parses requests, enforces *preflight*
5
+ // policy, and threads idempotency. The server only coordinates — it never executes an action or
6
+ // mutates editor/React state (AC5); the browser bridge owns that.
7
+ //
8
+ // Preflight conflicts (status "conflict", HTTP 409) gate admission to the queue. The Issue #1391/#1394
9
+ // structural gates (DIRTY / VERSION / HASH / OUT_OF_SCOPE / INVALID_EDITS / PRECONDITION_REQUIRED) run
10
+ // first; the Issue #1392 liveness gate runs last: an otherwise-valid action for a session with no live
11
+ // browser bridge is answered with NO_ACTIVE_BRIDGE (AC1). Past preflight, the registry can still return
12
+ // a lifecycle failure: QUEUE_FULL (HTTP 429, the bounded queue is saturated) or, asynchronously,
13
+ // TIMED_OUT when the bridge never acknowledges before the deadline (AC2).
14
+ //
15
+ // No raw source content (snapshot text, text edits, patch bodies) is logged anywhere in this path.
16
+ import { createHash } from "node:crypto";
17
+ import { EDITOR_AGENT_SCHEMA_VERSION, classifyEditorAgentAction, editorAgentWritePreconditionError, isContainedAgentPath, isEditorAgentAction, isEditorAgentWriteActionType, parseEditorAgentActionsPostBody, parseEditorAgentSnapshotRequest, validateAgentTextEdits, } from "@oscharko-dev/keiko-contracts";
18
+ import { computeFileContent, validatePatch, } from "@oscharko-dev/keiko-tools";
19
+ import { isDenied, resolveWithinWorkspace, } from "@oscharko-dev/keiko-workspace";
20
+ import { nodeWorkspaceFs } from "@oscharko-dev/keiko-workspace/internal/fs";
21
+ import { errorBody, STREAMING, } from "../routes.js";
3
22
  import { SSE_HEADERS, readyMessage } from "../sse.js";
4
23
  import { readJsonObject } from "../files.js";
24
+ import { editorAgentRegistry } from "./agentSessionRegistry.js";
25
+ import { _resetEditorAgentAuditForTests, listEditorAgentActionAudit, recordEditorAgentActionAudit, } from "./agentActionAudit.js";
5
26
  const MAX_AGENT_BODY_BYTES = 1_048_576;
6
27
  const DEFAULT_SNAPSHOT_TEXT_BUDGET_BYTES = 64 * 1024;
7
- const sessions = new Map();
28
+ // HTTP-level idempotency: a replayed Idempotency-Key returns the original outcome; a key reused with a
29
+ // different action body is a 409. This is request de-duplication and stays at the route edge. The map
30
+ // is bounded (FIFO eviction) and stores only a hash of the request body, so it cannot grow without
31
+ // limit or retain raw action content (text edits, patch bodies) on a long-lived server.
32
+ const MAX_IDEMPOTENCY_ENTRIES = 1024;
8
33
  const idempotency = new Map();
9
- const subscribers = new Set();
10
- let eventSeq = 0;
11
- function isRouteResult(value) {
12
- return typeof value === "object" && value !== null && "status" in value && "body" in value;
34
+ function hashRequest(body) {
35
+ return createHash("sha256").update(body).digest("hex");
13
36
  }
14
- function nextEventId() {
15
- eventSeq += 1;
16
- return `editor-agent-${String(eventSeq)}`;
37
+ function rememberIdempotency(key, requestHash, result) {
38
+ if (!idempotency.has(key) && idempotency.size >= MAX_IDEMPOTENCY_ENTRIES) {
39
+ const oldest = idempotency.keys().next().value;
40
+ if (oldest !== undefined)
41
+ idempotency.delete(oldest);
42
+ }
43
+ idempotency.set(key, { requestHash, result });
17
44
  }
18
- function emit(event) {
19
- const envelope = {
20
- schemaVersion: EDITOR_AGENT_SCHEMA_VERSION,
21
- eventId: nextEventId(),
22
- ...event,
23
- };
24
- for (const subscriber of subscribers)
25
- subscriber(envelope);
45
+ function isRouteResult(value) {
46
+ return typeof value === "object" && value !== null && "status" in value && "body" in value;
26
47
  }
27
48
  function utf8Prefix(text, maxBytes) {
28
49
  let bytes = 0;
@@ -72,14 +93,150 @@ function contentHashConflict(action, snapshot) {
72
93
  ? null
73
94
  : conflict(action, "CONTENT_HASH_MISMATCH", "The active document content hash no longer matches.");
74
95
  }
96
+ function containmentConflict(action, snapshot) {
97
+ const file = targetFile(action, snapshot);
98
+ if (file === null)
99
+ return null;
100
+ return isContainedAgentPath(file)
101
+ ? null
102
+ : conflict(action, "OUT_OF_SCOPE", "The target file escapes the workspace root.");
103
+ }
104
+ // Issue #1395 (ADR-0062) — a write action whose target is a contained but always-on-deny-listed path
105
+ // (.env, .ssh, .keiko, credentials, …) is denied by policy across ALL write action types. Previously
106
+ // the deny-list was enforced only on the applyPatch path (via validatePatch); this closes the gap for
107
+ // applyTextEdits/save/format. Surfaced as the existing OUT_OF_SCOPE conflict so no new wire code is
108
+ // introduced; the fine-grained governance reason (denied-sensitive-path) lives in the audit record.
109
+ function sensitivePathConflict(action, snapshot) {
110
+ const file = targetFile(action, snapshot);
111
+ if (file === null || !isContainedAgentPath(file))
112
+ return null;
113
+ return isDenied(file)
114
+ ? conflict(action, "OUT_OF_SCOPE", "The target file is a protected workspace path.")
115
+ : null;
116
+ }
117
+ function textEditsConflict(action) {
118
+ if (action.type !== "applyTextEdits")
119
+ return null;
120
+ const error = validateAgentTextEdits(action.textEdits ?? []);
121
+ return error === null ? null : conflict(action, "INVALID_EDITS", error);
122
+ }
123
+ function workspaceInfoFromRoot(root) {
124
+ return {
125
+ root,
126
+ name: undefined,
127
+ version: undefined,
128
+ testFramework: "unknown",
129
+ sourceDirs: [],
130
+ testDirs: [],
131
+ languages: [],
132
+ ignoreLines: [],
133
+ };
134
+ }
135
+ const OUT_OF_SCOPE_REJECTION_CODES = new Set(["path-unsafe", "path-denied", "binary"]);
136
+ function mapPatchValidation(action, validation) {
137
+ if (validation.files.length > 1) {
138
+ return conflict(action, "OUT_OF_SCOPE", "Multi-file patches are not supported on the agent action path.");
139
+ }
140
+ const scopeRejection = validation.reasons.find((reason) => OUT_OF_SCOPE_REJECTION_CODES.has(reason.code));
141
+ if (scopeRejection !== undefined) {
142
+ return conflict(action, "OUT_OF_SCOPE", scopeRejection.message);
143
+ }
144
+ const firstReason = validation.reasons.at(0);
145
+ if (firstReason !== undefined) {
146
+ return conflict(action, "INVALID_EDITS", firstReason.message);
147
+ }
148
+ const firstConflict = validation.conflicts.at(0);
149
+ if (firstConflict !== undefined) {
150
+ return conflict(action, "INVALID_EDITS", firstConflict.reason);
151
+ }
152
+ if (validation.ok && validation.files.length === 0) {
153
+ return conflict(action, "INVALID_EDITS", "Patch contains no applicable file change.");
154
+ }
155
+ return null;
156
+ }
157
+ function patchValidationConflict(action, snapshot) {
158
+ if (action.type !== "applyPatch")
159
+ return null;
160
+ const validation = validatePatch(workspaceInfoFromRoot(snapshot.workspaceRoot), action.patch ?? "", {
161
+ fs: nodeWorkspaceFs,
162
+ });
163
+ return mapPatchValidation(action, validation);
164
+ }
165
+ function wholeDocumentReplaceEdit(currentContent, postImage) {
166
+ const currentLineCount = currentContent.split("\n").length;
167
+ return {
168
+ range: {
169
+ start: { line: 0, character: 0 },
170
+ end: { line: currentLineCount + 1, character: 0 },
171
+ },
172
+ newText: postImage,
173
+ };
174
+ }
175
+ function deriveSingleFilePostImage(file, snapshot) {
176
+ const absolute = resolveWithinWorkspace(snapshot.workspaceRoot, file.path);
177
+ const exists = nodeWorkspaceFs.exists(absolute);
178
+ const currentContent = exists ? nodeWorkspaceFs.readFileUtf8(absolute) : "";
179
+ const outcome = computeFileContent(file, exists ? currentContent : undefined);
180
+ if (outcome.content === null || outcome.conflicts.length > 0)
181
+ return null;
182
+ return wholeDocumentReplaceEdit(currentContent, outcome.content);
183
+ }
184
+ // Translates a queued, preflight-validated single-file applyPatch into the contract textEdits the
185
+ // browser reviews and applies. Computing the post-image with keiko-tools' tested single-file apply
186
+ // logic against the current on-disk content guarantees the cross-cutting invariant
187
+ // applyTextEditsToText(currentContent, edits) === patchedSingleFileContent. Returns null when the
188
+ // patch is not the expected single-file shape or its post-image cannot be derived (including any
189
+ // filesystem/path error), so the caller fails the action rather than emitting an un-appliable one.
190
+ // keiko-tools strips a/ b/ git prefixes; both the patch path and snapshot.activeFile are
191
+ // workspace-relative POSIX paths. Normalize a leading ./ and backslashes so a legitimately
192
+ // matching same-file patch is not rejected on a cosmetic difference.
193
+ function normalizeWorkspaceRelativePath(path) {
194
+ return path.replace(/\\/gu, "/").replace(/^\.\//u, "");
195
+ }
196
+ function deriveAgentPatchTextEdits(action, snapshot) {
197
+ // Intentional re-validation: preflight already validated this patch, but validatePatch is
198
+ // deterministic and pure over (workspace, patch, fs), so deriving here keeps this function
199
+ // self-contained without threading mutable validation state through the queue path.
200
+ const validation = validatePatch(workspaceInfoFromRoot(snapshot.workspaceRoot), action.patch ?? "", { fs: nodeWorkspaceFs });
201
+ const file = validation.files.at(0);
202
+ if (!validation.ok || validation.files.length !== 1 || file === undefined)
203
+ return null;
204
+ // The post-image is derived from the patch's file content but the browser applies it to the
205
+ // open buffer. Refuse to apply a patch that targets a different file than the open buffer.
206
+ if (snapshot.activeFile === null ||
207
+ normalizeWorkspaceRelativePath(file.path) !==
208
+ normalizeWorkspaceRelativePath(snapshot.activeFile)) {
209
+ return null;
210
+ }
211
+ try {
212
+ const edit = deriveSingleFilePostImage(file, snapshot);
213
+ return edit === null ? null : [edit];
214
+ }
215
+ catch {
216
+ return null;
217
+ }
218
+ }
219
+ // Builds the action envelope to broadcast to the bridge. For applyPatch the contract textEdits are
220
+ // derived (whole-document replace) so the browser reviews a concrete edit; null means the patch could
221
+ // not be prepared and the action must be failed rather than queued. Every other action type is
222
+ // broadcast unchanged.
223
+ function buildEmitAction(action, snapshot) {
224
+ if (action.type !== "applyPatch")
225
+ return action;
226
+ const textEdits = deriveAgentPatchTextEdits(action, snapshot);
227
+ if (textEdits === null)
228
+ return null;
229
+ return { ...action, textEdits };
230
+ }
75
231
  function targetFile(action, snapshot) {
76
232
  return action.target?.file ?? snapshot.activeFile;
77
233
  }
78
- function writeAction(action) {
79
- return (action.type === "format" ||
80
- action.type === "save" ||
81
- action.type === "applyTextEdits" ||
82
- action.type === "applyPatch");
234
+ // Issue #1391 AC2 — reject a write action that does not pin the document revision it expects to write
235
+ // against. The contract owns both "what is a write action" and "what counts as a precondition"; the
236
+ // server only maps the missing-precondition rule onto the structured PRECONDITION_REQUIRED conflict.
237
+ function preconditionConflict(action) {
238
+ const error = editorAgentWritePreconditionError(action);
239
+ return error === null ? null : conflict(action, "PRECONDITION_REQUIRED", error);
83
240
  }
84
241
  function conflict(action, code, message) {
85
242
  return {
@@ -91,19 +248,39 @@ function conflict(action, code, message) {
91
248
  conflict: { code, message },
92
249
  };
93
250
  }
251
+ // The Issue #1391/#1394 structural write gates, unchanged: a doubly-invalid write reports its most
252
+ // specific failure. Non-write actions have no structural gate.
253
+ function structuralWriteConflict(action, snapshot) {
254
+ if (!isEditorAgentWriteActionType(action.type))
255
+ return null;
256
+ return (dirtyBufferConflict(action, snapshot) ??
257
+ documentVersionConflict(action, snapshot) ??
258
+ contentHashConflict(action, snapshot) ??
259
+ containmentConflict(action, snapshot) ??
260
+ sensitivePathConflict(action, snapshot) ??
261
+ textEditsConflict(action) ??
262
+ patchValidationConflict(action, snapshot) ??
263
+ preconditionConflict(action));
264
+ }
265
+ // Returns a structured conflict result when the action must not be admitted to the queue, or null when
266
+ // it is clear to enqueue. The structural gates run first (above); the Issue #1392 liveness gate runs
267
+ // last so any otherwise-valid action for a session with no live bridge is answered with the structured
268
+ // NO_ACTIVE_BRIDGE conflict (AC1) rather than queued where it could never be executed.
94
269
  function preflight(action) {
95
- const snapshot = sessions.get(action.sessionId);
270
+ const snapshot = editorAgentRegistry.snapshotFor(action.sessionId);
96
271
  if (snapshot === undefined) {
97
272
  return conflict(action, "NO_ACTIVE_SESSION", "No active browser bridge is registered.");
98
273
  }
99
- if (!writeAction(action))
100
- return null;
101
- return (dirtyBufferConflict(action, snapshot) ??
102
- documentVersionConflict(action, snapshot) ??
103
- contentHashConflict(action, snapshot));
274
+ const structural = structuralWriteConflict(action, snapshot);
275
+ if (structural !== null)
276
+ return structural;
277
+ if (!editorAgentRegistry.hasLiveBridge(action.sessionId)) {
278
+ return conflict(action, "NO_ACTIVE_BRIDGE", "No live browser bridge is connected for this session.");
279
+ }
280
+ return null;
104
281
  }
105
282
  export function handleEditorAgentSessions() {
106
- return { status: 200, body: { sessions: [...sessions.values()] } };
283
+ return { status: 200, body: { sessions: editorAgentRegistry.listSessions() } };
107
284
  }
108
285
  export async function handleEditorAgentSnapshot(ctx) {
109
286
  const body = await readJsonObject(ctx.req, MAX_AGENT_BODY_BYTES);
@@ -114,13 +291,10 @@ export async function handleEditorAgentSnapshot(ctx) {
114
291
  return { status: 400, body: errorBody("INVALID_REQUEST", parsed.errors.join("; ")) };
115
292
  }
116
293
  if ("kind" in parsed.value) {
117
- sessions.set(parsed.value.snapshot.sessionId, parsed.value.snapshot);
118
- emit({ type: "session", snapshot: parsed.value.snapshot });
294
+ editorAgentRegistry.registerSnapshot(parsed.value.snapshot);
119
295
  return { status: 200, body: { snapshot: parsed.value.snapshot } };
120
296
  }
121
- const selected = parsed.value.sessionId === undefined
122
- ? [...sessions.values()][0]
123
- : sessions.get(parsed.value.sessionId);
297
+ const selected = editorAgentRegistry.selectSnapshot(parsed.value.sessionId);
124
298
  if (selected === undefined)
125
299
  return { status: 200, body: { snapshot: null } };
126
300
  const maxBytes = parsed.value.maxBytes ?? DEFAULT_SNAPSHOT_TEXT_BUDGET_BYTES;
@@ -129,6 +303,37 @@ export async function handleEditorAgentSnapshot(ctx) {
129
303
  body: { snapshot: shapeSnapshot(selected, parsed.value.textMode, maxBytes) },
130
304
  };
131
305
  }
306
+ // Issue #1395 (ADR-0062) — the workspace-relative target path an action governs, or null when it has
307
+ // no file target. Used both for the policy decision and the (content-free) audit record.
308
+ function resolveActionTargetPath(action, snapshot) {
309
+ return action.target?.file ?? snapshot?.activeFile ?? null;
310
+ }
311
+ // Deterministic policy classification (AC2). Containment reuses the contract guard; sensitivity reuses
312
+ // the always-on workspace deny-list (a keiko-workspace concern the leaf classifier cannot reach, so
313
+ // the boolean is resolved here). Only meaningful for write actions; navigation/layout always allow.
314
+ function decideActionPolicy(action, snapshot) {
315
+ const targetPath = resolveActionTargetPath(action, snapshot);
316
+ const targetSensitive = targetPath !== null && isContainedAgentPath(targetPath) && isDenied(targetPath);
317
+ return classifyEditorAgentAction(action.type, { targetPath, targetSensitive });
318
+ }
319
+ // Issue #1395 (AC1) — record one content-free audit entry for this action at its admission decision.
320
+ // Best-effort: the ledger filters to mutating/denied actions and never throws. `editCount`/
321
+ // `patchByteLength` are counts only, never edit or patch content.
322
+ function auditAction(action, snapshot, decision, result) {
323
+ recordEditorAgentActionAudit({
324
+ occurredAt: Date.now(),
325
+ sessionId: action.sessionId,
326
+ actionId: action.actionId,
327
+ actionType: action.type,
328
+ decision,
329
+ outcome: result.status,
330
+ conflictCode: result.conflict?.code,
331
+ failureCode: result.failure?.code,
332
+ targetPath: resolveActionTargetPath(action, snapshot),
333
+ editCount: action.type === "applyTextEdits" ? action.textEdits?.length : undefined,
334
+ patchByteLength: action.type === "applyPatch" ? action.patch?.length : undefined,
335
+ });
336
+ }
132
337
  export async function handleEditorAgentActions(ctx) {
133
338
  const body = await readJsonObject(ctx.req, MAX_AGENT_BODY_BYTES);
134
339
  if (isRouteResult(body))
@@ -139,14 +344,14 @@ export async function handleEditorAgentActions(ctx) {
139
344
  }
140
345
  if (!isEditorAgentAction(parsed.value)) {
141
346
  const result = parsed.value.result;
142
- emit({ type: "result", result });
347
+ editorAgentRegistry.reportResult(result);
143
348
  return { status: 200, body: { result } };
144
349
  }
145
350
  const action = parsed.value;
146
- const requestBody = JSON.stringify(action);
351
+ const requestHash = hashRequest(JSON.stringify(action));
147
352
  const replay = idempotency.get(action.idempotencyKey);
148
353
  if (replay !== undefined) {
149
- if (replay.requestBody !== requestBody) {
354
+ if (replay.requestHash !== requestHash) {
150
355
  return {
151
356
  status: 409,
152
357
  body: errorBody("IDEMPOTENCY_CONFLICT", "Idempotency-Key was reused with a different action."),
@@ -154,44 +359,76 @@ export async function handleEditorAgentActions(ctx) {
154
359
  }
155
360
  return { status: 200, body: { result: replay.result } };
156
361
  }
362
+ const snapshot = editorAgentRegistry.snapshotFor(action.sessionId);
363
+ const decision = decideActionPolicy(action, snapshot);
157
364
  const failed = preflight(action);
158
365
  if (failed !== null) {
159
- idempotency.set(action.idempotencyKey, { requestBody, result: failed });
366
+ rememberIdempotency(action.idempotencyKey, requestHash, failed);
367
+ auditAction(action, snapshot, decision, failed);
368
+ editorAgentRegistry.reportResult(failed);
160
369
  return { status: 409, body: { result: failed } };
161
370
  }
162
- const result = {
371
+ return queueAndEmitAction(action, requestHash, snapshot, decision);
372
+ }
373
+ function failedResult(action, message) {
374
+ return {
163
375
  schemaVersion: EDITOR_AGENT_SCHEMA_VERSION,
164
376
  actionId: action.actionId,
165
377
  sessionId: action.sessionId,
166
- status: "queued",
378
+ status: "failed",
379
+ message,
167
380
  };
168
- idempotency.set(action.idempotencyKey, { requestBody, result });
169
- emit({ type: "action", action });
170
- return { status: 202, body: { result } };
381
+ }
382
+ function queueAndEmitAction(action, requestHash, snapshot, decision) {
383
+ const emitAction = snapshot === undefined ? null : buildEmitAction(action, snapshot);
384
+ if (emitAction === null) {
385
+ const result = failedResult(action, "Patch could not be prepared for review.");
386
+ rememberIdempotency(action.idempotencyKey, requestHash, result);
387
+ auditAction(action, snapshot, decision, result);
388
+ return { status: 409, body: { result } };
389
+ }
390
+ const outcome = editorAgentRegistry.queueAction(action, emitAction);
391
+ rememberIdempotency(action.idempotencyKey, requestHash, outcome.result);
392
+ auditAction(action, snapshot, decision, outcome.result);
393
+ if (outcome.kind === "rejected") {
394
+ // QUEUE_FULL is backpressure (429); a duplicate in-flight actionId is a conflict (409).
395
+ const status = outcome.result.failure?.code === "QUEUE_FULL" ? 429 : 409;
396
+ return { status, body: { result: outcome.result } };
397
+ }
398
+ return { status: 202, body: { result: outcome.result } };
171
399
  }
172
400
  export function handleEditorAgentEvents(ctx) {
173
- openAgentSseStream(ctx.res);
174
- ctx.req.on("close", () => {
175
- ctx.res.end();
176
- });
401
+ const sessionId = ctx.url.searchParams.get("sessionId") ?? undefined;
402
+ openAgentSseStream(ctx, sessionId);
177
403
  return STREAMING;
178
404
  }
179
- function openAgentSseStream(res) {
405
+ // Issue #1395 (ADR-0062, AC4) — read-only feed of the bounded audit ledger so users can inspect what
406
+ // an agent changed or attempted. Scoped to the session when `?sessionId=` is present; otherwise a
407
+ // bounded recent-activity view across sessions. Content-free records only (no raw source, no secrets).
408
+ export function handleEditorAgentAudit(ctx) {
409
+ const sessionId = ctx.url.searchParams.get("sessionId") ?? undefined;
410
+ return { status: 200, body: { records: listEditorAgentActionAudit(sessionId) } };
411
+ }
412
+ // Opens the SSE stream and registers the connection as either a session bridge (when `?sessionId=` is
413
+ // present) or a global observer. The disposer drops the subscription — and thus the bridge-liveness
414
+ // contribution — when the response closes (AC1: a dropped bridge makes the session unavailable again).
415
+ function openAgentSseStream(ctx, sessionId) {
416
+ const res = ctx.res;
180
417
  res.writeHead(200, SSE_HEADERS);
181
418
  const subscriber = (event) => {
182
419
  const frame = `id: ${event.eventId}\nevent: editor-agent:${event.type}\ndata: ${JSON.stringify(event)}\n\n`;
183
420
  if (!res.write(frame))
184
421
  res.destroy();
185
422
  };
186
- subscribers.add(subscriber);
423
+ const dispose = editorAgentRegistry.connect(sessionId, subscriber);
187
424
  res.write(readyMessage());
188
- res.on("close", () => {
189
- subscribers.delete(subscriber);
425
+ ctx.req.on("close", () => {
426
+ res.end();
190
427
  });
428
+ res.on("close", dispose);
191
429
  }
192
430
  export function _resetEditorAgentStateForTests() {
193
- sessions.clear();
194
431
  idempotency.clear();
195
- subscribers.clear();
196
- eventSeq = 0;
432
+ editorAgentRegistry.reset();
433
+ _resetEditorAgentAuditForTests();
197
434
  }
@@ -0,0 +1,35 @@
1
+ import { type EditorAgentAction, type EditorAgentActionResult, type EditorAgentEvent, type EditorAgentSessionSnapshot } from "@oscharko-dev/keiko-contracts";
2
+ export declare const EDITOR_AGENT_ACTION_TIMEOUT_MS = 15000;
3
+ export declare const EDITOR_AGENT_MAX_QUEUED_PER_SESSION = 64;
4
+ export declare const EDITOR_AGENT_MAX_SESSIONS = 256;
5
+ export type EditorAgentSubscriber = (event: EditorAgentEvent) => void;
6
+ export interface EditorAgentRegistryOptions {
7
+ readonly actionTimeoutMs?: number | undefined;
8
+ readonly maxQueuedPerSession?: number | undefined;
9
+ readonly maxSessions?: number | undefined;
10
+ readonly setTimer?: ((handler: () => void, ms: number) => unknown) | undefined;
11
+ readonly clearTimer?: ((handle: unknown) => void) | undefined;
12
+ }
13
+ export type EditorAgentQueueOutcome = {
14
+ readonly kind: "queued";
15
+ readonly result: EditorAgentActionResult;
16
+ } | {
17
+ readonly kind: "rejected";
18
+ readonly result: EditorAgentActionResult;
19
+ };
20
+ export interface EditorAgentRegistry {
21
+ registerSnapshot(snapshot: EditorAgentSessionSnapshot): void;
22
+ listSessions(): readonly EditorAgentSessionSnapshot[];
23
+ snapshotFor(sessionId: string): EditorAgentSessionSnapshot | undefined;
24
+ selectSnapshot(sessionId?: string): EditorAgentSessionSnapshot | undefined;
25
+ hasLiveBridge(sessionId: string): boolean;
26
+ liveBridgeCount(sessionId: string): number;
27
+ connect(sessionId: string | undefined, send: EditorAgentSubscriber): () => void;
28
+ queueAction(action: EditorAgentAction, emitAction: EditorAgentAction): EditorAgentQueueOutcome;
29
+ reportResult(result: EditorAgentActionResult): void;
30
+ pendingCount(sessionId: string): number;
31
+ reset(): void;
32
+ }
33
+ export declare function createEditorAgentRegistry(options?: EditorAgentRegistryOptions): EditorAgentRegistry;
34
+ export declare const editorAgentRegistry: EditorAgentRegistry;
35
+ //# sourceMappingURL=agentSessionRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentSessionRegistry.d.ts","sourceRoot":"","sources":["../../src/editor/agentSessionRegistry.ts"],"names":[],"mappings":"AAsBA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EAErB,KAAK,0BAA0B,EAChC,MAAM,+BAA+B,CAAC;AAKvC,eAAO,MAAM,8BAA8B,QAAS,CAAC;AACrD,eAAO,MAAM,mCAAmC,KAAK,CAAC;AACtD,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAEtE,MAAM,WAAW,0BAA0B;IAEzC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAE9C,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAElD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAG1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC;IAC/E,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAC/D;AAED,MAAM,MAAM,uBAAuB,GAC/B;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,GACrE;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,CAAC;AAE5E,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,CAAC,QAAQ,EAAE,0BAA0B,GAAG,IAAI,CAAC;IAC7D,YAAY,IAAI,SAAS,0BAA0B,EAAE,CAAC;IACtD,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS,CAAC;IACvE,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS,CAAC;IAC3E,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1C,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3C,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,qBAAqB,GAAG,MAAM,IAAI,CAAC;IAMhF,WAAW,CAAC,MAAM,EAAE,iBAAiB,EAAE,UAAU,EAAE,iBAAiB,GAAG,uBAAuB,CAAC;IAI/F,YAAY,CAAC,MAAM,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACpD,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IACxC,KAAK,IAAI,IAAI,CAAC;CACf;AA2ND,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,0BAA+B,GACvC,mBAAmB,CAuCrB;AAID,eAAO,MAAM,mBAAmB,EAAE,mBAAiD,CAAC"}