agentlife 1.4.1 → 1.5.0

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 +127 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5289,9 +5289,12 @@ import * as fs7 from "node:fs";
5289
5289
  import * as os5 from "node:os";
5290
5290
  import * as path9 from "node:path";
5291
5291
  var AGENTLIFE_DIR = path9.join(os5.homedir(), ".openclaw", "agentlife");
5292
- var DEVICE_FILE = path9.join(AGENTLIFE_DIR, "device.json");
5293
5292
  var TUNNEL_FILE = path9.join(AGENTLIFE_DIR, "tunnel.json");
5294
5293
  var BIN_DIR = path9.join(AGENTLIFE_DIR, "bin");
5294
+ var PAIR_REQUEST_MARKER = path9.join(AGENTLIFE_DIR, "pair-requested");
5295
+ var IDENTITY_DIR = path9.join(os5.homedir(), ".agentlife");
5296
+ var DEVICE_FILE = path9.join(IDENTITY_DIR, "device.json");
5297
+ var LEGACY_DEVICE_FILE = path9.join(AGENTLIFE_DIR, "device.json");
5295
5298
  var CLOUDFLARED_BIN = os5.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
5296
5299
  var CLOUDFLARED_PATH = path9.join(BIN_DIR, CLOUDFLARED_BIN);
5297
5300
  var API_BASE = process.env.AGENTLIFE_API_BASE || "https://api.agentlife.app";
@@ -5306,9 +5309,12 @@ function createInitialState() {
5306
5309
  tunnelInfo: null,
5307
5310
  process: null,
5308
5311
  restartTimer: null,
5312
+ provisionRetryTimer: null,
5309
5313
  stopped: false,
5310
5314
  readyPromise: promise,
5311
- readyResolve: resolver
5315
+ readyResolve: resolver,
5316
+ inFlight: null,
5317
+ binPath: null
5312
5318
  };
5313
5319
  }
5314
5320
  var state = createInitialState();
@@ -5336,6 +5342,10 @@ function stopCloudflaredSupervisor() {
5336
5342
  clearTimeout(state.restartTimer);
5337
5343
  state.restartTimer = null;
5338
5344
  }
5345
+ if (state.provisionRetryTimer) {
5346
+ clearTimeout(state.provisionRetryTimer);
5347
+ state.provisionRetryTimer = null;
5348
+ }
5339
5349
  if (state.process) {
5340
5350
  try {
5341
5351
  state.process.kill("SIGTERM");
@@ -5344,10 +5354,30 @@ function stopCloudflaredSupervisor() {
5344
5354
  }
5345
5355
  }
5346
5356
  async function bootstrap() {
5347
- const result = await doBootstrap();
5348
- state.tunnelInfo = result;
5357
+ const result = await runBootstrap();
5349
5358
  state.readyResolve(result);
5350
5359
  }
5360
+ async function runBootstrap() {
5361
+ if (state.inFlight)
5362
+ return state.inFlight;
5363
+ const promise = doBootstrap().then((result) => {
5364
+ if (result)
5365
+ state.tunnelInfo = result;
5366
+ return result;
5367
+ }).finally(() => {
5368
+ state.inFlight = null;
5369
+ });
5370
+ state.inFlight = promise;
5371
+ return promise;
5372
+ }
5373
+ function triggerProvision() {
5374
+ if (state.stopped)
5375
+ return;
5376
+ requestPair();
5377
+ runBootstrap().catch((err) => {
5378
+ console.warn("[cloudflared-supervisor] triggered provision failed:", err?.message ?? err);
5379
+ });
5380
+ }
5351
5381
  async function doBootstrap() {
5352
5382
  ensureDirs();
5353
5383
  const identity = loadOrCreateDeviceIdentity();
@@ -5357,29 +5387,84 @@ async function doBootstrap() {
5357
5387
  return null;
5358
5388
  }
5359
5389
  let tunnelInfo = loadCachedTunnel();
5360
- if (!tunnelInfo || Date.now() - tunnelInfo.provisionedAt > 24 * 60 * 60 * 1000) {
5390
+ if (!tunnelInfo) {
5391
+ if (!isPairRequested()) {
5392
+ console.log("[cloudflared-supervisor] no tunnel cached and no pair request — staying idle (LAN-only)");
5393
+ return null;
5394
+ }
5395
+ console.log("[cloudflared-supervisor] pair requested — provisioning fresh tunnel");
5361
5396
  tunnelInfo = await provisionTunnel(identity);
5362
5397
  if (!tunnelInfo) {
5363
- console.warn("[cloudflared-supervisor] provisioning failed tunnel disabled (LAN-only mode)");
5398
+ console.log("[cloudflared-supervisor] provision attempt did not yield a tunnel will retry on next pair request");
5364
5399
  return null;
5365
5400
  }
5366
5401
  persistTunnel(tunnelInfo);
5402
+ clearPairRequest();
5403
+ } else if (isPairRequested()) {
5404
+ console.log("[cloudflared-supervisor] pair requested with cache present — re-provisioning");
5405
+ const refreshed = await provisionTunnel(identity);
5406
+ if (refreshed) {
5407
+ tunnelInfo = refreshed;
5408
+ persistTunnel(tunnelInfo);
5409
+ } else {
5410
+ console.warn("[cloudflared-supervisor] re-provision failed — keeping cached tunnel");
5411
+ }
5412
+ clearPairRequest();
5367
5413
  }
5368
5414
  console.log(`[cloudflared-supervisor] tunnel ready: ${tunnelInfo.tunnelUrl}`);
5369
5415
  startCloudflaredProcess(binPath, tunnelInfo.tunnelToken);
5370
5416
  return tunnelInfo;
5371
5417
  }
5418
+ function isPairRequested() {
5419
+ return fs7.existsSync(PAIR_REQUEST_MARKER);
5420
+ }
5421
+ function requestPair() {
5422
+ try {
5423
+ if (!fs7.existsSync(AGENTLIFE_DIR))
5424
+ fs7.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
5425
+ fs7.writeFileSync(PAIR_REQUEST_MARKER, String(Date.now()), { mode: 384 });
5426
+ } catch (err) {
5427
+ console.warn(`[cloudflared-supervisor] failed to write pair-request marker: ${err?.message ?? err}`);
5428
+ }
5429
+ }
5430
+ function clearPairRequest() {
5431
+ try {
5432
+ fs7.unlinkSync(PAIR_REQUEST_MARKER);
5433
+ } catch {}
5434
+ }
5435
+ function parseRetryAfter(value) {
5436
+ const n = value == null ? NaN : parseInt(value.trim(), 10);
5437
+ if (!Number.isFinite(n) || n < 1)
5438
+ return 60;
5439
+ return Math.min(n, 3600);
5440
+ }
5441
+ function scheduleProvisionRetry(delayMs) {
5442
+ if (state.stopped)
5443
+ return;
5444
+ if (state.provisionRetryTimer) {
5445
+ clearTimeout(state.provisionRetryTimer);
5446
+ state.provisionRetryTimer = null;
5447
+ }
5448
+ state.provisionRetryTimer = setTimeout(() => {
5449
+ state.provisionRetryTimer = null;
5450
+ if (state.stopped || state.tunnelInfo)
5451
+ return;
5452
+ triggerProvision();
5453
+ }, delayMs);
5454
+ }
5372
5455
  function ensureDirs() {
5373
5456
  if (!fs7.existsSync(AGENTLIFE_DIR))
5374
5457
  fs7.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
5375
5458
  if (!fs7.existsSync(BIN_DIR))
5376
5459
  fs7.mkdirSync(BIN_DIR, { recursive: true, mode: 448 });
5460
+ if (!fs7.existsSync(IDENTITY_DIR))
5461
+ fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
5377
5462
  }
5378
- function loadDeviceIdentity() {
5379
- if (!fs7.existsSync(DEVICE_FILE))
5463
+ function readIdentityFile(filePath) {
5464
+ if (!fs7.existsSync(filePath))
5380
5465
  return null;
5381
5466
  try {
5382
- const raw = fs7.readFileSync(DEVICE_FILE, "utf-8");
5467
+ const raw = fs7.readFileSync(filePath, "utf-8");
5383
5468
  const parsed = JSON.parse(raw);
5384
5469
  if (typeof parsed.deviceId === "string" && typeof parsed.deviceSecret === "string") {
5385
5470
  return { deviceId: parsed.deviceId, deviceSecret: parsed.deviceSecret };
@@ -5389,10 +5474,35 @@ function loadDeviceIdentity() {
5389
5474
  return null;
5390
5475
  }
5391
5476
  }
5477
+ function loadDeviceIdentity() {
5478
+ const current = readIdentityFile(DEVICE_FILE);
5479
+ if (current)
5480
+ return current;
5481
+ const legacy = readIdentityFile(LEGACY_DEVICE_FILE);
5482
+ if (!legacy)
5483
+ return null;
5484
+ try {
5485
+ if (!fs7.existsSync(IDENTITY_DIR))
5486
+ fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
5487
+ fs7.writeFileSync(DEVICE_FILE, JSON.stringify(legacy, null, 2), { mode: 384 });
5488
+ try {
5489
+ fs7.chmodSync(DEVICE_FILE, 384);
5490
+ } catch {}
5491
+ try {
5492
+ fs7.unlinkSync(LEGACY_DEVICE_FILE);
5493
+ } catch {}
5494
+ console.log(`[cloudflared-supervisor] migrated device.json to ${DEVICE_FILE}`);
5495
+ } catch (err) {
5496
+ console.warn(`[cloudflared-supervisor] device.json migration failed: ${err?.message ?? err}`);
5497
+ }
5498
+ return legacy;
5499
+ }
5392
5500
  function loadOrCreateDeviceIdentity() {
5393
5501
  const existing = loadDeviceIdentity();
5394
5502
  if (existing)
5395
5503
  return existing;
5504
+ if (!fs7.existsSync(IDENTITY_DIR))
5505
+ fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
5396
5506
  const identity = {
5397
5507
  deviceId: crypto2.randomUUID(),
5398
5508
  deviceSecret: crypto2.randomBytes(32).toString("base64url")
@@ -5436,6 +5546,12 @@ async function provisionTunnel(identity) {
5436
5546
  });
5437
5547
  if (!response.ok) {
5438
5548
  const body = await response.text();
5549
+ if (response.status === 503) {
5550
+ const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
5551
+ console.warn(`[cloudflared-supervisor] provision rate-limited by server (HTTP 503); ` + `scheduling retry in ${retryAfter}s`);
5552
+ scheduleProvisionRetry(retryAfter * 1000);
5553
+ return null;
5554
+ }
5439
5555
  console.warn(`[cloudflared-supervisor] provision HTTP ${response.status}: ${body.slice(0, 200)}`);
5440
5556
  return null;
5441
5557
  }
@@ -5718,6 +5834,8 @@ function registerWebApp(api) {
5718
5834
  return true;
5719
5835
  const bootstrapToken = mintBootstrapToken();
5720
5836
  const tunnelInfo = getTunnelInfo();
5837
+ if (!tunnelInfo?.tunnelUrl)
5838
+ triggerProvision();
5721
5839
  const payload = { bootstrapToken };
5722
5840
  if (tunnelInfo?.tunnelUrl)
5723
5841
  payload.tunnelUrl = tunnelInfo.tunnelUrl;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlife",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "bun build index.ts --outfile dist/index.js --target node --external openclaw/plugin-sdk",