@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maintainer-pro/ai-bridge",
3
- "version": "0.1.20",
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.11",
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
- return JSON.parse(fs.readFileSync(file, "utf8"));
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}) — using AI server ${Number(ws.port) || 3100} so setup can continue`
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
- const apps = Array.isArray(ws.hostApps) ? ws.hostApps : [];
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 = Array.isArray(ws?.hostApps) ? ws.hostApps : [];
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) return ws.hostApps;
1551
- if (Array.isArray(remote?.hostApps)) return remote.hostApps;
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 = Array.isArray(ws.hostApps) ? ws.hostApps : [];
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 = Array.isArray(ws.hostApps) ? ws.hostApps : [];
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: chat.up || hosts.some((host) => host.up) || hostApps.some((app) => app.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}: chat=${status.probe.chatPort}${
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 listedAi = Array.isArray(ws.hostApps)
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 = (Array.isArray(ws.hostApps) ? ws.hostApps : []).filter(
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 = {
@@ -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, publicBase) {
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 viteBase = isViteDocument(out) ? inferViteBase(out) : "/";
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
- v = mapLocal(v);
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;