agentlife 1.3.2 → 1.4.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 +1376 -452
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@ import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
4
  // index.ts
5
- import { homedir as homedir9 } from "node:os";
6
- import * as path13 from "node:path";
5
+ import { homedir as homedir11 } from "node:os";
6
+ import * as path15 from "node:path";
7
7
  import { existsSync as existsSync6 } from "node:fs";
8
8
 
9
9
  // db.ts
@@ -193,6 +193,31 @@ function getOrCreateHistoryDb(state) {
193
193
  );
194
194
  CREATE INDEX IF NOT EXISTS idx_pqc_createdAt ON pending_quality_checks(createdAt);
195
195
 
196
+ CREATE TABLE IF NOT EXISTS cold_start_state (
197
+ id INTEGER PRIMARY KEY CHECK (id = 1),
198
+ phase TEXT NOT NULL,
199
+ enteredAt INTEGER NOT NULL,
200
+ lastActionAt INTEGER,
201
+ ackAt INTEGER,
202
+ deadlineAt INTEGER,
203
+ retryCount INTEGER NOT NULL DEFAULT 0,
204
+ failureReason TEXT,
205
+ lastTrigger TEXT,
206
+ actionSessionKey TEXT,
207
+ contractWarnings TEXT,
208
+ pollerLastTickAt INTEGER,
209
+ updatedAt INTEGER NOT NULL
210
+ );
211
+
212
+ CREATE TABLE IF NOT EXISTS cold_start_triggers (
213
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
214
+ kind TEXT NOT NULL,
215
+ payload TEXT NOT NULL,
216
+ createdAt INTEGER NOT NULL
217
+ );
218
+ CREATE INDEX IF NOT EXISTS idx_cold_start_triggers_createdAt
219
+ ON cold_start_triggers(createdAt);
220
+
196
221
  `);
197
222
  try {
198
223
  state.historyDb.exec("ALTER TABLE surfaces ADD COLUMN cronId TEXT");
@@ -203,6 +228,9 @@ function getOrCreateHistoryDb(state) {
203
228
  try {
204
229
  state.historyDb.exec("ALTER TABLE surfaces ADD COLUMN originSessionKey TEXT");
205
230
  } catch {}
231
+ try {
232
+ state.historyDb.exec("ALTER TABLE surfaces ADD COLUMN dashboardVisible INTEGER NOT NULL DEFAULT 1");
233
+ } catch {}
206
234
  try {
207
235
  const moved = state.historyDb.prepare(`
208
236
  INSERT INTO activity_log (ts, sessionKey, agentId, event, data)
@@ -324,6 +352,13 @@ class SurfaceDb {
324
352
  setState(id, state) {
325
353
  this.db.prepare("UPDATE surfaces SET state = ? WHERE surfaceId = ?").run(state, id);
326
354
  }
355
+ getDashboardVisible(id) {
356
+ const row = this.db.prepare("SELECT dashboardVisible FROM surfaces WHERE surfaceId = ?").get(id);
357
+ return row?.dashboardVisible !== 0;
358
+ }
359
+ setDashboardVisible(id, visible) {
360
+ this.db.prepare("UPDATE surfaces SET dashboardVisible = ? WHERE surfaceId = ?").run(visible ? 1 : 0, id);
361
+ }
327
362
  getActiveSurfacesWithCrons() {
328
363
  const rows = this.db.prepare("SELECT surfaceId, cronId FROM surfaces WHERE cronId IS NOT NULL AND state != 'dismissed'").all();
329
364
  return rows.map((r) => ({ surfaceId: r.surfaceId, cronId: r.cronId }));
@@ -736,6 +771,28 @@ When receiving via sessions_send: push widgets with results. If nothing to push,
736
771
 
737
772
  On every followup: review what you know, update stale entries. After every user interaction: did you learn something new? Store it in context: or agentlife.db.
738
773
 
774
+ ### \`[system:start]\` — First-Run Baseline Capture
775
+
776
+ When you receive \`[system:start]\` on your direct session, you were just created by the builder. You have zero memory about this user. Your one-turn job:
777
+
778
+ 1. Push a loading widget (\`state=loading\`) introducing yourself per \`IDENTITY.md\`.
779
+ 2. Push ONE \`input\` surface asking the most important baseline question for your domain. Use button choices where possible; offer a textfield for open-ended answers. The final widget push (non-loading) can be the input surface itself.
780
+ 3. Respond \`done\`.
781
+
782
+ On each subsequent \`[action:choice]\` or \`[action:submit]\` referencing a \`warmup-*\` surfaceId you pushed:
783
+ 1. Append the answer to \`memory/baseline.md\` in your workspace via \`exec\` (create the \`memory/\` directory if missing). This file persists across sessions and feeds \`[system:dashboard-bootstrap]\`.
784
+ 2. Delete the answered input surface.
785
+ 3. Push the NEXT baseline question as a new \`warmup-*\` input surface, OR — if you've asked enough to establish a meaningful baseline for your domain — stop.
786
+ 4. Respond \`done\`.
787
+
788
+ Rules:
789
+ - 2-4 total questions, one at a time. Never ask multiple questions in a single surface.
790
+ - Questions must be domain-relevant baseline — the minimum your domain needs to personalize first recommendations. Your AGENTS.md defines what baseline matters.
791
+ - Capture answers via \`exec\` writing to \`memory/baseline.md\`. Do NOT rely on session history — baseline must persist.
792
+ - No dashboard widgets in this flow beyond the intro + input surfaces. Domain work starts after baseline.
793
+ - Match the user's language.
794
+ - If \`memory/baseline.md\` already exists and is non-empty on entry, skip the questions and respond \`done\`.
795
+
739
796
  `;
740
797
  var INTERNAL_AGENTS_MD = `
741
798
  ## Internal Session — Self-Improvement Protocol
@@ -1253,64 +1310,6 @@ When you receive "Enrich agent registry descriptions" — system task, no widget
1253
1310
 
1254
1311
  For each agent: read workspace AGENTS.md + SOUL.md → write one-sentence description via agentlife.createAgent. Specific domains, actions, data types. Process all, respond "Done".
1255
1312
 
1256
- ## Dashboard Bootstrap
1257
-
1258
- When you receive "[system:dashboard-bootstrap]" — this is a different mode from agent creation.
1259
-
1260
- Your task is to create vision widgets, NOT to create or modify agents. The message contains the user's data inline and the widget structure. Read the data, follow the structure, push the widgets with agentlife_push, then respond "Done".
1261
-
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
-
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
- **CRITICAL — session scope:** this section applies ONLY on your DIRECT session (where the user is live and can see widgets). The platform also notifies your INTERNAL session with the same \`[system:dismiss-requested]\` message as a cleanup hook AFTER the widget is already deleted. If you see this message on an internal session, do NOT push a dismiss-alt, do NOT run the steps below — the widget is gone and pushing a questionnaire creates a ghost surface that nobody can interact with. On internal sessions, output "done" and stop.
1269
-
1270
- Direct session vs internal session: internal sessions receive the "Internal Session — Self-Improvement Protocol" preamble at the top of their system prompt. If you see that preamble in your context, you are on an internal session; this Vision Dismiss section does NOT apply to you, output "done".
1271
-
1272
- 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.
1273
-
1274
- ### Step 1 — Push the dismiss-alt input surface
1275
-
1276
- 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.
1277
-
1278
- \`\`\`
1279
- surface dismiss-alt-vision-{slug} input
1280
- card
1281
- column
1282
- text "Before I remove this:" h4
1283
- button "This isn't my dream anymore" action=choice
1284
- button "Wrong framing" action=choice
1285
- button "Already achieved" action=choice
1286
- button "Don't show on dashboard" action=choice
1287
- button "Remove it" action=choice
1288
- goal: Capture vision dismiss reason for vision-{slug}
1289
- followup: +1m "User did not respond to vision dismiss alternatives. Say done."
1290
- \`\`\`
1291
-
1292
- ### Step 2 — On the user's choice, persist the reason
1293
-
1294
- 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):
1295
-
1296
- \`\`\`
1297
- - {ISO timestamp} vision-{slug}: {chosen reason verbatim}
1298
- \`\`\`
1299
-
1300
- 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.
1301
-
1302
- ### Step 3 — Let the dismiss complete
1303
-
1304
- 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.
1305
-
1306
- If you receive \`[event:dismissed] surfaceId=vision-{slug}\` afterwards, the surface is already gone — respond "done" and stop.
1307
-
1308
- ### What this is NOT
1309
-
1310
- - Not a domain widget dismiss (those use the general contextual dismiss-alt rules).
1311
- - Not an opportunity to generate a replacement vision (the bootstrap path handles regeneration).
1312
- - Not a place to ask follow-up questions or push additional surfaces beyond the fixed dismiss-alt above.
1313
-
1314
1313
  ## Deleting an Agent
1315
1314
 
1316
1315
  1. agentlife.deleteAgent: {"id": "{agentId}"}
@@ -1339,16 +1338,16 @@ If you receive \`[event:dismissed] surfaceId=vision-{slug}\` afterwards, the sur
1339
1338
 
1340
1339
  When you receive \`[action:*]\` on a confirmation widget: the user wants to use the agent you just created. Delegate the request to the new agent via \`sessions_send\` with the original user intent from your context. Delete your confirmation widget — the new agent takes over.
1341
1340
 
1342
- ## Onboarding (Cold Start)
1341
+ ## Platform-Dispatched Entry Points — \`[system:*]\` Messages
1343
1342
 
1344
- When you receive \`[system:onboarding]\` the user has no agents yet. This is a fresh install. Your job is to help them create their FIRST agent with minimal friction. One question, one agent, break the cold start.
1343
+ The plugin's cold-start state machine dispatches these messages to your direct session at phase transitions. Each entry point is **atomic**: one starting message, one completion criterion. None overlap, none override your normal creation/improvement flow.
1345
1344
 
1346
- ### Step 1 — Push a guided input surface
1345
+ ### \`[system:onboarding]\`
1347
1346
 
1348
- Push a single \`input\` surface with life-domain choices. One question, one tap. The user picks a domain; you create an agent for it.
1347
+ User just installed AgentLife with zero specialist agents. Push the life-domain input surface, then \`done\`. Do not create any agent yet.
1349
1348
 
1350
1349
  \`\`\`
1351
- surface onboarding input
1350
+ surface onboarding-domain input
1352
1351
  card
1353
1352
  column
1354
1353
  text "What area of your life should your first agent focus on?" h4
@@ -1358,28 +1357,158 @@ surface onboarding input
1358
1357
  button "Finance & money" action=choice
1359
1358
  button "Something else" action=choice
1360
1359
  goal: User picks a life domain for their first agent
1361
- followup: +5m "User did not respond to onboarding. Say done."
1360
+ followup: +30m "User did not respond to onboarding. Say done."
1361
+ \`\`\`
1362
+
1363
+ Translate button labels into the user's locale if known. Do not vary the surfaceId \`onboarding-domain\` — the state machine observes it.
1364
+
1365
+ **When \`[action:choice] surfaceId=onboarding-domain label=<domain>\` arrives next, execute the full creation flow. You MUST complete every step below. A "ready" confirmation widget pushed without the \`agentlife.createAgent\` RPC call is a critical platform violation — the user sees a green "Ready" badge for an agent that does not exist, and the state machine never advances out of AWAITING_AGENT.**
1366
+
1367
+ Required call sequence for the post-choice creation turn:
1368
+
1369
+ 1. **Delete the input surface.** Push \`delete onboarding-domain\` (or \`delete health-qN\` if you asked follow-ups).
1370
+ 2. **Clarifying questions (optional, 1-2 max).** If domain framing is ambiguous, push \`warmup-{slug}\` input surfaces to narrow scope. Skip this step if the label is already specific enough to create from.
1371
+ 3. **Discover environment.** If the domain requires system facts (networks, devices, installed tools), run \`exec\` commands per Step 2 of the main "Creating a New Agent" flow above. Skip if the domain is purely behavioral (health, finance).
1372
+ 4. **Write workspace files.** \`exec mkdir -p ~/.openclaw/workspace-{agentId}\` then \`write\` AGENTS.md, SOUL.md, IDENTITY.md, USER.md, HEARTBEAT.md. Follow Step 3 of the main creation flow — every file present, no placeholders.
1373
+ 5. **Register the agent via \`exec\`.** Call \`exec openclaw gateway call agentlife.createAgent --params '<JSON>'\`. The JSON must include \`id\`, \`name\`, \`workspace\` (absolute path), \`description\`, \`tools\`, \`identity\`. This call is what makes the agent real — no shortcut.
1374
+ 6. **Only after createAgent returns ok**, push the confirmation widget per Step 6 of the main creation flow.
1375
+
1376
+ Forbidden shortcuts:
1377
+ - Do NOT push a "Ready" / "Created" / "Setting up" style confirmation widget before steps 4 and 5 complete.
1378
+ - Do NOT emit \`done\` until the RPC call in step 5 succeeded (check the response, not just that the tool ran).
1379
+ - Do NOT write about the agent being ready in chat text — the confirmation is the widget, and only after step 5.
1380
+
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
+
1383
+ ### \`[system:dashboard-bootstrap]\`
1384
+
1385
+ 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**:
1386
+
1387
+ 1. Push 1-4 \`vision-*\` posters quoted from USER DATA, then respond \`done\`.
1388
+ 2. Respond \`NO_SIGNAL\` alone on its own line and STOP.
1389
+
1390
+ Pushing a poster and then deleting it is NOT a valid recovery — the user sees the poster flash and disappear, which reads as a broken app. Pick ONE outcome.
1391
+
1392
+ **Step 1 — Scan for aspirational signal.** In your own reasoning (not pushed), list the exact phrases where the user names a future they want, a target they're chasing, or a transformation they're working toward. Operational logging (times, weights, metrics, "no baseline yet") is NOT signal. Tone specs, tool schemas, and agent descriptions are NOT signal.
1393
+
1394
+ **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
+
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).
1397
+
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.
1399
+
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."
1362
1411
  \`\`\`
1363
1412
 
1364
- Detect the user's language from the gateway context (session transcript, locale). If the user writes in Spanish, push the widget in Spanish. Match the user's language do NOT default to English.
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
+ \`\`\`
1425
+
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
+ \`\`\`
1443
+
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
1365
1453
 
1366
- ### Step 2 — On the user's choice, create the agent
1454
+ Variety rule: across the 3-4 posters, use AT LEAST 2 different templates.
1367
1455
 
1368
- When the user taps a choice, follow your standard agent creation flow (Steps 1–6 above) but SKIP the interactive question phase the user already told you the domain. Go straight to:
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.
1369
1457
 
1370
- 1. Environment discovery (exec checks relevant to the domain)
1371
- 2. Create workspace + all files (AGENTS.md, SOUL.md, IDENTITY.md, USER.md, HEARTBEAT.md)
1372
- 3. Register via agentlife.createAgent
1373
- 4. Push the confirmation widget
1374
- 5. Delete the onboarding input surface
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.
1375
1459
 
1376
- If the user taps "Something else," push ONE follow-up textfield input asking them to describe the domain in a sentence. Then proceed with creation.
1460
+ After all pushes, respond \`done\` and STOP. If you cannot find signal, respond \`NO_SIGNAL\` alone on its own line and STOP.
1461
+
1462
+ ### \`[system:ask-dream]\`
1463
+
1464
+ The bootstrap reported \`NO_SIGNAL\`. Push the dream-capture input surface and \`done\`. Do NOT push posters from this entry — the plugin re-fires \`[system:dashboard-bootstrap]\` with \`userDream:\` injected after the user answers.
1465
+
1466
+ \`\`\`
1467
+ surface awaiting-dream-input input
1468
+ card
1469
+ column
1470
+ text "Tell me one dream or goal you want to track" h4
1471
+ button "Body & health" action=choice
1472
+ button "Money & work" action=choice
1473
+ button "Place & life chapter" action=choice
1474
+ button "Craft or identity" action=choice
1475
+ goal: Capture the user's dream so the dashboard can synthesize a vision poster
1476
+ followup: +30m "User did not respond to dream-ask. Say done."
1477
+ \`\`\`
1377
1478
 
1378
- ### What this is NOT
1479
+ Translate labels into the user's locale if known. Do not vary the surfaceId \`awaiting-dream-input\` — the state machine observes it.
1379
1480
 
1380
- - Not a comprehensive onboarding wizard. One question. One agent. The user can create more later by chatting normally.
1381
- - Not a place to explain what AgentLife is. The user installed the plugin — they know.
1382
- - Not a place to push multiple widgets. One input, one confirmation, done.
1481
+ ### \`[system:dismiss-requested] surfaceId=vision-*\`
1482
+
1483
+ User dismissed a vision poster. Push the fixed dismiss-alt surface below. Wording is fixed: do not paraphrase, translate, or reorder buttons.
1484
+
1485
+ \`\`\`
1486
+ surface dismiss-alt-vision-{slug} input
1487
+ card
1488
+ column
1489
+ text "Before I remove this:" h4
1490
+ button "This isn't my dream anymore" action=choice
1491
+ button "Wrong framing" action=choice
1492
+ button "Already achieved" action=choice
1493
+ button "Don't show on dashboard" action=choice
1494
+ button "Remove it" action=choice
1495
+ goal: Capture vision dismiss reason for vision-{slug}
1496
+ followup: +1m "User did not respond to vision dismiss alternatives. Say done."
1497
+ \`\`\`
1498
+
1499
+ After pushing, respond \`done\`.
1500
+
1501
+ When \`[action:choice]\` arrives on \`dismiss-alt-vision-{slug}\`, append one line to your feedback memory via exec, then \`done\`:
1502
+
1503
+ \`\`\`
1504
+ exec mkdir -p ~/.openclaw/workspace-agentlife-builder/memory && \\
1505
+ echo "- $(date -u +%Y-%m-%dT%H:%M:%SZ) vision-{slug}: {reason}" \\
1506
+ >> ~/.openclaw/workspace-agentlife-builder/memory/vision-feedback.md
1507
+ \`\`\`
1508
+
1509
+ This file is injected as "Previously rejected dreams" into the next \`[system:dashboard-bootstrap]\` so future synthesis avoids the same misreads.
1510
+
1511
+ Do NOT push a confirmation widget. Do NOT push \`delete vision-{slug}\` — the platform handles removal (soft-delete for paywall persistence; the widget disappears from the dashboard). If you receive \`[event:dismissed] surfaceId=vision-{slug}\` afterwards, the surface state has already been finalized — respond \`done\`.
1383
1512
 
1384
1513
  ## What You Are Not
1385
1514
 
@@ -1604,216 +1733,6 @@ ${capped}`);
1604
1733
 
1605
1734
  `) };
1606
1735
  }
1607
- var VISION_MIN_MEMORY_CHARS = 200;
1608
- var BOOTSTRAP_COOLDOWN_MS = 10 * 60 * 1000;
1609
- var lastBootstrapSentAt = 0;
1610
- var lastWarmupSentAt = 0;
1611
- function resetBootstrapCooldown() {
1612
- lastBootstrapSentAt = 0;
1613
- lastWarmupSentAt = 0;
1614
- }
1615
- async function ensureVisionPosters(state, runtime, log, options = {}) {
1616
- if (!state.surfaceDb) {
1617
- log("[agentlife] ensureVisionPosters: skipped — surfaceDb not initialized");
1618
- return { status: "skipped", reason: "no_surface_db" };
1619
- }
1620
- let visionCount = 0;
1621
- for (const surfaceId of state.surfaceDb.keys()) {
1622
- if (surfaceId.startsWith("vision-"))
1623
- visionCount++;
1624
- }
1625
- if (visionCount > 0) {
1626
- log(`[agentlife] ensureVisionPosters: already_exists — ${visionCount} vision surface(s) in surfaceDb`);
1627
- return { status: "already_exists", count: visionCount };
1628
- }
1629
- const SKIP_IDS = new Set(PROVISIONED_AGENTS.filter((a) => !a.existingAgent).map((a) => a.id));
1630
- const finalList = runtime.config.loadConfig().agents?.list ?? [];
1631
- const specialistCount = [...state.agentRegistry.keys()].filter((id) => !SKIP_IDS.has(id)).length;
1632
- if (specialistCount === 0) {
1633
- log("[agentlife] ensureVisionPosters: skipped — specialistCount=0 (no user agents registered)");
1634
- return { status: "skipped", reason: "no_specialists" };
1635
- }
1636
- if (!state.runCommand) {
1637
- log("[agentlife] ensureVisionPosters: skipped — runCommand not available on runtime");
1638
- return { status: "skipped", reason: "no_run_command" };
1639
- }
1640
- const { totalChars, sections } = await gatherAllAgentMemory(state, finalList, SKIP_IDS);
1641
- if (totalChars < VISION_MIN_MEMORY_CHARS) {
1642
- const warmupNow = Date.now();
1643
- if (state.runCommand && warmupNow - lastWarmupSentAt >= BOOTSTRAP_COOLDOWN_MS) {
1644
- const specialistId = [...state.agentRegistry.keys()].find((id) => !SKIP_IDS.has(id));
1645
- if (specialistId) {
1646
- lastWarmupSentAt = warmupNow;
1647
- const warmupKey = buildAgentSessionKey(specialistId);
1648
- const warmupMsg = [
1649
- `[system:warmup] You were just created and have no user data yet.`,
1650
- ``,
1651
- `1. Push a guided input widget (input surface, action=choice buttons) asking the user for the most important baseline info for your domain. One question at a time, 2-3 questions max.`,
1652
- `2. After each answer, WRITE the data to your memory directory: exec mkdir -p ~/.openclaw/workspace-${specialistId}/memory && write the answer to a file there (e.g., memory/baseline.md). This is critical — your memory directory is what the platform reads to generate the user's vision dashboard. If you only store data in widget context, it's lost when the widget is dismissed.`,
1653
- `3. After collecting all answers, push a DASHBOARD widget (NOT input) summarizing what you learned. This widget must NOT have the "input" keyword — it renders on the main dashboard, not the input bar.`,
1654
- `4. Use the user's language.`,
1655
- ``,
1656
- `If you already collected baseline data in a previous session, check your memory directory. If it has content, push a dashboard summary widget and respond "done". Do NOT re-ask questions you already have answers for.`
1657
- ].join(`
1658
- `);
1659
- const warmupParams = JSON.stringify({
1660
- sessionKey: warmupKey,
1661
- message: warmupMsg,
1662
- idempotencyKey: `warmup-${specialistId}-${Date.now()}`
1663
- });
1664
- state.runCommand(["openclaw", "gateway", "call", "chat.send", "--params", warmupParams], { timeoutMs: 120000 }).then(() => {
1665
- log(`[agentlife] ensureVisionPosters: warmup sent to ${specialistId}`);
1666
- }).catch((e) => {
1667
- log(`[agentlife] ensureVisionPosters: warmup failed for ${specialistId}: ${e?.message}`);
1668
- });
1669
- }
1670
- }
1671
- log(`[agentlife] ensureVisionPosters: skipped — totalChars=${totalChars} below threshold ${VISION_MIN_MEMORY_CHARS} (memory too thin)`);
1672
- return { status: "skipped", reason: "thin_memory", details: `totalChars=${totalChars}` };
1673
- }
1674
- const homeDir = options.homedirOverride ?? os.homedir();
1675
- const builderWorkspace = path3.join(homeDir, ".openclaw", "workspace-agentlife-builder");
1676
- const feedbackFile = path3.join(builderWorkspace, "memory", "vision-feedback.md");
1677
- let rejectedDreams = "";
1678
- try {
1679
- const content = await fs2.readFile(feedbackFile, "utf-8");
1680
- if (content.trim())
1681
- rejectedDreams = content.trim().slice(0, 4000);
1682
- } catch {}
1683
- const now = Date.now();
1684
- if (now - lastBootstrapSentAt < BOOTSTRAP_COOLDOWN_MS) {
1685
- log(`[agentlife] ensureVisionPosters: skipped — bootstrap cooldown (${Math.round((BOOTSTRAP_COOLDOWN_MS - (now - lastBootstrapSentAt)) / 1000)}s remaining)`);
1686
- return { status: "skipped", reason: "cooldown", details: `${Math.round((BOOTSTRAP_COOLDOWN_MS - (now - lastBootstrapSentAt)) / 1000)}s remaining` };
1687
- }
1688
- const builderKey = buildAgentSessionKey("agentlife-builder");
1689
- const bootstrapMsg = [
1690
- `[system:dashboard-bootstrap] The dashboard is empty. Push 3-4 vision posters with agentlife_push.`,
1691
- ``,
1692
- `## What you do`,
1693
- ``,
1694
- `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.`,
1695
- ``,
1696
- `## Picking the dreams`,
1697
- ``,
1698
- `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.`,
1699
- ``,
1700
- `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.`,
1701
- ``,
1702
- `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.`,
1703
- ``,
1704
- `## Quality bar`,
1705
- ``,
1706
- `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.`,
1707
- ``,
1708
- `## Language consistency`,
1709
- ``,
1710
- `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.`,
1711
- ``,
1712
- `## The 3 templates`,
1713
- ``,
1714
- `Every template renders emoji + headline + support + a bottom element. The bottom element changes per template.`,
1715
- ``,
1716
- `### TEMPLATE A — Statement (best for identity, body, declarative truths)`,
1717
- ``,
1718
- `surface vision-{slug} size=l`,
1719
- ` card`,
1720
- ` column align=center`,
1721
- ` text "{EMOJI}" h1`,
1722
- ` text "{HEADLINE}" h1 color={ACCENT}`,
1723
- ` text "{SUPPORT}" body`,
1724
- ` text "{MOMENT}" h4 color={ACCENT}`,
1725
- `goal: Vision poster — {slug}`,
1726
- `followup: +30d "Refresh this vision poster if the user's data has shifted."`,
1727
- ``,
1728
- `### TEMPLATE B — Destination (best for place, move, new chapter)`,
1729
- ``,
1730
- `surface vision-{slug} size=l`,
1731
- ` card`,
1732
- ` column align=center`,
1733
- ` text "{EMOJI}" h1`,
1734
- ` text "{HEADLINE}" h1 color={ACCENT}`,
1735
- ` text "{SUPPORT}" body`,
1736
- ` badge "{PLACE}" color={ACCENT} outlined`,
1737
- `goal: Vision poster — {slug}`,
1738
- `followup: +30d "Refresh this vision poster if the user's data has shifted."`,
1739
- ``,
1740
- `### TEMPLATE C — Outcome (best for work, money, craft)`,
1741
- ``,
1742
- `surface vision-{slug} size=l`,
1743
- ` card`,
1744
- ` column align=center`,
1745
- ` text "{EMOJI}" h1`,
1746
- ` text "{HEADLINE}" h1 color={ACCENT}`,
1747
- ` text "{SUPPORT}" body`,
1748
- ` row distribute=spaceEvenly`,
1749
- ` text "{C1}" h4 color={ACCENT}`,
1750
- ` text "·" h4`,
1751
- ` text "{C2}" h4 color={ACCENT}`,
1752
- ` text "·" h4`,
1753
- ` text "{C3}" h4 color={ACCENT}`,
1754
- `goal: Vision poster — {slug}`,
1755
- `followup: +30d "Refresh this vision poster if the user's data has shifted."`,
1756
- ``,
1757
- `## Placeholder rules`,
1758
- ``,
1759
- `- {slug}: short kebab-case slug for the life area`,
1760
- `- {ACCENT}: hex color. Vary across posters: try #10B981, #7C3AED, #3B82F6, #F59E0B, #EC4899, #14B8A6`,
1761
- `- {EMOJI}: a single emoji capturing the emotional core. Visceral, not literal.`,
1762
- `- {HEADLINE}: 3-5 words, declarative, first-person if natural, periods welcome. The dream version, not the next checkpoint.`,
1763
- `- {SUPPORT}: 6-12 words, one short sentence. Concrete supporting context in the user's actual language.`,
1764
- `- {MOMENT} (Template A): 1-3 words, a time anchor — season, year, phase`,
1765
- `- {PLACE} (Template B): 1-3 words, must be a real geographic place name (not a date)`,
1766
- `- {C1}, {C2}, {C3} (Template C): 1-2 words each, concept beats from the user's vocabulary`,
1767
- ``,
1768
- `## Variety rule`,
1769
- ``,
1770
- `Across the 3-4 posters, use AT LEAST 2 different templates.`,
1771
- ``,
1772
- `## Hard constraints`,
1773
- ``,
1774
- `- Copy ONE of the 3 templates above exactly. Do not add components. Do not remove components. Do not change order.`,
1775
- `- Use the user's actual language from the data — never invent.`,
1776
- ``,
1777
- ...rejectedDreams ? [
1778
- `## Previously rejected dreams`,
1779
- ``,
1780
- `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.`,
1781
- ``,
1782
- rejectedDreams,
1783
- ``
1784
- ] : [],
1785
- `## USER DATA`,
1786
- ``,
1787
- sections.slice(0, 12000),
1788
- ``,
1789
- `## Output`,
1790
- ``,
1791
- `Push each poster with agentlife_push. After the last push, respond "Done".`
1792
- ].join(`
1793
- `);
1794
- const params = JSON.stringify({
1795
- sessionKey: builderKey,
1796
- message: bootstrapMsg,
1797
- idempotencyKey: `dashboard-bootstrap-${Date.now()}`
1798
- });
1799
- lastBootstrapSentAt = Date.now();
1800
- const delayMs = options.delayMs ?? 0;
1801
- const feedbackChars = rejectedDreams.length;
1802
- const runBootstrap = () => {
1803
- log(`[agentlife] ensureVisionPosters: synthesizing (${totalChars} chars memory across ${specialistCount} specialists${feedbackChars > 0 ? `, ${feedbackChars} chars prior feedback` : ""})`);
1804
- state.runCommand(["openclaw", "gateway", "call", "chat.send", "--params", params], { timeoutMs: 300000 }).then(() => {
1805
- log("[agentlife] ensureVisionPosters: dashboard bootstrap complete");
1806
- }).catch((e) => {
1807
- log(`[agentlife] ensureVisionPosters: dashboard bootstrap failed: ${e?.message}`);
1808
- });
1809
- };
1810
- if (delayMs > 0) {
1811
- setTimeout(runBootstrap, delayMs);
1812
- } else {
1813
- runBootstrap();
1814
- }
1815
- return { status: "synthesizing", specialistCount, totalChars, feedbackChars };
1816
- }
1817
1736
  async function provisionAgents(state, cfg, runtime, log) {
1818
1737
  const home = os.homedir();
1819
1738
  const currentList = [...cfg.agents?.list ?? []];
@@ -1880,10 +1799,12 @@ async function provisionAgents(state, cfg, runtime, log) {
1880
1799
  log(`[agentlife] backfilled subagents for ${agent.id}`);
1881
1800
  }
1882
1801
  }
1883
- const rawCfgForVisibility = JSON.parse(readFileSync(path3.join(os.homedir(), ".openclaw", "openclaw.json"), "utf-8"));
1802
+ const configPath = path3.join(os.homedir(), ".openclaw", "openclaw.json");
1803
+ const rawCfgForVisibility = JSON.parse(readFileSync(configPath, "utf-8"));
1804
+ const backupPath = path3.join(os.homedir(), ".openclaw", "agentlife", "config-backup.json");
1805
+ let visibilityWritten = false;
1884
1806
  const currentVisibility = rawCfgForVisibility?.tools?.sessions?.visibility;
1885
1807
  if (currentVisibility !== "all") {
1886
- const backupPath = path3.join(os.homedir(), ".openclaw", "agentlife", "config-backup.json");
1887
1808
  try {
1888
1809
  let backup = {};
1889
1810
  try {
@@ -1898,14 +1819,40 @@ async function provisionAgents(state, cfg, runtime, log) {
1898
1819
  if (!rawCfgForVisibility.tools.sessions)
1899
1820
  rawCfgForVisibility.tools.sessions = {};
1900
1821
  rawCfgForVisibility.tools.sessions.visibility = "all";
1901
- writeFileSync(path3.join(os.homedir(), ".openclaw", "openclaw.json"), JSON.stringify(rawCfgForVisibility, null, 2) + `
1822
+ visibilityWritten = true;
1823
+ }
1824
+ const currentAllow = rawCfgForVisibility?.tools?.allow;
1825
+ let globalAllowWritten = false;
1826
+ if (Array.isArray(currentAllow) && currentAllow.length > 0 && !currentAllow.includes("*")) {
1827
+ const onlyPluginTool = currentAllow.length === 1 && currentAllow[0] === "agentlife_push";
1828
+ if (onlyPluginTool) {
1829
+ try {
1830
+ let backup = {};
1831
+ try {
1832
+ backup = JSON.parse(readFileSync(backupPath, "utf-8"));
1833
+ } catch {}
1834
+ if (backup.toolsAllow === undefined) {
1835
+ backup.toolsAllow = [...currentAllow];
1836
+ writeFileSync(backupPath, JSON.stringify(backup, null, 2) + `
1837
+ `, "utf-8");
1838
+ }
1839
+ } catch {}
1840
+ rawCfgForVisibility.tools.allow = ["*", "agentlife_push"];
1841
+ globalAllowWritten = true;
1842
+ }
1843
+ }
1844
+ if (visibilityWritten || globalAllowWritten) {
1845
+ writeFileSync(configPath, JSON.stringify(rawCfgForVisibility, null, 2) + `
1902
1846
  `, "utf-8");
1903
1847
  configChanged = true;
1904
- log("[agentlife] set tools.sessions.visibility=all (cross-agent delegation)");
1848
+ if (visibilityWritten)
1849
+ log("[agentlife] set tools.sessions.visibility=all (cross-agent delegation)");
1850
+ if (globalAllowWritten)
1851
+ log('[agentlife] added "*" to tools.allow (unblock exec/read/write for provisioned agents)');
1905
1852
  }
1906
1853
  if (configChanged) {
1907
- const configPath = path3.join(os.homedir(), ".openclaw", "openclaw.json");
1908
- const rawCfg = JSON.parse(readFileSync(configPath, "utf-8"));
1854
+ const configPath2 = path3.join(os.homedir(), ".openclaw", "openclaw.json");
1855
+ const rawCfg = JSON.parse(readFileSync(configPath2, "utf-8"));
1909
1856
  const updatedCfg = {
1910
1857
  ...rawCfg,
1911
1858
  agents: {
@@ -1913,7 +1860,7 @@ async function provisionAgents(state, cfg, runtime, log) {
1913
1860
  list: currentList
1914
1861
  }
1915
1862
  };
1916
- writeFileSync(configPath, JSON.stringify(updatedCfg, null, 2) + `
1863
+ writeFileSync(configPath2, JSON.stringify(updatedCfg, null, 2) + `
1917
1864
  `, "utf-8");
1918
1865
  log("[agentlife] config updated with provisioned agents");
1919
1866
  }
@@ -1991,12 +1938,538 @@ async function provisionAgents(state, cfg, runtime, log) {
1991
1938
  }, 1e4);
1992
1939
  }
1993
1940
  }
1994
- await ensureVisionPosters(state, runtime, log, { delayMs: 15000 });
1995
1941
  log("[agentlife] agent provisioning complete");
1996
1942
  }
1997
1943
 
1998
- // services/surfaces-init.ts
1944
+ // cold-start.ts
1945
+ import * as fs3 from "node:fs/promises";
1946
+ import * as nodeFs from "node:fs";
1947
+ import * as os2 from "node:os";
1999
1948
  import * as path4 from "node:path";
1949
+ var DEADLINE_MS = {
1950
+ AWAITING_AGENT: 30 * 60 * 1000,
1951
+ AWAITING_BASELINE: 30 * 60 * 1000,
1952
+ SYNTHESIZING: 3 * 60 * 1000,
1953
+ AWAITING_DREAM: 30 * 60 * 1000
1954
+ };
1955
+ var VISION_MIN_MEMORY_CHARS = 200;
1956
+ var PHASE_BLANK = "BLANK";
1957
+ function readState(state) {
1958
+ const db = getOrCreateHistoryDb(state);
1959
+ const row = db.prepare("SELECT * FROM cold_start_state WHERE id = 1").get();
1960
+ if (!row)
1961
+ return null;
1962
+ let warnings = [];
1963
+ if (row.contractWarnings) {
1964
+ try {
1965
+ warnings = JSON.parse(row.contractWarnings);
1966
+ } catch {}
1967
+ }
1968
+ return {
1969
+ phase: row.phase,
1970
+ enteredAt: row.enteredAt,
1971
+ lastActionAt: row.lastActionAt ?? null,
1972
+ ackAt: row.ackAt ?? null,
1973
+ deadlineAt: row.deadlineAt ?? null,
1974
+ retryCount: row.retryCount ?? 0,
1975
+ failureReason: row.failureReason ?? null,
1976
+ lastTrigger: row.lastTrigger ?? null,
1977
+ actionSessionKey: row.actionSessionKey ?? null,
1978
+ contractWarnings: warnings,
1979
+ pollerLastTickAt: row.pollerLastTickAt ?? null,
1980
+ updatedAt: row.updatedAt
1981
+ };
1982
+ }
1983
+ function ensureInitialRow(state) {
1984
+ const existing = readState(state);
1985
+ if (existing)
1986
+ return existing;
1987
+ const now = Date.now();
1988
+ const db = getOrCreateHistoryDb(state);
1989
+ db.prepare(`
1990
+ INSERT OR IGNORE INTO cold_start_state
1991
+ (id, phase, enteredAt, retryCount, contractWarnings, updatedAt)
1992
+ VALUES (1, ?, ?, 0, '[]', ?)
1993
+ `).run(PHASE_BLANK, now, now);
1994
+ return readState(state);
1995
+ }
1996
+ function writeState(state, partial) {
1997
+ const current = ensureInitialRow(state);
1998
+ const next = { ...current, ...partial, updatedAt: Date.now() };
1999
+ const db = getOrCreateHistoryDb(state);
2000
+ db.prepare(`
2001
+ UPDATE cold_start_state SET
2002
+ phase = ?,
2003
+ enteredAt = ?,
2004
+ lastActionAt = ?,
2005
+ ackAt = ?,
2006
+ deadlineAt = ?,
2007
+ retryCount = ?,
2008
+ failureReason = ?,
2009
+ lastTrigger = ?,
2010
+ actionSessionKey = ?,
2011
+ contractWarnings = ?,
2012
+ pollerLastTickAt = ?,
2013
+ updatedAt = ?
2014
+ WHERE id = 1
2015
+ `).run(next.phase, next.enteredAt, next.lastActionAt, next.ackAt, next.deadlineAt, next.retryCount, next.failureReason, next.lastTrigger, next.actionSessionKey, JSON.stringify(next.contractWarnings ?? []), next.pollerLastTickAt, next.updatedAt);
2016
+ return next;
2017
+ }
2018
+ function emitTrigger(state, kind, payload) {
2019
+ if (state.disabled)
2020
+ return;
2021
+ try {
2022
+ const db = getOrCreateHistoryDb(state);
2023
+ db.prepare(`
2024
+ INSERT INTO cold_start_triggers (kind, payload, createdAt) VALUES (?, ?, ?)
2025
+ `).run(kind, JSON.stringify(payload), Date.now());
2026
+ } catch (e) {
2027
+ console.warn("[cold-start] emitTrigger failed:", e?.message);
2028
+ }
2029
+ }
2030
+ function drainTriggers(state, limit = 50) {
2031
+ const db = getOrCreateHistoryDb(state);
2032
+ const rows = db.prepare("SELECT id, kind, payload FROM cold_start_triggers ORDER BY createdAt ASC LIMIT ?").all(limit);
2033
+ if (rows.length === 0)
2034
+ return [];
2035
+ const ids = rows.map((r) => r.id);
2036
+ const placeholders = ids.map(() => "?").join(",");
2037
+ db.prepare(`DELETE FROM cold_start_triggers WHERE id IN (${placeholders})`).run(...ids);
2038
+ return rows.map((r) => ({
2039
+ kind: r.kind,
2040
+ payload: safeJson(r.payload)
2041
+ }));
2042
+ }
2043
+ function safeJson(s) {
2044
+ try {
2045
+ return JSON.parse(s);
2046
+ } catch {
2047
+ return {};
2048
+ }
2049
+ }
2050
+ function purgeStaleTriggers(state) {
2051
+ const db = getOrCreateHistoryDb(state);
2052
+ const cutoff = Date.now() - 60 * 60 * 1000;
2053
+ const r = db.prepare("DELETE FROM cold_start_triggers WHERE createdAt < ?").run(cutoff);
2054
+ return r.changes;
2055
+ }
2056
+ function pendingTriggerCount(state) {
2057
+ const db = getOrCreateHistoryDb(state);
2058
+ const row = db.prepare("SELECT COUNT(*) as c FROM cold_start_triggers").get();
2059
+ return row?.c ?? 0;
2060
+ }
2061
+ var PROVISIONED_IDS = new Set(PROVISIONED_AGENTS.filter((a) => !a.existingAgent).map((a) => a.id));
2062
+ function listSpecialistIds(state, runtime) {
2063
+ const cfg = runtime.config.loadConfig();
2064
+ const list = cfg?.agents?.list ?? [];
2065
+ return list.map((a) => a?.id).filter((id) => !!id && !PROVISIONED_IDS.has(id));
2066
+ }
2067
+ async function specialistMemoryTotal(state, runtime) {
2068
+ const cfg = runtime.config.loadConfig();
2069
+ const list = cfg?.agents?.list ?? [];
2070
+ const { totalChars, sections } = await gatherAllAgentMemory(state, list, PROVISIONED_IDS);
2071
+ return { total: totalChars, sections };
2072
+ }
2073
+ function visionSurfaceCount(state) {
2074
+ if (!state.surfaceDb)
2075
+ return 0;
2076
+ let n = 0;
2077
+ for (const id of state.surfaceDb.keys())
2078
+ if (id.startsWith("vision-"))
2079
+ n++;
2080
+ return n;
2081
+ }
2082
+ async function sendSystemMessage(state, agentId, message, idempotencyKey, log) {
2083
+ if (!state.runCommand) {
2084
+ log(`[cold-start] sendSystemMessage ${idempotencyKey}: skipped — runCommand not available`);
2085
+ return null;
2086
+ }
2087
+ const sessionKey = buildAgentSessionKey(agentId);
2088
+ const chatParams = JSON.stringify({ sessionKey, message, idempotencyKey });
2089
+ state.runCommand(["openclaw", "gateway", "call", "chat.send", "--params", chatParams], { timeoutMs: 120000 }).then(() => {
2090
+ log(`[cold-start] sent ${idempotencyKey} → ${sessionKey}`);
2091
+ }).catch((e) => {
2092
+ log(`[cold-start] sendSystemMessage ${idempotencyKey} failed: ${e?.message}`);
2093
+ });
2094
+ return sessionKey;
2095
+ }
2096
+ async function composeBootstrapMessage(state, runtime, userDream) {
2097
+ const { sections } = await specialistMemoryTotal(state, runtime);
2098
+ const rejected = await readRejectedDreams();
2099
+ const parts = ["[system:dashboard-bootstrap]", "", "## USER DATA", "", sections.slice(0, 12000)];
2100
+ if (rejected)
2101
+ parts.push("", "## Previously rejected dreams", "", rejected);
2102
+ if (userDream)
2103
+ parts.push("", "## userDream", "", userDream);
2104
+ return parts.join(`
2105
+ `);
2106
+ }
2107
+ async function readRejectedDreams() {
2108
+ const builderWorkspace = path4.join(os2.homedir(), ".openclaw", "workspace-agentlife-builder");
2109
+ const feedbackFile = path4.join(builderWorkspace, "memory", "vision-feedback.md");
2110
+ try {
2111
+ const content = await fs3.readFile(feedbackFile, "utf-8");
2112
+ return content.trim().slice(0, 4000);
2113
+ } catch {
2114
+ return "";
2115
+ }
2116
+ }
2117
+ async function computeStartingPhase(state, runtime) {
2118
+ if (visionSurfaceCount(state) > 0) {
2119
+ return { phase: "READY", details: "vision surfaces already exist" };
2120
+ }
2121
+ const specialists = listSpecialistIds(state, runtime);
2122
+ if (specialists.length === 0) {
2123
+ return { phase: "BLANK", details: "no specialists" };
2124
+ }
2125
+ const { total } = await specialistMemoryTotal(state, runtime);
2126
+ if (total < VISION_MIN_MEMORY_CHARS) {
2127
+ return { phase: "AWAITING_BASELINE", details: `totalChars=${total} below ${VISION_MIN_MEMORY_CHARS}` };
2128
+ }
2129
+ return { phase: "SYNTHESIZING", details: `totalChars=${total} ready for synthesis` };
2130
+ }
2131
+ async function transition(state, runtime, log, trigger) {
2132
+ const cur = ensureInitialRow(state);
2133
+ log(`[cold-start] trigger=${trigger.kind} phase=${cur.phase}`);
2134
+ switch (trigger.kind) {
2135
+ case "provisioned":
2136
+ case "userRetried": {
2137
+ const { phase, details } = await computeStartingPhase(state, runtime);
2138
+ log(`[cold-start] recompute → ${phase} (${details})`);
2139
+ return enterPhase(state, runtime, log, phase, {
2140
+ bumpRetry: trigger.kind === "userRetried"
2141
+ });
2142
+ }
2143
+ case "agentRegistered": {
2144
+ if (cur.phase === "BLANK" || cur.phase === "AWAITING_AGENT") {
2145
+ return enterPhase(state, runtime, log, "AWAITING_BASELINE", {
2146
+ specialistId: trigger.payload.agentId
2147
+ });
2148
+ }
2149
+ return cur;
2150
+ }
2151
+ case "agentDeleted": {
2152
+ const remaining = trigger.payload.remaining;
2153
+ if (remaining === 0) {
2154
+ return enterPhase(state, runtime, log, "BLANK");
2155
+ }
2156
+ return cur;
2157
+ }
2158
+ case "memoryWritten": {
2159
+ if (cur.phase !== "AWAITING_BASELINE")
2160
+ return cur;
2161
+ const { total } = await specialistMemoryTotal(state, runtime);
2162
+ if (total >= VISION_MIN_MEMORY_CHARS) {
2163
+ return enterPhase(state, runtime, log, "SYNTHESIZING");
2164
+ }
2165
+ log(`[cold-start] memory still thin: ${total}/${VISION_MIN_MEMORY_CHARS}`);
2166
+ return cur;
2167
+ }
2168
+ case "surfacePushed": {
2169
+ return cur;
2170
+ }
2171
+ case "agentReplied": {
2172
+ const sessionKey = trigger.payload.sessionKey;
2173
+ const marker = trigger.payload.marker;
2174
+ const lastText = trigger.payload.lastText ?? "";
2175
+ if (cur.actionSessionKey && sessionKey && sessionKey !== cur.actionSessionKey) {
2176
+ return cur;
2177
+ }
2178
+ if (cur.phase === "SYNTHESIZING") {
2179
+ if (marker === "NO_SIGNAL" || /\bNO_SIGNAL\b/.test(lastText)) {
2180
+ return enterPhase(state, runtime, log, "AWAITING_DREAM");
2181
+ }
2182
+ if (visionSurfaceCount(state) >= 1) {
2183
+ return enterPhase(state, runtime, log, "READY");
2184
+ }
2185
+ return cur;
2186
+ }
2187
+ if (cur.phase === "AWAITING_BASELINE") {
2188
+ return cur;
2189
+ }
2190
+ return cur;
2191
+ }
2192
+ case "userAnswered": {
2193
+ const surfaceId = trigger.payload.surfaceId;
2194
+ const answer = trigger.payload.answer ?? "";
2195
+ if (cur.phase === "AWAITING_DREAM" && surfaceId === "awaiting-dream-input") {
2196
+ return enterPhase(state, runtime, log, "SYNTHESIZING", { userDream: answer });
2197
+ }
2198
+ return cur;
2199
+ }
2200
+ case "deadlineElapsed": {
2201
+ const elapsedPhase = trigger.payload.phase;
2202
+ if (elapsedPhase !== cur.phase)
2203
+ return cur;
2204
+ const reason = `${cur.phase.toLowerCase()}_timeout`;
2205
+ return enterFailed(state, log, reason);
2206
+ }
2207
+ }
2208
+ }
2209
+ async function enterPhase(state, runtime, log, next, opts = {}) {
2210
+ const cur = ensureInitialRow(state);
2211
+ const now = Date.now();
2212
+ const deadlineMs = DEADLINE_MS[next];
2213
+ const deadlineAt = deadlineMs ? now + deadlineMs : null;
2214
+ const retryCount = opts.bumpRetry ? cur.retryCount + 1 : cur.retryCount;
2215
+ let actionSessionKey = null;
2216
+ let lastActionAt = null;
2217
+ writeState(state, {
2218
+ phase: next,
2219
+ enteredAt: now,
2220
+ deadlineAt,
2221
+ retryCount,
2222
+ failureReason: next === "FAILED" ? cur.failureReason : null,
2223
+ actionSessionKey: null,
2224
+ lastActionAt: null,
2225
+ ackAt: null
2226
+ });
2227
+ log(`[cold-start] enter ${next} (deadline=${deadlineAt ? new Date(deadlineAt).toISOString() : "none"}, retry=${retryCount})`);
2228
+ if (next === "BLANK") {
2229
+ const idem = `cold-start-onboarding-${now}-${retryCount}`;
2230
+ const msg = [
2231
+ "[system:onboarding]",
2232
+ "",
2233
+ "User just installed AgentLife and has zero specialist agents. Push the",
2234
+ "onboarding-domain input surface EXACTLY as specified in the [system:onboarding]",
2235
+ "section of your AGENTS.md, then respond `done`. Do NOT ask the user questions",
2236
+ "as chat text — only via the input surface. Do NOT create any specialist agent",
2237
+ "yet; wait for the user's choice on the surface."
2238
+ ].join(`
2239
+ `);
2240
+ actionSessionKey = await sendSystemMessage(state, "agentlife-builder", msg, idem, log);
2241
+ lastActionAt = Date.now();
2242
+ writeState(state, {
2243
+ phase: "AWAITING_AGENT",
2244
+ enteredAt: now,
2245
+ deadlineAt: now + (DEADLINE_MS.AWAITING_AGENT ?? 0),
2246
+ actionSessionKey,
2247
+ lastActionAt
2248
+ });
2249
+ scheduleDeadline(state, runtime, log, "AWAITING_AGENT", now + (DEADLINE_MS.AWAITING_AGENT ?? 0));
2250
+ return readState(state);
2251
+ }
2252
+ if (next === "AWAITING_BASELINE") {
2253
+ const specialistId = opts.specialistId ?? listSpecialistIds(state, runtime)[0];
2254
+ if (!specialistId) {
2255
+ log("[cold-start] AWAITING_BASELINE has no specialist to warm up — falling back to BLANK");
2256
+ return enterPhase(state, runtime, log, "BLANK");
2257
+ }
2258
+ const idem = `cold-start-start-${specialistId}-${now}-${retryCount}`;
2259
+ const msg = [
2260
+ "[system:start]",
2261
+ "",
2262
+ "You were just created by the builder. You have zero memory about this user.",
2263
+ "Follow the [system:start] section of your AGENTS.md: push a loading widget",
2264
+ "introducing yourself per IDENTITY.md, then push ONE warmup-* input surface",
2265
+ "asking the most important baseline question for your domain, then respond",
2266
+ "`done`. Do NOT ask multiple questions in chat text — only via input surfaces."
2267
+ ].join(`
2268
+ `);
2269
+ actionSessionKey = await sendSystemMessage(state, specialistId, msg, idem, log);
2270
+ lastActionAt = Date.now();
2271
+ }
2272
+ if (next === "SYNTHESIZING") {
2273
+ const msg = await composeBootstrapMessage(state, runtime, opts.userDream);
2274
+ const idem = `cold-start-bootstrap-${now}-${retryCount}`;
2275
+ actionSessionKey = await sendSystemMessage(state, "agentlife-builder", msg, idem, log);
2276
+ lastActionAt = Date.now();
2277
+ }
2278
+ if (next === "AWAITING_DREAM") {
2279
+ const idem = `cold-start-ask-dream-${now}-${retryCount}`;
2280
+ const msg = [
2281
+ "[system:ask-dream]",
2282
+ "",
2283
+ "Bootstrap reported NO_SIGNAL — the user's data has no aspirational content yet.",
2284
+ "Push the awaiting-dream-input input surface EXACTLY as specified in the",
2285
+ "[system:ask-dream] section of your AGENTS.md, then respond `done`. Do NOT push",
2286
+ "any vision posters from this turn — the plugin re-fires [system:dashboard-bootstrap]",
2287
+ "with userDream: injected after the user answers."
2288
+ ].join(`
2289
+ `);
2290
+ actionSessionKey = await sendSystemMessage(state, "agentlife-builder", msg, idem, log);
2291
+ lastActionAt = Date.now();
2292
+ }
2293
+ writeState(state, { actionSessionKey, lastActionAt });
2294
+ if (deadlineAt)
2295
+ scheduleDeadline(state, runtime, log, next, deadlineAt);
2296
+ return readState(state);
2297
+ }
2298
+ function enterFailed(state, log, reason) {
2299
+ log(`[cold-start] FAILED: ${reason}`);
2300
+ return writeState(state, {
2301
+ phase: "FAILED",
2302
+ enteredAt: Date.now(),
2303
+ failureReason: reason,
2304
+ deadlineAt: null,
2305
+ actionSessionKey: null
2306
+ });
2307
+ }
2308
+ var deadlineTimers = new Map;
2309
+ function scheduleDeadline(state, runtime, log, phase, deadlineAt) {
2310
+ const key = `${phase}-${deadlineAt}`;
2311
+ for (const [k, t] of deadlineTimers.entries()) {
2312
+ if (k.startsWith(`${phase}-`)) {
2313
+ clearTimeout(t);
2314
+ deadlineTimers.delete(k);
2315
+ }
2316
+ }
2317
+ const remaining = Math.max(0, deadlineAt - Date.now());
2318
+ const timer = setTimeout(() => {
2319
+ deadlineTimers.delete(key);
2320
+ emitTrigger(state, "deadlineElapsed", { phase });
2321
+ }, remaining);
2322
+ deadlineTimers.set(key, timer);
2323
+ log(`[cold-start] scheduled ${phase} deadline in ${Math.round(remaining / 1000)}s`);
2324
+ }
2325
+ function rearmDeadlines(state, runtime, log) {
2326
+ const cur = ensureInitialRow(state);
2327
+ if (cur.deadlineAt && cur.phase !== "READY" && cur.phase !== "FAILED") {
2328
+ if (cur.deadlineAt <= Date.now()) {
2329
+ emitTrigger(state, "deadlineElapsed", { phase: cur.phase });
2330
+ } else {
2331
+ scheduleDeadline(state, runtime, log, cur.phase, cur.deadlineAt);
2332
+ }
2333
+ }
2334
+ }
2335
+ var pollerTimer = null;
2336
+ var POLLER_INTERVAL_MS = 1000;
2337
+ var POLLER_STUCK_THRESHOLD_MS = 30000;
2338
+ var machineRuntime = null;
2339
+ var machineLog = null;
2340
+ var machineState = null;
2341
+ function startPoller(state, runtime, log) {
2342
+ if (pollerTimer)
2343
+ return;
2344
+ let lastDrainAt = Date.now();
2345
+ let lastWarnedAt = 0;
2346
+ pollerTimer = setInterval(async () => {
2347
+ if (state.disabled)
2348
+ return;
2349
+ try {
2350
+ const triggers = drainTriggers(state, 50);
2351
+ const now = Date.now();
2352
+ writeState(state, { pollerLastTickAt: now });
2353
+ if (triggers.length > 0) {
2354
+ lastDrainAt = now;
2355
+ for (const trigger of triggers) {
2356
+ try {
2357
+ await transition(state, runtime, log, trigger);
2358
+ } catch (e) {
2359
+ log(`[cold-start] transition error for ${trigger.kind}: ${e?.message ?? e}`);
2360
+ }
2361
+ }
2362
+ purgeStaleTriggers(state);
2363
+ } else {
2364
+ const pending = pendingTriggerCount(state);
2365
+ if (pending > 0 && now - lastDrainAt > POLLER_STUCK_THRESHOLD_MS && now - lastWarnedAt > POLLER_STUCK_THRESHOLD_MS) {
2366
+ log(`[cold-start] WARNING: poller has not drained ${pending} pending trigger(s) for ${Math.round((now - lastDrainAt) / 1000)}s`);
2367
+ lastWarnedAt = now;
2368
+ }
2369
+ }
2370
+ } catch (e) {
2371
+ log(`[cold-start] poller tick error: ${e?.message ?? e}`);
2372
+ }
2373
+ }, POLLER_INTERVAL_MS);
2374
+ }
2375
+ function stopPoller() {
2376
+ if (pollerTimer) {
2377
+ clearInterval(pollerTimer);
2378
+ pollerTimer = null;
2379
+ }
2380
+ }
2381
+ function runRuntimeContractProbe(state, runtime, log) {
2382
+ const PLUGIN_REGISTERED_TOOLS = ["agentlife_push"];
2383
+ let list = [];
2384
+ try {
2385
+ const raw = nodeFs.readFileSync(path4.join(os2.homedir(), ".openclaw", "openclaw.json"), "utf-8");
2386
+ const parsed = JSON.parse(raw);
2387
+ list = parsed?.agents?.list ?? [];
2388
+ } catch {
2389
+ list = runtime.config.loadConfig()?.agents?.list ?? [];
2390
+ }
2391
+ const warnings = [];
2392
+ for (const provisioned of PROVISIONED_AGENTS) {
2393
+ if (provisioned.existingAgent)
2394
+ continue;
2395
+ const live = list.find((a) => a?.id === provisioned.id);
2396
+ const liveTools = live?.tools ?? {};
2397
+ const allow = Array.isArray(liveTools.allow) ? liveTools.allow : [];
2398
+ const alsoAllow = Array.isArray(liveTools.alsoAllow) ? liveTools.alsoAllow : [];
2399
+ const deny = Array.isArray(liveTools.deny) ? liveTools.deny : [];
2400
+ for (const tool of PLUGIN_REGISTERED_TOOLS) {
2401
+ const escaped = tool.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
2402
+ const re = new RegExp("`[^`]*\\b" + escaped + "\\b[^`]*`");
2403
+ if (!re.test(provisioned.agentsMd))
2404
+ continue;
2405
+ const granted = (allow.includes(tool) || alsoAllow.includes(tool)) && !deny.includes(tool);
2406
+ if (!granted) {
2407
+ const w = `precondition_tool_missing:${provisioned.id}:${tool}`;
2408
+ warnings.push(w);
2409
+ log(`[cold-start] contract warning: ${w}`);
2410
+ }
2411
+ }
2412
+ }
2413
+ return warnings;
2414
+ }
2415
+ async function initColdStartMachine(state, runtime, log) {
2416
+ machineState = state;
2417
+ machineRuntime = runtime;
2418
+ machineLog = log;
2419
+ ensureInitialRow(state);
2420
+ const warnings = runRuntimeContractProbe(state, runtime, log);
2421
+ writeState(state, { contractWarnings: warnings });
2422
+ rearmDeadlines(state, runtime, log);
2423
+ startPoller(state, runtime, log);
2424
+ const cur = readState(state);
2425
+ if (cur.phase === "READY" && visionSurfaceCount(state) === 0) {
2426
+ log("[cold-start] READY with 0 vision surfaces — demoting to FAILED(ready_no_surfaces) on init");
2427
+ enterFailed(state, log, "ready_no_surfaces");
2428
+ log("[cold-start] machine initialized");
2429
+ return;
2430
+ }
2431
+ if (cur.phase !== "BLANK") {
2432
+ log(`[cold-start] restart recovery — preserving persisted phase=${cur.phase} (no provisioned re-fire)`);
2433
+ log("[cold-start] machine initialized");
2434
+ return;
2435
+ }
2436
+ emitTrigger(state, "provisioned", { source: "init" });
2437
+ log("[cold-start] machine initialized");
2438
+ }
2439
+ function shutdownColdStartMachine() {
2440
+ stopPoller();
2441
+ for (const t of deadlineTimers.values())
2442
+ clearTimeout(t);
2443
+ deadlineTimers.clear();
2444
+ }
2445
+ function observeColdStart(state) {
2446
+ const s = ensureInitialRow(state);
2447
+ return {
2448
+ phase: s.phase,
2449
+ failureReason: s.failureReason,
2450
+ retryCount: s.retryCount,
2451
+ contractWarnings: s.contractWarnings,
2452
+ enteredAt: s.enteredAt,
2453
+ deadlineAt: s.deadlineAt
2454
+ };
2455
+ }
2456
+ async function retryColdStart(state) {
2457
+ emitTrigger(state, "userRetried", { ts: Date.now() });
2458
+ if (machineRuntime && machineLog) {
2459
+ const triggers = drainTriggers(state, 50);
2460
+ for (const trigger of triggers) {
2461
+ try {
2462
+ await transition(state, machineRuntime, machineLog, trigger);
2463
+ } catch (e) {
2464
+ machineLog(`[cold-start] retry-drain transition error for ${trigger.kind}: ${e?.message ?? e}`);
2465
+ }
2466
+ }
2467
+ }
2468
+ return observeColdStart(state);
2469
+ }
2470
+
2471
+ // services/surfaces-init.ts
2472
+ import * as path5 from "node:path";
2000
2473
 
2001
2474
  // activity.ts
2002
2475
  function recordSurfaceEvent(state, surfaceId, event, dsl, agentId, metadata) {
@@ -2094,8 +2567,8 @@ function broadcastInput(message, sessionKey) {
2094
2567
  }
2095
2568
 
2096
2569
  // dashboard-state.ts
2097
- import { readFileSync as readFileSync2 } from "node:fs";
2098
- import { homedir as homedir2 } from "node:os";
2570
+ import { readFileSync as readFileSync3 } from "node:fs";
2571
+ import { homedir as homedir3 } from "node:os";
2099
2572
  function extractTitleAndDetail(meta) {
2100
2573
  let title = null;
2101
2574
  let detail = null;
@@ -2308,9 +2781,9 @@ ${enhanced.formatted}`;
2308
2781
  const agentFiles = [];
2309
2782
  for (const w of warnings) {
2310
2783
  const aid = w.agentId;
2311
- const filePath = `${homedir2()}/.openclaw/workspace-${aid}/AGENTS.md`;
2784
+ const filePath = `${homedir3()}/.openclaw/workspace-${aid}/AGENTS.md`;
2312
2785
  try {
2313
- const content = readFileSync2(filePath, "utf-8").slice(0, 3000);
2786
+ const content = readFileSync3(filePath, "utf-8").slice(0, 3000);
2314
2787
  agentFiles.push(`### ${aid} AGENTS.md (${w.cnt} warnings)
2315
2788
  ${content}`);
2316
2789
  } catch {}
@@ -2843,6 +3316,22 @@ function processDslBlock(state, dsl) {
2843
3316
  missingCardStructure: validation.missingCardStructure ? true : undefined
2844
3317
  });
2845
3318
  recordSurfaceEvent(state, sid, existing ? "updated" : "created", block, state.surfaceDb.getAgentId(sid));
3319
+ if (!existing) {
3320
+ let kind = null;
3321
+ if (sid.startsWith("vision-"))
3322
+ kind = "vision";
3323
+ else if (sid === "awaiting-dream-input")
3324
+ kind = "dream-input";
3325
+ else if (sid === "onboarding-domain")
3326
+ kind = "onboarding";
3327
+ if (kind) {
3328
+ try {
3329
+ emitTrigger(state, "surfacePushed", { surfaceId: sid, kind, agentId: state.surfaceDb.getAgentId(sid) ?? null });
3330
+ } catch (e) {
3331
+ console.warn("[agentlife] cold-start emit surfacePushed failed: %s", e?.message);
3332
+ }
3333
+ }
3334
+ }
2846
3335
  }
2847
3336
  return results;
2848
3337
  }
@@ -2986,23 +3475,27 @@ function registerSurfacesService(api, state) {
2986
3475
  api.registerService({
2987
3476
  id: "agentlife-surfaces",
2988
3477
  start: async (ctx) => {
2989
- const agentlifeDir = path4.join(ctx.stateDir, "agentlife");
3478
+ const agentlifeDir = path5.join(ctx.stateDir, "agentlife");
2990
3479
  state.agentlifeStateDir = agentlifeDir;
2991
- state.registryFilePath = path4.join(agentlifeDir, "agent-registry.json");
2992
- state.dbBaseDir = path4.join(agentlifeDir, "db");
2993
- state.historyDbPath = path4.join(agentlifeDir, "agentlife.db");
3480
+ state.registryFilePath = path5.join(agentlifeDir, "agent-registry.json");
3481
+ state.dbBaseDir = path5.join(agentlifeDir, "db");
3482
+ state.historyDbPath = path5.join(agentlifeDir, "agentlife.db");
2994
3483
  if (!state.surfaceDb) {
2995
3484
  const db = getOrCreateHistoryDb(state);
2996
3485
  state.surfaceDb = new SurfaceDb(db);
2997
3486
  }
2998
3487
  runStartupPurge(state);
3488
+ const FLOW_INPUT_IDS = new Set(["onboarding-domain", "awaiting-dream-input"]);
3489
+ const isFlowInput = (id) => FLOW_INPUT_IDS.has(id) || id.startsWith("warmup-") || id.startsWith("dismiss-alt-");
2999
3490
  let inputPurged = 0;
3000
3491
  for (const [surfaceId, meta] of state.surfaceDb.entries()) {
3001
3492
  const headerLine = meta.lines[0] ?? "";
3002
- if (/\binput\b/.test(headerLine)) {
3003
- state.surfaceDb.delete(surfaceId);
3004
- inputPurged++;
3005
- }
3493
+ if (!/\binput\b/.test(headerLine))
3494
+ continue;
3495
+ if (isFlowInput(surfaceId))
3496
+ continue;
3497
+ state.surfaceDb.delete(surfaceId);
3498
+ inputPurged++;
3006
3499
  }
3007
3500
  if (inputPurged > 0) {
3008
3501
  console.log("[agentlife] purged %d stale input surfaces on startup", inputPurged);
@@ -3055,22 +3548,22 @@ function registerSurfacesService(api, state) {
3055
3548
  }
3056
3549
 
3057
3550
  // services/config-optimizer.ts
3058
- import * as fs3 from "node:fs/promises";
3059
- import * as path5 from "node:path";
3060
- import * as os2 from "node:os";
3551
+ import * as fs4 from "node:fs/promises";
3552
+ import * as path6 from "node:path";
3553
+ import * as os3 from "node:os";
3061
3554
  var TARGET_MAX_PING_PONG = 1;
3062
3555
  var TARGET_MAX_CONCURRENT = 10;
3063
- var CONFIG_PATH = path5.join(os2.homedir(), ".openclaw", "openclaw.json");
3556
+ var CONFIG_PATH = path6.join(os3.homedir(), ".openclaw", "openclaw.json");
3064
3557
  async function readRawConfigFromDisk() {
3065
- const raw = await fs3.readFile(CONFIG_PATH, "utf-8");
3558
+ const raw = await fs4.readFile(CONFIG_PATH, "utf-8");
3066
3559
  return JSON.parse(raw);
3067
3560
  }
3068
3561
  async function writeRawConfigToDisk(cfg) {
3069
- await fs3.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2) + `
3562
+ await fs4.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2) + `
3070
3563
  `, "utf-8");
3071
3564
  }
3072
3565
  async function restoreConfigFromBackup(api, backupPath) {
3073
- const raw = await fs3.readFile(backupPath, "utf-8");
3566
+ const raw = await fs4.readFile(backupPath, "utf-8");
3074
3567
  const backup = JSON.parse(raw);
3075
3568
  const fileCfg = await readRawConfigFromDisk();
3076
3569
  if (backup.maxPingPongTurns != null) {
@@ -3084,7 +3577,7 @@ async function restoreConfigFromBackup(api, backupPath) {
3084
3577
  fileCfg.agents.defaults.maxConcurrent = backup.maxConcurrent;
3085
3578
  }
3086
3579
  await writeRawConfigToDisk(fileCfg);
3087
- await fs3.unlink(backupPath);
3580
+ await fs4.unlink(backupPath);
3088
3581
  console.log("[agentlife] restored config: maxPingPongTurns=%s, maxConcurrent=%s", backup.maxPingPongTurns ?? "default", backup.maxConcurrent ?? "default");
3089
3582
  return `config restored`;
3090
3583
  }
@@ -3092,8 +3585,8 @@ function registerConfigOptimizer(api, _state) {
3092
3585
  api.registerService({
3093
3586
  id: "agentlife-config-optimizer",
3094
3587
  start: async (ctx) => {
3095
- const configBackupDir = path5.join(ctx.stateDir, "agentlife");
3096
- const configBackupPath = path5.join(configBackupDir, "config-backup.json");
3588
+ const configBackupDir = path6.join(ctx.stateDir, "agentlife");
3589
+ const configBackupPath = path6.join(configBackupDir, "config-backup.json");
3097
3590
  const cfg = api.runtime.config.loadConfig();
3098
3591
  const currentPPT = cfg.session?.agentToAgent?.maxPingPongTurns;
3099
3592
  const currentMC = cfg.agents?.defaults?.maxConcurrent;
@@ -3103,8 +3596,8 @@ function registerConfigOptimizer(api, _state) {
3103
3596
  console.log("[agentlife] config already optimized, skipping");
3104
3597
  return;
3105
3598
  }
3106
- await fs3.mkdir(configBackupDir, { recursive: true });
3107
- await fs3.writeFile(configBackupPath, JSON.stringify({
3599
+ await fs4.mkdir(configBackupDir, { recursive: true });
3600
+ await fs4.writeFile(configBackupPath, JSON.stringify({
3108
3601
  maxPingPongTurns: currentPPT ?? null,
3109
3602
  maxConcurrent: currentMC ?? null
3110
3603
  }));
@@ -3129,7 +3622,7 @@ function registerConfigOptimizer(api, _state) {
3129
3622
  },
3130
3623
  stop: async (ctx) => {
3131
3624
  try {
3132
- const backupPath = path5.join(ctx.stateDir, "agentlife", "config-backup.json");
3625
+ const backupPath = path6.join(ctx.stateDir, "agentlife", "config-backup.json");
3133
3626
  await restoreConfigFromBackup(api, backupPath);
3134
3627
  } catch {}
3135
3628
  }
@@ -3580,8 +4073,8 @@ function drainAccumulatorToSurfaces(state, sessionKey, surfaceIds) {
3580
4073
  }
3581
4074
 
3582
4075
  // notifications.ts
3583
- import * as fs4 from "node:fs";
3584
- import * as path6 from "node:path";
4076
+ import * as fs5 from "node:fs";
4077
+ import * as path7 from "node:path";
3585
4078
  var config;
3586
4079
  var recentNotifications = new Map;
3587
4080
  var DEBOUNCE_MS = 60000;
@@ -3589,8 +4082,8 @@ function loadConfig() {
3589
4082
  if (config !== undefined)
3590
4083
  return config;
3591
4084
  try {
3592
- const configPath = path6.join(process.env.HOME ?? "~", ".openclaw", "agentlife", "notification-config.json");
3593
- const raw = fs4.readFileSync(configPath, "utf-8");
4085
+ const configPath = path7.join(process.env.HOME ?? "~", ".openclaw", "agentlife", "notification-config.json");
4086
+ const raw = fs5.readFileSync(configPath, "utf-8");
3594
4087
  const parsed = JSON.parse(raw);
3595
4088
  if (parsed.serverUrl && parsed.apiKey) {
3596
4089
  config = { serverUrl: parsed.serverUrl, apiKey: parsed.apiKey };
@@ -3821,6 +4314,17 @@ function registerActivityHooks(api, state) {
3821
4314
  console.log("[agentlife] action redirected to %s for %s (origin=%s)", actionSessionKey, actionSurfaceId, origin ? "set" : "legacy");
3822
4315
  }
3823
4316
  }
4317
+ const dreamMatch = message.match(/\[action:\S+\]\s*surfaceId=awaiting-dream-input\b/);
4318
+ if (dreamMatch) {
4319
+ const labelMatch = message.match(/\blabel=([^\n]+)/);
4320
+ const valueMatch = message.match(/\bvalue=([^\n]+)/);
4321
+ const answer = (labelMatch?.[1] ?? valueMatch?.[1] ?? "").trim();
4322
+ try {
4323
+ emitTrigger(state, "userAnswered", { surfaceId: "awaiting-dream-input", answer });
4324
+ } catch (e) {
4325
+ console.warn("[agentlife] cold-start emit userAnswered failed: %s", e?.message);
4326
+ }
4327
+ }
3824
4328
  });
3825
4329
  api.on("llm_output", (event, ctx) => {
3826
4330
  if (state.disabled)
@@ -3853,6 +4357,39 @@ function registerActivityHooks(api, state) {
3853
4357
  })
3854
4358
  });
3855
4359
  }
4360
+ let isCurrentColdStartSession = false;
4361
+ if (agentId) {
4362
+ try {
4363
+ const cs = readState(state);
4364
+ if (cs && cs.actionSessionKey === sessionKey && cs.phase !== "READY" && cs.phase !== "FAILED") {
4365
+ isCurrentColdStartSession = true;
4366
+ }
4367
+ } catch {}
4368
+ }
4369
+ if (isCurrentColdStartSession && agentId) {
4370
+ const lastText = texts.length > 0 ? texts[texts.length - 1].trim() : "";
4371
+ const lastLine = lastText.split(`
4372
+ `).pop()?.trim() ?? "";
4373
+ let marker = null;
4374
+ if (/\bNO_SIGNAL\b/.test(lastText)) {
4375
+ marker = "NO_SIGNAL";
4376
+ } else if (TERMINAL_MARKERS.has(lastLine)) {
4377
+ marker = lastLine;
4378
+ } else if (TERMINAL_MARKERS.has(lastText)) {
4379
+ marker = lastText;
4380
+ }
4381
+ try {
4382
+ emitTrigger(state, "agentReplied", {
4383
+ agentId,
4384
+ sessionKey,
4385
+ marker,
4386
+ lastText: lastText.slice(0, 4000),
4387
+ terminal: marker !== null
4388
+ });
4389
+ } catch (e) {
4390
+ console.warn("[agentlife] cold-start emit agentReplied failed: %s", e?.message);
4391
+ }
4392
+ }
3856
4393
  }, { priority: 100 });
3857
4394
  api.on("before_tool_call", (event, ctx) => {
3858
4395
  if (state.disabled)
@@ -3897,6 +4434,26 @@ function registerActivityHooks(api, state) {
3897
4434
  if (event.toolName === "agentlife_push" && typeof event.params?.dsl === "string") {
3898
4435
  canvasDsl = event.params.dsl;
3899
4436
  }
4437
+ if (!isError && agentId) {
4438
+ let touchedMemory = false;
4439
+ if (toolName === "exec") {
4440
+ const command = event.params?.command ?? event.params?.script ?? "";
4441
+ if (typeof command === "string" && /memory\/[\w.\-]+\.md/.test(command) && /(>>?|tee\b|cp\b|mv\b)/.test(command)) {
4442
+ touchedMemory = true;
4443
+ }
4444
+ } else if (toolName === "write" || toolName === "edit" || toolName === "str_replace" || toolName === "apply_patch") {
4445
+ const filePath = event.params?.path ?? event.params?.file_path ?? "";
4446
+ if (/memory\/[\w.\-]+\.md$/.test(filePath))
4447
+ touchedMemory = true;
4448
+ }
4449
+ if (touchedMemory) {
4450
+ try {
4451
+ emitTrigger(state, "memoryWritten", { agentId, sessionKey });
4452
+ } catch (e) {
4453
+ console.warn("[agentlife] cold-start emit memoryWritten failed: %s", e?.message);
4454
+ }
4455
+ }
4456
+ }
3900
4457
  const resultStr = typeof event.result === "string" ? event.result : event.result != null ? JSON.stringify(event.result) : null;
3901
4458
  recordActivity(state, "tool_end", sessionKey, agentId, {
3902
4459
  toolName,
@@ -4682,31 +5239,31 @@ function startDailySweepService(state) {
4682
5239
  import * as crypto4 from "node:crypto";
4683
5240
  import * as fsSync3 from "node:fs";
4684
5241
  import { createRequire as createRequire3 } from "node:module";
4685
- import * as os6 from "node:os";
4686
- import * as path10 from "node:path";
5242
+ import * as os7 from "node:os";
5243
+ import * as path11 from "node:path";
4687
5244
 
4688
5245
  // gateway/web-app.ts
4689
5246
  import * as crypto3 from "node:crypto";
4690
- import * as os5 from "node:os";
4691
- import * as path9 from "node:path";
4692
- import * as fs7 from "node:fs";
5247
+ import * as os6 from "node:os";
5248
+ import * as path10 from "node:path";
5249
+ import * as fs8 from "node:fs";
4693
5250
 
4694
5251
  // services/pairing-access-token.ts
4695
5252
  import * as crypto from "node:crypto";
4696
- import * as fs5 from "node:fs";
4697
- import * as os3 from "node:os";
4698
- import * as path7 from "node:path";
5253
+ import * as fs6 from "node:fs";
5254
+ import * as os4 from "node:os";
5255
+ import * as path8 from "node:path";
4699
5256
  var cachedToken = null;
4700
5257
  function pairingAccessPath() {
4701
- return path7.join(os3.homedir(), ".openclaw", "agentlife", "pairing-access.json");
5258
+ return path8.join(os4.homedir(), ".openclaw", "agentlife", "pairing-access.json");
4702
5259
  }
4703
5260
  function loadOrCreatePairingAccessToken() {
4704
5261
  if (cachedToken)
4705
5262
  return cachedToken;
4706
5263
  const filePath = pairingAccessPath();
4707
5264
  try {
4708
- if (fs5.existsSync(filePath)) {
4709
- const raw = fs5.readFileSync(filePath, "utf-8");
5265
+ if (fs6.existsSync(filePath)) {
5266
+ const raw = fs6.readFileSync(filePath, "utf-8");
4710
5267
  const obj = JSON.parse(raw);
4711
5268
  const token2 = typeof obj?.token === "string" ? obj.token : null;
4712
5269
  if (token2 && token2.length >= 32) {
@@ -4715,12 +5272,12 @@ function loadOrCreatePairingAccessToken() {
4715
5272
  }
4716
5273
  }
4717
5274
  const token = crypto.randomBytes(32).toString("base64url");
4718
- const dir = path7.dirname(filePath);
4719
- if (!fs5.existsSync(dir))
4720
- fs5.mkdirSync(dir, { recursive: true, mode: 448 });
4721
- fs5.writeFileSync(filePath, JSON.stringify({ token, createdAtMs: Date.now() }, null, 2), { mode: 384 });
5275
+ const dir = path8.dirname(filePath);
5276
+ if (!fs6.existsSync(dir))
5277
+ fs6.mkdirSync(dir, { recursive: true, mode: 448 });
5278
+ fs6.writeFileSync(filePath, JSON.stringify({ token, createdAtMs: Date.now() }, null, 2), { mode: 384 });
4722
5279
  try {
4723
- fs5.chmodSync(filePath, 384);
5280
+ fs6.chmodSync(filePath, 384);
4724
5281
  } catch {}
4725
5282
  cachedToken = token;
4726
5283
  return token;
@@ -4733,15 +5290,15 @@ function loadOrCreatePairingAccessToken() {
4733
5290
  // services/cloudflared-supervisor.ts
4734
5291
  import { spawn, spawnSync } from "node:child_process";
4735
5292
  import * as crypto2 from "node:crypto";
4736
- import * as fs6 from "node:fs";
4737
- import * as os4 from "node:os";
4738
- import * as path8 from "node:path";
4739
- var AGENTLIFE_DIR = path8.join(os4.homedir(), ".openclaw", "agentlife");
4740
- var DEVICE_FILE = path8.join(AGENTLIFE_DIR, "device.json");
4741
- var TUNNEL_FILE = path8.join(AGENTLIFE_DIR, "tunnel.json");
4742
- var BIN_DIR = path8.join(AGENTLIFE_DIR, "bin");
4743
- var CLOUDFLARED_BIN = os4.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
4744
- var CLOUDFLARED_PATH = path8.join(BIN_DIR, CLOUDFLARED_BIN);
5293
+ import * as fs7 from "node:fs";
5294
+ import * as os5 from "node:os";
5295
+ import * as path9 from "node:path";
5296
+ var AGENTLIFE_DIR = path9.join(os5.homedir(), ".openclaw", "agentlife");
5297
+ var DEVICE_FILE = path9.join(AGENTLIFE_DIR, "device.json");
5298
+ var TUNNEL_FILE = path9.join(AGENTLIFE_DIR, "tunnel.json");
5299
+ var BIN_DIR = path9.join(AGENTLIFE_DIR, "bin");
5300
+ var CLOUDFLARED_BIN = os5.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
5301
+ var CLOUDFLARED_PATH = path9.join(BIN_DIR, CLOUDFLARED_BIN);
4745
5302
  var API_BASE = process.env.AGENTLIFE_API_BASE || "https://api.agentlife.app";
4746
5303
  var RESTART_DELAY_MS = 5000;
4747
5304
  var STABLE_RUNTIME_MS = 60000;
@@ -4818,16 +5375,16 @@ async function doBootstrap() {
4818
5375
  return tunnelInfo;
4819
5376
  }
4820
5377
  function ensureDirs() {
4821
- if (!fs6.existsSync(AGENTLIFE_DIR))
4822
- fs6.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
4823
- if (!fs6.existsSync(BIN_DIR))
4824
- fs6.mkdirSync(BIN_DIR, { recursive: true, mode: 448 });
5378
+ if (!fs7.existsSync(AGENTLIFE_DIR))
5379
+ fs7.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
5380
+ if (!fs7.existsSync(BIN_DIR))
5381
+ fs7.mkdirSync(BIN_DIR, { recursive: true, mode: 448 });
4825
5382
  }
4826
5383
  function loadDeviceIdentity() {
4827
- if (!fs6.existsSync(DEVICE_FILE))
5384
+ if (!fs7.existsSync(DEVICE_FILE))
4828
5385
  return null;
4829
5386
  try {
4830
- const raw = fs6.readFileSync(DEVICE_FILE, "utf-8");
5387
+ const raw = fs7.readFileSync(DEVICE_FILE, "utf-8");
4831
5388
  const parsed = JSON.parse(raw);
4832
5389
  if (typeof parsed.deviceId === "string" && typeof parsed.deviceSecret === "string") {
4833
5390
  return { deviceId: parsed.deviceId, deviceSecret: parsed.deviceSecret };
@@ -4845,19 +5402,19 @@ function loadOrCreateDeviceIdentity() {
4845
5402
  deviceId: crypto2.randomUUID(),
4846
5403
  deviceSecret: crypto2.randomBytes(32).toString("base64url")
4847
5404
  };
4848
- fs6.writeFileSync(DEVICE_FILE, JSON.stringify(identity, null, 2), { mode: 384 });
5405
+ fs7.writeFileSync(DEVICE_FILE, JSON.stringify(identity, null, 2), { mode: 384 });
4849
5406
  try {
4850
- fs6.chmodSync(DEVICE_FILE, 384);
5407
+ fs7.chmodSync(DEVICE_FILE, 384);
4851
5408
  } catch {}
4852
5409
  console.log(`[cloudflared-supervisor] generated new device identity (deviceId=${identity.deviceId})`);
4853
5410
  return identity;
4854
5411
  }
4855
5412
  var AGENTLIFE_API_BASE = API_BASE;
4856
5413
  function loadCachedTunnel() {
4857
- if (!fs6.existsSync(TUNNEL_FILE))
5414
+ if (!fs7.existsSync(TUNNEL_FILE))
4858
5415
  return null;
4859
5416
  try {
4860
- const raw = fs6.readFileSync(TUNNEL_FILE, "utf-8");
5417
+ const raw = fs7.readFileSync(TUNNEL_FILE, "utf-8");
4861
5418
  const parsed = JSON.parse(raw);
4862
5419
  if (typeof parsed.subdomain === "string" && typeof parsed.hostname === "string" && typeof parsed.tunnelUrl === "string" && typeof parsed.tunnelToken === "string" && typeof parsed.provisionedAt === "number") {
4863
5420
  return parsed;
@@ -4866,9 +5423,9 @@ function loadCachedTunnel() {
4866
5423
  return null;
4867
5424
  }
4868
5425
  function persistTunnel(info) {
4869
- fs6.writeFileSync(TUNNEL_FILE, JSON.stringify(info, null, 2), { mode: 384 });
5426
+ fs7.writeFileSync(TUNNEL_FILE, JSON.stringify(info, null, 2), { mode: 384 });
4870
5427
  try {
4871
- fs6.chmodSync(TUNNEL_FILE, 384);
5428
+ fs7.chmodSync(TUNNEL_FILE, 384);
4872
5429
  } catch {}
4873
5430
  }
4874
5431
  async function provisionTunnel(identity) {
@@ -4905,14 +5462,14 @@ async function provisionTunnel(identity) {
4905
5462
  }
4906
5463
  }
4907
5464
  function ensureCloudflaredBinary() {
4908
- if (fs6.existsSync(CLOUDFLARED_PATH)) {
5465
+ if (fs7.existsSync(CLOUDFLARED_PATH)) {
4909
5466
  try {
4910
- fs6.accessSync(CLOUDFLARED_PATH, fs6.constants.X_OK);
5467
+ fs7.accessSync(CLOUDFLARED_PATH, fs7.constants.X_OK);
4911
5468
  return CLOUDFLARED_PATH;
4912
5469
  } catch {}
4913
5470
  }
4914
- const platform2 = os4.platform();
4915
- const arch2 = os4.arch();
5471
+ const platform2 = os5.platform();
5472
+ const arch2 = os5.arch();
4916
5473
  const release = detectCloudflaredRelease(platform2, arch2);
4917
5474
  if (!release) {
4918
5475
  console.warn(`[cloudflared-supervisor] unsupported platform: ${platform2}/${arch2}`);
@@ -4925,28 +5482,28 @@ function ensureCloudflaredBinary() {
4925
5482
  if (release.kind === "tgz") {
4926
5483
  const extracted = extractTgzCloudflared(release.tempPath, BIN_DIR);
4927
5484
  try {
4928
- fs6.unlinkSync(release.tempPath);
5485
+ fs7.unlinkSync(release.tempPath);
4929
5486
  } catch {}
4930
5487
  if (!extracted)
4931
5488
  return null;
4932
5489
  } else {
4933
5490
  try {
4934
- fs6.renameSync(release.tempPath, CLOUDFLARED_PATH);
5491
+ fs7.renameSync(release.tempPath, CLOUDFLARED_PATH);
4935
5492
  } catch (err) {
4936
5493
  console.warn(`[cloudflared-supervisor] rename failed: ${err?.message}`);
4937
5494
  return null;
4938
5495
  }
4939
5496
  }
4940
5497
  try {
4941
- fs6.chmodSync(CLOUDFLARED_PATH, 493);
5498
+ fs7.chmodSync(CLOUDFLARED_PATH, 493);
4942
5499
  } catch {}
4943
- return fs6.existsSync(CLOUDFLARED_PATH) ? CLOUDFLARED_PATH : null;
5500
+ return fs7.existsSync(CLOUDFLARED_PATH) ? CLOUDFLARED_PATH : null;
4944
5501
  }
4945
5502
  function detectCloudflaredRelease(platform2, arch2) {
4946
5503
  const base = "https://github.com/cloudflare/cloudflared/releases/latest/download";
4947
5504
  if (platform2 === "darwin") {
4948
5505
  const asset = arch2 === "arm64" ? "cloudflared-darwin-arm64.tgz" : "cloudflared-darwin-amd64.tgz";
4949
- return { url: `${base}/${asset}`, kind: "tgz", tempPath: path8.join(BIN_DIR, asset) };
5506
+ return { url: `${base}/${asset}`, kind: "tgz", tempPath: path9.join(BIN_DIR, asset) };
4950
5507
  }
4951
5508
  if (platform2 === "linux") {
4952
5509
  const asset = arch2 === "arm64" ? "cloudflared-linux-arm64" : arch2 === "arm" ? "cloudflared-linux-arm" : arch2 === "x64" ? "cloudflared-linux-amd64" : null;
@@ -4970,11 +5527,11 @@ function downloadWithCurl(url, dest) {
4970
5527
  if (result.status !== 0) {
4971
5528
  console.warn(`[cloudflared-supervisor] curl failed (exit ${result.status}): ${result.stderr?.toString().slice(0, 200)}`);
4972
5529
  try {
4973
- fs6.unlinkSync(dest);
5530
+ fs7.unlinkSync(dest);
4974
5531
  } catch {}
4975
5532
  return false;
4976
5533
  }
4977
- return fs6.existsSync(dest) && fs6.statSync(dest).size > 0;
5534
+ return fs7.existsSync(dest) && fs7.statSync(dest).size > 0;
4978
5535
  }
4979
5536
  function extractTgzCloudflared(tgzPath, destDir) {
4980
5537
  const result = spawnSync("tar", ["-xzf", tgzPath, "-C", destDir], { stdio: "pipe" });
@@ -4982,7 +5539,7 @@ function extractTgzCloudflared(tgzPath, destDir) {
4982
5539
  console.warn(`[cloudflared-supervisor] tar extract failed (exit ${result.status}): ${result.stderr?.toString().slice(0, 200)}`);
4983
5540
  return false;
4984
5541
  }
4985
- return fs6.existsSync(CLOUDFLARED_PATH);
5542
+ return fs7.existsSync(CLOUDFLARED_PATH);
4986
5543
  }
4987
5544
  function startCloudflaredProcess(binPath, tunnelToken) {
4988
5545
  if (state.stopped)
@@ -5039,11 +5596,11 @@ var MIME_TYPES = {
5039
5596
  };
5040
5597
  function mintBootstrapToken() {
5041
5598
  const bootstrapToken = crypto3.randomBytes(32).toString("base64url");
5042
- const devicesDir = path9.join(os5.homedir(), ".openclaw", "devices");
5043
- const bootstrapPath = path9.join(devicesDir, "bootstrap.json");
5044
- if (!fs7.existsSync(devicesDir))
5045
- fs7.mkdirSync(devicesDir, { recursive: true });
5046
- const registry = fs7.existsSync(bootstrapPath) ? JSON.parse(fs7.readFileSync(bootstrapPath, "utf-8")) : {};
5599
+ const devicesDir = path10.join(os6.homedir(), ".openclaw", "devices");
5600
+ const bootstrapPath = path10.join(devicesDir, "bootstrap.json");
5601
+ if (!fs8.existsSync(devicesDir))
5602
+ fs8.mkdirSync(devicesDir, { recursive: true });
5603
+ const registry = fs8.existsSync(bootstrapPath) ? JSON.parse(fs8.readFileSync(bootstrapPath, "utf-8")) : {};
5047
5604
  registry[bootstrapToken] = {
5048
5605
  token: bootstrapToken,
5049
5606
  profile: {
@@ -5052,15 +5609,15 @@ function mintBootstrapToken() {
5052
5609
  },
5053
5610
  issuedAtMs: Date.now()
5054
5611
  };
5055
- fs7.writeFileSync(bootstrapPath, JSON.stringify(registry, null, 2));
5612
+ fs8.writeFileSync(bootstrapPath, JSON.stringify(registry, null, 2));
5056
5613
  return bootstrapToken;
5057
5614
  }
5058
5615
  function registerWebApp(api) {
5059
- const pluginRoot = path9.resolve(path9.dirname(api.source), "..");
5060
- const appRoot = path9.join(pluginRoot, "web-build");
5061
- const hasWebBuild = fs7.existsSync(appRoot);
5062
- const indexPath = hasWebBuild ? path9.join(appRoot, "index.html") : null;
5063
- const hasIndex = !!(indexPath && fs7.existsSync(indexPath));
5616
+ const pluginRoot = path10.resolve(path10.dirname(api.source), "..");
5617
+ const appRoot = path10.join(pluginRoot, "web-build");
5618
+ const hasWebBuild = fs8.existsSync(appRoot);
5619
+ const indexPath = hasWebBuild ? path10.join(appRoot, "index.html") : null;
5620
+ const hasIndex = !!(indexPath && fs8.existsSync(indexPath));
5064
5621
  if (!hasWebBuild) {
5065
5622
  api.logger.info("[agentlife] web-build/ not found — /agentlife/pair will still register; static dashboard disabled");
5066
5623
  } else if (!hasIndex) {
@@ -5068,8 +5625,8 @@ function registerWebApp(api) {
5068
5625
  }
5069
5626
  let gatewayToken = "";
5070
5627
  try {
5071
- const configPath = path9.join(__require("node:os").homedir(), ".openclaw", "openclaw.json");
5072
- const raw = JSON.parse(fs7.readFileSync(configPath, "utf-8"));
5628
+ const configPath = path10.join(__require("node:os").homedir(), ".openclaw", "openclaw.json");
5629
+ const raw = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
5073
5630
  gatewayToken = raw?.gateway?.auth?.token || "";
5074
5631
  } catch {}
5075
5632
  loadOrCreatePairingAccessToken();
@@ -5185,16 +5742,16 @@ function registerWebApp(api) {
5185
5742
  return true;
5186
5743
  }
5187
5744
  const relative = urlPath.replace(/^\/agentlife\/?/, "") || "index.html";
5188
- const filePath = path9.resolve(appRoot, relative);
5745
+ const filePath = path10.resolve(appRoot, relative);
5189
5746
  if (!filePath.startsWith(appRoot)) {
5190
5747
  res.writeHead(403);
5191
5748
  res.end();
5192
5749
  return true;
5193
5750
  }
5194
- const target = fs7.existsSync(filePath) && fs7.statSync(filePath).isFile() ? filePath : indexPath;
5195
- const ext = path9.extname(target).toLowerCase();
5751
+ const target = fs8.existsSync(filePath) && fs8.statSync(filePath).isFile() ? filePath : indexPath;
5752
+ const ext = path10.extname(target).toLowerCase();
5196
5753
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
5197
- let content = fs7.readFileSync(target);
5754
+ let content = fs8.readFileSync(target);
5198
5755
  if (target === indexPath) {
5199
5756
  setTimeout(approveLatest, 2000);
5200
5757
  setTimeout(approveLatest, 5000);
@@ -5283,9 +5840,9 @@ Setup code: ${setupCode}
5283
5840
  api.registerService({
5284
5841
  id: "agentlife-auto-pair",
5285
5842
  start: async (ctx) => {
5286
- const devicesDir = path10.join(os6.homedir(), ".openclaw", "devices");
5287
- const pendingPath = path10.join(devicesDir, "pending.json");
5288
- const pairedPath = path10.join(devicesDir, "paired.json");
5843
+ const devicesDir = path11.join(os7.homedir(), ".openclaw", "devices");
5844
+ const pendingPath = path11.join(devicesDir, "pending.json");
5845
+ const pairedPath = path11.join(devicesDir, "paired.json");
5289
5846
  const pollInterval = 3000;
5290
5847
  const withAdmin = (arr) => {
5291
5848
  const base = Array.isArray(arr) ? arr.filter((s) => typeof s === "string") : [];
@@ -5537,11 +6094,11 @@ function registerBootstrapHookImpl(api, state2) {
5537
6094
  }
5538
6095
 
5539
6096
  // tools/widget-push.ts
5540
- var PROVISIONED_IDS = new Set(PROVISIONED_AGENTS.map((a) => a.id));
6097
+ var PROVISIONED_IDS2 = new Set(PROVISIONED_AGENTS.map((a) => a.id));
5541
6098
  function isKnownAgent(state2, agentId, api) {
5542
6099
  if (state2.agentRegistry.size === 0)
5543
6100
  return true;
5544
- if (PROVISIONED_IDS.has(agentId) || state2.agentRegistry.has(agentId))
6101
+ if (PROVISIONED_IDS2.has(agentId) || state2.agentRegistry.has(agentId))
5545
6102
  return true;
5546
6103
  try {
5547
6104
  const cfg = api.runtime.config.loadConfig();
@@ -5824,12 +6381,53 @@ function extractWidgetText(meta) {
5824
6381
  }
5825
6382
 
5826
6383
  // hooks/prompt-state.ts
6384
+ var lastEmittedAnswerByKey = new Map;
6385
+ function lastUserMessage(messages) {
6386
+ for (let i = messages.length - 1;i >= 0; i--) {
6387
+ const m = messages[i];
6388
+ if (m?.role !== "user")
6389
+ continue;
6390
+ if (typeof m.content === "string")
6391
+ return m.content;
6392
+ if (Array.isArray(m.content)) {
6393
+ const parts = [];
6394
+ for (const block of m.content) {
6395
+ if (typeof block === "string")
6396
+ parts.push(block);
6397
+ else if (block?.type === "text" && typeof block.text === "string")
6398
+ parts.push(block.text);
6399
+ }
6400
+ return parts.join(`
6401
+ `);
6402
+ }
6403
+ }
6404
+ return "";
6405
+ }
5827
6406
  function registerPromptStateHook(api, state2) {
5828
- api.on("before_prompt_build", (_event, ctx) => {
6407
+ api.on("before_prompt_build", (event, ctx) => {
5829
6408
  if (state2.disabled)
5830
6409
  return;
5831
6410
  if (!isAgentlifeSession(ctx?.sessionKey))
5832
6411
  return;
6412
+ const sessionKey = ctx?.sessionKey;
6413
+ if (sessionKey) {
6414
+ const messages = event?.messages ?? [];
6415
+ const latest = lastUserMessage(messages);
6416
+ if (latest && lastEmittedAnswerByKey.get(sessionKey) !== latest) {
6417
+ const actionMatch = latest.match(/\[action:\S+\]\s*surfaceId=awaiting-dream-input\b/);
6418
+ if (actionMatch) {
6419
+ const labelMatch = latest.match(/\blabel=([^\n]+)/);
6420
+ const valueMatch = latest.match(/\bvalue=([^\n]+)/);
6421
+ const answer = (labelMatch?.[1] ?? valueMatch?.[1] ?? "").trim();
6422
+ lastEmittedAnswerByKey.set(sessionKey, latest);
6423
+ try {
6424
+ emitTrigger(state2, "userAnswered", { surfaceId: "awaiting-dream-input", answer });
6425
+ } catch (e) {
6426
+ console.warn("[agentlife] cold-start emit userAnswered (prompt-build) failed: %s", e?.message);
6427
+ }
6428
+ }
6429
+ }
6430
+ }
5833
6431
  const agentId = ctx?.agentId;
5834
6432
  const isOrchestrator = agentId === "agentlife";
5835
6433
  if (isOrchestrator)
@@ -5849,8 +6447,8 @@ function registerPromptStateHook(api, state2) {
5849
6447
  }
5850
6448
 
5851
6449
  // gateway/agents.ts
5852
- import * as fs8 from "node:fs";
5853
- import * as path11 from "node:path";
6450
+ import * as fs9 from "node:fs";
6451
+ import * as path12 from "node:path";
5854
6452
  function registerAgentGateway(api, state2) {
5855
6453
  api.registerGatewayMethod("agentlife.createAgent", async ({ params, respond }) => {
5856
6454
  const id = typeof params?.id === "string" ? params.id.trim() : "";
@@ -5938,6 +6536,16 @@ function registerAgentGateway(api, state2) {
5938
6536
  configFields.push("subagents");
5939
6537
  if (identity)
5940
6538
  configFields.push("identity");
6539
+ if (!existing) {
6540
+ const provisionedIds = new Set(PROVISIONED_AGENTS.map((a) => a.id));
6541
+ if (!provisionedIds.has(id)) {
6542
+ try {
6543
+ emitTrigger(state2, "agentRegistered", { agentId: id, name, model });
6544
+ } catch (e) {
6545
+ console.warn("[agentlife] cold-start emit agentRegistered failed: %s", e?.message);
6546
+ }
6547
+ }
6548
+ }
5941
6549
  respond(true, { status, id, name, model, workspace, description, ...configFields.length ? { configFields } : {} });
5942
6550
  });
5943
6551
  api.registerGatewayMethod("agentlife.deleteAgent", async ({ params, respond }) => {
@@ -6000,10 +6608,10 @@ function registerAgentGateway(api, state2) {
6000
6608
  state2.agentDbs.delete(id);
6001
6609
  }
6002
6610
  if (state2.dbBaseDir) {
6003
- const dbPath = path11.join(state2.dbBaseDir, `${id}.db`);
6611
+ const dbPath = path12.join(state2.dbBaseDir, `${id}.db`);
6004
6612
  try {
6005
- if (fs8.existsSync(dbPath)) {
6006
- fs8.unlinkSync(dbPath);
6613
+ if (fs9.existsSync(dbPath)) {
6614
+ fs9.unlinkSync(dbPath);
6007
6615
  cleanup.agentDbDeleted = true;
6008
6616
  }
6009
6617
  } catch (e) {
@@ -6029,6 +6637,17 @@ function registerAgentGateway(api, state2) {
6029
6637
  await saveRegistryToDisk(state2);
6030
6638
  }
6031
6639
  console.log("[agentlife] deleted agent: %s (config=%s, registry=%s, sessions=%d, cron=%d, historyRows=%d, agentDb=%s)", id, removedFromConfig, removedFromRegistry, cleanup.sessions, cleanup.cronJobs, cleanup.historyRows, cleanup.agentDbDeleted);
6640
+ try {
6641
+ const provisionedIds2 = new Set(PROVISIONED_AGENTS.map((a) => a.id));
6642
+ let remaining = 0;
6643
+ for (const aid of state2.agentRegistry.keys()) {
6644
+ if (!provisionedIds2.has(aid))
6645
+ remaining++;
6646
+ }
6647
+ emitTrigger(state2, "agentDeleted", { agentId: id, remaining });
6648
+ } catch (e) {
6649
+ console.warn("[agentlife] cold-start emit agentDeleted failed: %s", e?.message);
6650
+ }
6032
6651
  respond(true, { status: "deleted", id, removedFromConfig, removedFromRegistry, cleanup });
6033
6652
  });
6034
6653
  api.registerGatewayMethod("agentlife.agents", ({ respond }) => {
@@ -6329,7 +6948,7 @@ function registerUsageGateway(api, state2) {
6329
6948
  var guidedDismissSent = new Set;
6330
6949
  var warnedMissingOrigin = new Set;
6331
6950
  function registerSurfacesGateway(api, state2) {
6332
- api.registerGatewayMethod("agentlife.surfaces", ({ respond, context }) => {
6951
+ api.registerGatewayMethod("agentlife.surfaces", ({ params, respond, context }) => {
6333
6952
  captureBridge(context);
6334
6953
  const now = Date.now();
6335
6954
  const activeSurfaceIds = [];
@@ -6338,23 +6957,21 @@ function registerSurfacesGateway(api, state2) {
6338
6957
  respond(true, { surfaces: [] });
6339
6958
  return;
6340
6959
  }
6341
- const LOADING_TTL_MS = 2 * 60 * 1000;
6342
- for (const [surfaceId, meta] of state2.surfaceDb.entries()) {
6343
- const headerLine = meta.lines[0] ?? "";
6344
- if (/\bstate=loading\b/.test(headerLine) && now - meta.updatedAt > LOADING_TTL_MS) {
6345
- const agentId = state2.surfaceDb.getAgentId(surfaceId);
6346
- meta.lines[0] = headerLine.replace(/\bstate=loading\b/, "state=error");
6347
- state2.surfaceDb.set(surfaceId, { ...meta, updatedAt: now });
6348
- recordSurfaceEvent(state2, surfaceId, "loading_resolved_error", undefined, agentId ?? undefined, JSON.stringify({ reason: "loading surface orphaned (>2min)", resolvedBy: "surfaces_read" }));
6349
- console.log("[agentlife] resolved stale loading surface %s → error (stuck >2min)", surfaceId);
6350
- }
6351
- }
6960
+ const visibility = params?.visibility === "paywall" ? "paywall" : "dashboard";
6961
+ const FLOW_INPUT_IDS = new Set(["onboarding-domain", "awaiting-dream-input"]);
6962
+ const isFlowInput = (id) => FLOW_INPUT_IDS.has(id) || id.startsWith("warmup-") || id.startsWith("dismiss-alt-");
6352
6963
  for (const [surfaceId, meta] of state2.surfaceDb.entries()) {
6353
6964
  if (isExpired(meta, now))
6354
6965
  continue;
6355
6966
  const headerLine = meta.lines[0] ?? "";
6356
- if (/\binput\b/.test(headerLine))
6967
+ const isInput = /\binput\b/.test(headerLine);
6968
+ if (isInput && !isFlowInput(surfaceId))
6357
6969
  continue;
6970
+ if (visibility === "dashboard" && surfaceId.startsWith("vision-")) {
6971
+ const visible = state2.surfaceDb.getDashboardVisible(surfaceId);
6972
+ if (!visible)
6973
+ continue;
6974
+ }
6358
6975
  if (meta.lines.length > 0) {
6359
6976
  surfaceEntries.push({ surfaceId, dsl: meta.lines.join(`
6360
6977
  `) });
@@ -6492,7 +7109,12 @@ function registerSurfacesGateway(api, state2) {
6492
7109
  automations = db.prepare("SELECT id, type, name, path FROM automations WHERE surfaceId = ? AND status != 'removed'").all(surfaceId);
6493
7110
  } catch {}
6494
7111
  guidedDismissSent.delete(surfaceId);
6495
- state2.surfaceDb.delete(surfaceId);
7112
+ const isVision = surfaceId.startsWith("vision-");
7113
+ if (isVision) {
7114
+ state2.surfaceDb.setDashboardVisible(surfaceId, false);
7115
+ } else {
7116
+ state2.surfaceDb.delete(surfaceId);
7117
+ }
6496
7118
  broadcastDelete(surfaceId);
6497
7119
  try {
6498
7120
  enqueueCleanupTasks(state2, surfaceId, agentId, cronId, automations);
@@ -6500,7 +7122,7 @@ function registerSurfacesGateway(api, state2) {
6500
7122
  console.error("[agentlife] dismiss: failed to enqueue cleanup tasks for %s: %s", surfaceId, err?.message);
6501
7123
  }
6502
7124
  const taskCount = (cronId ? 1 : 0) + (agentId ? 1 : 0);
6503
- console.log("[agentlife] dismiss: %s deleted, %d cleanup tasks enqueued (reason=%s)", surfaceId, taskCount, reason ?? "none");
7125
+ console.log("[agentlife] dismiss: %s %s, %d cleanup tasks enqueued (reason=%s)", surfaceId, isVision ? "hidden from dashboard (kept for paywall)" : "deleted", taskCount, reason ?? "none");
6504
7126
  respond(true, { surfaceId, dismissed: true });
6505
7127
  processCleanupTasks(state2, surfaceId).catch((e) => console.warn("[agentlife] dismiss cleanup processor error:", e?.message));
6506
7128
  }, { scope: "operator.write" });
@@ -6652,12 +7274,12 @@ function registerAutomationsGateway(api, state2) {
6652
7274
  }
6653
7275
 
6654
7276
  // gateway/admin.ts
6655
- import * as fs9 from "node:fs/promises";
7277
+ import * as fs10 from "node:fs/promises";
6656
7278
  import * as fsSync4 from "node:fs";
6657
- import * as os7 from "node:os";
6658
- import * as path12 from "node:path";
7279
+ import * as os8 from "node:os";
7280
+ import * as path13 from "node:path";
6659
7281
  function pluginConfigPath() {
6660
- return path12.join(os7.homedir(), ".openclaw", "agentlife", "plugin-config.json");
7282
+ return path13.join(os8.homedir(), ".openclaw", "agentlife", "plugin-config.json");
6661
7283
  }
6662
7284
  function readPluginConfig() {
6663
7285
  try {
@@ -6667,7 +7289,7 @@ function readPluginConfig() {
6667
7289
  }
6668
7290
  }
6669
7291
  function writePluginConfig(config2) {
6670
- const dir = path12.dirname(pluginConfigPath());
7292
+ const dir = path13.dirname(pluginConfigPath());
6671
7293
  fsSync4.mkdirSync(dir, { recursive: true });
6672
7294
  fsSync4.writeFileSync(pluginConfigPath(), JSON.stringify(config2, null, 2));
6673
7295
  }
@@ -6766,7 +7388,7 @@ function registerAdminGateway(api, state2) {
6766
7388
  api.registerGatewayMethod("agentlife.uninstall", async ({ respond }) => {
6767
7389
  try {
6768
7390
  const cleaned = [];
6769
- const baseDir = state2.agentlifeStateDir ?? path12.join(os7.homedir(), ".openclaw", "agentlife");
7391
+ const baseDir = state2.agentlifeStateDir ?? path13.join(os8.homedir(), ".openclaw", "agentlife");
6770
7392
  const identity = loadDeviceIdentity();
6771
7393
  if (!identity) {
6772
7394
  cleaned.push("server deprovision skipped (no device identity)");
@@ -6831,7 +7453,7 @@ function registerAdminGateway(api, state2) {
6831
7453
  } catch {
6832
7454
  cleaned.push("agent config cleanup skipped");
6833
7455
  }
6834
- const backupPath = path12.join(baseDir, "config-backup.json");
7456
+ const backupPath = path13.join(baseDir, "config-backup.json");
6835
7457
  try {
6836
7458
  cleaned.push(await restoreConfigFromBackup(api, backupPath));
6837
7459
  } catch {
@@ -6854,35 +7476,35 @@ function registerAdminGateway(api, state2) {
6854
7476
  dbPath,
6855
7477
  dbPath ? `${dbPath}-wal` : null,
6856
7478
  dbPath ? `${dbPath}-shm` : null,
6857
- path12.join(baseDir, "config-backup.json"),
6858
- path12.join(baseDir, "notification-config.json"),
6859
- path12.join(baseDir, "canvas-node-identity.json")
7479
+ path13.join(baseDir, "config-backup.json"),
7480
+ path13.join(baseDir, "notification-config.json"),
7481
+ path13.join(baseDir, "canvas-node-identity.json")
6860
7482
  ].filter(Boolean);
6861
7483
  for (const fp of stateFiles) {
6862
7484
  try {
6863
- await fs9.unlink(fp);
6864
- cleaned.push(`deleted ${path12.basename(fp)}`);
7485
+ await fs10.unlink(fp);
7486
+ cleaned.push(`deleted ${path13.basename(fp)}`);
6865
7487
  } catch {}
6866
7488
  }
6867
7489
  if (state2.dbBaseDir) {
6868
7490
  try {
6869
- await fs9.rm(state2.dbBaseDir, { recursive: true, force: true });
7491
+ await fs10.rm(state2.dbBaseDir, { recursive: true, force: true });
6870
7492
  cleaned.push("deleted agent databases");
6871
7493
  } catch {}
6872
7494
  }
6873
7495
  for (const agent of PROVISIONED_AGENTS) {
6874
7496
  if (agent.existingAgent)
6875
7497
  continue;
6876
- const wsDir = agent.workspaceDir ?? path12.join(os7.homedir(), ".openclaw", `workspace-${agent.id}`);
7498
+ const wsDir = agent.workspaceDir ?? path13.join(os8.homedir(), ".openclaw", `workspace-${agent.id}`);
6877
7499
  try {
6878
- await fs9.rm(wsDir, { recursive: true, force: true });
7500
+ await fs10.rm(wsDir, { recursive: true, force: true });
6879
7501
  cleaned.push(`deleted workspace ${agent.id}`);
6880
7502
  } catch {}
6881
7503
  }
6882
7504
  try {
6883
- const remaining = await fs9.readdir(baseDir);
7505
+ const remaining = await fs10.readdir(baseDir);
6884
7506
  if (remaining.length === 0) {
6885
- await fs9.rmdir(baseDir);
7507
+ await fs10.rmdir(baseDir);
6886
7508
  cleaned.push("deleted agentlife state directory");
6887
7509
  }
6888
7510
  } catch {}
@@ -7124,6 +7746,295 @@ function parseOffset2(offset) {
7124
7746
  }
7125
7747
  }
7126
7748
 
7749
+ // gateway/providers.ts
7750
+ import * as fs11 from "node:fs";
7751
+ import * as os9 from "node:os";
7752
+ import * as path14 from "node:path";
7753
+ import {
7754
+ upsertApiKeyProfile,
7755
+ applyAuthProfileConfig
7756
+ } from "openclaw/plugin-sdk/provider-auth-api-key";
7757
+ var catalogCache = null;
7758
+ var catalogPromise = null;
7759
+ var modelsCache = null;
7760
+ var modelsPromise = null;
7761
+ function configPath() {
7762
+ return path14.join(os9.homedir(), ".openclaw", "openclaw.json");
7763
+ }
7764
+ function readConfig() {
7765
+ try {
7766
+ return JSON.parse(fs11.readFileSync(configPath(), "utf-8"));
7767
+ } catch {
7768
+ return {};
7769
+ }
7770
+ }
7771
+ function writeConfig(cfg) {
7772
+ fs11.writeFileSync(configPath(), JSON.stringify(cfg, null, 2) + `
7773
+ `, "utf-8");
7774
+ }
7775
+ async function fetchCatalog(run) {
7776
+ const result = await run(["openclaw", "infer", "model", "providers", "--json"], { timeoutMs: 60000 });
7777
+ if ((result?.code ?? 0) !== 0)
7778
+ throw new Error(`providers catalog query failed`);
7779
+ const raw = (result?.stdout ?? "").trim();
7780
+ const jsonStart = raw.indexOf("[");
7781
+ if (jsonStart < 0)
7782
+ throw new Error("providers catalog: no JSON array in output");
7783
+ const parsed = JSON.parse(raw.slice(jsonStart));
7784
+ if (!Array.isArray(parsed))
7785
+ throw new Error("providers catalog: not an array");
7786
+ return parsed;
7787
+ }
7788
+ async function ensureCatalog(run) {
7789
+ if (catalogCache)
7790
+ return catalogCache;
7791
+ if (!run)
7792
+ return [];
7793
+ if (!catalogPromise) {
7794
+ catalogPromise = fetchCatalog(run).then((c) => {
7795
+ catalogCache = c;
7796
+ return c;
7797
+ }).catch((err) => {
7798
+ catalogPromise = null;
7799
+ throw err;
7800
+ });
7801
+ }
7802
+ return catalogPromise;
7803
+ }
7804
+ async function fetchModels(run) {
7805
+ const result = await run(["openclaw", "infer", "model", "list", "--json"], { timeoutMs: 60000 });
7806
+ if ((result?.code ?? 0) !== 0)
7807
+ throw new Error(`models list query failed`);
7808
+ const raw = (result?.stdout ?? "").trim();
7809
+ const jsonStart = raw.indexOf("[");
7810
+ if (jsonStart < 0)
7811
+ throw new Error("models list: no JSON array in output");
7812
+ const parsed = JSON.parse(raw.slice(jsonStart));
7813
+ if (!Array.isArray(parsed))
7814
+ throw new Error("models list: not an array");
7815
+ return parsed;
7816
+ }
7817
+ async function ensureModels(run) {
7818
+ if (modelsCache)
7819
+ return modelsCache;
7820
+ if (!run)
7821
+ return [];
7822
+ if (!modelsPromise) {
7823
+ modelsPromise = fetchModels(run).then((m) => {
7824
+ modelsCache = m;
7825
+ return m;
7826
+ }).catch((err) => {
7827
+ modelsPromise = null;
7828
+ throw err;
7829
+ });
7830
+ }
7831
+ return modelsPromise;
7832
+ }
7833
+ function authenticatedProviders() {
7834
+ const cfg = readConfig();
7835
+ const profiles = cfg?.auth?.profiles ?? {};
7836
+ const out = new Set;
7837
+ for (const entry of Object.values(profiles)) {
7838
+ const e = entry;
7839
+ if (typeof e?.provider === "string" && typeof e?.mode === "string") {
7840
+ out.add(e.provider);
7841
+ }
7842
+ }
7843
+ return out;
7844
+ }
7845
+ function configuredApiKeyProviders() {
7846
+ const cfg = readConfig();
7847
+ const profiles = cfg?.auth?.profiles ?? {};
7848
+ const out = new Set;
7849
+ for (const entry of Object.values(profiles)) {
7850
+ const e = entry;
7851
+ if (e?.mode === "api_key" && typeof e.provider === "string") {
7852
+ out.add(e.provider);
7853
+ }
7854
+ }
7855
+ return out;
7856
+ }
7857
+ async function probeAnthropic(apiKey) {
7858
+ const ctl = new AbortController;
7859
+ const timer = setTimeout(() => ctl.abort(), 1e4);
7860
+ try {
7861
+ const res = await fetch("https://api.anthropic.com/v1/messages", {
7862
+ method: "POST",
7863
+ headers: {
7864
+ "x-api-key": apiKey,
7865
+ "anthropic-version": "2023-06-01",
7866
+ "content-type": "application/json"
7867
+ },
7868
+ body: JSON.stringify({
7869
+ model: "claude-haiku-4-5",
7870
+ max_tokens: 1,
7871
+ messages: [{ role: "user", content: "ping" }]
7872
+ }),
7873
+ signal: ctl.signal
7874
+ });
7875
+ if (res.ok)
7876
+ return { ok: true };
7877
+ const body = await res.text().catch(() => "");
7878
+ return { ok: false, error: `Anthropic ${res.status}: ${body.slice(0, 240) || res.statusText}` };
7879
+ } catch (err) {
7880
+ return { ok: false, error: err?.message ?? String(err) };
7881
+ } finally {
7882
+ clearTimeout(timer);
7883
+ }
7884
+ }
7885
+ async function probeOpenAI(apiKey) {
7886
+ const ctl = new AbortController;
7887
+ const timer = setTimeout(() => ctl.abort(), 1e4);
7888
+ try {
7889
+ const res = await fetch("https://api.openai.com/v1/models", {
7890
+ headers: { authorization: `Bearer ${apiKey}` },
7891
+ signal: ctl.signal
7892
+ });
7893
+ if (res.ok)
7894
+ return { ok: true };
7895
+ const body = await res.text().catch(() => "");
7896
+ return { ok: false, error: `OpenAI ${res.status}: ${body.slice(0, 240) || res.statusText}` };
7897
+ } catch (err) {
7898
+ return { ok: false, error: err?.message ?? String(err) };
7899
+ } finally {
7900
+ clearTimeout(timer);
7901
+ }
7902
+ }
7903
+ function canProbe(provider) {
7904
+ return provider === "anthropic" || provider === "openai";
7905
+ }
7906
+ async function probe(provider, apiKey) {
7907
+ if (provider === "anthropic")
7908
+ return probeAnthropic(apiKey);
7909
+ if (provider === "openai")
7910
+ return probeOpenAI(apiKey);
7911
+ return { ok: false, error: `test not supported for provider: ${provider}` };
7912
+ }
7913
+ function registerProvidersGateway(api, state2) {
7914
+ const run = state2.runCommand;
7915
+ if (run) {
7916
+ ensureCatalog(run).catch(() => {});
7917
+ ensureModels(run).catch(() => {});
7918
+ }
7919
+ api.registerGatewayMethod("agentlife.providers.list", async ({ respond }) => {
7920
+ try {
7921
+ const [catalog, configured] = await Promise.all([
7922
+ ensureCatalog(run ?? null),
7923
+ Promise.resolve(configuredApiKeyProviders())
7924
+ ]);
7925
+ const providers = catalog.map((entry) => ({
7926
+ provider: entry.provider,
7927
+ modelCount: entry.count,
7928
+ available: entry.available,
7929
+ configured: configured.has(entry.provider),
7930
+ canProbe: canProbe(entry.provider)
7931
+ }));
7932
+ respond(true, { providers });
7933
+ } catch (err) {
7934
+ respond(false, { error: err?.message ?? String(err) });
7935
+ }
7936
+ }, { scope: "operator.read" });
7937
+ api.registerGatewayMethod("agentlife.providers.set", async ({ params, respond }) => {
7938
+ const provider = typeof params?.provider === "string" ? params.provider.trim() : "";
7939
+ const apiKey = typeof params?.apiKey === "string" ? params.apiKey.trim() : "";
7940
+ if (!provider)
7941
+ return respond(false, { error: "provider is empty" });
7942
+ if (!apiKey)
7943
+ return respond(false, { error: "apiKey is empty" });
7944
+ try {
7945
+ const catalog = await ensureCatalog(run ?? null);
7946
+ if (catalog.length > 0 && !catalog.some((e) => e.provider === provider)) {
7947
+ return respond(false, { error: `unknown provider: ${provider}` });
7948
+ }
7949
+ } catch {}
7950
+ try {
7951
+ const profileId = upsertApiKeyProfile({ provider, input: apiKey });
7952
+ const cfg = readConfig();
7953
+ const nextCfg = applyAuthProfileConfig(cfg, {
7954
+ profileId,
7955
+ provider,
7956
+ mode: "api_key",
7957
+ preferProfileFirst: true
7958
+ });
7959
+ writeConfig(nextCfg);
7960
+ respond(true, { provider, configured: true, profileId });
7961
+ } catch (err) {
7962
+ respond(false, { error: err?.message ?? String(err) });
7963
+ }
7964
+ }, { scope: "operator.write" });
7965
+ api.registerGatewayMethod("agentlife.providers.delete", ({ params, respond }) => {
7966
+ const provider = typeof params?.provider === "string" ? params.provider.trim() : "";
7967
+ if (!provider)
7968
+ return respond(false, { error: "provider is empty" });
7969
+ try {
7970
+ const cfg = readConfig();
7971
+ const profiles = cfg?.auth?.profiles;
7972
+ if (profiles && typeof profiles === "object") {
7973
+ for (const [id, entry] of Object.entries(profiles)) {
7974
+ if (entry?.provider === provider)
7975
+ delete profiles[id];
7976
+ }
7977
+ }
7978
+ if (cfg?.auth?.order && typeof cfg.auth.order === "object") {
7979
+ delete cfg.auth.order[provider];
7980
+ }
7981
+ writeConfig(cfg);
7982
+ respond(true, { provider, configured: false });
7983
+ } catch (err) {
7984
+ respond(false, { error: err?.message ?? String(err) });
7985
+ }
7986
+ }, { scope: "operator.write" });
7987
+ api.registerGatewayMethod("agentlife.providers.test", async ({ params, respond }) => {
7988
+ const provider = typeof params?.provider === "string" ? params.provider.trim() : "";
7989
+ const apiKey = typeof params?.apiKey === "string" ? params.apiKey.trim() : "";
7990
+ if (!provider)
7991
+ return respond(false, { error: "provider is empty" });
7992
+ if (!apiKey)
7993
+ return respond(true, { ok: false, error: "apiKey required — type a key before testing" });
7994
+ try {
7995
+ const result = await probe(provider, apiKey);
7996
+ respond(true, { ok: result.ok, error: result.error });
7997
+ } catch (err) {
7998
+ respond(true, { ok: false, error: err?.message ?? String(err) });
7999
+ }
8000
+ }, { scope: "operator.read" });
8001
+ api.registerGatewayMethod("agentlife.models.list", async ({ respond }) => {
8002
+ try {
8003
+ const [all, authed] = await Promise.all([
8004
+ ensureModels(run ?? null),
8005
+ Promise.resolve(authenticatedProviders())
8006
+ ]);
8007
+ const filtered = all.filter((m) => authed.has(m.provider));
8008
+ const DATE_SUFFIX = /-(?:\d{4}-\d{2}-\d{2}|\d{8})$/;
8009
+ const undatedIds = new Set(filtered.filter((m) => !DATE_SUFFIX.test(m.id)).map((m) => m.id));
8010
+ const deduped = filtered.filter((m) => {
8011
+ if (!DATE_SUFFIX.test(m.id))
8012
+ return true;
8013
+ return !undatedIds.has(m.id.replace(DATE_SUFFIX, ""));
8014
+ });
8015
+ respond(true, { models: deduped });
8016
+ } catch (err) {
8017
+ respond(false, { error: err?.message ?? String(err) });
8018
+ }
8019
+ }, { scope: "operator.read" });
8020
+ api.registerGatewayMethod("agentlife.providers.loginCodex", async ({ respond }) => {
8021
+ if (!run)
8022
+ return respond(false, { error: "runCommand unavailable" });
8023
+ try {
8024
+ const result = await run(["openclaw", "capability", "model", "auth", "login", "--provider", "openai-codex"], { timeoutMs: 180000 });
8025
+ const combined = `${result?.stdout ?? ""}
8026
+ ${result?.stderr ?? ""}`;
8027
+ const urlMatch = combined.match(/https:\/\/\S+/);
8028
+ if ((result?.code ?? 0) !== 0 && !urlMatch) {
8029
+ return respond(false, { error: (result?.stderr ?? "").trim() || "codex login failed" });
8030
+ }
8031
+ respond(true, { url: urlMatch?.[0] ?? null, completed: (result?.code ?? 0) === 0 });
8032
+ } catch (err) {
8033
+ respond(false, { error: err?.message ?? String(err) });
8034
+ }
8035
+ }, { scope: "operator.write" });
8036
+ }
8037
+
7127
8038
  // index.ts
7128
8039
  var currentState = null;
7129
8040
  var registered = false;
@@ -7133,7 +8044,7 @@ var stopQualityCheckPoller = null;
7133
8044
  var stopCloudflared = null;
7134
8045
  function resolveInternalModel(api) {
7135
8046
  try {
7136
- const pluginCfgPath = path13.join(homedir9(), ".openclaw", "agentlife", "plugin-config.json");
8047
+ const pluginCfgPath = path15.join(homedir11(), ".openclaw", "agentlife", "plugin-config.json");
7137
8048
  try {
7138
8049
  const raw = __require("node:fs").readFileSync(pluginCfgPath, "utf-8");
7139
8050
  const pluginCfg = JSON.parse(raw);
@@ -7169,7 +8080,7 @@ function register(api) {
7169
8080
  return;
7170
8081
  }
7171
8082
  registered = true;
7172
- const fallbackDir = path13.join(homedir9(), ".openclaw", "agentlife");
8083
+ const fallbackDir = path15.join(homedir11(), ".openclaw", "agentlife");
7173
8084
  const state2 = {
7174
8085
  surfaceDb: null,
7175
8086
  agentRegistry: new Map,
@@ -7179,9 +8090,9 @@ function register(api) {
7179
8090
  agentDbs: new Map,
7180
8091
  historyDb: null,
7181
8092
  agentlifeStateDir: fallbackDir,
7182
- registryFilePath: path13.join(fallbackDir, "agent-registry.json"),
7183
- dbBaseDir: path13.join(fallbackDir, "db"),
7184
- historyDbPath: path13.join(fallbackDir, "agentlife.db"),
8093
+ registryFilePath: path15.join(fallbackDir, "agent-registry.json"),
8094
+ dbBaseDir: path15.join(fallbackDir, "db"),
8095
+ historyDbPath: path15.join(fallbackDir, "agentlife.db"),
7185
8096
  runCommand: api.runtime.system?.runCommandWithTimeout ?? null,
7186
8097
  enqueueSystemEvent: null,
7187
8098
  requestHeartbeatNow: null,
@@ -7212,6 +8123,7 @@ function register(api) {
7212
8123
  stopObservability = startObservabilityService(state2);
7213
8124
  stopDailySweep = startDailySweepService(state2);
7214
8125
  stopQualityCheckPoller = startQualityCheckPoller(state2);
8126
+ await initColdStartMachine(state2, api.runtime, console.log);
7215
8127
  }
7216
8128
  });
7217
8129
  registerConfigOptimizer(api, state2);
@@ -7238,6 +8150,7 @@ function register(api) {
7238
8150
  stopCloudflared();
7239
8151
  stopCloudflared = null;
7240
8152
  }
8153
+ shutdownColdStartMachine();
7241
8154
  closeAllDbs(state2);
7242
8155
  }
7243
8156
  });
@@ -7254,8 +8167,9 @@ function register(api) {
7254
8167
  registerAutomationsGateway(api, state2);
7255
8168
  registerFollowupsGateway(api, state2);
7256
8169
  registerAdminGateway(api, state2);
8170
+ registerProvidersGateway(api, state2);
7257
8171
  registerWebApp(api);
7258
- const notifyConfigPath = path13.join(fallbackDir, "notification-config.json");
8172
+ const notifyConfigPath = path15.join(fallbackDir, "notification-config.json");
7259
8173
  api.registerGatewayMethod("agentlife.notifications.register", ({ params, respond }) => {
7260
8174
  const serverUrl = typeof params?.serverUrl === "string" ? params.serverUrl.trim() : "";
7261
8175
  const apiKey = typeof params?.apiKey === "string" ? params.apiKey.trim() : "";
@@ -7263,35 +8177,45 @@ function register(api) {
7263
8177
  return respond(false, { error: "missing serverUrl or apiKey" });
7264
8178
  }
7265
8179
  try {
7266
- const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync6 } = __require("node:fs");
7267
- mkdirSync6(path13.dirname(notifyConfigPath), { recursive: true });
7268
- writeFileSync7(notifyConfigPath, JSON.stringify({ serverUrl, apiKey }));
8180
+ const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync6 } = __require("node:fs");
8181
+ mkdirSync6(path15.dirname(notifyConfigPath), { recursive: true });
8182
+ writeFileSync8(notifyConfigPath, JSON.stringify({ serverUrl, apiKey }));
7269
8183
  } catch {}
7270
8184
  respond(true, { registered: true });
7271
8185
  }, { scope: "operator.write" });
7272
- api.registerGatewayMethod("agentlife.vision.ensure", async ({ respond }) => {
8186
+ api.registerGatewayMethod("agentlife.coldStart.observe", ({ respond }) => {
7273
8187
  try {
7274
8188
  if (!currentState) {
7275
8189
  return respond(false, { error: "plugin state not initialized" });
7276
8190
  }
7277
- const result = await ensureVisionPosters(currentState, api.runtime, console.log);
7278
- respond(true, result);
8191
+ respond(true, observeColdStart(currentState));
7279
8192
  } catch (e) {
7280
8193
  respond(false, { code: "internal_error", message: e?.message ?? String(e) });
7281
8194
  }
7282
8195
  }, { scope: "operator.read" });
7283
- api.registerGatewayMethod("agentlife.vision.retryWarmup", async ({ respond }) => {
8196
+ api.registerGatewayMethod("agentlife.coldStart.retry", async ({ respond }) => {
7284
8197
  try {
7285
8198
  if (!currentState) {
7286
8199
  return respond(false, { error: "plugin state not initialized" });
7287
8200
  }
7288
- resetBootstrapCooldown();
7289
- const result = await ensureVisionPosters(currentState, api.runtime, console.log);
7290
- respond(true, result);
8201
+ respond(true, await retryColdStart(currentState));
7291
8202
  } catch (e) {
7292
8203
  respond(false, { code: "internal_error", message: e?.message ?? String(e) });
7293
8204
  }
7294
8205
  }, { scope: "operator.read" });
8206
+ api.registerGatewayMethod("agentlife.coldStart.advance", ({ params, respond }) => {
8207
+ try {
8208
+ if (!currentState)
8209
+ return respond(false, { error: "plugin state not initialized" });
8210
+ const kind = typeof params?.kind === "string" ? params.kind : null;
8211
+ if (!kind)
8212
+ return respond(false, { error: "missing kind" });
8213
+ emitTrigger(currentState, kind, params?.payload ?? {});
8214
+ respond(true, { enqueued: true });
8215
+ } catch (e) {
8216
+ respond(false, { code: "internal_error", message: e?.message ?? String(e) });
8217
+ }
8218
+ }, { scope: "operator.admin" });
7295
8219
  }
7296
8220
  export {
7297
8221
  register as default,