agentlife 1.1.4 → 1.1.6

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 +261 -143
  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
 
@@ -4852,14 +4952,13 @@ function mintBootstrapToken() {
4852
4952
  function registerWebApp(api) {
4853
4953
  const pluginRoot = path9.resolve(path9.dirname(api.source), "..");
4854
4954
  const appRoot = path9.join(pluginRoot, "web-build");
4855
- if (!fs7.existsSync(appRoot)) {
4856
- api.logger.info("[agentlife] web-build/ not found /agentlife/ route not registered");
4857
- return;
4858
- }
4859
- const indexPath = path9.join(appRoot, "index.html");
4860
- if (!fs7.existsSync(indexPath)) {
4861
- api.logger.warn("[agentlife] web-build/index.html missing — /agentlife/ route not registered");
4862
- return;
4955
+ const hasWebBuild = fs7.existsSync(appRoot);
4956
+ const indexPath = hasWebBuild ? path9.join(appRoot, "index.html") : null;
4957
+ const hasIndex = !!(indexPath && fs7.existsSync(indexPath));
4958
+ if (!hasWebBuild) {
4959
+ api.logger.info("[agentlife] web-build/ not found — /agentlife/pair will still register; static dashboard disabled");
4960
+ } else if (!hasIndex) {
4961
+ api.logger.warn("[agentlife] web-build/index.html missing — /agentlife/pair will still register; static dashboard disabled");
4863
4962
  }
4864
4963
  let gatewayToken = "";
4865
4964
  try {
@@ -4971,6 +5070,14 @@ function registerWebApp(api) {
4971
5070
  res.end(JSON.stringify(payload));
4972
5071
  return true;
4973
5072
  }
5073
+ if (!hasWebBuild || !hasIndex) {
5074
+ res.writeHead(404, {
5075
+ "Content-Type": "text/plain",
5076
+ ...corsHeaders(req)
5077
+ });
5078
+ res.end("AgentLife WASM dashboard not bundled with this plugin install. Use /agentlife/pair for QR pairing, or install the web build separately.");
5079
+ return true;
5080
+ }
4974
5081
  const relative = urlPath.replace(/^\/agentlife\/?/, "") || "index.html";
4975
5082
  const filePath = path9.resolve(appRoot, relative);
4976
5083
  if (!filePath.startsWith(appRoot)) {
@@ -5012,7 +5119,7 @@ function registerWebApp(api) {
5012
5119
  return true;
5013
5120
  }
5014
5121
  });
5015
- api.logger.info("[agentlife] /agentlife/ route registered — serving WASM web app");
5122
+ api.logger.info(hasWebBuild && hasIndex ? "[agentlife] /agentlife/ route registered — serving WASM web app + /agentlife/pair" : "[agentlife] /agentlife/ route registered — /agentlife/pair only (no web-build)");
5016
5123
  }
5017
5124
 
5018
5125
  // services/pairing.ts
@@ -7077,6 +7184,17 @@ function register(api) {
7077
7184
  } catch {}
7078
7185
  respond(true, { registered: true });
7079
7186
  }, { scope: "operator.write" });
7187
+ api.registerGatewayMethod("agentlife.vision.ensure", async ({ respond }) => {
7188
+ try {
7189
+ if (!currentState) {
7190
+ return respond(false, { error: "plugin state not initialized" });
7191
+ }
7192
+ const result = await ensureVisionPosters(currentState, api.runtime, console.log);
7193
+ respond(true, result);
7194
+ } catch (e) {
7195
+ respond(false, { code: "internal_error", message: e?.message ?? String(e) });
7196
+ }
7197
+ }, { scope: "operator.read" });
7080
7198
  }
7081
7199
  export {
7082
7200
  register as default,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlife",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "bun build index.ts --outfile dist/index.js --target node --external openclaw/plugin-sdk",