@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.
- package/dist/index.js +29 -31
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +9 -1
- package/dist/index.test.js.map +1 -1
- package/dist/server/__tests__/instance-proxy.test.js +14 -0
- package/dist/server/__tests__/instance-proxy.test.js.map +1 -1
- package/dist/server/__tests__/listener-base-url.test.js +26 -1
- package/dist/server/__tests__/listener-base-url.test.js.map +1 -1
- package/dist/server/__tests__/network-addresses.test.js +40 -0
- package/dist/server/__tests__/network-addresses.test.js.map +1 -1
- package/dist/server/__tests__/network-host.test.js +34 -0
- package/dist/server/__tests__/network-host.test.js.map +1 -0
- package/dist/server/__tests__/tls-ipv6.test.js +54 -0
- package/dist/server/__tests__/tls-ipv6.test.js.map +1 -0
- package/dist/server/http-server.js +7 -6
- package/dist/server/http-server.js.map +1 -1
- package/dist/server/listener-base-url.js +12 -3
- package/dist/server/listener-base-url.js.map +1 -1
- package/dist/server/network-addresses.js +20 -42
- package/dist/server/network-addresses.js.map +1 -1
- package/dist/server/network-host.js +62 -0
- package/dist/server/network-host.js.map +1 -0
- package/dist/server/routes/meta.js +2 -4
- package/dist/server/routes/meta.js.map +1 -1
- package/dist/server/tls.js +52 -32
- package/dist/server/tls.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/App-0ECD8eRb.js +43 -0
- package/public/assets/App-0ECD8eRb.js.map +1 -0
- package/public/assets/FilesTab-BVJawmU7.js +3 -0
- package/public/assets/{FilesTab-C8fnWsM4.js.map → FilesTab-BVJawmU7.js.map} +1 -1
- package/public/assets/{GitChangesTab-C3rapWwl.js → GitChangesTab-BVdfbJ_l.js} +3 -3
- package/public/assets/{GitChangesTab-C3rapWwl.js.map → GitChangesTab-BVdfbJ_l.js.map} +1 -1
- package/public/assets/{SplitFilePanel-D3S7Zfcc.js → SplitFilePanel-CSR5-RHY.js} +2 -2
- package/public/assets/{SplitFilePanel-D3S7Zfcc.js.map → SplitFilePanel-CSR5-RHY.js.map} +1 -1
- package/public/assets/{StatusTab-DHvQ-0to.js → StatusTab-DIodfPQd.js} +2 -2
- package/public/assets/{StatusTab-DHvQ-0to.js.map → StatusTab-DIodfPQd.js.map} +1 -1
- package/public/assets/{align-justify-BIN0BnZw.js → align-justify-kDFhSvpm.js} +2 -2
- package/public/assets/{align-justify-BIN0BnZw.js.map → align-justify-kDFhSvpm.js.map} +1 -1
- package/public/assets/{bundle-full-BfEvraVO.js → bundle-full-B4eYITdG.js} +2 -2
- package/public/assets/{bundle-full-BfEvraVO.js.map → bundle-full-B4eYITdG.js.map} +1 -1
- package/public/assets/{client-state-CBKOWfhu.js → client-state-D3g-WH8W.js} +2 -2
- package/public/assets/{client-state-CBKOWfhu.js.map → client-state-D3g-WH8W.js.map} +1 -1
- package/public/assets/{diff-viewer-6oxyPi3-.js → diff-viewer-BLL0OcUK.js} +2 -2
- package/public/assets/{diff-viewer-6oxyPi3-.js.map → diff-viewer-BLL0OcUK.js.map} +1 -1
- package/public/assets/index-BIqxsbaj.js +2 -0
- package/public/assets/{index-C9znv1NB.js.map → index-BIqxsbaj.js.map} +1 -1
- package/public/assets/index-BtuH3zKP.js +2 -0
- package/public/assets/index-BtuH3zKP.js.map +1 -0
- package/public/assets/index-Bz_OQJYA.css +1 -0
- package/public/assets/{index-BhBg6Tnf.js → index-CeF-jpOV.js} +2 -2
- package/public/assets/{index-BhBg6Tnf.js.map → index-CeF-jpOV.js.map} +1 -1
- package/public/assets/index-CrOPzgHN.js +2 -0
- package/public/assets/index-CrOPzgHN.js.map +1 -0
- package/public/assets/index-D21P0GMO.js +2 -0
- package/public/assets/index-D21P0GMO.js.map +1 -0
- package/public/assets/index-DLRrUgI7.js +2 -0
- package/public/assets/index-DLRrUgI7.js.map +1 -0
- package/public/assets/index-DLtH2OSD.js +2 -0
- package/public/assets/index-DLtH2OSD.js.map +1 -0
- package/public/assets/index-DWnsOFes.js +2 -0
- package/public/assets/index-DWnsOFes.js.map +1 -0
- package/public/assets/index-DbfYo3i-.js +2 -0
- package/public/assets/index-DbfYo3i-.js.map +1 -0
- package/public/assets/index-DnqYBc2b.js +2 -0
- package/public/assets/index-DnqYBc2b.js.map +1 -0
- package/public/assets/index-ZnQhzm1X.js +2 -0
- package/public/assets/index-ZnQhzm1X.js.map +1 -0
- package/public/assets/index-e5yyN2yK.js +3 -0
- package/public/assets/index-e5yyN2yK.js.map +1 -0
- package/public/assets/{loading-CanSHf7z.js → loading-CaoeVfIV.js} +2 -2
- package/public/assets/{loading-CanSHf7z.js.map → loading-CaoeVfIV.js.map} +1 -1
- package/public/assets/main-CeMcnDRn.js +48 -0
- package/public/assets/main-CeMcnDRn.js.map +1 -0
- package/public/assets/{markdown-B04qkFuc.js → markdown-BeJ9YIFU.js} +4 -4
- package/public/assets/{markdown-B04qkFuc.js.map → markdown-BeJ9YIFU.js.map} +1 -1
- package/public/assets/{monaco-viewer-BB3_mBkA.js → monaco-viewer-Cc9bWg7I.js} +2 -2
- package/public/assets/{monaco-viewer-BB3_mBkA.js.map → monaco-viewer-Cc9bWg7I.js.map} +1 -1
- package/public/assets/{startup-state-settings-card-DE11-Je6.js → startup-state-settings-card-BvpJJ6ei.js} +2 -2
- package/public/assets/{startup-state-settings-card-DE11-Je6.js.map → startup-state-settings-card-BvpJJ6ei.js.map} +1 -1
- package/public/assets/{terminal-square-W8UL0Uzs.js → terminal-square-DBCSEAmh.js} +2 -2
- package/public/assets/{terminal-square-W8UL0Uzs.js.map → terminal-square-DBCSEAmh.js.map} +1 -1
- package/public/assets/tool-call-BJiSHSx7.js +60 -0
- package/public/assets/tool-call-BJiSHSx7.js.map +1 -0
- package/public/assets/{unified-picker-CkVc32ly.js → unified-picker-CAEyj3a0.js} +2 -2
- package/public/assets/{unified-picker-CkVc32ly.js.map → unified-picker-CAEyj3a0.js.map} +1 -1
- package/public/assets/{wrap-text-CR-cuiJP.js → wrap-text-DJNV0ypl.js} +2 -2
- package/public/assets/{wrap-text-CR-cuiJP.js.map → wrap-text-DJNV0ypl.js.map} +1 -1
- package/public/index.html +4 -4
- package/public/loading.html +4 -4
- package/public/sw.js +1 -1
- package/public/sw.js.map +1 -1
- package/public/assets/App-DPj4p3An.js +0 -43
- package/public/assets/App-DPj4p3An.js.map +0 -1
- package/public/assets/FilesTab-C8fnWsM4.js +0 -3
- package/public/assets/index-BJrZpSe6.js +0 -2
- package/public/assets/index-BJrZpSe6.js.map +0 -1
- package/public/assets/index-B_9dhJTl.js +0 -2
- package/public/assets/index-B_9dhJTl.js.map +0 -1
- package/public/assets/index-BeWVYvfJ.js +0 -2
- package/public/assets/index-BeWVYvfJ.js.map +0 -1
- package/public/assets/index-C9znv1NB.js +0 -2
- package/public/assets/index-CD9b6FoA.js +0 -2
- package/public/assets/index-CD9b6FoA.js.map +0 -1
- package/public/assets/index-CT42quXi.css +0 -1
- package/public/assets/index-CmJg6xjr.js +0 -2
- package/public/assets/index-CmJg6xjr.js.map +0 -1
- package/public/assets/index-DHNlQHw2.js +0 -2
- package/public/assets/index-DHNlQHw2.js.map +0 -1
- package/public/assets/index-DdklToXY.js +0 -2
- package/public/assets/index-DdklToXY.js.map +0 -1
- package/public/assets/index-Dr5L4afe.js +0 -2
- package/public/assets/index-Dr5L4afe.js.map +0 -1
- package/public/assets/index-DsmOPul3.js +0 -2
- package/public/assets/index-DsmOPul3.js.map +0 -1
- package/public/assets/index-ef9_CbWU.js +0 -3
- package/public/assets/index-ef9_CbWU.js.map +0 -1
- package/public/assets/main-COtTRZe6.js +0 -48
- package/public/assets/main-COtTRZe6.js.map +0 -1
- package/public/assets/tool-call-irc_6gLg.js +0 -60
- 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
|
|
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
|
|
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\/[^/]+$/],
|