@todoforai/edge 0.12.14 → 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.
Files changed (2) hide show
  1. package/dist/index.js +80 -22
  2. 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 scanCatalogTools() {
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
- for (const [name, entry] of Object.entries(TOOL_CATALOG)) {
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
- continue;
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 = spawnSync("sh", ["-c", entry.versionCmd], { env, timeout: 5000, encoding: "utf-8" });
49374
+ const r = await execShellAsync(entry.versionCmd, env, 5000);
49359
49375
  if (r.status === 0)
49360
- state.version = (r.stdout || "").toString().trim().slice(0, 100);
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 = spawnSync("sh", ["-c", entry.statusCmd], { env, timeout: 1e4, encoding: "utf-8" });
49381
+ const r = await execShellAsync(entry.statusCmd, env, 1e4);
49366
49382
  state.authenticated = r.status === 0;
49367
- state.statusOutput = (r.stdout || r.stderr || "").toString().trim().slice(0, 200);
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 = [
@@ -51272,6 +51288,9 @@ function waitForCompletion(blockId, timeoutMs) {
51272
51288
  function getBlockOutput(blockId) {
51273
51289
  return outputBuffers.get(blockId)?.getOutput() ?? "";
51274
51290
  }
51291
+ function getBlockRawOutput(blockId) {
51292
+ return outputBuffers.get(blockId)?.getRawIfComplete() ?? null;
51293
+ }
51275
51294
  function clearBlockOutput(blockId) {
51276
51295
  outputBuffers.delete(blockId);
51277
51296
  }
@@ -51327,7 +51346,7 @@ register("install_tool", async (args) => {
51327
51346
  if (!name || !(name in TOOL_CATALOG)) {
51328
51347
  return { success: false, error: `Unknown tool: ${name}` };
51329
51348
  }
51330
- const scan = scanCatalogTools();
51349
+ const scan = await scanCatalogTools();
51331
51350
  if (scan[name]?.installed) {
51332
51351
  return { success: true, alreadyInstalled: true, tool: name };
51333
51352
  }
@@ -51337,7 +51356,7 @@ register("install_tool", async (args) => {
51337
51356
  }
51338
51357
  const edge = getGlobalEdgeInstance();
51339
51358
  if (edge) {
51340
- await edge.updateConfig({ installedTools: scanCatalogTools() });
51359
+ await edge.updateConfig({ installedTools: await scanCatalogTools() });
51341
51360
  }
51342
51361
  return { success: true, tool: name, label: TOOL_CATALOG[name].label };
51343
51362
  });
@@ -51350,7 +51369,7 @@ register("uninstall_tool", async (args) => {
51350
51369
  if (success) {
51351
51370
  const edge = getGlobalEdgeInstance();
51352
51371
  if (edge)
51353
- await edge.updateConfig({ installedTools: scanCatalogTools() });
51372
+ await edge.updateConfig({ installedTools: await scanCatalogTools() });
51354
51373
  }
51355
51374
  return { success, tool: name };
51356
51375
  });
@@ -51486,6 +51505,19 @@ function extractTrailingTail(cmd) {
51486
51505
  `).slice(-n).join(`
51487
51506
  `) };
51488
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
+ }
51489
51521
  register("execute_shell_command", async (args, client) => {
51490
51522
  const { cmd, timeout = 120, root_path = "", todoId = "", messageId = "", blockId = "" } = args;
51491
51523
  const canStream = !!(todoId && blockId && client);
@@ -51496,7 +51528,7 @@ register("execute_shell_command", async (args, client) => {
51496
51528
  resolve((stdout || "") + (stderr || ""));
51497
51529
  });
51498
51530
  });
51499
- return { cmd, result };
51531
+ return { cmd, ...detectContentType(result, cmd) };
51500
51532
  }
51501
51533
  const { execCmd, postFilter } = extractTrailingTail(cmd);
51502
51534
  try {
@@ -51507,11 +51539,12 @@ register("execute_shell_command", async (args, client) => {
51507
51539
  return { __awaiting_approval__: true };
51508
51540
  }
51509
51541
  await waitForCompletion(blockId, (timeout + 5) * 1000);
51510
- let output = getBlockOutput(blockId);
51542
+ const rawOutput = getBlockRawOutput(blockId);
51543
+ let output = rawOutput ?? getBlockOutput(blockId);
51511
51544
  clearBlockOutput(blockId);
51512
51545
  if (postFilter)
51513
51546
  output = postFilter(output);
51514
- return { cmd, result: output };
51547
+ return rawOutput !== null ? { cmd, ...detectContentType(output, cmd) } : { cmd, result: output };
51515
51548
  } catch (e) {
51516
51549
  throw e;
51517
51550
  } finally {
@@ -51939,6 +51972,8 @@ class TODOforAIEdge {
51939
51972
  addWorkspacePath;
51940
51973
  frontendWs = null;
51941
51974
  browserExtensionBridge;
51975
+ stopping = false;
51976
+ reconnectTimer;
51942
51977
  edgeConfig = {
51943
51978
  id: "",
51944
51979
  name: "Name uninitialized",
@@ -52117,7 +52152,7 @@ class TODOforAIEdge {
52117
52152
  id2 += String.fromCharCode(frame[i10]);
52118
52153
  const data = frame.slice(36);
52119
52154
  this.pendingBinaries.set(id2, data);
52120
- setTimeout(() => this.pendingBinaries.delete(id2), 60000);
52155
+ setTimeout(() => this.pendingBinaries.delete(id2), 60000).unref();
52121
52156
  }
52122
52157
  async handleMessage(raw) {
52123
52158
  let data;
@@ -52149,7 +52184,7 @@ class TODOforAIEdge {
52149
52184
  this.edgeConfig.id = this.edgeId;
52150
52185
  console.log(`\x1B[32m\x1B[1m\uD83D\uDD17 Connected edge=${this.edgeId} user=${this.userId}\x1B[0m`);
52151
52186
  run(async () => {
52152
- this.updateConfig({ installedTools: scanCatalogTools() });
52187
+ this.updateConfig({ installedTools: await scanCatalogTools() });
52153
52188
  autoMountRcloneRemotes();
52154
52189
  });
52155
52190
  break;
@@ -52289,10 +52324,12 @@ class TODOforAIEdge {
52289
52324
  console.log(`\x1B[36m\x1B[1m\uD83D\uDC46 Fingerprint:\x1B[0m ${this.fingerprint}`);
52290
52325
  const maxAttempts = 20;
52291
52326
  let attempt = 0;
52292
- while (attempt < maxAttempts) {
52327
+ while (attempt < maxAttempts && !this.stopping) {
52293
52328
  console.log(`[info] Connecting (attempt ${attempt + 1}/${maxAttempts})`);
52294
52329
  try {
52295
52330
  await this.connect();
52331
+ if (this.stopping)
52332
+ break;
52296
52333
  attempt = 0;
52297
52334
  } catch (e) {
52298
52335
  if (e instanceof AuthenticationError) {
@@ -52309,16 +52346,26 @@ class TODOforAIEdge {
52309
52346
  this.connected = false;
52310
52347
  this.ws = null;
52311
52348
  }
52312
- if (attempt > 0 && attempt < maxAttempts) {
52349
+ if (attempt > 0 && attempt < maxAttempts && !this.stopping) {
52313
52350
  const delay = Math.min(4 + attempt, 20);
52314
52351
  console.log(`[info] Reconnecting in ${delay}s...`);
52315
- await new Promise((r) => setTimeout(r, delay * 1000));
52352
+ await new Promise((r) => {
52353
+ this.reconnectTimer = setTimeout(r, delay * 1000);
52354
+ });
52316
52355
  }
52317
52356
  }
52318
52357
  if (attempt >= maxAttempts) {
52319
52358
  console.error("\x1B[31mMax reconnection attempts reached.\x1B[0m");
52320
52359
  }
52321
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
+ }
52322
52369
  async getFrontendWs() {
52323
52370
  if (!this.frontendWs || !this.frontendWs.connected) {
52324
52371
  this.frontendWs = new FrontendWebSocket(this.api.apiUrl, this.api.apiKey);
@@ -52429,13 +52476,24 @@ async function main() {
52429
52476
  console.error("\x1B[31mAnother edge is already running for this user+server. Use --kill to replace it.\x1B[0m");
52430
52477
  process.exit(1);
52431
52478
  }
52479
+ let cleaned = false;
52432
52480
  const cleanup = () => {
52481
+ if (cleaned)
52482
+ return;
52483
+ cleaned = true;
52484
+ edge.stop();
52433
52485
  unmountAllRclone();
52434
52486
  releaseLock(lp2);
52435
52487
  };
52436
52488
  process.on("exit", cleanup);
52437
- process.on("SIGINT", () => process.exit(0));
52438
- process.on("SIGTERM", () => process.exit(0));
52489
+ process.on("SIGINT", () => {
52490
+ cleanup();
52491
+ process.exit(0);
52492
+ });
52493
+ process.on("SIGTERM", () => {
52494
+ cleanup();
52495
+ process.exit(0);
52496
+ });
52439
52497
  await edge.start();
52440
52498
  }
52441
52499
  main().catch((e) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/edge",
3
- "version": "0.12.14",
3
+ "version": "0.12.15",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoforai-edge": "dist/index.js"