agentlife 1.4.1 → 1.5.1

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 +150 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2070,6 +2070,24 @@ function visionSurfaceCount(state) {
2070
2070
  n++;
2071
2071
  return n;
2072
2072
  }
2073
+ function specialistHasAnySurface(state, specialistId) {
2074
+ if (!state.surfaceDb)
2075
+ return false;
2076
+ for (const id of state.surfaceDb.keys()) {
2077
+ if (state.surfaceDb.getAgentId(id) === specialistId)
2078
+ return true;
2079
+ }
2080
+ return false;
2081
+ }
2082
+ function specialistIdFromSessionKey(sessionKey) {
2083
+ if (!sessionKey)
2084
+ return null;
2085
+ const parts = sessionKey.split(":");
2086
+ if (parts.length < 2 || parts[0] !== "agent")
2087
+ return null;
2088
+ const id = parts[1];
2089
+ return id && id.length > 0 ? id : null;
2090
+ }
2073
2091
  async function sendSystemMessage(state, agentId, message, idempotencyKey, log) {
2074
2092
  if (!state.runCommand) {
2075
2093
  log(`[cold-start] sendSystemMessage ${idempotencyKey}: skipped — runCommand not available`);
@@ -2176,6 +2194,11 @@ async function transition(state, runtime, log, trigger) {
2176
2194
  return cur;
2177
2195
  }
2178
2196
  if (cur.phase === "AWAITING_BASELINE") {
2197
+ const specialistId = specialistIdFromSessionKey(cur.actionSessionKey);
2198
+ if (specialistId && !specialistHasAnySurface(state, specialistId)) {
2199
+ log(`[cold-start] ${specialistId} replied in AWAITING_BASELINE with no surface ` + `attributed — [system:start] contract violation (expected a warmup-* input surface)`);
2200
+ return enterFailed(state, log, "agent_produced_no_surface");
2201
+ }
2179
2202
  return cur;
2180
2203
  }
2181
2204
  return cur;
@@ -5289,9 +5312,12 @@ import * as fs7 from "node:fs";
5289
5312
  import * as os5 from "node:os";
5290
5313
  import * as path9 from "node:path";
5291
5314
  var AGENTLIFE_DIR = path9.join(os5.homedir(), ".openclaw", "agentlife");
5292
- var DEVICE_FILE = path9.join(AGENTLIFE_DIR, "device.json");
5293
5315
  var TUNNEL_FILE = path9.join(AGENTLIFE_DIR, "tunnel.json");
5294
5316
  var BIN_DIR = path9.join(AGENTLIFE_DIR, "bin");
5317
+ var PAIR_REQUEST_MARKER = path9.join(AGENTLIFE_DIR, "pair-requested");
5318
+ var IDENTITY_DIR = path9.join(os5.homedir(), ".agentlife");
5319
+ var DEVICE_FILE = path9.join(IDENTITY_DIR, "device.json");
5320
+ var LEGACY_DEVICE_FILE = path9.join(AGENTLIFE_DIR, "device.json");
5295
5321
  var CLOUDFLARED_BIN = os5.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
5296
5322
  var CLOUDFLARED_PATH = path9.join(BIN_DIR, CLOUDFLARED_BIN);
5297
5323
  var API_BASE = process.env.AGENTLIFE_API_BASE || "https://api.agentlife.app";
@@ -5306,9 +5332,12 @@ function createInitialState() {
5306
5332
  tunnelInfo: null,
5307
5333
  process: null,
5308
5334
  restartTimer: null,
5335
+ provisionRetryTimer: null,
5309
5336
  stopped: false,
5310
5337
  readyPromise: promise,
5311
- readyResolve: resolver
5338
+ readyResolve: resolver,
5339
+ inFlight: null,
5340
+ binPath: null
5312
5341
  };
5313
5342
  }
5314
5343
  var state = createInitialState();
@@ -5336,6 +5365,10 @@ function stopCloudflaredSupervisor() {
5336
5365
  clearTimeout(state.restartTimer);
5337
5366
  state.restartTimer = null;
5338
5367
  }
5368
+ if (state.provisionRetryTimer) {
5369
+ clearTimeout(state.provisionRetryTimer);
5370
+ state.provisionRetryTimer = null;
5371
+ }
5339
5372
  if (state.process) {
5340
5373
  try {
5341
5374
  state.process.kill("SIGTERM");
@@ -5344,10 +5377,30 @@ function stopCloudflaredSupervisor() {
5344
5377
  }
5345
5378
  }
5346
5379
  async function bootstrap() {
5347
- const result = await doBootstrap();
5348
- state.tunnelInfo = result;
5380
+ const result = await runBootstrap();
5349
5381
  state.readyResolve(result);
5350
5382
  }
5383
+ async function runBootstrap() {
5384
+ if (state.inFlight)
5385
+ return state.inFlight;
5386
+ const promise = doBootstrap().then((result) => {
5387
+ if (result)
5388
+ state.tunnelInfo = result;
5389
+ return result;
5390
+ }).finally(() => {
5391
+ state.inFlight = null;
5392
+ });
5393
+ state.inFlight = promise;
5394
+ return promise;
5395
+ }
5396
+ function triggerProvision() {
5397
+ if (state.stopped)
5398
+ return;
5399
+ requestPair();
5400
+ runBootstrap().catch((err) => {
5401
+ console.warn("[cloudflared-supervisor] triggered provision failed:", err?.message ?? err);
5402
+ });
5403
+ }
5351
5404
  async function doBootstrap() {
5352
5405
  ensureDirs();
5353
5406
  const identity = loadOrCreateDeviceIdentity();
@@ -5357,29 +5410,84 @@ async function doBootstrap() {
5357
5410
  return null;
5358
5411
  }
5359
5412
  let tunnelInfo = loadCachedTunnel();
5360
- if (!tunnelInfo || Date.now() - tunnelInfo.provisionedAt > 24 * 60 * 60 * 1000) {
5413
+ if (!tunnelInfo) {
5414
+ if (!isPairRequested()) {
5415
+ console.log("[cloudflared-supervisor] no tunnel cached and no pair request — staying idle (LAN-only)");
5416
+ return null;
5417
+ }
5418
+ console.log("[cloudflared-supervisor] pair requested — provisioning fresh tunnel");
5361
5419
  tunnelInfo = await provisionTunnel(identity);
5362
5420
  if (!tunnelInfo) {
5363
- console.warn("[cloudflared-supervisor] provisioning failed tunnel disabled (LAN-only mode)");
5421
+ console.log("[cloudflared-supervisor] provision attempt did not yield a tunnel will retry on next pair request");
5364
5422
  return null;
5365
5423
  }
5366
5424
  persistTunnel(tunnelInfo);
5425
+ clearPairRequest();
5426
+ } else if (isPairRequested()) {
5427
+ console.log("[cloudflared-supervisor] pair requested with cache present — re-provisioning");
5428
+ const refreshed = await provisionTunnel(identity);
5429
+ if (refreshed) {
5430
+ tunnelInfo = refreshed;
5431
+ persistTunnel(tunnelInfo);
5432
+ } else {
5433
+ console.warn("[cloudflared-supervisor] re-provision failed — keeping cached tunnel");
5434
+ }
5435
+ clearPairRequest();
5367
5436
  }
5368
5437
  console.log(`[cloudflared-supervisor] tunnel ready: ${tunnelInfo.tunnelUrl}`);
5369
5438
  startCloudflaredProcess(binPath, tunnelInfo.tunnelToken);
5370
5439
  return tunnelInfo;
5371
5440
  }
5441
+ function isPairRequested() {
5442
+ return fs7.existsSync(PAIR_REQUEST_MARKER);
5443
+ }
5444
+ function requestPair() {
5445
+ try {
5446
+ if (!fs7.existsSync(AGENTLIFE_DIR))
5447
+ fs7.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
5448
+ fs7.writeFileSync(PAIR_REQUEST_MARKER, String(Date.now()), { mode: 384 });
5449
+ } catch (err) {
5450
+ console.warn(`[cloudflared-supervisor] failed to write pair-request marker: ${err?.message ?? err}`);
5451
+ }
5452
+ }
5453
+ function clearPairRequest() {
5454
+ try {
5455
+ fs7.unlinkSync(PAIR_REQUEST_MARKER);
5456
+ } catch {}
5457
+ }
5458
+ function parseRetryAfter(value) {
5459
+ const n = value == null ? NaN : parseInt(value.trim(), 10);
5460
+ if (!Number.isFinite(n) || n < 1)
5461
+ return 60;
5462
+ return Math.min(n, 3600);
5463
+ }
5464
+ function scheduleProvisionRetry(delayMs) {
5465
+ if (state.stopped)
5466
+ return;
5467
+ if (state.provisionRetryTimer) {
5468
+ clearTimeout(state.provisionRetryTimer);
5469
+ state.provisionRetryTimer = null;
5470
+ }
5471
+ state.provisionRetryTimer = setTimeout(() => {
5472
+ state.provisionRetryTimer = null;
5473
+ if (state.stopped || state.tunnelInfo)
5474
+ return;
5475
+ triggerProvision();
5476
+ }, delayMs);
5477
+ }
5372
5478
  function ensureDirs() {
5373
5479
  if (!fs7.existsSync(AGENTLIFE_DIR))
5374
5480
  fs7.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
5375
5481
  if (!fs7.existsSync(BIN_DIR))
5376
5482
  fs7.mkdirSync(BIN_DIR, { recursive: true, mode: 448 });
5483
+ if (!fs7.existsSync(IDENTITY_DIR))
5484
+ fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
5377
5485
  }
5378
- function loadDeviceIdentity() {
5379
- if (!fs7.existsSync(DEVICE_FILE))
5486
+ function readIdentityFile(filePath) {
5487
+ if (!fs7.existsSync(filePath))
5380
5488
  return null;
5381
5489
  try {
5382
- const raw = fs7.readFileSync(DEVICE_FILE, "utf-8");
5490
+ const raw = fs7.readFileSync(filePath, "utf-8");
5383
5491
  const parsed = JSON.parse(raw);
5384
5492
  if (typeof parsed.deviceId === "string" && typeof parsed.deviceSecret === "string") {
5385
5493
  return { deviceId: parsed.deviceId, deviceSecret: parsed.deviceSecret };
@@ -5389,10 +5497,35 @@ function loadDeviceIdentity() {
5389
5497
  return null;
5390
5498
  }
5391
5499
  }
5500
+ function loadDeviceIdentity() {
5501
+ const current = readIdentityFile(DEVICE_FILE);
5502
+ if (current)
5503
+ return current;
5504
+ const legacy = readIdentityFile(LEGACY_DEVICE_FILE);
5505
+ if (!legacy)
5506
+ return null;
5507
+ try {
5508
+ if (!fs7.existsSync(IDENTITY_DIR))
5509
+ fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
5510
+ fs7.writeFileSync(DEVICE_FILE, JSON.stringify(legacy, null, 2), { mode: 384 });
5511
+ try {
5512
+ fs7.chmodSync(DEVICE_FILE, 384);
5513
+ } catch {}
5514
+ try {
5515
+ fs7.unlinkSync(LEGACY_DEVICE_FILE);
5516
+ } catch {}
5517
+ console.log(`[cloudflared-supervisor] migrated device.json to ${DEVICE_FILE}`);
5518
+ } catch (err) {
5519
+ console.warn(`[cloudflared-supervisor] device.json migration failed: ${err?.message ?? err}`);
5520
+ }
5521
+ return legacy;
5522
+ }
5392
5523
  function loadOrCreateDeviceIdentity() {
5393
5524
  const existing = loadDeviceIdentity();
5394
5525
  if (existing)
5395
5526
  return existing;
5527
+ if (!fs7.existsSync(IDENTITY_DIR))
5528
+ fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
5396
5529
  const identity = {
5397
5530
  deviceId: crypto2.randomUUID(),
5398
5531
  deviceSecret: crypto2.randomBytes(32).toString("base64url")
@@ -5436,6 +5569,12 @@ async function provisionTunnel(identity) {
5436
5569
  });
5437
5570
  if (!response.ok) {
5438
5571
  const body = await response.text();
5572
+ if (response.status === 503) {
5573
+ const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
5574
+ console.warn(`[cloudflared-supervisor] provision rate-limited by server (HTTP 503); ` + `scheduling retry in ${retryAfter}s`);
5575
+ scheduleProvisionRetry(retryAfter * 1000);
5576
+ return null;
5577
+ }
5439
5578
  console.warn(`[cloudflared-supervisor] provision HTTP ${response.status}: ${body.slice(0, 200)}`);
5440
5579
  return null;
5441
5580
  }
@@ -5718,6 +5857,8 @@ function registerWebApp(api) {
5718
5857
  return true;
5719
5858
  const bootstrapToken = mintBootstrapToken();
5720
5859
  const tunnelInfo = getTunnelInfo();
5860
+ if (!tunnelInfo?.tunnelUrl)
5861
+ triggerProvision();
5721
5862
  const payload = { bootstrapToken };
5722
5863
  if (tunnelInfo?.tunnelUrl)
5723
5864
  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.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "bun build index.ts --outfile dist/index.js --target node --external openclaw/plugin-sdk",