@neuralnomads/codenomad-dev 0.20.0-dev-20260905-52f0e629 → 0.20.0-dev-20260907-e9e94a67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/api-types.js +1 -0
  2. package/dist/api-types.js.map +1 -1
  3. package/dist/opencode/automation-plugin.test.js +14 -1
  4. package/dist/opencode/automation-plugin.test.js.map +1 -1
  5. package/dist/server/routes/binary-validation.test.js +41 -0
  6. package/dist/server/routes/binary-validation.test.js.map +1 -0
  7. package/dist/server/routes/settings.js +2 -3
  8. package/dist/server/routes/settings.js.map +1 -1
  9. package/dist/workspaces/__tests__/binary-probe-fixture.js +46 -0
  10. package/dist/workspaces/__tests__/binary-probe-fixture.js.map +1 -0
  11. package/dist/workspaces/__tests__/binary-probe.test.js +81 -0
  12. package/dist/workspaces/__tests__/binary-probe.test.js.map +1 -0
  13. package/dist/workspaces/__tests__/spawn.test.js +39 -1
  14. package/dist/workspaces/__tests__/spawn.test.js.map +1 -1
  15. package/dist/workspaces/host-opencode-service.test.js +25 -0
  16. package/dist/workspaces/host-opencode-service.test.js.map +1 -1
  17. package/dist/workspaces/opencode-cli-compatibility.js +32 -0
  18. package/dist/workspaces/opencode-cli-compatibility.js.map +1 -0
  19. package/dist/workspaces/opencode-cli-compatibility.test.js +35 -0
  20. package/dist/workspaces/opencode-cli-compatibility.test.js.map +1 -0
  21. package/dist/workspaces/opencode-cli-service.js +14 -2
  22. package/dist/workspaces/opencode-cli-service.js.map +1 -1
  23. package/dist/workspaces/spawn.js +94 -31
  24. package/dist/workspaces/spawn.js.map +1 -1
  25. package/dist/workspaces/wsl-opencode-service.test.js +18 -0
  26. package/dist/workspaces/wsl-opencode-service.test.js.map +1 -1
  27. package/package.json +1 -1
  28. package/public/assets/{App-CqjB0XCD.js → App-lAZZOE--.js} +8 -8
  29. package/public/assets/App-lAZZOE--.js.map +1 -0
  30. package/public/assets/{FilesTab-5DFefbXn.js → FilesTab-CgfwF6tR.js} +2 -2
  31. package/public/assets/{FilesTab-5DFefbXn.js.map → FilesTab-CgfwF6tR.js.map} +1 -1
  32. package/public/assets/{GitChangesTab-DpT-MHU0.js → GitChangesTab-r1JoAD9n.js} +2 -2
  33. package/public/assets/{GitChangesTab-DpT-MHU0.js.map → GitChangesTab-r1JoAD9n.js.map} +1 -1
  34. package/public/assets/{SplitFilePanel-BbE1LzCe.js → SplitFilePanel-D9FfoZUZ.js} +2 -2
  35. package/public/assets/{SplitFilePanel-BbE1LzCe.js.map → SplitFilePanel-D9FfoZUZ.js.map} +1 -1
  36. package/public/assets/{StatusTab-BFoX-V9X.js → StatusTab-DMnWj5WS.js} +2 -2
  37. package/public/assets/{StatusTab-BFoX-V9X.js.map → StatusTab-DMnWj5WS.js.map} +1 -1
  38. package/public/assets/{align-justify-qrXM4H3l.js → align-justify-Dc1gjAkp.js} +2 -2
  39. package/public/assets/{align-justify-qrXM4H3l.js.map → align-justify-Dc1gjAkp.js.map} +1 -1
  40. package/public/assets/{bundle-full-unk5xR1Y.js → bundle-full-CXK0RpEe.js} +2 -2
  41. package/public/assets/{bundle-full-unk5xR1Y.js.map → bundle-full-CXK0RpEe.js.map} +1 -1
  42. package/public/assets/{client-state-CHCvxsCU.js → client-state-DmImbmIC.js} +2 -2
  43. package/public/assets/{client-state-CHCvxsCU.js.map → client-state-DmImbmIC.js.map} +1 -1
  44. package/public/assets/{diff-viewer-RKOl9AXZ.js → diff-viewer-CbJWj99J.js} +2 -2
  45. package/public/assets/{diff-viewer-RKOl9AXZ.js.map → diff-viewer-CbJWj99J.js.map} +1 -1
  46. package/public/assets/index-AwBa5De4.js +2 -0
  47. package/public/assets/index-AwBa5De4.js.map +1 -0
  48. package/public/assets/index-BJSdILt0.js +2 -0
  49. package/public/assets/index-BJSdILt0.js.map +1 -0
  50. package/public/assets/index-BfORYhCR.js +3 -0
  51. package/public/assets/index-BfORYhCR.js.map +1 -0
  52. package/public/assets/{index-CEAIa6p2.js → index-Bv4cVEdt.js} +2 -2
  53. package/public/assets/{index-CEAIa6p2.js.map → index-Bv4cVEdt.js.map} +1 -1
  54. package/public/assets/{index-Cld6pmhN.js → index-C1AVy0gw.js} +2 -2
  55. package/public/assets/{index-Cld6pmhN.js.map → index-C1AVy0gw.js.map} +1 -1
  56. package/public/assets/index-C8aIk3bb.js +2 -0
  57. package/public/assets/index-C8aIk3bb.js.map +1 -0
  58. package/public/assets/index-CF7UTwOC.js +2 -0
  59. package/public/assets/index-CF7UTwOC.js.map +1 -0
  60. package/public/assets/index-D5cfewGV.js +2 -0
  61. package/public/assets/index-D5cfewGV.js.map +1 -0
  62. package/public/assets/index-Dggdc5Fz.js +2 -0
  63. package/public/assets/index-Dggdc5Fz.js.map +1 -0
  64. package/public/assets/index-UtSy15g1.js +2 -0
  65. package/public/assets/index-UtSy15g1.js.map +1 -0
  66. package/public/assets/index-kaTj6a0V.js +2 -0
  67. package/public/assets/index-kaTj6a0V.js.map +1 -0
  68. package/public/assets/{index-DtTSGKVJ.js → index-xkTcWbMt.js} +2 -2
  69. package/public/assets/index-xkTcWbMt.js.map +1 -0
  70. package/public/assets/{loading-D3uPMfMu.js → loading-CLjF2dnt.js} +2 -2
  71. package/public/assets/{loading-D3uPMfMu.js.map → loading-CLjF2dnt.js.map} +1 -1
  72. package/public/assets/{main-BwbZb3g5.js → main-CA9pw_Mr.js} +24 -24
  73. package/public/assets/main-CA9pw_Mr.js.map +1 -0
  74. package/public/assets/{markdown-BJLcfdun.js → markdown-DP0osupz.js} +4 -4
  75. package/public/assets/{markdown-BJLcfdun.js.map → markdown-DP0osupz.js.map} +1 -1
  76. package/public/assets/{startup-state-settings-card-B4gPI-c4.js → startup-state-settings-card-Bc27wplD.js} +2 -2
  77. package/public/assets/{startup-state-settings-card-B4gPI-c4.js.map → startup-state-settings-card-Bc27wplD.js.map} +1 -1
  78. package/public/assets/{terminal-square-B5KdW_c-.js → terminal-square-4t6I40X6.js} +2 -2
  79. package/public/assets/{terminal-square-B5KdW_c-.js.map → terminal-square-4t6I40X6.js.map} +1 -1
  80. package/public/assets/{tool-call-B9DCPGVo.js → tool-call-BIHyGWIy.js} +4 -4
  81. package/public/assets/{tool-call-B9DCPGVo.js.map → tool-call-BIHyGWIy.js.map} +1 -1
  82. package/public/assets/{unified-picker-BvxdLAJQ.js → unified-picker-CpvYPG8V.js} +2 -2
  83. package/public/assets/{unified-picker-BvxdLAJQ.js.map → unified-picker-CpvYPG8V.js.map} +1 -1
  84. package/public/assets/{wrap-text-NfrmWiEu.js → wrap-text-Dq8UDb8D.js} +2 -2
  85. package/public/assets/{wrap-text-NfrmWiEu.js.map → wrap-text-Dq8UDb8D.js.map} +1 -1
  86. package/public/index.html +2 -2
  87. package/public/loading.html +2 -2
  88. package/public/sw.js +1 -1
  89. package/public/sw.js.map +1 -1
  90. package/public/assets/App-CqjB0XCD.js.map +0 -1
  91. package/public/assets/index-BOrPHA7t.js +0 -2
  92. package/public/assets/index-BOrPHA7t.js.map +0 -1
  93. package/public/assets/index-BaPs8N0o.js +0 -2
  94. package/public/assets/index-BaPs8N0o.js.map +0 -1
  95. package/public/assets/index-BrjHffsb.js +0 -2
  96. package/public/assets/index-BrjHffsb.js.map +0 -1
  97. package/public/assets/index-C-CjhNjl.js +0 -2
  98. package/public/assets/index-C-CjhNjl.js.map +0 -1
  99. package/public/assets/index-CvrBisww.js +0 -2
  100. package/public/assets/index-CvrBisww.js.map +0 -1
  101. package/public/assets/index-DDpJlsfl.js +0 -3
  102. package/public/assets/index-DDpJlsfl.js.map +0 -1
  103. package/public/assets/index-DViQvmpk.js +0 -2
  104. package/public/assets/index-DViQvmpk.js.map +0 -1
  105. package/public/assets/index-DtTSGKVJ.js.map +0 -1
  106. package/public/assets/index-DvRzkWiJ.js +0 -2
  107. package/public/assets/index-DvRzkWiJ.js.map +0 -1
  108. package/public/assets/index-HuQOubUd.js +0 -2
  109. package/public/assets/index-HuQOubUd.js.map +0 -1
  110. package/public/assets/main-BwbZb3g5.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"spawn.test.js","sourceRoot":"","sources":["../../../src/workspaces/__tests__/spawn.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\"\nimport { spawnSync } from \"node:child_process\"\nimport { mkdirSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\"\nimport { tmpdir } from \"node:os\"\nimport path from \"node:path\"\nimport { describe, it } from \"node:test\"\n\nimport {\n buildServiceLaunchSpec,\n buildWindowsSpawnSpec,\n parseWslUncPath,\n resolveWslHostDirectory,\n resolveWslServiceDirectory,\n resolveWslWorkingDirectory,\n} from \"../spawn\"\n\ndescribe(\"parseWslUncPath\", () => {\n it(\"parses WSL UNC paths into distro and linux path\", () => {\n assert.deepEqual(parseWslUncPath(String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\.opencode\\bin\\opencode`), {\n distro: \"Ubuntu\",\n linuxPath: \"/home/dev/.opencode/bin/opencode\",\n })\n })\n\n it(\"supports the legacy wsl$ UNC prefix\", () => {\n assert.deepEqual(parseWslUncPath(String.raw`\\\\wsl$\\Ubuntu\\home\\dev`), {\n distro: \"Ubuntu\",\n linuxPath: \"/home/dev\",\n })\n })\n})\n\ndescribe(\"resolveWslWorkingDirectory\", () => {\n it(\"keeps WSL workspace folders in the same distro\", () => {\n assert.equal(\n JSON.stringify(resolveWslWorkingDirectory(String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\workspace`, \"Ubuntu\")),\n JSON.stringify({ kind: \"linux\", path: \"/home/dev/workspace\" }),\n )\n })\n\n it(\"keeps Windows drive paths so WSL can resolve them with wslpath\", () => {\n assert.equal(\n JSON.stringify(resolveWslWorkingDirectory(String.raw`C:\\Users\\dev\\workspace`, \"Ubuntu\")),\n JSON.stringify({ kind: \"windows\", path: String.raw`C:\\Users\\dev\\workspace` }),\n )\n })\n\n it(\"keeps UNC network paths so WSL can resolve them with wslpath\", () => {\n assert.equal(\n JSON.stringify(resolveWslWorkingDirectory(String.raw`\\\\server\\share\\workspace`, \"Ubuntu\")),\n JSON.stringify({ kind: \"windows\", path: String.raw`\\\\server\\share\\workspace` }),\n )\n })\n\n it(\"rejects WSL workspace folders from a different distro\", () => {\n assert.equal(resolveWslWorkingDirectory(String.raw`\\\\wsl.localhost\\Debian\\home\\dev\\workspace`, \"Ubuntu\"), null)\n })\n})\n\ndescribe(\"buildWindowsSpawnSpec\", () => {\n it(\"resolves a bare cmd shim from a quoted PATH entry and wraps its absolute path\", { skip: process.platform !== \"win32\" }, () => {\n const root = mkdtempSync(path.join(tmpdir(), \"codenomad-spawn-\"))\n const cwd = path.join(root, \"workspace\")\n const bin = path.join(root, \"bin with spaces\")\n mkdirSync(cwd)\n mkdirSync(bin)\n const shim = path.join(bin, \"opencode.cmd\")\n writeFileSync(shim, \"@echo off\\r\\n\")\n\n try {\n const spec = buildWindowsSpawnSpec(\"opencode\", [\"serve\"], {\n cwd,\n env: { Path: `\"${bin}\"`, PathExt: \".CMD;.EXE\", ComSpec: \"test-cmd.exe\" },\n })\n\n assert.equal(spec.command, \"test-cmd.exe\")\n assert.equal(spec.options.windowsVerbatimArguments, true)\n assert.match(spec.args[3] ?? \"\", new RegExp(escapeRegex(path.win32.resolve(shim)), \"i\"))\n } finally {\n rmSync(root, { recursive: true, force: true })\n }\n })\n\n it(\"honors PATHEXT precedence when both native and shim files exist\", { skip: process.platform !== \"win32\" }, () => {\n const root = mkdtempSync(path.join(tmpdir(), \"codenomad-spawn-\"))\n writeFileSync(path.join(root, \"opencode.cmd\"), \"@echo off\\r\\n\")\n writeFileSync(path.join(root, \"opencode.exe\"), \"\")\n\n try {\n const spec = buildWindowsSpawnSpec(\"opencode\", [], {\n cwd: root,\n env: { PATH: \"\", PATHEXT: \".EXE;.CMD\" },\n })\n\n assert.equal(spec.command.toLowerCase(), path.win32.resolve(root, \"opencode.exe\").toLowerCase())\n } finally {\n rmSync(root, { recursive: true, force: true })\n }\n })\n\n it(\"leaves an unresolved bare command unchanged without injecting a shell\", () => {\n const spec = buildWindowsSpawnSpec(\"missing-opencode\", [\"serve\"], {\n cwd: String.raw`C:\\missing-workspace`,\n env: { PATH: \"\", PATHEXT: \".CMD;.EXE\", ComSpec: \"must-not-run.exe\" },\n })\n\n assert.equal(spec.command, \"missing-opencode\")\n assert.deepEqual(spec.args, [\"serve\"])\n assert.equal(spec.options.windowsVerbatimArguments, undefined)\n })\n\n it(\"wraps WSL binaries with wsl.exe\", () => {\n const spec = buildWindowsSpawnSpec(\n String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\.opencode\\bin\\opencode`,\n [\"serve\", \"--port\", \"0\"],\n {\n cwd: String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\workspace`,\n },\n )\n\n assert.equal(spec.command, \"wsl.exe\")\n assert.deepEqual(spec.args, [\n \"--distribution\",\n \"Ubuntu\",\n \"--cd\",\n \"/home/dev/workspace\",\n \"--exec\",\n \"/home/dev/.opencode/bin/opencode\",\n \"serve\",\n \"--port\",\n \"0\",\n ])\n assert.equal(spec.cwd, undefined)\n })\n\n it(\"uses wslpath for Windows workspace folders instead of assuming /mnt\", () => {\n const spec = buildWindowsSpawnSpec(\n String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\.opencode\\bin\\opencode`,\n [\"serve\", \"--port\", \"0\"],\n {\n cwd: String.raw`C:\\Users\\dev\\workspace`,\n },\n )\n\n assert.equal(spec.command, \"wsl.exe\")\n assert.deepEqual(spec.args, [\n \"--distribution\",\n \"Ubuntu\",\n \"--exec\",\n \"sh\",\n \"-lc\",\n 'cd \"$(wslpath -au \"$1\")\" && shift && exec \"$@\"',\n \"codenomad-wsl-launch\",\n String.raw`C:\\Users\\dev\\workspace`,\n \"/home/dev/.opencode/bin/opencode\",\n \"serve\",\n \"--port\",\n \"0\",\n ])\n })\n\n it(\"uses wslpath for UNC network workspace folders\", () => {\n const spec = buildWindowsSpawnSpec(\n String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\.opencode\\bin\\opencode`,\n [\"serve\"],\n {\n cwd: String.raw`\\\\server\\share\\workspace`,\n },\n )\n\n assert.equal(spec.command, \"wsl.exe\")\n assert.deepEqual(spec.args, [\n \"--distribution\",\n \"Ubuntu\",\n \"--exec\",\n \"sh\",\n \"-lc\",\n 'cd \"$(wslpath -au \"$1\")\" && shift && exec \"$@\"',\n \"codenomad-wsl-launch\",\n String.raw`\\\\server\\share\\workspace`,\n \"/home/dev/.opencode/bin/opencode\",\n \"serve\",\n ])\n })\n\n})\n\ndescribe(\"resolveWslServiceDirectory\", () => {\n it(\"converts WSL UNC paths without invoking wslpath\", async () => {\n assert.equal(\n await resolveWslServiceDirectory(String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\workspace`, \"Ubuntu\", () => {\n throw new Error(\"wslpath should not run\")\n }),\n \"/home/dev/workspace\",\n )\n })\n\n it(\"uses wslpath for Windows workspace paths\", async () => {\n assert.equal(\n await resolveWslServiceDirectory(String.raw`C:\\Users\\dev\\workspace`, \"Ubuntu\", (folder, distro) => {\n assert.equal(folder, String.raw`C:\\Users\\dev\\workspace`)\n assert.equal(distro, \"Ubuntu\")\n return \"/mnt/c/Users/dev/workspace\"\n }),\n \"/mnt/c/Users/dev/workspace\",\n )\n })\n\n it(\"bounds Windows path translation and returns null on timeout\", async () => {\n let timeoutMs = 0\n const startedAt = Date.now()\n assert.equal(\n await resolveWslServiceDirectory(String.raw`C:\\Users\\dev\\workspace`, \"Ubuntu\", (_folder, _distro, timeout) => {\n timeoutMs = timeout\n const result = spawnSync(process.execPath, [\"-e\", \"setInterval(() => {}, 1000)\"], { timeout })\n assert.equal((result.error as NodeJS.ErrnoException | undefined)?.code, \"ETIMEDOUT\")\n return result.status === 0 ? result.stdout.toString() : undefined\n }, 25),\n null,\n )\n assert.equal(timeoutMs, 25)\n assert.ok(Date.now() - startedAt < 1_000)\n })\n\n it(\"maps service paths back to host paths with the same bound\", async () => {\n assert.equal(\n await resolveWslHostDirectory(\"/mnt/c/Users/dev/workspace\", \"Ubuntu\", (folder, distro, timeout) => {\n assert.deepEqual([folder, distro, timeout], [\"/mnt/c/Users/dev/workspace\", \"Ubuntu\", 23])\n return String.raw`C:\\Users\\dev\\workspace`\n }, 23),\n String.raw`C:\\Users\\dev\\workspace`,\n )\n })\n})\n\ndescribe(\"buildServiceLaunchSpec\", () => {\n it(\"returns the configured host binary or discriminated WSL binary\", () => {\n assert.deepEqual(\n buildServiceLaunchSpec(\"opencode.exe\", { platform: \"win32\" }),\n { kind: \"host\", binary: \"opencode.exe\", platform: \"win32\" },\n )\n assert.deepEqual(\n buildServiceLaunchSpec(\"opencode.ps1\", { platform: \"win32\" }),\n { kind: \"host\", binary: \"opencode.ps1\", platform: \"win32\" },\n )\n assert.deepEqual(\n buildServiceLaunchSpec(String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\opencode`, { platform: \"win32\" }),\n { kind: \"wsl\", distro: \"Ubuntu\", binary: \"/home/dev/opencode\" },\n )\n })\n\n it(\"never converts a WSL service binary into a host lifecycle\", () => {\n const launch = buildServiceLaunchSpec(\n String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\opencode`,\n { platform: \"win32\" },\n )\n\n assert.deepEqual(launch, { kind: \"wsl\", distro: \"Ubuntu\", binary: \"/home/dev/opencode\" })\n assert.equal(\"binary\" in launch, true)\n })\n})\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n}\n"]}
1
+ {"version":3,"file":"spawn.test.js","sourceRoot":"","sources":["../../../src/workspaces/__tests__/spawn.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\"\nimport { spawnSync } from \"node:child_process\"\nimport { mkdirSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\"\nimport { tmpdir } from \"node:os\"\nimport path from \"node:path\"\nimport { describe, it } from \"node:test\"\n\nimport {\n buildServiceLaunchSpec,\n buildWindowsSpawnSpec,\n parseWslUncPath,\n probeOpenCodeBinary,\n resolveWslHostDirectory,\n resolveWslServiceDirectory,\n resolveWslWorkingDirectory,\n} from \"../spawn\"\nimport { OPENCODE_V2_REQUIRED_ERROR_CODE } from \"../../api-types\"\n\nconst legacyOpenCodeHelp = `\\x1b[31mCommands:\\x1b[0m\n opencode completion generate shell completion script\n opencode [project] start opencode tui [default]\n opencode attach <url> attach to a running opencode server\n`\n\ndescribe(\"parseWslUncPath\", () => {\n it(\"parses WSL UNC paths into distro and linux path\", () => {\n assert.deepEqual(parseWslUncPath(String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\.opencode\\bin\\opencode`), {\n distro: \"Ubuntu\",\n linuxPath: \"/home/dev/.opencode/bin/opencode\",\n })\n })\n\n it(\"supports the legacy wsl$ UNC prefix\", () => {\n assert.deepEqual(parseWslUncPath(String.raw`\\\\wsl$\\Ubuntu\\home\\dev`), {\n distro: \"Ubuntu\",\n linuxPath: \"/home/dev\",\n })\n })\n})\n\ndescribe(\"resolveWslWorkingDirectory\", () => {\n it(\"keeps WSL workspace folders in the same distro\", () => {\n assert.equal(\n JSON.stringify(resolveWslWorkingDirectory(String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\workspace`, \"Ubuntu\")),\n JSON.stringify({ kind: \"linux\", path: \"/home/dev/workspace\" }),\n )\n })\n\n it(\"keeps Windows drive paths so WSL can resolve them with wslpath\", () => {\n assert.equal(\n JSON.stringify(resolveWslWorkingDirectory(String.raw`C:\\Users\\dev\\workspace`, \"Ubuntu\")),\n JSON.stringify({ kind: \"windows\", path: String.raw`C:\\Users\\dev\\workspace` }),\n )\n })\n\n it(\"keeps UNC network paths so WSL can resolve them with wslpath\", () => {\n assert.equal(\n JSON.stringify(resolveWslWorkingDirectory(String.raw`\\\\server\\share\\workspace`, \"Ubuntu\")),\n JSON.stringify({ kind: \"windows\", path: String.raw`\\\\server\\share\\workspace` }),\n )\n })\n\n it(\"rejects WSL workspace folders from a different distro\", () => {\n assert.equal(resolveWslWorkingDirectory(String.raw`\\\\wsl.localhost\\Debian\\home\\dev\\workspace`, \"Ubuntu\"), null)\n })\n})\n\ndescribe(\"buildWindowsSpawnSpec\", () => {\n it(\"resolves a bare cmd shim from a quoted PATH entry and wraps its absolute path\", { skip: process.platform !== \"win32\" }, () => {\n const root = mkdtempSync(path.join(tmpdir(), \"codenomad-spawn-\"))\n const cwd = path.join(root, \"workspace\")\n const bin = path.join(root, \"bin with spaces\")\n mkdirSync(cwd)\n mkdirSync(bin)\n const shim = path.join(bin, \"opencode.cmd\")\n writeFileSync(shim, \"@echo off\\r\\n\")\n\n try {\n const spec = buildWindowsSpawnSpec(\"opencode\", [\"serve\"], {\n cwd,\n env: { Path: `\"${bin}\"`, PathExt: \".CMD;.EXE\", ComSpec: \"test-cmd.exe\" },\n })\n\n assert.equal(spec.command, \"test-cmd.exe\")\n assert.equal(spec.options.windowsVerbatimArguments, true)\n assert.match(spec.args[3] ?? \"\", new RegExp(escapeRegex(path.win32.resolve(shim)), \"i\"))\n } finally {\n rmSync(root, { recursive: true, force: true })\n }\n })\n\n it(\"honors PATHEXT precedence when both native and shim files exist\", { skip: process.platform !== \"win32\" }, () => {\n const root = mkdtempSync(path.join(tmpdir(), \"codenomad-spawn-\"))\n writeFileSync(path.join(root, \"opencode.cmd\"), \"@echo off\\r\\n\")\n writeFileSync(path.join(root, \"opencode.exe\"), \"\")\n\n try {\n const spec = buildWindowsSpawnSpec(\"opencode\", [], {\n cwd: root,\n env: { PATH: \"\", PATHEXT: \".EXE;.CMD\" },\n })\n\n assert.equal(spec.command.toLowerCase(), path.win32.resolve(root, \"opencode.exe\").toLowerCase())\n } finally {\n rmSync(root, { recursive: true, force: true })\n }\n })\n\n it(\"leaves an unresolved bare command unchanged without injecting a shell\", () => {\n const spec = buildWindowsSpawnSpec(\"missing-opencode\", [\"serve\"], {\n cwd: String.raw`C:\\missing-workspace`,\n env: { PATH: \"\", PATHEXT: \".CMD;.EXE\", ComSpec: \"must-not-run.exe\" },\n })\n\n assert.equal(spec.command, \"missing-opencode\")\n assert.deepEqual(spec.args, [\"serve\"])\n assert.equal(spec.options.windowsVerbatimArguments, undefined)\n })\n\n it(\"wraps WSL binaries with wsl.exe\", () => {\n const spec = buildWindowsSpawnSpec(\n String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\.opencode\\bin\\opencode`,\n [\"serve\", \"--port\", \"0\"],\n {\n cwd: String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\workspace`,\n },\n )\n\n assert.equal(spec.command, \"wsl.exe\")\n assert.deepEqual(spec.args, [\n \"--distribution\",\n \"Ubuntu\",\n \"--cd\",\n \"/home/dev/workspace\",\n \"--exec\",\n \"/home/dev/.opencode/bin/opencode\",\n \"serve\",\n \"--port\",\n \"0\",\n ])\n assert.equal(spec.cwd, undefined)\n })\n\n it(\"uses wslpath for Windows workspace folders instead of assuming /mnt\", () => {\n const spec = buildWindowsSpawnSpec(\n String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\.opencode\\bin\\opencode`,\n [\"serve\", \"--port\", \"0\"],\n {\n cwd: String.raw`C:\\Users\\dev\\workspace`,\n },\n )\n\n assert.equal(spec.command, \"wsl.exe\")\n assert.deepEqual(spec.args, [\n \"--distribution\",\n \"Ubuntu\",\n \"--exec\",\n \"sh\",\n \"-lc\",\n 'cd \"$(wslpath -au \"$1\")\" && shift && exec \"$@\"',\n \"codenomad-wsl-launch\",\n String.raw`C:\\Users\\dev\\workspace`,\n \"/home/dev/.opencode/bin/opencode\",\n \"serve\",\n \"--port\",\n \"0\",\n ])\n })\n\n it(\"uses wslpath for UNC network workspace folders\", () => {\n const spec = buildWindowsSpawnSpec(\n String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\.opencode\\bin\\opencode`,\n [\"serve\"],\n {\n cwd: String.raw`\\\\server\\share\\workspace`,\n },\n )\n\n assert.equal(spec.command, \"wsl.exe\")\n assert.deepEqual(spec.args, [\n \"--distribution\",\n \"Ubuntu\",\n \"--exec\",\n \"sh\",\n \"-lc\",\n 'cd \"$(wslpath -au \"$1\")\" && shift && exec \"$@\"',\n \"codenomad-wsl-launch\",\n String.raw`\\\\server\\share\\workspace`,\n \"/home/dev/.opencode/bin/opencode\",\n \"serve\",\n ])\n })\n\n})\n\ndescribe(\"resolveWslServiceDirectory\", () => {\n it(\"converts WSL UNC paths without invoking wslpath\", async () => {\n assert.equal(\n await resolveWslServiceDirectory(String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\workspace`, \"Ubuntu\", () => {\n throw new Error(\"wslpath should not run\")\n }),\n \"/home/dev/workspace\",\n )\n })\n\n it(\"uses wslpath for Windows workspace paths\", async () => {\n assert.equal(\n await resolveWslServiceDirectory(String.raw`C:\\Users\\dev\\workspace`, \"Ubuntu\", (folder, distro) => {\n assert.equal(folder, String.raw`C:\\Users\\dev\\workspace`)\n assert.equal(distro, \"Ubuntu\")\n return \"/mnt/c/Users/dev/workspace\"\n }),\n \"/mnt/c/Users/dev/workspace\",\n )\n })\n\n it(\"bounds Windows path translation and returns null on timeout\", async () => {\n let timeoutMs = 0\n const startedAt = Date.now()\n assert.equal(\n await resolveWslServiceDirectory(String.raw`C:\\Users\\dev\\workspace`, \"Ubuntu\", (_folder, _distro, timeout) => {\n timeoutMs = timeout\n const result = spawnSync(process.execPath, [\"-e\", \"setInterval(() => {}, 1000)\"], { timeout })\n assert.equal((result.error as NodeJS.ErrnoException | undefined)?.code, \"ETIMEDOUT\")\n return result.status === 0 ? result.stdout.toString() : undefined\n }, 25),\n null,\n )\n assert.equal(timeoutMs, 25)\n assert.ok(Date.now() - startedAt < 1_000)\n })\n\n it(\"maps service paths back to host paths with the same bound\", async () => {\n assert.equal(\n await resolveWslHostDirectory(\"/mnt/c/Users/dev/workspace\", \"Ubuntu\", (folder, distro, timeout) => {\n assert.deepEqual([folder, distro, timeout], [\"/mnt/c/Users/dev/workspace\", \"Ubuntu\", 23])\n return String.raw`C:\\Users\\dev\\workspace`\n }, 23),\n String.raw`C:\\Users\\dev\\workspace`,\n )\n })\n})\n\ndescribe(\"buildServiceLaunchSpec\", () => {\n it(\"returns the configured host binary or discriminated WSL binary\", () => {\n assert.deepEqual(\n buildServiceLaunchSpec(\"opencode.exe\", { platform: \"win32\" }),\n { kind: \"host\", binary: \"opencode.exe\", platform: \"win32\" },\n )\n assert.deepEqual(\n buildServiceLaunchSpec(\"opencode.ps1\", { platform: \"win32\" }),\n { kind: \"host\", binary: \"opencode.ps1\", platform: \"win32\" },\n )\n assert.deepEqual(\n buildServiceLaunchSpec(String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\opencode`, { platform: \"win32\" }),\n { kind: \"wsl\", distro: \"Ubuntu\", binary: \"/home/dev/opencode\" },\n )\n })\n\n it(\"never converts a WSL service binary into a host lifecycle\", () => {\n const launch = buildServiceLaunchSpec(\n String.raw`\\\\wsl.localhost\\Ubuntu\\home\\dev\\opencode`,\n { platform: \"win32\" },\n )\n\n assert.deepEqual(launch, { kind: \"wsl\", distro: \"Ubuntu\", binary: \"/home/dev/opencode\" })\n assert.equal(\"binary\" in launch, true)\n })\n})\n\ndescribe(\"probeOpenCodeBinary\", () => {\n it(\"rejects a V1 binary whose root help exits successfully without a service command\", async () => {\n const calls: string[][] = []\n const result = await probeOpenCodeBinary(process.execPath, (spec) => {\n calls.push(spec.args)\n return spec.args[0] === \"--version\"\n ? { status: 0, stdout: \"1.18.25\\n\", stderr: \"\" }\n : { status: 0, stdout: \"\", stderr: legacyOpenCodeHelp }\n })\n\n assert.deepEqual(calls, [[\"--version\"], [\"service\", \"--help\"]])\n assert.deepEqual(result, {\n valid: false,\n version: \"1.18.25\",\n errorCode: OPENCODE_V2_REQUIRED_ERROR_CODE,\n })\n })\n\n it(\"accepts a binary that supports the service lifecycle\", async () => {\n const result = await probeOpenCodeBinary(process.execPath, (spec) => spec.args[0] === \"--version\"\n ? { status: 0, stdout: \"opencode2 v0.0.0-beta-18999\\n\", stderr: \"\" }\n : { status: 0, stdout: \"USAGE\\n opencode2 service <subcommand> [flags]\\nSUBCOMMANDS\\n start Start server\\n status Server status\\n get Get configuration\\n\", stderr: \"\" })\n\n assert.deepEqual(result, { valid: true, version: \"0.0.0-beta-18999\" })\n })\n\n it(\"does not accept an empty, unrelated, or daemon-status response as capability evidence\", async () => {\n for (const stdout of [\"\", \"stopped\\n\", \"http://127.0.0.1:1234\\n\", \"arbitrary wrapper help\\n\"]) {\n const result = await probeOpenCodeBinary(process.execPath, (spec) => ({\n status: 0, stdout: spec.args[0] === \"--version\" ? \"2.0.0\\n\" : stdout,\n }))\n assert.equal(result.valid, false, JSON.stringify(stdout))\n assert.equal(result.errorCode, undefined, \"unknown output must not be mislabeled as V1\")\n }\n })\n})\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { describe, it } from "node:test";
3
3
  import { HostOpenCodeService, hostOpenCodeServiceIdentity } from "./host-opencode-service";
4
+ import { OPENCODE_V2_REQUIRED_ERROR_CODE } from "../api-types";
4
5
  const url = "http://127.0.0.1:4321";
5
6
  describe("HostOpenCodeService", () => {
6
7
  it("uses status, start, and password through buildSpawnSpec without a shell", async () => {
@@ -79,6 +80,30 @@ describe("HostOpenCodeService", () => {
79
80
  assert.match(identity, /:env:[a-f0-9]{64}$/);
80
81
  assert.equal(identity.includes(secret), false);
81
82
  });
83
+ it("reports an actionable compatibility error for an OpenCode V1 binary", async () => {
84
+ const service = createService([], {}, {
85
+ execFile: async () => {
86
+ throw Object.assign(new Error("Command failed"), {
87
+ code: 1,
88
+ stdout: "",
89
+ stderr: `Commands:\n opencode completion\n opencode [project] start opencode tui [default]\n`,
90
+ });
91
+ },
92
+ });
93
+ await assert.rejects(service.discover(), (error) => {
94
+ assert.match(error.message, new RegExp(OPENCODE_V2_REQUIRED_ERROR_CODE));
95
+ assert.doesNotMatch(error.message, /Commands:/);
96
+ return true;
97
+ });
98
+ });
99
+ it("recognizes legacy help even when a wrapper exits zero on stdout or stderr", async () => {
100
+ for (const stream of ["stdout", "stderr"]) {
101
+ const service = createService([], {}, {
102
+ execFile: async () => ({ stdout: "", stderr: "", [stream]: "Commands:\n opencode completion\n opencode [project]\n" }),
103
+ });
104
+ await assert.rejects(service.discover(), new RegExp(`^Error: ${OPENCODE_V2_REQUIRED_ERROR_CODE}:`));
105
+ }
106
+ });
82
107
  });
83
108
  function createService(calls, startupEnvironment, overrides = {}) {
84
109
  return new HostOpenCodeService({
@@ -1 +1 @@
1
- {"version":3,"file":"host-opencode-service.test.js","sourceRoot":"","sources":["../../src/workspaces/host-opencode-service.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\"\nimport { describe, it } from \"node:test\"\n\nimport { HostOpenCodeService, hostOpenCodeServiceIdentity } from \"./host-opencode-service\"\nimport type { OpenCodeCliServiceDependencies, ServiceExecOptions } from \"./opencode-cli-service\"\n\nconst url = \"http://127.0.0.1:4321\"\n\ndescribe(\"HostOpenCodeService\", () => {\n it(\"uses status, start, and password through buildSpawnSpec without a shell\", async () => {\n const calls: Array<{ file: string; args: string[]; options: ServiceExecOptions }> = []\n const service = createService(calls, { PROVIDER_TOKEN: \"secret\", NODE_EXTRA_CA_CERTS: \"/ca.pem\" })\n\n assert.equal(await service.discover(), undefined)\n assert.deepEqual(await service.ensure(), {\n url,\n auth: { type: \"basic\", username: \"opencode\", password: \"password\" },\n })\n\n assert.deepEqual(calls.map(({ args }) => args), [\n [\"service\", \"status\"],\n [\"service\", \"start\"],\n [\"service\", \"get\", \"password\"],\n ])\n assert.equal(calls.every(({ options }) => options.shell === false), true)\n assert.equal(calls[0]?.options.env, undefined)\n assert.equal(calls[2]?.options.env, undefined)\n assert.equal(calls[1]?.options.env?.PROVIDER_TOKEN, \"secret\")\n assert.equal(calls[1]?.options.env?.NODE_EXTRA_CA_CERTS, \"/ca.pem\")\n })\n\n it(\"authenticates strict bounded health and rejects malformed output\", async () => {\n let authorization: string | null = null\n const calls: Array<{ file: string; args: string[]; options: ServiceExecOptions }> = []\n const service = createService(calls, {}, {\n fetch: async (_input, init) => {\n authorization = new Headers(init?.headers).get(\"authorization\")\n return Response.json({ healthy: true, version: \"2.0.0\", pid: 1 })\n },\n execFile: async (file, args, options) => {\n calls.push({ file, args, options })\n return { stdout: args[args.length - 1] === \"password\" ? \"password\\n\" : `${url}\\n`, stderr: \"\" }\n },\n })\n await service.discover()\n assert.equal(authorization, `Basic ${Buffer.from(\"opencode:password\").toString(\"base64\")}`)\n\n const malformed = createService([], {}, {\n execFile: async () => ({ stdout: `${url}\\nhttp://127.0.0.1:4322\\n`, stderr: \"\" }),\n })\n await assert.rejects(malformed.discover(), /multiline/)\n })\n\n it(\"connects to wildcard services through loopback\", async () => {\n let healthUrl = \"\"\n const service = createService([], {}, {\n execFile: async (_file, args) => ({\n stdout: args[args.length - 1] === \"password\" ? \"password\\n\" : \"http://0.0.0.0:4321\\n\",\n stderr: \"\",\n }),\n fetch: async (input) => {\n healthUrl = String(input)\n return Response.json({ healthy: true, version: \"2.0.0\", pid: 1 })\n },\n })\n\n assert.equal((await service.discover())?.url, \"http://127.0.0.1:4321/\")\n assert.equal(healthUrl, \"http://127.0.0.1:4321/api/health\")\n })\n\n it(\"redacts startup environment values from failures and hashes identity\", async () => {\n const secret = \"DO_NOT_LEAK\"\n const service = createService([], { TOKEN: secret }, {\n execFile: async (_file, args) => {\n if (args[args.length - 1] === \"status\") return { stdout: \"stopped\\n\", stderr: \"\" }\n throw Object.assign(new Error(secret), { code: 7, stdout: secret, stderr: secret })\n },\n })\n await service.discover()\n await assert.rejects(service.ensure(), (error: Error) => {\n assert.match(error.message, /start failed \\(exit code 7\\)/)\n assert.equal(error.message.includes(secret), false)\n return true\n })\n\n const identity = hostOpenCodeServiceIdentity({\n binary: process.platform === \"win32\" ? String.raw`C:\\tools\\..\\opencode.exe` : \"/opt/../opencode\",\n startupEnvironment: { TOKEN: secret },\n })\n assert.match(identity, /:env:[a-f0-9]{64}$/)\n assert.equal(identity.includes(secret), false)\n })\n})\n\nfunction createService(\n calls: Array<{ file: string; args: string[]; options: ServiceExecOptions }>,\n startupEnvironment: NodeJS.ProcessEnv,\n overrides: Partial<OpenCodeCliServiceDependencies> = {},\n) {\n return new HostOpenCodeService({\n binary: process.execPath,\n startupEnvironment,\n timeoutMs: 500,\n }, {\n execFile: async (file, args, options) => {\n calls.push({ file, args, options })\n const command = args.join(\" \")\n if (command === \"service status\") return { stdout: \"stopped\\n\", stderr: \"\" }\n if (command === \"service start\") return { stdout: `${url}\\n`, stderr: \"\" }\n return { stdout: \"password\\n\", stderr: \"\" }\n },\n fetch: async () => Response.json({ healthy: true, version: \"2.0.0\", pid: 123 }),\n ...overrides,\n })\n}\n"]}
1
+ {"version":3,"file":"host-opencode-service.test.js","sourceRoot":"","sources":["../../src/workspaces/host-opencode-service.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\"\nimport { describe, it } from \"node:test\"\n\nimport { HostOpenCodeService, hostOpenCodeServiceIdentity } from \"./host-opencode-service\"\nimport type { OpenCodeCliServiceDependencies, ServiceExecOptions } from \"./opencode-cli-service\"\nimport { OPENCODE_V2_REQUIRED_ERROR_CODE } from \"../api-types\"\n\nconst url = \"http://127.0.0.1:4321\"\n\ndescribe(\"HostOpenCodeService\", () => {\n it(\"uses status, start, and password through buildSpawnSpec without a shell\", async () => {\n const calls: Array<{ file: string; args: string[]; options: ServiceExecOptions }> = []\n const service = createService(calls, { PROVIDER_TOKEN: \"secret\", NODE_EXTRA_CA_CERTS: \"/ca.pem\" })\n\n assert.equal(await service.discover(), undefined)\n assert.deepEqual(await service.ensure(), {\n url,\n auth: { type: \"basic\", username: \"opencode\", password: \"password\" },\n })\n\n assert.deepEqual(calls.map(({ args }) => args), [\n [\"service\", \"status\"],\n [\"service\", \"start\"],\n [\"service\", \"get\", \"password\"],\n ])\n assert.equal(calls.every(({ options }) => options.shell === false), true)\n assert.equal(calls[0]?.options.env, undefined)\n assert.equal(calls[2]?.options.env, undefined)\n assert.equal(calls[1]?.options.env?.PROVIDER_TOKEN, \"secret\")\n assert.equal(calls[1]?.options.env?.NODE_EXTRA_CA_CERTS, \"/ca.pem\")\n })\n\n it(\"authenticates strict bounded health and rejects malformed output\", async () => {\n let authorization: string | null = null\n const calls: Array<{ file: string; args: string[]; options: ServiceExecOptions }> = []\n const service = createService(calls, {}, {\n fetch: async (_input, init) => {\n authorization = new Headers(init?.headers).get(\"authorization\")\n return Response.json({ healthy: true, version: \"2.0.0\", pid: 1 })\n },\n execFile: async (file, args, options) => {\n calls.push({ file, args, options })\n return { stdout: args[args.length - 1] === \"password\" ? \"password\\n\" : `${url}\\n`, stderr: \"\" }\n },\n })\n await service.discover()\n assert.equal(authorization, `Basic ${Buffer.from(\"opencode:password\").toString(\"base64\")}`)\n\n const malformed = createService([], {}, {\n execFile: async () => ({ stdout: `${url}\\nhttp://127.0.0.1:4322\\n`, stderr: \"\" }),\n })\n await assert.rejects(malformed.discover(), /multiline/)\n })\n\n it(\"connects to wildcard services through loopback\", async () => {\n let healthUrl = \"\"\n const service = createService([], {}, {\n execFile: async (_file, args) => ({\n stdout: args[args.length - 1] === \"password\" ? \"password\\n\" : \"http://0.0.0.0:4321\\n\",\n stderr: \"\",\n }),\n fetch: async (input) => {\n healthUrl = String(input)\n return Response.json({ healthy: true, version: \"2.0.0\", pid: 1 })\n },\n })\n\n assert.equal((await service.discover())?.url, \"http://127.0.0.1:4321/\")\n assert.equal(healthUrl, \"http://127.0.0.1:4321/api/health\")\n })\n\n it(\"redacts startup environment values from failures and hashes identity\", async () => {\n const secret = \"DO_NOT_LEAK\"\n const service = createService([], { TOKEN: secret }, {\n execFile: async (_file, args) => {\n if (args[args.length - 1] === \"status\") return { stdout: \"stopped\\n\", stderr: \"\" }\n throw Object.assign(new Error(secret), { code: 7, stdout: secret, stderr: secret })\n },\n })\n await service.discover()\n await assert.rejects(service.ensure(), (error: Error) => {\n assert.match(error.message, /start failed \\(exit code 7\\)/)\n assert.equal(error.message.includes(secret), false)\n return true\n })\n\n const identity = hostOpenCodeServiceIdentity({\n binary: process.platform === \"win32\" ? String.raw`C:\\tools\\..\\opencode.exe` : \"/opt/../opencode\",\n startupEnvironment: { TOKEN: secret },\n })\n assert.match(identity, /:env:[a-f0-9]{64}$/)\n assert.equal(identity.includes(secret), false)\n })\n\n it(\"reports an actionable compatibility error for an OpenCode V1 binary\", async () => {\n const service = createService([], {}, {\n execFile: async () => {\n throw Object.assign(new Error(\"Command failed\"), {\n code: 1,\n stdout: \"\",\n stderr: `Commands:\\n opencode completion\\n opencode [project] start opencode tui [default]\\n`,\n })\n },\n })\n\n await assert.rejects(service.discover(), (error: Error) => {\n assert.match(error.message, new RegExp(OPENCODE_V2_REQUIRED_ERROR_CODE))\n assert.doesNotMatch(error.message, /Commands:/)\n return true\n })\n })\n\n it(\"recognizes legacy help even when a wrapper exits zero on stdout or stderr\", async () => {\n for (const stream of [\"stdout\", \"stderr\"]) {\n const service = createService([], {}, {\n execFile: async () => ({ stdout: \"\", stderr: \"\", [stream]: \"Commands:\\n opencode completion\\n opencode [project]\\n\" }),\n })\n await assert.rejects(service.discover(), new RegExp(`^Error: ${OPENCODE_V2_REQUIRED_ERROR_CODE}:`))\n }\n })\n})\n\nfunction createService(\n calls: Array<{ file: string; args: string[]; options: ServiceExecOptions }>,\n startupEnvironment: NodeJS.ProcessEnv,\n overrides: Partial<OpenCodeCliServiceDependencies> = {},\n) {\n return new HostOpenCodeService({\n binary: process.execPath,\n startupEnvironment,\n timeoutMs: 500,\n }, {\n execFile: async (file, args, options) => {\n calls.push({ file, args, options })\n const command = args.join(\" \")\n if (command === \"service status\") return { stdout: \"stopped\\n\", stderr: \"\" }\n if (command === \"service start\") return { stdout: `${url}\\n`, stderr: \"\" }\n return { stdout: \"password\\n\", stderr: \"\" }\n },\n fetch: async () => Response.json({ healthy: true, version: \"2.0.0\", pid: 123 }),\n ...overrides,\n })\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import { stripVTControlCharacters } from "node:util";
2
+ function helpOutput(stdout, stderr) {
3
+ return stripVTControlCharacters([stdout, stderr]
4
+ .filter((value) => value !== undefined && value !== null)
5
+ .map(String)
6
+ .join("\n"))
7
+ .replace(/\r/g, "");
8
+ }
9
+ export function isOpenCodeServiceCommandUnavailable(stdout, stderr) {
10
+ const output = helpOutput(stdout, stderr);
11
+ const commandsIndex = output.search(/^Commands:\s*$/im);
12
+ if (commandsIndex < 0)
13
+ return false;
14
+ const commandLines = output
15
+ .slice(commandsIndex)
16
+ .split("\n")
17
+ .filter((line) => /^\s*opencode(?:2)?(?:\s|$)/i.test(line));
18
+ return commandLines.length > 0
19
+ && !commandLines.some((line) => /^\s*opencode(?:2)?\s+service(?:\s|$)/i.test(line));
20
+ }
21
+ export function isOpenCodeServiceHelp(stdout, stderr) {
22
+ const output = helpOutput(stdout, stderr);
23
+ // Require the service namespace and the commands we actually use. A zero
24
+ // exit, version number, root help or running daemon alone proves nothing.
25
+ if (!/^\s*opencode(?:2)?\s+service(?:\s|$)/im.test(output))
26
+ return false;
27
+ const commands = output.split(/^\s*(?:SUBCOMMANDS|Commands:)\s*$/im)[1];
28
+ if (!commands)
29
+ return false;
30
+ return ["start", "status", "get"].every((command) => new RegExp(`^\\s*(?:opencode(?:2)?\\s+service\\s+)?${command}(?:\\s|$)`, "im").test(commands));
31
+ }
32
+ //# sourceMappingURL=opencode-cli-compatibility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-cli-compatibility.js","sourceRoot":"","sources":["../../src/workspaces/opencode-cli-compatibility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAA;AAEpD,SAAS,UAAU,CAAC,MAAe,EAAE,MAAgB;IACnD,OAAO,wBAAwB,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC;SAC7C,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC;SACxD,GAAG,CAAC,MAAM,CAAC;SACX,IAAI,CAAC,IAAI,CAAC,CAAC;SACX,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;AACvB,CAAC;AAED,MAAM,UAAU,mCAAmC,CAAC,MAAe,EAAE,MAAgB;IACnF,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzC,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAA;IACvD,IAAI,aAAa,GAAG,CAAC;QAAE,OAAO,KAAK,CAAA;IAEnC,MAAM,YAAY,GAAG,MAAM;SACxB,KAAK,CAAC,aAAa,CAAC;SACpB,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IAE7D,OAAO,YAAY,CAAC,MAAM,GAAG,CAAC;WACzB,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,uCAAuC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;AACvF,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAe,EAAE,MAAgB;IACrE,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzC,yEAAyE;IACzE,0EAA0E;IAC1E,IAAI,CAAC,wCAAwC,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IACxE,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC,CAAC,CAAC,CAAA;IACvE,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC3B,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,MAAM,CAC7D,0CAA0C,OAAO,WAAW,EAAE,IAAI,CACnE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;AACnB,CAAC","sourcesContent":["import { stripVTControlCharacters } from \"node:util\"\n\nfunction helpOutput(stdout: unknown, stderr?: unknown): string {\n return stripVTControlCharacters([stdout, stderr]\n .filter((value) => value !== undefined && value !== null)\n .map(String)\n .join(\"\\n\"))\n .replace(/\\r/g, \"\")\n}\n\nexport function isOpenCodeServiceCommandUnavailable(stdout: unknown, stderr?: unknown): boolean {\n const output = helpOutput(stdout, stderr)\n const commandsIndex = output.search(/^Commands:\\s*$/im)\n if (commandsIndex < 0) return false\n\n const commandLines = output\n .slice(commandsIndex)\n .split(\"\\n\")\n .filter((line) => /^\\s*opencode(?:2)?(?:\\s|$)/i.test(line))\n\n return commandLines.length > 0\n && !commandLines.some((line) => /^\\s*opencode(?:2)?\\s+service(?:\\s|$)/i.test(line))\n}\n\nexport function isOpenCodeServiceHelp(stdout: unknown, stderr?: unknown): boolean {\n const output = helpOutput(stdout, stderr)\n // Require the service namespace and the commands we actually use. A zero\n // exit, version number, root help or running daemon alone proves nothing.\n if (!/^\\s*opencode(?:2)?\\s+service(?:\\s|$)/im.test(output)) return false\n const commands = output.split(/^\\s*(?:SUBCOMMANDS|Commands:)\\s*$/im)[1]\n if (!commands) return false\n return [\"start\", \"status\", \"get\"].every((command) => new RegExp(\n `^\\\\s*(?:opencode(?:2)?\\\\s+service\\\\s+)?${command}(?:\\\\s|$)`, \"im\",\n ).test(commands))\n}\n"]}
@@ -0,0 +1,35 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { isOpenCodeServiceCommandUnavailable, isOpenCodeServiceHelp } from "./opencode-cli-compatibility";
4
+ import { legacyHelp, serviceHelp } from "./__tests__/binary-probe-fixture";
5
+ describe("OpenCode CLI compatibility evidence", () => {
6
+ it("recognizes V1 root help on either stream with CRLF and terminal formatting", () => {
7
+ for (const help of [legacyHelp, `\x1b]0;OpenCode\x07\x1b[31m${legacyHelp.replace(/\n/g, "\r\n")}\x1b[0m`]) {
8
+ assert.equal(isOpenCodeServiceCommandUnavailable(help), true);
9
+ assert.equal(isOpenCodeServiceCommandUnavailable("", help), true);
10
+ assert.equal(isOpenCodeServiceHelp(help), false);
11
+ }
12
+ });
13
+ it("recognizes required V2 commands without a version or binary filename gate", () => {
14
+ for (const help of [serviceHelp, serviceHelp.replaceAll("opencode2", "opencode"),
15
+ `\x1b[32m${serviceHelp.replace(/\n/g, "\r\n")}\x1b[0m`,
16
+ "opencode service [command]\nCommands:\n opencode service start\n opencode service status\n opencode service get <key>\n",
17
+ ]) {
18
+ assert.equal(isOpenCodeServiceHelp(help), true);
19
+ assert.equal(isOpenCodeServiceHelp("", help), true);
20
+ assert.equal(isOpenCodeServiceCommandUnavailable(help), false);
21
+ }
22
+ });
23
+ it("does not confuse generic failures or partial help with a compatible CLI", () => {
24
+ for (const help of ["", "EACCES", "stopped", "http://127.0.0.1:1234", "service start status get",
25
+ serviceHelp.replace(/ get .*\n/, ""), serviceHelp.replace("opencode2 service", "other-tool service"),
26
+ legacyHelp + " opencode service manage background service\n",
27
+ ]) {
28
+ assert.equal(isOpenCodeServiceHelp(help), false, help);
29
+ }
30
+ for (const failure of ["ENOENT", "Unknown command: config", "Cannot reach daemon", "Commands:\n other run\n"]) {
31
+ assert.equal(isOpenCodeServiceCommandUnavailable(failure), false, failure);
32
+ }
33
+ });
34
+ });
35
+ //# sourceMappingURL=opencode-cli-compatibility.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-cli-compatibility.test.js","sourceRoot":"","sources":["../../src/workspaces/opencode-cli-compatibility.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,mCAAmC,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AACzG,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAE1E,QAAQ,CAAC,qCAAqC,EAAE,GAAG,EAAE;IACnD,EAAE,CAAC,4EAA4E,EAAE,GAAG,EAAE;QACpF,KAAK,MAAM,IAAI,IAAI,CAAC,UAAU,EAAE,8BAA8B,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1G,MAAM,CAAC,KAAK,CAAC,mCAAmC,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;YAC7D,MAAM,CAAC,KAAK,CAAC,mCAAmC,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;YACjE,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAA;QAClD,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2EAA2E,EAAE,GAAG,EAAE;QACnF,KAAK,MAAM,IAAI,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,UAAU,CAAC,WAAW,EAAE,UAAU,CAAC;YAC9E,WAAW,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS;YACtD,4HAA4H;SAC7H,EAAE,CAAC;YACF,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;YAC/C,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;YACnD,MAAM,CAAC,KAAK,CAAC,mCAAmC,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAA;QAChE,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yEAAyE,EAAE,GAAG,EAAE;QACjF,KAAK,MAAM,IAAI,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,uBAAuB,EAAE,0BAA0B;YAC9F,WAAW,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,mBAAmB,EAAE,oBAAoB,CAAC;YACrG,UAAU,GAAG,iDAAiD;SAC/D,EAAE,CAAC;YACF,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;QACxD,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,CAAC,QAAQ,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,0BAA0B,CAAC,EAAE,CAAC;YAC/G,MAAM,CAAC,KAAK,CAAC,mCAAmC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAA;QAC5E,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import assert from \"node:assert/strict\"\nimport { describe, it } from \"node:test\"\nimport { isOpenCodeServiceCommandUnavailable, isOpenCodeServiceHelp } from \"./opencode-cli-compatibility\"\nimport { legacyHelp, serviceHelp } from \"./__tests__/binary-probe-fixture\"\n\ndescribe(\"OpenCode CLI compatibility evidence\", () => {\n it(\"recognizes V1 root help on either stream with CRLF and terminal formatting\", () => {\n for (const help of [legacyHelp, `\\x1b]0;OpenCode\\x07\\x1b[31m${legacyHelp.replace(/\\n/g, \"\\r\\n\")}\\x1b[0m`]) {\n assert.equal(isOpenCodeServiceCommandUnavailable(help), true)\n assert.equal(isOpenCodeServiceCommandUnavailable(\"\", help), true)\n assert.equal(isOpenCodeServiceHelp(help), false)\n }\n })\n\n it(\"recognizes required V2 commands without a version or binary filename gate\", () => {\n for (const help of [serviceHelp, serviceHelp.replaceAll(\"opencode2\", \"opencode\"),\n `\\x1b[32m${serviceHelp.replace(/\\n/g, \"\\r\\n\")}\\x1b[0m`,\n \"opencode service [command]\\nCommands:\\n opencode service start\\n opencode service status\\n opencode service get <key>\\n\",\n ]) {\n assert.equal(isOpenCodeServiceHelp(help), true)\n assert.equal(isOpenCodeServiceHelp(\"\", help), true)\n assert.equal(isOpenCodeServiceCommandUnavailable(help), false)\n }\n })\n\n it(\"does not confuse generic failures or partial help with a compatible CLI\", () => {\n for (const help of [\"\", \"EACCES\", \"stopped\", \"http://127.0.0.1:1234\", \"service start status get\",\n serviceHelp.replace(/ get .*\\n/, \"\"), serviceHelp.replace(\"opencode2 service\", \"other-tool service\"),\n legacyHelp + \" opencode service manage background service\\n\",\n ]) {\n assert.equal(isOpenCodeServiceHelp(help), false, help)\n }\n for (const failure of [\"ENOENT\", \"Unknown command: config\", \"Cannot reach daemon\", \"Commands:\\n other run\\n\"]) {\n assert.equal(isOpenCodeServiceCommandUnavailable(failure), false, failure)\n }\n })\n})\n"]}
@@ -1,6 +1,8 @@
1
1
  import { execFile as nodeExecFile } from "node:child_process";
2
2
  import { Service } from "@opencode-ai/client/service";
3
+ import { OPENCODE_V2_REQUIRED_ERROR_CODE } from "../api-types";
3
4
  import { assertLoopbackServiceUrl } from "./service-state";
5
+ import { isOpenCodeServiceCommandUnavailable } from "./opencode-cli-compatibility";
4
6
  export const MAX_SERVICE_OUTPUT_BYTES = 64 * 1024;
5
7
  const MAX_ERROR_CHARS = 1_024;
6
8
  export class OpenCodeCliService {
@@ -43,11 +45,17 @@ export class OpenCodeCliService {
43
45
  ...(spec.env ? { env: spec.env } : {}),
44
46
  ...(spec.options.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
45
47
  };
48
+ let result;
46
49
  try {
47
- const result = await this.withDeadline(this.dependencies.execFile(spec.command, spec.args, options), deadlineAt, commandLabel);
48
- return result.stdout;
50
+ result = await this.withDeadline(this.dependencies.execFile(spec.command, spec.args, options), deadlineAt, commandLabel);
49
51
  }
50
52
  catch (error) {
53
+ const output = error && typeof error === "object"
54
+ ? error
55
+ : {};
56
+ if (isOpenCodeServiceCommandUnavailable(output.stdout, output.stderr)) {
57
+ throw new Error(`${OPENCODE_V2_REQUIRED_ERROR_CODE}: ${this.options.label} binary does not support the OpenCode V2 service lifecycle`);
58
+ }
51
59
  if (start || commandLabel === "service get password") {
52
60
  const operation = start ? "start" : "password retrieval";
53
61
  const code = safeNumericExecCode(error);
@@ -56,6 +64,10 @@ export class OpenCodeCliService {
56
64
  const detail = boundedExecError(error);
57
65
  throw new Error(`${this.options.label} OpenCode ${commandLabel} failed${detail ? `: ${detail}` : ""}`);
58
66
  }
67
+ if (isOpenCodeServiceCommandUnavailable(result.stdout, result.stderr)) {
68
+ throw new Error(`${OPENCODE_V2_REQUIRED_ERROR_CODE}: ${this.options.label} binary does not support the OpenCode V2 service lifecycle`);
69
+ }
70
+ return result.stdout;
59
71
  }
60
72
  async validateHealth(endpoint, deadlineAt) {
61
73
  let response;
@@ -1 +1 @@
1
- {"version":3,"file":"opencode-cli-service.js","sourceRoot":"","sources":["../../src/workspaces/opencode-cli-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile as nodeExecFile } from \"node:child_process\"\nimport { Service, type Endpoint } from \"@opencode-ai/client/service\"\n\nimport { assertLoopbackServiceUrl } from \"./service-state\"\nimport type { OpenCodeServiceLifecycle } from \"./opencode-service\"\nimport type { SpawnSpec } from \"./spawn\"\n\nexport const MAX_SERVICE_OUTPUT_BYTES = 64 * 1024\nconst MAX_ERROR_CHARS = 1_024\n\nexport interface ServiceExecOptions {\n encoding: \"utf8\"\n maxBuffer: number\n shell: false\n timeout: number\n windowsHide: true\n cwd?: string\n env?: NodeJS.ProcessEnv\n windowsVerbatimArguments?: boolean\n}\n\nexport interface ServiceExecResult {\n stdout: string\n stderr: string\n}\n\nexport interface OpenCodeCliServiceDependencies {\n execFile: (file: string, args: string[], options: ServiceExecOptions) => Promise<ServiceExecResult>\n fetch: typeof globalThis.fetch\n}\n\nexport interface OpenCodeCliServiceOptions {\n label: string\n timeoutMs: number\n command: (args: string[], start: boolean) => SpawnSpec\n beforeHealth?: (endpoint: Endpoint, deadlineAt: number) => Promise<void>\n unreachableMessage?: (url: string) => string\n}\n\nexport class OpenCodeCliService implements OpenCodeServiceLifecycle {\n private readonly dependencies: OpenCodeCliServiceDependencies\n private readonly timeoutMs: number\n\n constructor(\n private readonly options: OpenCodeCliServiceOptions,\n dependencies: Partial<OpenCodeCliServiceDependencies> = {},\n ) {\n this.timeoutMs = Math.max(1, options.timeoutMs)\n this.dependencies = { execFile: executeFile, fetch: globalThis.fetch, ...dependencies }\n }\n\n async discover(deadlineAt = Date.now() + this.timeoutMs): Promise<Endpoint | undefined> {\n const status = this.singleLine(await this.run([\"service\", \"status\"], false, deadlineAt), \"status\")\n if (status === \"stopped\") return undefined\n return this.endpoint(status, deadlineAt)\n }\n\n async ensure(deadlineAt = Date.now() + this.timeoutMs): Promise<Endpoint> {\n const url = this.singleLine(await this.run([\"service\", \"start\"], true, deadlineAt), \"start\")\n return this.endpoint(url, deadlineAt)\n }\n\n private async endpoint(value: string, deadlineAt: number): Promise<Endpoint> {\n const url = this.assertServiceUrl(value)\n const password = this.singleLine(\n await this.run([\"service\", \"get\", \"password\"], false, deadlineAt),\n \"password\",\n )\n if (!password) throw new Error(`${this.options.label} OpenCode service returned an empty password`)\n const endpoint: Endpoint = { url, auth: { type: \"basic\", username: \"opencode\", password } }\n await this.options.beforeHealth?.(endpoint, deadlineAt)\n await this.validateHealth(endpoint, deadlineAt)\n return endpoint\n }\n\n private async run(args: string[], start: boolean, deadlineAt: number): Promise<string> {\n const commandLabel = args.join(\" \")\n const timeout = this.remaining(deadlineAt, commandLabel)\n const spec = this.options.command(args, start)\n const options: ServiceExecOptions = {\n encoding: \"utf8\",\n maxBuffer: MAX_SERVICE_OUTPUT_BYTES,\n shell: false,\n timeout,\n windowsHide: true,\n ...(spec.cwd ? { cwd: spec.cwd } : {}),\n ...(spec.env ? { env: spec.env } : {}),\n ...(spec.options.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),\n }\n try {\n const result = await this.withDeadline(\n this.dependencies.execFile(spec.command, spec.args, options),\n deadlineAt,\n commandLabel,\n )\n return result.stdout\n } catch (error) {\n if (start || commandLabel === \"service get password\") {\n const operation = start ? \"start\" : \"password retrieval\"\n const code = safeNumericExecCode(error)\n throw new Error(\n `${this.options.label} OpenCode ${operation} failed${code === undefined ? \"\" : ` (exit code ${code})`}`,\n )\n }\n const detail = boundedExecError(error)\n throw new Error(`${this.options.label} OpenCode ${commandLabel} failed${detail ? `: ${detail}` : \"\"}`)\n }\n }\n\n private async validateHealth(endpoint: Endpoint, deadlineAt: number): Promise<void> {\n let response: Response\n try {\n const timeout = this.remaining(deadlineAt, \"health validation\")\n response = await this.withDeadline(this.dependencies.fetch(new URL(\"/api/health\", endpoint.url), {\n headers: Service.headers(endpoint),\n signal: AbortSignal.timeout(timeout),\n }), deadlineAt, \"health validation\")\n } catch {\n const message = this.options.unreachableMessage?.(endpoint.url)\n throw new Error(message ?? `Cannot reach the ${this.options.label} OpenCode service at ${endpoint.url}`)\n }\n if (response.status === 401) {\n throw new Error(`${this.options.label} OpenCode service authentication failed at ${endpoint.url} (HTTP 401)`)\n }\n if (!response.ok) {\n throw new Error(`${this.options.label} OpenCode service health check failed at ${endpoint.url} (HTTP ${response.status})`)\n }\n\n let health: unknown\n try {\n const body = await this.withDeadline(\n readBoundedBody(response, MAX_SERVICE_OUTPUT_BYTES),\n deadlineAt,\n \"health response\",\n )\n health = JSON.parse(body)\n } catch {\n throw new Error(`${this.options.label} OpenCode service returned an invalid health response at ${endpoint.url}`)\n }\n const value = health as { healthy?: unknown; version?: unknown; pid?: unknown } | null\n if (\n !value\n || typeof value !== \"object\"\n || value.healthy !== true\n || typeof value.version !== \"string\"\n || !value.version.trim()\n || !Number.isSafeInteger(value.pid)\n || Number(value.pid) <= 0\n ) {\n throw new Error(`${this.options.label} OpenCode service returned an invalid health response at ${endpoint.url}`)\n }\n }\n\n private assertServiceUrl(value: string): string {\n let url: URL\n let wildcard = false\n try {\n wildcard = new URL(value).hostname === \"0.0.0.0\"\n url = assertLoopbackServiceUrl(value)\n } catch {\n throw new Error(`${this.options.label} OpenCode service returned an invalid loopback URL`)\n }\n if (/[^\\S\\r\\n]|[\\x00-\\x1f\\x7f]/.test(value) || url.username || url.password || url.pathname !== \"/\" || url.search || url.hash) {\n throw new Error(`${this.options.label} OpenCode service returned an invalid loopback URL`)\n }\n return wildcard ? url.toString() : value\n }\n\n private singleLine(value: string, label: string): string {\n const line = value.endsWith(\"\\r\\n\") ? value.slice(0, -2)\n : value.endsWith(\"\\n\") || value.endsWith(\"\\r\") ? value.slice(0, -1)\n : value\n if (line.includes(\"\\n\") || line.includes(\"\\r\")) {\n throw new Error(`${this.options.label} OpenCode service returned multiline ${label} output`)\n }\n if (line !== line.trim()) {\n throw new Error(`${this.options.label} OpenCode service returned malformed ${label} output`)\n }\n return line\n }\n\n private remaining(deadlineAt: number, label: string): number {\n const remaining = deadlineAt - Date.now()\n if (remaining <= 0) {\n throw new Error(`${this.options.label} OpenCode ${label} timed out after ${this.timeoutMs}ms`)\n }\n return remaining\n }\n\n private async withDeadline<T>(operation: Promise<T>, deadlineAt: number, label: string): Promise<T> {\n const timeoutMs = this.remaining(deadlineAt, label)\n let timer: NodeJS.Timeout | undefined\n try {\n return await Promise.race([\n operation,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error(`${this.options.label} OpenCode ${label} timed out after ${this.timeoutMs}ms`)),\n timeoutMs,\n )\n }),\n ])\n } finally {\n if (timer) clearTimeout(timer)\n }\n }\n}\n\nfunction executeFile(file: string, args: string[], options: ServiceExecOptions): Promise<ServiceExecResult> {\n return new Promise((resolve, reject) => {\n nodeExecFile(file, args, options, (error, stdout, stderr) => {\n if (error) {\n Object.assign(error, { stdout, stderr })\n reject(error)\n } else {\n resolve({ stdout, stderr })\n }\n })\n })\n}\n\nfunction boundedExecError(error: unknown): string {\n if (!error || typeof error !== \"object\") return clip(String(error))\n const value = error as { code?: unknown; message?: unknown; stdout?: unknown; stderr?: unknown }\n return clip([\n value.code === undefined ? \"\" : `code ${clip(String(value.code))}`,\n value.message === undefined ? \"\" : clip(String(value.message)),\n value.stderr === undefined ? \"\" : clip(String(value.stderr).trim()),\n value.stdout === undefined ? \"\" : clip(String(value.stdout).trim()),\n ].filter(Boolean).join(\": \"))\n}\n\nfunction safeNumericExecCode(error: unknown): number | undefined {\n if (!error || typeof error !== \"object\") return undefined\n const code = (error as { code?: unknown }).code\n return typeof code === \"number\" && Number.isSafeInteger(code) ? code : undefined\n}\n\nasync function readBoundedBody(response: Response, maxBytes: number): Promise<string> {\n if (!response.body) return \"\"\n const reader = response.body.getReader()\n const chunks: Uint8Array[] = []\n let total = 0\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n if (total + value.byteLength > maxBytes) {\n await reader.cancel().catch(() => undefined)\n throw new Error(\"Response body exceeds limit\")\n }\n chunks.push(value)\n total += value.byteLength\n }\n } catch (error) {\n await reader.cancel().catch(() => undefined)\n throw error\n } finally {\n reader.releaseLock()\n }\n return Buffer.concat(chunks, total).toString(\"utf8\")\n}\n\nfunction clip(value: string): string {\n return value.length <= MAX_ERROR_CHARS ? value : `${value.slice(0, MAX_ERROR_CHARS)}...`\n}\n"]}
1
+ {"version":3,"file":"opencode-cli-service.js","sourceRoot":"","sources":["../../src/workspaces/opencode-cli-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,EAAE,OAAO,EAAiB,MAAM,6BAA6B,CAAA;AAEpE,OAAO,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAA;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,mCAAmC,EAAE,MAAM,8BAA8B,CAAA;AAIlF,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,GAAG,IAAI,CAAA;AACjD,MAAM,eAAe,GAAG,KAAK,CAAA;AA+B7B,MAAM,OAAO,kBAAkB;IAI7B,YACmB,OAAkC,EACnD,eAAwD,EAAE;QADzC,YAAO,GAAP,OAAO,CAA2B;QAGnD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;QAC/C,IAAI,CAAC,YAAY,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,GAAG,YAAY,EAAE,CAAA;IACzF,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS;QACrD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,EAAE,QAAQ,CAAC,CAAA;QAClG,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QAC1C,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAC1C,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS;QACnD,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,EAAE,OAAO,CAAC,CAAA;QAC5F,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,CAAA;IACvC,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAC,KAAa,EAAE,UAAkB;QACtD,MAAM,GAAG,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAA;QACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAC9B,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,UAAU,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,EACjE,UAAU,CACX,CAAA;QACD,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,8CAA8C,CAAC,CAAA;QACnG,MAAM,QAAQ,GAAa,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,CAAA;QAC3F,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;QACvD,MAAM,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;QAC/C,OAAO,QAAQ,CAAA;IACjB,CAAC;IAEO,KAAK,CAAC,GAAG,CAAC,IAAc,EAAE,KAAc,EAAE,UAAkB;QAClE,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;QACxD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QAC9C,MAAM,OAAO,GAAuB;YAClC,QAAQ,EAAE,MAAM;YAChB,SAAS,EAAE,wBAAwB;YACnC,KAAK,EAAE,KAAK;YACZ,OAAO;YACP,WAAW,EAAE,IAAI;YACjB,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,wBAAwB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACrF,CAAA;QACD,IAAI,MAAyB,CAAA;QAC7B,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAC9B,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,EAC5D,UAAU,EACV,YAAY,CACb,CAAA;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAC/C,CAAC,CAAC,KAA+C;gBACjD,CAAC,CAAC,EAAE,CAAA;YACN,IAAI,mCAAmC,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBACtE,MAAM,IAAI,KAAK,CAAC,GAAG,+BAA+B,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,4DAA4D,CAAC,CAAA;YACxI,CAAC;YACD,IAAI,KAAK,IAAI,YAAY,KAAK,sBAAsB,EAAE,CAAC;gBACrD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAA;gBACxD,MAAM,IAAI,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAA;gBACvC,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,aAAa,SAAS,UAAU,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,IAAI,GAAG,EAAE,CACxG,CAAA;YACH,CAAC;YACD,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAA;YACtC,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,aAAa,YAAY,UAAU,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;QACxG,CAAC;QACD,IAAI,mCAAmC,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YACtE,MAAM,IAAI,KAAK,CAAC,GAAG,+BAA+B,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,4DAA4D,CAAC,CAAA;QACxI,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAA;IACtB,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,QAAkB,EAAE,UAAkB;QACjE,IAAI,QAAkB,CAAA;QACtB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAA;YAC/D,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,aAAa,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE;gBAC/F,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;gBAClC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC;aACrC,CAAC,EAAE,UAAU,EAAE,mBAAmB,CAAC,CAAA;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;YAC/D,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,oBAAoB,IAAI,CAAC,OAAO,CAAC,KAAK,wBAAwB,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAA;QAC1G,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,8CAA8C,QAAQ,CAAC,GAAG,aAAa,CAAC,CAAA;QAC/G,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,4CAA4C,QAAQ,CAAC,GAAG,UAAU,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAA;QAC5H,CAAC;QAED,IAAI,MAAe,CAAA;QACnB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAClC,eAAe,CAAC,QAAQ,EAAE,wBAAwB,CAAC,EACnD,UAAU,EACV,iBAAiB,CAClB,CAAA;YACD,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,4DAA4D,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAA;QAClH,CAAC;QACD,MAAM,KAAK,GAAG,MAAwE,CAAA;QACtF,IACE,CAAC,KAAK;eACH,OAAO,KAAK,KAAK,QAAQ;eACzB,KAAK,CAAC,OAAO,KAAK,IAAI;eACtB,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;eACjC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE;eACrB,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC;eAChC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EACzB,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,4DAA4D,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAA;QAClH,CAAC;IACH,CAAC;IAEO,gBAAgB,CAAC,KAAa;QACpC,IAAI,GAAQ,CAAA;QACZ,IAAI,QAAQ,GAAG,KAAK,CAAA;QACpB,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAA;YAChD,GAAG,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAA;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,oDAAoD,CAAC,CAAA;QAC5F,CAAC;QACD,IAAI,2BAA2B,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9H,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,oDAAoD,CAAC,CAAA;QAC5F,CAAC;QACD,OAAO,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;IAC1C,CAAC;IAEO,UAAU,CAAC,KAAa,EAAE,KAAa;QAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACtD,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACjE,CAAC,CAAC,KAAK,CAAA;QACX,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,wCAAwC,KAAK,SAAS,CAAC,CAAA;QAC9F,CAAC;QACD,IAAI,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,wCAAwC,KAAK,SAAS,CAAC,CAAA;QAC9F,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAEO,SAAS,CAAC,UAAkB,EAAE,KAAa;QACjD,MAAM,SAAS,GAAG,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACzC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,aAAa,KAAK,oBAAoB,IAAI,CAAC,SAAS,IAAI,CAAC,CAAA;QAChG,CAAC;QACD,OAAO,SAAS,CAAA;IAClB,CAAC;IAEO,KAAK,CAAC,YAAY,CAAI,SAAqB,EAAE,UAAkB,EAAE,KAAa;QACpF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;QACnD,IAAI,KAAiC,CAAA;QACrC,IAAI,CAAC;YACH,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC;gBACxB,SAAS;gBACT,IAAI,OAAO,CAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;oBACtC,KAAK,GAAG,UAAU,CAChB,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,aAAa,KAAK,oBAAoB,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,EACtG,SAAS,CACV,CAAA;gBACH,CAAC,CAAC;aACH,CAAC,CAAA;QACJ,CAAC;gBAAS,CAAC;YACT,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAA;QAChC,CAAC;IACH,CAAC;CACF;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,IAAc,EAAE,OAA2B;IAC5E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YAC1D,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;gBACxC,MAAM,CAAC,KAAK,CAAC,CAAA;YACf,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;YAC7B,CAAC;QACH,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAc;IACtC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;IACnE,MAAM,KAAK,GAAG,KAAkF,CAAA;IAChG,OAAO,IAAI,CAAC;QACV,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE;QAClE,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9D,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACnE,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;KACpE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;AAC/B,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAc;IACzC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IACzD,MAAM,IAAI,GAAI,KAA4B,CAAC,IAAI,CAAA;IAC/C,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;AAClF,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,QAAkB,EAAE,QAAgB;IACjE,IAAI,CAAC,QAAQ,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IAC7B,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAA;IACxC,MAAM,MAAM,GAAiB,EAAE,CAAA;IAC/B,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;YAC3C,IAAI,IAAI;gBAAE,MAAK;YACf,IAAI,KAAK,GAAG,KAAK,CAAC,UAAU,GAAG,QAAQ,EAAE,CAAC;gBACxC,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;gBAC5C,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;YAChD,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAClB,KAAK,IAAI,KAAK,CAAC,UAAU,CAAA;QAC3B,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QAC5C,MAAM,KAAK,CAAA;IACb,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,WAAW,EAAE,CAAA;IACtB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AACtD,CAAC;AAED,SAAS,IAAI,CAAC,KAAa;IACzB,OAAO,KAAK,CAAC,MAAM,IAAI,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,KAAK,CAAA;AAC1F,CAAC","sourcesContent":["import { execFile as nodeExecFile } from \"node:child_process\"\nimport { Service, type Endpoint } from \"@opencode-ai/client/service\"\n\nimport { OPENCODE_V2_REQUIRED_ERROR_CODE } from \"../api-types\"\nimport { assertLoopbackServiceUrl } from \"./service-state\"\nimport { isOpenCodeServiceCommandUnavailable } from \"./opencode-cli-compatibility\"\nimport type { OpenCodeServiceLifecycle } from \"./opencode-service\"\nimport type { SpawnSpec } from \"./spawn\"\n\nexport const MAX_SERVICE_OUTPUT_BYTES = 64 * 1024\nconst MAX_ERROR_CHARS = 1_024\n\nexport interface ServiceExecOptions {\n encoding: \"utf8\"\n maxBuffer: number\n shell: false\n timeout: number\n windowsHide: true\n cwd?: string\n env?: NodeJS.ProcessEnv\n windowsVerbatimArguments?: boolean\n}\n\nexport interface ServiceExecResult {\n stdout: string\n stderr: string\n}\n\nexport interface OpenCodeCliServiceDependencies {\n execFile: (file: string, args: string[], options: ServiceExecOptions) => Promise<ServiceExecResult>\n fetch: typeof globalThis.fetch\n}\n\nexport interface OpenCodeCliServiceOptions {\n label: string\n timeoutMs: number\n command: (args: string[], start: boolean) => SpawnSpec\n beforeHealth?: (endpoint: Endpoint, deadlineAt: number) => Promise<void>\n unreachableMessage?: (url: string) => string\n}\n\nexport class OpenCodeCliService implements OpenCodeServiceLifecycle {\n private readonly dependencies: OpenCodeCliServiceDependencies\n private readonly timeoutMs: number\n\n constructor(\n private readonly options: OpenCodeCliServiceOptions,\n dependencies: Partial<OpenCodeCliServiceDependencies> = {},\n ) {\n this.timeoutMs = Math.max(1, options.timeoutMs)\n this.dependencies = { execFile: executeFile, fetch: globalThis.fetch, ...dependencies }\n }\n\n async discover(deadlineAt = Date.now() + this.timeoutMs): Promise<Endpoint | undefined> {\n const status = this.singleLine(await this.run([\"service\", \"status\"], false, deadlineAt), \"status\")\n if (status === \"stopped\") return undefined\n return this.endpoint(status, deadlineAt)\n }\n\n async ensure(deadlineAt = Date.now() + this.timeoutMs): Promise<Endpoint> {\n const url = this.singleLine(await this.run([\"service\", \"start\"], true, deadlineAt), \"start\")\n return this.endpoint(url, deadlineAt)\n }\n\n private async endpoint(value: string, deadlineAt: number): Promise<Endpoint> {\n const url = this.assertServiceUrl(value)\n const password = this.singleLine(\n await this.run([\"service\", \"get\", \"password\"], false, deadlineAt),\n \"password\",\n )\n if (!password) throw new Error(`${this.options.label} OpenCode service returned an empty password`)\n const endpoint: Endpoint = { url, auth: { type: \"basic\", username: \"opencode\", password } }\n await this.options.beforeHealth?.(endpoint, deadlineAt)\n await this.validateHealth(endpoint, deadlineAt)\n return endpoint\n }\n\n private async run(args: string[], start: boolean, deadlineAt: number): Promise<string> {\n const commandLabel = args.join(\" \")\n const timeout = this.remaining(deadlineAt, commandLabel)\n const spec = this.options.command(args, start)\n const options: ServiceExecOptions = {\n encoding: \"utf8\",\n maxBuffer: MAX_SERVICE_OUTPUT_BYTES,\n shell: false,\n timeout,\n windowsHide: true,\n ...(spec.cwd ? { cwd: spec.cwd } : {}),\n ...(spec.env ? { env: spec.env } : {}),\n ...(spec.options.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),\n }\n let result: ServiceExecResult\n try {\n result = await this.withDeadline(\n this.dependencies.execFile(spec.command, spec.args, options),\n deadlineAt,\n commandLabel,\n )\n } catch (error) {\n const output = error && typeof error === \"object\"\n ? error as { stdout?: unknown; stderr?: unknown }\n : {}\n if (isOpenCodeServiceCommandUnavailable(output.stdout, output.stderr)) {\n throw new Error(`${OPENCODE_V2_REQUIRED_ERROR_CODE}: ${this.options.label} binary does not support the OpenCode V2 service lifecycle`)\n }\n if (start || commandLabel === \"service get password\") {\n const operation = start ? \"start\" : \"password retrieval\"\n const code = safeNumericExecCode(error)\n throw new Error(\n `${this.options.label} OpenCode ${operation} failed${code === undefined ? \"\" : ` (exit code ${code})`}`,\n )\n }\n const detail = boundedExecError(error)\n throw new Error(`${this.options.label} OpenCode ${commandLabel} failed${detail ? `: ${detail}` : \"\"}`)\n }\n if (isOpenCodeServiceCommandUnavailable(result.stdout, result.stderr)) {\n throw new Error(`${OPENCODE_V2_REQUIRED_ERROR_CODE}: ${this.options.label} binary does not support the OpenCode V2 service lifecycle`)\n }\n return result.stdout\n }\n\n private async validateHealth(endpoint: Endpoint, deadlineAt: number): Promise<void> {\n let response: Response\n try {\n const timeout = this.remaining(deadlineAt, \"health validation\")\n response = await this.withDeadline(this.dependencies.fetch(new URL(\"/api/health\", endpoint.url), {\n headers: Service.headers(endpoint),\n signal: AbortSignal.timeout(timeout),\n }), deadlineAt, \"health validation\")\n } catch {\n const message = this.options.unreachableMessage?.(endpoint.url)\n throw new Error(message ?? `Cannot reach the ${this.options.label} OpenCode service at ${endpoint.url}`)\n }\n if (response.status === 401) {\n throw new Error(`${this.options.label} OpenCode service authentication failed at ${endpoint.url} (HTTP 401)`)\n }\n if (!response.ok) {\n throw new Error(`${this.options.label} OpenCode service health check failed at ${endpoint.url} (HTTP ${response.status})`)\n }\n\n let health: unknown\n try {\n const body = await this.withDeadline(\n readBoundedBody(response, MAX_SERVICE_OUTPUT_BYTES),\n deadlineAt,\n \"health response\",\n )\n health = JSON.parse(body)\n } catch {\n throw new Error(`${this.options.label} OpenCode service returned an invalid health response at ${endpoint.url}`)\n }\n const value = health as { healthy?: unknown; version?: unknown; pid?: unknown } | null\n if (\n !value\n || typeof value !== \"object\"\n || value.healthy !== true\n || typeof value.version !== \"string\"\n || !value.version.trim()\n || !Number.isSafeInteger(value.pid)\n || Number(value.pid) <= 0\n ) {\n throw new Error(`${this.options.label} OpenCode service returned an invalid health response at ${endpoint.url}`)\n }\n }\n\n private assertServiceUrl(value: string): string {\n let url: URL\n let wildcard = false\n try {\n wildcard = new URL(value).hostname === \"0.0.0.0\"\n url = assertLoopbackServiceUrl(value)\n } catch {\n throw new Error(`${this.options.label} OpenCode service returned an invalid loopback URL`)\n }\n if (/[^\\S\\r\\n]|[\\x00-\\x1f\\x7f]/.test(value) || url.username || url.password || url.pathname !== \"/\" || url.search || url.hash) {\n throw new Error(`${this.options.label} OpenCode service returned an invalid loopback URL`)\n }\n return wildcard ? url.toString() : value\n }\n\n private singleLine(value: string, label: string): string {\n const line = value.endsWith(\"\\r\\n\") ? value.slice(0, -2)\n : value.endsWith(\"\\n\") || value.endsWith(\"\\r\") ? value.slice(0, -1)\n : value\n if (line.includes(\"\\n\") || line.includes(\"\\r\")) {\n throw new Error(`${this.options.label} OpenCode service returned multiline ${label} output`)\n }\n if (line !== line.trim()) {\n throw new Error(`${this.options.label} OpenCode service returned malformed ${label} output`)\n }\n return line\n }\n\n private remaining(deadlineAt: number, label: string): number {\n const remaining = deadlineAt - Date.now()\n if (remaining <= 0) {\n throw new Error(`${this.options.label} OpenCode ${label} timed out after ${this.timeoutMs}ms`)\n }\n return remaining\n }\n\n private async withDeadline<T>(operation: Promise<T>, deadlineAt: number, label: string): Promise<T> {\n const timeoutMs = this.remaining(deadlineAt, label)\n let timer: NodeJS.Timeout | undefined\n try {\n return await Promise.race([\n operation,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error(`${this.options.label} OpenCode ${label} timed out after ${this.timeoutMs}ms`)),\n timeoutMs,\n )\n }),\n ])\n } finally {\n if (timer) clearTimeout(timer)\n }\n }\n}\n\nfunction executeFile(file: string, args: string[], options: ServiceExecOptions): Promise<ServiceExecResult> {\n return new Promise((resolve, reject) => {\n nodeExecFile(file, args, options, (error, stdout, stderr) => {\n if (error) {\n Object.assign(error, { stdout, stderr })\n reject(error)\n } else {\n resolve({ stdout, stderr })\n }\n })\n })\n}\n\nfunction boundedExecError(error: unknown): string {\n if (!error || typeof error !== \"object\") return clip(String(error))\n const value = error as { code?: unknown; message?: unknown; stdout?: unknown; stderr?: unknown }\n return clip([\n value.code === undefined ? \"\" : `code ${clip(String(value.code))}`,\n value.message === undefined ? \"\" : clip(String(value.message)),\n value.stderr === undefined ? \"\" : clip(String(value.stderr).trim()),\n value.stdout === undefined ? \"\" : clip(String(value.stdout).trim()),\n ].filter(Boolean).join(\": \"))\n}\n\nfunction safeNumericExecCode(error: unknown): number | undefined {\n if (!error || typeof error !== \"object\") return undefined\n const code = (error as { code?: unknown }).code\n return typeof code === \"number\" && Number.isSafeInteger(code) ? code : undefined\n}\n\nasync function readBoundedBody(response: Response, maxBytes: number): Promise<string> {\n if (!response.body) return \"\"\n const reader = response.body.getReader()\n const chunks: Uint8Array[] = []\n let total = 0\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n if (total + value.byteLength > maxBytes) {\n await reader.cancel().catch(() => undefined)\n throw new Error(\"Response body exceeds limit\")\n }\n chunks.push(value)\n total += value.byteLength\n }\n } catch (error) {\n await reader.cancel().catch(() => undefined)\n throw error\n } finally {\n reader.releaseLock()\n }\n return Buffer.concat(chunks, total).toString(\"utf8\")\n}\n\nfunction clip(value: string): string {\n return value.length <= MAX_ERROR_CHARS ? value : `${value.slice(0, MAX_ERROR_CHARS)}...`\n}\n"]}
@@ -1,6 +1,8 @@
1
1
  import { execFile, spawnSync } from "child_process";
2
2
  import { readFileSync, statSync } from "fs";
3
3
  import path from "path";
4
+ import { OPENCODE_V2_REQUIRED_ERROR_CODE } from "../api-types";
5
+ import { isOpenCodeServiceCommandUnavailable, isOpenCodeServiceHelp } from "./opencode-cli-compatibility";
4
6
  export const WINDOWS_CMD_EXTENSIONS = new Set([".cmd", ".bat"]);
5
7
  export const WINDOWS_POWERSHELL_EXTENSIONS = new Set([".ps1"]);
6
8
  const VERSION_REGEX = /([0-9]+\.[0-9]+\.[0-9A-Za-z.-]+)/;
@@ -113,49 +115,110 @@ export function buildServiceLaunchSpec(binaryPath, options = {}) {
113
115
  return { kind: "wsl", distro: wslPath.distro, binary: wslPath.linuxPath };
114
116
  return { kind: "host", binary: binaryPath, platform };
115
117
  }
116
- export function probeBinaryVersion(binaryPath) {
118
+ export function probeBinaryVersion(binaryPath, execute = executeBinaryProbe) {
117
119
  if (!binaryPath) {
118
120
  return { valid: false, error: "Missing binary path" };
119
121
  }
120
122
  try {
121
123
  const spec = buildSpawnSpec(binaryPath, ["--version"]);
122
- const result = spawnSync(spec.command, spec.args, {
123
- encoding: "utf8",
124
- cwd: spec.cwd,
125
- env: spec.env,
126
- windowsVerbatimArguments: Boolean(spec.options.windowsVerbatimArguments),
127
- });
128
- if (result.error) {
129
- return { valid: false, error: result.error.message };
130
- }
131
- if (result.status !== 0) {
132
- const stderr = result.stderr?.trim();
133
- const stdout = result.stdout?.trim();
134
- const combined = stderr || stdout;
135
- const error = combined ? `Exited with code ${result.status}: ${combined}` : `Exited with code ${result.status}`;
136
- return { valid: false, error };
124
+ const result = execute(spec);
125
+ return parseBinaryVersion(result);
126
+ }
127
+ catch (error) {
128
+ return { valid: false, error: error instanceof Error ? error.message : String(error) };
129
+ }
130
+ }
131
+ function parseBinaryVersion(result) {
132
+ if (result.error) {
133
+ return { valid: false, error: result.error.message };
134
+ }
135
+ if (result.status !== 0) {
136
+ const stderr = result.stderr?.trim();
137
+ const stdout = result.stdout?.trim();
138
+ const combined = stderr || stdout;
139
+ const error = combined ? `Exited with code ${result.status}: ${combined}` : `Exited with code ${result.status}`;
140
+ return { valid: false, error };
141
+ }
142
+ const stdoutLines = String(result.stdout ?? "")
143
+ .split(/\r?\n/)
144
+ .map((line) => line.trim())
145
+ .filter((line) => line.length > 0);
146
+ const stderrLines = String(result.stderr ?? "")
147
+ .split(/\r?\n/)
148
+ .map((line) => line.trim())
149
+ .filter((line) => line.length > 0);
150
+ // Prefer stdout; fall back to stderr (some tools report version there).
151
+ const reported = stdoutLines[0] ?? stderrLines[0];
152
+ if (!reported) {
153
+ return { valid: true };
154
+ }
155
+ const versionMatch = reported.match(VERSION_REGEX);
156
+ const version = versionMatch?.[1];
157
+ return { valid: true, version, reported };
158
+ }
159
+ export async function probeOpenCodeBinary(binaryPath, execute = executeAsyncBinaryProbe) {
160
+ if (!binaryPath)
161
+ return { valid: false, error: "Missing binary path" };
162
+ try {
163
+ // Neither probe starts a daemon, and both are asynchronous and bounded so
164
+ // opening settings cannot stall the shared backend's requests/event stream.
165
+ const version = parseBinaryVersion(await execute(buildSpawnSpec(binaryPath, ["--version"]), 5_000));
166
+ if (!version.valid)
167
+ return { valid: false, ...(version.error ? { error: version.error } : {}) };
168
+ const versionResult = version.version ? { version: version.version } : {};
169
+ const result = await execute(buildSpawnSpec(binaryPath, ["service", "--help"]), 5_000);
170
+ if (result.error)
171
+ return { valid: false, ...versionResult, error: result.error.message };
172
+ if (isOpenCodeServiceCommandUnavailable(result.stdout, result.stderr)) {
173
+ return { valid: false, ...versionResult, errorCode: OPENCODE_V2_REQUIRED_ERROR_CODE };
137
174
  }
138
- const stdoutLines = String(result.stdout ?? "")
139
- .split(/\r?\n/)
140
- .map((line) => line.trim())
141
- .filter((line) => line.length > 0);
142
- const stderrLines = String(result.stderr ?? "")
143
- .split(/\r?\n/)
144
- .map((line) => line.trim())
145
- .filter((line) => line.length > 0);
146
- // Prefer stdout; fall back to stderr (some tools report version there).
147
- const reported = stdoutLines[0] ?? stderrLines[0];
148
- if (!reported) {
149
- return { valid: true };
175
+ if (result.status === 0) {
176
+ return isOpenCodeServiceHelp(result.stdout, result.stderr)
177
+ ? { valid: true, ...versionResult }
178
+ : { valid: false, ...versionResult };
150
179
  }
151
- const versionMatch = reported.match(VERSION_REGEX);
152
- const version = versionMatch?.[1];
153
- return { valid: true, version, reported };
180
+ const detail = String(result.stderr ?? "").trim() || String(result.stdout ?? "").trim();
181
+ const suffix = detail ? `: ${detail.slice(0, 1_024)}` : "";
182
+ return { valid: false, ...versionResult, error: `OpenCode service probe exited with code ${result.status}${suffix}` };
154
183
  }
155
184
  catch (error) {
156
185
  return { valid: false, error: error instanceof Error ? error.message : String(error) };
157
186
  }
158
187
  }
188
+ function executeAsyncBinaryProbe(spec, timeoutMs) {
189
+ return new Promise((resolve) => {
190
+ execFile(spec.command, spec.args, {
191
+ encoding: "utf8",
192
+ cwd: spec.cwd,
193
+ env: spec.env,
194
+ timeout: timeoutMs,
195
+ maxBuffer: 64 * 1024,
196
+ windowsHide: true,
197
+ shell: false,
198
+ windowsVerbatimArguments: Boolean(spec.options.windowsVerbatimArguments),
199
+ }, (error, stdout, stderr) => resolve({
200
+ status: error ? (typeof error.code === "number" ? error.code : null) : 0,
201
+ ...(error && typeof error.code !== "number" ? { error } : {}),
202
+ stdout,
203
+ stderr,
204
+ }));
205
+ });
206
+ }
207
+ function executeBinaryProbe(spec, timeoutMs) {
208
+ const result = spawnSync(spec.command, spec.args, {
209
+ encoding: "utf8",
210
+ cwd: spec.cwd,
211
+ env: spec.env,
212
+ timeout: timeoutMs,
213
+ windowsVerbatimArguments: Boolean(spec.options.windowsVerbatimArguments),
214
+ });
215
+ return {
216
+ status: result.status,
217
+ ...(result.error ? { error: result.error } : {}),
218
+ ...(result.stdout !== null ? { stdout: result.stdout } : {}),
219
+ ...(result.stderr !== null ? { stderr: result.stderr } : {}),
220
+ };
221
+ }
159
222
  function buildWslSpawnSpec(wslPath, args, options) {
160
223
  const workingDirectory = options.cwd ? resolveWslWorkingDirectory(options.cwd, wslPath.distro) : undefined;
161
224
  if (options.cwd && !workingDirectory) {