@maintainer-pro/ai-bridge 0.1.3 → 0.1.4
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 +213 -78
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 = "") {
|
|
@@ -1073,7 +1132,7 @@ function runLauncher(command, args, extra = {}) {
|
|
|
1073
1132
|
}
|
|
1074
1133
|
|
|
1075
1134
|
async function openInNewTerminal(opts) {
|
|
1076
|
-
const { title, folder, command, env = {}, launchKey } = opts;
|
|
1135
|
+
const { title, folder, command, env = {}, launchKey, sandboxId } = opts;
|
|
1077
1136
|
if (launchKey) {
|
|
1078
1137
|
if (!opts.force && recentlyLaunched(launchKey)) {
|
|
1079
1138
|
return { ok: true, skipped: true };
|
|
@@ -1086,6 +1145,13 @@ async function openInNewTerminal(opts) {
|
|
|
1086
1145
|
return { ok: false, error };
|
|
1087
1146
|
}
|
|
1088
1147
|
|
|
1148
|
+
await closeWindowsByTitle(title);
|
|
1149
|
+
await sleep(250);
|
|
1150
|
+
rememberTerminalTitle(
|
|
1151
|
+
sandboxId || String(launchKey || "").split(":")[0],
|
|
1152
|
+
title
|
|
1153
|
+
);
|
|
1154
|
+
|
|
1089
1155
|
const envWin = Object.entries(env)
|
|
1090
1156
|
.map(([key, value]) => `set ${key}=${value}`)
|
|
1091
1157
|
.join("&& ");
|
|
@@ -1098,18 +1164,14 @@ async function openInNewTerminal(opts) {
|
|
|
1098
1164
|
try {
|
|
1099
1165
|
if (process.platform === "win32") {
|
|
1100
1166
|
const inner = `cd /d "${folder}" && ${envWin ? `${envWin}&& ` : ""}title ${title}&& ${command}`;
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
"
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
`Start-Process -FilePath $env:ComSpec -WorkingDirectory ${JSON.stringify(folder)} -ArgumentList @('/k', '${escaped}')`,
|
|
1110
|
-
],
|
|
1111
|
-
{ windowsHide: true }
|
|
1112
|
-
);
|
|
1167
|
+
const opened = await runLauncher(process.env.ComSpec || "cmd.exe", [
|
|
1168
|
+
"/c",
|
|
1169
|
+
"start",
|
|
1170
|
+
title,
|
|
1171
|
+
"cmd.exe",
|
|
1172
|
+
"/k",
|
|
1173
|
+
inner,
|
|
1174
|
+
]);
|
|
1113
1175
|
if (!opened.ok) {
|
|
1114
1176
|
return { ok: false, error: friendlyLaunchError(opened.error, title) };
|
|
1115
1177
|
}
|
|
@@ -1255,6 +1317,7 @@ async function startAiServerForWorkspace(ws, opts = {}) {
|
|
|
1255
1317
|
NEXT_PUBLIC_AI_SERVER_URL: `http://localhost:${port}`,
|
|
1256
1318
|
},
|
|
1257
1319
|
launchKey,
|
|
1320
|
+
sandboxId: ws.sandboxId,
|
|
1258
1321
|
});
|
|
1259
1322
|
if (opened.skipped) {
|
|
1260
1323
|
return { port, up: false, launched: false, starting: true };
|
|
@@ -1382,6 +1445,7 @@ async function stopWorkspaceApps(ws) {
|
|
|
1382
1445
|
}
|
|
1383
1446
|
}
|
|
1384
1447
|
log(`stopping local apps on ports ${[...ports].join(", ") || "(none)"}`);
|
|
1448
|
+
await closeRememberedTerminals(ws.sandboxId);
|
|
1385
1449
|
for (const port of ports) {
|
|
1386
1450
|
await killPort(port);
|
|
1387
1451
|
}
|
|
@@ -1413,12 +1477,10 @@ async function waitForUrlInFile(file, timeoutMs = 90_000) {
|
|
|
1413
1477
|
}
|
|
1414
1478
|
|
|
1415
1479
|
function cloudflaredCommand(localUrl, logFile) {
|
|
1416
|
-
const
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
}
|
|
1421
|
-
return `${run} 2>&1 | tee ${JSON.stringify(logFile)}`;
|
|
1480
|
+
const logArg = JSON.stringify(logFile);
|
|
1481
|
+
const run = `npx --yes cloudflared tunnel --url ${localUrl} --no-autoupdate --logfile ${logArg}`;
|
|
1482
|
+
if (process.platform === "win32") return run;
|
|
1483
|
+
return `${run} 2>&1 | tee ${logArg}`;
|
|
1422
1484
|
}
|
|
1423
1485
|
|
|
1424
1486
|
async function startCloudflareTerminal(ws, role, localUrl) {
|
|
@@ -1439,6 +1501,7 @@ async function startCloudflareTerminal(ws, role, localUrl) {
|
|
|
1439
1501
|
folder,
|
|
1440
1502
|
command: cloudflaredCommand(localUrl, logFile),
|
|
1441
1503
|
launchKey: `${ws.sandboxId}:cf:${role}`,
|
|
1504
|
+
sandboxId: ws.sandboxId,
|
|
1442
1505
|
force: true,
|
|
1443
1506
|
});
|
|
1444
1507
|
if (!opened.ok) {
|
|
@@ -1501,22 +1564,52 @@ function writeTunnelEnv(ws, tunnels) {
|
|
|
1501
1564
|
}
|
|
1502
1565
|
}
|
|
1503
1566
|
|
|
1504
|
-
|
|
1505
|
-
const sandboxId = ws.sandboxId;
|
|
1506
|
-
const label = ws.sandboxName || "this sandbox";
|
|
1507
|
-
const folder = path.resolve(ws.folderPath || "");
|
|
1567
|
+
function reservedPortsFor(cfg, sandboxId) {
|
|
1508
1568
|
const reserved = new Set();
|
|
1509
1569
|
for (const other of cfg.workspaces || []) {
|
|
1510
1570
|
if (other.sandboxId !== sandboxId && other.port) {
|
|
1511
1571
|
reserved.add(Number(other.port));
|
|
1512
1572
|
}
|
|
1513
1573
|
}
|
|
1574
|
+
return reserved;
|
|
1575
|
+
}
|
|
1514
1576
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1577
|
+
function appsWanted(ws) {
|
|
1578
|
+
return Boolean(ws?.appsRequested);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
async function configureCloudflareForWorkspace(ws, cfg) {
|
|
1582
|
+
const sandboxId = ws.sandboxId;
|
|
1583
|
+
const label = ws.sandboxName || "this sandbox";
|
|
1584
|
+
|
|
1585
|
+
log(`Cloudflare queued for ${label}: stop apps and wait for Start`);
|
|
1517
1586
|
await stopCloudflare(sandboxId);
|
|
1518
1587
|
await stopWorkspaceApps(ws);
|
|
1588
|
+
await forgetLaunch(sandboxId);
|
|
1589
|
+
ws.cloudflarePending = true;
|
|
1590
|
+
ws.appsRequested = false;
|
|
1591
|
+
persistWorkspaceEntry(cfg, ws);
|
|
1519
1592
|
|
|
1593
|
+
return {
|
|
1594
|
+
sandboxId,
|
|
1595
|
+
folderPath: ws.folderPath,
|
|
1596
|
+
port: ws.port,
|
|
1597
|
+
pending: true,
|
|
1598
|
+
cloudflarePending: true,
|
|
1599
|
+
waitingForStart: true,
|
|
1600
|
+
warning:
|
|
1601
|
+
"Cloudflare is ready in Maintainer Pro. Use Start chat server when you want to launch the apps and create the public URLs.",
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
async function launchCloudflareTunnels(ws, cfg) {
|
|
1606
|
+
const sandboxId = ws.sandboxId;
|
|
1607
|
+
const label = ws.sandboxName || "this sandbox";
|
|
1608
|
+
const folder = path.resolve(ws.folderPath || "");
|
|
1609
|
+
const reserved = reservedPortsFor(cfg, sandboxId);
|
|
1610
|
+
|
|
1611
|
+
log(`Cloudflare start for ${label}: tunnel chat script/backend before UI`);
|
|
1612
|
+
try {
|
|
1520
1613
|
if (!cfg.noAiServer) {
|
|
1521
1614
|
await startAiServerForWorkspace(ws, { reserved, cfg });
|
|
1522
1615
|
await waitUntilReachable(
|
|
@@ -1596,6 +1689,8 @@ async function configureCloudflareForWorkspace(ws, cfg) {
|
|
|
1596
1689
|
ws.cloudflareUrl = appUrl;
|
|
1597
1690
|
ws.cloudflare = tunnels;
|
|
1598
1691
|
ws.appUrl = appUrl;
|
|
1692
|
+
ws.cloudflarePending = false;
|
|
1693
|
+
ws.appsRequested = true;
|
|
1599
1694
|
persistWorkspaceEntry(cfg, ws);
|
|
1600
1695
|
cloudflareTunnels.set(sandboxId, { tunnels: started });
|
|
1601
1696
|
clearProcessProblem(sandboxId, "cloudflare_launch", "tunnel");
|
|
@@ -1603,6 +1698,7 @@ async function configureCloudflareForWorkspace(ws, cfg) {
|
|
|
1603
1698
|
return {
|
|
1604
1699
|
sandboxId,
|
|
1605
1700
|
folderPath: ws.folderPath,
|
|
1701
|
+
port: ws.port,
|
|
1606
1702
|
appUrl,
|
|
1607
1703
|
origins: Object.values(tunnels).filter(Boolean),
|
|
1608
1704
|
tunnels,
|
|
@@ -1618,13 +1714,52 @@ async function configureCloudflareForWorkspace(ws, cfg) {
|
|
|
1618
1714
|
title: `Could not start Cloudflare (${label})`,
|
|
1619
1715
|
message,
|
|
1620
1716
|
resolution:
|
|
1621
|
-
"Install cloudflared or allow npx to download it, then
|
|
1622
|
-
actionCode: "
|
|
1717
|
+
"Install cloudflared or allow npx to download it, then use Start chat server again.",
|
|
1718
|
+
actionCode: "start_ai_server",
|
|
1623
1719
|
});
|
|
1624
1720
|
throw err;
|
|
1625
1721
|
}
|
|
1626
1722
|
}
|
|
1627
1723
|
|
|
1724
|
+
async function startAppsForWorkspace(ws, cfg) {
|
|
1725
|
+
if (ws.cloudflarePending) {
|
|
1726
|
+
return launchCloudflareTunnels(ws, cfg);
|
|
1727
|
+
}
|
|
1728
|
+
ws.appsRequested = true;
|
|
1729
|
+
persistWorkspaceEntry(cfg, ws);
|
|
1730
|
+
const reserved = reservedPortsFor(cfg, ws.sandboxId);
|
|
1731
|
+
if (!cfg.noAiServer) {
|
|
1732
|
+
await startAiServerForWorkspace(ws, { reserved, cfg });
|
|
1733
|
+
await sleep(1500);
|
|
1734
|
+
}
|
|
1735
|
+
const startedHosts = await ensureHostProcesses(ws, {
|
|
1736
|
+
reserved,
|
|
1737
|
+
cfg,
|
|
1738
|
+
force: true,
|
|
1739
|
+
});
|
|
1740
|
+
await sleep(800);
|
|
1741
|
+
await inspectHostJobs(ws);
|
|
1742
|
+
const up = await probeUrl(`http://127.0.0.1:${ws.port}/embed-config.js`);
|
|
1743
|
+
if (up) {
|
|
1744
|
+
clearProcessProblem(ws.sandboxId, "ai_server_launch", "ai");
|
|
1745
|
+
clearProcessProblem(ws.sandboxId, "apps_not_started");
|
|
1746
|
+
}
|
|
1747
|
+
const processIssues = issuesForSandbox(ws.sandboxId).map(
|
|
1748
|
+
({ role: _role, ...issue }) => issue
|
|
1749
|
+
);
|
|
1750
|
+
const warning = processIssues[0]?.message || null;
|
|
1751
|
+
return {
|
|
1752
|
+
up,
|
|
1753
|
+
startedHosts,
|
|
1754
|
+
sandboxId: ws.sandboxId,
|
|
1755
|
+
folderPath: ws.folderPath,
|
|
1756
|
+
port: ws.port,
|
|
1757
|
+
appUrl: ws.appUrl,
|
|
1758
|
+
processIssues,
|
|
1759
|
+
warning,
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1628
1763
|
async function ensureHostProcesses(ws, opts = {}) {
|
|
1629
1764
|
const reserved = opts.reserved instanceof Set ? opts.reserved : new Set();
|
|
1630
1765
|
const cfg = opts.cfg || null;
|
|
@@ -1704,6 +1839,7 @@ async function ensureHostProcesses(ws, opts = {}) {
|
|
|
1704
1839
|
command,
|
|
1705
1840
|
env: { PORT: String(port), ...extraEnv },
|
|
1706
1841
|
launchKey,
|
|
1842
|
+
sandboxId: ws.sandboxId,
|
|
1707
1843
|
force: Boolean(opts.force),
|
|
1708
1844
|
});
|
|
1709
1845
|
if (opened.skipped) continue;
|
|
@@ -1763,6 +1899,11 @@ async function inspectHostJobs(ws) {
|
|
|
1763
1899
|
clearProcessProblem(ws.sandboxId, "host_process_down", job.role);
|
|
1764
1900
|
continue;
|
|
1765
1901
|
}
|
|
1902
|
+
if (!appsWanted(ws)) {
|
|
1903
|
+
clearProcessProblem(ws.sandboxId, "host_process_launch", job.role);
|
|
1904
|
+
clearProcessProblem(ws.sandboxId, "host_process_down", job.role);
|
|
1905
|
+
continue;
|
|
1906
|
+
}
|
|
1766
1907
|
if (starting) continue;
|
|
1767
1908
|
const tried = launchedAt.has(launchKey);
|
|
1768
1909
|
recordProcessProblem({
|
|
@@ -1863,6 +2004,8 @@ async function setupWorkspace(cfg, action) {
|
|
|
1863
2004
|
clientKind: client.kind,
|
|
1864
2005
|
appUrl,
|
|
1865
2006
|
sameOrigin: Boolean(client.sameOrigin),
|
|
2007
|
+
appsRequested: false,
|
|
2008
|
+
cloudflarePending: false,
|
|
1866
2009
|
};
|
|
1867
2010
|
if (existing >= 0) cfg.workspaces[existing] = entry;
|
|
1868
2011
|
else cfg.workspaces.push(entry);
|
|
@@ -1881,12 +2024,6 @@ async function setupWorkspace(cfg, action) {
|
|
|
1881
2024
|
.join("\n"),
|
|
1882
2025
|
});
|
|
1883
2026
|
|
|
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
2027
|
await inspectHostJobs(entry);
|
|
1891
2028
|
|
|
1892
2029
|
const openUrl = client.sameOrigin
|
|
@@ -1895,23 +2032,22 @@ async function setupWorkspace(cfg, action) {
|
|
|
1895
2032
|
const aiServerUp = await probeUrl(`http://127.0.0.1:${entry.port}/embed-config.js`);
|
|
1896
2033
|
if (aiServerUp) {
|
|
1897
2034
|
clearProcessProblem(sandboxId, "ai_server_launch", "ai");
|
|
2035
|
+
clearProcessProblem(sandboxId, "apps_not_started");
|
|
1898
2036
|
}
|
|
1899
2037
|
|
|
1900
2038
|
const processIssues = issuesForSandbox(sandboxId).map(
|
|
1901
2039
|
({ role: _role, ...issue }) => issue
|
|
1902
2040
|
);
|
|
1903
|
-
const
|
|
2041
|
+
const waitingForStart = !aiServerUp;
|
|
2042
|
+
const warning = waitingForStart
|
|
2043
|
+
? "Folder is attached in Maintainer Pro. Use Start chat server when you want to launch the apps."
|
|
2044
|
+
: processIssues[0]?.message || null;
|
|
1904
2045
|
|
|
1905
2046
|
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}`);
|
|
2047
|
+
if (waitingForStart) {
|
|
2048
|
+
log(`folder attached — waiting for Start (${openUrl})`);
|
|
1913
2049
|
} else {
|
|
1914
|
-
log(
|
|
2050
|
+
log(`ai-server already up — open ${openUrl}`);
|
|
1915
2051
|
}
|
|
1916
2052
|
|
|
1917
2053
|
return {
|
|
@@ -1925,10 +2061,11 @@ async function setupWorkspace(cfg, action) {
|
|
|
1925
2061
|
clientFiles: client.filesWritten,
|
|
1926
2062
|
clientNotes: client.notes,
|
|
1927
2063
|
aiServerUp,
|
|
1928
|
-
startedHosts,
|
|
2064
|
+
startedHosts: [],
|
|
1929
2065
|
openUrl,
|
|
1930
2066
|
processIssues,
|
|
1931
2067
|
warning,
|
|
2068
|
+
waitingForStart,
|
|
1932
2069
|
projectInfo,
|
|
1933
2070
|
};
|
|
1934
2071
|
}
|
|
@@ -1970,12 +2107,6 @@ async function runActions(cfg, actions) {
|
|
|
1970
2107
|
ok = false;
|
|
1971
2108
|
result = { error: "No workspace or --no-ai-server" };
|
|
1972
2109
|
} 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
2110
|
const problem = issuesForSandbox(ws.sandboxId)
|
|
1980
2111
|
.map((issue) => issue.message)
|
|
1981
2112
|
.join("\n");
|
|
@@ -1985,32 +2116,15 @@ async function runActions(cfg, actions) {
|
|
|
1985
2116
|
problem ||
|
|
1986
2117
|
"Local processes are not running or the project setup looks incomplete.",
|
|
1987
2118
|
});
|
|
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
2119
|
result = {
|
|
2002
|
-
|
|
2003
|
-
startedHosts,
|
|
2004
|
-
sandboxId: ws.sandboxId,
|
|
2005
|
-
folderPath: ws.folderPath,
|
|
2006
|
-
port: ws.port,
|
|
2007
|
-
appUrl: ws.appUrl,
|
|
2008
|
-
processIssues,
|
|
2009
|
-
warning,
|
|
2120
|
+
...(await startAppsForWorkspace(ws, cfg)),
|
|
2010
2121
|
projectInfo,
|
|
2011
2122
|
};
|
|
2012
|
-
if (
|
|
2013
|
-
result.
|
|
2123
|
+
if (
|
|
2124
|
+
Array.isArray(result.processIssues) &&
|
|
2125
|
+
result.processIssues.some((issue) => issue.code === "ai_server_launch")
|
|
2126
|
+
) {
|
|
2127
|
+
result.error = result.warning;
|
|
2014
2128
|
ok = false;
|
|
2015
2129
|
}
|
|
2016
2130
|
}
|
|
@@ -2051,7 +2165,7 @@ async function runActions(cfg, actions) {
|
|
|
2051
2165
|
const sandboxId = String(
|
|
2052
2166
|
action.sandboxId || action.payload?.sandboxId || ""
|
|
2053
2167
|
);
|
|
2054
|
-
forgetLaunch(sandboxId);
|
|
2168
|
+
await forgetLaunch(sandboxId);
|
|
2055
2169
|
cfg.workspaces = (cfg.workspaces || []).filter(
|
|
2056
2170
|
(w) => w.sandboxId !== sandboxId
|
|
2057
2171
|
);
|
|
@@ -2100,6 +2214,7 @@ async function collectWorkspaceStates(cfg) {
|
|
|
2100
2214
|
aiServerUp: up,
|
|
2101
2215
|
startingAi: recentlyLaunched(`${ws.sandboxId}:${folder}:ai`),
|
|
2102
2216
|
appUrl: ws.appUrl || null,
|
|
2217
|
+
appsRequested: appsWanted(ws),
|
|
2103
2218
|
});
|
|
2104
2219
|
}
|
|
2105
2220
|
return localStates;
|
|
@@ -2122,6 +2237,7 @@ async function sendHeartbeat(cfg, folders, localStates) {
|
|
|
2122
2237
|
aiServerUp: st.aiServerUp,
|
|
2123
2238
|
port: st.port,
|
|
2124
2239
|
appUrl: st.appUrl || undefined,
|
|
2240
|
+
appsRequested: Boolean(st.appsRequested),
|
|
2125
2241
|
})),
|
|
2126
2242
|
}
|
|
2127
2243
|
);
|
|
@@ -2145,6 +2261,19 @@ async function buildIssues(cfg, workspaceStates) {
|
|
|
2145
2261
|
}
|
|
2146
2262
|
for (const st of workspaceStates) {
|
|
2147
2263
|
if (st.aiServerUp || st.startingAi) continue;
|
|
2264
|
+
if (!st.appsRequested) {
|
|
2265
|
+
issues.push({
|
|
2266
|
+
code: "apps_not_started",
|
|
2267
|
+
severity: "info",
|
|
2268
|
+
title: `Apps are not running (${st.sandboxName || "sandbox"})`,
|
|
2269
|
+
message:
|
|
2270
|
+
"This folder is attached in Maintainer Pro. Use Start chat server when you want to launch the local apps.",
|
|
2271
|
+
resolution: "Use Start chat server.",
|
|
2272
|
+
actionCode: "start_ai_server",
|
|
2273
|
+
sandboxId: st.sandboxId,
|
|
2274
|
+
});
|
|
2275
|
+
continue;
|
|
2276
|
+
}
|
|
2148
2277
|
const launch = [...processProblems.values()].find(
|
|
2149
2278
|
(issue) =>
|
|
2150
2279
|
issue.sandboxId === st.sandboxId && issue.code === "ai_server_launch"
|
|
@@ -2281,13 +2410,18 @@ async function main() {
|
|
|
2281
2410
|
const up = await probeUrl(
|
|
2282
2411
|
`http://127.0.0.1:${ws.port}/embed-config.js`
|
|
2283
2412
|
);
|
|
2284
|
-
if (!cfg.noAiServer && !up) {
|
|
2413
|
+
if (appsWanted(ws) && !cfg.noAiServer && !up) {
|
|
2285
2414
|
await startAiServerForWorkspace(ws, { reserved, cfg });
|
|
2286
2415
|
} else if (ws.port) {
|
|
2287
2416
|
reserved.add(Number(ws.port));
|
|
2288
|
-
if (up)
|
|
2417
|
+
if (up) {
|
|
2418
|
+
clearProcessProblem(ws.sandboxId, "ai_server_launch", "ai");
|
|
2419
|
+
clearProcessProblem(ws.sandboxId, "apps_not_started");
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
if (appsWanted(ws)) {
|
|
2423
|
+
await ensureHostProcesses(ws, { reserved, cfg });
|
|
2289
2424
|
}
|
|
2290
|
-
await ensureHostProcesses(ws, { reserved, cfg });
|
|
2291
2425
|
await inspectHostJobs(ws);
|
|
2292
2426
|
localStates.push({
|
|
2293
2427
|
sandboxId: ws.sandboxId,
|
|
@@ -2297,6 +2431,7 @@ async function main() {
|
|
|
2297
2431
|
aiServerUp: up,
|
|
2298
2432
|
startingAi: recentlyLaunched(`${ws.sandboxId}:${folder}:ai`),
|
|
2299
2433
|
appUrl: ws.appUrl || null,
|
|
2434
|
+
appsRequested: appsWanted(ws),
|
|
2300
2435
|
});
|
|
2301
2436
|
}
|
|
2302
2437
|
|