assistme 0.1.11 → 0.1.13
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 +135 -228
- package/package.json +1 -1
- package/src/index.ts +4 -10
- package/src/tools/browser.ts +196 -124
package/dist/index.js
CHANGED
|
@@ -393,8 +393,9 @@ import {
|
|
|
393
393
|
// src/tools/browser.ts
|
|
394
394
|
import { WebSocket } from "ws";
|
|
395
395
|
import { execSync, spawn } from "child_process";
|
|
396
|
-
import { platform } from "os";
|
|
397
|
-
import { existsSync, unlinkSync } from "fs";
|
|
396
|
+
import { platform, homedir } from "os";
|
|
397
|
+
import { existsSync, unlinkSync, mkdirSync, cpSync } from "fs";
|
|
398
|
+
import { join } from "path";
|
|
398
399
|
var BrowserController = class {
|
|
399
400
|
ws = null;
|
|
400
401
|
debugPort;
|
|
@@ -903,148 +904,97 @@ function findChromePath() {
|
|
|
903
904
|
}
|
|
904
905
|
return null;
|
|
905
906
|
}
|
|
906
|
-
function
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
return
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
encoding: "utf-8",
|
|
920
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
921
|
-
});
|
|
922
|
-
return out3.trim().length > 0;
|
|
923
|
-
}
|
|
924
|
-
const out2 = execSync(
|
|
925
|
-
'pgrep -f "(Google Chrome|Microsoft Edge|Brave Browser|Chromium).app/Contents/MacOS/"',
|
|
926
|
-
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
927
|
-
);
|
|
928
|
-
return out2.trim().length > 0;
|
|
929
|
-
}
|
|
930
|
-
if (chromePath) {
|
|
931
|
-
const out2 = execSync(`pgrep -f ${JSON.stringify(chromePath)} 2>/dev/null || true`, {
|
|
932
|
-
encoding: "utf-8",
|
|
933
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
934
|
-
});
|
|
935
|
-
return out2.trim().length > 0;
|
|
936
|
-
}
|
|
937
|
-
const out = execSync("pgrep -f '(chrome|chromium|msedge|brave)' 2>/dev/null || true", {
|
|
938
|
-
encoding: "utf-8",
|
|
939
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
940
|
-
});
|
|
941
|
-
return out.trim().length > 0;
|
|
942
|
-
} catch {
|
|
943
|
-
return false;
|
|
907
|
+
function getDefaultProfileDir(chromePath) {
|
|
908
|
+
const home = homedir();
|
|
909
|
+
const os = platform();
|
|
910
|
+
if (os === "darwin") {
|
|
911
|
+
if (chromePath.includes("Brave Browser"))
|
|
912
|
+
return join(home, "Library", "Application Support", "BraveSoftware", "Brave-Browser");
|
|
913
|
+
if (chromePath.includes("Microsoft Edge"))
|
|
914
|
+
return join(home, "Library", "Application Support", "Microsoft Edge");
|
|
915
|
+
if (chromePath.includes("Chromium"))
|
|
916
|
+
return join(home, "Library", "Application Support", "Chromium");
|
|
917
|
+
if (chromePath.includes("Canary"))
|
|
918
|
+
return join(home, "Library", "Application Support", "Google", "Chrome Canary");
|
|
919
|
+
return join(home, "Library", "Application Support", "Google", "Chrome");
|
|
944
920
|
}
|
|
921
|
+
if (os === "win32") {
|
|
922
|
+
const appData = process.env.LOCALAPPDATA || join(home, "AppData", "Local");
|
|
923
|
+
if (chromePath.includes("brave"))
|
|
924
|
+
return join(appData, "BraveSoftware", "Brave-Browser", "User Data");
|
|
925
|
+
if (chromePath.includes("msedge")) return join(appData, "Microsoft", "Edge", "User Data");
|
|
926
|
+
return join(appData, "Google", "Chrome", "User Data");
|
|
927
|
+
}
|
|
928
|
+
if (chromePath.includes("brave")) return join(home, ".config", "BraveSoftware", "Brave-Browser");
|
|
929
|
+
if (chromePath.includes("microsoft-edge")) return join(home, ".config", "microsoft-edge");
|
|
930
|
+
if (chromePath.includes("chromium")) return join(home, ".config", "chromium");
|
|
931
|
+
return join(home, ".config", "google-chrome");
|
|
945
932
|
}
|
|
946
|
-
function
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
if (
|
|
950
|
-
|
|
951
|
-
|
|
933
|
+
function getDebugProfileDir(chromePath) {
|
|
934
|
+
const home = homedir();
|
|
935
|
+
const debugDir = join(home, ".assistme", "browser-profile");
|
|
936
|
+
if (!existsSync(debugDir)) {
|
|
937
|
+
mkdirSync(debugDir, { recursive: true });
|
|
938
|
+
log.debug(`Created debug profile directory: ${debugDir}`);
|
|
939
|
+
const realDir = getDefaultProfileDir(chromePath);
|
|
940
|
+
if (existsSync(realDir)) {
|
|
941
|
+
seedDebugProfile(realDir, debugDir);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return debugDir;
|
|
952
945
|
}
|
|
953
|
-
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
966
|
-
});
|
|
967
|
-
} else if (os === "win32") {
|
|
968
|
-
const exe = chromePath.split("\\").pop() || "chrome.exe";
|
|
969
|
-
execSync(`taskkill /IM "${exe}"`, {
|
|
970
|
-
timeout: 5e3,
|
|
971
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
} catch {
|
|
975
|
-
}
|
|
976
|
-
const start = Date.now();
|
|
977
|
-
while (Date.now() - start < 8e3) {
|
|
978
|
-
if (!isChromeRunning(chromePath)) {
|
|
979
|
-
log.debug(`Browser exited after ${Date.now() - start}ms`);
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
983
|
-
}
|
|
984
|
-
log.debug("Browser still running after graceful quit, force-killing...");
|
|
985
|
-
try {
|
|
986
|
-
if (os === "win32") {
|
|
987
|
-
const exe = chromePath.split("\\").pop() || "chrome.exe";
|
|
988
|
-
execSync(`taskkill /F /IM "${exe}"`, {
|
|
989
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
990
|
-
});
|
|
991
|
-
} else {
|
|
992
|
-
execSync(`pkill -9 -f ${JSON.stringify(chromePath)}`, {
|
|
993
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
994
|
-
});
|
|
946
|
+
function seedDebugProfile(realDir, debugDir) {
|
|
947
|
+
const rootFiles = ["Local State"];
|
|
948
|
+
const profileFiles = ["Bookmarks", "Preferences", "Favicons", "Top Sites", "Shortcuts"];
|
|
949
|
+
for (const file of rootFiles) {
|
|
950
|
+
const src = join(realDir, file);
|
|
951
|
+
const dest = join(debugDir, file);
|
|
952
|
+
try {
|
|
953
|
+
if (existsSync(src)) {
|
|
954
|
+
cpSync(src, dest, { force: true });
|
|
955
|
+
log.debug(`Seeded: ${file}`);
|
|
956
|
+
}
|
|
957
|
+
} catch {
|
|
995
958
|
}
|
|
996
|
-
} catch {
|
|
997
959
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
`${home}/.config/chromium`,
|
|
1010
|
-
`${home}/.config/microsoft-edge`,
|
|
1011
|
-
`${home}/.config/BraveSoftware/Brave-Browser`
|
|
1012
|
-
];
|
|
1013
|
-
for (const dir of profileDirs) {
|
|
1014
|
-
for (const suffix of lockSuffixes) {
|
|
1015
|
-
const lockPath = `${dir}/${suffix}`;
|
|
1016
|
-
try {
|
|
1017
|
-
if (existsSync(lockPath)) {
|
|
1018
|
-
unlinkSync(lockPath);
|
|
1019
|
-
log.debug(`Removed stale lock: ${lockPath}`);
|
|
1020
|
-
}
|
|
1021
|
-
} catch {
|
|
1022
|
-
}
|
|
960
|
+
const srcProfile = join(realDir, "Default");
|
|
961
|
+
const destProfile = join(debugDir, "Default");
|
|
962
|
+
if (existsSync(srcProfile)) {
|
|
963
|
+
mkdirSync(destProfile, { recursive: true });
|
|
964
|
+
for (const file of profileFiles) {
|
|
965
|
+
const src = join(srcProfile, file);
|
|
966
|
+
const dest = join(destProfile, file);
|
|
967
|
+
try {
|
|
968
|
+
if (existsSync(src)) {
|
|
969
|
+
cpSync(src, dest, { force: true });
|
|
970
|
+
log.debug(`Seeded: Default/${file}`);
|
|
1023
971
|
}
|
|
972
|
+
} catch {
|
|
1024
973
|
}
|
|
1025
974
|
}
|
|
975
|
+
const srcExt = join(srcProfile, "Extensions");
|
|
976
|
+
const destExt = join(destProfile, "Extensions");
|
|
977
|
+
try {
|
|
978
|
+
if (existsSync(srcExt)) {
|
|
979
|
+
cpSync(srcExt, destExt, { recursive: true, force: true });
|
|
980
|
+
log.debug("Seeded: Default/Extensions");
|
|
981
|
+
}
|
|
982
|
+
} catch {
|
|
983
|
+
}
|
|
1026
984
|
}
|
|
1027
985
|
}
|
|
1028
986
|
function spawnChrome(chromePath, port) {
|
|
1029
|
-
const
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
});
|
|
1041
|
-
} else {
|
|
1042
|
-
log.debug(`Spawning Chrome: ${chromePath} ${cdpFlag} --restore-last-session`);
|
|
1043
|
-
child = spawn(chromePath, [cdpFlag, "--restore-last-session"], {
|
|
1044
|
-
detached: true,
|
|
1045
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
987
|
+
const profileDir = getDebugProfileDir(chromePath);
|
|
988
|
+
const flags = [
|
|
989
|
+
`--remote-debugging-port=${port}`,
|
|
990
|
+
`--user-data-dir=${profileDir}`,
|
|
991
|
+
"--restore-last-session"
|
|
992
|
+
];
|
|
993
|
+
log.debug(`Spawning browser: ${chromePath} ${flags.join(" ")}`);
|
|
994
|
+
const child = spawn(chromePath, flags, {
|
|
995
|
+
detached: true,
|
|
996
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
997
|
+
});
|
|
1048
998
|
let stderr = "";
|
|
1049
999
|
child.stderr?.on("data", (chunk) => {
|
|
1050
1000
|
stderr += chunk.toString();
|
|
@@ -1086,7 +1036,7 @@ async function isPortInUse(port) {
|
|
|
1086
1036
|
signal: AbortSignal.timeout(1e3)
|
|
1087
1037
|
});
|
|
1088
1038
|
const body = await res.text();
|
|
1089
|
-
return !body.includes("
|
|
1039
|
+
return !body.includes("webSocketDebuggerUrl");
|
|
1090
1040
|
} catch {
|
|
1091
1041
|
return false;
|
|
1092
1042
|
}
|
|
@@ -1111,71 +1061,34 @@ async function ensureBrowserAvailable(port = 9222) {
|
|
|
1111
1061
|
return { success: false, action: "chrome_not_found" };
|
|
1112
1062
|
}
|
|
1113
1063
|
log.debug(`Found Chrome at: ${chromePath}`);
|
|
1114
|
-
|
|
1115
|
-
log.debug(`Browser currently running: ${running}`);
|
|
1116
|
-
if (running) {
|
|
1117
|
-
log.debug("Killing browser gracefully for restart with CDP...");
|
|
1118
|
-
await killChromeGracefully(chromePath);
|
|
1119
|
-
if (isChromeRunning(chromePath)) {
|
|
1120
|
-
log.debug("Browser still running after kill attempt \u2014 cannot restart with CDP");
|
|
1121
|
-
return {
|
|
1122
|
-
success: false,
|
|
1123
|
-
action: "launch_failed",
|
|
1124
|
-
chromePath,
|
|
1125
|
-
detail: "Could not stop the existing browser process. Please quit the browser manually and run assistme again."
|
|
1126
|
-
};
|
|
1127
|
-
}
|
|
1128
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
1129
|
-
const child2 = spawnChrome(chromePath, port);
|
|
1130
|
-
if (await waitForCDP(browser)) {
|
|
1131
|
-
return { success: true, action: "restarted", chromePath };
|
|
1132
|
-
}
|
|
1133
|
-
if (child2.exitCode !== null) {
|
|
1134
|
-
log.debug(
|
|
1135
|
-
`Browser process already exited (code ${child2.exitCode}) \u2014 may have crashed or profile is locked`
|
|
1136
|
-
);
|
|
1137
|
-
return {
|
|
1138
|
-
success: false,
|
|
1139
|
-
action: "launch_failed",
|
|
1140
|
-
chromePath,
|
|
1141
|
-
detail: `Browser exited immediately (code ${child2.exitCode}). The profile may be locked. Try closing all browser windows first, then run assistme again.`
|
|
1142
|
-
};
|
|
1143
|
-
}
|
|
1144
|
-
log.debug("First CDP wait timed out after restart, retrying...");
|
|
1145
|
-
if (await waitForCDP(browser, 15e3)) {
|
|
1146
|
-
return { success: true, action: "restarted", chromePath };
|
|
1147
|
-
}
|
|
1148
|
-
const stillRunning2 = isChromeRunning(chromePath);
|
|
1149
|
-
return {
|
|
1150
|
-
success: false,
|
|
1151
|
-
action: "launch_failed",
|
|
1152
|
-
chromePath,
|
|
1153
|
-
detail: stillRunning2 ? "Browser is running but CDP port is not responding. Try: 1) Quit the browser completely, 2) Run assistme again." : "Browser was restarted but exited unexpectedly. Try launching it manually to check for errors."
|
|
1154
|
-
};
|
|
1155
|
-
}
|
|
1156
|
-
const child = spawnChrome(chromePath, port);
|
|
1064
|
+
spawnChrome(chromePath, port);
|
|
1157
1065
|
if (await waitForCDP(browser)) {
|
|
1158
1066
|
return { success: true, action: "launched", chromePath };
|
|
1159
1067
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1068
|
+
const debugDir = getDebugProfileDir(chromePath);
|
|
1069
|
+
const lockPath = join(debugDir, "SingletonLock");
|
|
1070
|
+
if (existsSync(lockPath)) {
|
|
1071
|
+
log.debug("Found stale SingletonLock in debug profile \u2014 removing and retrying");
|
|
1072
|
+
try {
|
|
1073
|
+
unlinkSync(lockPath);
|
|
1074
|
+
for (const f of ["SingletonSocket", "SingletonCookie"]) {
|
|
1075
|
+
try {
|
|
1076
|
+
unlinkSync(join(debugDir, f));
|
|
1077
|
+
} catch {
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
} catch {
|
|
1081
|
+
}
|
|
1082
|
+
spawnChrome(chromePath, port);
|
|
1083
|
+
if (await waitForCDP(browser, 15e3)) {
|
|
1084
|
+
return { success: true, action: "launched", chromePath };
|
|
1085
|
+
}
|
|
1172
1086
|
}
|
|
1173
|
-
const stillRunning = isChromeRunning(chromePath);
|
|
1174
1087
|
return {
|
|
1175
1088
|
success: false,
|
|
1176
1089
|
action: "launch_failed",
|
|
1177
1090
|
chromePath,
|
|
1178
|
-
detail:
|
|
1091
|
+
detail: "Could not start browser with remote debugging. Possible causes:\n 1) Another assistme debug browser is already using port " + port + "\n 2) The browser crashed on startup\nTry: rm -rf ~/.assistme/browser-profile && assistme"
|
|
1179
1092
|
};
|
|
1180
1093
|
}
|
|
1181
1094
|
var browserInstance = null;
|
|
@@ -1326,16 +1239,16 @@ var MemoryManager = class {
|
|
|
1326
1239
|
// src/agent/skills.ts
|
|
1327
1240
|
import {
|
|
1328
1241
|
existsSync as existsSync2,
|
|
1329
|
-
mkdirSync,
|
|
1330
|
-
readdirSync,
|
|
1242
|
+
mkdirSync as mkdirSync2,
|
|
1243
|
+
readdirSync as readdirSync2,
|
|
1331
1244
|
readFileSync,
|
|
1332
1245
|
writeFileSync,
|
|
1333
1246
|
statSync,
|
|
1334
1247
|
unlinkSync as unlinkSync2,
|
|
1335
1248
|
rmSync
|
|
1336
1249
|
} from "fs";
|
|
1337
|
-
import { join, basename, dirname } from "path";
|
|
1338
|
-
import { homedir } from "os";
|
|
1250
|
+
import { join as join2, basename, dirname } from "path";
|
|
1251
|
+
import { homedir as homedir2 } from "os";
|
|
1339
1252
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
1340
1253
|
"the",
|
|
1341
1254
|
"a",
|
|
@@ -1455,8 +1368,8 @@ function bigrams(tokens) {
|
|
|
1455
1368
|
}
|
|
1456
1369
|
return result;
|
|
1457
1370
|
}
|
|
1458
|
-
var SKILLS_DIR =
|
|
1459
|
-
var BUNDLED_SKILLS_DIR =
|
|
1371
|
+
var SKILLS_DIR = join2(homedir2(), ".config", "assistme", "skills");
|
|
1372
|
+
var BUNDLED_SKILLS_DIR = join2(
|
|
1460
1373
|
new URL(".", import.meta.url).pathname,
|
|
1461
1374
|
"..",
|
|
1462
1375
|
"..",
|
|
@@ -1464,7 +1377,7 @@ var BUNDLED_SKILLS_DIR = join(
|
|
|
1464
1377
|
);
|
|
1465
1378
|
function ensureSkillsDir() {
|
|
1466
1379
|
if (!existsSync2(SKILLS_DIR)) {
|
|
1467
|
-
|
|
1380
|
+
mkdirSync2(SKILLS_DIR, { recursive: true });
|
|
1468
1381
|
}
|
|
1469
1382
|
}
|
|
1470
1383
|
function parseSkillFile(filePath, source = "user") {
|
|
@@ -1540,13 +1453,13 @@ var SkillManager = class {
|
|
|
1540
1453
|
}
|
|
1541
1454
|
loadFromDir(dir, source) {
|
|
1542
1455
|
try {
|
|
1543
|
-
const entries =
|
|
1456
|
+
const entries = readdirSync2(dir);
|
|
1544
1457
|
for (const entry of entries) {
|
|
1545
|
-
const fullPath =
|
|
1458
|
+
const fullPath = join2(dir, entry);
|
|
1546
1459
|
const stat2 = statSync(fullPath);
|
|
1547
1460
|
if (stat2.isDirectory()) {
|
|
1548
|
-
const skillMd =
|
|
1549
|
-
const skillMdLower =
|
|
1461
|
+
const skillMd = join2(fullPath, "SKILL.md");
|
|
1462
|
+
const skillMdLower = join2(fullPath, "skill.md");
|
|
1550
1463
|
const mdPath = existsSync2(skillMd) ? skillMd : existsSync2(skillMdLower) ? skillMdLower : null;
|
|
1551
1464
|
if (mdPath) {
|
|
1552
1465
|
const skill = parseSkillFile(mdPath, source);
|
|
@@ -1655,9 +1568,9 @@ var SkillManager = class {
|
|
|
1655
1568
|
*/
|
|
1656
1569
|
create(name, description, content) {
|
|
1657
1570
|
ensureSkillsDir();
|
|
1658
|
-
const skillDir =
|
|
1659
|
-
|
|
1660
|
-
const filePath =
|
|
1571
|
+
const skillDir = join2(SKILLS_DIR, name);
|
|
1572
|
+
mkdirSync2(skillDir, { recursive: true });
|
|
1573
|
+
const filePath = join2(skillDir, "SKILL.md");
|
|
1661
1574
|
const fileContent = `---
|
|
1662
1575
|
name: ${name}
|
|
1663
1576
|
description: ${description}
|
|
@@ -1696,7 +1609,7 @@ ${content}
|
|
|
1696
1609
|
gitUrl += ".git";
|
|
1697
1610
|
}
|
|
1698
1611
|
const name = basename(gitUrl, ".git");
|
|
1699
|
-
const targetDir =
|
|
1612
|
+
const targetDir = join2(SKILLS_DIR, name);
|
|
1700
1613
|
if (existsSync2(targetDir)) {
|
|
1701
1614
|
throw new Error(`Skill "${name}" already exists. Remove it first.`);
|
|
1702
1615
|
}
|
|
@@ -1713,8 +1626,8 @@ ${content}
|
|
|
1713
1626
|
{ cause: err }
|
|
1714
1627
|
);
|
|
1715
1628
|
}
|
|
1716
|
-
const skillMd =
|
|
1717
|
-
const skillMdLower =
|
|
1629
|
+
const skillMd = join2(targetDir, "SKILL.md");
|
|
1630
|
+
const skillMdLower = join2(targetDir, "skill.md");
|
|
1718
1631
|
if (!existsSync2(skillMd) && !existsSync2(skillMdLower)) {
|
|
1719
1632
|
rmSync(targetDir, { recursive: true, force: true });
|
|
1720
1633
|
throw new Error(
|
|
@@ -1740,9 +1653,9 @@ ${content}
|
|
|
1740
1653
|
throw new Error(`HTTP ${dlResp.status}`);
|
|
1741
1654
|
}
|
|
1742
1655
|
const buffer = Buffer.from(await dlResp.arrayBuffer());
|
|
1743
|
-
const skillDir =
|
|
1744
|
-
|
|
1745
|
-
const zipPath =
|
|
1656
|
+
const skillDir = join2(SKILLS_DIR, name);
|
|
1657
|
+
mkdirSync2(skillDir, { recursive: true });
|
|
1658
|
+
const zipPath = join2(skillDir, "_download.zip");
|
|
1746
1659
|
writeFileSync(zipPath, buffer);
|
|
1747
1660
|
const { exec: execCb } = await import("child_process");
|
|
1748
1661
|
const { promisify: promisifyUtil } = await import("util");
|
|
@@ -1761,12 +1674,12 @@ ${content}
|
|
|
1761
1674
|
throw new Error(`Could not fetch SKILL.md`);
|
|
1762
1675
|
}
|
|
1763
1676
|
writeFileSync(
|
|
1764
|
-
|
|
1677
|
+
join2(skillDir, "SKILL.md"),
|
|
1765
1678
|
await fileResp.text(),
|
|
1766
1679
|
"utf-8"
|
|
1767
1680
|
);
|
|
1768
1681
|
}
|
|
1769
|
-
const skillMd =
|
|
1682
|
+
const skillMd = join2(skillDir, "SKILL.md");
|
|
1770
1683
|
if (!existsSync2(skillMd)) {
|
|
1771
1684
|
rmSync(skillDir, { recursive: true, force: true });
|
|
1772
1685
|
throw new Error("No SKILL.md in downloaded package");
|
|
@@ -2204,7 +2117,7 @@ import { z } from "zod/v4";
|
|
|
2204
2117
|
|
|
2205
2118
|
// src/tools/filesystem.ts
|
|
2206
2119
|
import { readFile, writeFile, readdir, stat, mkdir } from "fs/promises";
|
|
2207
|
-
import { resolve, relative, join as
|
|
2120
|
+
import { resolve, relative, join as join3 } from "path";
|
|
2208
2121
|
import { glob } from "glob";
|
|
2209
2122
|
function assertWithinWorkspace(filePath) {
|
|
2210
2123
|
const config = getConfig();
|
|
@@ -2241,7 +2154,7 @@ async function searchFiles(pattern, directory) {
|
|
|
2241
2154
|
ignore: ["node_modules/**", ".git/**", "dist/**", ".next/**"]
|
|
2242
2155
|
});
|
|
2243
2156
|
if (matches.length === 0) return "No files found matching the pattern.";
|
|
2244
|
-
return matches.slice(0, 50).map((m) => relative(config.workspacePath,
|
|
2157
|
+
return matches.slice(0, 50).map((m) => relative(config.workspacePath, join3(cwd, m))).join("\n");
|
|
2245
2158
|
}
|
|
2246
2159
|
async function listDirectory(path) {
|
|
2247
2160
|
const config = getConfig();
|
|
@@ -2251,7 +2164,7 @@ async function listDirectory(path) {
|
|
|
2251
2164
|
for (const entry of entries) {
|
|
2252
2165
|
if (entry.name.startsWith(".") && entry.name !== ".env.example") continue;
|
|
2253
2166
|
const icon = entry.isDirectory() ? "\u{1F4C1}" : "\u{1F4C4}";
|
|
2254
|
-
const info = entry.isFile() ? await stat(
|
|
2167
|
+
const info = entry.isFile() ? await stat(join3(resolved, entry.name)).then(
|
|
2255
2168
|
(s) => ` (${formatSize(s.size)})`
|
|
2256
2169
|
) : "";
|
|
2257
2170
|
results.push(`${icon} ${entry.name}${info}`);
|
|
@@ -2275,11 +2188,11 @@ async function searchContent(pattern, fileGlob, directory) {
|
|
|
2275
2188
|
const results = [];
|
|
2276
2189
|
for (const file of files.slice(0, 200)) {
|
|
2277
2190
|
try {
|
|
2278
|
-
const content = await readFile(
|
|
2191
|
+
const content = await readFile(join3(cwd, file), "utf-8");
|
|
2279
2192
|
const lines = content.split("\n");
|
|
2280
2193
|
for (let i = 0; i < lines.length; i++) {
|
|
2281
2194
|
if (regex.test(lines[i])) {
|
|
2282
|
-
const relPath = relative(config.workspacePath,
|
|
2195
|
+
const relPath = relative(config.workspacePath, join3(cwd, file));
|
|
2283
2196
|
results.push(`${relPath}:${i + 1}: ${lines[i].trim()}`);
|
|
2284
2197
|
regex.lastIndex = 0;
|
|
2285
2198
|
if (results.length >= 30) break;
|
|
@@ -3331,13 +3244,10 @@ browserCmd.command("setup").description("Set up Chrome for AI control").option("
|
|
|
3331
3244
|
if (result.success) {
|
|
3332
3245
|
switch (result.action) {
|
|
3333
3246
|
case "already_available":
|
|
3334
|
-
spinner.succeed("
|
|
3247
|
+
spinner.succeed("Browser is already running with remote debugging enabled");
|
|
3335
3248
|
break;
|
|
3336
3249
|
case "launched":
|
|
3337
|
-
spinner.succeed("
|
|
3338
|
-
break;
|
|
3339
|
-
case "restarted":
|
|
3340
|
-
spinner.succeed("Chrome restarted with remote debugging enabled (tabs restored)");
|
|
3250
|
+
spinner.succeed("Browser launched with remote debugging (debug profile)");
|
|
3341
3251
|
break;
|
|
3342
3252
|
}
|
|
3343
3253
|
console.log(chalk.dim("\n You can now run: assistme start\n"));
|
|
@@ -3430,13 +3340,10 @@ program.command("start", { isDefault: true }).description("Start the agent and l
|
|
|
3430
3340
|
if (launchResult.success) {
|
|
3431
3341
|
switch (launchResult.action) {
|
|
3432
3342
|
case "already_available":
|
|
3433
|
-
launchSpinner.succeed("
|
|
3343
|
+
launchSpinner.succeed("Browser detected (CDP port 9222)");
|
|
3434
3344
|
break;
|
|
3435
3345
|
case "launched":
|
|
3436
|
-
launchSpinner.succeed("
|
|
3437
|
-
break;
|
|
3438
|
-
case "restarted":
|
|
3439
|
-
launchSpinner.succeed("Chrome restarted with remote debugging enabled (tabs restored)");
|
|
3346
|
+
launchSpinner.succeed("Browser launched with remote debugging (debug profile)");
|
|
3440
3347
|
break;
|
|
3441
3348
|
}
|
|
3442
3349
|
} else {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -191,13 +191,10 @@ browserCmd
|
|
|
191
191
|
if (result.success) {
|
|
192
192
|
switch (result.action) {
|
|
193
193
|
case "already_available":
|
|
194
|
-
spinner.succeed("
|
|
194
|
+
spinner.succeed("Browser is already running with remote debugging enabled");
|
|
195
195
|
break;
|
|
196
196
|
case "launched":
|
|
197
|
-
spinner.succeed("
|
|
198
|
-
break;
|
|
199
|
-
case "restarted":
|
|
200
|
-
spinner.succeed("Chrome restarted with remote debugging enabled (tabs restored)");
|
|
197
|
+
spinner.succeed("Browser launched with remote debugging (debug profile)");
|
|
201
198
|
break;
|
|
202
199
|
}
|
|
203
200
|
console.log(chalk.dim("\n You can now run: assistme start\n"));
|
|
@@ -318,13 +315,10 @@ program
|
|
|
318
315
|
if (launchResult.success) {
|
|
319
316
|
switch (launchResult.action) {
|
|
320
317
|
case "already_available":
|
|
321
|
-
launchSpinner.succeed("
|
|
318
|
+
launchSpinner.succeed("Browser detected (CDP port 9222)");
|
|
322
319
|
break;
|
|
323
320
|
case "launched":
|
|
324
|
-
launchSpinner.succeed("
|
|
325
|
-
break;
|
|
326
|
-
case "restarted":
|
|
327
|
-
launchSpinner.succeed("Chrome restarted with remote debugging enabled (tabs restored)");
|
|
321
|
+
launchSpinner.succeed("Browser launched with remote debugging (debug profile)");
|
|
328
322
|
break;
|
|
329
323
|
}
|
|
330
324
|
} else {
|
package/src/tools/browser.ts
CHANGED
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
import { WebSocket } from "ws";
|
|
18
18
|
import { execSync, spawn, type ChildProcess } from "node:child_process";
|
|
19
|
-
import { platform } from "node:os";
|
|
20
|
-
import { existsSync, unlinkSync } from "node:fs";
|
|
19
|
+
import { platform, homedir } from "node:os";
|
|
20
|
+
import { existsSync, unlinkSync, mkdirSync, cpSync, readdirSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
21
22
|
import { log } from "../utils/logger.js";
|
|
22
23
|
|
|
23
24
|
interface CDPTab {
|
|
@@ -807,40 +808,160 @@ async function killChromeGracefully(chromePath: string): Promise<void> {
|
|
|
807
808
|
}
|
|
808
809
|
|
|
809
810
|
/**
|
|
810
|
-
*
|
|
811
|
-
|
|
811
|
+
* Return the browser's default profile directory.
|
|
812
|
+
*/
|
|
813
|
+
function getDefaultProfileDir(chromePath: string): string {
|
|
814
|
+
const home = homedir();
|
|
815
|
+
const os = platform();
|
|
816
|
+
|
|
817
|
+
if (os === "darwin") {
|
|
818
|
+
if (chromePath.includes("Brave Browser"))
|
|
819
|
+
return join(home, "Library", "Application Support", "BraveSoftware", "Brave-Browser");
|
|
820
|
+
if (chromePath.includes("Microsoft Edge"))
|
|
821
|
+
return join(home, "Library", "Application Support", "Microsoft Edge");
|
|
822
|
+
if (chromePath.includes("Chromium"))
|
|
823
|
+
return join(home, "Library", "Application Support", "Chromium");
|
|
824
|
+
if (chromePath.includes("Canary"))
|
|
825
|
+
return join(home, "Library", "Application Support", "Google", "Chrome Canary");
|
|
826
|
+
return join(home, "Library", "Application Support", "Google", "Chrome");
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (os === "win32") {
|
|
830
|
+
const appData = process.env.LOCALAPPDATA || join(home, "AppData", "Local");
|
|
831
|
+
if (chromePath.includes("brave"))
|
|
832
|
+
return join(appData, "BraveSoftware", "Brave-Browser", "User Data");
|
|
833
|
+
if (chromePath.includes("msedge")) return join(appData, "Microsoft", "Edge", "User Data");
|
|
834
|
+
return join(appData, "Google", "Chrome", "User Data");
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Linux
|
|
838
|
+
if (chromePath.includes("brave")) return join(home, ".config", "BraveSoftware", "Brave-Browser");
|
|
839
|
+
if (chromePath.includes("microsoft-edge")) return join(home, ".config", "microsoft-edge");
|
|
840
|
+
if (chromePath.includes("chromium")) return join(home, ".config", "chromium");
|
|
841
|
+
return join(home, ".config", "google-chrome");
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Return a dedicated debug profile directory for assistme.
|
|
846
|
+
*
|
|
847
|
+
* Chrome 136+ silently ignores --remote-debugging-port when launched with the
|
|
848
|
+
* DEFAULT user-data-dir (security hardening against cookie-stealing malware).
|
|
849
|
+
* It also ignores --user-data-dir pointing to the default path.
|
|
850
|
+
* The flag ONLY works with a NON-DEFAULT --user-data-dir.
|
|
812
851
|
*
|
|
813
|
-
*
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
*
|
|
852
|
+
* Strategy: use ~/.assistme/browser-profile as a dedicated debug profile.
|
|
853
|
+
* On first use, copy key files from the real profile (bookmarks, cookies,
|
|
854
|
+
* login data, preferences) so the user doesn't start completely fresh.
|
|
855
|
+
* Sessions accumulate in the debug profile from then on.
|
|
817
856
|
*
|
|
818
|
-
*
|
|
819
|
-
* Callers must ensure Chrome is fully quit before calling this function.
|
|
857
|
+
* See: https://developer.chrome.com/blog/remote-debugging-port
|
|
820
858
|
*/
|
|
821
|
-
function
|
|
822
|
-
const
|
|
823
|
-
const
|
|
859
|
+
function getDebugProfileDir(chromePath: string): string {
|
|
860
|
+
const home = homedir();
|
|
861
|
+
const debugDir = join(home, ".assistme", "browser-profile");
|
|
862
|
+
|
|
863
|
+
if (!existsSync(debugDir)) {
|
|
864
|
+
mkdirSync(debugDir, { recursive: true });
|
|
865
|
+
log.debug(`Created debug profile directory: ${debugDir}`);
|
|
866
|
+
|
|
867
|
+
// Seed from the real profile — copy lightweight files, skip caches
|
|
868
|
+
const realDir = getDefaultProfileDir(chromePath);
|
|
869
|
+
if (existsSync(realDir)) {
|
|
870
|
+
seedDebugProfile(realDir, debugDir);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
824
873
|
|
|
825
|
-
|
|
874
|
+
return debugDir;
|
|
875
|
+
}
|
|
826
876
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
877
|
+
/**
|
|
878
|
+
* Copy essential profile data from the user's real Chrome profile to the
|
|
879
|
+
* debug profile. This preserves bookmarks, preferences, and (where possible)
|
|
880
|
+
* login state without copying multi-GB caches.
|
|
881
|
+
*
|
|
882
|
+
* Note: cookies/login data are encrypted with a key tied to the user-data-dir
|
|
883
|
+
* on Chrome 136+, so they won't decrypt in the debug profile. The user will
|
|
884
|
+
* need to log in once in the debug browser. After that, sessions persist.
|
|
885
|
+
*/
|
|
886
|
+
function seedDebugProfile(realDir: string, debugDir: string): void {
|
|
887
|
+
// Files to copy from the profile root
|
|
888
|
+
const rootFiles = ["Local State"];
|
|
889
|
+
// Files to copy from the "Default" sub-profile
|
|
890
|
+
const profileFiles = ["Bookmarks", "Preferences", "Favicons", "Top Sites", "Shortcuts"];
|
|
891
|
+
|
|
892
|
+
for (const file of rootFiles) {
|
|
893
|
+
const src = join(realDir, file);
|
|
894
|
+
const dest = join(debugDir, file);
|
|
895
|
+
try {
|
|
896
|
+
if (existsSync(src)) {
|
|
897
|
+
cpSync(src, dest, { force: true });
|
|
898
|
+
log.debug(`Seeded: ${file}`);
|
|
899
|
+
}
|
|
900
|
+
} catch {
|
|
901
|
+
/* best effort */
|
|
902
|
+
}
|
|
842
903
|
}
|
|
843
904
|
|
|
905
|
+
// Copy the Default profile sub-directory essentials
|
|
906
|
+
const srcProfile = join(realDir, "Default");
|
|
907
|
+
const destProfile = join(debugDir, "Default");
|
|
908
|
+
if (existsSync(srcProfile)) {
|
|
909
|
+
mkdirSync(destProfile, { recursive: true });
|
|
910
|
+
for (const file of profileFiles) {
|
|
911
|
+
const src = join(srcProfile, file);
|
|
912
|
+
const dest = join(destProfile, file);
|
|
913
|
+
try {
|
|
914
|
+
if (existsSync(src)) {
|
|
915
|
+
cpSync(src, dest, { force: true });
|
|
916
|
+
log.debug(`Seeded: Default/${file}`);
|
|
917
|
+
}
|
|
918
|
+
} catch {
|
|
919
|
+
/* best effort */
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Copy Extensions directory if it exists (preserves user's extensions)
|
|
924
|
+
const srcExt = join(srcProfile, "Extensions");
|
|
925
|
+
const destExt = join(destProfile, "Extensions");
|
|
926
|
+
try {
|
|
927
|
+
if (existsSync(srcExt)) {
|
|
928
|
+
cpSync(srcExt, destExt, { recursive: true, force: true });
|
|
929
|
+
log.debug("Seeded: Default/Extensions");
|
|
930
|
+
}
|
|
931
|
+
} catch {
|
|
932
|
+
/* best effort — extensions can be large */
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* Spawn a Chromium-based browser with CDP enabled.
|
|
939
|
+
* Returns the child process for exit-code monitoring.
|
|
940
|
+
*
|
|
941
|
+
* Key design decisions:
|
|
942
|
+
* - Launches the binary directly (not via macOS `open -a`) so flags are
|
|
943
|
+
* guaranteed to reach the process and the child stays alive.
|
|
944
|
+
* - Uses a dedicated debug profile (not the default profile) so that:
|
|
945
|
+
* (a) Chrome 136+ allows --remote-debugging-port
|
|
946
|
+
* (b) Can run alongside the user's regular Chrome (different singleton)
|
|
947
|
+
* - Callers should ensure no OTHER debug-profile Chrome is running, but
|
|
948
|
+
* the user's regular Chrome can stay open.
|
|
949
|
+
*/
|
|
950
|
+
function spawnChrome(chromePath: string, port: number): ChildProcess {
|
|
951
|
+
const profileDir = getDebugProfileDir(chromePath);
|
|
952
|
+
const flags = [
|
|
953
|
+
`--remote-debugging-port=${port}`,
|
|
954
|
+
`--user-data-dir=${profileDir}`,
|
|
955
|
+
"--restore-last-session",
|
|
956
|
+
];
|
|
957
|
+
|
|
958
|
+
log.debug(`Spawning browser: ${chromePath} ${flags.join(" ")}`);
|
|
959
|
+
|
|
960
|
+
const child = spawn(chromePath, flags, {
|
|
961
|
+
detached: true,
|
|
962
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
963
|
+
});
|
|
964
|
+
|
|
844
965
|
// Capture stderr for diagnostics — Chrome prints errors here
|
|
845
966
|
let stderr = "";
|
|
846
967
|
child.stderr?.on("data", (chunk: Buffer) => {
|
|
@@ -894,9 +1015,10 @@ async function isPortInUse(port: number): Promise<boolean> {
|
|
|
894
1015
|
const res = await fetch(`http://127.0.0.1:${port}/json/version`, {
|
|
895
1016
|
signal: AbortSignal.timeout(1000),
|
|
896
1017
|
});
|
|
897
|
-
//
|
|
1018
|
+
// CDP /json/version returns a JSON object with "Browser" and "webSocketDebuggerUrl" keys.
|
|
1019
|
+
// All Chromium-based browsers (Chrome, Edge, Brave) include these.
|
|
898
1020
|
const body = await res.text();
|
|
899
|
-
return !body.includes("
|
|
1021
|
+
return !body.includes("webSocketDebuggerUrl");
|
|
900
1022
|
} catch {
|
|
901
1023
|
// Connection refused → port is free
|
|
902
1024
|
return false;
|
|
@@ -908,27 +1030,23 @@ async function isPortInUse(port: number): Promise<boolean> {
|
|
|
908
1030
|
*/
|
|
909
1031
|
export interface AutoLaunchResult {
|
|
910
1032
|
success: boolean;
|
|
911
|
-
action:
|
|
912
|
-
| "already_available"
|
|
913
|
-
| "launched"
|
|
914
|
-
| "restarted"
|
|
915
|
-
| "chrome_not_found"
|
|
916
|
-
| "launch_failed"
|
|
917
|
-
| "port_conflict";
|
|
1033
|
+
action: "already_available" | "launched" | "chrome_not_found" | "launch_failed" | "port_conflict";
|
|
918
1034
|
chromePath?: string;
|
|
919
1035
|
detail?: string;
|
|
920
1036
|
}
|
|
921
1037
|
|
|
922
1038
|
/**
|
|
923
|
-
* Ensure
|
|
1039
|
+
* Ensure a Chromium browser is running with CDP enabled.
|
|
924
1040
|
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
927
|
-
*
|
|
928
|
-
*
|
|
929
|
-
* Chrome's session restore brings back all tabs.
|
|
1041
|
+
* Uses a SEPARATE debug profile (~/.assistme/browser-profile) so that:
|
|
1042
|
+
* - The user's regular Chrome can stay open — no killing required
|
|
1043
|
+
* - Chrome 136+ enables --remote-debugging-port (requires non-default dir)
|
|
1044
|
+
* - The debug browser has its own singleton — no conflicts
|
|
930
1045
|
*
|
|
931
|
-
*
|
|
1046
|
+
* Flow:
|
|
1047
|
+
* 1. CDP already reachable on the port → return immediately.
|
|
1048
|
+
* 2. Port occupied by a non-Chromium process → report conflict.
|
|
1049
|
+
* 3. Launch a new browser instance with the debug profile + CDP flag.
|
|
932
1050
|
*/
|
|
933
1051
|
export async function ensureBrowserAvailable(port = 9222): Promise<AutoLaunchResult> {
|
|
934
1052
|
const browser = getBrowser(port);
|
|
@@ -958,97 +1076,51 @@ export async function ensureBrowserAvailable(port = 9222): Promise<AutoLaunchRes
|
|
|
958
1076
|
|
|
959
1077
|
log.debug(`Found Chrome at: ${chromePath}`);
|
|
960
1078
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
// Case 3: Browser running without CDP → restart
|
|
965
|
-
if (running) {
|
|
966
|
-
log.debug("Killing browser gracefully for restart with CDP...");
|
|
967
|
-
await killChromeGracefully(chromePath);
|
|
968
|
-
|
|
969
|
-
// Verify browser is fully dead — critical on macOS where `open -a`
|
|
970
|
-
// ignores --args if the app is still alive.
|
|
971
|
-
if (isChromeRunning(chromePath)) {
|
|
972
|
-
log.debug("Browser still running after kill attempt — cannot restart with CDP");
|
|
973
|
-
return {
|
|
974
|
-
success: false,
|
|
975
|
-
action: "launch_failed",
|
|
976
|
-
chromePath,
|
|
977
|
-
detail:
|
|
978
|
-
"Could not stop the existing browser process. Please quit the browser manually and run assistme again.",
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// Extra wait for profile lock release after kill
|
|
983
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
984
|
-
|
|
985
|
-
const child = spawnChrome(chromePath, port);
|
|
986
|
-
|
|
987
|
-
if (await waitForCDP(browser)) {
|
|
988
|
-
return { success: true, action: "restarted", chromePath };
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
// Check if browser process exited immediately
|
|
992
|
-
if (child.exitCode !== null) {
|
|
993
|
-
log.debug(
|
|
994
|
-
`Browser process already exited (code ${child.exitCode}) — may have crashed or profile is locked`
|
|
995
|
-
);
|
|
996
|
-
return {
|
|
997
|
-
success: false,
|
|
998
|
-
action: "launch_failed",
|
|
999
|
-
chromePath,
|
|
1000
|
-
detail: `Browser exited immediately (code ${child.exitCode}). The profile may be locked. Try closing all browser windows first, then run assistme again.`,
|
|
1001
|
-
};
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
// Retry once — browser can be slow to start (extensions, session restore)
|
|
1005
|
-
log.debug("First CDP wait timed out after restart, retrying...");
|
|
1006
|
-
if (await waitForCDP(browser, 15000)) {
|
|
1007
|
-
return { success: true, action: "restarted", chromePath };
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
const stillRunning = isChromeRunning(chromePath);
|
|
1011
|
-
return {
|
|
1012
|
-
success: false,
|
|
1013
|
-
action: "launch_failed",
|
|
1014
|
-
chromePath,
|
|
1015
|
-
detail: stillRunning
|
|
1016
|
-
? "Browser is running but CDP port is not responding. Try: 1) Quit the browser completely, 2) Run assistme again."
|
|
1017
|
-
: "Browser was restarted but exited unexpectedly. Try launching it manually to check for errors.",
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
// Case 4: Browser not running → launch
|
|
1022
|
-
const child = spawnChrome(chromePath, port);
|
|
1079
|
+
// Launch a debug Chrome instance (separate profile — no need to kill the user's Chrome)
|
|
1080
|
+
spawnChrome(chromePath, port);
|
|
1023
1081
|
|
|
1024
1082
|
if (await waitForCDP(browser)) {
|
|
1025
1083
|
return { success: true, action: "launched", chromePath };
|
|
1026
1084
|
}
|
|
1027
1085
|
|
|
1028
|
-
if
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1086
|
+
// CDP didn't come up — check if the debug profile is locked by a previous
|
|
1087
|
+
// crashed assistme session (stale SingletonLock)
|
|
1088
|
+
const debugDir = getDebugProfileDir(chromePath);
|
|
1089
|
+
const lockPath = join(debugDir, "SingletonLock");
|
|
1090
|
+
if (existsSync(lockPath)) {
|
|
1091
|
+
log.debug("Found stale SingletonLock in debug profile — removing and retrying");
|
|
1092
|
+
try {
|
|
1093
|
+
unlinkSync(lockPath);
|
|
1094
|
+
// Also clean SingletonSocket/Cookie
|
|
1095
|
+
for (const f of ["SingletonSocket", "SingletonCookie"]) {
|
|
1096
|
+
try {
|
|
1097
|
+
unlinkSync(join(debugDir, f));
|
|
1098
|
+
} catch {
|
|
1099
|
+
/* ok */
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
} catch {
|
|
1103
|
+
/* best effort */
|
|
1104
|
+
}
|
|
1037
1105
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1106
|
+
// Retry spawn
|
|
1107
|
+
spawnChrome(chromePath, port);
|
|
1108
|
+
if (await waitForCDP(browser, 15000)) {
|
|
1109
|
+
return { success: true, action: "launched", chromePath };
|
|
1110
|
+
}
|
|
1042
1111
|
}
|
|
1043
1112
|
|
|
1044
|
-
const stillRunning = isChromeRunning(chromePath);
|
|
1045
1113
|
return {
|
|
1046
1114
|
success: false,
|
|
1047
1115
|
action: "launch_failed",
|
|
1048
1116
|
chromePath,
|
|
1049
|
-
detail:
|
|
1050
|
-
|
|
1051
|
-
|
|
1117
|
+
detail:
|
|
1118
|
+
"Could not start browser with remote debugging. Possible causes:\n" +
|
|
1119
|
+
" 1) Another assistme debug browser is already using port " +
|
|
1120
|
+
port +
|
|
1121
|
+
"\n" +
|
|
1122
|
+
" 2) The browser crashed on startup\n" +
|
|
1123
|
+
"Try: rm -rf ~/.assistme/browser-profile && assistme",
|
|
1052
1124
|
};
|
|
1053
1125
|
}
|
|
1054
1126
|
|