agentlife 1.1.5 → 1.1.7

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 +294 -182
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1261,6 +1261,52 @@ Your task is to create vision widgets, NOT to create or modify agents. The messa
1261
1261
 
1262
1262
  Do NOT call agentlife.createAgent. Do NOT write workspace files. Do NOT spawn sub-sessions. Do NOT ask interactive questions. The user is not present — this runs at install time. Just read, build, push.
1263
1263
 
1264
+ ## Handling Vision Dismiss
1265
+
1266
+ When you receive \`[system:dismiss-requested] surfaceId=vision-{slug}\` — this is vision-specific dismiss handling. It is a third mode, distinct from agent creation and from dashboard bootstrap, and it OVERRIDES the general dismiss-alt protocol described elsewhere.
1267
+
1268
+ The general dismiss-alt rule says "craft 2-3 contextual alternatives from the widget's goal." For vision posters that rule does NOT apply. Vision posters are aspirational content with a closed ontology of failure modes — the user is rejecting either the dream itself, its framing, its timing, or its dashboard placement. Improvising alternatives per dismiss would produce inconsistent wording across dismissals, which reads as broken UX. Use the fixed wording below verbatim.
1269
+
1270
+ ### Step 1 — Push the dismiss-alt input surface
1271
+
1272
+ Push exactly this surface (substitute \`{slug}\` with the dismissed vision's slug). Wording is fixed: do not paraphrase, translate, or reorder the buttons. The user's language detection happens at synthesis time, not at dismiss time.
1273
+
1274
+ \`\`\`
1275
+ surface dismiss-alt-vision-{slug} input
1276
+ card
1277
+ column
1278
+ text "Before I remove this:" h4
1279
+ button "This isn't my dream anymore" action=choice
1280
+ button "Wrong framing" action=choice
1281
+ button "Already achieved" action=choice
1282
+ button "Don't show on dashboard" action=choice
1283
+ button "Remove it" action=choice
1284
+ goal: Capture vision dismiss reason for vision-{slug}
1285
+ followup: +1m "User did not respond to vision dismiss alternatives. Say done."
1286
+ \`\`\`
1287
+
1288
+ ### Step 2 — On the user's choice, persist the reason
1289
+
1290
+ When you receive \`[action:choice]\` on the dismiss-alt-vision-{slug} surface, append one line to your own workspace file at \`~/.openclaw/workspace-agentlife-builder/memory/vision-feedback.md\` (create the file and parent directory if missing):
1291
+
1292
+ \`\`\`
1293
+ - {ISO timestamp} vision-{slug}: {chosen reason verbatim}
1294
+ \`\`\`
1295
+
1296
+ Use exec for the append: \`mkdir -p ~/.openclaw/workspace-agentlife-builder/memory && echo "..." >> ~/.openclaw/workspace-agentlife-builder/memory/vision-feedback.md\`. This file is read by the next dashboard bootstrap as negative constraints under "## Previously rejected dreams" — your own future self will use it.
1297
+
1298
+ ### Step 3 — Let the dismiss complete
1299
+
1300
+ Do NOT push a confirmation widget. Do NOT push \`delete vision-{slug}\`. The widget disappearing is the confirmation. The platform completes the dismiss after you process the action; you only need to record the reason.
1301
+
1302
+ If you receive \`[event:dismissed] surfaceId=vision-{slug}\` afterwards, the surface is already gone — respond "done" and stop.
1303
+
1304
+ ### What this is NOT
1305
+
1306
+ - Not a domain widget dismiss (those use the general contextual dismiss-alt rules).
1307
+ - Not an opportunity to generate a replacement vision (the bootstrap path handles regeneration).
1308
+ - Not a place to ask follow-up questions or push additional surfaces beyond the fixed dismiss-alt above.
1309
+
1264
1310
  ## Deleting an Agent
1265
1311
 
1266
1312
  1. agentlife.deleteAgent: {"id": "{agentId}"}
@@ -1394,9 +1440,7 @@ var PROVISIONED_AGENTS = [
1394
1440
  id: "agentlife-builder",
1395
1441
  name: "AgentLife Builder",
1396
1442
  agentsMd: BUILDER_AGENTS_MD,
1397
- tools: {
1398
- allow: ["agentlife_push"]
1399
- }
1443
+ tools: { profile: "full" }
1400
1444
  },
1401
1445
  {
1402
1446
  id: "supervisor",
@@ -1463,12 +1507,27 @@ function scheduleEnrichment(state, runtime, targets, log) {
1463
1507
  async function readAgentMemory(workspace) {
1464
1508
  if (!workspace)
1465
1509
  return "";
1466
- const memoryDir = path3.join(workspace, "memory");
1467
1510
  const parts = [];
1511
+ const rootFiles = [
1512
+ ["SOUL.md", 600],
1513
+ ["AGENTS.md", 1200],
1514
+ ["USER.md", 1200]
1515
+ ];
1516
+ for (const [name, cap] of rootFiles) {
1517
+ try {
1518
+ const content = await fs2.readFile(path3.join(workspace, name), "utf-8");
1519
+ const trimmed = content.trim();
1520
+ if (trimmed)
1521
+ parts.push(`### ${name}
1522
+ ${trimmed.slice(0, cap)}`);
1523
+ } catch {}
1524
+ }
1525
+ const memoryDir = path3.join(workspace, "memory");
1468
1526
  try {
1469
1527
  const index = await fs2.readFile(path3.join(memoryDir, "MEMORY.md"), "utf-8");
1470
1528
  if (index.trim())
1471
- parts.push(index.trim());
1529
+ parts.push(`### memory/MEMORY.md
1530
+ ${index.trim()}`);
1472
1531
  } catch {}
1473
1532
  try {
1474
1533
  const files = await fs2.readdir(memoryDir);
@@ -1477,7 +1536,7 @@ async function readAgentMemory(workspace) {
1477
1536
  try {
1478
1537
  const content = await fs2.readFile(path3.join(memoryDir, file), "utf-8");
1479
1538
  if (content.trim())
1480
- parts.push(`### ${file}
1539
+ parts.push(`### memory/${file}
1481
1540
  ${content.trim().slice(0, 600)}`);
1482
1541
  } catch {}
1483
1542
  }
@@ -1513,6 +1572,174 @@ ${capped}`);
1513
1572
 
1514
1573
  `) };
1515
1574
  }
1575
+ var VISION_MIN_MEMORY_CHARS = 200;
1576
+ async function ensureVisionPosters(state, runtime, log, options = {}) {
1577
+ if (!state.surfaceDb) {
1578
+ log("[agentlife] ensureVisionPosters: skipped — surfaceDb not initialized");
1579
+ return { status: "skipped", reason: "no_surface_db" };
1580
+ }
1581
+ let visionCount = 0;
1582
+ for (const surfaceId of state.surfaceDb.keys()) {
1583
+ if (surfaceId.startsWith("vision-"))
1584
+ visionCount++;
1585
+ }
1586
+ if (visionCount > 0) {
1587
+ log(`[agentlife] ensureVisionPosters: already_exists — ${visionCount} vision surface(s) in surfaceDb`);
1588
+ return { status: "already_exists", count: visionCount };
1589
+ }
1590
+ const SKIP_IDS = new Set(PROVISIONED_AGENTS.filter((a) => !a.existingAgent).map((a) => a.id));
1591
+ const finalList = runtime.config.loadConfig().agents?.list ?? [];
1592
+ const specialistCount = [...state.agentRegistry.keys()].filter((id) => !SKIP_IDS.has(id)).length;
1593
+ if (specialistCount === 0) {
1594
+ log("[agentlife] ensureVisionPosters: skipped — specialistCount=0 (no user agents registered)");
1595
+ return { status: "skipped", reason: "no_specialists" };
1596
+ }
1597
+ if (!state.runCommand) {
1598
+ log("[agentlife] ensureVisionPosters: skipped — runCommand not available on runtime");
1599
+ return { status: "skipped", reason: "no_run_command" };
1600
+ }
1601
+ const { totalChars, sections } = await gatherAllAgentMemory(state, finalList, SKIP_IDS);
1602
+ if (totalChars < VISION_MIN_MEMORY_CHARS) {
1603
+ log(`[agentlife] ensureVisionPosters: skipped — totalChars=${totalChars} below threshold ${VISION_MIN_MEMORY_CHARS} (memory too thin)`);
1604
+ return { status: "skipped", reason: "thin_memory", details: `totalChars=${totalChars}` };
1605
+ }
1606
+ const homeDir = options.homedirOverride ?? os.homedir();
1607
+ const builderWorkspace = path3.join(homeDir, ".openclaw", "workspace-agentlife-builder");
1608
+ const feedbackFile = path3.join(builderWorkspace, "memory", "vision-feedback.md");
1609
+ let rejectedDreams = "";
1610
+ try {
1611
+ const content = await fs2.readFile(feedbackFile, "utf-8");
1612
+ if (content.trim())
1613
+ rejectedDreams = content.trim().slice(0, 4000);
1614
+ } catch {}
1615
+ const builderKey = buildAgentSessionKey("agentlife-builder");
1616
+ const bootstrapMsg = [
1617
+ `[system:dashboard-bootstrap] The dashboard is empty. Push 3-4 vision posters with agentlife_push.`,
1618
+ ``,
1619
+ `## What you do`,
1620
+ ``,
1621
+ `Pick 2 to 4 dreams from the user data below — quality over quantity. For each, copy one of the 3 DSL templates exactly and replace the placeholders with the user's own words. Push via agentlife_push.`,
1622
+ ``,
1623
+ `## Picking the dreams`,
1624
+ ``,
1625
+ `Find aspirational signal — moments where the user names a future they want, a target they're chasing, a transformation they're working toward. Operational logging is not signal.`,
1626
+ ``,
1627
+ `Each poster covers a DIFFERENT life area. Body, money, place, work, relationships, identity, craft are distinct. If two candidates share a product, person, or domain name, they're the same area — pick one.`,
1628
+ ``,
1629
+ `Pick the dream version of each goal — the one-to-three-year outcome if everything goes right, not the next checkpoint. The first real revenue is a checkpoint; the first profitable year is a dream.`,
1630
+ ``,
1631
+ `## Quality bar`,
1632
+ ``,
1633
+ `It is 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.`,
1634
+ ``,
1635
+ `## Language consistency`,
1636
+ ``,
1637
+ `Detect the primary language the user writes in (look at the data: Spanish, English, etc.). Write ALL posters in that single language. Never mix languages across the carousel.`,
1638
+ ``,
1639
+ `## The 3 templates`,
1640
+ ``,
1641
+ `Every template renders emoji + headline + support + a bottom element. The bottom element changes per template.`,
1642
+ ``,
1643
+ `### TEMPLATE A — Statement (best for identity, body, declarative truths)`,
1644
+ ``,
1645
+ `surface vision-{slug} size=l`,
1646
+ ` card`,
1647
+ ` column align=center`,
1648
+ ` text "{EMOJI}" h1`,
1649
+ ` text "{HEADLINE}" h1 color={ACCENT}`,
1650
+ ` text "{SUPPORT}" body`,
1651
+ ` text "{MOMENT}" h4 color={ACCENT}`,
1652
+ `goal: Vision poster — {slug}`,
1653
+ `followup: +30d "Refresh this vision poster if the user's data has shifted."`,
1654
+ ``,
1655
+ `### TEMPLATE B — Destination (best for place, move, new chapter)`,
1656
+ ``,
1657
+ `surface vision-{slug} size=l`,
1658
+ ` card`,
1659
+ ` column align=center`,
1660
+ ` text "{EMOJI}" h1`,
1661
+ ` text "{HEADLINE}" h1 color={ACCENT}`,
1662
+ ` text "{SUPPORT}" body`,
1663
+ ` badge "{PLACE}" color={ACCENT} outlined`,
1664
+ `goal: Vision poster — {slug}`,
1665
+ `followup: +30d "Refresh this vision poster if the user's data has shifted."`,
1666
+ ``,
1667
+ `### TEMPLATE C — Outcome (best for work, money, craft)`,
1668
+ ``,
1669
+ `surface vision-{slug} size=l`,
1670
+ ` card`,
1671
+ ` column align=center`,
1672
+ ` text "{EMOJI}" h1`,
1673
+ ` text "{HEADLINE}" h1 color={ACCENT}`,
1674
+ ` text "{SUPPORT}" body`,
1675
+ ` row distribute=spaceEvenly`,
1676
+ ` text "{C1}" h4 color={ACCENT}`,
1677
+ ` text "·" h4`,
1678
+ ` text "{C2}" h4 color={ACCENT}`,
1679
+ ` text "·" h4`,
1680
+ ` text "{C3}" h4 color={ACCENT}`,
1681
+ `goal: Vision poster — {slug}`,
1682
+ `followup: +30d "Refresh this vision poster if the user's data has shifted."`,
1683
+ ``,
1684
+ `## Placeholder rules`,
1685
+ ``,
1686
+ `- {slug}: short kebab-case slug for the life area`,
1687
+ `- {ACCENT}: hex color. Vary across posters: try #10B981, #7C3AED, #3B82F6, #F59E0B, #EC4899, #14B8A6`,
1688
+ `- {EMOJI}: a single emoji capturing the emotional core. Visceral, not literal.`,
1689
+ `- {HEADLINE}: 3-5 words, declarative, first-person if natural, periods welcome. The dream version, not the next checkpoint.`,
1690
+ `- {SUPPORT}: 6-12 words, one short sentence. Concrete supporting context in the user's actual language.`,
1691
+ `- {MOMENT} (Template A): 1-3 words, a time anchor — season, year, phase`,
1692
+ `- {PLACE} (Template B): 1-3 words, must be a real geographic place name (not a date)`,
1693
+ `- {C1}, {C2}, {C3} (Template C): 1-2 words each, concept beats from the user's vocabulary`,
1694
+ ``,
1695
+ `## Variety rule`,
1696
+ ``,
1697
+ `Across the 3-4 posters, use AT LEAST 2 different templates.`,
1698
+ ``,
1699
+ `## Hard constraints`,
1700
+ ``,
1701
+ `- Copy ONE of the 3 templates above exactly. Do not add components. Do not remove components. Do not change order.`,
1702
+ `- Use the user's actual language from the data — never invent.`,
1703
+ ``,
1704
+ ...rejectedDreams ? [
1705
+ `## Previously rejected dreams`,
1706
+ ``,
1707
+ `The user has dismissed these past vision posters with the following reasons. Do NOT re-pick these dreams or reframe them with the same emphasis — the user already told you they were wrong.`,
1708
+ ``,
1709
+ rejectedDreams,
1710
+ ``
1711
+ ] : [],
1712
+ `## USER DATA`,
1713
+ ``,
1714
+ sections.slice(0, 12000),
1715
+ ``,
1716
+ `## Output`,
1717
+ ``,
1718
+ `Push each poster with agentlife_push. After the last push, respond "Done".`
1719
+ ].join(`
1720
+ `);
1721
+ const params = JSON.stringify({
1722
+ sessionKey: builderKey,
1723
+ message: bootstrapMsg,
1724
+ idempotencyKey: `dashboard-bootstrap-${Date.now()}`
1725
+ });
1726
+ const delayMs = options.delayMs ?? 0;
1727
+ const feedbackChars = rejectedDreams.length;
1728
+ const runBootstrap = () => {
1729
+ log(`[agentlife] ensureVisionPosters: synthesizing (${totalChars} chars memory across ${specialistCount} specialists${feedbackChars > 0 ? `, ${feedbackChars} chars prior feedback` : ""})`);
1730
+ state.runCommand(["openclaw", "gateway", "call", "chat.send", "--params", params], { timeoutMs: 300000 }).then(() => {
1731
+ log("[agentlife] ensureVisionPosters: dashboard bootstrap complete");
1732
+ }).catch((e) => {
1733
+ log(`[agentlife] ensureVisionPosters: dashboard bootstrap failed: ${e?.message}`);
1734
+ });
1735
+ };
1736
+ if (delayMs > 0) {
1737
+ setTimeout(runBootstrap, delayMs);
1738
+ } else {
1739
+ runBootstrap();
1740
+ }
1741
+ return { status: "synthesizing", specialistCount, totalChars, feedbackChars };
1742
+ }
1516
1743
  async function provisionAgents(state, cfg, runtime, log) {
1517
1744
  const home = os.homedir();
1518
1745
  const currentList = [...cfg.agents?.list ?? []];
@@ -1668,134 +1895,7 @@ async function provisionAgents(state, cfg, runtime, log) {
1668
1895
  }, 1e4);
1669
1896
  }
1670
1897
  }
1671
- const specialistCount = [...state.agentRegistry.keys()].filter((id) => !SKIP_IDS.has(id)).length;
1672
- let hasVisionPosters = false;
1673
- if (state.surfaceDb) {
1674
- for (const surfaceId of state.surfaceDb.keys()) {
1675
- if (surfaceId.startsWith("vision-")) {
1676
- hasVisionPosters = true;
1677
- break;
1678
- }
1679
- }
1680
- }
1681
- if (!hasVisionPosters && specialistCount > 0 && state.runCommand) {
1682
- const { totalChars, sections } = await gatherAllAgentMemory(state, finalList, SKIP_IDS);
1683
- if (totalChars < 200) {
1684
- log("[agentlife] skipping dashboard bootstrap — not enough memory data yet");
1685
- } else {
1686
- const builderKey = buildAgentSessionKey("agentlife-builder");
1687
- const bootstrapMsg = [
1688
- `[system:dashboard-bootstrap] The dashboard is empty. Push 3-4 vision posters with agentlife_push.`,
1689
- ``,
1690
- `## What you do`,
1691
- ``,
1692
- `Pick 2 to 4 dreams from the user data below — quality over quantity. For each, copy one of the 3 DSL templates exactly and replace the placeholders with the user's own words. Push via agentlife_push.`,
1693
- ``,
1694
- `## Picking the dreams`,
1695
- ``,
1696
- `Find aspirational signal — moments where the user names a future they want, a target they're chasing, a transformation they're working toward. Operational logging is not signal.`,
1697
- ``,
1698
- `Each poster covers a DIFFERENT life area. Body, money, place, work, relationships, identity, craft are distinct. If two candidates share a product, person, or domain name, they're the same area — pick one.`,
1699
- ``,
1700
- `Pick the dream version of each goal — the one-to-three-year outcome if everything goes right, not the next checkpoint. The first real revenue is a checkpoint; the first profitable year is a dream.`,
1701
- ``,
1702
- `## Quality bar`,
1703
- ``,
1704
- `It is 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.`,
1705
- ``,
1706
- `## Language consistency`,
1707
- ``,
1708
- `Detect the primary language the user writes in (look at the data: Spanish, English, etc.). Write ALL posters in that single language. Never mix languages across the carousel.`,
1709
- ``,
1710
- `## The 3 templates`,
1711
- ``,
1712
- `Every template renders emoji + headline + support + a bottom element. The bottom element changes per template.`,
1713
- ``,
1714
- `### TEMPLATE A — Statement (best for identity, body, declarative truths)`,
1715
- ``,
1716
- `surface vision-{slug} size=l`,
1717
- ` card`,
1718
- ` column align=center`,
1719
- ` text "{EMOJI}" h1`,
1720
- ` text "{HEADLINE}" h1 color={ACCENT}`,
1721
- ` text "{SUPPORT}" body`,
1722
- ` text "{MOMENT}" h4 color={ACCENT}`,
1723
- `goal: Vision poster — {slug}`,
1724
- `followup: +30d "Refresh this vision poster if the user's data has shifted."`,
1725
- ``,
1726
- `### TEMPLATE B — Destination (best for place, move, new chapter)`,
1727
- ``,
1728
- `surface vision-{slug} size=l`,
1729
- ` card`,
1730
- ` column align=center`,
1731
- ` text "{EMOJI}" h1`,
1732
- ` text "{HEADLINE}" h1 color={ACCENT}`,
1733
- ` text "{SUPPORT}" body`,
1734
- ` badge "{PLACE}" color={ACCENT} outlined`,
1735
- `goal: Vision poster — {slug}`,
1736
- `followup: +30d "Refresh this vision poster if the user's data has shifted."`,
1737
- ``,
1738
- `### TEMPLATE C — Outcome (best for work, money, craft)`,
1739
- ``,
1740
- `surface vision-{slug} size=l`,
1741
- ` card`,
1742
- ` column align=center`,
1743
- ` text "{EMOJI}" h1`,
1744
- ` text "{HEADLINE}" h1 color={ACCENT}`,
1745
- ` text "{SUPPORT}" body`,
1746
- ` row distribute=spaceEvenly`,
1747
- ` text "{C1}" h4 color={ACCENT}`,
1748
- ` text "·" h4`,
1749
- ` text "{C2}" h4 color={ACCENT}`,
1750
- ` text "·" h4`,
1751
- ` text "{C3}" h4 color={ACCENT}`,
1752
- `goal: Vision poster — {slug}`,
1753
- `followup: +30d "Refresh this vision poster if the user's data has shifted."`,
1754
- ``,
1755
- `## Placeholder rules`,
1756
- ``,
1757
- `- {slug}: short kebab-case slug for the life area`,
1758
- `- {ACCENT}: hex color. Vary across posters: try #10B981, #7C3AED, #3B82F6, #F59E0B, #EC4899, #14B8A6`,
1759
- `- {EMOJI}: a single emoji capturing the emotional core. Visceral, not literal.`,
1760
- `- {HEADLINE}: 3-5 words, declarative, first-person if natural, periods welcome. The dream version, not the next checkpoint.`,
1761
- `- {SUPPORT}: 6-12 words, one short sentence. Concrete supporting context in the user's actual language.`,
1762
- `- {MOMENT} (Template A): 1-3 words, a time anchor — season, year, phase`,
1763
- `- {PLACE} (Template B): 1-3 words, must be a real geographic place name (not a date)`,
1764
- `- {C1}, {C2}, {C3} (Template C): 1-2 words each, concept beats from the user's vocabulary`,
1765
- ``,
1766
- `## Variety rule`,
1767
- ``,
1768
- `Across the 3-4 posters, use AT LEAST 2 different templates.`,
1769
- ``,
1770
- `## Hard constraints`,
1771
- ``,
1772
- `- Copy ONE of the 3 templates above exactly. Do not add components. Do not remove components. Do not change order.`,
1773
- `- Use the user's actual language from the data — never invent.`,
1774
- ``,
1775
- `## USER DATA`,
1776
- ``,
1777
- sections.slice(0, 12000),
1778
- ``,
1779
- `## Output`,
1780
- ``,
1781
- `Push each poster with agentlife_push. After the last push, respond "Done".`
1782
- ].join(`
1783
- `);
1784
- const params = JSON.stringify({
1785
- sessionKey: builderKey,
1786
- message: bootstrapMsg,
1787
- idempotencyKey: `dashboard-bootstrap-${Date.now()}`
1788
- });
1789
- setTimeout(() => {
1790
- log(`[agentlife] bootstrapping empty dashboard via builder (${totalChars} chars of memory across ${specialistCount} specialists)`);
1791
- state.runCommand(["openclaw", "gateway", "call", "chat.send", "--params", params], { timeoutMs: 300000 }).then(() => {
1792
- log("[agentlife] dashboard bootstrap complete");
1793
- }).catch((e) => {
1794
- log(`[agentlife] dashboard bootstrap failed: ${e?.message}`);
1795
- });
1796
- }, 15000);
1797
- }
1798
- }
1898
+ await ensureVisionPosters(state, runtime, log, { delayMs: 15000 });
1799
1899
  log("[agentlife] agent provisioning complete");
1800
1900
  }
1801
1901
 
@@ -4626,16 +4726,24 @@ function ensureDirs() {
4626
4726
  if (!fs6.existsSync(BIN_DIR))
4627
4727
  fs6.mkdirSync(BIN_DIR, { recursive: true, mode: 448 });
4628
4728
  }
4629
- function loadOrCreateDeviceIdentity() {
4630
- if (fs6.existsSync(DEVICE_FILE)) {
4631
- try {
4632
- const raw = fs6.readFileSync(DEVICE_FILE, "utf-8");
4633
- const parsed = JSON.parse(raw);
4634
- if (typeof parsed.deviceId === "string" && typeof parsed.deviceSecret === "string") {
4635
- return { deviceId: parsed.deviceId, deviceSecret: parsed.deviceSecret };
4636
- }
4637
- } catch {}
4729
+ function loadDeviceIdentity() {
4730
+ if (!fs6.existsSync(DEVICE_FILE))
4731
+ return null;
4732
+ try {
4733
+ const raw = fs6.readFileSync(DEVICE_FILE, "utf-8");
4734
+ const parsed = JSON.parse(raw);
4735
+ if (typeof parsed.deviceId === "string" && typeof parsed.deviceSecret === "string") {
4736
+ return { deviceId: parsed.deviceId, deviceSecret: parsed.deviceSecret };
4737
+ }
4738
+ return null;
4739
+ } catch {
4740
+ return null;
4638
4741
  }
4742
+ }
4743
+ function loadOrCreateDeviceIdentity() {
4744
+ const existing = loadDeviceIdentity();
4745
+ if (existing)
4746
+ return existing;
4639
4747
  const identity = {
4640
4748
  deviceId: crypto2.randomUUID(),
4641
4749
  deviceSecret: crypto2.randomBytes(32).toString("base64url")
@@ -4647,6 +4755,7 @@ function loadOrCreateDeviceIdentity() {
4647
4755
  console.log(`[cloudflared-supervisor] generated new device identity (deviceId=${identity.deviceId})`);
4648
4756
  return identity;
4649
4757
  }
4758
+ var AGENTLIFE_API_BASE = API_BASE;
4650
4759
  function loadCachedTunnel() {
4651
4760
  if (!fs6.existsSync(TUNNEL_FILE))
4652
4761
  return null;
@@ -5024,57 +5133,22 @@ function registerWebApp(api) {
5024
5133
 
5025
5134
  // services/pairing.ts
5026
5135
  var TUNNEL_READY_TIMEOUT_MS = 90000;
5027
- async function detectTunnelUrl(port) {
5028
- try {
5029
- const cfgPath = path10.join(os6.homedir(), ".cloudflared", "config.yml");
5030
- if (!fsSync3.existsSync(cfgPath))
5031
- return null;
5032
- const cfg = fsSync3.readFileSync(cfgPath, "utf-8");
5033
- const lines = cfg.split(`
5034
- `);
5035
- for (let i = 0;i < lines.length; i++) {
5036
- const hostMatch = lines[i].match(/hostname:\s*(.+)/);
5037
- if (hostMatch) {
5038
- const nextLine = lines[i + 1] || "";
5039
- const svcMatch = nextLine.match(/service:\s*http:\/\/localhost:(\d+)/);
5040
- if (svcMatch && parseInt(svcMatch[1]) === port) {
5041
- return `wss://${hostMatch[1].trim()}`;
5042
- }
5043
- }
5044
- }
5045
- return null;
5046
- } catch {
5047
- return null;
5048
- }
5049
- }
5050
5136
  function registerPairingServices(api) {
5051
5137
  api.registerService({
5052
5138
  id: "agentlife-pairing-qr",
5053
5139
  start: async (_ctx) => {
5054
5140
  try {
5055
5141
  const cfg = api.runtime.config.loadConfig();
5056
- const bind = cfg.gateway?.bind ?? "loopback";
5057
5142
  const port = cfg.gateway?.port ?? 18789;
5058
5143
  const token = cfg.gateway?.auth?.token;
5059
5144
  if (!token)
5060
5145
  return;
5061
5146
  const supervisorTunnel = await awaitTunnelReady(TUNNEL_READY_TIMEOUT_MS) ?? getTunnelInfo();
5062
- let url;
5063
- if (supervisorTunnel?.tunnelUrl) {
5064
- url = supervisorTunnel.tunnelUrl;
5065
- } else {
5066
- console.warn("[agentlife] Tunnel not ready after " + TUNNEL_READY_TIMEOUT_MS / 1000 + "s — falling back to LAN URL. Phone pairing will only work on the same Wi-Fi.");
5067
- const userTunnel = await detectTunnelUrl(port);
5068
- if (userTunnel) {
5069
- url = userTunnel;
5070
- } else if (bind === "lan" || bind === "auto" || bind === "custom") {
5071
- const nets = os6.networkInterfaces();
5072
- const lanIp = Object.values(nets).flat().find((n) => n && !n.internal && n.family === "IPv4")?.address;
5073
- url = `ws://${lanIp ?? "127.0.0.1"}:${port}`;
5074
- } else {
5075
- url = `ws://127.0.0.1:${port}`;
5076
- }
5147
+ if (!supervisorTunnel?.tunnelUrl) {
5148
+ console.warn(`[agentlife] Cloudflared tunnel did not come up within ${TUNNEL_READY_TIMEOUT_MS / 1000}s — ` + `pairing QR will not be emitted. Check cloudflared-supervisor logs in the gateway output.`);
5149
+ return;
5077
5150
  }
5151
+ const url = supervisorTunnel.tunnelUrl;
5078
5152
  const bootstrapToken = mintBootstrapToken();
5079
5153
  const payload = JSON.stringify({ url, bootstrapToken });
5080
5154
  const setupCode = Buffer.from(payload).toString("base64url");
@@ -6609,6 +6683,33 @@ function registerAdminGateway(api, state2) {
6609
6683
  try {
6610
6684
  const cleaned = [];
6611
6685
  const baseDir = state2.agentlifeStateDir ?? path12.join(os7.homedir(), ".openclaw", "agentlife");
6686
+ const identity = loadDeviceIdentity();
6687
+ if (!identity) {
6688
+ cleaned.push("server deprovision skipped (no device identity)");
6689
+ } else {
6690
+ try {
6691
+ const res = await fetch(`${AGENTLIFE_API_BASE}/api/v1/devices/deprovision`, {
6692
+ method: "POST",
6693
+ headers: { "Content-Type": "application/json" },
6694
+ body: JSON.stringify({
6695
+ deviceId: identity.deviceId,
6696
+ deviceSecret: identity.deviceSecret
6697
+ }),
6698
+ signal: AbortSignal.timeout(1e4)
6699
+ });
6700
+ if (res.ok) {
6701
+ cleaned.push("deprovisioned tunnel on server");
6702
+ } else if (res.status === 404) {
6703
+ cleaned.push("server had no record of this device (already gone)");
6704
+ } else {
6705
+ cleaned.push(`server deprovision returned HTTP ${res.status} (continuing)`);
6706
+ console.warn(`[agentlife] uninstall: deprovision HTTP ${res.status}, continuing with local cleanup`);
6707
+ }
6708
+ } catch (err) {
6709
+ cleaned.push(`server deprovision failed (continuing): ${err?.message ?? "unknown error"}`);
6710
+ console.warn(`[agentlife] uninstall: deprovision failed (${err?.message ?? err}), continuing with local cleanup`);
6711
+ }
6712
+ }
6612
6713
  if (state2.runCommand) {
6613
6714
  try {
6614
6715
  const result = await state2.runCommand(["openclaw", "cron", "list", "--json"], { timeoutMs: 1e4 });
@@ -7084,6 +7185,17 @@ function register(api) {
7084
7185
  } catch {}
7085
7186
  respond(true, { registered: true });
7086
7187
  }, { scope: "operator.write" });
7188
+ api.registerGatewayMethod("agentlife.vision.ensure", async ({ respond }) => {
7189
+ try {
7190
+ if (!currentState) {
7191
+ return respond(false, { error: "plugin state not initialized" });
7192
+ }
7193
+ const result = await ensureVisionPosters(currentState, api.runtime, console.log);
7194
+ respond(true, result);
7195
+ } catch (e) {
7196
+ respond(false, { code: "internal_error", message: e?.message ?? String(e) });
7197
+ }
7198
+ }, { scope: "operator.read" });
7087
7199
  }
7088
7200
  export {
7089
7201
  register as default,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlife",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "bun build index.ts --outfile dist/index.js --target node --external openclaw/plugin-sdk",