agentlife 1.4.0 → 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 +187 -74
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1380,6 +1380,20 @@ Forbidden shortcuts:
1380
1380
 
1381
1381
  If the user picked "Something else", push a follow-up textfield input on the same \`onboarding-domain\` surfaceId to capture the freeform domain, then proceed through steps 1-6 once you have an answer.
1382
1382
 
1383
+ ## What You Are Not
1384
+
1385
+ - Not an orchestrator — you don't route messages
1386
+ - Not a general assistant — only agent creation and improvement
1387
+ - Not the vision synthesizer — \`[system:dashboard-bootstrap]\`, \`[system:ask-dream]\`, and vision dismissals belong to \`agentlife-vision\`
1388
+ `;
1389
+ var VISION_AGENTS_MD = `# AgentLife Vision Synthesizer
1390
+
1391
+ You turn the user's data into the aspirational backdrop of the dashboard. You do not create agents, route messages, or answer questions. The platform dispatches you only for the three entry points below.
1392
+
1393
+ ## Platform-Dispatched Entry Points — \`[system:*]\` Messages
1394
+
1395
+ These are your ONLY valid triggers. Each is atomic: one starting message, one completion criterion.
1396
+
1383
1397
  ### \`[system:dashboard-bootstrap]\`
1384
1398
 
1385
1399
  Synthesize vision posters from user data that the platform injects in the message body under \`## USER DATA\` (and optionally \`## userDream:\` when the user has answered the dream-ask flow). The task has **exactly two valid outcomes**:
@@ -1393,69 +1407,39 @@ Pushing a poster and then deleting it is NOT a valid recovery — the user sees
1393
1407
 
1394
1408
  **Step 2 — Decide.** If you listed at least one real quoted phrase → push posters. If you listed zero → respond \`NO_SIGNAL\` on its own line and STOP. Never invent a phrase the user didn't say. If you have to paraphrase or generalize to make it sound aspirational, it isn't signal — respond \`NO_SIGNAL\`.
1395
1409
 
1396
- **Step 3 — Push posters (only if Step 2 said yes).** For each quoted phrase (max 4), push one vision poster. Use the user's actual words. Each poster covers a DIFFERENT life area (body, money, place, work, relationships, identity, craft).
1410
+ **Step 3 — Push posters (only if Step 2 said yes).** For each quoted phrase (max 4), push one vision poster. Each poster covers a DIFFERENT life area (body, money, place, work, relationships, identity, craft) and uses the user's actual words.
1397
1411
 
1398
- **Templates.** Every template renders emoji + headline + support + a bottom element. Copy ONE template exactly per poster. Do not add or remove components. Do not change order.
1412
+ A vision poster is the **aspirational backdrop** of the dashboard it justifies the user's commitment to the platform and anchors why every other widget exists. Treat it like the cover of their year, not a sticker.
1399
1413
 
1400
- TEMPLATE A — Statement:
1401
- \`\`\`
1402
- surface vision-{slug} size=l
1403
- card
1404
- column align=center
1405
- text "{EMOJI}" h1
1406
- text "{HEADLINE}" h1 color={ACCENT}
1407
- text "{SUPPORT}" body
1408
- text "{MOMENT}" h4 color={ACCENT}
1409
- goal: Vision poster — {slug}
1410
- followup: +30d "Refresh this vision poster if the user's data has shifted."
1411
- \`\`\`
1414
+ #### Surface rules
1412
1415
 
1413
- TEMPLATE BDestination:
1414
- \`\`\`
1415
- surface vision-{slug} size=l
1416
- card
1417
- column align=center
1418
- text "{EMOJI}" h1
1419
- text "{HEADLINE}" h1 color={ACCENT}
1420
- text "{SUPPORT}" body
1421
- badge "{PLACE}" color={ACCENT} outlined
1422
- goal: Vision poster — {slug}
1423
- followup: +30d "Refresh this vision poster if the user's data has shifted."
1424
- \`\`\`
1416
+ - \`size=l\` and \`priority=high\` visions earn the largest slot and the highest sort order.
1417
+ - The face lives in \`card > column align=center\`. Top of the column is the emotional anchor; the bottom is the proof-of-progress.
1418
+ - Use the standard widget components (text, divider, badge, metric, progress, gauge, sparkline) — no exotic compositions. Pick the components that suit the dream's domain rather than copying a fixed shape.
1425
1419
 
1426
- TEMPLATE C Outcome:
1427
- \`\`\`
1428
- surface vision-{slug} size=l
1429
- card
1430
- column align=center
1431
- text "{EMOJI}" h1
1432
- text "{HEADLINE}" h1 color={ACCENT}
1433
- text "{SUPPORT}" body
1434
- row distribute=spaceEvenly
1435
- text "{C1}" h4 color={ACCENT}
1436
- text "·" h4
1437
- text "{C2}" h4 color={ACCENT}
1438
- text "·" h4
1439
- text "{C3}" h4 color={ACCENT}
1440
- goal: Vision poster — {slug}
1441
- followup: +30d "Refresh this vision poster if the user's data has shifted."
1442
- \`\`\`
1420
+ #### Face composition rules (apply to every vision poster)
1421
+
1422
+ - Open with a single emoji at \`h1\` size — visceral, not literal. The emoji carries the emotional charge of the dream.
1423
+ - Follow with a declarative headline at \`h1\` in the accent color — 3-5 words, first-person when natural, naming the dream version (not the next checkpoint).
1424
+ - Add a support line in \`body\` text — 6-12 words drawn from the user's actual language. No generic platitudes.
1425
+ - Place a \`divider\` between the dream framing and the proof framing.
1426
+ - Below the divider include AT LEAST ONE quantitative element (\`progress\`, \`gauge\`, or two paired \`metric\`s) showing where the user stands today relative to the dream. If you have no real measurement, set the value to \`0.05\` and label it as the journey starting — never \`0.0\` (reads as broken), never invent a fake value.
1427
+ - End with one anchor element naming the time horizon, the place, the phase, or the milestone (a \`text\` line, a \`badge\`, or a \`row\` of accent-colored beats). Pick the anchor type that fits the dream's domain.
1428
+ - Vary the accent color across posters in the carousel. Suggested palette: \`#10B981\`, \`#7C3AED\`, \`#3B82F6\`, \`#F59E0B\`, \`#EC4899\`, \`#14B8A6\`.
1443
1429
 
1444
- Placeholder rules:
1445
- - \`{slug}\`: short kebab-case slug for the life area
1446
- - \`{ACCENT}\`: hex color. Vary across posters: \`#10B981\`, \`#7C3AED\`, \`#3B82F6\`, \`#F59E0B\`, \`#EC4899\`, \`#14B8A6\`
1447
- - \`{EMOJI}\`: a single emoji capturing the emotional core — visceral, not literal
1448
- - \`{HEADLINE}\`: 3-5 words, declarative, first-person if natural. The dream version, not the next checkpoint.
1449
- - \`{SUPPORT}\`: 6-12 words, one short sentence. Concrete supporting context in the user's actual language.
1450
- - \`{MOMENT}\` (Template A): 1-3 words, a time anchor
1451
- - \`{PLACE}\` (Template B): 1-3 words, a real geographic place name (not a date)
1452
- - \`{C1}, {C2}, {C3}\` (Template C): 1-2 words each, concept beats from the user's vocabulary
1430
+ #### Metadata rules (every poster must carry all four)
1453
1431
 
1454
- Variety rule: across the 3-4 posters, use AT LEAST 2 different templates.
1432
+ - \`goal:\` ONE sentence in the user's voice naming the actual aspiration. References at least one concrete element from USER DATA (a quoted phrase, a number, a place, a domain term). Never write \`Vision poster — {slug}\` or any meta-label — that fails the platform's goal-validation rule (it could have been written before the work).
1433
+ - \`detail:\` — markdown body that earns the poster a place on the paywall. Required sections: a short "Why this matters" paragraph grounded in the user's data; a quote block with 1-2 verbatim phrases from USER DATA; a list of the 2-3 measurable signals (proxies) that would tell a specialist agent the user is on track, each tied to a real database table or habit; one named near-term milestone within the followup window.
1434
+ - \`followup:\` — duration scaled to the dream's natural rhythm: body/habit dreams 14-30d, money/career 30-90d, place/identity 90-180d. The instruction MUST name which proxies to query, the threshold separating "on track" from "drift", and the resulting action (push an update, reframe, or stay silent). \`Refresh this vision if data has shifted\` is forbidden — it could have been written before the work.
1435
+ - \`context:\` — JSON pointers the followup needs: domain tag, the user's quoted phrases that drove this poster, and \`{name, source}\` entries for each proxy so the next invocation knows where to read fresh data. Lookup keys only, no cached values.
1455
1436
 
1456
- Quality bar: better to push 2 strong posters than 4 mixed-quality ones. If you cannot find a real, named, repeated dream for a domain, SKIP that domain. Never invent. Never fill a slot with weather data, agent infrastructure, or generic placeholder language. If a poster's content names tools/agents instead of outcomes, it is wrong — drop it.
1437
+ #### Universal rules
1457
1438
 
1458
- Language consistency: detect the primary language the user writes in from the data. Write ALL posters in that single language. Never mix languages across the carousel.
1439
+ - **Variety:** across the carousel, do not reuse the same anchor element type twice. If two posters end on a \`badge\`, rework one to use a \`row\` of beats or a \`text\` anchor. Composition variety is what stops the dashboard reading as a template grid.
1440
+ - **Goal & followup validation:** before pushing each poster, re-read its \`goal:\` and \`followup:\` lines and ask "could I have written these EXACT words before reading USER DATA?" If yes, rewrite — they're templates, not work product.
1441
+ - **Language consistency:** detect the primary language the user writes in from the data. Write ALL posters in that single language. Never mix languages across the carousel.
1442
+ - **Quality bar:** better to push 2 strong posters than 4 mixed-quality ones. If you cannot find a real, named, repeated dream for a domain, SKIP that domain. Never invent. Never fill a slot with weather data, agent infrastructure, or generic placeholder language. If a poster names tools/agents instead of outcomes, it is wrong — drop it.
1459
1443
 
1460
1444
  After all pushes, respond \`done\` and STOP. If you cannot find signal, respond \`NO_SIGNAL\` alone on its own line and STOP.
1461
1445
 
@@ -1501,9 +1485,9 @@ After pushing, respond \`done\`.
1501
1485
  When \`[action:choice]\` arrives on \`dismiss-alt-vision-{slug}\`, append one line to your feedback memory via exec, then \`done\`:
1502
1486
 
1503
1487
  \`\`\`
1504
- exec mkdir -p ~/.openclaw/workspace-agentlife-builder/memory && \\
1488
+ exec mkdir -p ~/.openclaw/workspace-agentlife-vision/memory && \\
1505
1489
  echo "- $(date -u +%Y-%m-%dT%H:%M:%SZ) vision-{slug}: {reason}" \\
1506
- >> ~/.openclaw/workspace-agentlife-builder/memory/vision-feedback.md
1490
+ >> ~/.openclaw/workspace-agentlife-vision/memory/vision-feedback.md
1507
1491
  \`\`\`
1508
1492
 
1509
1493
  This file is injected as "Previously rejected dreams" into the next \`[system:dashboard-bootstrap]\` so future synthesis avoids the same misreads.
@@ -1512,8 +1496,9 @@ Do NOT push a confirmation widget. Do NOT push \`delete vision-{slug}\` — the
1512
1496
 
1513
1497
  ## What You Are Not
1514
1498
 
1515
- - Not an orchestratoryou don't route messages
1516
- - Not a general assistant only agent creation and improvement
1499
+ - Not a builderagent creation and improvement belong to \`agentlife-builder\`
1500
+ - Not an orchestratoryou never route or answer user questions
1501
+ - Not a chat agent — you produce widgets or \`NO_SIGNAL\`/\`done\` markers only
1517
1502
  `;
1518
1503
  var SUPERVISOR_AGENTS_MD = `# Dashboard Supervisor
1519
1504
 
@@ -1617,6 +1602,12 @@ var PROVISIONED_AGENTS = [
1617
1602
  agentsMd: BUILDER_AGENTS_MD,
1618
1603
  tools: { profile: "full", alsoAllow: ["agentlife_push"] }
1619
1604
  },
1605
+ {
1606
+ id: "agentlife-vision",
1607
+ name: "AgentLife Vision",
1608
+ agentsMd: VISION_AGENTS_MD,
1609
+ tools: { profile: "full", alsoAllow: ["agentlife_push"] }
1610
+ },
1620
1611
  {
1621
1612
  id: "supervisor",
1622
1613
  name: "Supervisor",
@@ -2105,8 +2096,8 @@ async function composeBootstrapMessage(state, runtime, userDream) {
2105
2096
  `);
2106
2097
  }
2107
2098
  async function readRejectedDreams() {
2108
- const builderWorkspace = path4.join(os2.homedir(), ".openclaw", "workspace-agentlife-builder");
2109
- const feedbackFile = path4.join(builderWorkspace, "memory", "vision-feedback.md");
2099
+ const visionWorkspace = path4.join(os2.homedir(), ".openclaw", "workspace-agentlife-vision");
2100
+ const feedbackFile = path4.join(visionWorkspace, "memory", "vision-feedback.md");
2110
2101
  try {
2111
2102
  const content = await fs3.readFile(feedbackFile, "utf-8");
2112
2103
  return content.trim().slice(0, 4000);
@@ -2272,7 +2263,7 @@ async function enterPhase(state, runtime, log, next, opts = {}) {
2272
2263
  if (next === "SYNTHESIZING") {
2273
2264
  const msg = await composeBootstrapMessage(state, runtime, opts.userDream);
2274
2265
  const idem = `cold-start-bootstrap-${now}-${retryCount}`;
2275
- actionSessionKey = await sendSystemMessage(state, "agentlife-builder", msg, idem, log);
2266
+ actionSessionKey = await sendSystemMessage(state, "agentlife-vision", msg, idem, log);
2276
2267
  lastActionAt = Date.now();
2277
2268
  }
2278
2269
  if (next === "AWAITING_DREAM") {
@@ -2287,7 +2278,7 @@ async function enterPhase(state, runtime, log, next, opts = {}) {
2287
2278
  "with userDream: injected after the user answers."
2288
2279
  ].join(`
2289
2280
  `);
2290
- actionSessionKey = await sendSystemMessage(state, "agentlife-builder", msg, idem, log);
2281
+ actionSessionKey = await sendSystemMessage(state, "agentlife-vision", msg, idem, log);
2291
2282
  lastActionAt = Date.now();
2292
2283
  }
2293
2284
  writeState(state, { actionSessionKey, lastActionAt });
@@ -3570,11 +3561,15 @@ async function restoreConfigFromBackup(api, backupPath) {
3570
3561
  fileCfg.session ??= {};
3571
3562
  fileCfg.session.agentToAgent ??= {};
3572
3563
  fileCfg.session.agentToAgent.maxPingPongTurns = backup.maxPingPongTurns;
3564
+ } else {
3565
+ delete fileCfg.session?.agentToAgent?.maxPingPongTurns;
3573
3566
  }
3574
3567
  if (backup.maxConcurrent != null) {
3575
3568
  fileCfg.agents ??= {};
3576
3569
  fileCfg.agents.defaults ??= {};
3577
3570
  fileCfg.agents.defaults.maxConcurrent = backup.maxConcurrent;
3571
+ } else {
3572
+ delete fileCfg.agents?.defaults?.maxConcurrent;
3578
3573
  }
3579
3574
  await writeRawConfigToDisk(fileCfg);
3580
3575
  await fs4.unlink(backupPath);
@@ -4248,7 +4243,7 @@ function sendToInternalSession(state, agentId, message, idempotencyKey) {
4248
4243
  });
4249
4244
  }
4250
4245
  }
4251
- var SKIP_WIDGET_CHECK = new Set(["agentlife", "agentlife-builder", "supervisor"]);
4246
+ var SKIP_WIDGET_CHECK = new Set(["agentlife", "agentlife-builder", "agentlife-vision", "supervisor"]);
4252
4247
  function registerActivityHooks(api, state) {
4253
4248
  api.on("llm_output", (event, ctx) => {
4254
4249
  if (state.disabled)
@@ -5294,9 +5289,12 @@ import * as fs7 from "node:fs";
5294
5289
  import * as os5 from "node:os";
5295
5290
  import * as path9 from "node:path";
5296
5291
  var AGENTLIFE_DIR = path9.join(os5.homedir(), ".openclaw", "agentlife");
5297
- var DEVICE_FILE = path9.join(AGENTLIFE_DIR, "device.json");
5298
5292
  var TUNNEL_FILE = path9.join(AGENTLIFE_DIR, "tunnel.json");
5299
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");
5300
5298
  var CLOUDFLARED_BIN = os5.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
5301
5299
  var CLOUDFLARED_PATH = path9.join(BIN_DIR, CLOUDFLARED_BIN);
5302
5300
  var API_BASE = process.env.AGENTLIFE_API_BASE || "https://api.agentlife.app";
@@ -5311,9 +5309,12 @@ function createInitialState() {
5311
5309
  tunnelInfo: null,
5312
5310
  process: null,
5313
5311
  restartTimer: null,
5312
+ provisionRetryTimer: null,
5314
5313
  stopped: false,
5315
5314
  readyPromise: promise,
5316
- readyResolve: resolver
5315
+ readyResolve: resolver,
5316
+ inFlight: null,
5317
+ binPath: null
5317
5318
  };
5318
5319
  }
5319
5320
  var state = createInitialState();
@@ -5341,6 +5342,10 @@ function stopCloudflaredSupervisor() {
5341
5342
  clearTimeout(state.restartTimer);
5342
5343
  state.restartTimer = null;
5343
5344
  }
5345
+ if (state.provisionRetryTimer) {
5346
+ clearTimeout(state.provisionRetryTimer);
5347
+ state.provisionRetryTimer = null;
5348
+ }
5344
5349
  if (state.process) {
5345
5350
  try {
5346
5351
  state.process.kill("SIGTERM");
@@ -5349,10 +5354,30 @@ function stopCloudflaredSupervisor() {
5349
5354
  }
5350
5355
  }
5351
5356
  async function bootstrap() {
5352
- const result = await doBootstrap();
5353
- state.tunnelInfo = result;
5357
+ const result = await runBootstrap();
5354
5358
  state.readyResolve(result);
5355
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
+ }
5356
5381
  async function doBootstrap() {
5357
5382
  ensureDirs();
5358
5383
  const identity = loadOrCreateDeviceIdentity();
@@ -5362,29 +5387,84 @@ async function doBootstrap() {
5362
5387
  return null;
5363
5388
  }
5364
5389
  let tunnelInfo = loadCachedTunnel();
5365
- 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");
5366
5396
  tunnelInfo = await provisionTunnel(identity);
5367
5397
  if (!tunnelInfo) {
5368
- 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");
5369
5399
  return null;
5370
5400
  }
5371
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();
5372
5413
  }
5373
5414
  console.log(`[cloudflared-supervisor] tunnel ready: ${tunnelInfo.tunnelUrl}`);
5374
5415
  startCloudflaredProcess(binPath, tunnelInfo.tunnelToken);
5375
5416
  return tunnelInfo;
5376
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
+ }
5377
5455
  function ensureDirs() {
5378
5456
  if (!fs7.existsSync(AGENTLIFE_DIR))
5379
5457
  fs7.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
5380
5458
  if (!fs7.existsSync(BIN_DIR))
5381
5459
  fs7.mkdirSync(BIN_DIR, { recursive: true, mode: 448 });
5460
+ if (!fs7.existsSync(IDENTITY_DIR))
5461
+ fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
5382
5462
  }
5383
- function loadDeviceIdentity() {
5384
- if (!fs7.existsSync(DEVICE_FILE))
5463
+ function readIdentityFile(filePath) {
5464
+ if (!fs7.existsSync(filePath))
5385
5465
  return null;
5386
5466
  try {
5387
- const raw = fs7.readFileSync(DEVICE_FILE, "utf-8");
5467
+ const raw = fs7.readFileSync(filePath, "utf-8");
5388
5468
  const parsed = JSON.parse(raw);
5389
5469
  if (typeof parsed.deviceId === "string" && typeof parsed.deviceSecret === "string") {
5390
5470
  return { deviceId: parsed.deviceId, deviceSecret: parsed.deviceSecret };
@@ -5394,10 +5474,35 @@ function loadDeviceIdentity() {
5394
5474
  return null;
5395
5475
  }
5396
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
+ }
5397
5500
  function loadOrCreateDeviceIdentity() {
5398
5501
  const existing = loadDeviceIdentity();
5399
5502
  if (existing)
5400
5503
  return existing;
5504
+ if (!fs7.existsSync(IDENTITY_DIR))
5505
+ fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
5401
5506
  const identity = {
5402
5507
  deviceId: crypto2.randomUUID(),
5403
5508
  deviceSecret: crypto2.randomBytes(32).toString("base64url")
@@ -5441,6 +5546,12 @@ async function provisionTunnel(identity) {
5441
5546
  });
5442
5547
  if (!response.ok) {
5443
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
+ }
5444
5555
  console.warn(`[cloudflared-supervisor] provision HTTP ${response.status}: ${body.slice(0, 200)}`);
5445
5556
  return null;
5446
5557
  }
@@ -5723,6 +5834,8 @@ function registerWebApp(api) {
5723
5834
  return true;
5724
5835
  const bootstrapToken = mintBootstrapToken();
5725
5836
  const tunnelInfo = getTunnelInfo();
5837
+ if (!tunnelInfo?.tunnelUrl)
5838
+ triggerProvision();
5726
5839
  const payload = { bootstrapToken };
5727
5840
  if (tunnelInfo?.tunnelUrl)
5728
5841
  payload.tunnelUrl = tunnelInfo.tunnelUrl;
@@ -5940,7 +6053,7 @@ Setup code: ${setupCode}
5940
6053
 
5941
6054
  // hooks/bootstrap.ts
5942
6055
  var SKIP_GUIDANCE_AGENTS = new Set(["agentlife"]);
5943
- var SKIP_AGENTS_MD_INJECTION = new Set(["agentlife", "agentlife-builder", "quick", "supervisor"]);
6056
+ var SKIP_AGENTS_MD_INJECTION = new Set(["agentlife", "agentlife-builder", "agentlife-vision", "quick", "supervisor"]);
5944
6057
  function registerBootstrapHook(api, state2) {
5945
6058
  api.registerService({
5946
6059
  id: "agentlife-bootstrap-hook",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlife",
3
- "version": "1.4.0",
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",