@todoforai/edge 0.12.13 → 0.12.15
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 +136 -47
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48418,8 +48418,12 @@ class BrowserExtensionBridge {
|
|
|
48418
48418
|
pending.ws.send(JSON.stringify({ type: "browser.command.result", requestId, error: "Browser bridge stopped" }));
|
|
48419
48419
|
}
|
|
48420
48420
|
this.pending.clear();
|
|
48421
|
+
if (isOpen(this.extensionWs))
|
|
48422
|
+
this.extensionWs.terminate();
|
|
48421
48423
|
this.extensionWs = null;
|
|
48424
|
+
this.wss?.clients.forEach((ws) => ws.terminate());
|
|
48422
48425
|
this.wss?.close();
|
|
48426
|
+
this.server?.closeAllConnections();
|
|
48423
48427
|
this.server?.close();
|
|
48424
48428
|
this.wss = undefined;
|
|
48425
48429
|
this.server = undefined;
|
|
@@ -48608,7 +48612,7 @@ import path4 from "path";
|
|
|
48608
48612
|
import fs3 from "fs";
|
|
48609
48613
|
import os3 from "os";
|
|
48610
48614
|
import path3 from "path";
|
|
48611
|
-
import { execSync, spawnSync } from "child_process";
|
|
48615
|
+
import { execSync, spawnSync, execFile } from "child_process";
|
|
48612
48616
|
|
|
48613
48617
|
// src/tool-catalog.ts
|
|
48614
48618
|
import os2 from "os";
|
|
@@ -49344,27 +49348,39 @@ function uninstallTool(name) {
|
|
|
49344
49348
|
return false;
|
|
49345
49349
|
}
|
|
49346
49350
|
}
|
|
49347
|
-
function
|
|
49351
|
+
function execShellAsync(cmd, env, timeout) {
|
|
49352
|
+
return new Promise((resolve) => {
|
|
49353
|
+
execFile("sh", ["-c", cmd], { env, timeout, encoding: "utf-8", maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
|
49354
|
+
resolve({ status: err ? 1 : 0, stdout: (stdout || "").toString(), stderr: (stderr || "").toString() });
|
|
49355
|
+
});
|
|
49356
|
+
});
|
|
49357
|
+
}
|
|
49358
|
+
async function scanCatalogTools() {
|
|
49348
49359
|
const result = {};
|
|
49349
49360
|
const env = buildEnvWithTools();
|
|
49350
|
-
|
|
49361
|
+
const entries = Object.entries(TOOL_CATALOG);
|
|
49362
|
+
const installed = [];
|
|
49363
|
+
for (const [name, entry] of entries) {
|
|
49351
49364
|
if (!isToolInstalled(name)) {
|
|
49352
49365
|
result[name] = { installed: false };
|
|
49353
|
-
|
|
49366
|
+
} else {
|
|
49367
|
+
installed.push([name, entry]);
|
|
49354
49368
|
}
|
|
49369
|
+
}
|
|
49370
|
+
await Promise.all(installed.map(async ([name, entry]) => {
|
|
49355
49371
|
const state = { installed: true };
|
|
49356
49372
|
if (entry.versionCmd) {
|
|
49357
49373
|
try {
|
|
49358
|
-
const r =
|
|
49374
|
+
const r = await execShellAsync(entry.versionCmd, env, 5000);
|
|
49359
49375
|
if (r.status === 0)
|
|
49360
|
-
state.version =
|
|
49376
|
+
state.version = r.stdout.trim().slice(0, 100);
|
|
49361
49377
|
} catch {}
|
|
49362
49378
|
}
|
|
49363
49379
|
if (entry.statusCmd) {
|
|
49364
49380
|
try {
|
|
49365
|
-
const r =
|
|
49381
|
+
const r = await execShellAsync(entry.statusCmd, env, 1e4);
|
|
49366
49382
|
state.authenticated = r.status === 0;
|
|
49367
|
-
state.statusOutput = (r.stdout || r.stderr
|
|
49383
|
+
state.statusOutput = (r.stdout || r.stderr).trim().slice(0, 200);
|
|
49368
49384
|
} catch {
|
|
49369
49385
|
state.authenticated = false;
|
|
49370
49386
|
}
|
|
@@ -49372,7 +49388,7 @@ function scanCatalogTools() {
|
|
|
49372
49388
|
state.authenticated = true;
|
|
49373
49389
|
}
|
|
49374
49390
|
result[name] = state;
|
|
49375
|
-
}
|
|
49391
|
+
}));
|
|
49376
49392
|
return result;
|
|
49377
49393
|
}
|
|
49378
49394
|
var MOUNT_FLAGS = [
|
|
@@ -50906,8 +50922,10 @@ async function readFileContent(filePath, rootPath, fallbackRootPaths) {
|
|
|
50906
50922
|
import os5 from "os";
|
|
50907
50923
|
import fs8 from "fs";
|
|
50908
50924
|
import path5 from "path";
|
|
50925
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
50909
50926
|
var IS_WIN = os5.platform() === "win32";
|
|
50910
|
-
var
|
|
50927
|
+
var HAS_BUN = typeof globalThis.Bun !== "undefined";
|
|
50928
|
+
var HAS_BUN_TERMINAL = HAS_BUN && typeof Bun.Terminal === "function";
|
|
50911
50929
|
function whichSync(name) {
|
|
50912
50930
|
const dirs = (process.env.PATH || "").split(path5.delimiter);
|
|
50913
50931
|
const exts = IS_WIN ? ["", ".exe", ".cmd", ".bat"] : [""];
|
|
@@ -51011,6 +51029,13 @@ ${this.lastPart}`;
|
|
|
51011
51029
|
}
|
|
51012
51030
|
const all = current ? [...this.savedSegments, current] : [...this.savedSegments];
|
|
51013
51031
|
return all.join(`
|
|
51032
|
+
`);
|
|
51033
|
+
}
|
|
51034
|
+
getRawIfComplete() {
|
|
51035
|
+
if (this.truncated)
|
|
51036
|
+
return null;
|
|
51037
|
+
const all = this.firstPart ? [...this.savedSegments, this.firstPart] : [...this.savedSegments];
|
|
51038
|
+
return all.join(`
|
|
51014
51039
|
`);
|
|
51015
51040
|
}
|
|
51016
51041
|
}
|
|
@@ -51134,29 +51159,51 @@ async function executeBlock(blockId, content, send, todoId, messageId, timeout,
|
|
|
51134
51159
|
});
|
|
51135
51160
|
};
|
|
51136
51161
|
const spawnWithPipes = () => {
|
|
51137
|
-
|
|
51138
|
-
|
|
51139
|
-
|
|
51140
|
-
|
|
51141
|
-
|
|
51142
|
-
|
|
51143
|
-
|
|
51144
|
-
|
|
51145
|
-
|
|
51146
|
-
|
|
51147
|
-
|
|
51148
|
-
|
|
51149
|
-
|
|
51150
|
-
|
|
51151
|
-
|
|
51152
|
-
await
|
|
51153
|
-
|
|
51154
|
-
|
|
51155
|
-
|
|
51156
|
-
|
|
51157
|
-
|
|
51158
|
-
|
|
51159
|
-
|
|
51162
|
+
if (HAS_BUN) {
|
|
51163
|
+
const proc = Bun.spawn([sc2.shell, ...sc2.args], {
|
|
51164
|
+
cwd,
|
|
51165
|
+
env,
|
|
51166
|
+
stdin: "pipe",
|
|
51167
|
+
stdout: "pipe",
|
|
51168
|
+
stderr: "pipe"
|
|
51169
|
+
});
|
|
51170
|
+
const handle = { proc, pid: proc.pid };
|
|
51171
|
+
processes.set(blockId, handle);
|
|
51172
|
+
const timer = startTimeout();
|
|
51173
|
+
const pipeStream = async (stream) => {
|
|
51174
|
+
if (!stream)
|
|
51175
|
+
return;
|
|
51176
|
+
const decoder = new TextDecoder;
|
|
51177
|
+
for await (const chunk of stream) {
|
|
51178
|
+
await onData(decoder.decode(chunk, { stream: true }));
|
|
51179
|
+
}
|
|
51180
|
+
};
|
|
51181
|
+
Promise.all([
|
|
51182
|
+
pipeStream(proc.stdout),
|
|
51183
|
+
pipeStream(proc.stderr),
|
|
51184
|
+
proc.exited
|
|
51185
|
+
]).then(([, , code]) => onExit(code ?? -1, timer)).catch(() => onExit(-1, timer));
|
|
51186
|
+
} else {
|
|
51187
|
+
const proc = nodeSpawn(sc2.shell, sc2.args, {
|
|
51188
|
+
cwd,
|
|
51189
|
+
env,
|
|
51190
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
51191
|
+
});
|
|
51192
|
+
const handle = { proc, pid: proc.pid ?? -1 };
|
|
51193
|
+
processes.set(blockId, handle);
|
|
51194
|
+
const timer = startTimeout();
|
|
51195
|
+
let exited = false;
|
|
51196
|
+
const exit = (code) => {
|
|
51197
|
+
if (!exited) {
|
|
51198
|
+
exited = true;
|
|
51199
|
+
onExit(code, timer);
|
|
51200
|
+
}
|
|
51201
|
+
};
|
|
51202
|
+
proc.stdout?.on("data", (chunk) => onData(chunk.toString()));
|
|
51203
|
+
proc.stderr?.on("data", (chunk) => onData(chunk.toString()));
|
|
51204
|
+
proc.on("close", (code) => exit(code ?? -1));
|
|
51205
|
+
proc.on("error", () => exit(-1));
|
|
51206
|
+
}
|
|
51160
51207
|
};
|
|
51161
51208
|
if (HAS_BUN_TERMINAL) {
|
|
51162
51209
|
try {
|
|
@@ -51241,6 +51288,9 @@ function waitForCompletion(blockId, timeoutMs) {
|
|
|
51241
51288
|
function getBlockOutput(blockId) {
|
|
51242
51289
|
return outputBuffers.get(blockId)?.getOutput() ?? "";
|
|
51243
51290
|
}
|
|
51291
|
+
function getBlockRawOutput(blockId) {
|
|
51292
|
+
return outputBuffers.get(blockId)?.getRawIfComplete() ?? null;
|
|
51293
|
+
}
|
|
51244
51294
|
function clearBlockOutput(blockId) {
|
|
51245
51295
|
outputBuffers.delete(blockId);
|
|
51246
51296
|
}
|
|
@@ -51296,7 +51346,7 @@ register("install_tool", async (args) => {
|
|
|
51296
51346
|
if (!name || !(name in TOOL_CATALOG)) {
|
|
51297
51347
|
return { success: false, error: `Unknown tool: ${name}` };
|
|
51298
51348
|
}
|
|
51299
|
-
const scan = scanCatalogTools();
|
|
51349
|
+
const scan = await scanCatalogTools();
|
|
51300
51350
|
if (scan[name]?.installed) {
|
|
51301
51351
|
return { success: true, alreadyInstalled: true, tool: name };
|
|
51302
51352
|
}
|
|
@@ -51306,7 +51356,7 @@ register("install_tool", async (args) => {
|
|
|
51306
51356
|
}
|
|
51307
51357
|
const edge = getGlobalEdgeInstance();
|
|
51308
51358
|
if (edge) {
|
|
51309
|
-
await edge.updateConfig({ installedTools: scanCatalogTools() });
|
|
51359
|
+
await edge.updateConfig({ installedTools: await scanCatalogTools() });
|
|
51310
51360
|
}
|
|
51311
51361
|
return { success: true, tool: name, label: TOOL_CATALOG[name].label };
|
|
51312
51362
|
});
|
|
@@ -51319,7 +51369,7 @@ register("uninstall_tool", async (args) => {
|
|
|
51319
51369
|
if (success) {
|
|
51320
51370
|
const edge = getGlobalEdgeInstance();
|
|
51321
51371
|
if (edge)
|
|
51322
|
-
await edge.updateConfig({ installedTools: scanCatalogTools() });
|
|
51372
|
+
await edge.updateConfig({ installedTools: await scanCatalogTools() });
|
|
51323
51373
|
}
|
|
51324
51374
|
return { success, tool: name };
|
|
51325
51375
|
});
|
|
@@ -51455,6 +51505,19 @@ function extractTrailingTail(cmd) {
|
|
|
51455
51505
|
`).slice(-n).join(`
|
|
51456
51506
|
`) };
|
|
51457
51507
|
}
|
|
51508
|
+
var DATA_URL_IMAGE_REGEX = /^data:(image\/[^;]+);base64,[A-Za-z0-9+/]+=*$/;
|
|
51509
|
+
function detectContentType(output, cmd) {
|
|
51510
|
+
const trimmed = output.trim();
|
|
51511
|
+
const match = trimmed.match(DATA_URL_IMAGE_REGEX);
|
|
51512
|
+
if (match) {
|
|
51513
|
+
console.log(`
|
|
51514
|
+
\uD83D\uDDBC️ [edge] Image output detected! type=${match[1]} size=${trimmed.length} chars${cmd ? `
|
|
51515
|
+
cmd: ${cmd}` : ""}
|
|
51516
|
+
`);
|
|
51517
|
+
return { result: trimmed, contentType: match[1] };
|
|
51518
|
+
}
|
|
51519
|
+
return { result: output };
|
|
51520
|
+
}
|
|
51458
51521
|
register("execute_shell_command", async (args, client) => {
|
|
51459
51522
|
const { cmd, timeout = 120, root_path = "", todoId = "", messageId = "", blockId = "" } = args;
|
|
51460
51523
|
const canStream = !!(todoId && blockId && client);
|
|
@@ -51465,7 +51528,7 @@ register("execute_shell_command", async (args, client) => {
|
|
|
51465
51528
|
resolve((stdout || "") + (stderr || ""));
|
|
51466
51529
|
});
|
|
51467
51530
|
});
|
|
51468
|
-
return { cmd, result };
|
|
51531
|
+
return { cmd, ...detectContentType(result, cmd) };
|
|
51469
51532
|
}
|
|
51470
51533
|
const { execCmd, postFilter } = extractTrailingTail(cmd);
|
|
51471
51534
|
try {
|
|
@@ -51476,11 +51539,12 @@ register("execute_shell_command", async (args, client) => {
|
|
|
51476
51539
|
return { __awaiting_approval__: true };
|
|
51477
51540
|
}
|
|
51478
51541
|
await waitForCompletion(blockId, (timeout + 5) * 1000);
|
|
51479
|
-
|
|
51542
|
+
const rawOutput = getBlockRawOutput(blockId);
|
|
51543
|
+
let output = rawOutput ?? getBlockOutput(blockId);
|
|
51480
51544
|
clearBlockOutput(blockId);
|
|
51481
51545
|
if (postFilter)
|
|
51482
51546
|
output = postFilter(output);
|
|
51483
|
-
return { cmd, result: output };
|
|
51547
|
+
return rawOutput !== null ? { cmd, ...detectContentType(output, cmd) } : { cmd, result: output };
|
|
51484
51548
|
} catch (e) {
|
|
51485
51549
|
throw e;
|
|
51486
51550
|
} finally {
|
|
@@ -51908,6 +51972,8 @@ class TODOforAIEdge {
|
|
|
51908
51972
|
addWorkspacePath;
|
|
51909
51973
|
frontendWs = null;
|
|
51910
51974
|
browserExtensionBridge;
|
|
51975
|
+
stopping = false;
|
|
51976
|
+
reconnectTimer;
|
|
51911
51977
|
edgeConfig = {
|
|
51912
51978
|
id: "",
|
|
51913
51979
|
name: "Name uninitialized",
|
|
@@ -52086,7 +52152,7 @@ class TODOforAIEdge {
|
|
|
52086
52152
|
id2 += String.fromCharCode(frame[i10]);
|
|
52087
52153
|
const data = frame.slice(36);
|
|
52088
52154
|
this.pendingBinaries.set(id2, data);
|
|
52089
|
-
setTimeout(() => this.pendingBinaries.delete(id2), 60000);
|
|
52155
|
+
setTimeout(() => this.pendingBinaries.delete(id2), 60000).unref();
|
|
52090
52156
|
}
|
|
52091
52157
|
async handleMessage(raw) {
|
|
52092
52158
|
let data;
|
|
@@ -52118,7 +52184,7 @@ class TODOforAIEdge {
|
|
|
52118
52184
|
this.edgeConfig.id = this.edgeId;
|
|
52119
52185
|
console.log(`\x1B[32m\x1B[1m\uD83D\uDD17 Connected edge=${this.edgeId} user=${this.userId}\x1B[0m`);
|
|
52120
52186
|
run(async () => {
|
|
52121
|
-
this.updateConfig({ installedTools: scanCatalogTools() });
|
|
52187
|
+
this.updateConfig({ installedTools: await scanCatalogTools() });
|
|
52122
52188
|
autoMountRcloneRemotes();
|
|
52123
52189
|
});
|
|
52124
52190
|
break;
|
|
@@ -52258,10 +52324,12 @@ class TODOforAIEdge {
|
|
|
52258
52324
|
console.log(`\x1B[36m\x1B[1m\uD83D\uDC46 Fingerprint:\x1B[0m ${this.fingerprint}`);
|
|
52259
52325
|
const maxAttempts = 20;
|
|
52260
52326
|
let attempt = 0;
|
|
52261
|
-
while (attempt < maxAttempts) {
|
|
52327
|
+
while (attempt < maxAttempts && !this.stopping) {
|
|
52262
52328
|
console.log(`[info] Connecting (attempt ${attempt + 1}/${maxAttempts})`);
|
|
52263
52329
|
try {
|
|
52264
52330
|
await this.connect();
|
|
52331
|
+
if (this.stopping)
|
|
52332
|
+
break;
|
|
52265
52333
|
attempt = 0;
|
|
52266
52334
|
} catch (e) {
|
|
52267
52335
|
if (e instanceof AuthenticationError) {
|
|
@@ -52278,16 +52346,26 @@ class TODOforAIEdge {
|
|
|
52278
52346
|
this.connected = false;
|
|
52279
52347
|
this.ws = null;
|
|
52280
52348
|
}
|
|
52281
|
-
if (attempt > 0 && attempt < maxAttempts) {
|
|
52349
|
+
if (attempt > 0 && attempt < maxAttempts && !this.stopping) {
|
|
52282
52350
|
const delay = Math.min(4 + attempt, 20);
|
|
52283
52351
|
console.log(`[info] Reconnecting in ${delay}s...`);
|
|
52284
|
-
await new Promise((r) =>
|
|
52352
|
+
await new Promise((r) => {
|
|
52353
|
+
this.reconnectTimer = setTimeout(r, delay * 1000);
|
|
52354
|
+
});
|
|
52285
52355
|
}
|
|
52286
52356
|
}
|
|
52287
52357
|
if (attempt >= maxAttempts) {
|
|
52288
52358
|
console.error("\x1B[31mMax reconnection attempts reached.\x1B[0m");
|
|
52289
52359
|
}
|
|
52290
52360
|
}
|
|
52361
|
+
stop() {
|
|
52362
|
+
this.stopping = true;
|
|
52363
|
+
clearTimeout(this.reconnectTimer);
|
|
52364
|
+
this.stopHeartbeat();
|
|
52365
|
+
this.browserExtensionBridge.stop();
|
|
52366
|
+
this.frontendWs?.close();
|
|
52367
|
+
this.ws?.terminate();
|
|
52368
|
+
}
|
|
52291
52369
|
async getFrontendWs() {
|
|
52292
52370
|
if (!this.frontendWs || !this.frontendWs.connected) {
|
|
52293
52371
|
this.frontendWs = new FrontendWebSocket(this.api.apiUrl, this.api.apiKey);
|
|
@@ -52355,7 +52433,7 @@ function killExistingEdge(lp2) {
|
|
|
52355
52433
|
} catch {
|
|
52356
52434
|
break;
|
|
52357
52435
|
}
|
|
52358
|
-
|
|
52436
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
|
|
52359
52437
|
}
|
|
52360
52438
|
try {
|
|
52361
52439
|
process.kill(pid, "SIGKILL");
|
|
@@ -52398,13 +52476,24 @@ async function main() {
|
|
|
52398
52476
|
console.error("\x1B[31mAnother edge is already running for this user+server. Use --kill to replace it.\x1B[0m");
|
|
52399
52477
|
process.exit(1);
|
|
52400
52478
|
}
|
|
52479
|
+
let cleaned = false;
|
|
52401
52480
|
const cleanup = () => {
|
|
52481
|
+
if (cleaned)
|
|
52482
|
+
return;
|
|
52483
|
+
cleaned = true;
|
|
52484
|
+
edge.stop();
|
|
52402
52485
|
unmountAllRclone();
|
|
52403
52486
|
releaseLock(lp2);
|
|
52404
52487
|
};
|
|
52405
52488
|
process.on("exit", cleanup);
|
|
52406
|
-
process.on("SIGINT", () =>
|
|
52407
|
-
|
|
52489
|
+
process.on("SIGINT", () => {
|
|
52490
|
+
cleanup();
|
|
52491
|
+
process.exit(0);
|
|
52492
|
+
});
|
|
52493
|
+
process.on("SIGTERM", () => {
|
|
52494
|
+
cleanup();
|
|
52495
|
+
process.exit(0);
|
|
52496
|
+
});
|
|
52408
52497
|
await edge.start();
|
|
52409
52498
|
}
|
|
52410
52499
|
main().catch((e) => {
|