@neuralnomads/codenomad-dev 0.20.0-dev-20260908-e5115fe8 → 0.20.0-dev-20260910-abaf6eba

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 (121) hide show
  1. package/dist/index.js +29 -31
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.test.js +9 -1
  4. package/dist/index.test.js.map +1 -1
  5. package/dist/server/__tests__/instance-proxy.test.js +14 -0
  6. package/dist/server/__tests__/instance-proxy.test.js.map +1 -1
  7. package/dist/server/__tests__/listener-base-url.test.js +26 -1
  8. package/dist/server/__tests__/listener-base-url.test.js.map +1 -1
  9. package/dist/server/__tests__/network-addresses.test.js +40 -0
  10. package/dist/server/__tests__/network-addresses.test.js.map +1 -1
  11. package/dist/server/__tests__/network-host.test.js +34 -0
  12. package/dist/server/__tests__/network-host.test.js.map +1 -0
  13. package/dist/server/__tests__/tls-ipv6.test.js +54 -0
  14. package/dist/server/__tests__/tls-ipv6.test.js.map +1 -0
  15. package/dist/server/http-server.js +7 -6
  16. package/dist/server/http-server.js.map +1 -1
  17. package/dist/server/listener-base-url.js +12 -3
  18. package/dist/server/listener-base-url.js.map +1 -1
  19. package/dist/server/network-addresses.js +20 -42
  20. package/dist/server/network-addresses.js.map +1 -1
  21. package/dist/server/network-host.js +62 -0
  22. package/dist/server/network-host.js.map +1 -0
  23. package/dist/server/routes/meta.js +2 -4
  24. package/dist/server/routes/meta.js.map +1 -1
  25. package/dist/server/tls.js +52 -32
  26. package/dist/server/tls.js.map +1 -1
  27. package/package.json +1 -1
  28. package/public/assets/App-0ECD8eRb.js +43 -0
  29. package/public/assets/App-0ECD8eRb.js.map +1 -0
  30. package/public/assets/FilesTab-BVJawmU7.js +3 -0
  31. package/public/assets/{FilesTab-C8fnWsM4.js.map → FilesTab-BVJawmU7.js.map} +1 -1
  32. package/public/assets/{GitChangesTab-C3rapWwl.js → GitChangesTab-BVdfbJ_l.js} +3 -3
  33. package/public/assets/{GitChangesTab-C3rapWwl.js.map → GitChangesTab-BVdfbJ_l.js.map} +1 -1
  34. package/public/assets/{SplitFilePanel-D3S7Zfcc.js → SplitFilePanel-CSR5-RHY.js} +2 -2
  35. package/public/assets/{SplitFilePanel-D3S7Zfcc.js.map → SplitFilePanel-CSR5-RHY.js.map} +1 -1
  36. package/public/assets/{StatusTab-DHvQ-0to.js → StatusTab-DIodfPQd.js} +2 -2
  37. package/public/assets/{StatusTab-DHvQ-0to.js.map → StatusTab-DIodfPQd.js.map} +1 -1
  38. package/public/assets/{align-justify-BIN0BnZw.js → align-justify-kDFhSvpm.js} +2 -2
  39. package/public/assets/{align-justify-BIN0BnZw.js.map → align-justify-kDFhSvpm.js.map} +1 -1
  40. package/public/assets/{bundle-full-BfEvraVO.js → bundle-full-B4eYITdG.js} +2 -2
  41. package/public/assets/{bundle-full-BfEvraVO.js.map → bundle-full-B4eYITdG.js.map} +1 -1
  42. package/public/assets/{client-state-CBKOWfhu.js → client-state-D3g-WH8W.js} +2 -2
  43. package/public/assets/{client-state-CBKOWfhu.js.map → client-state-D3g-WH8W.js.map} +1 -1
  44. package/public/assets/{diff-viewer-6oxyPi3-.js → diff-viewer-BLL0OcUK.js} +2 -2
  45. package/public/assets/{diff-viewer-6oxyPi3-.js.map → diff-viewer-BLL0OcUK.js.map} +1 -1
  46. package/public/assets/index-BIqxsbaj.js +2 -0
  47. package/public/assets/{index-C9znv1NB.js.map → index-BIqxsbaj.js.map} +1 -1
  48. package/public/assets/index-BtuH3zKP.js +2 -0
  49. package/public/assets/index-BtuH3zKP.js.map +1 -0
  50. package/public/assets/index-Bz_OQJYA.css +1 -0
  51. package/public/assets/{index-BhBg6Tnf.js → index-CeF-jpOV.js} +2 -2
  52. package/public/assets/{index-BhBg6Tnf.js.map → index-CeF-jpOV.js.map} +1 -1
  53. package/public/assets/index-CrOPzgHN.js +2 -0
  54. package/public/assets/index-CrOPzgHN.js.map +1 -0
  55. package/public/assets/index-D21P0GMO.js +2 -0
  56. package/public/assets/index-D21P0GMO.js.map +1 -0
  57. package/public/assets/index-DLRrUgI7.js +2 -0
  58. package/public/assets/index-DLRrUgI7.js.map +1 -0
  59. package/public/assets/index-DLtH2OSD.js +2 -0
  60. package/public/assets/index-DLtH2OSD.js.map +1 -0
  61. package/public/assets/index-DWnsOFes.js +2 -0
  62. package/public/assets/index-DWnsOFes.js.map +1 -0
  63. package/public/assets/index-DbfYo3i-.js +2 -0
  64. package/public/assets/index-DbfYo3i-.js.map +1 -0
  65. package/public/assets/index-DnqYBc2b.js +2 -0
  66. package/public/assets/index-DnqYBc2b.js.map +1 -0
  67. package/public/assets/index-ZnQhzm1X.js +2 -0
  68. package/public/assets/index-ZnQhzm1X.js.map +1 -0
  69. package/public/assets/index-e5yyN2yK.js +3 -0
  70. package/public/assets/index-e5yyN2yK.js.map +1 -0
  71. package/public/assets/{loading-CanSHf7z.js → loading-CaoeVfIV.js} +2 -2
  72. package/public/assets/{loading-CanSHf7z.js.map → loading-CaoeVfIV.js.map} +1 -1
  73. package/public/assets/main-CeMcnDRn.js +48 -0
  74. package/public/assets/main-CeMcnDRn.js.map +1 -0
  75. package/public/assets/{markdown-B04qkFuc.js → markdown-BeJ9YIFU.js} +4 -4
  76. package/public/assets/{markdown-B04qkFuc.js.map → markdown-BeJ9YIFU.js.map} +1 -1
  77. package/public/assets/{monaco-viewer-BB3_mBkA.js → monaco-viewer-Cc9bWg7I.js} +2 -2
  78. package/public/assets/{monaco-viewer-BB3_mBkA.js.map → monaco-viewer-Cc9bWg7I.js.map} +1 -1
  79. package/public/assets/{startup-state-settings-card-DE11-Je6.js → startup-state-settings-card-BvpJJ6ei.js} +2 -2
  80. package/public/assets/{startup-state-settings-card-DE11-Je6.js.map → startup-state-settings-card-BvpJJ6ei.js.map} +1 -1
  81. package/public/assets/{terminal-square-W8UL0Uzs.js → terminal-square-DBCSEAmh.js} +2 -2
  82. package/public/assets/{terminal-square-W8UL0Uzs.js.map → terminal-square-DBCSEAmh.js.map} +1 -1
  83. package/public/assets/tool-call-BJiSHSx7.js +60 -0
  84. package/public/assets/tool-call-BJiSHSx7.js.map +1 -0
  85. package/public/assets/{unified-picker-CkVc32ly.js → unified-picker-CAEyj3a0.js} +2 -2
  86. package/public/assets/{unified-picker-CkVc32ly.js.map → unified-picker-CAEyj3a0.js.map} +1 -1
  87. package/public/assets/{wrap-text-CR-cuiJP.js → wrap-text-DJNV0ypl.js} +2 -2
  88. package/public/assets/{wrap-text-CR-cuiJP.js.map → wrap-text-DJNV0ypl.js.map} +1 -1
  89. package/public/index.html +4 -4
  90. package/public/loading.html +4 -4
  91. package/public/sw.js +1 -1
  92. package/public/sw.js.map +1 -1
  93. package/public/assets/App-DPj4p3An.js +0 -43
  94. package/public/assets/App-DPj4p3An.js.map +0 -1
  95. package/public/assets/FilesTab-C8fnWsM4.js +0 -3
  96. package/public/assets/index-BJrZpSe6.js +0 -2
  97. package/public/assets/index-BJrZpSe6.js.map +0 -1
  98. package/public/assets/index-B_9dhJTl.js +0 -2
  99. package/public/assets/index-B_9dhJTl.js.map +0 -1
  100. package/public/assets/index-BeWVYvfJ.js +0 -2
  101. package/public/assets/index-BeWVYvfJ.js.map +0 -1
  102. package/public/assets/index-C9znv1NB.js +0 -2
  103. package/public/assets/index-CD9b6FoA.js +0 -2
  104. package/public/assets/index-CD9b6FoA.js.map +0 -1
  105. package/public/assets/index-CT42quXi.css +0 -1
  106. package/public/assets/index-CmJg6xjr.js +0 -2
  107. package/public/assets/index-CmJg6xjr.js.map +0 -1
  108. package/public/assets/index-DHNlQHw2.js +0 -2
  109. package/public/assets/index-DHNlQHw2.js.map +0 -1
  110. package/public/assets/index-DdklToXY.js +0 -2
  111. package/public/assets/index-DdklToXY.js.map +0 -1
  112. package/public/assets/index-Dr5L4afe.js +0 -2
  113. package/public/assets/index-Dr5L4afe.js.map +0 -1
  114. package/public/assets/index-DsmOPul3.js +0 -2
  115. package/public/assets/index-DsmOPul3.js.map +0 -1
  116. package/public/assets/index-ef9_CbWU.js +0 -3
  117. package/public/assets/index-ef9_CbWU.js.map +0 -1
  118. package/public/assets/main-COtTRZe6.js +0 -48
  119. package/public/assets/main-COtTRZe6.js.map +0 -1
  120. package/public/assets/tool-call-irc_6gLg.js +0 -60
  121. package/public/assets/tool-call-irc_6gLg.js.map +0 -1
@@ -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";
@@ -31,6 +31,7 @@ import { createOpenCodeUpdateService } from "../opencode-update/service";
31
31
  import { WorktreeDeletionFence } from "../workspaces/worktree-session-evacuation";
32
32
  import { isAutomationPluginRequest, registerAutomationPluginRoute } from "./routes/automation-plugin";
33
33
  import { DeveloperCdp } from "../developer-cdp";
34
+ import { formatHostForUrl, isLoopbackHost, isWildcardHost, stripHostBrackets } from "./network-host";
34
35
  export function shouldRetryPreferredPort(error, autoPortRequested, platform = process.platform) {
35
36
  if (!autoPortRequested)
36
37
  return false;
@@ -86,7 +87,6 @@ export function createHttpServer(deps) {
86
87
  done();
87
88
  });
88
89
  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
90
  const getSelfOrigins = () => {
91
91
  const origins = new Set();
92
92
  const candidates = [deps.serverMeta.localUrl, deps.serverMeta.remoteUrl];
@@ -130,7 +130,7 @@ export function createHttpServer(deps) {
130
130
  return;
131
131
  }
132
132
  // 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)) {
133
+ if (isWildcardHost(deps.bindHost) || !isLoopbackHost(deps.bindHost)) {
134
134
  cb(null, true);
135
135
  return;
136
136
  }
@@ -276,7 +276,8 @@ export function createHttpServer(deps) {
276
276
  instance: app,
277
277
  start: async () => {
278
278
  const attemptListen = async (requestedPort) => {
279
- const addressInfo = await app.listen({ port: requestedPort, host: deps.bindHost });
279
+ const dualStackWildcard = isWildcardHost(deps.bindHost) && isIP(stripHostBrackets(deps.bindHost)) === 6;
280
+ const addressInfo = await app.listen({ port: requestedPort, host: deps.bindHost, ...(dualStackWildcard ? { ipv6Only: false } : {}) });
280
281
  return { addressInfo, requestedPort };
281
282
  };
282
283
  const autoPortRequested = deps.bindPort === 0;
@@ -308,7 +309,7 @@ export function createHttpServer(deps) {
308
309
  actualPort = address.port;
309
310
  }
310
311
  }
311
- const displayHost = deps.bindHost === "127.0.0.1" ? "localhost" : deps.bindHost;
312
+ const displayHost = deps.bindHost === "127.0.0.1" ? "localhost" : formatHostForUrl(deps.bindHost);
312
313
  const serverUrl = `${deps.protocol}://${displayHost}:${actualPort}`;
313
314
  deps.logger.info({ port: actualPort, host: deps.bindHost, protocol: deps.protocol }, "HTTP server listening");
314
315
  return { port: actualPort, url: serverUrl, displayHost };
@@ -1101,7 +1102,7 @@ function isAllowedInstanceApiRoute(method, pathname) {
1101
1102
  ["GET", /^\/api\/session\/[^/]+\/(?:permission|form)$/],
1102
1103
  ["DELETE", /^\/api\/session\/[^/]+$/],
1103
1104
  ["DELETE", /^\/api\/session\/[^/]+\/inbox\/[^/]+$/],
1104
- ["POST", /^\/api\/session\/[^/]+\/(?:agent|model|rename|move|prompt|command|shell|compact|interrupt|background|fork)$/],
1105
+ ["POST", /^\/api\/session\/[^/]+\/(?:agent|model|rename|move|prompt|command|shell|compact|interrupt|wait|background|fork)$/],
1105
1106
  ["POST", /^\/api\/session\/[^/]+\/inbox\/[^/]+\/(?:steer|queue)$/],
1106
1107
  ["POST", /^\/api\/session\/[^/]+\/revert\/(?:stage|clear)$/],
1107
1108
  ["PUT", /^\/api\/session\/[^/]+\/instructions\/entries\/[^/]+$/],