@maintainer-pro/ai-bridge 0.1.3 → 0.1.5
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 +1 -1
- package/src/daemon.mjs +215 -77
package/package.json
CHANGED
package/src/daemon.mjs
CHANGED
|
@@ -850,7 +850,65 @@ const launchedAt = new Map();
|
|
|
850
850
|
/** Last process problems to send on heartbeat. Key: sandboxId::code::role */
|
|
851
851
|
const processProblems = new Map();
|
|
852
852
|
|
|
853
|
-
|
|
853
|
+
/** @type {Map<string, Set<string>>} sandboxId -> CMD/terminal titles we opened */
|
|
854
|
+
const openedTerminalTitles = new Map();
|
|
855
|
+
|
|
856
|
+
function rememberTerminalTitle(sandboxId, title) {
|
|
857
|
+
const id = String(sandboxId || "").trim();
|
|
858
|
+
const name = String(title || "").trim();
|
|
859
|
+
if (!id || !name) return;
|
|
860
|
+
let titles = openedTerminalTitles.get(id);
|
|
861
|
+
if (!titles) {
|
|
862
|
+
titles = new Set();
|
|
863
|
+
openedTerminalTitles.set(id, titles);
|
|
864
|
+
}
|
|
865
|
+
titles.add(name);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function closeWindowsByTitle(title) {
|
|
869
|
+
const name = String(title || "").trim();
|
|
870
|
+
if (!name) return Promise.resolve();
|
|
871
|
+
return new Promise((resolve) => {
|
|
872
|
+
const done = () => resolve();
|
|
873
|
+
if (process.platform === "win32") {
|
|
874
|
+
const child = spawn(
|
|
875
|
+
"taskkill",
|
|
876
|
+
["/F", "/T", "/FI", `WINDOWTITLE eq ${name}*`],
|
|
877
|
+
{ windowsHide: true, stdio: "ignore" }
|
|
878
|
+
);
|
|
879
|
+
child.on("exit", done);
|
|
880
|
+
child.on("error", done);
|
|
881
|
+
setTimeout(done, 4000);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (process.platform === "darwin") {
|
|
885
|
+
const child = spawn(
|
|
886
|
+
"osascript",
|
|
887
|
+
[
|
|
888
|
+
"-e",
|
|
889
|
+
`tell application "Terminal" to close (every window whose name contains ${JSON.stringify(name)})`,
|
|
890
|
+
],
|
|
891
|
+
{ stdio: "ignore" }
|
|
892
|
+
);
|
|
893
|
+
child.on("exit", done);
|
|
894
|
+
child.on("error", done);
|
|
895
|
+
setTimeout(done, 4000);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
done();
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
async function closeRememberedTerminals(sandboxId) {
|
|
903
|
+
const titles = openedTerminalTitles.get(sandboxId);
|
|
904
|
+
if (!titles) return;
|
|
905
|
+
for (const title of titles) {
|
|
906
|
+
await closeWindowsByTitle(title);
|
|
907
|
+
}
|
|
908
|
+
openedTerminalTitles.delete(sandboxId);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
async function forgetLaunch(sandboxId) {
|
|
854
912
|
for (const key of [...launchedAt.keys()]) {
|
|
855
913
|
if (key === sandboxId || key.startsWith(`${sandboxId}:`)) {
|
|
856
914
|
launchedAt.delete(key);
|
|
@@ -859,7 +917,8 @@ function forgetLaunch(sandboxId) {
|
|
|
859
917
|
for (const key of [...processProblems.keys()]) {
|
|
860
918
|
if (key.startsWith(`${sandboxId}::`)) processProblems.delete(key);
|
|
861
919
|
}
|
|
862
|
-
|
|
920
|
+
await closeRememberedTerminals(sandboxId);
|
|
921
|
+
await stopCloudflare(sandboxId);
|
|
863
922
|
}
|
|
864
923
|
|
|
865
924
|
function problemKey(sandboxId, code, role = "") {
|
|
@@ -1041,6 +1100,7 @@ function runLauncher(command, args, extra = {}) {
|
|
|
1041
1100
|
child = spawn(command, args, {
|
|
1042
1101
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1043
1102
|
windowsHide: extra.windowsHide,
|
|
1103
|
+
windowsVerbatimArguments: Boolean(extra.windowsVerbatimArguments),
|
|
1044
1104
|
});
|
|
1045
1105
|
} catch (err) {
|
|
1046
1106
|
done({
|
|
@@ -1073,7 +1133,7 @@ function runLauncher(command, args, extra = {}) {
|
|
|
1073
1133
|
}
|
|
1074
1134
|
|
|
1075
1135
|
async function openInNewTerminal(opts) {
|
|
1076
|
-
const { title, folder, command, env = {}, launchKey } = opts;
|
|
1136
|
+
const { title, folder, command, env = {}, launchKey, sandboxId } = opts;
|
|
1077
1137
|
if (launchKey) {
|
|
1078
1138
|
if (!opts.force && recentlyLaunched(launchKey)) {
|
|
1079
1139
|
return { ok: true, skipped: true };
|
|
@@ -1086,6 +1146,13 @@ async function openInNewTerminal(opts) {
|
|
|
1086
1146
|
return { ok: false, error };
|
|
1087
1147
|
}
|
|
1088
1148
|
|
|
1149
|
+
await closeWindowsByTitle(title);
|
|
1150
|
+
await sleep(250);
|
|
1151
|
+
rememberTerminalTitle(
|
|
1152
|
+
sandboxId || String(launchKey || "").split(":")[0],
|
|
1153
|
+
title
|
|
1154
|
+
);
|
|
1155
|
+
|
|
1089
1156
|
const envWin = Object.entries(env)
|
|
1090
1157
|
.map(([key, value]) => `set ${key}=${value}`)
|
|
1091
1158
|
.join("&& ");
|
|
@@ -1097,18 +1164,16 @@ async function openInNewTerminal(opts) {
|
|
|
1097
1164
|
|
|
1098
1165
|
try {
|
|
1099
1166
|
if (process.platform === "win32") {
|
|
1100
|
-
const
|
|
1101
|
-
|
|
1167
|
+
const safeTitle =
|
|
1168
|
+
String(title || "Maintainer Pro").replace(/["&<>|^]/g, " ").trim() ||
|
|
1169
|
+
"Maintainer Pro";
|
|
1170
|
+
const body = [envWin, `title ${safeTitle}`, command]
|
|
1171
|
+
.filter(Boolean)
|
|
1172
|
+
.join("&& ");
|
|
1102
1173
|
const opened = await runLauncher(
|
|
1103
|
-
"
|
|
1104
|
-
[
|
|
1105
|
-
|
|
1106
|
-
"-WindowStyle",
|
|
1107
|
-
"Hidden",
|
|
1108
|
-
"-Command",
|
|
1109
|
-
`Start-Process -FilePath $env:ComSpec -WorkingDirectory ${JSON.stringify(folder)} -ArgumentList @('/k', '${escaped}')`,
|
|
1110
|
-
],
|
|
1111
|
-
{ windowsHide: true }
|
|
1174
|
+
process.env.ComSpec || "cmd.exe",
|
|
1175
|
+
["/d", "/s", "/c", `start "${safeTitle}" /D "${folder}" cmd.exe /k ${body}`],
|
|
1176
|
+
{ windowsVerbatimArguments: true }
|
|
1112
1177
|
);
|
|
1113
1178
|
if (!opened.ok) {
|
|
1114
1179
|
return { ok: false, error: friendlyLaunchError(opened.error, title) };
|
|
@@ -1255,6 +1320,7 @@ async function startAiServerForWorkspace(ws, opts = {}) {
|
|
|
1255
1320
|
NEXT_PUBLIC_AI_SERVER_URL: `http://localhost:${port}`,
|
|
1256
1321
|
},
|
|
1257
1322
|
launchKey,
|
|
1323
|
+
sandboxId: ws.sandboxId,
|
|
1258
1324
|
});
|
|
1259
1325
|
if (opened.skipped) {
|
|
1260
1326
|
return { port, up: false, launched: false, starting: true };
|
|
@@ -1382,6 +1448,7 @@ async function stopWorkspaceApps(ws) {
|
|
|
1382
1448
|
}
|
|
1383
1449
|
}
|
|
1384
1450
|
log(`stopping local apps on ports ${[...ports].join(", ") || "(none)"}`);
|
|
1451
|
+
await closeRememberedTerminals(ws.sandboxId);
|
|
1385
1452
|
for (const port of ports) {
|
|
1386
1453
|
await killPort(port);
|
|
1387
1454
|
}
|
|
@@ -1413,12 +1480,10 @@ async function waitForUrlInFile(file, timeoutMs = 90_000) {
|
|
|
1413
1480
|
}
|
|
1414
1481
|
|
|
1415
1482
|
function cloudflaredCommand(localUrl, logFile) {
|
|
1416
|
-
const
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
}
|
|
1421
|
-
return `${run} 2>&1 | tee ${JSON.stringify(logFile)}`;
|
|
1483
|
+
const logArg = JSON.stringify(logFile);
|
|
1484
|
+
const run = `npx --yes cloudflared tunnel --url ${localUrl} --no-autoupdate --logfile ${logArg}`;
|
|
1485
|
+
if (process.platform === "win32") return run;
|
|
1486
|
+
return `${run} 2>&1 | tee ${logArg}`;
|
|
1422
1487
|
}
|
|
1423
1488
|
|
|
1424
1489
|
async function startCloudflareTerminal(ws, role, localUrl) {
|
|
@@ -1439,6 +1504,7 @@ async function startCloudflareTerminal(ws, role, localUrl) {
|
|
|
1439
1504
|
folder,
|
|
1440
1505
|
command: cloudflaredCommand(localUrl, logFile),
|
|
1441
1506
|
launchKey: `${ws.sandboxId}:cf:${role}`,
|
|
1507
|
+
sandboxId: ws.sandboxId,
|
|
1442
1508
|
force: true,
|
|
1443
1509
|
});
|
|
1444
1510
|
if (!opened.ok) {
|
|
@@ -1501,22 +1567,52 @@ function writeTunnelEnv(ws, tunnels) {
|
|
|
1501
1567
|
}
|
|
1502
1568
|
}
|
|
1503
1569
|
|
|
1504
|
-
|
|
1505
|
-
const sandboxId = ws.sandboxId;
|
|
1506
|
-
const label = ws.sandboxName || "this sandbox";
|
|
1507
|
-
const folder = path.resolve(ws.folderPath || "");
|
|
1570
|
+
function reservedPortsFor(cfg, sandboxId) {
|
|
1508
1571
|
const reserved = new Set();
|
|
1509
1572
|
for (const other of cfg.workspaces || []) {
|
|
1510
1573
|
if (other.sandboxId !== sandboxId && other.port) {
|
|
1511
1574
|
reserved.add(Number(other.port));
|
|
1512
1575
|
}
|
|
1513
1576
|
}
|
|
1577
|
+
return reserved;
|
|
1578
|
+
}
|
|
1514
1579
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1580
|
+
function appsWanted(ws) {
|
|
1581
|
+
return Boolean(ws?.appsRequested);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
async function configureCloudflareForWorkspace(ws, cfg) {
|
|
1585
|
+
const sandboxId = ws.sandboxId;
|
|
1586
|
+
const label = ws.sandboxName || "this sandbox";
|
|
1587
|
+
|
|
1588
|
+
log(`Cloudflare queued for ${label}: stop apps and wait for Start`);
|
|
1517
1589
|
await stopCloudflare(sandboxId);
|
|
1518
1590
|
await stopWorkspaceApps(ws);
|
|
1591
|
+
await forgetLaunch(sandboxId);
|
|
1592
|
+
ws.cloudflarePending = true;
|
|
1593
|
+
ws.appsRequested = false;
|
|
1594
|
+
persistWorkspaceEntry(cfg, ws);
|
|
1519
1595
|
|
|
1596
|
+
return {
|
|
1597
|
+
sandboxId,
|
|
1598
|
+
folderPath: ws.folderPath,
|
|
1599
|
+
port: ws.port,
|
|
1600
|
+
pending: true,
|
|
1601
|
+
cloudflarePending: true,
|
|
1602
|
+
waitingForStart: true,
|
|
1603
|
+
warning:
|
|
1604
|
+
"Cloudflare is ready in Maintainer Pro. Use Start chat server when you want to launch the apps and create the public URLs.",
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
async function launchCloudflareTunnels(ws, cfg) {
|
|
1609
|
+
const sandboxId = ws.sandboxId;
|
|
1610
|
+
const label = ws.sandboxName || "this sandbox";
|
|
1611
|
+
const folder = path.resolve(ws.folderPath || "");
|
|
1612
|
+
const reserved = reservedPortsFor(cfg, sandboxId);
|
|
1613
|
+
|
|
1614
|
+
log(`Cloudflare start for ${label}: tunnel chat script/backend before UI`);
|
|
1615
|
+
try {
|
|
1520
1616
|
if (!cfg.noAiServer) {
|
|
1521
1617
|
await startAiServerForWorkspace(ws, { reserved, cfg });
|
|
1522
1618
|
await waitUntilReachable(
|
|
@@ -1596,6 +1692,8 @@ async function configureCloudflareForWorkspace(ws, cfg) {
|
|
|
1596
1692
|
ws.cloudflareUrl = appUrl;
|
|
1597
1693
|
ws.cloudflare = tunnels;
|
|
1598
1694
|
ws.appUrl = appUrl;
|
|
1695
|
+
ws.cloudflarePending = false;
|
|
1696
|
+
ws.appsRequested = true;
|
|
1599
1697
|
persistWorkspaceEntry(cfg, ws);
|
|
1600
1698
|
cloudflareTunnels.set(sandboxId, { tunnels: started });
|
|
1601
1699
|
clearProcessProblem(sandboxId, "cloudflare_launch", "tunnel");
|
|
@@ -1603,6 +1701,7 @@ async function configureCloudflareForWorkspace(ws, cfg) {
|
|
|
1603
1701
|
return {
|
|
1604
1702
|
sandboxId,
|
|
1605
1703
|
folderPath: ws.folderPath,
|
|
1704
|
+
port: ws.port,
|
|
1606
1705
|
appUrl,
|
|
1607
1706
|
origins: Object.values(tunnels).filter(Boolean),
|
|
1608
1707
|
tunnels,
|
|
@@ -1618,13 +1717,52 @@ async function configureCloudflareForWorkspace(ws, cfg) {
|
|
|
1618
1717
|
title: `Could not start Cloudflare (${label})`,
|
|
1619
1718
|
message,
|
|
1620
1719
|
resolution:
|
|
1621
|
-
"Install cloudflared or allow npx to download it, then
|
|
1622
|
-
actionCode: "
|
|
1720
|
+
"Install cloudflared or allow npx to download it, then use Start chat server again.",
|
|
1721
|
+
actionCode: "start_ai_server",
|
|
1623
1722
|
});
|
|
1624
1723
|
throw err;
|
|
1625
1724
|
}
|
|
1626
1725
|
}
|
|
1627
1726
|
|
|
1727
|
+
async function startAppsForWorkspace(ws, cfg) {
|
|
1728
|
+
if (ws.cloudflarePending) {
|
|
1729
|
+
return launchCloudflareTunnels(ws, cfg);
|
|
1730
|
+
}
|
|
1731
|
+
ws.appsRequested = true;
|
|
1732
|
+
persistWorkspaceEntry(cfg, ws);
|
|
1733
|
+
const reserved = reservedPortsFor(cfg, ws.sandboxId);
|
|
1734
|
+
if (!cfg.noAiServer) {
|
|
1735
|
+
await startAiServerForWorkspace(ws, { reserved, cfg });
|
|
1736
|
+
await sleep(1500);
|
|
1737
|
+
}
|
|
1738
|
+
const startedHosts = await ensureHostProcesses(ws, {
|
|
1739
|
+
reserved,
|
|
1740
|
+
cfg,
|
|
1741
|
+
force: true,
|
|
1742
|
+
});
|
|
1743
|
+
await sleep(800);
|
|
1744
|
+
await inspectHostJobs(ws);
|
|
1745
|
+
const up = await probeUrl(`http://127.0.0.1:${ws.port}/embed-config.js`);
|
|
1746
|
+
if (up) {
|
|
1747
|
+
clearProcessProblem(ws.sandboxId, "ai_server_launch", "ai");
|
|
1748
|
+
clearProcessProblem(ws.sandboxId, "apps_not_started");
|
|
1749
|
+
}
|
|
1750
|
+
const processIssues = issuesForSandbox(ws.sandboxId).map(
|
|
1751
|
+
({ role: _role, ...issue }) => issue
|
|
1752
|
+
);
|
|
1753
|
+
const warning = processIssues[0]?.message || null;
|
|
1754
|
+
return {
|
|
1755
|
+
up,
|
|
1756
|
+
startedHosts,
|
|
1757
|
+
sandboxId: ws.sandboxId,
|
|
1758
|
+
folderPath: ws.folderPath,
|
|
1759
|
+
port: ws.port,
|
|
1760
|
+
appUrl: ws.appUrl,
|
|
1761
|
+
processIssues,
|
|
1762
|
+
warning,
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1628
1766
|
async function ensureHostProcesses(ws, opts = {}) {
|
|
1629
1767
|
const reserved = opts.reserved instanceof Set ? opts.reserved : new Set();
|
|
1630
1768
|
const cfg = opts.cfg || null;
|
|
@@ -1704,6 +1842,7 @@ async function ensureHostProcesses(ws, opts = {}) {
|
|
|
1704
1842
|
command,
|
|
1705
1843
|
env: { PORT: String(port), ...extraEnv },
|
|
1706
1844
|
launchKey,
|
|
1845
|
+
sandboxId: ws.sandboxId,
|
|
1707
1846
|
force: Boolean(opts.force),
|
|
1708
1847
|
});
|
|
1709
1848
|
if (opened.skipped) continue;
|
|
@@ -1763,6 +1902,11 @@ async function inspectHostJobs(ws) {
|
|
|
1763
1902
|
clearProcessProblem(ws.sandboxId, "host_process_down", job.role);
|
|
1764
1903
|
continue;
|
|
1765
1904
|
}
|
|
1905
|
+
if (!appsWanted(ws)) {
|
|
1906
|
+
clearProcessProblem(ws.sandboxId, "host_process_launch", job.role);
|
|
1907
|
+
clearProcessProblem(ws.sandboxId, "host_process_down", job.role);
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1766
1910
|
if (starting) continue;
|
|
1767
1911
|
const tried = launchedAt.has(launchKey);
|
|
1768
1912
|
recordProcessProblem({
|
|
@@ -1863,6 +2007,8 @@ async function setupWorkspace(cfg, action) {
|
|
|
1863
2007
|
clientKind: client.kind,
|
|
1864
2008
|
appUrl,
|
|
1865
2009
|
sameOrigin: Boolean(client.sameOrigin),
|
|
2010
|
+
appsRequested: false,
|
|
2011
|
+
cloudflarePending: false,
|
|
1866
2012
|
};
|
|
1867
2013
|
if (existing >= 0) cfg.workspaces[existing] = entry;
|
|
1868
2014
|
else cfg.workspaces.push(entry);
|
|
@@ -1881,12 +2027,6 @@ async function setupWorkspace(cfg, action) {
|
|
|
1881
2027
|
.join("\n"),
|
|
1882
2028
|
});
|
|
1883
2029
|
|
|
1884
|
-
if (!cfg.noAiServer) {
|
|
1885
|
-
await startAiServerForWorkspace(entry, { reserved, cfg });
|
|
1886
|
-
await sleep(1500);
|
|
1887
|
-
}
|
|
1888
|
-
const startedHosts = await ensureHostProcesses(entry, { reserved, cfg });
|
|
1889
|
-
await sleep(800);
|
|
1890
2030
|
await inspectHostJobs(entry);
|
|
1891
2031
|
|
|
1892
2032
|
const openUrl = client.sameOrigin
|
|
@@ -1895,23 +2035,22 @@ async function setupWorkspace(cfg, action) {
|
|
|
1895
2035
|
const aiServerUp = await probeUrl(`http://127.0.0.1:${entry.port}/embed-config.js`);
|
|
1896
2036
|
if (aiServerUp) {
|
|
1897
2037
|
clearProcessProblem(sandboxId, "ai_server_launch", "ai");
|
|
2038
|
+
clearProcessProblem(sandboxId, "apps_not_started");
|
|
1898
2039
|
}
|
|
1899
2040
|
|
|
1900
2041
|
const processIssues = issuesForSandbox(sandboxId).map(
|
|
1901
2042
|
({ role: _role, ...issue }) => issue
|
|
1902
2043
|
);
|
|
1903
|
-
const
|
|
2044
|
+
const waitingForStart = !aiServerUp;
|
|
2045
|
+
const warning = waitingForStart
|
|
2046
|
+
? "Folder is attached in Maintainer Pro. Use Start chat server when you want to launch the apps."
|
|
2047
|
+
: processIssues[0]?.message || null;
|
|
1904
2048
|
|
|
1905
2049
|
for (const note of client.notes) log(note);
|
|
1906
|
-
if (
|
|
1907
|
-
log(`
|
|
1908
|
-
}
|
|
1909
|
-
if (aiServerUp) {
|
|
1910
|
-
log(`ai-server up — open ${openUrl}`);
|
|
1911
|
-
} else if (warning) {
|
|
1912
|
-
log(`process issue: ${warning}`);
|
|
2050
|
+
if (waitingForStart) {
|
|
2051
|
+
log(`folder attached — waiting for Start (${openUrl})`);
|
|
1913
2052
|
} else {
|
|
1914
|
-
log(
|
|
2053
|
+
log(`ai-server already up — open ${openUrl}`);
|
|
1915
2054
|
}
|
|
1916
2055
|
|
|
1917
2056
|
return {
|
|
@@ -1925,10 +2064,11 @@ async function setupWorkspace(cfg, action) {
|
|
|
1925
2064
|
clientFiles: client.filesWritten,
|
|
1926
2065
|
clientNotes: client.notes,
|
|
1927
2066
|
aiServerUp,
|
|
1928
|
-
startedHosts,
|
|
2067
|
+
startedHosts: [],
|
|
1929
2068
|
openUrl,
|
|
1930
2069
|
processIssues,
|
|
1931
2070
|
warning,
|
|
2071
|
+
waitingForStart,
|
|
1932
2072
|
projectInfo,
|
|
1933
2073
|
};
|
|
1934
2074
|
}
|
|
@@ -1970,12 +2110,6 @@ async function runActions(cfg, actions) {
|
|
|
1970
2110
|
ok = false;
|
|
1971
2111
|
result = { error: "No workspace or --no-ai-server" };
|
|
1972
2112
|
} else {
|
|
1973
|
-
const reserved = new Set();
|
|
1974
|
-
for (const other of cfg.workspaces || []) {
|
|
1975
|
-
if (other.sandboxId !== ws.sandboxId && other.port) {
|
|
1976
|
-
reserved.add(Number(other.port));
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
2113
|
const problem = issuesForSandbox(ws.sandboxId)
|
|
1980
2114
|
.map((issue) => issue.message)
|
|
1981
2115
|
.join("\n");
|
|
@@ -1985,32 +2119,15 @@ async function runActions(cfg, actions) {
|
|
|
1985
2119
|
problem ||
|
|
1986
2120
|
"Local processes are not running or the project setup looks incomplete.",
|
|
1987
2121
|
});
|
|
1988
|
-
await startAiServerForWorkspace(ws, { reserved, cfg });
|
|
1989
|
-
await sleep(1500);
|
|
1990
|
-
const startedHosts = await ensureHostProcesses(ws, { reserved, cfg });
|
|
1991
|
-
await sleep(800);
|
|
1992
|
-
await inspectHostJobs(ws);
|
|
1993
|
-
const up = await probeUrl(
|
|
1994
|
-
`http://127.0.0.1:${ws.port}/embed-config.js`
|
|
1995
|
-
);
|
|
1996
|
-
if (up) clearProcessProblem(ws.sandboxId, "ai_server_launch", "ai");
|
|
1997
|
-
const processIssues = issuesForSandbox(ws.sandboxId).map(
|
|
1998
|
-
({ role: _role, ...issue }) => issue
|
|
1999
|
-
);
|
|
2000
|
-
const warning = processIssues[0]?.message || null;
|
|
2001
2122
|
result = {
|
|
2002
|
-
|
|
2003
|
-
startedHosts,
|
|
2004
|
-
sandboxId: ws.sandboxId,
|
|
2005
|
-
folderPath: ws.folderPath,
|
|
2006
|
-
port: ws.port,
|
|
2007
|
-
appUrl: ws.appUrl,
|
|
2008
|
-
processIssues,
|
|
2009
|
-
warning,
|
|
2123
|
+
...(await startAppsForWorkspace(ws, cfg)),
|
|
2010
2124
|
projectInfo,
|
|
2011
2125
|
};
|
|
2012
|
-
if (
|
|
2013
|
-
result.
|
|
2126
|
+
if (
|
|
2127
|
+
Array.isArray(result.processIssues) &&
|
|
2128
|
+
result.processIssues.some((issue) => issue.code === "ai_server_launch")
|
|
2129
|
+
) {
|
|
2130
|
+
result.error = result.warning;
|
|
2014
2131
|
ok = false;
|
|
2015
2132
|
}
|
|
2016
2133
|
}
|
|
@@ -2051,7 +2168,7 @@ async function runActions(cfg, actions) {
|
|
|
2051
2168
|
const sandboxId = String(
|
|
2052
2169
|
action.sandboxId || action.payload?.sandboxId || ""
|
|
2053
2170
|
);
|
|
2054
|
-
forgetLaunch(sandboxId);
|
|
2171
|
+
await forgetLaunch(sandboxId);
|
|
2055
2172
|
cfg.workspaces = (cfg.workspaces || []).filter(
|
|
2056
2173
|
(w) => w.sandboxId !== sandboxId
|
|
2057
2174
|
);
|
|
@@ -2100,6 +2217,7 @@ async function collectWorkspaceStates(cfg) {
|
|
|
2100
2217
|
aiServerUp: up,
|
|
2101
2218
|
startingAi: recentlyLaunched(`${ws.sandboxId}:${folder}:ai`),
|
|
2102
2219
|
appUrl: ws.appUrl || null,
|
|
2220
|
+
appsRequested: appsWanted(ws),
|
|
2103
2221
|
});
|
|
2104
2222
|
}
|
|
2105
2223
|
return localStates;
|
|
@@ -2122,6 +2240,7 @@ async function sendHeartbeat(cfg, folders, localStates) {
|
|
|
2122
2240
|
aiServerUp: st.aiServerUp,
|
|
2123
2241
|
port: st.port,
|
|
2124
2242
|
appUrl: st.appUrl || undefined,
|
|
2243
|
+
appsRequested: Boolean(st.appsRequested),
|
|
2125
2244
|
})),
|
|
2126
2245
|
}
|
|
2127
2246
|
);
|
|
@@ -2145,6 +2264,19 @@ async function buildIssues(cfg, workspaceStates) {
|
|
|
2145
2264
|
}
|
|
2146
2265
|
for (const st of workspaceStates) {
|
|
2147
2266
|
if (st.aiServerUp || st.startingAi) continue;
|
|
2267
|
+
if (!st.appsRequested) {
|
|
2268
|
+
issues.push({
|
|
2269
|
+
code: "apps_not_started",
|
|
2270
|
+
severity: "info",
|
|
2271
|
+
title: `Apps are not running (${st.sandboxName || "sandbox"})`,
|
|
2272
|
+
message:
|
|
2273
|
+
"This folder is attached in Maintainer Pro. Use Start chat server when you want to launch the local apps.",
|
|
2274
|
+
resolution: "Use Start chat server.",
|
|
2275
|
+
actionCode: "start_ai_server",
|
|
2276
|
+
sandboxId: st.sandboxId,
|
|
2277
|
+
});
|
|
2278
|
+
continue;
|
|
2279
|
+
}
|
|
2148
2280
|
const launch = [...processProblems.values()].find(
|
|
2149
2281
|
(issue) =>
|
|
2150
2282
|
issue.sandboxId === st.sandboxId && issue.code === "ai_server_launch"
|
|
@@ -2281,13 +2413,18 @@ async function main() {
|
|
|
2281
2413
|
const up = await probeUrl(
|
|
2282
2414
|
`http://127.0.0.1:${ws.port}/embed-config.js`
|
|
2283
2415
|
);
|
|
2284
|
-
if (!cfg.noAiServer && !up) {
|
|
2416
|
+
if (appsWanted(ws) && !cfg.noAiServer && !up) {
|
|
2285
2417
|
await startAiServerForWorkspace(ws, { reserved, cfg });
|
|
2286
2418
|
} else if (ws.port) {
|
|
2287
2419
|
reserved.add(Number(ws.port));
|
|
2288
|
-
if (up)
|
|
2420
|
+
if (up) {
|
|
2421
|
+
clearProcessProblem(ws.sandboxId, "ai_server_launch", "ai");
|
|
2422
|
+
clearProcessProblem(ws.sandboxId, "apps_not_started");
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
if (appsWanted(ws)) {
|
|
2426
|
+
await ensureHostProcesses(ws, { reserved, cfg });
|
|
2289
2427
|
}
|
|
2290
|
-
await ensureHostProcesses(ws, { reserved, cfg });
|
|
2291
2428
|
await inspectHostJobs(ws);
|
|
2292
2429
|
localStates.push({
|
|
2293
2430
|
sandboxId: ws.sandboxId,
|
|
@@ -2297,6 +2434,7 @@ async function main() {
|
|
|
2297
2434
|
aiServerUp: up,
|
|
2298
2435
|
startingAi: recentlyLaunched(`${ws.sandboxId}:${folder}:ai`),
|
|
2299
2436
|
appUrl: ws.appUrl || null,
|
|
2437
|
+
appsRequested: appsWanted(ws),
|
|
2300
2438
|
});
|
|
2301
2439
|
}
|
|
2302
2440
|
|