@neuralnomads/codenomad-dev 0.20.0-dev-20260911-ea37f97b → 0.20.0-dev-20260917-aa51cbb9

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 (191) hide show
  1. package/README.md +2 -2
  2. package/dist/api-types.js.map +1 -1
  3. package/dist/opencode/automation-plugin.test.js +32 -38
  4. package/dist/opencode/automation-plugin.test.js.map +1 -1
  5. package/dist/opencode/compatibility/contract-fixture.js +15 -0
  6. package/dist/opencode/compatibility/contract-fixture.js.map +1 -0
  7. package/dist/opencode/compatibility/events.js +9 -0
  8. package/dist/opencode/compatibility/events.js.map +1 -0
  9. package/dist/opencode/compatibility/location.js +89 -0
  10. package/dist/opencode/compatibility/location.js.map +1 -0
  11. package/dist/opencode/compatibility/location.test.js +92 -0
  12. package/dist/opencode/compatibility/location.test.js.map +1 -0
  13. package/dist/opencode/compatibility/negotiate.js +73 -0
  14. package/dist/opencode/compatibility/negotiate.js.map +1 -0
  15. package/dist/opencode/compatibility/negotiate.test.js +61 -0
  16. package/dist/opencode/compatibility/negotiate.test.js.map +1 -0
  17. package/dist/opencode/compatibility/proxy-locations.js +137 -0
  18. package/dist/opencode/compatibility/proxy-locations.js.map +1 -0
  19. package/dist/opencode/compatibility/proxy-locations.test.js +42 -0
  20. package/dist/opencode/compatibility/proxy-locations.test.js.map +1 -0
  21. package/dist/opencode/compatibility/proxy.js +60 -0
  22. package/dist/opencode/compatibility/proxy.js.map +1 -0
  23. package/dist/opencode/compatibility/proxy.test.js +47 -0
  24. package/dist/opencode/compatibility/proxy.test.js.map +1 -0
  25. package/dist/opencode/compatibility/requests.js +54 -0
  26. package/dist/opencode/compatibility/requests.js.map +1 -0
  27. package/dist/opencode/compatibility/runtime.js +26 -0
  28. package/dist/opencode/compatibility/runtime.js.map +1 -0
  29. package/dist/opencode/compatibility/transport.js +112 -0
  30. package/dist/opencode/compatibility/transport.js.map +1 -0
  31. package/dist/opencode/compatibility/transport.test.js +83 -0
  32. package/dist/opencode/compatibility/transport.test.js.map +1 -0
  33. package/dist/opencode/session-pruning/location.js +16 -0
  34. package/dist/opencode/session-pruning/location.js.map +1 -0
  35. package/dist/opencode/session-pruning/package.test.js +37 -0
  36. package/dist/opencode/session-pruning/package.test.js.map +1 -0
  37. package/dist/opencode/session-pruning/plugin.js +5 -2
  38. package/dist/opencode/session-pruning/plugin.js.map +1 -1
  39. package/dist/opencode/session-pruning/preview-store.js +3 -2
  40. package/dist/opencode/session-pruning/preview-store.js.map +1 -1
  41. package/dist/opencode/session-pruning/pruning.test.js +7 -5
  42. package/dist/opencode/session-pruning/pruning.test.js.map +1 -1
  43. package/dist/opencode/session-pruning/service.js +5 -4
  44. package/dist/opencode/session-pruning/service.js.map +1 -1
  45. package/dist/opencode/session-pruning/service.test.js +65 -53
  46. package/dist/opencode/session-pruning/service.test.js.map +1 -1
  47. package/dist/permissions/opencode-replier.js +2 -2
  48. package/dist/permissions/opencode-replier.js.map +1 -1
  49. package/dist/permissions/opencode-replier.test.js +1 -1
  50. package/dist/permissions/opencode-replier.test.js.map +1 -1
  51. package/dist/permissions/opencode-yolo-metadata.js.map +1 -1
  52. package/dist/permissions/opencode-yolo-metadata.test.js.map +1 -1
  53. package/dist/plugins/session-pruning/plugin.mjs +422 -271
  54. package/dist/server/__tests__/instance-proxy-legacy.test.js +111 -0
  55. package/dist/server/__tests__/instance-proxy-legacy.test.js.map +1 -0
  56. package/dist/server/__tests__/instance-proxy.test.js +95 -34
  57. package/dist/server/__tests__/instance-proxy.test.js.map +1 -1
  58. package/dist/server/http-server.js +147 -237
  59. package/dist/server/http-server.js.map +1 -1
  60. package/dist/server/routes/session-pruning.js +4 -2
  61. package/dist/server/routes/session-pruning.js.map +1 -1
  62. package/dist/server/routes/session-pruning.test.js +16 -2
  63. package/dist/server/routes/session-pruning.test.js.map +1 -1
  64. package/dist/server/routes/worktrees.js +1 -0
  65. package/dist/server/routes/worktrees.js.map +1 -1
  66. package/dist/server/routes/worktrees.test.js +54 -1
  67. package/dist/server/routes/worktrees.test.js.map +1 -1
  68. package/dist/workspaces/host-opencode-service.test.js +56 -7
  69. package/dist/workspaces/host-opencode-service.test.js.map +1 -1
  70. package/dist/workspaces/instance-client.js.map +1 -1
  71. package/dist/workspaces/instance-events.js +71 -57
  72. package/dist/workspaces/instance-events.js.map +1 -1
  73. package/dist/workspaces/instance-events.test.js +122 -9
  74. package/dist/workspaces/instance-events.test.js.map +1 -1
  75. package/dist/workspaces/manager.js +24 -39
  76. package/dist/workspaces/manager.js.map +1 -1
  77. package/dist/workspaces/manager.test.js +43 -7
  78. package/dist/workspaces/manager.test.js.map +1 -1
  79. package/dist/workspaces/opencode-cli-service.js +56 -29
  80. package/dist/workspaces/opencode-cli-service.js.map +1 -1
  81. package/dist/workspaces/opencode-connection-generation.test.js +57 -0
  82. package/dist/workspaces/opencode-connection-generation.test.js.map +1 -0
  83. package/dist/workspaces/opencode-service.js +70 -16
  84. package/dist/workspaces/opencode-service.js.map +1 -1
  85. package/dist/workspaces/opencode-service.test.js +3 -4
  86. package/dist/workspaces/opencode-service.test.js.map +1 -1
  87. package/dist/workspaces/worktree-session-evacuation.js +31 -11
  88. package/dist/workspaces/worktree-session-evacuation.js.map +1 -1
  89. package/dist/workspaces/worktree-session-evacuation.test.js +211 -0
  90. package/dist/workspaces/worktree-session-evacuation.test.js.map +1 -1
  91. package/dist/workspaces/wsl-opencode-service.test.js +64 -24
  92. package/dist/workspaces/wsl-opencode-service.test.js.map +1 -1
  93. package/package.json +3 -3
  94. package/public/assets/App-BROhwPo9.js +54 -0
  95. package/public/assets/App-BROhwPo9.js.map +1 -0
  96. package/public/assets/{FilesTab-CRpGDs1B.js → FilesTab-DY3Vr-3r.js} +3 -3
  97. package/public/assets/{FilesTab-CRpGDs1B.js.map → FilesTab-DY3Vr-3r.js.map} +1 -1
  98. package/public/assets/{GitChangesTab-DxFebTiq.js → GitChangesTab-BD6CJngE.js} +3 -3
  99. package/public/assets/{GitChangesTab-DxFebTiq.js.map → GitChangesTab-BD6CJngE.js.map} +1 -1
  100. package/public/assets/{SplitFilePanel-DgTMrrO-.js → SplitFilePanel-BdY6d_6m.js} +2 -2
  101. package/public/assets/{SplitFilePanel-DgTMrrO-.js.map → SplitFilePanel-BdY6d_6m.js.map} +1 -1
  102. package/public/assets/StatusTab-C-_2yaPa.js +2 -0
  103. package/public/assets/StatusTab-C-_2yaPa.js.map +1 -0
  104. package/public/assets/{align-justify-v_yPXSys.js → align-justify-DI8VTZ4G.js} +2 -2
  105. package/public/assets/{align-justify-v_yPXSys.js.map → align-justify-DI8VTZ4G.js.map} +1 -1
  106. package/public/assets/{bundle-full-HzL_Jtqk.js → bundle-full-D_M65Eb_.js} +2 -2
  107. package/public/assets/{bundle-full-HzL_Jtqk.js.map → bundle-full-D_M65Eb_.js.map} +1 -1
  108. package/public/assets/{client-state-CF-PJ0GG.js → client-state-CWCzCOz6.js} +2 -2
  109. package/public/assets/{client-state-CF-PJ0GG.js.map → client-state-CWCzCOz6.js.map} +1 -1
  110. package/public/assets/{diff-viewer-BFDQBDVy.js → diff-viewer-CGPsYEr3.js} +2 -2
  111. package/public/assets/{diff-viewer-BFDQBDVy.js.map → diff-viewer-CGPsYEr3.js.map} +1 -1
  112. package/public/assets/index-B8-lAqjr.js +2 -0
  113. package/public/assets/index-B8-lAqjr.js.map +1 -0
  114. package/public/assets/index-BeUewQym.js +2 -0
  115. package/public/assets/index-BeUewQym.js.map +1 -0
  116. package/public/assets/index-BxRFHpJd.js +2 -0
  117. package/public/assets/{index-D2UHSOG5.js.map → index-BxRFHpJd.js.map} +1 -1
  118. package/public/assets/index-Bzv1kU-S.js +2 -0
  119. package/public/assets/{index-CTKYD_VJ.js.map → index-Bzv1kU-S.js.map} +1 -1
  120. package/public/assets/index-C9tI4CRG.js +2 -0
  121. package/public/assets/index-C9tI4CRG.js.map +1 -0
  122. package/public/assets/index-CGYHFZ6F.js +2 -0
  123. package/public/assets/index-CGYHFZ6F.js.map +1 -0
  124. package/public/assets/{index-CHYmKl10.js → index-CMEAjBFp.js} +2 -2
  125. package/public/assets/index-CMEAjBFp.js.map +1 -0
  126. package/public/assets/index-Cazypvyc.js +2 -0
  127. package/public/assets/index-Cazypvyc.js.map +1 -0
  128. package/public/assets/index-CwqzofWP.js +2 -0
  129. package/public/assets/index-CwqzofWP.js.map +1 -0
  130. package/public/assets/index-Da9yOa6q.js +2 -0
  131. package/public/assets/index-Da9yOa6q.js.map +1 -0
  132. package/public/assets/{index-RK9gf0uk.js → index-Ddjkirek.js} +2 -2
  133. package/public/assets/{index-RK9gf0uk.js.map → index-Ddjkirek.js.map} +1 -1
  134. package/public/assets/index-HHgvEOAH.js +3 -0
  135. package/public/assets/index-HHgvEOAH.js.map +1 -0
  136. package/public/assets/index-MeFffHqb.css +1 -0
  137. package/public/assets/loading-CdhHiSED.css +1 -0
  138. package/public/assets/{loading-BSB82snY.js → loading-Gqh6NPUr.js} +2 -2
  139. package/public/assets/{loading-BSB82snY.js.map → loading-Gqh6NPUr.js.map} +1 -1
  140. package/public/assets/main-Cbx7KdTr.js +38 -0
  141. package/public/assets/main-Cbx7KdTr.js.map +1 -0
  142. package/public/assets/{markdown-DtidoL2Z.js → markdown-BWnjyvnX.js} +4 -4
  143. package/public/assets/{markdown-DtidoL2Z.js.map → markdown-BWnjyvnX.js.map} +1 -1
  144. package/public/assets/monaco-viewer-BEJIiZWL.js +27 -0
  145. package/public/assets/monaco-viewer-BEJIiZWL.js.map +1 -0
  146. package/public/assets/{startup-state-settings-card-BY741Rt0.js → startup-state-settings-card-C4E2Itop.js} +2 -2
  147. package/public/assets/{startup-state-settings-card-BY741Rt0.js.map → startup-state-settings-card-C4E2Itop.js.map} +1 -1
  148. package/public/assets/{terminal-square-BdC_jwm3.js → terminal-square-C3wutdV0.js} +2 -2
  149. package/public/assets/{terminal-square-BdC_jwm3.js.map → terminal-square-C3wutdV0.js.map} +1 -1
  150. package/public/assets/tool-call-DNsAQ_m4.js +60 -0
  151. package/public/assets/tool-call-DNsAQ_m4.js.map +1 -0
  152. package/public/assets/{unified-picker-C0eo1chJ.js → unified-picker-tB7y17Qz.js} +2 -2
  153. package/public/assets/unified-picker-tB7y17Qz.js.map +1 -0
  154. package/public/assets/{wrap-text-BuMtdvMx.js → wrap-text-CkBxzt04.js} +2 -2
  155. package/public/assets/{wrap-text-BuMtdvMx.js.map → wrap-text-CkBxzt04.js.map} +1 -1
  156. package/public/index.html +4 -4
  157. package/public/loading.html +5 -5
  158. package/public/sw.js +1 -1
  159. package/public/sw.js.map +1 -1
  160. package/public/assets/App-0-FcHzPS.js +0 -44
  161. package/public/assets/App-0-FcHzPS.js.map +0 -1
  162. package/public/assets/StatusTab-LKUA9cbH.js +0 -2
  163. package/public/assets/StatusTab-LKUA9cbH.js.map +0 -1
  164. package/public/assets/index-8iPlm2r0.js +0 -2
  165. package/public/assets/index-8iPlm2r0.js.map +0 -1
  166. package/public/assets/index-B185mC7m.js +0 -2
  167. package/public/assets/index-B185mC7m.js.map +0 -1
  168. package/public/assets/index-Bbj1xF8i.js +0 -2
  169. package/public/assets/index-Bbj1xF8i.js.map +0 -1
  170. package/public/assets/index-BsmFN3k1.js +0 -2
  171. package/public/assets/index-BsmFN3k1.js.map +0 -1
  172. package/public/assets/index-Bz_OQJYA.css +0 -1
  173. package/public/assets/index-C52CfqK3.js +0 -2
  174. package/public/assets/index-C52CfqK3.js.map +0 -1
  175. package/public/assets/index-CHYmKl10.js.map +0 -1
  176. package/public/assets/index-CTKYD_VJ.js +0 -2
  177. package/public/assets/index-D2UHSOG5.js +0 -2
  178. package/public/assets/index-DDoB_Z36.js +0 -2
  179. package/public/assets/index-DDoB_Z36.js.map +0 -1
  180. package/public/assets/index-DcwAtZw-.js +0 -3
  181. package/public/assets/index-DcwAtZw-.js.map +0 -1
  182. package/public/assets/index-DxUD3RYr.js +0 -2
  183. package/public/assets/index-DxUD3RYr.js.map +0 -1
  184. package/public/assets/loading-CmEVQgyj.css +0 -1
  185. package/public/assets/main-CZztwrfv.js +0 -48
  186. package/public/assets/main-CZztwrfv.js.map +0 -1
  187. package/public/assets/monaco-viewer-CpxDfB-6.js +0 -27
  188. package/public/assets/monaco-viewer-CpxDfB-6.js.map +0 -1
  189. package/public/assets/tool-call-DbtCsJ4x.js +0 -60
  190. package/public/assets/tool-call-DbtCsJ4x.js.map +0 -1
  191. package/public/assets/unified-picker-C0eo1chJ.js.map +0 -1
package/README.md CHANGED
@@ -20,7 +20,7 @@
20
20
 
21
21
  ## Prerequisites
22
22
 
23
- - **OpenCode V2**: Install a compatible `opencode2` CLI. CodeNomad uses the CLI's official `service status`, `service start`, and `service get password` lifecycle to connect to OpenCode's externally owned global daemon, then validates its authenticated loopback health response rather than enforcing an exact CLI version string.
23
+ - **OpenCode V2**: Install a compatible `opencode2` CLI. CodeNomad uses the CLI's official `service status`, `service start`, and `service get password` lifecycle to connect to OpenCode's externally owned global daemon, then validates its authenticated loopback `/api/status` response rather than enforcing an exact CLI version string.
24
24
  - **OpenCode data**: The global daemon owns its platform-default storage, database, and service registration. Configured startup environment applies only when CodeNomad starts a missing daemon; an existing daemon is unchanged.
25
25
  - **Windows to WSL**: A configured WSL UNC binary uses Linux `service status`, `service start`, and `service get password`; Windows must have WSL localhost forwarding enabled to reach its loopback service.
26
26
  - Node.js 18+ and npm (for running or building from source).
@@ -232,7 +232,7 @@ Explicit **Stop Workspace** evicts that location and its resources from the glob
232
232
 
233
233
  CodeNomad holds one shared OpenCode V2 `client.event.subscribe()` stream. It routes native location-scoped events to logical workspaces and multiplexes them with CodeNomad events over `GET /api/events` for browser `EventSource` clients.
234
234
 
235
- The stream is volatile and has no replay guarantee. After reconnecting, clients must refetch authoritative sessions and pending permission, question, and form requests; file and config consumers must also refetch after `filesystem.changed` and `config.updated` invalidations.
235
+ The stream is volatile and has no replay guarantee. After reconnecting, clients must refetch authoritative sessions and pending permission and Form requests; file and config consumers must also refetch after `filesystem.changed` and `config.updated` invalidations.
236
236
 
237
237
  ### Provider Plan Usage
238
238
 
@@ -1 +1 @@
1
- {"version":3,"file":"api-types.js","sourceRoot":"","sources":["../src/api-types.ts"],"names":[],"mappings":"AAqQA,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAAA;AA8E/C,MAAM,CAAC,MAAM,+BAA+B,GAAG,sBAA+B,CAAA","sourcesContent":["import type {\n AgentModelSelection,\n AgentModelSelections,\n ModelPreference,\n OpenCodeBinary,\n Preferences,\n RecentFolder,\n} from \"./config/schema\"\nimport type { OpenCodeEvent } from \"@opencode-ai/client\"\n\n/**\n * Canonical HTTP/SSE contract for the CLI server.\n * These types are consumed by both the CLI implementation and any UI clients.\n */\n\nexport type WorkspaceStatus = \"starting\" | \"ready\" | \"stopped\" | \"error\"\n\nexport interface WorkspaceDescriptor {\n id: string\n /** Correlates creation events with the client request that initiated them. */\n requestId?: string\n /** Absolute path on the server host. */\n path: string\n name?: string\n status: WorkspaceStatus\n /** PID/port are populated when the workspace is running. */\n pid?: number\n port?: number\n /** Canonical proxy path the CLI exposes for this instance. */\n proxyPath: string\n /** Identifier of the binary resolved from config. */\n binaryId: string\n binaryLabel: string\n binaryVersion?: string\n createdAt: string\n updatedAt: string\n /** Present when `status` is \"error\". */\n error?: string\n}\n\nexport interface WorkspaceCreateRequest {\n path: string\n name?: string\n requestId?: string\n}\n\nexport interface WorkspaceCloneRequest {\n repositoryUrl: string\n destinationPath: string\n cleanup?: boolean\n}\n\nexport interface WorkspaceCloneResponse {\n path: string\n}\n\nexport type WorkspaceCreateResponse = WorkspaceDescriptor & {\n /** True when this request did not own creation of the returned workspace. */\n reused?: true\n}\nexport type WorkspaceListResponse = WorkspaceDescriptor[]\nexport type WorkspaceDetailResponse = WorkspaceDescriptor\n\nexport interface WorkspaceDeleteResponse {\n id: string\n status: WorkspaceStatus\n}\n\nexport interface ProviderUsageWindow {\n usedPercent: number | null\n remainingPercent: number | null\n windowSeconds: number | null\n resetAt: number | null\n valueLabel?: string\n}\n\nexport interface ProviderUsageResponse {\n requestedProviderId: string\n providerId: string | null\n providerName: string\n modelId?: string\n supported: boolean\n configured: boolean\n ok: boolean\n windows: Record<string, ProviderUsageWindow>\n fetchedAt: number\n}\n\nexport type WorktreeKind = \"root\" | \"worktree\"\n\nexport interface WorktreeDescriptor {\n /** Stable identifier used by CodeNomad + clients (\"root\" for the selected workspace folder). */\n slug: string\n /** Absolute directory path on the server host. */\n directory: string\n kind: WorktreeKind\n /** Optional VCS branch name when available. */\n branch?: string\n}\n\nexport interface WorktreeListResponse {\n worktrees: WorktreeDescriptor[]\n /** True when the workspace folder resolves to a Git repository. */\n isGitRepo?: boolean\n}\n\nexport interface WorktreeCreateRequest {\n slug: string\n /** Optional branch name (defaults to slug). */\n branch?: string\n}\n\nexport type GitChangeKind = \"added\" | \"modified\" | \"deleted\" | \"renamed\" | \"copied\" | \"untracked\" | \"unmerged\"\n\nexport interface WorktreeGitStatusEntry {\n path: string\n originalPath?: string | null\n stagedStatus: GitChangeKind | null\n stagedAdditions: number\n stagedDeletions: number\n unstagedStatus: GitChangeKind | null\n unstagedAdditions: number\n unstagedDeletions: number\n}\n\nexport type WorktreeGitStatusResponse = WorktreeGitStatusEntry[]\n\nexport type WorktreeGitDiffScope = \"staged\" | \"unstaged\"\n\nexport interface WorktreeGitPathsRequest {\n paths: string[]\n}\n\nexport interface WorktreeGitMutationResponse {\n ok: true\n}\n\nexport interface WorktreeGitCommitRequest {\n message: string\n}\n\nexport interface WorktreeGitCommitResponse {\n ok: true\n commitSha?: string\n}\n\nexport interface WorktreeGitDiffResponse {\n path: string\n originalPath?: string | null\n scope: WorktreeGitDiffScope\n before: string\n after: string\n isBinary?: boolean\n}\n\nexport interface WorktreeGitDiffRequest {\n path: string\n originalPath?: string | null\n scope: WorktreeGitDiffScope\n}\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\"\n\nexport interface WorkspaceLogEntry {\n workspaceId: string\n timestamp: string\n level: LogLevel\n message: string\n}\n\nexport interface FileSystemEntry {\n name: string\n /**\n * Path identifier for the entry. Relative to the server root in restricted\n * single-root listings (\".\" represents the root itself); absolute in\n * unrestricted, drives, and multi-root top-level listings.\n */\n path: string\n /** Absolute path when available (unrestricted and multi-root listings). */\n absolutePath?: string\n type: \"file\" | \"directory\"\n size?: number\n /** ISO timestamp of last modification when available. */\n modifiedAt?: string\n}\n\nexport type FileSystemScope = \"restricted\" | \"unrestricted\"\nexport type FileSystemPathKind = \"relative\" | \"absolute\" | \"drives\"\n\nexport interface FileSystemListingMetadata {\n scope: FileSystemScope\n /**\n * Canonical identifier of the current view:\n * - \".\" for restricted single-root listings\n * - WINDOWS_DRIVES_ROOT for the Windows drives pseudo-root\n * - absolute path otherwise\n */\n currentPath: string\n /** Optional parent path if navigation upward is allowed. */\n parentPath?: string\n /** Absolute path representing the root or origin point for this listing. */\n rootPath: string\n /** Absolute home directory of the CLI host (useful defaults for unrestricted mode). */\n homePath: string\n /** Human-friendly label for the current path. */\n displayPath: string\n /** Indicates whether entry paths are relative, absolute, or represent the drive pseudo-view. */\n pathKind: FileSystemPathKind\n}\n\nexport interface FileSystemListResponse {\n entries: FileSystemEntry[]\n metadata: FileSystemListingMetadata\n}\n\nexport interface FileSystemCreateFolderRequest {\n /**\n * Path identifier for the currently browsed directory.\n * Matches the `path` parameter used for `/api/filesystem`.\n */\n parentPath?: string\n /** Single folder name (no separators). */\n name: string\n}\n\nexport interface FileSystemCreateFolderResponse {\n /**\n * Path identifier that can be passed back to `/api/filesystem` to browse the new folder.\n * Relative for restricted listings and absolute for unrestricted listings.\n */\n path: string\n /** Absolute folder path on the server host. */\n absolutePath: string\n}\n\nexport interface FileSystemFileContentResponse {\n path: string\n contents: string\n encoding: \"utf-8\" | \"base64\"\n}\n\nexport interface ConfigFileDescriptor {\n id: string\n label: string\n path: string\n language: string\n}\n\nexport type ConfigFileListResponse = ConfigFileDescriptor[]\n\nexport interface ConfigFileContentResponse {\n id: string\n path: string\n contents: string\n exists: boolean\n}\n\nexport interface ConfigFileContentRequest {\n contents: string\n}\n\nexport const WINDOWS_DRIVES_ROOT = \"__drives__\"\n\nexport interface WorkspaceFileResponse {\n workspaceId: string\n relativePath: string\n /** UTF-8 file contents; binary files should be base64 encoded by the caller. */\n contents: string\n encoding?: \"utf-8\" | \"base64\"\n}\n\nexport type WorkspaceFileSearchResponse = FileSystemEntry[]\n\nexport interface InstanceData {\n messageHistory: string[]\n agentModelSelections: AgentModelSelection\n}\n\nexport type InstanceStreamStatus = \"connecting\" | \"connected\" | \"error\" | \"disconnected\"\n\nexport type InstanceStreamEvent = OpenCodeEvent\n\nexport type SideCarKind = \"port\"\n\nexport type SideCarPrefixMode = \"strip\" | \"preserve\"\n\nexport type SideCarStatus = \"running\" | \"stopped\"\n\nexport interface SideCar {\n id: string\n kind: SideCarKind\n name: string\n port: number\n insecure: boolean\n prefixMode: SideCarPrefixMode\n status: SideCarStatus\n createdAt: string\n updatedAt: string\n}\n\nexport interface PreviewSession {\n token: string\n sessionId: string\n targetUrl: string\n proxyUrl: string\n createdAt: string\n}\n\nexport interface BinaryRecord {\n id: string\n path: string\n label: string\n version?: string\n\n /** Indicates that this binary will be picked when workspaces omit an explicit choice. */\n isDefault: boolean\n lastValidatedAt?: string\n validationError?: string\n}\n\nexport type SettingsOwner = string\nexport type SettingsBucket = Record<string, unknown>\nexport type SettingsDoc = Record<string, unknown>\n\nexport interface BinaryListResponse {\n binaries: BinaryRecord[]\n}\n\nexport interface BinaryCreateRequest {\n path: string\n label?: string\n makeDefault?: boolean\n}\n\nexport interface BinaryUpdateRequest {\n label?: string\n makeDefault?: boolean\n}\n\nexport const OPENCODE_V2_REQUIRED_ERROR_CODE = \"opencode_v2_required\" as const\n\nexport interface BinaryValidationResult {\n valid: boolean\n version?: string\n error?: string\n errorCode?: typeof OPENCODE_V2_REQUIRED_ERROR_CODE\n}\n\nexport interface OpenCodeUpdateStatus {\n currentVersion: string\n latestVersion: string | null\n updateAvailable: boolean | null\n canUpgrade: boolean\n checkError?: \"update_check_failed\"\n}\n\nexport interface OpenCodeUpdateResponse {\n success: boolean\n version: string\n}\n\nexport interface SpeechSegment {\n startMs: number\n endMs: number\n text: string\n}\n\nexport interface SpeechCapabilitiesResponse {\n available: boolean\n configured: boolean\n provider: string\n supportsStt: boolean\n supportsTts: boolean\n supportsStreamingTts: boolean\n baseUrl?: string\n sttModel: string\n ttsModel: string\n ttsVoice: string\n ttsFormats: string[]\n streamingTtsFormats: string[]\n separateProviders?: boolean\n sttConfigured?: boolean\n ttsConfigured?: boolean\n sttBaseUrl?: string\n ttsBaseUrl?: string\n}\n\nexport interface SpeechTranscriptionResponse {\n text: string\n language?: string\n durationMs?: number\n segments?: SpeechSegment[]\n}\n\nexport interface SpeechSynthesisResponse {\n audioBase64: string\n mimeType: string\n}\n\nexport interface YoloStateResponse {\n enabled: boolean\n}\n\nexport interface RemoteServerProfile {\n id: string\n name: string\n baseUrl: string\n skipTlsVerify: boolean\n createdAt: string\n updatedAt: string\n lastConnectedAt?: string\n}\n\nexport interface RemoteServerProbeRequest {\n baseUrl: string\n skipTlsVerify?: boolean\n}\n\nexport interface RemoteServerProbeResponse {\n ok: boolean\n reachable: boolean\n normalizedUrl: string\n skipTlsVerify: boolean\n requiresAuth: boolean\n authenticated: boolean\n error?: string\n errorCode?: string\n}\n\nexport interface RemoteProxySessionCreateRequest {\n baseUrl: string\n skipTlsVerify?: boolean\n}\n\nexport interface RemoteProxySessionCreateResponse {\n sessionId: string\n windowUrl: string\n}\n\nexport type WorkspaceEventType =\n | \"workspace.created\"\n | \"workspace.started\"\n | \"workspace.error\"\n | \"workspace.stopped\"\n | \"workspace.log\"\n | \"sidecar.updated\"\n | \"sidecar.removed\"\n | \"storage.configChanged\"\n | \"storage.stateChanged\"\n | \"instance.dataChanged\"\n | \"instance.event\"\n | \"instance.eventStatus\"\n | \"yolo.stateChanged\"\n | \"yolo.autoAccepted\"\n\nexport type WorkspaceEventPayload =\n | { type: \"workspace.created\"; workspace: WorkspaceDescriptor }\n | { type: \"workspace.started\"; workspace: WorkspaceDescriptor }\n | { type: \"workspace.error\"; workspace: WorkspaceDescriptor }\n | { type: \"workspace.stopped\"; workspaceId: string; reason?: \"deleted\" | \"stopped\" }\n | { type: \"workspace.log\"; entry: WorkspaceLogEntry }\n | { type: \"sidecar.updated\"; sidecar: SideCar }\n | { type: \"sidecar.removed\"; sidecarId: string }\n | { type: \"storage.configChanged\"; owner: SettingsOwner; value: SettingsBucket }\n | { type: \"storage.stateChanged\"; owner: SettingsOwner; value: SettingsBucket }\n | { type: \"instance.dataChanged\"; instanceId: string; data: InstanceData }\n | { type: \"instance.event\"; instanceId: string; event: InstanceStreamEvent }\n | { type: \"instance.eventStatus\"; instanceId: string; status: InstanceStreamStatus; generation: number; reason?: string }\n | { type: \"yolo.stateChanged\"; instanceId: string; sessionId: string; enabled: boolean }\n | { type: \"yolo.autoAccepted\"; instanceId: string; sessionId: string; permissionId: string }\n\nexport interface NetworkAddress {\n ip: string\n family: \"ipv4\" | \"ipv6\"\n scope: \"external\" | \"internal\" | \"loopback\"\n /** Remote URL using the server's remote protocol/port for this IP. */\n remoteUrl: string\n}\n\nexport interface LatestReleaseInfo {\n version: string\n tag: string\n url: string\n channel: \"stable\" | \"dev\"\n publishedAt?: string\n notes?: string\n}\n\nexport interface UiMeta {\n version?: string\n source: \"bundled\" | \"downloaded\" | \"previous\" | \"override\" | \"dev-proxy\" | \"missing\"\n}\n\nexport interface SupportMeta {\n supported: boolean\n message?: string\n minServerVersion?: string\n latestServerVersion?: string\n latestServerUrl?: string\n}\n\nexport interface ServerMeta {\n /** URL desktop apps should use to connect (prefers loopback HTTP when enabled). */\n localUrl: string\n /** URL remote clients should use (prefers HTTPS when enabled). */\n remoteUrl?: string\n /** SSE endpoint advertised to clients (`/api/events` by default). */\n eventsUrl: string\n /** Host the server is bound to (e.g., 127.0.0.1 or 0.0.0.0). */\n host: string\n /** Listening mode derived from host binding. */\n listeningMode: \"local\" | \"all\"\n /** Actual local port in use after binding. */\n localPort: number\n /** Actual remote port in use after binding (when remoteUrl is set). */\n remotePort?: number\n /** Display label for the host (e.g., hostname or friendly name). */\n hostLabel: string\n /** Absolute path of the filesystem root exposed to clients. */\n workspaceRoot: string\n /** Reachable addresses for this server, external first. */\n addresses: NetworkAddress[]\n serverVersion?: string\n ui?: UiMeta\n support?: SupportMeta\n /** Optional update info (dev channel only). */\n update?: LatestReleaseInfo | null\n}\n\nexport type {\n Preferences,\n ModelPreference,\n AgentModelSelections,\n RecentFolder,\n OpenCodeBinary,\n}\nexport type { PruneRequest, PruneResult } from \"./opencode/session-pruning/contract\"\n"]}
1
+ {"version":3,"file":"api-types.js","sourceRoot":"","sources":["../src/api-types.ts"],"names":[],"mappings":"AAqQA,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAAA;AA8E/C,MAAM,CAAC,MAAM,+BAA+B,GAAG,sBAA+B,CAAA","sourcesContent":["import type {\n AgentModelSelection,\n AgentModelSelections,\n ModelPreference,\n OpenCodeBinary,\n Preferences,\n RecentFolder,\n} from \"./config/schema\"\nimport type { OpenCodeEvent } from \"@opencode/client\"\n\n/**\n * Canonical HTTP/SSE contract for the CLI server.\n * These types are consumed by both the CLI implementation and any UI clients.\n */\n\nexport type WorkspaceStatus = \"starting\" | \"ready\" | \"stopped\" | \"error\"\n\nexport interface WorkspaceDescriptor {\n id: string\n /** Correlates creation events with the client request that initiated them. */\n requestId?: string\n /** Absolute path on the server host. */\n path: string\n name?: string\n status: WorkspaceStatus\n /** PID/port are populated when the workspace is running. */\n pid?: number\n port?: number\n /** Canonical proxy path the CLI exposes for this instance. */\n proxyPath: string\n /** Identifier of the binary resolved from config. */\n binaryId: string\n binaryLabel: string\n binaryVersion?: string\n createdAt: string\n updatedAt: string\n /** Present when `status` is \"error\". */\n error?: string\n}\n\nexport interface WorkspaceCreateRequest {\n path: string\n name?: string\n requestId?: string\n}\n\nexport interface WorkspaceCloneRequest {\n repositoryUrl: string\n destinationPath: string\n cleanup?: boolean\n}\n\nexport interface WorkspaceCloneResponse {\n path: string\n}\n\nexport type WorkspaceCreateResponse = WorkspaceDescriptor & {\n /** True when this request did not own creation of the returned workspace. */\n reused?: true\n}\nexport type WorkspaceListResponse = WorkspaceDescriptor[]\nexport type WorkspaceDetailResponse = WorkspaceDescriptor\n\nexport interface WorkspaceDeleteResponse {\n id: string\n status: WorkspaceStatus\n}\n\nexport interface ProviderUsageWindow {\n usedPercent: number | null\n remainingPercent: number | null\n windowSeconds: number | null\n resetAt: number | null\n valueLabel?: string\n}\n\nexport interface ProviderUsageResponse {\n requestedProviderId: string\n providerId: string | null\n providerName: string\n modelId?: string\n supported: boolean\n configured: boolean\n ok: boolean\n windows: Record<string, ProviderUsageWindow>\n fetchedAt: number\n}\n\nexport type WorktreeKind = \"root\" | \"worktree\"\n\nexport interface WorktreeDescriptor {\n /** Stable identifier used by CodeNomad + clients (\"root\" for the selected workspace folder). */\n slug: string\n /** Absolute directory path on the server host. */\n directory: string\n kind: WorktreeKind\n /** Optional VCS branch name when available. */\n branch?: string\n}\n\nexport interface WorktreeListResponse {\n worktrees: WorktreeDescriptor[]\n /** True when the workspace folder resolves to a Git repository. */\n isGitRepo?: boolean\n}\n\nexport interface WorktreeCreateRequest {\n slug: string\n /** Optional branch name (defaults to slug). */\n branch?: string\n}\n\nexport type GitChangeKind = \"added\" | \"modified\" | \"deleted\" | \"renamed\" | \"copied\" | \"untracked\" | \"unmerged\"\n\nexport interface WorktreeGitStatusEntry {\n path: string\n originalPath?: string | null\n stagedStatus: GitChangeKind | null\n stagedAdditions: number\n stagedDeletions: number\n unstagedStatus: GitChangeKind | null\n unstagedAdditions: number\n unstagedDeletions: number\n}\n\nexport type WorktreeGitStatusResponse = WorktreeGitStatusEntry[]\n\nexport type WorktreeGitDiffScope = \"staged\" | \"unstaged\"\n\nexport interface WorktreeGitPathsRequest {\n paths: string[]\n}\n\nexport interface WorktreeGitMutationResponse {\n ok: true\n}\n\nexport interface WorktreeGitCommitRequest {\n message: string\n}\n\nexport interface WorktreeGitCommitResponse {\n ok: true\n commitSha?: string\n}\n\nexport interface WorktreeGitDiffResponse {\n path: string\n originalPath?: string | null\n scope: WorktreeGitDiffScope\n before: string\n after: string\n isBinary?: boolean\n}\n\nexport interface WorktreeGitDiffRequest {\n path: string\n originalPath?: string | null\n scope: WorktreeGitDiffScope\n}\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\"\n\nexport interface WorkspaceLogEntry {\n workspaceId: string\n timestamp: string\n level: LogLevel\n message: string\n}\n\nexport interface FileSystemEntry {\n name: string\n /**\n * Path identifier for the entry. Relative to the server root in restricted\n * single-root listings (\".\" represents the root itself); absolute in\n * unrestricted, drives, and multi-root top-level listings.\n */\n path: string\n /** Absolute path when available (unrestricted and multi-root listings). */\n absolutePath?: string\n type: \"file\" | \"directory\"\n size?: number\n /** ISO timestamp of last modification when available. */\n modifiedAt?: string\n}\n\nexport type FileSystemScope = \"restricted\" | \"unrestricted\"\nexport type FileSystemPathKind = \"relative\" | \"absolute\" | \"drives\"\n\nexport interface FileSystemListingMetadata {\n scope: FileSystemScope\n /**\n * Canonical identifier of the current view:\n * - \".\" for restricted single-root listings\n * - WINDOWS_DRIVES_ROOT for the Windows drives pseudo-root\n * - absolute path otherwise\n */\n currentPath: string\n /** Optional parent path if navigation upward is allowed. */\n parentPath?: string\n /** Absolute path representing the root or origin point for this listing. */\n rootPath: string\n /** Absolute home directory of the CLI host (useful defaults for unrestricted mode). */\n homePath: string\n /** Human-friendly label for the current path. */\n displayPath: string\n /** Indicates whether entry paths are relative, absolute, or represent the drive pseudo-view. */\n pathKind: FileSystemPathKind\n}\n\nexport interface FileSystemListResponse {\n entries: FileSystemEntry[]\n metadata: FileSystemListingMetadata\n}\n\nexport interface FileSystemCreateFolderRequest {\n /**\n * Path identifier for the currently browsed directory.\n * Matches the `path` parameter used for `/api/filesystem`.\n */\n parentPath?: string\n /** Single folder name (no separators). */\n name: string\n}\n\nexport interface FileSystemCreateFolderResponse {\n /**\n * Path identifier that can be passed back to `/api/filesystem` to browse the new folder.\n * Relative for restricted listings and absolute for unrestricted listings.\n */\n path: string\n /** Absolute folder path on the server host. */\n absolutePath: string\n}\n\nexport interface FileSystemFileContentResponse {\n path: string\n contents: string\n encoding: \"utf-8\" | \"base64\"\n}\n\nexport interface ConfigFileDescriptor {\n id: string\n label: string\n path: string\n language: string\n}\n\nexport type ConfigFileListResponse = ConfigFileDescriptor[]\n\nexport interface ConfigFileContentResponse {\n id: string\n path: string\n contents: string\n exists: boolean\n}\n\nexport interface ConfigFileContentRequest {\n contents: string\n}\n\nexport const WINDOWS_DRIVES_ROOT = \"__drives__\"\n\nexport interface WorkspaceFileResponse {\n workspaceId: string\n relativePath: string\n /** UTF-8 file contents; binary files should be base64 encoded by the caller. */\n contents: string\n encoding?: \"utf-8\" | \"base64\"\n}\n\nexport type WorkspaceFileSearchResponse = FileSystemEntry[]\n\nexport interface InstanceData {\n messageHistory: string[]\n agentModelSelections: AgentModelSelection\n}\n\nexport type InstanceStreamStatus = \"connecting\" | \"connected\" | \"error\" | \"disconnected\"\n\nexport type InstanceStreamEvent = OpenCodeEvent\n\nexport type SideCarKind = \"port\"\n\nexport type SideCarPrefixMode = \"strip\" | \"preserve\"\n\nexport type SideCarStatus = \"running\" | \"stopped\"\n\nexport interface SideCar {\n id: string\n kind: SideCarKind\n name: string\n port: number\n insecure: boolean\n prefixMode: SideCarPrefixMode\n status: SideCarStatus\n createdAt: string\n updatedAt: string\n}\n\nexport interface PreviewSession {\n token: string\n sessionId: string\n targetUrl: string\n proxyUrl: string\n createdAt: string\n}\n\nexport interface BinaryRecord {\n id: string\n path: string\n label: string\n version?: string\n\n /** Indicates that this binary will be picked when workspaces omit an explicit choice. */\n isDefault: boolean\n lastValidatedAt?: string\n validationError?: string\n}\n\nexport type SettingsOwner = string\nexport type SettingsBucket = Record<string, unknown>\nexport type SettingsDoc = Record<string, unknown>\n\nexport interface BinaryListResponse {\n binaries: BinaryRecord[]\n}\n\nexport interface BinaryCreateRequest {\n path: string\n label?: string\n makeDefault?: boolean\n}\n\nexport interface BinaryUpdateRequest {\n label?: string\n makeDefault?: boolean\n}\n\nexport const OPENCODE_V2_REQUIRED_ERROR_CODE = \"opencode_v2_required\" as const\n\nexport interface BinaryValidationResult {\n valid: boolean\n version?: string\n error?: string\n errorCode?: typeof OPENCODE_V2_REQUIRED_ERROR_CODE\n}\n\nexport interface OpenCodeUpdateStatus {\n currentVersion: string\n latestVersion: string | null\n updateAvailable: boolean | null\n canUpgrade: boolean\n checkError?: \"update_check_failed\"\n}\n\nexport interface OpenCodeUpdateResponse {\n success: boolean\n version: string\n}\n\nexport interface SpeechSegment {\n startMs: number\n endMs: number\n text: string\n}\n\nexport interface SpeechCapabilitiesResponse {\n available: boolean\n configured: boolean\n provider: string\n supportsStt: boolean\n supportsTts: boolean\n supportsStreamingTts: boolean\n baseUrl?: string\n sttModel: string\n ttsModel: string\n ttsVoice: string\n ttsFormats: string[]\n streamingTtsFormats: string[]\n separateProviders?: boolean\n sttConfigured?: boolean\n ttsConfigured?: boolean\n sttBaseUrl?: string\n ttsBaseUrl?: string\n}\n\nexport interface SpeechTranscriptionResponse {\n text: string\n language?: string\n durationMs?: number\n segments?: SpeechSegment[]\n}\n\nexport interface SpeechSynthesisResponse {\n audioBase64: string\n mimeType: string\n}\n\nexport interface YoloStateResponse {\n enabled: boolean\n}\n\nexport interface RemoteServerProfile {\n id: string\n name: string\n baseUrl: string\n skipTlsVerify: boolean\n createdAt: string\n updatedAt: string\n lastConnectedAt?: string\n}\n\nexport interface RemoteServerProbeRequest {\n baseUrl: string\n skipTlsVerify?: boolean\n}\n\nexport interface RemoteServerProbeResponse {\n ok: boolean\n reachable: boolean\n normalizedUrl: string\n skipTlsVerify: boolean\n requiresAuth: boolean\n authenticated: boolean\n error?: string\n errorCode?: string\n}\n\nexport interface RemoteProxySessionCreateRequest {\n baseUrl: string\n skipTlsVerify?: boolean\n}\n\nexport interface RemoteProxySessionCreateResponse {\n sessionId: string\n windowUrl: string\n}\n\nexport type WorkspaceEventType =\n | \"workspace.created\"\n | \"workspace.started\"\n | \"workspace.error\"\n | \"workspace.stopped\"\n | \"workspace.log\"\n | \"sidecar.updated\"\n | \"sidecar.removed\"\n | \"storage.configChanged\"\n | \"storage.stateChanged\"\n | \"instance.dataChanged\"\n | \"instance.event\"\n | \"instance.eventStatus\"\n | \"yolo.stateChanged\"\n | \"yolo.autoAccepted\"\n\nexport type WorkspaceEventPayload =\n | { type: \"workspace.created\"; workspace: WorkspaceDescriptor }\n | { type: \"workspace.started\"; workspace: WorkspaceDescriptor }\n | { type: \"workspace.error\"; workspace: WorkspaceDescriptor }\n | { type: \"workspace.stopped\"; workspaceId: string; reason?: \"deleted\" | \"stopped\" }\n | { type: \"workspace.log\"; entry: WorkspaceLogEntry }\n | { type: \"sidecar.updated\"; sidecar: SideCar }\n | { type: \"sidecar.removed\"; sidecarId: string }\n | { type: \"storage.configChanged\"; owner: SettingsOwner; value: SettingsBucket }\n | { type: \"storage.stateChanged\"; owner: SettingsOwner; value: SettingsBucket }\n | { type: \"instance.dataChanged\"; instanceId: string; data: InstanceData }\n | { type: \"instance.event\"; instanceId: string; event: InstanceStreamEvent }\n | { type: \"instance.eventStatus\"; instanceId: string; status: InstanceStreamStatus; generation: number; reason?: string }\n | { type: \"yolo.stateChanged\"; instanceId: string; sessionId: string; enabled: boolean }\n | { type: \"yolo.autoAccepted\"; instanceId: string; sessionId: string; permissionId: string }\n\nexport interface NetworkAddress {\n ip: string\n family: \"ipv4\" | \"ipv6\"\n scope: \"external\" | \"internal\" | \"loopback\"\n /** Remote URL using the server's remote protocol/port for this IP. */\n remoteUrl: string\n}\n\nexport interface LatestReleaseInfo {\n version: string\n tag: string\n url: string\n channel: \"stable\" | \"dev\"\n publishedAt?: string\n notes?: string\n}\n\nexport interface UiMeta {\n version?: string\n source: \"bundled\" | \"downloaded\" | \"previous\" | \"override\" | \"dev-proxy\" | \"missing\"\n}\n\nexport interface SupportMeta {\n supported: boolean\n message?: string\n minServerVersion?: string\n latestServerVersion?: string\n latestServerUrl?: string\n}\n\nexport interface ServerMeta {\n /** URL desktop apps should use to connect (prefers loopback HTTP when enabled). */\n localUrl: string\n /** URL remote clients should use (prefers HTTPS when enabled). */\n remoteUrl?: string\n /** SSE endpoint advertised to clients (`/api/events` by default). */\n eventsUrl: string\n /** Host the server is bound to (e.g., 127.0.0.1 or 0.0.0.0). */\n host: string\n /** Listening mode derived from host binding. */\n listeningMode: \"local\" | \"all\"\n /** Actual local port in use after binding. */\n localPort: number\n /** Actual remote port in use after binding (when remoteUrl is set). */\n remotePort?: number\n /** Display label for the host (e.g., hostname or friendly name). */\n hostLabel: string\n /** Absolute path of the filesystem root exposed to clients. */\n workspaceRoot: string\n /** Reachable addresses for this server, external first. */\n addresses: NetworkAddress[]\n serverVersion?: string\n ui?: UiMeta\n support?: SupportMeta\n /** Optional update info (dev channel only). */\n update?: LatestReleaseInfo | null\n}\n\nexport type {\n Preferences,\n ModelPreference,\n AgentModelSelections,\n RecentFolder,\n OpenCodeBinary,\n}\nexport type { PruneRequest, PruneResult } from \"./opencode/session-pruning/contract\"\n"]}
@@ -4,7 +4,7 @@ import http from "node:http";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import test from "node:test";
7
- import { AUTOMATION_BRIDGE_PATH, automationBridgeDirectories, createAutomationBridgeRegistration, parseDeveloperAction, publishAutomationBridge, removeLegacyAutomationPlugin, setupAutomationPlugin, } from "./automation-plugin";
7
+ import { AUTOMATION_BRIDGE_PATH, automationBridgeDirectory, automationBridgeDirectories, createAutomationBridgeRegistration, parseDeveloperAction, publishAutomationBridge, removeLegacyAutomationPlugin, setupAutomationPlugin, } from "./automation-plugin";
8
8
  function listen(handler) {
9
9
  return new Promise((resolve) => {
10
10
  const server = http.createServer((request, response) => {
@@ -33,6 +33,28 @@ async function collectTools() {
33
33
  function closeServer(server) {
34
34
  return new Promise((resolve) => server?.close(() => resolve()) ?? resolve());
35
35
  }
36
+ function isolateAutomationBridgeRegistry(root) {
37
+ const previousLocalAppData = process.env.LOCALAPPDATA;
38
+ const previousXdgRuntimeDir = process.env.XDG_RUNTIME_DIR;
39
+ const previousWslDistroName = process.env.WSL_DISTRO_NAME;
40
+ process.env.LOCALAPPDATA = root;
41
+ process.env.XDG_RUNTIME_DIR = root;
42
+ delete process.env.WSL_DISTRO_NAME;
43
+ return () => {
44
+ if (previousLocalAppData === undefined)
45
+ delete process.env.LOCALAPPDATA;
46
+ else
47
+ process.env.LOCALAPPDATA = previousLocalAppData;
48
+ if (previousXdgRuntimeDir === undefined)
49
+ delete process.env.XDG_RUNTIME_DIR;
50
+ else
51
+ process.env.XDG_RUNTIME_DIR = previousXdgRuntimeDir;
52
+ if (previousWslDistroName === undefined)
53
+ delete process.env.WSL_DISTRO_NAME;
54
+ else
55
+ process.env.WSL_DISTRO_NAME = previousWslDistroName;
56
+ };
57
+ }
36
58
  test("validates Developer Mode actions", () => {
37
59
  assert.deepEqual(parseDeveloperAction({ action: "type", ref: "e4", text: "CodeNomad" }), {
38
60
  action: "type",
@@ -78,8 +100,7 @@ test("removes only the generated legacy global plugin shim", async () => {
78
100
  });
79
101
  test("restart waits for a new native generation and returns a fresh inspection", async () => {
80
102
  const root = await mkdtemp(path.join(os.tmpdir(), "codenomad-automation-restart-"));
81
- const previousLocalAppData = process.env.LOCALAPPDATA;
82
- process.env.LOCALAPPDATA = root;
103
+ const restoreBridgeRegistry = isolateAutomationBridgeRegistry(root);
83
104
  const definitions = [];
84
105
  let removeOld;
85
106
  let removeNew;
@@ -140,17 +161,13 @@ test("restart waits for a new native generation and returns a fresh inspection",
140
161
  await closeServer(newServer);
141
162
  await closeServer(preexistingServer);
142
163
  await Promise.all(distractorServers.map(closeServer));
143
- if (previousLocalAppData === undefined)
144
- delete process.env.LOCALAPPDATA;
145
- else
146
- process.env.LOCALAPPDATA = previousLocalAppData;
164
+ restoreBridgeRegistry();
147
165
  await rm(root, { recursive: true, force: true });
148
166
  }
149
167
  });
150
168
  test("keeps inspected targets isolated per plugin setup", async () => {
151
169
  const root = await mkdtemp(path.join(os.tmpdir(), "codenomad-automation-isolation-"));
152
- const previousLocalAppData = process.env.LOCALAPPDATA;
153
- process.env.LOCALAPPDATA = root;
170
+ const restoreBridgeRegistry = isolateAutomationBridgeRegistry(root);
154
171
  let removeBridge;
155
172
  let server;
156
173
  try {
@@ -167,17 +184,13 @@ test("keeps inspected targets isolated per plugin setup", async () => {
167
184
  finally {
168
185
  await removeBridge?.();
169
186
  await closeServer(server);
170
- if (previousLocalAppData === undefined)
171
- delete process.env.LOCALAPPDATA;
172
- else
173
- process.env.LOCALAPPDATA = previousLocalAppData;
187
+ restoreBridgeRegistry();
174
188
  await rm(root, { recursive: true, force: true });
175
189
  }
176
190
  });
177
191
  test("pins parallel sessions to their independently inspected bridges", async () => {
178
192
  const root = await mkdtemp(path.join(os.tmpdir(), "codenomad-automation-sessions-"));
179
- const previousLocalAppData = process.env.LOCALAPPDATA;
180
- process.env.LOCALAPPDATA = root;
193
+ const restoreBridgeRegistry = isolateAutomationBridgeRegistry(root);
181
194
  const removals = [];
182
195
  const servers = [];
183
196
  try {
@@ -199,21 +212,13 @@ test("pins parallel sessions to their independently inspected bridges", async ()
199
212
  finally {
200
213
  await Promise.all(removals.map((remove) => remove()));
201
214
  await Promise.all(servers.map(closeServer));
202
- if (previousLocalAppData === undefined)
203
- delete process.env.LOCALAPPDATA;
204
- else
205
- process.env.LOCALAPPDATA = previousLocalAppData;
215
+ restoreBridgeRegistry();
206
216
  await rm(root, { recursive: true, force: true });
207
217
  }
208
218
  });
209
219
  test("prunes stale registry pressure before limiting discovery", async () => {
210
220
  const root = await mkdtemp(path.join(os.tmpdir(), "codenomad-automation-stale-"));
211
- const previousLocalAppData = process.env.LOCALAPPDATA;
212
- const previousXdgRuntimeDir = process.env.XDG_RUNTIME_DIR;
213
- const previousWslDistroName = process.env.WSL_DISTRO_NAME;
214
- process.env.LOCALAPPDATA = root;
215
- process.env.XDG_RUNTIME_DIR = root;
216
- delete process.env.WSL_DISTRO_NAME;
221
+ const restoreBridgeRegistry = isolateAutomationBridgeRegistry(root);
217
222
  let removeBridge;
218
223
  let server;
219
224
  try {
@@ -222,7 +227,7 @@ test("prunes stale registry pressure before limiting discovery", async () => {
222
227
  : { result: { target: { id: "live", title: "Live", url: "http://app.test" }, nodes: [], diagnostics: [] } });
223
228
  server = bridge.server;
224
229
  removeBridge = await publishAutomationBridge(createAutomationBridgeRegistration(bridge.url));
225
- const directory = automationBridgeDirectories()[0];
230
+ const directory = automationBridgeDirectory();
226
231
  assert.equal(path.dirname(path.dirname(directory)), root);
227
232
  const base = Date.now() + 10_000;
228
233
  for (let index = 0; index < 70; index += 1) {
@@ -243,18 +248,7 @@ test("prunes stale registry pressure before limiting discovery", async () => {
243
248
  finally {
244
249
  await removeBridge?.();
245
250
  await closeServer(server);
246
- if (previousLocalAppData === undefined)
247
- delete process.env.LOCALAPPDATA;
248
- else
249
- process.env.LOCALAPPDATA = previousLocalAppData;
250
- if (previousXdgRuntimeDir === undefined)
251
- delete process.env.XDG_RUNTIME_DIR;
252
- else
253
- process.env.XDG_RUNTIME_DIR = previousXdgRuntimeDir;
254
- if (previousWslDistroName === undefined)
255
- delete process.env.WSL_DISTRO_NAME;
256
- else
257
- process.env.WSL_DISTRO_NAME = previousWslDistroName;
251
+ restoreBridgeRegistry();
258
252
  await rm(root, { recursive: true, force: true });
259
253
  }
260
254
  });
@@ -1 +1 @@
1
- {"version":3,"file":"automation-plugin.test.js","sourceRoot":"","sources":["../../src/opencode/automation-plugin.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\"\nimport { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from \"node:fs/promises\"\nimport http from \"node:http\"\nimport os from \"node:os\"\nimport path from \"node:path\"\nimport test from \"node:test\"\nimport {\n AUTOMATION_BRIDGE_PATH,\n automationBridgeDirectories,\n createAutomationBridgeRegistration,\n parseDeveloperAction,\n publishAutomationBridge,\n removeLegacyAutomationPlugin,\n setupAutomationPlugin,\n} from \"./automation-plugin\"\n\ntype ToolDefinition = {\n name: string\n execute(input: unknown, context: { sessionID: string }): Promise<{ content: unknown }>\n}\n\nfunction listen(handler: (body: Record<string, unknown>) => Promise<Record<string, unknown>>) {\n return new Promise<{ server: http.Server; url: string }>((resolve) => {\n const server = http.createServer((request, response) => {\n const chunks: Buffer[] = []\n request.on(\"data\", (chunk) => chunks.push(Buffer.from(chunk)))\n request.on(\"end\", () => {\n void handler(JSON.parse(Buffer.concat(chunks).toString(\"utf8\"))).then((body) => {\n response.setHeader(\"content-type\", \"application/json\")\n response.end(JSON.stringify(body))\n })\n })\n })\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address() as { port: number }\n resolve({ server, url: `http://127.0.0.1:${address.port}` })\n })\n })\n}\n\nasync function collectTools(): Promise<ToolDefinition[]> {\n const definitions: ToolDefinition[] = []\n await setupAutomationPlugin({\n tool: { transform: async (callback) => callback({ add: (value) => definitions.push(value as ToolDefinition) }) },\n })\n return definitions\n}\n\nfunction closeServer(server: http.Server | undefined): Promise<void> {\n return new Promise((resolve) => server?.close(() => resolve()) ?? resolve())\n}\n\ntest(\"validates Developer Mode actions\", () => {\n assert.deepEqual(parseDeveloperAction({ action: \"type\", ref: \"e4\", text: \"CodeNomad\" }), {\n action: \"type\",\n ref: \"e4\",\n text: \"CodeNomad\",\n })\n assert.deepEqual(parseDeveloperAction({ action: \"restart\" }), { action: \"restart\" })\n assert.throws(() => parseDeveloperAction({ action: \"click\" }), /click requires ref/)\n})\n\ntest(\"registers developer tools while execution remains session-gated\", async () => {\n const tools: string[] = []\n await setupAutomationPlugin({\n tool: { transform: async (callback) => callback({ add: (value) => tools.push(value.name) }) },\n })\n\n assert.deepEqual(tools, [\"inspect\", \"act\", \"screenshot\"])\n})\n\ntest(\"discovers the Windows bridge registry from a WSL plugin\", () => {\n assert.deepEqual(automationBridgeDirectories({\n platform: \"linux\",\n env: { WSL_DISTRO_NAME: \"Ubuntu\", XDG_RUNTIME_DIR: \"/run/user/1000\" },\n home: \"/home/dev\",\n windowsLocalAppData: \"/mnt/c/Users/dev/AppData/Local\",\n }), [\n \"/run/user/1000/codenomad/automation-bridges\",\n \"/mnt/c/Users/dev/AppData/Local/CodeNomad/automation-bridges\",\n ])\n})\n\ntest(\"removes only the generated legacy global plugin shim\", async () => {\n const root = await mkdtemp(path.join(os.tmpdir(), \"codenomad-legacy-plugin-\"))\n const plugin = path.join(root, \"opencode\", \"plugins\", \"codenomad-automation.ts\")\n await mkdir(path.dirname(plugin), { recursive: true })\n try {\n await writeFile(plugin, 'export { default } from \"file:///C:/CodeNomad/resources/server/dist/opencode/automation-plugin.js\"\\n')\n assert.equal(await removeLegacyAutomationPlugin(root), true)\n await assert.rejects(readFile(plugin, \"utf8\"), { code: \"ENOENT\" })\n\n await writeFile(plugin, \"export default { id: 'user-plugin' }\\n\")\n assert.equal(await removeLegacyAutomationPlugin(root), false)\n assert.match(await readFile(plugin, \"utf8\"), /user-plugin/)\n } finally {\n await rm(root, { recursive: true, force: true })\n }\n})\n\ntest(\"restart waits for a new native generation and returns a fresh inspection\", async () => {\n const root = await mkdtemp(path.join(os.tmpdir(), \"codenomad-automation-restart-\"))\n const previousLocalAppData = process.env.LOCALAPPDATA\n process.env.LOCALAPPDATA = root\n const definitions: ToolDefinition[] = []\n let removeOld: (() => Promise<void>) | undefined\n let removeNew: (() => Promise<void>) | undefined\n let removePreexisting: (() => Promise<void>) | undefined\n const removeDistractors: Array<() => Promise<void>> = []\n let oldServer: http.Server | undefined\n let newServer: http.Server | undefined\n let preexistingServer: http.Server | undefined\n const distractorServers: http.Server[] = []\n let restarting = false\n\n try {\n await setupAutomationPlugin({\n tool: { transform: async (callback) => callback({ add: (value) => definitions.push(value as typeof definitions[number]) }) },\n })\n const replacement = await listen(async (body) => body.mode === \"developer-probe\"\n ? { result: { available: true, nativeIdentity: \"electron:test\", runId: \"run-new\" } }\n : { result: { target: { id: \"page-new\", title: \"Restarted\", url: \"http://app.test\" }, nodes: [], diagnostics: [] } })\n newServer = replacement.server\n const preexisting = await listen(async (body) => body.mode === \"developer-probe\" && restarting\n ? { result: { available: true, nativeIdentity: \"electron:test\", runId: \"run-preexisting\" } }\n : body.mode === \"developer-probe\"\n ? { result: { available: false } }\n : { result: { target: { id: \"page-wrong\", title: \"Pre-existing\", url: \"http://app.test\" }, nodes: [], diagnostics: [] } })\n preexistingServer = preexisting.server\n removePreexisting = await publishAutomationBridge(createAutomationBridgeRegistration(preexisting.url))\n const distractors = await Promise.all(Array.from({ length: 5 }, (_, index) => listen(async (body) => body.mode === \"developer-probe\"\n ? { result: { available: true, nativeIdentity: `electron:distractor-${index}`, runId: `run-distractor-${index}` } }\n : { result: { target: { id: `page-${index}`, title: \"Distractor\", url: \"http://app.test\" }, nodes: [], diagnostics: [] } })))\n distractorServers.push(...distractors.map((value) => value.server))\n const old = await listen(async (body) => {\n if (body.mode === \"developer-probe\") return { result: { available: true, nativeIdentity: \"electron:test\", runId: \"run-old\" } }\n if (body.command && (body.command as { action?: unknown }).action === \"inspect\") {\n return { result: { target: { id: \"page-old\", title: \"Original\", url: \"http://app.test\" }, nodes: [], diagnostics: [] } }\n }\n restarting = true\n removeNew = await publishAutomationBridge(createAutomationBridgeRegistration(replacement.url))\n for (const distractor of distractors) {\n await new Promise((resolve) => setTimeout(resolve, 2))\n removeDistractors.push(await publishAutomationBridge(createAutomationBridgeRegistration(distractor.url)))\n }\n return { result: { state: \"starting\" } }\n })\n oldServer = old.server\n removeOld = await publishAutomationBridge(createAutomationBridgeRegistration(old.url))\n\n const inspect = definitions.find((definition) => definition.name === \"inspect\")!\n await inspect.execute({}, { sessionID: \"session-1\" })\n const act = definitions.find((definition) => definition.name === \"act\")!\n const result = await act.execute({ action: \"restart\" }, { sessionID: \"session-1\" })\n assert.match(String(result.content), /Restarted/)\n } finally {\n await removeOld?.()\n await removeNew?.()\n await removePreexisting?.()\n await Promise.all(removeDistractors.map((remove) => remove()))\n await closeServer(oldServer)\n await closeServer(newServer)\n await closeServer(preexistingServer)\n await Promise.all(distractorServers.map(closeServer))\n if (previousLocalAppData === undefined) delete process.env.LOCALAPPDATA\n else process.env.LOCALAPPDATA = previousLocalAppData\n await rm(root, { recursive: true, force: true })\n }\n})\n\ntest(\"keeps inspected targets isolated per plugin setup\", async () => {\n const root = await mkdtemp(path.join(os.tmpdir(), \"codenomad-automation-isolation-\"))\n const previousLocalAppData = process.env.LOCALAPPDATA\n process.env.LOCALAPPDATA = root\n let removeBridge: (() => Promise<void>) | undefined\n let server: http.Server | undefined\n try {\n const bridge = await listen(async (body) => body.mode === \"developer-probe\"\n ? { result: { available: true, nativeIdentity: \"electron:isolation\", runId: \"run-isolation\" } }\n : { result: { target: { id: \"isolated\", title: \"Isolated\", url: \"http://app.test\" }, nodes: [], diagnostics: [] } })\n server = bridge.server\n removeBridge = await publishAutomationBridge(createAutomationBridgeRegistration(bridge.url))\n const first = await collectTools()\n const second = await collectTools()\n await first.find((definition) => definition.name === \"inspect\")!.execute({}, { sessionID: \"session-isolation\" })\n await assert.rejects(\n second.find((definition) => definition.name === \"screenshot\")!.execute({}, { sessionID: \"session-isolation\" }),\n /Run codenomad\\.inspect/,\n )\n } finally {\n await removeBridge?.()\n await closeServer(server)\n if (previousLocalAppData === undefined) delete process.env.LOCALAPPDATA\n else process.env.LOCALAPPDATA = previousLocalAppData\n await rm(root, { recursive: true, force: true })\n }\n})\n\ntest(\"pins parallel sessions to their independently inspected bridges\", async () => {\n const root = await mkdtemp(path.join(os.tmpdir(), \"codenomad-automation-sessions-\"))\n const previousLocalAppData = process.env.LOCALAPPDATA\n process.env.LOCALAPPDATA = root\n const removals: Array<() => Promise<void>> = []\n const servers: http.Server[] = []\n try {\n for (const sessionID of [\"session-a\", \"session-b\"]) {\n const bridge = await listen(async (body) => body.mode === \"developer-probe\"\n ? { result: { available: body.sessionID === sessionID, nativeIdentity: `electron:${sessionID}`, runId: `run-${sessionID}` } }\n : { result: { target: { id: sessionID, title: sessionID, url: \"http://app.test\" }, nodes: [], diagnostics: [] } })\n servers.push(bridge.server)\n removals.push(await publishAutomationBridge(createAutomationBridgeRegistration(bridge.url)))\n }\n const tools = await collectTools()\n const inspect = tools.find((definition) => definition.name === \"inspect\")!\n const screenshot = tools.find((definition) => definition.name === \"screenshot\")!\n await inspect.execute({}, { sessionID: \"session-a\" })\n await inspect.execute({}, { sessionID: \"session-b\" })\n assert.match(String((await screenshot.execute({}, { sessionID: \"session-a\" })).content), /session-a/)\n assert.match(String((await screenshot.execute({}, { sessionID: \"session-b\" })).content), /session-b/)\n } finally {\n await Promise.all(removals.map((remove) => remove()))\n await Promise.all(servers.map(closeServer))\n if (previousLocalAppData === undefined) delete process.env.LOCALAPPDATA\n else process.env.LOCALAPPDATA = previousLocalAppData\n await rm(root, { recursive: true, force: true })\n }\n})\n\ntest(\"prunes stale registry pressure before limiting discovery\", async () => {\n const root = await mkdtemp(path.join(os.tmpdir(), \"codenomad-automation-stale-\"))\n const previousLocalAppData = process.env.LOCALAPPDATA\n const previousXdgRuntimeDir = process.env.XDG_RUNTIME_DIR\n const previousWslDistroName = process.env.WSL_DISTRO_NAME\n process.env.LOCALAPPDATA = root\n process.env.XDG_RUNTIME_DIR = root\n delete process.env.WSL_DISTRO_NAME\n let removeBridge: (() => Promise<void>) | undefined\n let server: http.Server | undefined\n try {\n const bridge = await listen(async (body) => body.mode === \"developer-probe\"\n ? { result: { available: true, nativeIdentity: \"electron:live\", runId: \"run-live\" } }\n : { result: { target: { id: \"live\", title: \"Live\", url: \"http://app.test\" }, nodes: [], diagnostics: [] } })\n server = bridge.server\n removeBridge = await publishAutomationBridge(createAutomationBridgeRegistration(bridge.url))\n const directory = automationBridgeDirectories()[0]\n assert.equal(path.dirname(path.dirname(directory)), root)\n const base = Date.now() + 10_000\n for (let index = 0; index < 70; index += 1) {\n const startedAt = base + index\n const token = `dead${String(index).padStart(8, \"0\")}`\n await writeFile(path.join(directory, `${startedAt}-99999999-${token}.json`), JSON.stringify({\n version: 1,\n url: new URL(AUTOMATION_BRIDGE_PATH, bridge.url).href,\n token,\n pid: 99_999_999,\n startedAt,\n }))\n }\n const inspect = (await collectTools()).find((definition) => definition.name === \"inspect\")!\n assert.match(String((await inspect.execute({}, { sessionID: \"session-live\" })).content), /Live/)\n assert.equal((await readdir(directory)).length, 1)\n } finally {\n await removeBridge?.()\n await closeServer(server)\n if (previousLocalAppData === undefined) delete process.env.LOCALAPPDATA\n else process.env.LOCALAPPDATA = previousLocalAppData\n if (previousXdgRuntimeDir === undefined) delete process.env.XDG_RUNTIME_DIR\n else process.env.XDG_RUNTIME_DIR = previousXdgRuntimeDir\n if (previousWslDistroName === undefined) delete process.env.WSL_DISTRO_NAME\n else process.env.WSL_DISTRO_NAME = previousWslDistroName\n await rm(root, { recursive: true, force: true })\n }\n})\n"]}
1
+ {"version":3,"file":"automation-plugin.test.js","sourceRoot":"","sources":["../../src/opencode/automation-plugin.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACnF,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,2BAA2B,EAC3B,kCAAkC,EAClC,oBAAoB,EACpB,uBAAuB,EACvB,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,qBAAqB,CAAA;AAO5B,SAAS,MAAM,CAAC,OAA4E;IAC1F,OAAO,IAAI,OAAO,CAAuC,CAAC,OAAO,EAAE,EAAE;QACnE,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;YACrD,MAAM,MAAM,GAAa,EAAE,CAAA;YAC3B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YAC9D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACrB,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;oBAC7E,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAA;oBACtD,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;gBACpC,CAAC,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;YACjC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAsB,CAAA;YACpD,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,oBAAoB,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;QAC9D,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,KAAK,UAAU,YAAY;IACzB,MAAM,WAAW,GAAqB,EAAE,CAAA;IACxC,MAAM,qBAAqB,CAAC;QAC1B,IAAI,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,KAAuB,CAAC,EAAE,CAAC,EAAE;KACjH,CAAC,CAAA;IACF,OAAO,WAAW,CAAA;AACpB,CAAC;AAED,SAAS,WAAW,CAAC,MAA+B;IAClD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,OAAO,EAAE,CAAC,CAAA;AAC9E,CAAC;AAED,SAAS,+BAA+B,CAAC,IAAY;IACnD,MAAM,oBAAoB,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAA;IACrD,MAAM,qBAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;IACzD,MAAM,qBAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;IACzD,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,IAAI,CAAA;IAC/B,OAAO,CAAC,GAAG,CAAC,eAAe,GAAG,IAAI,CAAA;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;IAClC,OAAO,GAAG,EAAE;QACV,IAAI,oBAAoB,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAA;;YAClE,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,oBAAoB,CAAA;QACpD,IAAI,qBAAqB,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;;YACtE,OAAO,CAAC,GAAG,CAAC,eAAe,GAAG,qBAAqB,CAAA;QACxD,IAAI,qBAAqB,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;;YACtE,OAAO,CAAC,GAAG,CAAC,eAAe,GAAG,qBAAqB,CAAA;IAC1D,CAAC,CAAA;AACH,CAAC;AAED,IAAI,CAAC,kCAAkC,EAAE,GAAG,EAAE;IAC5C,MAAM,CAAC,SAAS,CAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE;QACvF,MAAM,EAAE,MAAM;QACd,GAAG,EAAE,IAAI;QACT,IAAI,EAAE,WAAW;KAClB,CAAC,CAAA;IACF,MAAM,CAAC,SAAS,CAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAA;IACpF,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,oBAAoB,CAAC,CAAA;AACtF,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;IACjF,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,MAAM,qBAAqB,CAAC;QAC1B,IAAI,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;KAC9F,CAAC,CAAA;IAEF,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAA;AAC3D,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACnE,MAAM,CAAC,SAAS,CAAC,2BAA2B,CAAC;QAC3C,QAAQ,EAAE,OAAO;QACjB,GAAG,EAAE,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,gBAAgB,EAAE;QACrE,IAAI,EAAE,WAAW;QACjB,mBAAmB,EAAE,gCAAgC;KACtD,CAAC,EAAE;QACF,6CAA6C;QAC7C,6DAA6D;KAC9D,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;IACtE,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,0BAA0B,CAAC,CAAC,CAAA;IAC9E,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,yBAAyB,CAAC,CAAA;IAChF,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACtD,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,MAAM,EAAE,sGAAsG,CAAC,CAAA;QAC/H,MAAM,CAAC,KAAK,CAAC,MAAM,4BAA4B,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;QAC5D,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;QAElE,MAAM,SAAS,CAAC,MAAM,EAAE,wCAAwC,CAAC,CAAA;QACjE,MAAM,CAAC,KAAK,CAAC,MAAM,4BAA4B,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAA;QAC7D,MAAM,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,CAAC,CAAA;IAC7D,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IAClD,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;IAC1F,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,+BAA+B,CAAC,CAAC,CAAA;IACnF,MAAM,qBAAqB,GAAG,+BAA+B,CAAC,IAAI,CAAC,CAAA;IACnE,MAAM,WAAW,GAAqB,EAAE,CAAA;IACxC,IAAI,SAA4C,CAAA;IAChD,IAAI,SAA4C,CAAA;IAChD,IAAI,iBAAoD,CAAA;IACxD,MAAM,iBAAiB,GAA+B,EAAE,CAAA;IACxD,IAAI,SAAkC,CAAA;IACtC,IAAI,SAAkC,CAAA;IACtC,IAAI,iBAA0C,CAAA;IAC9C,MAAM,iBAAiB,GAAkB,EAAE,CAAA;IAC3C,IAAI,UAAU,GAAG,KAAK,CAAA;IAEtB,IAAI,CAAC;QACH,MAAM,qBAAqB,CAAC;YAC1B,IAAI,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,KAAmC,CAAC,EAAE,CAAC,EAAE;SAC7H,CAAC,CAAA;QACF,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB;YAC9E,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;YACpF,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;QACvH,SAAS,GAAG,WAAW,CAAC,MAAM,CAAA;QAC9B,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB,IAAI,UAAU;YAC5F,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE;YAC5F,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB;gBAC/B,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE;gBAClC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;QAC9H,iBAAiB,GAAG,WAAW,CAAC,MAAM,CAAA;QACtC,iBAAiB,GAAG,MAAM,uBAAuB,CAAC,kCAAkC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAA;QACtG,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB;YAClI,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,uBAAuB,KAAK,EAAE,EAAE,KAAK,EAAE,kBAAkB,KAAK,EAAE,EAAE,EAAE;YACnH,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,KAAK,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;QAC/H,iBAAiB,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;QACnE,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;YACtC,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB;gBAAE,OAAO,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAA;YAC9H,IAAI,IAAI,CAAC,OAAO,IAAK,IAAI,CAAC,OAAgC,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAChF,OAAO,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,EAAE,CAAA;YAC1H,CAAC;YACD,UAAU,GAAG,IAAI,CAAA;YACjB,SAAS,GAAG,MAAM,uBAAuB,CAAC,kCAAkC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAA;YAC9F,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;gBACrC,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;gBACtD,iBAAiB,CAAC,IAAI,CAAC,MAAM,uBAAuB,CAAC,kCAAkC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YAC3G,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAA;QAC1C,CAAC,CAAC,CAAA;QACF,SAAS,GAAG,GAAG,CAAC,MAAM,CAAA;QACtB,SAAS,GAAG,MAAM,uBAAuB,CAAC,kCAAkC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;QAEtF,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,KAAK,SAAS,CAAE,CAAA;QAChF,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAA;QACrD,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,KAAK,KAAK,CAAE,CAAA;QACxE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAA;QACnF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,CAAA;IACnD,CAAC;YAAS,CAAC;QACT,MAAM,SAAS,EAAE,EAAE,CAAA;QACnB,MAAM,SAAS,EAAE,EAAE,CAAA;QACnB,MAAM,iBAAiB,EAAE,EAAE,CAAA;QAC3B,MAAM,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;QAC9D,MAAM,WAAW,CAAC,SAAS,CAAC,CAAA;QAC5B,MAAM,WAAW,CAAC,SAAS,CAAC,CAAA;QAC5B,MAAM,WAAW,CAAC,iBAAiB,CAAC,CAAA;QACpC,MAAM,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAA;QACrD,qBAAqB,EAAE,CAAA;QACvB,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IAClD,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;IACnE,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,iCAAiC,CAAC,CAAC,CAAA;IACrF,MAAM,qBAAqB,GAAG,+BAA+B,CAAC,IAAI,CAAC,CAAA;IACnE,IAAI,YAA+C,CAAA;IACnD,IAAI,MAA+B,CAAA;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB;YACzE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,oBAAoB,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE;YAC/F,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;QACtH,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;QACtB,YAAY,GAAG,MAAM,uBAAuB,CAAC,kCAAkC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC5F,MAAM,KAAK,GAAG,MAAM,YAAY,EAAE,CAAA;QAClC,MAAM,MAAM,GAAG,MAAM,YAAY,EAAE,CAAA;QACnC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,KAAK,SAAS,CAAE,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,mBAAmB,EAAE,CAAC,CAAA;QAChH,MAAM,MAAM,CAAC,OAAO,CAClB,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,KAAK,YAAY,CAAE,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,mBAAmB,EAAE,CAAC,EAC9G,wBAAwB,CACzB,CAAA;IACH,CAAC;YAAS,CAAC;QACT,MAAM,YAAY,EAAE,EAAE,CAAA;QACtB,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;QACzB,qBAAqB,EAAE,CAAA;QACvB,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IAClD,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;IACjF,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,gCAAgC,CAAC,CAAC,CAAA;IACpF,MAAM,qBAAqB,GAAG,+BAA+B,CAAC,IAAI,CAAC,CAAA;IACnE,MAAM,QAAQ,GAA+B,EAAE,CAAA;IAC/C,MAAM,OAAO,GAAkB,EAAE,CAAA;IACjC,IAAI,CAAC;QACH,KAAK,MAAM,SAAS,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC;YACnD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB;gBACzE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,cAAc,EAAE,YAAY,SAAS,EAAE,EAAE,KAAK,EAAE,OAAO,SAAS,EAAE,EAAE,EAAE;gBAC7H,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;YACpH,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YAC3B,QAAQ,CAAC,IAAI,CAAC,MAAM,uBAAuB,CAAC,kCAAkC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QAC9F,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,YAAY,EAAE,CAAA;QAClC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,KAAK,SAAS,CAAE,CAAA;QAC1E,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,KAAK,YAAY,CAAE,CAAA;QAChF,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAA;QACrD,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAA;QACrD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,UAAU,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,CAAA;QACrG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,UAAU,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,CAAA;IACvG,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;QACrD,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAA;QAC3C,qBAAqB,EAAE,CAAA;QACvB,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IAClD,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;IAC1E,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,6BAA6B,CAAC,CAAC,CAAA;IACjF,MAAM,qBAAqB,GAAG,+BAA+B,CAAC,IAAI,CAAC,CAAA;IACnE,IAAI,YAA+C,CAAA;IACnD,IAAI,MAA+B,CAAA;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB;YACzE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;YACrF,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;QAC9G,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;QACtB,YAAY,GAAG,MAAM,uBAAuB,CAAC,kCAAkC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC5F,MAAM,SAAS,GAAG,yBAAyB,EAAE,CAAA;QAC7C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;QACzD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAA;QAChC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,EAAE,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;YAC3C,MAAM,SAAS,GAAG,IAAI,GAAG,KAAK,CAAA;YAC9B,MAAM,KAAK,GAAG,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAA;YACrD,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,SAAS,aAAa,KAAK,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC;gBAC1F,OAAO,EAAE,CAAC;gBACV,GAAG,EAAE,IAAI,GAAG,CAAC,sBAAsB,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI;gBACrD,KAAK;gBACL,GAAG,EAAE,UAAU;gBACf,SAAS;aACV,CAAC,CAAC,CAAA;QACL,CAAC;QACD,MAAM,OAAO,GAAG,CAAC,MAAM,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,KAAK,SAAS,CAAE,CAAA;QAC3F,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAA;QAChG,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IACpD,CAAC;YAAS,CAAC;QACT,MAAM,YAAY,EAAE,EAAE,CAAA;QACtB,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;QACzB,qBAAqB,EAAE,CAAA;QACvB,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IAClD,CAAC;AACH,CAAC,CAAC,CAAA","sourcesContent":["import assert from \"node:assert/strict\"\nimport { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from \"node:fs/promises\"\nimport http from \"node:http\"\nimport os from \"node:os\"\nimport path from \"node:path\"\nimport test from \"node:test\"\nimport {\n AUTOMATION_BRIDGE_PATH,\n automationBridgeDirectory,\n automationBridgeDirectories,\n createAutomationBridgeRegistration,\n parseDeveloperAction,\n publishAutomationBridge,\n removeLegacyAutomationPlugin,\n setupAutomationPlugin,\n} from \"./automation-plugin\"\n\ntype ToolDefinition = {\n name: string\n execute(input: unknown, context: { sessionID: string }): Promise<{ content: unknown }>\n}\n\nfunction listen(handler: (body: Record<string, unknown>) => Promise<Record<string, unknown>>) {\n return new Promise<{ server: http.Server; url: string }>((resolve) => {\n const server = http.createServer((request, response) => {\n const chunks: Buffer[] = []\n request.on(\"data\", (chunk) => chunks.push(Buffer.from(chunk)))\n request.on(\"end\", () => {\n void handler(JSON.parse(Buffer.concat(chunks).toString(\"utf8\"))).then((body) => {\n response.setHeader(\"content-type\", \"application/json\")\n response.end(JSON.stringify(body))\n })\n })\n })\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address() as { port: number }\n resolve({ server, url: `http://127.0.0.1:${address.port}` })\n })\n })\n}\n\nasync function collectTools(): Promise<ToolDefinition[]> {\n const definitions: ToolDefinition[] = []\n await setupAutomationPlugin({\n tool: { transform: async (callback) => callback({ add: (value) => definitions.push(value as ToolDefinition) }) },\n })\n return definitions\n}\n\nfunction closeServer(server: http.Server | undefined): Promise<void> {\n return new Promise((resolve) => server?.close(() => resolve()) ?? resolve())\n}\n\nfunction isolateAutomationBridgeRegistry(root: string): () => void {\n const previousLocalAppData = process.env.LOCALAPPDATA\n const previousXdgRuntimeDir = process.env.XDG_RUNTIME_DIR\n const previousWslDistroName = process.env.WSL_DISTRO_NAME\n process.env.LOCALAPPDATA = root\n process.env.XDG_RUNTIME_DIR = root\n delete process.env.WSL_DISTRO_NAME\n return () => {\n if (previousLocalAppData === undefined) delete process.env.LOCALAPPDATA\n else process.env.LOCALAPPDATA = previousLocalAppData\n if (previousXdgRuntimeDir === undefined) delete process.env.XDG_RUNTIME_DIR\n else process.env.XDG_RUNTIME_DIR = previousXdgRuntimeDir\n if (previousWslDistroName === undefined) delete process.env.WSL_DISTRO_NAME\n else process.env.WSL_DISTRO_NAME = previousWslDistroName\n }\n}\n\ntest(\"validates Developer Mode actions\", () => {\n assert.deepEqual(parseDeveloperAction({ action: \"type\", ref: \"e4\", text: \"CodeNomad\" }), {\n action: \"type\",\n ref: \"e4\",\n text: \"CodeNomad\",\n })\n assert.deepEqual(parseDeveloperAction({ action: \"restart\" }), { action: \"restart\" })\n assert.throws(() => parseDeveloperAction({ action: \"click\" }), /click requires ref/)\n})\n\ntest(\"registers developer tools while execution remains session-gated\", async () => {\n const tools: string[] = []\n await setupAutomationPlugin({\n tool: { transform: async (callback) => callback({ add: (value) => tools.push(value.name) }) },\n })\n\n assert.deepEqual(tools, [\"inspect\", \"act\", \"screenshot\"])\n})\n\ntest(\"discovers the Windows bridge registry from a WSL plugin\", () => {\n assert.deepEqual(automationBridgeDirectories({\n platform: \"linux\",\n env: { WSL_DISTRO_NAME: \"Ubuntu\", XDG_RUNTIME_DIR: \"/run/user/1000\" },\n home: \"/home/dev\",\n windowsLocalAppData: \"/mnt/c/Users/dev/AppData/Local\",\n }), [\n \"/run/user/1000/codenomad/automation-bridges\",\n \"/mnt/c/Users/dev/AppData/Local/CodeNomad/automation-bridges\",\n ])\n})\n\ntest(\"removes only the generated legacy global plugin shim\", async () => {\n const root = await mkdtemp(path.join(os.tmpdir(), \"codenomad-legacy-plugin-\"))\n const plugin = path.join(root, \"opencode\", \"plugins\", \"codenomad-automation.ts\")\n await mkdir(path.dirname(plugin), { recursive: true })\n try {\n await writeFile(plugin, 'export { default } from \"file:///C:/CodeNomad/resources/server/dist/opencode/automation-plugin.js\"\\n')\n assert.equal(await removeLegacyAutomationPlugin(root), true)\n await assert.rejects(readFile(plugin, \"utf8\"), { code: \"ENOENT\" })\n\n await writeFile(plugin, \"export default { id: 'user-plugin' }\\n\")\n assert.equal(await removeLegacyAutomationPlugin(root), false)\n assert.match(await readFile(plugin, \"utf8\"), /user-plugin/)\n } finally {\n await rm(root, { recursive: true, force: true })\n }\n})\n\ntest(\"restart waits for a new native generation and returns a fresh inspection\", async () => {\n const root = await mkdtemp(path.join(os.tmpdir(), \"codenomad-automation-restart-\"))\n const restoreBridgeRegistry = isolateAutomationBridgeRegistry(root)\n const definitions: ToolDefinition[] = []\n let removeOld: (() => Promise<void>) | undefined\n let removeNew: (() => Promise<void>) | undefined\n let removePreexisting: (() => Promise<void>) | undefined\n const removeDistractors: Array<() => Promise<void>> = []\n let oldServer: http.Server | undefined\n let newServer: http.Server | undefined\n let preexistingServer: http.Server | undefined\n const distractorServers: http.Server[] = []\n let restarting = false\n\n try {\n await setupAutomationPlugin({\n tool: { transform: async (callback) => callback({ add: (value) => definitions.push(value as typeof definitions[number]) }) },\n })\n const replacement = await listen(async (body) => body.mode === \"developer-probe\"\n ? { result: { available: true, nativeIdentity: \"electron:test\", runId: \"run-new\" } }\n : { result: { target: { id: \"page-new\", title: \"Restarted\", url: \"http://app.test\" }, nodes: [], diagnostics: [] } })\n newServer = replacement.server\n const preexisting = await listen(async (body) => body.mode === \"developer-probe\" && restarting\n ? { result: { available: true, nativeIdentity: \"electron:test\", runId: \"run-preexisting\" } }\n : body.mode === \"developer-probe\"\n ? { result: { available: false } }\n : { result: { target: { id: \"page-wrong\", title: \"Pre-existing\", url: \"http://app.test\" }, nodes: [], diagnostics: [] } })\n preexistingServer = preexisting.server\n removePreexisting = await publishAutomationBridge(createAutomationBridgeRegistration(preexisting.url))\n const distractors = await Promise.all(Array.from({ length: 5 }, (_, index) => listen(async (body) => body.mode === \"developer-probe\"\n ? { result: { available: true, nativeIdentity: `electron:distractor-${index}`, runId: `run-distractor-${index}` } }\n : { result: { target: { id: `page-${index}`, title: \"Distractor\", url: \"http://app.test\" }, nodes: [], diagnostics: [] } })))\n distractorServers.push(...distractors.map((value) => value.server))\n const old = await listen(async (body) => {\n if (body.mode === \"developer-probe\") return { result: { available: true, nativeIdentity: \"electron:test\", runId: \"run-old\" } }\n if (body.command && (body.command as { action?: unknown }).action === \"inspect\") {\n return { result: { target: { id: \"page-old\", title: \"Original\", url: \"http://app.test\" }, nodes: [], diagnostics: [] } }\n }\n restarting = true\n removeNew = await publishAutomationBridge(createAutomationBridgeRegistration(replacement.url))\n for (const distractor of distractors) {\n await new Promise((resolve) => setTimeout(resolve, 2))\n removeDistractors.push(await publishAutomationBridge(createAutomationBridgeRegistration(distractor.url)))\n }\n return { result: { state: \"starting\" } }\n })\n oldServer = old.server\n removeOld = await publishAutomationBridge(createAutomationBridgeRegistration(old.url))\n\n const inspect = definitions.find((definition) => definition.name === \"inspect\")!\n await inspect.execute({}, { sessionID: \"session-1\" })\n const act = definitions.find((definition) => definition.name === \"act\")!\n const result = await act.execute({ action: \"restart\" }, { sessionID: \"session-1\" })\n assert.match(String(result.content), /Restarted/)\n } finally {\n await removeOld?.()\n await removeNew?.()\n await removePreexisting?.()\n await Promise.all(removeDistractors.map((remove) => remove()))\n await closeServer(oldServer)\n await closeServer(newServer)\n await closeServer(preexistingServer)\n await Promise.all(distractorServers.map(closeServer))\n restoreBridgeRegistry()\n await rm(root, { recursive: true, force: true })\n }\n})\n\ntest(\"keeps inspected targets isolated per plugin setup\", async () => {\n const root = await mkdtemp(path.join(os.tmpdir(), \"codenomad-automation-isolation-\"))\n const restoreBridgeRegistry = isolateAutomationBridgeRegistry(root)\n let removeBridge: (() => Promise<void>) | undefined\n let server: http.Server | undefined\n try {\n const bridge = await listen(async (body) => body.mode === \"developer-probe\"\n ? { result: { available: true, nativeIdentity: \"electron:isolation\", runId: \"run-isolation\" } }\n : { result: { target: { id: \"isolated\", title: \"Isolated\", url: \"http://app.test\" }, nodes: [], diagnostics: [] } })\n server = bridge.server\n removeBridge = await publishAutomationBridge(createAutomationBridgeRegistration(bridge.url))\n const first = await collectTools()\n const second = await collectTools()\n await first.find((definition) => definition.name === \"inspect\")!.execute({}, { sessionID: \"session-isolation\" })\n await assert.rejects(\n second.find((definition) => definition.name === \"screenshot\")!.execute({}, { sessionID: \"session-isolation\" }),\n /Run codenomad\\.inspect/,\n )\n } finally {\n await removeBridge?.()\n await closeServer(server)\n restoreBridgeRegistry()\n await rm(root, { recursive: true, force: true })\n }\n})\n\ntest(\"pins parallel sessions to their independently inspected bridges\", async () => {\n const root = await mkdtemp(path.join(os.tmpdir(), \"codenomad-automation-sessions-\"))\n const restoreBridgeRegistry = isolateAutomationBridgeRegistry(root)\n const removals: Array<() => Promise<void>> = []\n const servers: http.Server[] = []\n try {\n for (const sessionID of [\"session-a\", \"session-b\"]) {\n const bridge = await listen(async (body) => body.mode === \"developer-probe\"\n ? { result: { available: body.sessionID === sessionID, nativeIdentity: `electron:${sessionID}`, runId: `run-${sessionID}` } }\n : { result: { target: { id: sessionID, title: sessionID, url: \"http://app.test\" }, nodes: [], diagnostics: [] } })\n servers.push(bridge.server)\n removals.push(await publishAutomationBridge(createAutomationBridgeRegistration(bridge.url)))\n }\n const tools = await collectTools()\n const inspect = tools.find((definition) => definition.name === \"inspect\")!\n const screenshot = tools.find((definition) => definition.name === \"screenshot\")!\n await inspect.execute({}, { sessionID: \"session-a\" })\n await inspect.execute({}, { sessionID: \"session-b\" })\n assert.match(String((await screenshot.execute({}, { sessionID: \"session-a\" })).content), /session-a/)\n assert.match(String((await screenshot.execute({}, { sessionID: \"session-b\" })).content), /session-b/)\n } finally {\n await Promise.all(removals.map((remove) => remove()))\n await Promise.all(servers.map(closeServer))\n restoreBridgeRegistry()\n await rm(root, { recursive: true, force: true })\n }\n})\n\ntest(\"prunes stale registry pressure before limiting discovery\", async () => {\n const root = await mkdtemp(path.join(os.tmpdir(), \"codenomad-automation-stale-\"))\n const restoreBridgeRegistry = isolateAutomationBridgeRegistry(root)\n let removeBridge: (() => Promise<void>) | undefined\n let server: http.Server | undefined\n try {\n const bridge = await listen(async (body) => body.mode === \"developer-probe\"\n ? { result: { available: true, nativeIdentity: \"electron:live\", runId: \"run-live\" } }\n : { result: { target: { id: \"live\", title: \"Live\", url: \"http://app.test\" }, nodes: [], diagnostics: [] } })\n server = bridge.server\n removeBridge = await publishAutomationBridge(createAutomationBridgeRegistration(bridge.url))\n const directory = automationBridgeDirectory()\n assert.equal(path.dirname(path.dirname(directory)), root)\n const base = Date.now() + 10_000\n for (let index = 0; index < 70; index += 1) {\n const startedAt = base + index\n const token = `dead${String(index).padStart(8, \"0\")}`\n await writeFile(path.join(directory, `${startedAt}-99999999-${token}.json`), JSON.stringify({\n version: 1,\n url: new URL(AUTOMATION_BRIDGE_PATH, bridge.url).href,\n token,\n pid: 99_999_999,\n startedAt,\n }))\n }\n const inspect = (await collectTools()).find((definition) => definition.name === \"inspect\")!\n assert.match(String((await inspect.execute({}, { sessionID: \"session-live\" })).content), /Live/)\n assert.equal((await readdir(directory)).length, 1)\n } finally {\n await removeBridge?.()\n await closeServer(server)\n restoreBridgeRegistry()\n await rm(root, { recursive: true, force: true })\n }\n})\n"]}
@@ -0,0 +1,15 @@
1
+ // Minimal structural excerpts from the published earlier V2 OpenAPI document.
2
+ // For test fixtures only; real-runtime tests also negotiate full native schemas.
3
+ export const legacyContractFixture = {
4
+ paths: {
5
+ "/api/session/{sessionID}/rename": { post: {} },
6
+ "/api/form/request": { get: {} },
7
+ "/api/session/{sessionID}/form/{formID}/cancel": { post: {} },
8
+ "/api/session/{sessionID}/wait": { post: {} },
9
+ "/api/session/{sessionID}/fork": { post: { requestBody: { content: { "application/json": { schema: { properties: { boundary: {} } } } } } } },
10
+ "/api/session/{sessionID}/command": { post: { requestBody: { content: { "application/json": { schema: { properties: { command: {} } } } } } } },
11
+ "/api/session/{sessionID}/permission/{requestID}/reply": { post: { requestBody: { content: { "application/json": { schema: { properties: { reply: {} } } } } } } },
12
+ },
13
+ components: { schemas: { "Session.Inbox.User": { properties: { timeCreated: { type: "number" } } } } },
14
+ };
15
+ //# sourceMappingURL=contract-fixture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-fixture.js","sourceRoot":"","sources":["../../../src/opencode/compatibility/contract-fixture.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,iFAAiF;AACjF,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,KAAK,EAAE;QACL,iCAAiC,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QAC/C,mBAAmB,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;QAChC,+CAA+C,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QAC7D,+BAA+B,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QAC7C,+BAA+B,EAAE,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;QAC7I,kCAAkC,EAAE,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;QAC/I,uDAAuD,EAAE,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;KACnK;IACD,UAAU,EAAE,EAAE,OAAO,EAAE,EAAE,oBAAoB,EAAE,EAAE,UAAU,EAAE,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE;CACvG,CAAA","sourcesContent":["// Minimal structural excerpts from the published earlier V2 OpenAPI document.\n// For test fixtures only; real-runtime tests also negotiate full native schemas.\nexport const legacyContractFixture = {\n paths: {\n \"/api/session/{sessionID}/rename\": { post: {} },\n \"/api/form/request\": { get: {} },\n \"/api/session/{sessionID}/form/{formID}/cancel\": { post: {} },\n \"/api/session/{sessionID}/wait\": { post: {} },\n \"/api/session/{sessionID}/fork\": { post: { requestBody: { content: { \"application/json\": { schema: { properties: { boundary: {} } } } } } } },\n \"/api/session/{sessionID}/command\": { post: { requestBody: { content: { \"application/json\": { schema: { properties: { command: {} } } } } } } },\n \"/api/session/{sessionID}/permission/{requestID}/reply\": { post: { requestBody: { content: { \"application/json\": { schema: { properties: { reply: {} } } } } } } },\n },\n components: { schemas: { \"Session.Inbox.User\": { properties: { timeCreated: { type: \"number\" } } } } },\n}\n"]}
@@ -0,0 +1,9 @@
1
+ export function normalizeRuntimeEvent(event) {
2
+ // Native metadata/durable identity is retained. Catalog/content changes stay
3
+ // invalidation intents; no synthetic upstream durable records are invented.
4
+ if (event.type === "session.permissions.updated") {
5
+ return { ...event, type: "session.permissions" };
6
+ }
7
+ return event;
8
+ }
9
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../../src/opencode/compatibility/events.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,qBAAqB,CAAC,KAAoB;IACxD,6EAA6E;IAC7E,4EAA4E;IAC5E,IAAK,KAAK,CAAC,IAAe,KAAK,6BAA6B,EAAE,CAAC;QAC7D,OAAO,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,qBAAqB,EAAmB,CAAA;IACnE,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC","sourcesContent":["import type { OpenCodeEvent } from \"@opencode/client\"\n\nexport function normalizeRuntimeEvent(event: OpenCodeEvent): OpenCodeEvent {\n // Native metadata/durable identity is retained. Catalog/content changes stay\n // invalidation intents; no synthetic upstream durable records are invented.\n if ((event.type as string) === \"session.permissions.updated\") {\n return { ...event, type: \"session.permissions\" } as OpenCodeEvent\n }\n return event\n}\n"]}
@@ -0,0 +1,89 @@
1
+ import { readLocationRef } from "../session-pruning/location";
2
+ export { readLocationRef, sameLocation } from "../session-pruning/location";
3
+ // An explicit CodeNomad context channel survives the generated modern client's
4
+ // field selection. Never forward this header to OpenCode unchanged. The guarded
5
+ // proxy must authorize its complete location before transport serialization.
6
+ export const LOCATION_CONTEXT_HEADER = "x-codenomad-location";
7
+ export function locationRequestOptions(location, options) {
8
+ const resolved = readLocationRef(location);
9
+ return resolved.workspaceID === undefined && !options?.includeDirectory ? undefined : {
10
+ headers: { [LOCATION_CONTEXT_HEADER]: encodeURIComponent(JSON.stringify(resolved)) },
11
+ };
12
+ }
13
+ export function readLocationContext(value, profile) {
14
+ if (value === undefined)
15
+ return undefined;
16
+ if (typeof value !== "string" || value.length > 16_384)
17
+ throw new Error("Invalid location context");
18
+ const input = JSON.parse(decodeURIComponent(value));
19
+ if (!input || typeof input !== "object" || Object.keys(input).some(key => key !== "directory" && key !== "workspaceID")) {
20
+ throw new Error("Invalid location context");
21
+ }
22
+ const location = readLocationRef(input);
23
+ if (location.workspaceID !== undefined && profile !== "legacy")
24
+ throw new Error("Unsupported workspace selector");
25
+ return location;
26
+ }
27
+ export async function moveSessionToLocation(client, sessionID, location) {
28
+ await client.session.move({ sessionID, directory: location.directory }, locationRequestOptions(location));
29
+ }
30
+ // The selected transport calls this AFTER authorization. Only the explicitly
31
+ // supported wire slots are adapted; this is not a general header-to-body merge.
32
+ export function applyLocationContext(url, method, body, headers, profile) {
33
+ const location = readLocationContext(headers.get(LOCATION_CONTEXT_HEADER) ?? undefined, profile);
34
+ headers.delete(LOCATION_CONTEXT_HEADER);
35
+ if (!location)
36
+ return body;
37
+ const input = body && typeof body === "object" && !Array.isArray(body) ? body : undefined;
38
+ const assertDirectory = (directory) => {
39
+ if (directory !== location.directory)
40
+ throw new Error("Location context does not match request");
41
+ };
42
+ if (method === "POST" && /^\/api\/session\/[^/]+\/move\/?$/.test(url.pathname)) {
43
+ assertDirectory(input?.directory);
44
+ if (input?.workspaceID !== undefined && input.workspaceID !== location.workspaceID)
45
+ throw new Error("Conflicting workspace selector");
46
+ return { ...input, workspaceID: location.workspaceID };
47
+ }
48
+ if (method === "POST" && /^\/api\/(?:experimental\/)?session(?:\/import)?\/?$/.test(url.pathname)) {
49
+ const destination = readLocationRef(input?.location);
50
+ assertDirectory(destination.directory);
51
+ if (destination.workspaceID !== undefined && destination.workspaceID !== location.workspaceID)
52
+ throw new Error("Conflicting workspace selector");
53
+ return { ...input, location: { ...destination, workspaceID: location.workspaceID } };
54
+ }
55
+ if (/^\/api\/session\/global\/form(?:\/[^/]+(?:\/(?:reply|cancel|state))?)?\/?$/.test(url.pathname)) {
56
+ assertDirectory(decodeURIComponent(headers.get("x-opencode-directory") ?? ""));
57
+ if (location.workspaceID !== undefined)
58
+ headers.set("x-opencode-workspace", location.workspaceID);
59
+ return body;
60
+ }
61
+ if (method === "GET" && /^\/api\/session\/?$/.test(url.pathname)) {
62
+ // The cursor is the complete native query, not a location selector to amend.
63
+ if (url.searchParams.has("cursor"))
64
+ throw new Error("Cursor already carries session list scope");
65
+ assertDirectory(url.searchParams.get("directory"));
66
+ if (url.searchParams.has("workspace"))
67
+ throw new Error("Conflicting workspace selector");
68
+ if (location.workspaceID !== undefined)
69
+ url.searchParams.set("workspace", location.workspaceID);
70
+ return body;
71
+ }
72
+ if (/^\/api\/credential\/[^/]+(?:\/activate)?\/?$/.test(url.pathname)) {
73
+ // Modern credential mutations are global; legacy invalidation is scoped.
74
+ // The generated modern client has no location query to preserve for us.
75
+ if (profile !== "legacy")
76
+ return body;
77
+ if (url.searchParams.has("location[directory]"))
78
+ assertDirectory(url.searchParams.get("location[directory]"));
79
+ url.searchParams.set("location[directory]", location.directory);
80
+ }
81
+ const directory = url.searchParams.get("location[directory]");
82
+ assertDirectory(directory);
83
+ if (url.searchParams.has("location[workspace]"))
84
+ throw new Error("Conflicting workspace selector");
85
+ if (location.workspaceID !== undefined)
86
+ url.searchParams.set("location[workspace]", location.workspaceID);
87
+ return body;
88
+ }
89
+ //# sourceMappingURL=location.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"location.js","sourceRoot":"","sources":["../../../src/opencode/compatibility/location.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LocationRef, OpenCodeClient } from \"@opencode/client\"\nimport type { ContractProfile } from \"./runtime\"\nimport { readLocationRef } from \"../session-pruning/location\"\nexport { readLocationRef, sameLocation } from \"../session-pruning/location\"\n\n// An explicit CodeNomad context channel survives the generated modern client's\n// field selection. Never forward this header to OpenCode unchanged. The guarded\n// proxy must authorize its complete location before transport serialization.\nexport const LOCATION_CONTEXT_HEADER = \"x-codenomad-location\"\n\nexport function locationRequestOptions(location: LocationRef, options?: { includeDirectory?: boolean }): { headers: Record<string, string> } | undefined {\n const resolved = readLocationRef(location)\n return resolved.workspaceID === undefined && !options?.includeDirectory ? undefined : {\n headers: { [LOCATION_CONTEXT_HEADER]: encodeURIComponent(JSON.stringify(resolved)) },\n }\n}\n\nexport function readLocationContext(value: string | string[] | undefined, profile: ContractProfile): LocationRef | undefined {\n if (value === undefined) return undefined\n if (typeof value !== \"string\" || value.length > 16_384) throw new Error(\"Invalid location context\")\n const input: unknown = JSON.parse(decodeURIComponent(value))\n if (!input || typeof input !== \"object\" || Object.keys(input).some(key => key !== \"directory\" && key !== \"workspaceID\")) {\n throw new Error(\"Invalid location context\")\n }\n const location = readLocationRef(input)\n if (location.workspaceID !== undefined && profile !== \"legacy\") throw new Error(\"Unsupported workspace selector\")\n return location\n}\n\nexport async function moveSessionToLocation(client: OpenCodeClient, sessionID: string, location: LocationRef): Promise<void> {\n await client.session.move({ sessionID, directory: location.directory }, locationRequestOptions(location))\n}\n\n// The selected transport calls this AFTER authorization. Only the explicitly\n// supported wire slots are adapted; this is not a general header-to-body merge.\nexport function applyLocationContext(url: URL, method: string, body: unknown, headers: Headers, profile: ContractProfile): unknown {\n const location = readLocationContext(headers.get(LOCATION_CONTEXT_HEADER) ?? undefined, profile)\n headers.delete(LOCATION_CONTEXT_HEADER)\n if (!location) return body\n const input = body && typeof body === \"object\" && !Array.isArray(body) ? body as Record<string, unknown> : undefined\n const assertDirectory = (directory: unknown) => {\n if (directory !== location.directory) throw new Error(\"Location context does not match request\")\n }\n if (method === \"POST\" && /^\\/api\\/session\\/[^/]+\\/move\\/?$/.test(url.pathname)) {\n assertDirectory(input?.directory)\n if (input?.workspaceID !== undefined && input.workspaceID !== location.workspaceID) throw new Error(\"Conflicting workspace selector\")\n return { ...input, workspaceID: location.workspaceID }\n }\n if (method === \"POST\" && /^\\/api\\/(?:experimental\\/)?session(?:\\/import)?\\/?$/.test(url.pathname)) {\n const destination = readLocationRef(input?.location)\n assertDirectory(destination.directory)\n if (destination.workspaceID !== undefined && destination.workspaceID !== location.workspaceID) throw new Error(\"Conflicting workspace selector\")\n return { ...input, location: { ...destination, workspaceID: location.workspaceID } }\n }\n if (/^\\/api\\/session\\/global\\/form(?:\\/[^/]+(?:\\/(?:reply|cancel|state))?)?\\/?$/.test(url.pathname)) {\n assertDirectory(decodeURIComponent(headers.get(\"x-opencode-directory\") ?? \"\"))\n if (location.workspaceID !== undefined) headers.set(\"x-opencode-workspace\", location.workspaceID)\n return body\n }\n if (method === \"GET\" && /^\\/api\\/session\\/?$/.test(url.pathname)) {\n // The cursor is the complete native query, not a location selector to amend.\n if (url.searchParams.has(\"cursor\")) throw new Error(\"Cursor already carries session list scope\")\n assertDirectory(url.searchParams.get(\"directory\"))\n if (url.searchParams.has(\"workspace\")) throw new Error(\"Conflicting workspace selector\")\n if (location.workspaceID !== undefined) url.searchParams.set(\"workspace\", location.workspaceID)\n return body\n }\n if (/^\\/api\\/credential\\/[^/]+(?:\\/activate)?\\/?$/.test(url.pathname)) {\n // Modern credential mutations are global; legacy invalidation is scoped.\n // The generated modern client has no location query to preserve for us.\n if (profile !== \"legacy\") return body\n if (url.searchParams.has(\"location[directory]\")) assertDirectory(url.searchParams.get(\"location[directory]\"))\n url.searchParams.set(\"location[directory]\", location.directory)\n }\n const directory = url.searchParams.get(\"location[directory]\")\n assertDirectory(directory)\n if (url.searchParams.has(\"location[workspace]\")) throw new Error(\"Conflicting workspace selector\")\n if (location.workspaceID !== undefined) url.searchParams.set(\"location[workspace]\", location.workspaceID)\n return body\n}\n"]}