@maintainer-pro/ai-bridge 0.1.20 → 0.1.21
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/package.json +2 -2
- package/src/daemon.mjs +57 -50
- package/src/share-rewrite.mjs +43 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maintainer-pro/ai-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "Local bridge daemon that pairs a machine to Maintainer Pro and configures multiple client sandboxes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"maintainer-pro",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"node": ">=22"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@maintainer-pro/ai-cli": "^0.1.
|
|
31
|
+
"@maintainer-pro/ai-cli": "^0.1.12",
|
|
32
32
|
"@maintainer-pro/ai-server": "^0.1.7"
|
|
33
33
|
}
|
|
34
34
|
}
|
package/src/daemon.mjs
CHANGED
|
@@ -257,7 +257,21 @@ function loadConfig() {
|
|
|
257
257
|
const file = configPath();
|
|
258
258
|
if (!fs.existsSync(file)) return null;
|
|
259
259
|
try {
|
|
260
|
-
|
|
260
|
+
const cfg = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
261
|
+
let dirty = false;
|
|
262
|
+
for (const ws of cfg.workspaces || []) {
|
|
263
|
+
const before = JSON.stringify(ws.hostApps || []);
|
|
264
|
+
const beforeProxy = JSON.stringify(ws.proxy || null);
|
|
265
|
+
stripAiServerFromWorkspace(ws);
|
|
266
|
+
if (
|
|
267
|
+
JSON.stringify(ws.hostApps || []) !== before ||
|
|
268
|
+
JSON.stringify(ws.proxy || null) !== beforeProxy
|
|
269
|
+
) {
|
|
270
|
+
dirty = true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (dirty) saveConfig(cfg);
|
|
274
|
+
return cfg;
|
|
261
275
|
} catch {
|
|
262
276
|
return null;
|
|
263
277
|
}
|
|
@@ -434,7 +448,7 @@ async function resolveWorkspaceHostApps(ws, opts = {}) {
|
|
|
434
448
|
const match = previous.find((row) => row && row.id === app.id);
|
|
435
449
|
return match ? { ...app, host: match.host === true || app.host === true } : app;
|
|
436
450
|
});
|
|
437
|
-
ws.hostApps = result.apps;
|
|
451
|
+
ws.hostApps = listedHostApps(result.apps);
|
|
438
452
|
if (opts.cfg) persistWorkspaceEntry(opts.cfg, ws);
|
|
439
453
|
const summary = result.apps
|
|
440
454
|
.map((app) => `${app.name}:${app.port}`)
|
|
@@ -455,19 +469,10 @@ async function resolveWorkspaceHostApps(ws, opts = {}) {
|
|
|
455
469
|
activity(
|
|
456
470
|
ws.sandboxId,
|
|
457
471
|
"warn",
|
|
458
|
-
`port detect failed (${message}) —
|
|
472
|
+
`port detect failed (${message}) — no host apps listed yet`
|
|
459
473
|
);
|
|
460
474
|
if (!Array.isArray(ws.hostApps) || !ws.hostApps.length) {
|
|
461
|
-
ws.hostApps = [
|
|
462
|
-
{
|
|
463
|
-
id: "ai-server",
|
|
464
|
-
name: "AI server",
|
|
465
|
-
role: "ai-server",
|
|
466
|
-
port: Number(ws.port) || 3100,
|
|
467
|
-
source: "default",
|
|
468
|
-
locked: true,
|
|
469
|
-
},
|
|
470
|
-
];
|
|
475
|
+
ws.hostApps = [];
|
|
471
476
|
}
|
|
472
477
|
return {
|
|
473
478
|
apps: ws.hostApps,
|
|
@@ -763,8 +768,31 @@ function isLocalAppUrl(url) {
|
|
|
763
768
|
}
|
|
764
769
|
}
|
|
765
770
|
|
|
771
|
+
function isAiServerApp(app) {
|
|
772
|
+
return Boolean(app && (app.role === "ai-server" || app.id === "ai-server"));
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function listedHostApps(apps) {
|
|
776
|
+
return (Array.isArray(apps) ? apps : []).filter((app) => !isAiServerApp(app));
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function stripAiServerFromWorkspace(ws) {
|
|
780
|
+
if (!ws || typeof ws !== "object") return ws;
|
|
781
|
+
if (Array.isArray(ws.hostApps)) ws.hostApps = listedHostApps(ws.hostApps);
|
|
782
|
+
if (ws.proxy && typeof ws.proxy === "object") {
|
|
783
|
+
if (ws.proxy.slugs && typeof ws.proxy.slugs === "object") {
|
|
784
|
+
delete ws.proxy.slugs["ai-server"];
|
|
785
|
+
}
|
|
786
|
+
if (ws.proxy.urls && typeof ws.proxy.urls === "object") {
|
|
787
|
+
delete ws.proxy.urls["ai-server"];
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return ws;
|
|
791
|
+
}
|
|
792
|
+
|
|
766
793
|
function persistWorkspaceEntry(cfg, ws) {
|
|
767
794
|
if (!cfg || !ws?.sandboxId) return;
|
|
795
|
+
stripAiServerFromWorkspace(ws);
|
|
768
796
|
cfg.workspaces = cfg.workspaces || [];
|
|
769
797
|
const index = cfg.workspaces.findIndex((row) => row.sandboxId === ws.sandboxId);
|
|
770
798
|
if (index >= 0) cfg.workspaces[index] = { ...cfg.workspaces[index], ...ws };
|
|
@@ -1001,16 +1029,7 @@ function probeEmbedConfig(url, timeoutMs = 2500) {
|
|
|
1001
1029
|
|
|
1002
1030
|
function sanitizeAiServerPort(ws) {
|
|
1003
1031
|
if (!ws || typeof ws !== "object") return;
|
|
1004
|
-
|
|
1005
|
-
for (const app of apps) {
|
|
1006
|
-
if (
|
|
1007
|
-
app &&
|
|
1008
|
-
(app.role === "ai-server" || app.id === "ai-server") &&
|
|
1009
|
-
isAdminListenPort(app.port)
|
|
1010
|
-
) {
|
|
1011
|
-
app.port = 3100;
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1032
|
+
if (Array.isArray(ws.hostApps)) ws.hostApps = listedHostApps(ws.hostApps);
|
|
1014
1033
|
if (isAdminListenPort(ws.port)) ws.port = 3100;
|
|
1015
1034
|
}
|
|
1016
1035
|
|
|
@@ -1304,10 +1323,6 @@ async function reconcileWorkspacePresence(ws, cfg, opts = {}) {
|
|
|
1304
1323
|
// 3. Host + CORS origins for Maintainer Pro
|
|
1305
1324
|
const host = workspaceHostReport(ws);
|
|
1306
1325
|
const uiShare = proxyUrlForApp(ws, hostAppOf(ws) || { id: "ui", role: "ui", host: true });
|
|
1307
|
-
const aiShare = proxyUrlForApp(ws, {
|
|
1308
|
-
id: "ai-server",
|
|
1309
|
-
role: "ai-server",
|
|
1310
|
-
});
|
|
1311
1326
|
if (uiShare) {
|
|
1312
1327
|
host.appUrl = uiShare;
|
|
1313
1328
|
} else if (probe.running) {
|
|
@@ -1316,12 +1331,9 @@ async function reconcileWorkspacePresence(ws, cfg, opts = {}) {
|
|
|
1316
1331
|
);
|
|
1317
1332
|
if (ui?.port) {
|
|
1318
1333
|
host.appUrl = `http://localhost:${ui.port}`;
|
|
1319
|
-
} else if (probe.chatUp) {
|
|
1320
|
-
host.appUrl = `http://localhost:${probe.chatPort}`;
|
|
1321
1334
|
}
|
|
1322
1335
|
}
|
|
1323
1336
|
if (uiShare && !host.origins.includes(uiShare)) host.origins.push(uiShare);
|
|
1324
|
-
if (aiShare && !host.origins.includes(aiShare)) host.origins.push(aiShare);
|
|
1325
1337
|
const proxyOrigin = String(ws?.proxy?.origin || "").replace(/\/$/, "");
|
|
1326
1338
|
if (proxyOrigin && !host.origins.includes(proxyOrigin)) {
|
|
1327
1339
|
host.origins.push(proxyOrigin);
|
|
@@ -1491,7 +1503,7 @@ function uniqueProxySlugs(apps) {
|
|
|
1491
1503
|
const used = new Set();
|
|
1492
1504
|
/** @type {Record<string, string>} */
|
|
1493
1505
|
const slugs = {};
|
|
1494
|
-
for (const app of apps
|
|
1506
|
+
for (const app of listedHostApps(apps)) {
|
|
1495
1507
|
let slug = proxySlugForApp(app);
|
|
1496
1508
|
if (!slug || !app?.id) continue;
|
|
1497
1509
|
if (used.has(slug)) {
|
|
@@ -1512,7 +1524,7 @@ function uniqueProxySlugs(apps) {
|
|
|
1512
1524
|
}
|
|
1513
1525
|
|
|
1514
1526
|
function sharePortUrls(ws) {
|
|
1515
|
-
const apps =
|
|
1527
|
+
const apps = listedHostApps(ws?.hostApps);
|
|
1516
1528
|
/** @type {Record<string, string>} */
|
|
1517
1529
|
const out = {};
|
|
1518
1530
|
for (const app of apps) {
|
|
@@ -1547,8 +1559,10 @@ function applyAssignedProxy(ws, remote, cfg) {
|
|
|
1547
1559
|
}
|
|
1548
1560
|
|
|
1549
1561
|
function appsFrom(ws, remote) {
|
|
1550
|
-
if (Array.isArray(ws?.hostApps) && ws.hostApps.length)
|
|
1551
|
-
|
|
1562
|
+
if (Array.isArray(ws?.hostApps) && ws.hostApps.length) {
|
|
1563
|
+
return listedHostApps(ws.hostApps);
|
|
1564
|
+
}
|
|
1565
|
+
if (Array.isArray(remote?.hostApps)) return listedHostApps(remote.hostApps);
|
|
1552
1566
|
return [];
|
|
1553
1567
|
}
|
|
1554
1568
|
|
|
@@ -1569,10 +1583,9 @@ function proxyUrlForApp(ws, app) {
|
|
|
1569
1583
|
}
|
|
1570
1584
|
|
|
1571
1585
|
function jobsFromHostApps(ws, filter = {}) {
|
|
1572
|
-
const apps =
|
|
1586
|
+
const apps = listedHostApps(ws.hostApps);
|
|
1573
1587
|
const hostId = hostAppOf(ws)?.id;
|
|
1574
1588
|
return apps
|
|
1575
|
-
.filter((app) => app && app.role !== "ai-server")
|
|
1576
1589
|
.filter((app) => (filter.appId ? app.id === filter.appId : true))
|
|
1577
1590
|
.map((app) => {
|
|
1578
1591
|
const port = Number(app.port) || 3000;
|
|
@@ -1622,7 +1635,7 @@ async function probeRunningApps(ws, timeoutMs = 2500) {
|
|
|
1622
1635
|
appId: job.appId || job.role,
|
|
1623
1636
|
});
|
|
1624
1637
|
}
|
|
1625
|
-
const listed =
|
|
1638
|
+
const listed = listedHostApps(ws.hostApps);
|
|
1626
1639
|
const hostApps = await Promise.all(
|
|
1627
1640
|
listed.map(async (app) => {
|
|
1628
1641
|
const port = Number(app.port) || 0;
|
|
@@ -1645,7 +1658,7 @@ async function probeRunningApps(ws, timeoutMs = 2500) {
|
|
|
1645
1658
|
chatPort: chat.port,
|
|
1646
1659
|
hosts,
|
|
1647
1660
|
hostApps,
|
|
1648
|
-
running:
|
|
1661
|
+
running: hosts.some((host) => host.up) || hostApps.some((app) => app.running),
|
|
1649
1662
|
};
|
|
1650
1663
|
}
|
|
1651
1664
|
|
|
@@ -1662,9 +1675,7 @@ async function restoreHostsAfterReconnect(cfg) {
|
|
|
1662
1675
|
.join(" ");
|
|
1663
1676
|
if (status.appsRunning) {
|
|
1664
1677
|
log(
|
|
1665
|
-
`found running apps for ${label}:
|
|
1666
|
-
status.probe.chatUp ? "(up)" : "(down)"
|
|
1667
|
-
}${hostSummary ? ` ${hostSummary}` : ""} share=${
|
|
1678
|
+
`found running apps for ${label}: ${hostSummary || "(none)"} share=${
|
|
1668
1679
|
status.host.appUrl || "(none)"
|
|
1669
1680
|
} origins=${
|
|
1670
1681
|
status.host.origins.join(",") || "none"
|
|
@@ -1703,10 +1714,7 @@ async function prepareWorkspaceLaunch(ws, cfg, reserved) {
|
|
|
1703
1714
|
return { aiPort, jobs: [] };
|
|
1704
1715
|
}
|
|
1705
1716
|
log(`ports pick ${label} in ${folder}`);
|
|
1706
|
-
const
|
|
1707
|
-
? ws.hostApps.find((app) => app.role === "ai-server" || app.id === "ai-server")
|
|
1708
|
-
: null;
|
|
1709
|
-
const preferredRaw = Number(listedAi?.port || ws.port) || 3100;
|
|
1717
|
+
const preferredRaw = Number(ws.port) || 3100;
|
|
1710
1718
|
const preferredAi = isAdminListenPort(preferredRaw) ? 3100 : preferredRaw;
|
|
1711
1719
|
const aiUp = await isChatServerOnPort(preferredAi);
|
|
1712
1720
|
const aiPort = aiUp
|
|
@@ -4679,12 +4687,11 @@ async function runActions(cfg, actions) {
|
|
|
4679
4687
|
String(action.payload.folderPathRemaining || remaining[0] || ws.folderPath);
|
|
4680
4688
|
ws.folderPath = primary;
|
|
4681
4689
|
ws.extraFolders = remaining.filter((folder) => !sameFolder(folder, primary));
|
|
4682
|
-
ws.hostApps = (
|
|
4690
|
+
ws.hostApps = listedHostApps(
|
|
4691
|
+
Array.isArray(ws.hostApps) ? ws.hostApps : []
|
|
4692
|
+
).filter(
|
|
4683
4693
|
(app) =>
|
|
4684
|
-
!app ||
|
|
4685
|
-
app.role === "ai-server" ||
|
|
4686
|
-
!app.folderPath ||
|
|
4687
|
-
!sameFolder(app.folderPath, removed)
|
|
4694
|
+
!app.folderPath || !sameFolder(app.folderPath, removed)
|
|
4688
4695
|
);
|
|
4689
4696
|
persistWorkspaceEntry(cfg, ws);
|
|
4690
4697
|
result = {
|
package/src/share-rewrite.mjs
CHANGED
|
@@ -347,7 +347,7 @@ export function rewriteMappedLocalUrls(text, portUrls) {
|
|
|
347
347
|
* @param {Record<string, string>} headers
|
|
348
348
|
* @param {Record<string, string> | null | undefined} portUrls
|
|
349
349
|
*/
|
|
350
|
-
export function rewriteShareRequestBody(body, headers, portUrls,
|
|
350
|
+
export function rewriteShareRequestBody(body, headers, portUrls, _publicBase) {
|
|
351
351
|
if (!body) return body;
|
|
352
352
|
const buf = Buffer.isBuffer(body) ? body : Buffer.from(body);
|
|
353
353
|
if (!buf.length) return buf;
|
|
@@ -357,7 +357,6 @@ export function rewriteShareRequestBody(body, headers, portUrls, publicBase) {
|
|
|
357
357
|
if (portUrls && Object.keys(portUrls).length) {
|
|
358
358
|
next = rewriteMappedLocalUrls(next, portUrls);
|
|
359
359
|
}
|
|
360
|
-
if (publicBase) next = rewriteShareOriginPaths(next, publicBase);
|
|
361
360
|
if (next === text) return buf;
|
|
362
361
|
return Buffer.from(next, "utf8");
|
|
363
362
|
}
|
|
@@ -809,12 +808,11 @@ function prefixRootPaths(body, publicBase, contentType = "") {
|
|
|
809
808
|
let out = body;
|
|
810
809
|
if (
|
|
811
810
|
html &&
|
|
811
|
+
isViteDocument(out) &&
|
|
812
812
|
/<head[\s>]/i.test(out) &&
|
|
813
|
-
!/<base\s/i.test(out)
|
|
814
|
-
!isNextDocument(out)
|
|
813
|
+
!/<base\s/i.test(out)
|
|
815
814
|
) {
|
|
816
|
-
const
|
|
817
|
-
const href = joinProxyAndViteBase(pathPrefix, viteBase);
|
|
815
|
+
const href = joinProxyAndViteBase(pathPrefix, inferViteBase(out));
|
|
818
816
|
out = out.replace(/<head([^>]*)>/i, `<head$1><base href="${href}">`);
|
|
819
817
|
}
|
|
820
818
|
if (html) {
|
|
@@ -871,6 +869,12 @@ function isScriptRequestPath(path) {
|
|
|
871
869
|
return /\.(?:m?js|cjs)$/.test(p);
|
|
872
870
|
}
|
|
873
871
|
|
|
872
|
+
function isScriptOrJsonBody(headers, path) {
|
|
873
|
+
const mime = contentMime(headerGet(headers, "content-type"));
|
|
874
|
+
if (/javascript|ecmascript|json/.test(mime)) return true;
|
|
875
|
+
return isScriptRequestPath(path);
|
|
876
|
+
}
|
|
877
|
+
|
|
874
878
|
function rewriteLocalSidecarUrls(html, aiPublicBase, uiPublicBase) {
|
|
875
879
|
const ai = String(aiPublicBase || "").replace(/\/$/, "");
|
|
876
880
|
const ui = String(uiPublicBase || "").replace(/\/$/, "");
|
|
@@ -968,6 +972,14 @@ function shareProxyShim(p, portMap, rewriteMappedLocalUrls) {
|
|
|
968
972
|
)
|
|
969
973
|
);
|
|
970
974
|
}
|
|
975
|
+
function loopback(h) {
|
|
976
|
+
h = String(h || "")
|
|
977
|
+
.replace(/^\[|\]$/g, "")
|
|
978
|
+
.toLowerCase();
|
|
979
|
+
return (
|
|
980
|
+
h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0"
|
|
981
|
+
);
|
|
982
|
+
}
|
|
971
983
|
function prefixPath(path) {
|
|
972
984
|
if (typeof path !== "string" || !path) return path;
|
|
973
985
|
if (path.charAt(0) === "/" && path.charAt(1) !== "/") {
|
|
@@ -1007,10 +1019,12 @@ function shareProxyShim(p, portMap, rewriteMappedLocalUrls) {
|
|
|
1007
1019
|
}
|
|
1008
1020
|
function addNav(v) {
|
|
1009
1021
|
if (typeof v !== "string" || !v) return v;
|
|
1010
|
-
|
|
1022
|
+
var mapped = mapLocal(v);
|
|
1023
|
+
if (mapped !== v) return mapped;
|
|
1011
1024
|
if (v.charAt(0) === "/" && v.charAt(1) !== "/") return prefixPath(v);
|
|
1012
1025
|
try {
|
|
1013
1026
|
var nav = new URL(v, location.href);
|
|
1027
|
+
if (!loopback(nav.hostname)) return v;
|
|
1014
1028
|
if (nav.origin !== location.origin) return v;
|
|
1015
1029
|
if (nav.pathname === "/" || nav.pathname === "") return v;
|
|
1016
1030
|
nav.pathname = prefixPath(nav.pathname);
|
|
@@ -1019,6 +1033,15 @@ function shareProxyShim(p, portMap, rewriteMappedLocalUrls) {
|
|
|
1019
1033
|
return v;
|
|
1020
1034
|
}
|
|
1021
1035
|
function addNet(v) {
|
|
1036
|
+
if (typeof v !== "string" || !v) return v;
|
|
1037
|
+
var mapped = mapLocal(v);
|
|
1038
|
+
if (mapped !== v) return mapped;
|
|
1039
|
+
if (/^(https?:|wss?:)\/\//i.test(v)) {
|
|
1040
|
+
try {
|
|
1041
|
+
var u = new URL(v);
|
|
1042
|
+
if (!loopback(u.hostname)) return v;
|
|
1043
|
+
} catch (e) {}
|
|
1044
|
+
}
|
|
1022
1045
|
return addNav(v);
|
|
1023
1046
|
}
|
|
1024
1047
|
function mapSel(sel) {
|
|
@@ -1213,9 +1236,21 @@ function shareServiceWorkerMain(portMap, tokenRoot, rewriteMappedLocalUrls) {
|
|
|
1213
1236
|
} catch (e) {}
|
|
1214
1237
|
return "";
|
|
1215
1238
|
}
|
|
1239
|
+
function loopback(h) {
|
|
1240
|
+
h = String(h || "")
|
|
1241
|
+
.replace(/^\[|\]$/g, "")
|
|
1242
|
+
.toLowerCase();
|
|
1243
|
+
return (
|
|
1244
|
+
h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0"
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1216
1247
|
function prefixWith(url, prefix) {
|
|
1217
1248
|
if (!prefix) return url;
|
|
1218
1249
|
try {
|
|
1250
|
+
if (/^(https?:|wss?:)\/\//i.test(url)) {
|
|
1251
|
+
var abs = new URL(url);
|
|
1252
|
+
if (!loopback(abs.hostname)) return url;
|
|
1253
|
+
}
|
|
1219
1254
|
var u = new URL(url, self.location.href);
|
|
1220
1255
|
if (u.origin !== self.location.origin) return url;
|
|
1221
1256
|
if (u.pathname === "/" || u.pathname === "") return url;
|
|
@@ -1392,7 +1427,7 @@ export function processShareHttpResponse(opts) {
|
|
|
1392
1427
|
mutated = true;
|
|
1393
1428
|
}
|
|
1394
1429
|
}
|
|
1395
|
-
if (publicBase && rewriteTextBody) {
|
|
1430
|
+
if (publicBase && rewriteTextBody && !isScriptOrJsonBody(headers, path)) {
|
|
1396
1431
|
const prefixed = rewriteShareOriginPaths(text, publicBase);
|
|
1397
1432
|
if (prefixed !== text) {
|
|
1398
1433
|
text = prefixed;
|