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.
- package/dist/index.js +294 -182
- 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(
|
|
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(`###
|
|
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
|
-
|
|
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
|
|
4630
|
-
if (fs6.existsSync(DEVICE_FILE))
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
}
|
|
4637
|
-
}
|
|
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
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
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,
|