@neuralnomads/codenomad-dev 0.20.0-dev-20260909-855d2ee7 → 0.20.0-dev-20260911-ea37f97b

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 (177) hide show
  1. package/dist/api-types.js.map +1 -1
  2. package/dist/index.js +42 -31
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.test.js +9 -1
  5. package/dist/index.test.js.map +1 -1
  6. package/dist/opencode/pruning-installation.js +65 -0
  7. package/dist/opencode/pruning-installation.js.map +1 -0
  8. package/dist/opencode/pruning-installation.test.js +101 -0
  9. package/dist/opencode/pruning-installation.test.js.map +1 -0
  10. package/dist/opencode/pruning-lifecycle.js +66 -0
  11. package/dist/opencode/pruning-lifecycle.js.map +1 -0
  12. package/dist/opencode/session-pruning/claim-fence.js +33 -0
  13. package/dist/opencode/session-pruning/claim-fence.js.map +1 -0
  14. package/dist/opencode/session-pruning/claim-fence.test.js +104 -0
  15. package/dist/opencode/session-pruning/claim-fence.test.js.map +1 -0
  16. package/dist/opencode/session-pruning/contract.js +40 -0
  17. package/dist/opencode/session-pruning/contract.js.map +1 -0
  18. package/dist/opencode/session-pruning/database-path.js +17 -0
  19. package/dist/opencode/session-pruning/database-path.js.map +1 -0
  20. package/dist/opencode/session-pruning/database-path.test.js +22 -0
  21. package/dist/opencode/session-pruning/database-path.test.js.map +1 -0
  22. package/dist/opencode/session-pruning/desktop-plugin.js +14 -0
  23. package/dist/opencode/session-pruning/desktop-plugin.js.map +1 -0
  24. package/dist/opencode/session-pruning/index.js +2 -0
  25. package/dist/opencode/session-pruning/index.js.map +1 -0
  26. package/dist/opencode/session-pruning/isolated-store.js +18 -0
  27. package/dist/opencode/session-pruning/isolated-store.js.map +1 -0
  28. package/dist/opencode/session-pruning/planner.js +36 -0
  29. package/dist/opencode/session-pruning/planner.js.map +1 -0
  30. package/dist/opencode/session-pruning/plugin.js +45 -0
  31. package/dist/opencode/session-pruning/plugin.js.map +1 -0
  32. package/dist/opencode/session-pruning/presence.js +42 -0
  33. package/dist/opencode/session-pruning/presence.js.map +1 -0
  34. package/dist/opencode/session-pruning/preview-store.js +21 -0
  35. package/dist/opencode/session-pruning/preview-store.js.map +1 -0
  36. package/dist/opencode/session-pruning/pruning.test.js +172 -0
  37. package/dist/opencode/session-pruning/pruning.test.js.map +1 -0
  38. package/dist/opencode/session-pruning/revision.js +18 -0
  39. package/dist/opencode/session-pruning/revision.js.map +1 -0
  40. package/dist/opencode/session-pruning/service.js +56 -0
  41. package/dist/opencode/session-pruning/service.js.map +1 -0
  42. package/dist/opencode/session-pruning/service.test.js +74 -0
  43. package/dist/opencode/session-pruning/service.test.js.map +1 -0
  44. package/dist/opencode/session-pruning/storage-path.js +6 -0
  45. package/dist/opencode/session-pruning/storage-path.js.map +1 -0
  46. package/dist/opencode/session-pruning/transaction.js +53 -0
  47. package/dist/opencode/session-pruning/transaction.js.map +1 -0
  48. package/dist/opencode/session-pruning/tui-reload.js +84 -0
  49. package/dist/opencode/session-pruning/tui-reload.js.map +1 -0
  50. package/dist/opencode/session-pruning/tui-reload.native.js +269 -0
  51. package/dist/opencode/session-pruning/tui-reload.native.js.map +1 -0
  52. package/dist/opencode/session-pruning/tui-reload.test.js +71 -0
  53. package/dist/opencode/session-pruning/tui-reload.test.js.map +1 -0
  54. package/dist/opencode/session-pruning/tui.js +12 -0
  55. package/dist/opencode/session-pruning/tui.js.map +1 -0
  56. package/dist/plugins/session-pruning/plugin.mjs +26559 -0
  57. package/dist/server/__tests__/instance-proxy.test.js +10 -5
  58. package/dist/server/__tests__/instance-proxy.test.js.map +1 -1
  59. package/dist/server/__tests__/listener-base-url.test.js +26 -1
  60. package/dist/server/__tests__/listener-base-url.test.js.map +1 -1
  61. package/dist/server/__tests__/network-addresses.test.js +40 -0
  62. package/dist/server/__tests__/network-addresses.test.js.map +1 -1
  63. package/dist/server/__tests__/network-host.test.js +34 -0
  64. package/dist/server/__tests__/network-host.test.js.map +1 -0
  65. package/dist/server/__tests__/tls-ipv6.test.js +54 -0
  66. package/dist/server/__tests__/tls-ipv6.test.js.map +1 -0
  67. package/dist/server/http-server.js +8 -6
  68. package/dist/server/http-server.js.map +1 -1
  69. package/dist/server/listener-base-url.js +12 -3
  70. package/dist/server/listener-base-url.js.map +1 -1
  71. package/dist/server/network-addresses.js +20 -42
  72. package/dist/server/network-addresses.js.map +1 -1
  73. package/dist/server/network-host.js +62 -0
  74. package/dist/server/network-host.js.map +1 -0
  75. package/dist/server/routes/meta.js +2 -4
  76. package/dist/server/routes/meta.js.map +1 -1
  77. package/dist/server/routes/session-pruning.js +45 -0
  78. package/dist/server/routes/session-pruning.js.map +1 -0
  79. package/dist/server/routes/session-pruning.test.js +98 -0
  80. package/dist/server/routes/session-pruning.test.js.map +1 -0
  81. package/dist/server/tls.js +52 -32
  82. package/dist/server/tls.js.map +1 -1
  83. package/dist/workspaces/manager.js +1 -0
  84. package/dist/workspaces/manager.js.map +1 -1
  85. package/package.json +6 -3
  86. package/public/assets/App-0-FcHzPS.js +44 -0
  87. package/public/assets/App-0-FcHzPS.js.map +1 -0
  88. package/public/assets/FilesTab-CRpGDs1B.js +3 -0
  89. package/public/assets/{FilesTab-z7UrjcAQ.js.map → FilesTab-CRpGDs1B.js.map} +1 -1
  90. package/public/assets/{GitChangesTab-C04bYKhs.js → GitChangesTab-DxFebTiq.js} +3 -3
  91. package/public/assets/{GitChangesTab-C04bYKhs.js.map → GitChangesTab-DxFebTiq.js.map} +1 -1
  92. package/public/assets/{SplitFilePanel-B29MqCuU.js → SplitFilePanel-DgTMrrO-.js} +2 -2
  93. package/public/assets/{SplitFilePanel-B29MqCuU.js.map → SplitFilePanel-DgTMrrO-.js.map} +1 -1
  94. package/public/assets/{StatusTab-wUKfwnFf.js → StatusTab-LKUA9cbH.js} +2 -2
  95. package/public/assets/{StatusTab-wUKfwnFf.js.map → StatusTab-LKUA9cbH.js.map} +1 -1
  96. package/public/assets/{align-justify-glU678D9.js → align-justify-v_yPXSys.js} +2 -2
  97. package/public/assets/{align-justify-glU678D9.js.map → align-justify-v_yPXSys.js.map} +1 -1
  98. package/public/assets/{bundle-full-BrsCElYv.js → bundle-full-HzL_Jtqk.js} +2 -2
  99. package/public/assets/{bundle-full-BrsCElYv.js.map → bundle-full-HzL_Jtqk.js.map} +1 -1
  100. package/public/assets/{client-state-j9Iu7F-E.js → client-state-CF-PJ0GG.js} +2 -2
  101. package/public/assets/{client-state-j9Iu7F-E.js.map → client-state-CF-PJ0GG.js.map} +1 -1
  102. package/public/assets/{diff-viewer-B0VS1voD.js → diff-viewer-BFDQBDVy.js} +2 -2
  103. package/public/assets/{diff-viewer-B0VS1voD.js.map → diff-viewer-BFDQBDVy.js.map} +1 -1
  104. package/public/assets/index-8iPlm2r0.js +2 -0
  105. package/public/assets/index-8iPlm2r0.js.map +1 -0
  106. package/public/assets/index-B185mC7m.js +2 -0
  107. package/public/assets/index-B185mC7m.js.map +1 -0
  108. package/public/assets/index-Bbj1xF8i.js +2 -0
  109. package/public/assets/index-Bbj1xF8i.js.map +1 -0
  110. package/public/assets/index-BsmFN3k1.js +2 -0
  111. package/public/assets/index-BsmFN3k1.js.map +1 -0
  112. package/public/assets/index-Bz_OQJYA.css +1 -0
  113. package/public/assets/index-C52CfqK3.js +2 -0
  114. package/public/assets/index-C52CfqK3.js.map +1 -0
  115. package/public/assets/index-CHYmKl10.js +2 -0
  116. package/public/assets/index-CHYmKl10.js.map +1 -0
  117. package/public/assets/index-CTKYD_VJ.js +2 -0
  118. package/public/assets/index-CTKYD_VJ.js.map +1 -0
  119. package/public/assets/index-D2UHSOG5.js +2 -0
  120. package/public/assets/{index-ClaHfu-1.js.map → index-D2UHSOG5.js.map} +1 -1
  121. package/public/assets/index-DDoB_Z36.js +2 -0
  122. package/public/assets/index-DDoB_Z36.js.map +1 -0
  123. package/public/assets/{index-BzAEHHap.js → index-DcwAtZw-.js} +3 -3
  124. package/public/assets/index-DcwAtZw-.js.map +1 -0
  125. package/public/assets/index-DxUD3RYr.js +2 -0
  126. package/public/assets/index-DxUD3RYr.js.map +1 -0
  127. package/public/assets/{index-B6bX3AMT.js → index-RK9gf0uk.js} +2 -2
  128. package/public/assets/{index-B6bX3AMT.js.map → index-RK9gf0uk.js.map} +1 -1
  129. package/public/assets/{loading-CYiiwb08.js → loading-BSB82snY.js} +2 -2
  130. package/public/assets/{loading-CYiiwb08.js.map → loading-BSB82snY.js.map} +1 -1
  131. package/public/assets/main-CZztwrfv.js +48 -0
  132. package/public/assets/main-CZztwrfv.js.map +1 -0
  133. package/public/assets/{markdown-DWvSdCZz.js → markdown-DtidoL2Z.js} +4 -4
  134. package/public/assets/{markdown-DWvSdCZz.js.map → markdown-DtidoL2Z.js.map} +1 -1
  135. package/public/assets/{monaco-viewer-Cc9bWg7I.js → monaco-viewer-CpxDfB-6.js} +2 -2
  136. package/public/assets/monaco-viewer-CpxDfB-6.js.map +1 -0
  137. package/public/assets/{startup-state-settings-card-2KHpBL8U.js → startup-state-settings-card-BY741Rt0.js} +2 -2
  138. package/public/assets/{startup-state-settings-card-2KHpBL8U.js.map → startup-state-settings-card-BY741Rt0.js.map} +1 -1
  139. package/public/assets/{terminal-square-lzHuj_of.js → terminal-square-BdC_jwm3.js} +2 -2
  140. package/public/assets/{terminal-square-lzHuj_of.js.map → terminal-square-BdC_jwm3.js.map} +1 -1
  141. package/public/assets/{tool-call-DjkszeT4.js → tool-call-DbtCsJ4x.js} +10 -10
  142. package/public/assets/{tool-call-DjkszeT4.js.map → tool-call-DbtCsJ4x.js.map} +1 -1
  143. package/public/assets/{unified-picker-5JmIM6a0.js → unified-picker-C0eo1chJ.js} +2 -2
  144. package/public/assets/{unified-picker-5JmIM6a0.js.map → unified-picker-C0eo1chJ.js.map} +1 -1
  145. package/public/assets/{wrap-text-N0Cav2Vp.js → wrap-text-BuMtdvMx.js} +2 -2
  146. package/public/assets/{wrap-text-N0Cav2Vp.js.map → wrap-text-BuMtdvMx.js.map} +1 -1
  147. package/public/index.html +4 -4
  148. package/public/loading.html +4 -4
  149. package/public/sw.js +1 -1
  150. package/public/sw.js.map +1 -1
  151. package/public/assets/App-Nlt9zV-v.js +0 -43
  152. package/public/assets/App-Nlt9zV-v.js.map +0 -1
  153. package/public/assets/FilesTab-z7UrjcAQ.js +0 -3
  154. package/public/assets/index-BIRLnDWY.js +0 -2
  155. package/public/assets/index-BIRLnDWY.js.map +0 -1
  156. package/public/assets/index-BuScpzpP.js +0 -2
  157. package/public/assets/index-BuScpzpP.js.map +0 -1
  158. package/public/assets/index-BzAEHHap.js.map +0 -1
  159. package/public/assets/index-C-DZt-oz.css +0 -1
  160. package/public/assets/index-C9RXVQl9.js +0 -2
  161. package/public/assets/index-C9RXVQl9.js.map +0 -1
  162. package/public/assets/index-ClaHfu-1.js +0 -2
  163. package/public/assets/index-DBx5htn3.js +0 -2
  164. package/public/assets/index-DBx5htn3.js.map +0 -1
  165. package/public/assets/index-DHHMf3u1.js +0 -2
  166. package/public/assets/index-DHHMf3u1.js.map +0 -1
  167. package/public/assets/index-DN4Kstsd.js +0 -2
  168. package/public/assets/index-DN4Kstsd.js.map +0 -1
  169. package/public/assets/index-DbMyNDyo.js +0 -2
  170. package/public/assets/index-DbMyNDyo.js.map +0 -1
  171. package/public/assets/index-rcsmuqww.js +0 -2
  172. package/public/assets/index-rcsmuqww.js.map +0 -1
  173. package/public/assets/index-uz2QFRlk.js +0 -2
  174. package/public/assets/index-uz2QFRlk.js.map +0 -1
  175. package/public/assets/main-OmXs7qtG.js +0 -48
  176. package/public/assets/main-OmXs7qtG.js.map +0 -1
  177. package/public/assets/monaco-viewer-Cc9bWg7I.js.map +0 -1
@@ -0,0 +1,34 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { formatHostForUrl, isLoopbackHost, isWildcardHost, normalizeNetworkHost } from "../network-host";
4
+ describe("network host helpers", () => {
5
+ it("recognizes IPv4 and IPv6 wildcard forms", () => {
6
+ assert.equal(isWildcardHost("0.0.0.0"), true);
7
+ assert.equal(isWildcardHost("::"), true);
8
+ assert.equal(isWildcardHost("0:0:0:0:0:0:0:0"), true);
9
+ assert.equal(isWildcardHost("::ffff:0:0"), true);
10
+ assert.equal(isWildcardHost("::1"), false);
11
+ });
12
+ it("recognizes concrete IPv4 and IPv6 loopback forms", () => {
13
+ assert.equal(isLoopbackHost("localhost"), true);
14
+ assert.equal(isLoopbackHost("127.0.0.2"), true);
15
+ assert.equal(isLoopbackHost("::1"), true);
16
+ assert.equal(isLoopbackHost("::0001"), true);
17
+ assert.equal(isLoopbackHost("0:0:0:0:0:0:0:1"), true);
18
+ assert.equal(isLoopbackHost("0:0:0:0:0:0:0:0001"), true);
19
+ assert.equal(isLoopbackHost("::ffff:7f00:1"), true);
20
+ assert.equal(isLoopbackHost("2001:db8::1"), false);
21
+ });
22
+ it("brackets IPv6 literals for URLs", () => {
23
+ assert.equal(formatHostForUrl("192.168.1.20"), "192.168.1.20");
24
+ assert.equal(formatHostForUrl("::1"), "[::1]");
25
+ assert.equal(formatHostForUrl("[2001:db8::20]"), "[2001:db8::20]");
26
+ });
27
+ it("normalizes IPv4-mapped addresses and internationalized DNS names", () => {
28
+ assert.equal(normalizeNetworkHost("::ffff:0:0"), "0.0.0.0");
29
+ assert.equal(normalizeNetworkHost("::ffff:7f00:1"), "127.0.0.1");
30
+ assert.equal(normalizeNetworkHost("::ffff:192.168.1.20"), "192.168.1.20");
31
+ assert.equal(normalizeNetworkHost("münchen.local"), "xn--mnchen-3ya.local");
32
+ });
33
+ });
34
+ //# sourceMappingURL=network-host.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network-host.test.js","sourceRoot":"","sources":["../../../src/server/__tests__/network-host.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAExG,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAA;QAC7C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;QACxC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,iBAAiB,CAAC,EAAE,IAAI,CAAC,CAAA;QACrD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE,IAAI,CAAC,CAAA;QAChD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC,CAAA;QAC/C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC,CAAA;QAC/C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAA;QACzC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAA;QAC5C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,iBAAiB,CAAC,EAAE,IAAI,CAAC,CAAA;QACrD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,oBAAoB,CAAC,EAAE,IAAI,CAAC,CAAA;QACxD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,IAAI,CAAC,CAAA;QACnD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC,CAAA;QAC9D,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAA;QAC9C,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC,CAAA;IACpE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,YAAY,CAAC,EAAE,SAAS,CAAC,CAAA;QAC3D,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,eAAe,CAAC,EAAE,WAAW,CAAC,CAAA;QAChE,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,qBAAqB,CAAC,EAAE,cAAc,CAAC,CAAA;QACzE,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,CAAA;IAC7E,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import assert from \"node:assert/strict\"\nimport { describe, it } from \"node:test\"\n\nimport { formatHostForUrl, isLoopbackHost, isWildcardHost, normalizeNetworkHost } from \"../network-host\"\n\ndescribe(\"network host helpers\", () => {\n it(\"recognizes IPv4 and IPv6 wildcard forms\", () => {\n assert.equal(isWildcardHost(\"0.0.0.0\"), true)\n assert.equal(isWildcardHost(\"::\"), true)\n assert.equal(isWildcardHost(\"0:0:0:0:0:0:0:0\"), true)\n assert.equal(isWildcardHost(\"::ffff:0:0\"), true)\n assert.equal(isWildcardHost(\"::1\"), false)\n })\n\n it(\"recognizes concrete IPv4 and IPv6 loopback forms\", () => {\n assert.equal(isLoopbackHost(\"localhost\"), true)\n assert.equal(isLoopbackHost(\"127.0.0.2\"), true)\n assert.equal(isLoopbackHost(\"::1\"), true)\n assert.equal(isLoopbackHost(\"::0001\"), true)\n assert.equal(isLoopbackHost(\"0:0:0:0:0:0:0:1\"), true)\n assert.equal(isLoopbackHost(\"0:0:0:0:0:0:0:0001\"), true)\n assert.equal(isLoopbackHost(\"::ffff:7f00:1\"), true)\n assert.equal(isLoopbackHost(\"2001:db8::1\"), false)\n })\n\n it(\"brackets IPv6 literals for URLs\", () => {\n assert.equal(formatHostForUrl(\"192.168.1.20\"), \"192.168.1.20\")\n assert.equal(formatHostForUrl(\"::1\"), \"[::1]\")\n assert.equal(formatHostForUrl(\"[2001:db8::20]\"), \"[2001:db8::20]\")\n })\n\n it(\"normalizes IPv4-mapped addresses and internationalized DNS names\", () => {\n assert.equal(normalizeNetworkHost(\"::ffff:0:0\"), \"0.0.0.0\")\n assert.equal(normalizeNetworkHost(\"::ffff:7f00:1\"), \"127.0.0.1\")\n assert.equal(normalizeNetworkHost(\"::ffff:192.168.1.20\"), \"192.168.1.20\")\n assert.equal(normalizeNetworkHost(\"münchen.local\"), \"xn--mnchen-3ya.local\")\n })\n})\n"]}
@@ -0,0 +1,54 @@
1
+ import assert from "node:assert/strict";
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { describe, it } from "node:test";
7
+ import { resolveHttpsOptions } from "../tls";
8
+ const logger = { info() { }, warn() { }, error() { }, child() { return logger; } };
9
+ describe("generated IPv6 certificates", () => {
10
+ it("includes a concrete IPv6 address SAN", () => withTempConfig((configDir) => {
11
+ const resolved = resolveHttpsOptions({ enabled: true, configDir, host: "2001:db8::20", logger });
12
+ assert.ok(resolved);
13
+ const certificate = new crypto.X509Certificate(resolved.httpsOptions.cert);
14
+ assert.match(certificate.subjectAltName ?? "", /IP Address:2001:DB8:0:0:0:0:0:20/i);
15
+ }));
16
+ it("includes IPv6 loopback for a fresh wildcard certificate", () => withTempConfig((configDir) => {
17
+ const resolved = resolveHttpsOptions({ enabled: true, configDir, host: "::", logger });
18
+ assert.ok(resolved);
19
+ const certificate = new crypto.X509Certificate(resolved.httpsOptions.cert);
20
+ assert.equal(certificate.checkIP("::1"), "::1");
21
+ }));
22
+ it("rotates a reused certificate when required host and configured SANs change", () => withTempConfig((configDir) => {
23
+ const initial = resolveHttpsOptions({ enabled: true, configDir, host: "127.0.0.1", logger });
24
+ assert.ok(initial);
25
+ const initialCertificate = new crypto.X509Certificate(initial.httpsOptions.cert);
26
+ const rotated = resolveHttpsOptions({ enabled: true, configDir, host: "::", tlsSANs: "diagnostics.local", logger });
27
+ assert.ok(rotated);
28
+ const rotatedCertificate = new crypto.X509Certificate(rotated.httpsOptions.cert);
29
+ assert.notEqual(rotatedCertificate.fingerprint256, initialCertificate.fingerprint256);
30
+ assert.equal(rotatedCertificate.checkIP("::1"), "::1");
31
+ assert.equal(rotatedCertificate.checkHost("diagnostics.local"), "diagnostics.local");
32
+ }));
33
+ it("reuses certificates for normalized IDN and scoped IPv6 SAN values", () => withTempConfig((configDir) => {
34
+ const first = resolveHttpsOptions({ enabled: true, configDir, host: "münchen.local", tlsSANs: "fe80::1%12", logger });
35
+ assert.ok(first);
36
+ const firstCertificate = new crypto.X509Certificate(first.httpsOptions.cert);
37
+ const reused = resolveHttpsOptions({ enabled: true, configDir, host: "münchen.local", tlsSANs: "fe80::1%12", logger });
38
+ assert.ok(reused);
39
+ const reusedCertificate = new crypto.X509Certificate(reused.httpsOptions.cert);
40
+ assert.equal(reusedCertificate.fingerprint256, firstCertificate.fingerprint256);
41
+ assert.equal(reusedCertificate.checkHost("xn--mnchen-3ya.local"), "xn--mnchen-3ya.local");
42
+ assert.equal(reusedCertificate.checkIP("fe80::1"), "fe80::1");
43
+ }));
44
+ });
45
+ function withTempConfig(callback) {
46
+ const configDir = fs.mkdtempSync(path.join(os.tmpdir(), "codenomad-tls-ipv6-"));
47
+ try {
48
+ callback(configDir);
49
+ }
50
+ finally {
51
+ fs.rmSync(configDir, { recursive: true, force: true });
52
+ }
53
+ }
54
+ //# sourceMappingURL=tls-ipv6.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tls-ipv6.test.js","sourceRoot":"","sources":["../../../src/server/__tests__/tls-ipv6.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\"\nimport crypto from \"node:crypto\"\nimport fs from \"node:fs\"\nimport os from \"node:os\"\nimport path from \"node:path\"\nimport { describe, it } from \"node:test\"\n\nimport type { Logger } from \"../../logger\"\nimport { resolveHttpsOptions } from \"../tls\"\n\nconst logger = { info() {}, warn() {}, error() {}, child() { return logger } } as unknown as Logger\n\ndescribe(\"generated IPv6 certificates\", () => {\n it(\"includes a concrete IPv6 address SAN\", () => withTempConfig((configDir) => {\n const resolved = resolveHttpsOptions({ enabled: true, configDir, host: \"2001:db8::20\", logger })\n assert.ok(resolved)\n const certificate = new crypto.X509Certificate(resolved.httpsOptions.cert)\n assert.match(certificate.subjectAltName ?? \"\", /IP Address:2001:DB8:0:0:0:0:0:20/i)\n }))\n\n it(\"includes IPv6 loopback for a fresh wildcard certificate\", () => withTempConfig((configDir) => {\n const resolved = resolveHttpsOptions({ enabled: true, configDir, host: \"::\", logger })\n assert.ok(resolved)\n const certificate = new crypto.X509Certificate(resolved.httpsOptions.cert)\n assert.equal(certificate.checkIP(\"::1\"), \"::1\")\n }))\n\n it(\"rotates a reused certificate when required host and configured SANs change\", () => withTempConfig((configDir) => {\n const initial = resolveHttpsOptions({ enabled: true, configDir, host: \"127.0.0.1\", logger })\n assert.ok(initial)\n const initialCertificate = new crypto.X509Certificate(initial.httpsOptions.cert)\n\n const rotated = resolveHttpsOptions({ enabled: true, configDir, host: \"::\", tlsSANs: \"diagnostics.local\", logger })\n assert.ok(rotated)\n const rotatedCertificate = new crypto.X509Certificate(rotated.httpsOptions.cert)\n\n assert.notEqual(rotatedCertificate.fingerprint256, initialCertificate.fingerprint256)\n assert.equal(rotatedCertificate.checkIP(\"::1\"), \"::1\")\n assert.equal(rotatedCertificate.checkHost(\"diagnostics.local\"), \"diagnostics.local\")\n }))\n\n it(\"reuses certificates for normalized IDN and scoped IPv6 SAN values\", () => withTempConfig((configDir) => {\n const first = resolveHttpsOptions({ enabled: true, configDir, host: \"münchen.local\", tlsSANs: \"fe80::1%12\", logger })\n assert.ok(first)\n const firstCertificate = new crypto.X509Certificate(first.httpsOptions.cert)\n\n const reused = resolveHttpsOptions({ enabled: true, configDir, host: \"münchen.local\", tlsSANs: \"fe80::1%12\", logger })\n assert.ok(reused)\n const reusedCertificate = new crypto.X509Certificate(reused.httpsOptions.cert)\n\n assert.equal(reusedCertificate.fingerprint256, firstCertificate.fingerprint256)\n assert.equal(reusedCertificate.checkHost(\"xn--mnchen-3ya.local\"), \"xn--mnchen-3ya.local\")\n assert.equal(reusedCertificate.checkIP(\"fe80::1\"), \"fe80::1\")\n }))\n})\n\nfunction withTempConfig(callback: (configDir: string) => void) {\n const configDir = fs.mkdtempSync(path.join(os.tmpdir(), \"codenomad-tls-ipv6-\"))\n try {\n callback(configDir)\n } finally {\n fs.rmSync(configDir, { recursive: true, force: true })\n }\n}\n"]}
@@ -3,7 +3,7 @@ import cors from "@fastify/cors";
3
3
  import fastifyStatic from "@fastify/static";
4
4
  import replyFrom from "@fastify/reply-from";
5
5
  import fs from "fs";
6
- import { connect as connectTcp } from "net";
6
+ import { connect as connectTcp, isIP } from "net";
7
7
  import path from "path";
8
8
  import { connect as connectTls } from "tls";
9
9
  import { fetch } from "undici";
@@ -16,6 +16,7 @@ import { registerMetaRoutes } from "./routes/meta";
16
16
  import { registerEventRoutes } from "./routes/events";
17
17
  import { registerStorageRoutes } from "./routes/storage";
18
18
  import { registerYoloRoutes } from "./routes/yolo";
19
+ import { registerSessionPruningRoutes } from "./routes/session-pruning";
19
20
  import { registerWorktreeRoutes } from "./routes/worktrees";
20
21
  import { registerSpeechRoutes } from "./routes/speech";
21
22
  import { registerOpenCodeUpdateRoutes } from "./routes/opencode-update";
@@ -31,6 +32,7 @@ import { createOpenCodeUpdateService } from "../opencode-update/service";
31
32
  import { WorktreeDeletionFence } from "../workspaces/worktree-session-evacuation";
32
33
  import { isAutomationPluginRequest, registerAutomationPluginRoute } from "./routes/automation-plugin";
33
34
  import { DeveloperCdp } from "../developer-cdp";
35
+ import { formatHostForUrl, isLoopbackHost, isWildcardHost, stripHostBrackets } from "./network-host";
34
36
  export function shouldRetryPreferredPort(error, autoPortRequested, platform = process.platform) {
35
37
  if (!autoPortRequested)
36
38
  return false;
@@ -86,7 +88,6 @@ export function createHttpServer(deps) {
86
88
  done();
87
89
  });
88
90
  const allowedDevOrigins = new Set(["http://localhost:3000", "http://127.0.0.1:3000"]);
89
- const isLoopbackHost = (host) => host === "127.0.0.1" || host === "::1" || host.startsWith("127.");
90
91
  const getSelfOrigins = () => {
91
92
  const origins = new Set();
92
93
  const candidates = [deps.serverMeta.localUrl, deps.serverMeta.remoteUrl];
@@ -130,7 +131,7 @@ export function createHttpServer(deps) {
130
131
  return;
131
132
  }
132
133
  // When we bind to a non-loopback host (e.g., 0.0.0.0 or LAN IP), allow cross-origin UI access.
133
- if (deps.bindHost === "0.0.0.0" || !isLoopbackHost(deps.bindHost)) {
134
+ if (isWildcardHost(deps.bindHost) || !isLoopbackHost(deps.bindHost)) {
134
135
  cb(null, true);
135
136
  return;
136
137
  }
@@ -265,6 +266,7 @@ export function createHttpServer(deps) {
265
266
  logger: proxyLogger,
266
267
  });
267
268
  registerYoloRoutes(app, { yoloManager: deps.yoloManager });
269
+ registerSessionPruningRoutes(app, { workspaceManager: deps.workspaceManager, worktreeDeletionFence });
268
270
  registerInstanceProxyRoutes(app, { workspaceManager: deps.workspaceManager, logger: proxyLogger, worktreeDeletionFence });
269
271
  if (deps.uiDevServerUrl) {
270
272
  setupDevProxy(app, deps.uiDevServerUrl, deps.authManager, deps.previewManager, proxyLogger);
@@ -276,7 +278,8 @@ export function createHttpServer(deps) {
276
278
  instance: app,
277
279
  start: async () => {
278
280
  const attemptListen = async (requestedPort) => {
279
- const addressInfo = await app.listen({ port: requestedPort, host: deps.bindHost });
281
+ const dualStackWildcard = isWildcardHost(deps.bindHost) && isIP(stripHostBrackets(deps.bindHost)) === 6;
282
+ const addressInfo = await app.listen({ port: requestedPort, host: deps.bindHost, ...(dualStackWildcard ? { ipv6Only: false } : {}) });
280
283
  return { addressInfo, requestedPort };
281
284
  };
282
285
  const autoPortRequested = deps.bindPort === 0;
@@ -308,7 +311,7 @@ export function createHttpServer(deps) {
308
311
  actualPort = address.port;
309
312
  }
310
313
  }
311
- const displayHost = deps.bindHost === "127.0.0.1" ? "localhost" : deps.bindHost;
314
+ const displayHost = deps.bindHost === "127.0.0.1" ? "localhost" : formatHostForUrl(deps.bindHost);
312
315
  const serverUrl = `${deps.protocol}://${displayHost}:${actualPort}`;
313
316
  deps.logger.info({ port: actualPort, host: deps.bindHost, protocol: deps.protocol }, "HTTP server listening");
314
317
  return { port: actualPort, url: serverUrl, displayHost };
@@ -1096,7 +1099,6 @@ function isAllowedInstanceApiRoute(method, pathname) {
1096
1099
  ["GET", /^\/api\/session(?:\/active)?$/],
1097
1100
  ["POST", /^\/api\/session(?:\/import)?$/],
1098
1101
  ["GET", /^\/api\/session\/[^/]+(?:\/message(?:\/[^/]+)?)?$/],
1099
- ["PATCH", /^\/api\/session\/[^/]+\/message\/[^/]+$/],
1100
1102
  ["GET", /^\/api\/session\/[^/]+\/inbox$/],
1101
1103
  ["GET", /^\/api\/session\/[^/]+\/(?:permission|form)$/],
1102
1104
  ["DELETE", /^\/api\/session\/[^/]+$/],