@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.
- package/README.md +2 -2
- package/dist/api-types.js.map +1 -1
- package/dist/opencode/automation-plugin.test.js +32 -38
- package/dist/opencode/automation-plugin.test.js.map +1 -1
- package/dist/opencode/compatibility/contract-fixture.js +15 -0
- package/dist/opencode/compatibility/contract-fixture.js.map +1 -0
- package/dist/opencode/compatibility/events.js +9 -0
- package/dist/opencode/compatibility/events.js.map +1 -0
- package/dist/opencode/compatibility/location.js +89 -0
- package/dist/opencode/compatibility/location.js.map +1 -0
- package/dist/opencode/compatibility/location.test.js +92 -0
- package/dist/opencode/compatibility/location.test.js.map +1 -0
- package/dist/opencode/compatibility/negotiate.js +73 -0
- package/dist/opencode/compatibility/negotiate.js.map +1 -0
- package/dist/opencode/compatibility/negotiate.test.js +61 -0
- package/dist/opencode/compatibility/negotiate.test.js.map +1 -0
- package/dist/opencode/compatibility/proxy-locations.js +137 -0
- package/dist/opencode/compatibility/proxy-locations.js.map +1 -0
- package/dist/opencode/compatibility/proxy-locations.test.js +42 -0
- package/dist/opencode/compatibility/proxy-locations.test.js.map +1 -0
- package/dist/opencode/compatibility/proxy.js +60 -0
- package/dist/opencode/compatibility/proxy.js.map +1 -0
- package/dist/opencode/compatibility/proxy.test.js +47 -0
- package/dist/opencode/compatibility/proxy.test.js.map +1 -0
- package/dist/opencode/compatibility/requests.js +54 -0
- package/dist/opencode/compatibility/requests.js.map +1 -0
- package/dist/opencode/compatibility/runtime.js +26 -0
- package/dist/opencode/compatibility/runtime.js.map +1 -0
- package/dist/opencode/compatibility/transport.js +112 -0
- package/dist/opencode/compatibility/transport.js.map +1 -0
- package/dist/opencode/compatibility/transport.test.js +83 -0
- package/dist/opencode/compatibility/transport.test.js.map +1 -0
- package/dist/opencode/session-pruning/location.js +16 -0
- package/dist/opencode/session-pruning/location.js.map +1 -0
- package/dist/opencode/session-pruning/package.test.js +37 -0
- package/dist/opencode/session-pruning/package.test.js.map +1 -0
- package/dist/opencode/session-pruning/plugin.js +5 -2
- package/dist/opencode/session-pruning/plugin.js.map +1 -1
- package/dist/opencode/session-pruning/preview-store.js +3 -2
- package/dist/opencode/session-pruning/preview-store.js.map +1 -1
- package/dist/opencode/session-pruning/pruning.test.js +7 -5
- package/dist/opencode/session-pruning/pruning.test.js.map +1 -1
- package/dist/opencode/session-pruning/service.js +5 -4
- package/dist/opencode/session-pruning/service.js.map +1 -1
- package/dist/opencode/session-pruning/service.test.js +65 -53
- package/dist/opencode/session-pruning/service.test.js.map +1 -1
- package/dist/permissions/opencode-replier.js +2 -2
- package/dist/permissions/opencode-replier.js.map +1 -1
- package/dist/permissions/opencode-replier.test.js +1 -1
- package/dist/permissions/opencode-replier.test.js.map +1 -1
- package/dist/permissions/opencode-yolo-metadata.js.map +1 -1
- package/dist/permissions/opencode-yolo-metadata.test.js.map +1 -1
- package/dist/plugins/session-pruning/plugin.mjs +422 -271
- package/dist/server/__tests__/instance-proxy-legacy.test.js +111 -0
- package/dist/server/__tests__/instance-proxy-legacy.test.js.map +1 -0
- package/dist/server/__tests__/instance-proxy.test.js +95 -34
- package/dist/server/__tests__/instance-proxy.test.js.map +1 -1
- package/dist/server/http-server.js +147 -237
- package/dist/server/http-server.js.map +1 -1
- package/dist/server/routes/session-pruning.js +4 -2
- package/dist/server/routes/session-pruning.js.map +1 -1
- package/dist/server/routes/session-pruning.test.js +16 -2
- package/dist/server/routes/session-pruning.test.js.map +1 -1
- package/dist/server/routes/worktrees.js +1 -0
- package/dist/server/routes/worktrees.js.map +1 -1
- package/dist/server/routes/worktrees.test.js +54 -1
- package/dist/server/routes/worktrees.test.js.map +1 -1
- package/dist/workspaces/host-opencode-service.test.js +56 -7
- package/dist/workspaces/host-opencode-service.test.js.map +1 -1
- package/dist/workspaces/instance-client.js.map +1 -1
- package/dist/workspaces/instance-events.js +71 -57
- package/dist/workspaces/instance-events.js.map +1 -1
- package/dist/workspaces/instance-events.test.js +122 -9
- package/dist/workspaces/instance-events.test.js.map +1 -1
- package/dist/workspaces/manager.js +24 -39
- package/dist/workspaces/manager.js.map +1 -1
- package/dist/workspaces/manager.test.js +43 -7
- package/dist/workspaces/manager.test.js.map +1 -1
- package/dist/workspaces/opencode-cli-service.js +56 -29
- package/dist/workspaces/opencode-cli-service.js.map +1 -1
- package/dist/workspaces/opencode-connection-generation.test.js +57 -0
- package/dist/workspaces/opencode-connection-generation.test.js.map +1 -0
- package/dist/workspaces/opencode-service.js +70 -16
- package/dist/workspaces/opencode-service.js.map +1 -1
- package/dist/workspaces/opencode-service.test.js +3 -4
- package/dist/workspaces/opencode-service.test.js.map +1 -1
- package/dist/workspaces/worktree-session-evacuation.js +31 -11
- package/dist/workspaces/worktree-session-evacuation.js.map +1 -1
- package/dist/workspaces/worktree-session-evacuation.test.js +211 -0
- package/dist/workspaces/worktree-session-evacuation.test.js.map +1 -1
- package/dist/workspaces/wsl-opencode-service.test.js +64 -24
- package/dist/workspaces/wsl-opencode-service.test.js.map +1 -1
- package/package.json +3 -3
- package/public/assets/App-BROhwPo9.js +54 -0
- package/public/assets/App-BROhwPo9.js.map +1 -0
- package/public/assets/{FilesTab-CRpGDs1B.js → FilesTab-DY3Vr-3r.js} +3 -3
- package/public/assets/{FilesTab-CRpGDs1B.js.map → FilesTab-DY3Vr-3r.js.map} +1 -1
- package/public/assets/{GitChangesTab-DxFebTiq.js → GitChangesTab-BD6CJngE.js} +3 -3
- package/public/assets/{GitChangesTab-DxFebTiq.js.map → GitChangesTab-BD6CJngE.js.map} +1 -1
- package/public/assets/{SplitFilePanel-DgTMrrO-.js → SplitFilePanel-BdY6d_6m.js} +2 -2
- package/public/assets/{SplitFilePanel-DgTMrrO-.js.map → SplitFilePanel-BdY6d_6m.js.map} +1 -1
- package/public/assets/StatusTab-C-_2yaPa.js +2 -0
- package/public/assets/StatusTab-C-_2yaPa.js.map +1 -0
- package/public/assets/{align-justify-v_yPXSys.js → align-justify-DI8VTZ4G.js} +2 -2
- package/public/assets/{align-justify-v_yPXSys.js.map → align-justify-DI8VTZ4G.js.map} +1 -1
- package/public/assets/{bundle-full-HzL_Jtqk.js → bundle-full-D_M65Eb_.js} +2 -2
- package/public/assets/{bundle-full-HzL_Jtqk.js.map → bundle-full-D_M65Eb_.js.map} +1 -1
- package/public/assets/{client-state-CF-PJ0GG.js → client-state-CWCzCOz6.js} +2 -2
- package/public/assets/{client-state-CF-PJ0GG.js.map → client-state-CWCzCOz6.js.map} +1 -1
- package/public/assets/{diff-viewer-BFDQBDVy.js → diff-viewer-CGPsYEr3.js} +2 -2
- package/public/assets/{diff-viewer-BFDQBDVy.js.map → diff-viewer-CGPsYEr3.js.map} +1 -1
- package/public/assets/index-B8-lAqjr.js +2 -0
- package/public/assets/index-B8-lAqjr.js.map +1 -0
- package/public/assets/index-BeUewQym.js +2 -0
- package/public/assets/index-BeUewQym.js.map +1 -0
- package/public/assets/index-BxRFHpJd.js +2 -0
- package/public/assets/{index-D2UHSOG5.js.map → index-BxRFHpJd.js.map} +1 -1
- package/public/assets/index-Bzv1kU-S.js +2 -0
- package/public/assets/{index-CTKYD_VJ.js.map → index-Bzv1kU-S.js.map} +1 -1
- package/public/assets/index-C9tI4CRG.js +2 -0
- package/public/assets/index-C9tI4CRG.js.map +1 -0
- package/public/assets/index-CGYHFZ6F.js +2 -0
- package/public/assets/index-CGYHFZ6F.js.map +1 -0
- package/public/assets/{index-CHYmKl10.js → index-CMEAjBFp.js} +2 -2
- package/public/assets/index-CMEAjBFp.js.map +1 -0
- package/public/assets/index-Cazypvyc.js +2 -0
- package/public/assets/index-Cazypvyc.js.map +1 -0
- package/public/assets/index-CwqzofWP.js +2 -0
- package/public/assets/index-CwqzofWP.js.map +1 -0
- package/public/assets/index-Da9yOa6q.js +2 -0
- package/public/assets/index-Da9yOa6q.js.map +1 -0
- package/public/assets/{index-RK9gf0uk.js → index-Ddjkirek.js} +2 -2
- package/public/assets/{index-RK9gf0uk.js.map → index-Ddjkirek.js.map} +1 -1
- package/public/assets/index-HHgvEOAH.js +3 -0
- package/public/assets/index-HHgvEOAH.js.map +1 -0
- package/public/assets/index-MeFffHqb.css +1 -0
- package/public/assets/loading-CdhHiSED.css +1 -0
- package/public/assets/{loading-BSB82snY.js → loading-Gqh6NPUr.js} +2 -2
- package/public/assets/{loading-BSB82snY.js.map → loading-Gqh6NPUr.js.map} +1 -1
- package/public/assets/main-Cbx7KdTr.js +38 -0
- package/public/assets/main-Cbx7KdTr.js.map +1 -0
- package/public/assets/{markdown-DtidoL2Z.js → markdown-BWnjyvnX.js} +4 -4
- package/public/assets/{markdown-DtidoL2Z.js.map → markdown-BWnjyvnX.js.map} +1 -1
- package/public/assets/monaco-viewer-BEJIiZWL.js +27 -0
- package/public/assets/monaco-viewer-BEJIiZWL.js.map +1 -0
- package/public/assets/{startup-state-settings-card-BY741Rt0.js → startup-state-settings-card-C4E2Itop.js} +2 -2
- package/public/assets/{startup-state-settings-card-BY741Rt0.js.map → startup-state-settings-card-C4E2Itop.js.map} +1 -1
- package/public/assets/{terminal-square-BdC_jwm3.js → terminal-square-C3wutdV0.js} +2 -2
- package/public/assets/{terminal-square-BdC_jwm3.js.map → terminal-square-C3wutdV0.js.map} +1 -1
- package/public/assets/tool-call-DNsAQ_m4.js +60 -0
- package/public/assets/tool-call-DNsAQ_m4.js.map +1 -0
- package/public/assets/{unified-picker-C0eo1chJ.js → unified-picker-tB7y17Qz.js} +2 -2
- package/public/assets/unified-picker-tB7y17Qz.js.map +1 -0
- package/public/assets/{wrap-text-BuMtdvMx.js → wrap-text-CkBxzt04.js} +2 -2
- package/public/assets/{wrap-text-BuMtdvMx.js.map → wrap-text-CkBxzt04.js.map} +1 -1
- package/public/index.html +4 -4
- package/public/loading.html +5 -5
- package/public/sw.js +1 -1
- package/public/sw.js.map +1 -1
- package/public/assets/App-0-FcHzPS.js +0 -44
- package/public/assets/App-0-FcHzPS.js.map +0 -1
- package/public/assets/StatusTab-LKUA9cbH.js +0 -2
- package/public/assets/StatusTab-LKUA9cbH.js.map +0 -1
- package/public/assets/index-8iPlm2r0.js +0 -2
- package/public/assets/index-8iPlm2r0.js.map +0 -1
- package/public/assets/index-B185mC7m.js +0 -2
- package/public/assets/index-B185mC7m.js.map +0 -1
- package/public/assets/index-Bbj1xF8i.js +0 -2
- package/public/assets/index-Bbj1xF8i.js.map +0 -1
- package/public/assets/index-BsmFN3k1.js +0 -2
- package/public/assets/index-BsmFN3k1.js.map +0 -1
- package/public/assets/index-Bz_OQJYA.css +0 -1
- package/public/assets/index-C52CfqK3.js +0 -2
- package/public/assets/index-C52CfqK3.js.map +0 -1
- package/public/assets/index-CHYmKl10.js.map +0 -1
- package/public/assets/index-CTKYD_VJ.js +0 -2
- package/public/assets/index-D2UHSOG5.js +0 -2
- package/public/assets/index-DDoB_Z36.js +0 -2
- package/public/assets/index-DDoB_Z36.js.map +0 -1
- package/public/assets/index-DcwAtZw-.js +0 -3
- package/public/assets/index-DcwAtZw-.js.map +0 -1
- package/public/assets/index-DxUD3RYr.js +0 -2
- package/public/assets/index-DxUD3RYr.js.map +0 -1
- package/public/assets/loading-CmEVQgyj.css +0 -1
- package/public/assets/main-CZztwrfv.js +0 -48
- package/public/assets/main-CZztwrfv.js.map +0 -1
- package/public/assets/monaco-viewer-CpxDfB-6.js +0 -27
- package/public/assets/monaco-viewer-CpxDfB-6.js.map +0 -1
- package/public/assets/tool-call-DbtCsJ4x.js +0 -60
- package/public/assets/tool-call-DbtCsJ4x.js.map +0 -1
- 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
|
|
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
|
|
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
|
|
package/dist/api-types.js.map
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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":"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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 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"]}
|