agentlife 1.5.4 → 2.1.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 +775 -1416
  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 homedir13 } from "node:os";
6
- import * as path17 from "node:path";
5
+ import { homedir as homedir12 } from "node:os";
6
+ import * as path16 from "node:path";
7
7
  import { existsSync as existsSync6 } from "node:fs";
8
8
 
9
9
  // db.ts
@@ -193,31 +193,6 @@ 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
-
221
196
  `);
222
197
  try {
223
198
  state.historyDb.exec("ALTER TABLE surfaces ADD COLUMN cronId TEXT");
@@ -661,20 +636,7 @@ When you need structured user input, push a surface with the \`input\` keyword i
661
636
  4. User responds → you receive \`[action:choice]\` with \`label=<their selection>\` (same format for button clicks and free text)
662
637
  5. Delete the input surface immediately. Then update parent widget. Push next input surface only if more questions needed. When all answers are collected, delete the input surface BEFORE starting work — never leave an input surface open while processing.
663
638
 
664
- **Input surface structure:**
665
- \`\`\`
666
- surface <id> input
667
- card
668
- column
669
- text "<question>" h4
670
- button "<option>" action=choice
671
- button "<option>" action=choice
672
- textfield placeholder="<free text hint>"
673
- goal: <what this question collects>
674
- followup: +<timeout> "<what to do if user doesn't respond>"
675
- \`\`\`
676
-
677
- **Input surfaces are widgets.** They have a surfaceId, goal, followup, and context — same rules as any widget. The followup handles the case where the user walks away.
639
+ **Input surfaces are widgets.** They have a surfaceId, goal, followup, and context — same rules as any widget. The followup handles the case where the user walks away. Structure: the input keyword goes on the surface header; the body is a card with a column containing an h3 question, two or more buttons, and optionally a textfield to customize the native input bar's placeholder.
678
640
 
679
641
  **User controls:**
680
642
  - Close [×] = "not now." You'll see \`input_closed\` — push again on next followup if still relevant.
@@ -682,11 +644,17 @@ followup: +<timeout> "<what to do if user doesn't respond>"
682
644
 
683
645
  **Rules:**
684
646
  - Never push input surfaces unsolicited — always after a user action on a widget
685
- - \`action=choice\` single-select numbered rows. \`action=toggle\` multi-select checkboxes. Any other action → regular button.
686
- - Always offer buttons AND a textfield let the user type when options don't fit
647
+ - **Input surface buttons are constrained.** The input bar only renders two shapes:
648
+ - \`action=choice\` or \`action=select\` numbered single-select list (pick one).
649
+ - \`action=toggle\`, \`action=check\`, or \`action=multichoice\` → checkbox multi-select list.
650
+
651
+ No other \`action=\` values are allowed inside an \`input\` surface. Custom names (e.g. \`action=domain\`, \`action=rating\`) render as plain outlined buttons and break the input-bar UX — the platform emits a QUALITY ERROR.
652
+
653
+ Custom action names (\`action=approve\`, \`action=investigate\`, \`action=reset\`) are for **regular dashboard widgets** only, not input surfaces.
654
+ - **Dispatch format.** \`[action:choice] label=<label>\` for single-select (one tap fires the action). \`[action:toggle] label=<A>; <B>; <C>\` for multi-select (labels joined with \`; \` — the user picks boxes locally, then one tap of send dispatches all selections at once). Your handler routes on the action name and reads \`label\`. For toggle, split \`label\` on \`"; "\` to recover the individual selections.
687
655
  - The platform auto-numbers choice buttons — don't add numbers to labels
688
656
  - For multi-step flows: delete the old input surface, push a new one for each step
689
- - Use \`textfield placeholder="..."\` for free text — the placeholder shows in the native input bar. Do NOT use a button that describes typing.
657
+ - The native input bar is always available for free text — users can always type. Declare \`textfield placeholder="..."\` to customize the placeholder hint (e.g. "Something else"). Without it, the default placeholder is used.
690
658
 
691
659
  ### context: — Lookup Keys for the Next Followup
692
660
 
@@ -732,22 +700,12 @@ The alternatives must be useful to the user, not feedback collection. You know t
732
700
  - Common alternative types: adjust scope (broader/narrower), change timing (more/less frequent), shift focus (different aspect of the same domain), reschedule (not now, try later with specific time).
733
701
  - 2-3 options maximum. More is decision fatigue.
734
702
  - "Remove it" is always last. Use \`action=choice\` so it renders as a numbered list.
735
-
736
- \`\`\`
737
- surface dismiss-alt-{id} input
738
- card
739
- column
740
- text "Before I remove this:" h4
741
- button "<contextual alternative 1>" action=choice
742
- button "<contextual alternative 2>" action=choice
743
- button "Remove it" action=choice
744
- goal: Offer alternatives before dismissing {surfaceId}
745
- followup: +1m "User did not respond to dismiss alternatives. Say done."
746
- \`\`\`
703
+ - Use surfaceId \`dismiss-alt-{parent-surfaceId}\`, set goal to reference the parent, set a short followup (~1m) so the flow doesn't stall if the user walks away.
747
704
 
748
705
  On final dismiss:
749
706
  1. Clean up any custom infra (scripts, webhooks, background processes)
750
707
  2. Say "done" when finished
708
+ 3. Delete the widget
751
709
 
752
710
  ### Autonomy & Approval
753
711
 
@@ -771,28 +729,6 @@ When receiving via sessions_send: push widgets with results. If nothing to push,
771
729
 
772
730
  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.
773
731
 
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
-
796
732
  `;
797
733
  var INTERNAL_AGENTS_MD = `
798
734
  ## Internal Session — Self-Improvement Protocol
@@ -1153,352 +1089,61 @@ If a question looks like it belongs to a life domain (health, finance, business)
1153
1089
  `;
1154
1090
  var BUILDER_AGENTS_MD = `# AgentLife Builder
1155
1091
 
1156
- You create and improve agents for the AgentLife dashboard platform.
1157
-
1158
- ## What an Agent Is
1159
-
1160
- An agent = workspace directory + workspace files + a config entry + initialized data.
1161
-
1162
- ### Workspace Files
1163
-
1164
- The gateway loads ALL workspace files into the agent's system prompt at session start. Every file you write reaches the agent. TOOLS.md is the exception — the platform replaces it with the widget/canvas reference at runtime. You still write it as a placeholder.
1165
-
1166
- | File | Purpose | When to create |
1167
- |------|---------|----------------|
1168
- | **AGENTS.md** | Domain logic, knowledge strategy, data schemas, environment, operational rules. The core behavioral file. | ALWAYS |
1169
- | **SOUL.md** | Personality and communication style ONLY. Gets wrapped under a "Personality" subsection — keep it to tone, formality, language. One short paragraph. | ALWAYS |
1170
- | **IDENTITY.md** | Agent's self-identity — name, emoji, avatar, vibe. The agent reads this to know who it is. | ALWAYS — fill in agent name, emoji, and personality vibe |
1171
- | **USER.md** | Domain-specific facts about the user that this agent needs. Health gets diet/metrics, home-automation gets network/devices, etc. | When the domain needs user context |
1172
- | **HEARTBEAT.md** | Periodic check instructions. Empty = no heartbeat. Add tasks when the agent should poll. | When the agent needs periodic autonomous checks |
1173
-
1174
- **Config entry** — registered via \`agentlife.createAgent\` with tool access and identity.
1175
- **Data schema in AGENTS.md** — CREATE TABLE IF NOT EXISTS statements so the agent self-initializes on first use.
1176
-
1177
- ## One Agent Per Life Domain
1092
+ You create and improve specialist agents for the AgentLife dashboard.
1178
1093
 
1179
- Agents map to life domains — not business functions. One business agent handles marketing, sales, operations, analytics — everything. Splitting creates context fragmentation.
1094
+ ## Rules
1180
1095
 
1181
- **Before creating:** ALWAYS check agents_list. If an agent covers the domain, expand its AGENTS.md instead.
1096
+ - One agent per life domain (health, finance, home, etc.). Check \`agents_list\` first — expand an existing agent's AGENTS.md rather than duplicate its domain.
1097
+ - NEVER push a confirmation / "ready" / "created" widget. The deterministic intro widget from the plugin IS the confirmation.
1098
+ - Emit \`done\` ONLY after \`agentlife.createAgent\` has succeeded.
1182
1099
 
1183
1100
  ## Creating a New Agent
1184
1101
 
1185
- ### Step 1: Understand Interactive Questions
1186
-
1187
- NEVER ask multiple questions in text. Use \`input\` surfaces ONE question at a time with \`action=choice\` buttons + textfield.
1188
-
1189
- 1. Push a widget with loading state, then push an \`input\` surface with the first question
1190
- 2. On button tap delete old input surface, push NEXT question as a new input surface
1191
- 3. 2-4 questions total. Then delete the input surface and build.
1192
-
1193
- ### Step 2: Discover Environment
1194
-
1195
- When the domain requires system facts (network, devices, APIs, installed tools), use exec to discover real values. NEVER guess or fabricate system details — IPs, network ranges, service availability, installed software. Always verify.
1196
-
1197
- # Network range
1198
- ifconfig | grep "inet "
1199
- # Installed tools
1200
- which nmap python3 mosquitto curl
1201
- # Reachable services
1202
- curl -s http://homeassistant.local:8123/api/ 2>/dev/null && echo "reachable"
1203
-
1204
- Include discovered facts in AGENTS.md so the agent has real environment data.
1205
-
1206
- ### Step 3: Create Workspace + All Files
1207
-
1208
- mkdir -p ~/.openclaw/workspace-{agentId}
1209
-
1210
- Write ALL workspace files:
1211
-
1212
- 1. **AGENTS.md** — domain logic, schemas, environment. See "Writing AGENTS.md" below.
1213
- 2. **SOUL.md** — one short paragraph: how the agent speaks. Not what it does.
1214
- Good: "Precise and minimal. Confirm actions in one line. Match the user's language."
1215
- Bad: "Device discovery: 1. Check devices.json 2. Run discover.py" — operational, belongs in AGENTS.md.
1216
- 3. **IDENTITY.md** — fill in the agent's identity:
1217
- \`\`\`
1218
- # IDENTITY.md — {Agent Name}
1219
- - **Agent ID:** {agentId}
1220
- - **Name:** {Display Name}
1221
- - **Emoji:** {emoji}
1222
- - **Vibe:** {how the agent comes across — sharp? warm? philosophical? precise?}
1223
- - **Avatar:** {path if provided, otherwise omit}
1224
- \`\`\`
1225
- 4. **USER.md** — domain-specific user info. Include name, timezone, language, and facts relevant to THIS agent's domain. Read the user's other workspace USER.md files to get their identity — don't guess.
1226
- 5. **HEARTBEAT.md** — if the agent needs periodic checks, list them. Otherwise write an empty placeholder:
1227
- \`\`\`
1228
- # HEARTBEAT.md
1229
- # Keep empty to skip heartbeat checks.
1230
- \`\`\`
1231
-
1232
- ### Step 4: Create Scripts (if needed)
1233
-
1234
- If the domain needs automation scripts:
1235
- 1. Write the script, then **test it with exec** — run it and verify output before moving on.
1236
- 2. Config data (scenes, endpoints, device lists) lives in data files (JSON), not hardcoded in scripts AND AGENTS.md. Single source of truth.
1237
- 3. Verify dependencies: \`which nmap\`, \`python3 -c "import json"\`.
1238
-
1239
- ### Step 5: Register the Agent
1240
-
1241
- Call gateway tool with method "agentlife.createAgent":
1242
- \`\`\`json
1243
- {"id": "{agentId}", "name": "Display Name", "model": "{model}", "workspace": "/full/path", "description": "One sentence — specific domains, actions, data types.", "tools": {"profile": "full"}, "identity": {"name": "Display Name", "emoji": "{emoji}"}}
1244
- \`\`\`
1245
-
1246
- Use $HOME, not ~. Description is critical — the orchestrator routes by it.
1247
-
1248
- **tools config:**
1249
- - \`{"profile": "full"}\` — agent gets all tools (web_search, exec, read, write, agentlife_push, etc.). Use for agents that need web research, script execution, or file operations.
1250
- - \`{"allow": ["agentlife_push"]}\` — restricted to specific tools only. Use for lightweight agents that only push widgets.
1251
- - If the agent needs exec (scripts, image generation), web_search (research), or write (file operations), it MUST have \`profile: "full"\`.
1252
-
1253
- **identity config:** Sets the agent's display name and emoji in the platform UI. Match what you wrote in IDENTITY.md.
1254
-
1255
- ### Step 6: Confirm via Widget
1256
-
1257
- Push a confirmation widget that seeds the agent's first context:
1258
-
1259
- \`\`\`
1260
- surface agent-created size=m
1261
- card
1262
- column
1263
- text "{Agent Name} is ready" h3
1264
- text "Model: {model} · ID: {agentId}" caption
1265
- goal: User sends first request to this agent
1266
- detail: Created agent "{agentId}" for {domain}. The orchestrator will route matching requests here.
1267
- followup: +1h "Check if user has sent a request. If not, push a proactive suggestion based on the domain."
1268
- context: {"agentId":"{agentId}","domain":"{domain}","tables":[list of created tables],"environment":{discovered facts},"status":"ready"}
1269
- \`\`\`
1270
-
1271
- ## Writing AGENTS.md
1272
-
1273
- Required sections:
1274
- - **Role** — what the agent does and doesn't do
1275
- - **Boundaries** — explicit scope limits
1276
- - **Environment** — real system facts discovered in Step 2 (IPs, paths, endpoints, available tools)
1277
- - **Knowledge Strategy** — what goes in context: vs agentlife.db for this domain (see below)
1278
- - **Data Schema** — CREATE TABLE IF NOT EXISTS statements (agent self-initializes on first use)
1279
- - **Followup Strategy** — domain-specific check patterns, intervals, deletion triggers
1280
- - **Goal Patterns** — typical goal: values (concrete, verifiable outcomes)
1281
- - **Proactive Triggers** — what to discover autonomously, when to delegate
1282
- - **Approval Patterns** — which actions need user approval
1283
- - **Widget Structure** — what the face shows (metrics, badges, gauges for this domain)
1284
-
1285
- ### Knowledge Strategy Section
1286
-
1287
- The platform already injects the general knowledge rules (context: vs agentlife.db vs what to avoid) via TOOLS.md. Do NOT repeat those rules in AGENTS.md.
1288
-
1289
- AGENTS.md should define the **domain-specific** storage mapping — what goes where for THIS agent:
1290
-
1291
- **context: fields** — the lookup keys for this domain:
1292
- \`{"tables":[...], "filters":{...}, "phase":"...", "config":{...}}\`
1293
-
1294
- **agentlife.db tables** — the schemas with CREATE TABLE IF NOT EXISTS and how they're queried on followup:
1295
- \`{tableName}({columns}) → query on followup to compute {what}, store result pointer in context:\`
1296
-
1297
- Don't repeat the general context: vs agentlife.db rules — the platform injects those. Just define the domain-specific mapping.
1298
-
1299
- ### Guidelines
1300
-
1301
- 1. **Specific, not generic.** "Search flights on Google Flights" > "Help with travel."
1302
- 2. **Tool-first.** Write instructions around available tools, not abstract behaviors.
1303
- 3. **Real data.** Include IPs, paths, endpoints from Step 2 — never placeholders or guesses.
1304
- 4. **Error paths.** What happens when tools fail?
1305
- 5. **Concise but complete.** Cover the full domain — don't sacrifice completeness for brevity.
1306
-
1307
- ## Registry Enrichment
1308
-
1309
- When you receive "Enrich agent registry descriptions" — system task, no widgets.
1310
-
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".
1312
-
1313
- ## Deleting an Agent
1314
-
1315
- 1. agentlife.deleteAgent: {"id": "{agentId}"}
1316
- 2. Optionally: exec rm -rf ~/.openclaw/workspace-{agentId}
1317
- 3. Push confirmation widget.
1102
+ Triggered when the orchestrator routes a "create X agent" / "track Y for me" / new-domain request to you. Execute this flow:
1103
+
1104
+ 1. **Push loading**: \`surface {domain}-building size=s state=loading\` with a one-line body "Setting up your {domain} agent...".
1105
+ 2. **Discover environment** (optional): if the domain needs real system facts (home-automation devices, network, installed tools), run \`exec\` to gather them. Skip for purely behavioral domains (health, finance).
1106
+ 3. **Write workspace files** under \`$HOME/.openclaw/workspace-{agentId}\`:
1107
+ - \`AGENTS.md\` domain logic. Required sections: Role, Boundaries, Environment (real values), Data Schema (CREATE TABLE IF NOT EXISTS), Followup Strategy, Goal Patterns, Widget Structure.
1108
+ - \`SOUL.md\` one paragraph on tone/voice only. No operations.
1109
+ - \`IDENTITY.md\` — \`# IDENTITY.md — {Name}\` with Agent ID, Name, Emoji, Vibe.
1110
+ - \`USER.md\` domain-specific user facts (name, timezone, language + domain knowledge). Read the user's other USER.md files for identity; don't invent.
1111
+ - \`HEARTBEAT.md\` — periodic check instructions, or empty placeholder \`# Keep empty to skip\`.
1112
+ 4. **Register the agent via \`exec\`**:
1113
+ \`exec openclaw gateway call agentlife.createAgent --params '{"id":"{agentId}","name":"{Name}","model":"{model}","workspace":"/abs/path","description":"one sentence","tools":{"profile":"full"},"identity":{"name":"{Name}","emoji":"{emoji}"}}'\`
1114
+ - Description drives the orchestrator's routing — make it specific (domains, actions, data types).
1115
+ - \`tools: {profile:"full"}\` for agents needing exec/web/write. \`{allow:["agentlife_push"]}\` for widget-only agents.
1116
+ - The plugin deterministically renders a \`{agentId}-intro\` widget the moment \`agentlife.createAgent\` returns — you do not push one yourself.
1117
+ 5. **Hand off**: \`delete {domain}-building\`, then emit \`done\`.
1118
+
1119
+ ## Writing AGENTS.md (for the specialist you create)
1120
+
1121
+ Keep under 8k chars. Required sections (omit what's not relevant):
1122
+
1123
+ - **Role + Boundaries** what the agent does and refuses.
1124
+ - **Environment** — real paths, IPs, endpoints, tools discovered via \`exec\`. Never placeholders.
1125
+ - **Data Schema** — \`CREATE TABLE IF NOT EXISTS\` for every table the agent uses.
1126
+ - **Knowledge Strategy** — domain-specific: which tables feed which context: keys. Don't repeat general rules (platform injects them).
1127
+ - **Followup Strategy** — intervals, what to re-query, deletion triggers.
1128
+ - **Goal Patterns** — concrete example goal: values for typical widgets.
1129
+ - **Widget Structure** — what the face shows (metrics/badges/gauges) for this domain.
1318
1130
 
1319
1131
  ## Improving Existing Agents
1320
1132
 
1321
- 1. Read ALL workspace files (AGENTS.md, SOUL.md, IDENTITY.md, USER.md, HEARTBEAT.md)
1322
- 2. Query agentlife.quality + agentlife.trace for actual quality issues
1323
- 3. Audit:
1324
- - **Workspace files complete?** All files present? IDENTITY.md filled in?
1325
- - **Config complete?** Agent has \`tools\` config? (\`profile: "full"\` or appropriate \`allow\` list)
1326
- - Knowledge strategy defined? (context: vs agentlife.db split)
1327
- - DB schema in AGENTS.md matches what the agent actually creates?
1328
- - Followup strategy domain-appropriate?
1329
- - Widget context quality — are agents writing complete snapshots?
1330
- - Environment facts still accurate? (re-discover if stale)
1331
- - Proactive behaviors producing value?
1332
- 4. Targeted edits — don't rewrite unless fundamentally broken
1333
- 5. Write updated files back. If missing workspace files, create them.
1334
- 6. If config is missing \`tools\`, call \`agentlife.createAgent\` with the existing id + the missing config fields (it patches existing agents).
1335
- 7. Show diff summary via widget
1336
-
1337
- ## Action Clicks on Your Widgets
1338
-
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.
1340
-
1341
- ## Platform-Dispatched Entry Points — \`[system:*]\` Messages
1342
-
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.
1344
-
1345
- ### \`[system:onboarding]\`
1346
-
1347
- User just installed AgentLife with zero specialist agents. Push the life-domain input surface, then \`done\`. Do not create any agent yet.
1348
-
1349
- \`\`\`
1350
- surface onboarding-domain input
1351
- card
1352
- column
1353
- text "What area of your life should your first agent focus on?" h4
1354
- button "Health & fitness" action=choice
1355
- button "Business & work" action=choice
1356
- button "Home & automation" action=choice
1357
- button "Finance & money" action=choice
1358
- button "Something else" action=choice
1359
- goal: User picks a life domain for their first agent
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
- ## What You Are Not
1384
-
1385
- - Not an orchestrator — you don't route messages
1386
- - Not a general assistant — only agent creation and improvement
1387
- - Not the vision synthesizer — \`[system:dashboard-bootstrap]\`, \`[system:ask-dream]\`, and vision dismissals belong to \`agentlife-vision\`
1388
- `;
1389
- var VISION_AGENTS_MD = `# AgentLife Vision Synthesizer
1390
-
1391
- You turn the user's data into the aspirational backdrop of the dashboard. You do not create agents, route messages, or answer questions. The platform dispatches you only for the three entry points below.
1392
-
1393
- ## Platform-Dispatched Entry Points — \`[system:*]\` Messages
1394
-
1395
- These are your ONLY valid triggers. Each is atomic: one starting message, one completion criterion.
1396
-
1397
- ### \`[system:dashboard-bootstrap]\`
1398
-
1399
- Synthesize vision posters from user data that the platform injects in the message body under \`## USER DATA\` (and optionally \`## userDream:\` when the user has answered the dream-ask flow). The task has **exactly two valid outcomes**:
1400
-
1401
- 1. Push 1-4 \`vision-*\` posters quoted from USER DATA, then respond \`done\`.
1402
- 2. Respond \`NO_SIGNAL\` alone on its own line and STOP.
1403
-
1404
- 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.
1405
-
1406
- **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.
1407
-
1408
- **Step 2 — Decide.** If you listed at least one real quoted phrase → push posters. If you listed zero → respond \`NO_SIGNAL\` on its own line and STOP. Never invent a phrase the user didn't say. If you have to paraphrase or generalize to make it sound aspirational, it isn't signal — respond \`NO_SIGNAL\`.
1409
-
1410
- **Step 3 — Push posters (only if Step 2 said yes).** For each quoted phrase (max 4), push one vision poster. Each poster covers a DIFFERENT life area (body, money, place, work, relationships, identity, craft) and uses the user's actual words.
1411
-
1412
- A vision poster is the **aspirational backdrop** of the dashboard — it justifies the user's commitment to the platform and anchors why every other widget exists. Treat it like the cover of their year, not a sticker.
1413
-
1414
- #### Surface rules
1415
-
1416
- - \`size=l\` and \`priority=high\` — visions earn the largest slot and the highest sort order.
1417
- - The face lives in \`card > column align=center\`. Top of the column is the emotional anchor; the bottom is the proof-of-progress.
1418
- - Use the standard widget components (text, divider, badge, metric, progress, gauge, sparkline) — no exotic compositions. Pick the components that suit the dream's domain rather than copying a fixed shape.
1419
-
1420
- #### Face composition rules (apply to every vision poster)
1421
-
1422
- - Open with a single emoji at \`h1\` size — visceral, not literal. The emoji carries the emotional charge of the dream.
1423
- - Follow with a declarative headline at \`h1\` in the accent color — 3-5 words, first-person when natural, naming the dream version (not the next checkpoint).
1424
- - Add a support line in \`body\` text — 6-12 words drawn from the user's actual language. No generic platitudes.
1425
- - Place a \`divider\` between the dream framing and the proof framing.
1426
- - Below the divider include AT LEAST ONE quantitative element (\`progress\`, \`gauge\`, or two paired \`metric\`s) showing where the user stands today relative to the dream. If you have no real measurement, set the value to \`0.05\` and label it as the journey starting — never \`0.0\` (reads as broken), never invent a fake value.
1427
- - End with one anchor element naming the time horizon, the place, the phase, or the milestone (a \`text\` line, a \`badge\`, or a \`row\` of accent-colored beats). Pick the anchor type that fits the dream's domain.
1428
- - Vary the accent color across posters in the carousel. Suggested palette: \`#10B981\`, \`#7C3AED\`, \`#3B82F6\`, \`#F59E0B\`, \`#EC4899\`, \`#14B8A6\`.
1429
-
1430
- #### Metadata rules (every poster must carry all four)
1431
-
1432
- - \`goal:\` — ONE sentence in the user's voice naming the actual aspiration. References at least one concrete element from USER DATA (a quoted phrase, a number, a place, a domain term). Never write \`Vision poster — {slug}\` or any meta-label — that fails the platform's goal-validation rule (it could have been written before the work).
1433
- - \`detail:\` — markdown body that earns the poster a place on the paywall. Required sections: a short "Why this matters" paragraph grounded in the user's data; a quote block with 1-2 verbatim phrases from USER DATA; a list of the 2-3 measurable signals (proxies) that would tell a specialist agent the user is on track, each tied to a real database table or habit; one named near-term milestone within the followup window.
1434
- - \`followup:\` — duration scaled to the dream's natural rhythm: body/habit dreams 14-30d, money/career 30-90d, place/identity 90-180d. The instruction MUST name which proxies to query, the threshold separating "on track" from "drift", and the resulting action (push an update, reframe, or stay silent). \`Refresh this vision if data has shifted\` is forbidden — it could have been written before the work.
1435
- - \`context:\` — JSON pointers the followup needs: domain tag, the user's quoted phrases that drove this poster, and \`{name, source}\` entries for each proxy so the next invocation knows where to read fresh data. Lookup keys only, no cached values.
1436
-
1437
- #### Universal rules
1438
-
1439
- - **Variety:** across the carousel, do not reuse the same anchor element type twice. If two posters end on a \`badge\`, rework one to use a \`row\` of beats or a \`text\` anchor. Composition variety is what stops the dashboard reading as a template grid.
1440
- - **Goal & followup validation:** before pushing each poster, re-read its \`goal:\` and \`followup:\` lines and ask "could I have written these EXACT words before reading USER DATA?" If yes, rewrite — they're templates, not work product.
1441
- - **Language consistency:** detect the primary language the user writes in from the data. Write ALL posters in that single language. Never mix languages across the carousel.
1442
- - **Quality bar:** better to push 2 strong posters than 4 mixed-quality ones. If you cannot find a real, named, repeated dream for a domain, SKIP that domain. Never invent. Never fill a slot with weather data, agent infrastructure, or generic placeholder language. If a poster names tools/agents instead of outcomes, it is wrong — drop it.
1443
-
1444
- After all pushes, respond \`done\` and STOP. If you cannot find signal, respond \`NO_SIGNAL\` alone on its own line and STOP.
1445
-
1446
- ### \`[system:ask-dream]\`
1447
-
1448
- 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.
1449
-
1450
- \`\`\`
1451
- surface awaiting-dream-input input
1452
- card
1453
- column
1454
- text "Tell me one dream or goal you want to track" h4
1455
- button "Body & health" action=choice
1456
- button "Money & work" action=choice
1457
- button "Place & life chapter" action=choice
1458
- button "Craft or identity" action=choice
1459
- goal: Capture the user's dream so the dashboard can synthesize a vision poster
1460
- followup: +30m "User did not respond to dream-ask. Say done."
1461
- \`\`\`
1462
-
1463
- Translate labels into the user's locale if known. Do not vary the surfaceId \`awaiting-dream-input\` — the state machine observes it.
1464
-
1465
- ### \`[system:dismiss-requested] surfaceId=vision-*\`
1466
-
1467
- User dismissed a vision poster. Push the fixed dismiss-alt surface below. Wording is fixed: do not paraphrase, translate, or reorder buttons.
1468
-
1469
- \`\`\`
1470
- surface dismiss-alt-vision-{slug} input
1471
- card
1472
- column
1473
- text "Before I remove this:" h4
1474
- button "This isn't my dream anymore" action=choice
1475
- button "Wrong framing" action=choice
1476
- button "Already achieved" action=choice
1477
- button "Don't show on dashboard" action=choice
1478
- button "Remove it" action=choice
1479
- goal: Capture vision dismiss reason for vision-{slug}
1480
- followup: +1m "User did not respond to vision dismiss alternatives. Say done."
1481
- \`\`\`
1482
-
1483
- After pushing, respond \`done\`.
1133
+ Read all workspace files + \`agentlife.quality\` + \`agentlife.trace\`. Targeted edits only — don't rewrite unless fundamentally broken. If config is missing \`tools\`, call \`agentlife.createAgent\` with the existing id + patch. Summarize in a widget.
1484
1134
 
1485
- When \`[action:choice]\` arrives on \`dismiss-alt-vision-{slug}\`, append one line to your feedback memory via exec, then \`done\`:
1135
+ ## Deleting an Agent
1486
1136
 
1487
- \`\`\`
1488
- exec mkdir -p ~/.openclaw/workspace-agentlife-vision/memory && \\
1489
- echo "- $(date -u +%Y-%m-%dT%H:%M:%SZ) vision-{slug}: {reason}" \\
1490
- >> ~/.openclaw/workspace-agentlife-vision/memory/vision-feedback.md
1491
- \`\`\`
1137
+ \`agentlife.deleteAgent: {"id":"{agentId}"}\` then optionally \`exec rm -rf ~/.openclaw/workspace-{agentId}\`. Push a confirmation widget.
1492
1138
 
1493
- This file is injected as "Previously rejected dreams" into the next \`[system:dashboard-bootstrap]\` so future synthesis avoids the same misreads.
1139
+ ## Registry Enrichment (system task)
1494
1140
 
1495
- 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\`.
1141
+ For each agent in the list: read its workspace \`AGENTS.md\` + \`SOUL.md\`, write a one-sentence description via \`agentlife.createAgent\`, respond "Done". No widgets.
1496
1142
 
1497
1143
  ## What You Are Not
1498
1144
 
1499
- - Not a builderagent creation and improvement belong to \`agentlife-builder\`
1500
- - Not an orchestrator — you never route or answer user questions
1501
- - Not a chat agent — you produce widgets or \`NO_SIGNAL\`/\`done\` markers only
1145
+ - Not an orchestratoryou don't route user messages.
1146
+ - Not a specialist — you don't run domains; you create the agents that do.
1502
1147
  `;
1503
1148
  var SUPERVISOR_AGENTS_MD = `# Dashboard Supervisor
1504
1149
 
@@ -1602,12 +1247,6 @@ var PROVISIONED_AGENTS = [
1602
1247
  agentsMd: BUILDER_AGENTS_MD,
1603
1248
  tools: { profile: "full", alsoAllow: ["agentlife_push"] }
1604
1249
  },
1605
- {
1606
- id: "agentlife-vision",
1607
- name: "AgentLife Vision",
1608
- agentsMd: VISION_AGENTS_MD,
1609
- tools: { profile: "full", alsoAllow: ["agentlife_push"] }
1610
- },
1611
1250
  {
1612
1251
  id: "supervisor",
1613
1252
  name: "Supervisor",
@@ -1670,60 +1309,6 @@ function scheduleEnrichment(state, runtime, targets, log) {
1670
1309
  log("[agentlife] enrichment complete");
1671
1310
  }, 5000);
1672
1311
  }
1673
- async function readAgentMemory(workspace) {
1674
- if (!workspace)
1675
- return "";
1676
- const parts = [];
1677
- const memoryDir = path3.join(workspace, "memory");
1678
- try {
1679
- const index = await fs2.readFile(path3.join(memoryDir, "MEMORY.md"), "utf-8");
1680
- if (index.trim())
1681
- parts.push(`### memory/MEMORY.md
1682
- ${index.trim()}`);
1683
- } catch {}
1684
- try {
1685
- const files = await fs2.readdir(memoryDir);
1686
- const mdFiles = files.filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort().reverse().slice(0, 20);
1687
- for (const file of mdFiles) {
1688
- try {
1689
- const content = await fs2.readFile(path3.join(memoryDir, file), "utf-8");
1690
- if (content.trim())
1691
- parts.push(`### memory/${file}
1692
- ${content.trim().slice(0, 600)}`);
1693
- } catch {}
1694
- }
1695
- } catch {}
1696
- return parts.join(`
1697
-
1698
- `);
1699
- }
1700
- async function gatherAllAgentMemory(state, finalList, skipIds) {
1701
- const sections = [];
1702
- let total = 0;
1703
- for (const agent of finalList) {
1704
- const id = agent?.id;
1705
- if (!id || skipIds.has(id))
1706
- continue;
1707
- const workspace = agent.workspace || "";
1708
- if (!workspace)
1709
- continue;
1710
- const entry = state.agentRegistry.get(id);
1711
- const memory = await readAgentMemory(workspace);
1712
- if (!memory)
1713
- continue;
1714
- const capped = memory.slice(0, 2000);
1715
- sections.push(`# ${entry?.name || id} (${id})
1716
- ${entry?.description || ""}
1717
-
1718
- ${capped}`);
1719
- total += capped.length;
1720
- }
1721
- return { totalChars: total, sections: sections.join(`
1722
-
1723
- ---
1724
-
1725
- `) };
1726
- }
1727
1312
  async function provisionAgents(state, cfg, runtime, log) {
1728
1313
  const home = os.homedir();
1729
1314
  const currentList = [...cfg.agents?.list ?? []];
@@ -1955,590 +1540,6 @@ async function provisionAgents(state, cfg, runtime, log) {
1955
1540
  log("[agentlife] agent provisioning complete");
1956
1541
  }
1957
1542
 
1958
- // cold-start.ts
1959
- import * as fs3 from "node:fs/promises";
1960
- import * as nodeFs from "node:fs";
1961
- import * as os2 from "node:os";
1962
- import * as path4 from "node:path";
1963
- var DEADLINE_MS = {
1964
- AWAITING_AGENT: 30 * 60 * 1000,
1965
- AWAITING_BASELINE: 30 * 60 * 1000,
1966
- SYNTHESIZING: 3 * 60 * 1000,
1967
- AWAITING_DREAM: 30 * 60 * 1000
1968
- };
1969
- var VISION_MIN_MEMORY_CHARS = 200;
1970
- var PHASE_BLANK = "BLANK";
1971
- function readState(state) {
1972
- const db = getOrCreateHistoryDb(state);
1973
- const row = db.prepare("SELECT * FROM cold_start_state WHERE id = 1").get();
1974
- if (!row)
1975
- return null;
1976
- let warnings = [];
1977
- if (row.contractWarnings) {
1978
- try {
1979
- warnings = JSON.parse(row.contractWarnings);
1980
- } catch {}
1981
- }
1982
- return {
1983
- phase: row.phase,
1984
- enteredAt: row.enteredAt,
1985
- lastActionAt: row.lastActionAt ?? null,
1986
- ackAt: row.ackAt ?? null,
1987
- deadlineAt: row.deadlineAt ?? null,
1988
- retryCount: row.retryCount ?? 0,
1989
- failureReason: row.failureReason ?? null,
1990
- lastTrigger: row.lastTrigger ?? null,
1991
- actionSessionKey: row.actionSessionKey ?? null,
1992
- contractWarnings: warnings,
1993
- pollerLastTickAt: row.pollerLastTickAt ?? null,
1994
- updatedAt: row.updatedAt
1995
- };
1996
- }
1997
- function ensureInitialRow(state) {
1998
- const existing = readState(state);
1999
- if (existing)
2000
- return existing;
2001
- const now = Date.now();
2002
- const db = getOrCreateHistoryDb(state);
2003
- db.prepare(`
2004
- INSERT OR IGNORE INTO cold_start_state
2005
- (id, phase, enteredAt, retryCount, contractWarnings, updatedAt)
2006
- VALUES (1, ?, ?, 0, '[]', ?)
2007
- `).run(PHASE_BLANK, now, now);
2008
- return readState(state);
2009
- }
2010
- function writeState(state, partial) {
2011
- const current = ensureInitialRow(state);
2012
- const next = { ...current, ...partial, updatedAt: Date.now() };
2013
- const db = getOrCreateHistoryDb(state);
2014
- db.prepare(`
2015
- UPDATE cold_start_state SET
2016
- phase = ?,
2017
- enteredAt = ?,
2018
- lastActionAt = ?,
2019
- ackAt = ?,
2020
- deadlineAt = ?,
2021
- retryCount = ?,
2022
- failureReason = ?,
2023
- lastTrigger = ?,
2024
- actionSessionKey = ?,
2025
- contractWarnings = ?,
2026
- pollerLastTickAt = ?,
2027
- updatedAt = ?
2028
- WHERE id = 1
2029
- `).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);
2030
- return next;
2031
- }
2032
- function emitTrigger(state, kind, payload) {
2033
- if (state.disabled)
2034
- return;
2035
- try {
2036
- const db = getOrCreateHistoryDb(state);
2037
- db.prepare(`
2038
- INSERT INTO cold_start_triggers (kind, payload, createdAt) VALUES (?, ?, ?)
2039
- `).run(kind, JSON.stringify(payload), Date.now());
2040
- } catch (e) {
2041
- console.warn("[cold-start] emitTrigger failed:", e?.message);
2042
- }
2043
- }
2044
- function drainTriggers(state, limit = 50) {
2045
- const db = getOrCreateHistoryDb(state);
2046
- const rows = db.prepare("SELECT id, kind, payload FROM cold_start_triggers ORDER BY createdAt ASC LIMIT ?").all(limit);
2047
- if (rows.length === 0)
2048
- return [];
2049
- const ids = rows.map((r) => r.id);
2050
- const placeholders = ids.map(() => "?").join(",");
2051
- db.prepare(`DELETE FROM cold_start_triggers WHERE id IN (${placeholders})`).run(...ids);
2052
- return rows.map((r) => ({
2053
- kind: r.kind,
2054
- payload: safeJson(r.payload)
2055
- }));
2056
- }
2057
- function safeJson(s) {
2058
- try {
2059
- return JSON.parse(s);
2060
- } catch {
2061
- return {};
2062
- }
2063
- }
2064
- function purgeStaleTriggers(state) {
2065
- const db = getOrCreateHistoryDb(state);
2066
- const cutoff = Date.now() - 60 * 60 * 1000;
2067
- const r = db.prepare("DELETE FROM cold_start_triggers WHERE createdAt < ?").run(cutoff);
2068
- return r.changes;
2069
- }
2070
- function pendingTriggerCount(state) {
2071
- const db = getOrCreateHistoryDb(state);
2072
- const row = db.prepare("SELECT COUNT(*) as c FROM cold_start_triggers").get();
2073
- return row?.c ?? 0;
2074
- }
2075
- var PROVISIONED_IDS = new Set(PROVISIONED_AGENTS.filter((a) => !a.existingAgent).map((a) => a.id));
2076
- function listSpecialistIds(state, runtime) {
2077
- const cfg = runtime.config.loadConfig();
2078
- const list = cfg?.agents?.list ?? [];
2079
- return list.map((a) => a?.id).filter((id) => !!id && !PROVISIONED_IDS.has(id));
2080
- }
2081
- async function specialistMemoryTotal(state, runtime) {
2082
- const cfg = runtime.config.loadConfig();
2083
- const list = cfg?.agents?.list ?? [];
2084
- const { totalChars, sections } = await gatherAllAgentMemory(state, list, PROVISIONED_IDS);
2085
- return { total: totalChars, sections };
2086
- }
2087
- function visionSurfaceCount(state) {
2088
- if (!state.surfaceDb)
2089
- return 0;
2090
- let n = 0;
2091
- for (const id of state.surfaceDb.keys())
2092
- if (id.startsWith("vision-"))
2093
- n++;
2094
- return n;
2095
- }
2096
- async function anySpecialistHasBaseline(state, runtime) {
2097
- const cfg = runtime.config.loadConfig();
2098
- const list = cfg?.agents?.list ?? [];
2099
- for (const agent of list) {
2100
- const id = agent?.id;
2101
- if (!id || PROVISIONED_IDS.has(id))
2102
- continue;
2103
- const workspace = agent.workspace || "";
2104
- if (!workspace)
2105
- continue;
2106
- try {
2107
- const content = await fs3.readFile(path4.join(workspace, "memory", "baseline.md"), "utf-8");
2108
- if (content.trim().length > 0)
2109
- return true;
2110
- } catch {}
2111
- }
2112
- return false;
2113
- }
2114
- function specialistHasAnySurface(state, specialistId) {
2115
- if (!state.surfaceDb)
2116
- return false;
2117
- for (const id of state.surfaceDb.keys()) {
2118
- if (state.surfaceDb.getAgentId(id) === specialistId)
2119
- return true;
2120
- }
2121
- return false;
2122
- }
2123
- function specialistIdFromSessionKey(sessionKey) {
2124
- if (!sessionKey)
2125
- return null;
2126
- const parts = sessionKey.split(":");
2127
- if (parts.length < 2 || parts[0] !== "agent")
2128
- return null;
2129
- const id = parts[1];
2130
- return id && id.length > 0 ? id : null;
2131
- }
2132
- async function sendSystemMessage(state, agentId, message, idempotencyKey, log) {
2133
- if (!state.runCommand) {
2134
- log(`[cold-start] sendSystemMessage ${idempotencyKey}: skipped — runCommand not available`);
2135
- return null;
2136
- }
2137
- const sessionKey = buildAgentSessionKey(agentId);
2138
- const chatParams = JSON.stringify({ sessionKey, message, idempotencyKey });
2139
- state.runCommand(["openclaw", "gateway", "call", "chat.send", "--params", chatParams], { timeoutMs: 120000 }).then(() => {
2140
- log(`[cold-start] sent ${idempotencyKey} → ${sessionKey}`);
2141
- }).catch((e) => {
2142
- log(`[cold-start] sendSystemMessage ${idempotencyKey} failed: ${e?.message}`);
2143
- });
2144
- return sessionKey;
2145
- }
2146
- async function composeBootstrapMessage(state, runtime, userDream) {
2147
- const { sections } = await specialistMemoryTotal(state, runtime);
2148
- const rejected = await readRejectedDreams();
2149
- const parts = ["[system:dashboard-bootstrap]", "", "## USER DATA", "", sections.slice(0, 12000)];
2150
- if (rejected)
2151
- parts.push("", "## Previously rejected dreams", "", rejected);
2152
- if (userDream)
2153
- parts.push("", "## userDream", "", userDream);
2154
- return parts.join(`
2155
- `);
2156
- }
2157
- async function readRejectedDreams() {
2158
- const visionWorkspace = path4.join(os2.homedir(), ".openclaw", "workspace-agentlife-vision");
2159
- const feedbackFile = path4.join(visionWorkspace, "memory", "vision-feedback.md");
2160
- try {
2161
- const content = await fs3.readFile(feedbackFile, "utf-8");
2162
- return content.trim().slice(0, 4000);
2163
- } catch {
2164
- return "";
2165
- }
2166
- }
2167
- async function computeStartingPhase(state, runtime) {
2168
- if (visionSurfaceCount(state) > 0) {
2169
- return { phase: "READY", details: "vision surfaces already exist" };
2170
- }
2171
- const specialists = listSpecialistIds(state, runtime);
2172
- if (specialists.length === 0) {
2173
- return { phase: "BLANK", details: "no specialists" };
2174
- }
2175
- if (await anySpecialistHasBaseline(state, runtime)) {
2176
- return { phase: "SYNTHESIZING", details: "baseline.md present" };
2177
- }
2178
- const { total } = await specialistMemoryTotal(state, runtime);
2179
- if (total < VISION_MIN_MEMORY_CHARS) {
2180
- return { phase: "AWAITING_BASELINE", details: `totalChars=${total} below ${VISION_MIN_MEMORY_CHARS}` };
2181
- }
2182
- return { phase: "SYNTHESIZING", details: `totalChars=${total} ready for synthesis` };
2183
- }
2184
- async function transition(state, runtime, log, trigger) {
2185
- const cur = ensureInitialRow(state);
2186
- log(`[cold-start] trigger=${trigger.kind} phase=${cur.phase}`);
2187
- switch (trigger.kind) {
2188
- case "provisioned":
2189
- case "userRetried": {
2190
- const { phase, details } = await computeStartingPhase(state, runtime);
2191
- log(`[cold-start] recompute → ${phase} (${details})`);
2192
- return enterPhase(state, runtime, log, phase, {
2193
- bumpRetry: trigger.kind === "userRetried"
2194
- });
2195
- }
2196
- case "agentRegistered": {
2197
- if (cur.phase === "BLANK" || cur.phase === "AWAITING_AGENT") {
2198
- return enterPhase(state, runtime, log, "AWAITING_BASELINE", {
2199
- specialistId: trigger.payload.agentId
2200
- });
2201
- }
2202
- return cur;
2203
- }
2204
- case "agentDeleted": {
2205
- const remaining = trigger.payload.remaining;
2206
- if (remaining === 0) {
2207
- return enterPhase(state, runtime, log, "BLANK");
2208
- }
2209
- return cur;
2210
- }
2211
- case "memoryWritten": {
2212
- if (cur.phase !== "AWAITING_BASELINE")
2213
- return cur;
2214
- if (await anySpecialistHasBaseline(state, runtime)) {
2215
- return enterPhase(state, runtime, log, "SYNTHESIZING");
2216
- }
2217
- const { total } = await specialistMemoryTotal(state, runtime);
2218
- if (total >= VISION_MIN_MEMORY_CHARS) {
2219
- return enterPhase(state, runtime, log, "SYNTHESIZING");
2220
- }
2221
- log(`[cold-start] memory still thin: ${total}/${VISION_MIN_MEMORY_CHARS}`);
2222
- return cur;
2223
- }
2224
- case "surfacePushed": {
2225
- return cur;
2226
- }
2227
- case "agentReplied": {
2228
- const sessionKey = trigger.payload.sessionKey;
2229
- const marker = trigger.payload.marker;
2230
- const lastText = trigger.payload.lastText ?? "";
2231
- if (cur.actionSessionKey && sessionKey && sessionKey !== cur.actionSessionKey) {
2232
- return cur;
2233
- }
2234
- if (cur.phase === "SYNTHESIZING") {
2235
- if (marker === "NO_SIGNAL" || /\bNO_SIGNAL\b/.test(lastText)) {
2236
- return enterPhase(state, runtime, log, "AWAITING_DREAM");
2237
- }
2238
- if (visionSurfaceCount(state) >= 1) {
2239
- return enterPhase(state, runtime, log, "READY");
2240
- }
2241
- if (lastText.length === 0) {
2242
- log(`[cold-start] agentlife-vision replied empty in SYNTHESIZING — model error or silent fail`);
2243
- return enterFailed(state, log, "vision_agent_empty_reply");
2244
- }
2245
- return cur;
2246
- }
2247
- if (cur.phase === "AWAITING_BASELINE") {
2248
- if (await anySpecialistHasBaseline(state, runtime)) {
2249
- return enterPhase(state, runtime, log, "SYNTHESIZING");
2250
- }
2251
- const specialistId = specialistIdFromSessionKey(cur.actionSessionKey);
2252
- if (specialistId && !specialistHasAnySurface(state, specialistId)) {
2253
- log(`[cold-start] ${specialistId} replied in AWAITING_BASELINE with no surface ` + `attributed and no baseline.md written — [system:start] contract violation`);
2254
- return enterFailed(state, log, "agent_produced_no_surface");
2255
- }
2256
- return cur;
2257
- }
2258
- return cur;
2259
- }
2260
- case "userAnswered": {
2261
- const surfaceId = trigger.payload.surfaceId;
2262
- const answer = trigger.payload.answer ?? "";
2263
- if (cur.phase === "AWAITING_DREAM" && surfaceId === "awaiting-dream-input") {
2264
- return enterPhase(state, runtime, log, "SYNTHESIZING", { userDream: answer });
2265
- }
2266
- return cur;
2267
- }
2268
- case "deadlineElapsed": {
2269
- const elapsedPhase = trigger.payload.phase;
2270
- if (elapsedPhase !== cur.phase)
2271
- return cur;
2272
- const reason = `${cur.phase.toLowerCase()}_timeout`;
2273
- return enterFailed(state, log, reason);
2274
- }
2275
- }
2276
- }
2277
- async function enterPhase(state, runtime, log, next, opts = {}) {
2278
- const cur = ensureInitialRow(state);
2279
- const now = Date.now();
2280
- const deadlineMs = DEADLINE_MS[next];
2281
- const deadlineAt = deadlineMs ? now + deadlineMs : null;
2282
- const retryCount = opts.bumpRetry ? cur.retryCount + 1 : cur.retryCount;
2283
- let actionSessionKey = null;
2284
- let lastActionAt = null;
2285
- writeState(state, {
2286
- phase: next,
2287
- enteredAt: now,
2288
- deadlineAt,
2289
- retryCount,
2290
- failureReason: next === "FAILED" ? cur.failureReason : null,
2291
- actionSessionKey: null,
2292
- lastActionAt: null,
2293
- ackAt: null
2294
- });
2295
- log(`[cold-start] enter ${next} (deadline=${deadlineAt ? new Date(deadlineAt).toISOString() : "none"}, retry=${retryCount})`);
2296
- if (next === "BLANK") {
2297
- const idem = `cold-start-onboarding-${now}-${retryCount}`;
2298
- const msg = [
2299
- "[system:onboarding]",
2300
- "",
2301
- "User just installed AgentLife and has zero specialist agents. Push the",
2302
- "onboarding-domain input surface EXACTLY as specified in the [system:onboarding]",
2303
- "section of your AGENTS.md, then respond `done`. Do NOT ask the user questions",
2304
- "as chat text — only via the input surface. Do NOT create any specialist agent",
2305
- "yet; wait for the user's choice on the surface."
2306
- ].join(`
2307
- `);
2308
- actionSessionKey = await sendSystemMessage(state, "agentlife-builder", msg, idem, log);
2309
- lastActionAt = Date.now();
2310
- writeState(state, {
2311
- phase: "AWAITING_AGENT",
2312
- enteredAt: now,
2313
- deadlineAt: now + (DEADLINE_MS.AWAITING_AGENT ?? 0),
2314
- actionSessionKey,
2315
- lastActionAt
2316
- });
2317
- scheduleDeadline(state, runtime, log, "AWAITING_AGENT", now + (DEADLINE_MS.AWAITING_AGENT ?? 0));
2318
- return readState(state);
2319
- }
2320
- if (next === "AWAITING_BASELINE") {
2321
- const specialistId = opts.specialistId ?? listSpecialistIds(state, runtime)[0];
2322
- if (!specialistId) {
2323
- log("[cold-start] AWAITING_BASELINE has no specialist to warm up — falling back to BLANK");
2324
- return enterPhase(state, runtime, log, "BLANK");
2325
- }
2326
- const idem = `cold-start-start-${specialistId}-${now}-${retryCount}`;
2327
- const msg = [
2328
- "[system:start]",
2329
- "",
2330
- "You were just created by the builder. You have zero memory about this user.",
2331
- "Follow the [system:start] section of your AGENTS.md: push a loading widget",
2332
- "introducing yourself per IDENTITY.md, then push ONE warmup-* input surface",
2333
- "asking the most important baseline question for your domain, then respond",
2334
- "`done`. Do NOT ask multiple questions in chat text — only via input surfaces."
2335
- ].join(`
2336
- `);
2337
- actionSessionKey = await sendSystemMessage(state, specialistId, msg, idem, log);
2338
- lastActionAt = Date.now();
2339
- }
2340
- if (next === "SYNTHESIZING") {
2341
- const msg = await composeBootstrapMessage(state, runtime, opts.userDream);
2342
- const idem = `cold-start-bootstrap-${now}-${retryCount}`;
2343
- actionSessionKey = await sendSystemMessage(state, "agentlife-vision", msg, idem, log);
2344
- lastActionAt = Date.now();
2345
- }
2346
- if (next === "AWAITING_DREAM") {
2347
- const idem = `cold-start-ask-dream-${now}-${retryCount}`;
2348
- const msg = [
2349
- "[system:ask-dream]",
2350
- "",
2351
- "Bootstrap reported NO_SIGNAL — the user's data has no aspirational content yet.",
2352
- "Push the awaiting-dream-input input surface EXACTLY as specified in the",
2353
- "[system:ask-dream] section of your AGENTS.md, then respond `done`. Do NOT push",
2354
- "any vision posters from this turn — the plugin re-fires [system:dashboard-bootstrap]",
2355
- "with userDream: injected after the user answers."
2356
- ].join(`
2357
- `);
2358
- actionSessionKey = await sendSystemMessage(state, "agentlife-vision", msg, idem, log);
2359
- lastActionAt = Date.now();
2360
- }
2361
- writeState(state, { actionSessionKey, lastActionAt });
2362
- if (deadlineAt)
2363
- scheduleDeadline(state, runtime, log, next, deadlineAt);
2364
- return readState(state);
2365
- }
2366
- function enterFailed(state, log, reason) {
2367
- log(`[cold-start] FAILED: ${reason}`);
2368
- return writeState(state, {
2369
- phase: "FAILED",
2370
- enteredAt: Date.now(),
2371
- failureReason: reason,
2372
- deadlineAt: null,
2373
- actionSessionKey: null
2374
- });
2375
- }
2376
- var deadlineTimers = new Map;
2377
- function scheduleDeadline(state, runtime, log, phase, deadlineAt) {
2378
- const key = `${phase}-${deadlineAt}`;
2379
- for (const [k, t] of deadlineTimers.entries()) {
2380
- if (k.startsWith(`${phase}-`)) {
2381
- clearTimeout(t);
2382
- deadlineTimers.delete(k);
2383
- }
2384
- }
2385
- const remaining = Math.max(0, deadlineAt - Date.now());
2386
- const timer = setTimeout(() => {
2387
- deadlineTimers.delete(key);
2388
- emitTrigger(state, "deadlineElapsed", { phase });
2389
- }, remaining);
2390
- deadlineTimers.set(key, timer);
2391
- log(`[cold-start] scheduled ${phase} deadline in ${Math.round(remaining / 1000)}s`);
2392
- }
2393
- function rearmDeadlines(state, runtime, log) {
2394
- const cur = ensureInitialRow(state);
2395
- if (cur.deadlineAt && cur.phase !== "READY" && cur.phase !== "FAILED") {
2396
- if (cur.deadlineAt <= Date.now()) {
2397
- emitTrigger(state, "deadlineElapsed", { phase: cur.phase });
2398
- } else {
2399
- scheduleDeadline(state, runtime, log, cur.phase, cur.deadlineAt);
2400
- }
2401
- }
2402
- }
2403
- var pollerTimer = null;
2404
- var POLLER_INTERVAL_MS = 1000;
2405
- var POLLER_STUCK_THRESHOLD_MS = 30000;
2406
- var machineRuntime = null;
2407
- var machineLog = null;
2408
- var machineState = null;
2409
- function startPoller(state, runtime, log) {
2410
- if (pollerTimer)
2411
- return;
2412
- let lastDrainAt = Date.now();
2413
- let lastWarnedAt = 0;
2414
- pollerTimer = setInterval(async () => {
2415
- if (state.disabled)
2416
- return;
2417
- try {
2418
- const triggers = drainTriggers(state, 50);
2419
- const now = Date.now();
2420
- writeState(state, { pollerLastTickAt: now });
2421
- if (triggers.length > 0) {
2422
- lastDrainAt = now;
2423
- for (const trigger of triggers) {
2424
- try {
2425
- await transition(state, runtime, log, trigger);
2426
- } catch (e) {
2427
- log(`[cold-start] transition error for ${trigger.kind}: ${e?.message ?? e}`);
2428
- }
2429
- }
2430
- purgeStaleTriggers(state);
2431
- } else {
2432
- const pending = pendingTriggerCount(state);
2433
- if (pending > 0 && now - lastDrainAt > POLLER_STUCK_THRESHOLD_MS && now - lastWarnedAt > POLLER_STUCK_THRESHOLD_MS) {
2434
- log(`[cold-start] WARNING: poller has not drained ${pending} pending trigger(s) for ${Math.round((now - lastDrainAt) / 1000)}s`);
2435
- lastWarnedAt = now;
2436
- }
2437
- }
2438
- } catch (e) {
2439
- log(`[cold-start] poller tick error: ${e?.message ?? e}`);
2440
- }
2441
- }, POLLER_INTERVAL_MS);
2442
- }
2443
- function stopPoller() {
2444
- if (pollerTimer) {
2445
- clearInterval(pollerTimer);
2446
- pollerTimer = null;
2447
- }
2448
- }
2449
- function runRuntimeContractProbe(state, runtime, log) {
2450
- const PLUGIN_REGISTERED_TOOLS = ["agentlife_push"];
2451
- let list = [];
2452
- try {
2453
- const raw = nodeFs.readFileSync(path4.join(os2.homedir(), ".openclaw", "openclaw.json"), "utf-8");
2454
- const parsed = JSON.parse(raw);
2455
- list = parsed?.agents?.list ?? [];
2456
- } catch {
2457
- list = runtime.config.loadConfig()?.agents?.list ?? [];
2458
- }
2459
- const warnings = [];
2460
- for (const provisioned of PROVISIONED_AGENTS) {
2461
- if (provisioned.existingAgent)
2462
- continue;
2463
- const live = list.find((a) => a?.id === provisioned.id);
2464
- const liveTools = live?.tools ?? {};
2465
- const allow = Array.isArray(liveTools.allow) ? liveTools.allow : [];
2466
- const alsoAllow = Array.isArray(liveTools.alsoAllow) ? liveTools.alsoAllow : [];
2467
- const deny = Array.isArray(liveTools.deny) ? liveTools.deny : [];
2468
- for (const tool of PLUGIN_REGISTERED_TOOLS) {
2469
- const escaped = tool.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
2470
- const re = new RegExp("`[^`]*\\b" + escaped + "\\b[^`]*`");
2471
- if (!re.test(provisioned.agentsMd))
2472
- continue;
2473
- const granted = (allow.includes(tool) || alsoAllow.includes(tool)) && !deny.includes(tool);
2474
- if (!granted) {
2475
- const w = `precondition_tool_missing:${provisioned.id}:${tool}`;
2476
- warnings.push(w);
2477
- log(`[cold-start] contract warning: ${w}`);
2478
- }
2479
- }
2480
- }
2481
- return warnings;
2482
- }
2483
- async function initColdStartMachine(state, runtime, log) {
2484
- machineState = state;
2485
- machineRuntime = runtime;
2486
- machineLog = log;
2487
- ensureInitialRow(state);
2488
- const warnings = runRuntimeContractProbe(state, runtime, log);
2489
- writeState(state, { contractWarnings: warnings });
2490
- rearmDeadlines(state, runtime, log);
2491
- startPoller(state, runtime, log);
2492
- const cur = readState(state);
2493
- if (cur.phase === "READY" && visionSurfaceCount(state) === 0) {
2494
- log("[cold-start] READY with 0 vision surfaces — demoting to FAILED(ready_no_surfaces) on init");
2495
- enterFailed(state, log, "ready_no_surfaces");
2496
- log("[cold-start] machine initialized");
2497
- return;
2498
- }
2499
- if (cur.phase !== "BLANK") {
2500
- log(`[cold-start] restart recovery — preserving persisted phase=${cur.phase} (no provisioned re-fire)`);
2501
- log("[cold-start] machine initialized");
2502
- return;
2503
- }
2504
- emitTrigger(state, "provisioned", { source: "init" });
2505
- log("[cold-start] machine initialized");
2506
- }
2507
- function shutdownColdStartMachine() {
2508
- stopPoller();
2509
- for (const t of deadlineTimers.values())
2510
- clearTimeout(t);
2511
- deadlineTimers.clear();
2512
- }
2513
- function observeColdStart(state) {
2514
- const s = ensureInitialRow(state);
2515
- return {
2516
- phase: s.phase,
2517
- failureReason: s.failureReason,
2518
- retryCount: s.retryCount,
2519
- contractWarnings: s.contractWarnings,
2520
- enteredAt: s.enteredAt,
2521
- deadlineAt: s.deadlineAt
2522
- };
2523
- }
2524
- async function retryColdStart(state) {
2525
- emitTrigger(state, "userRetried", { ts: Date.now() });
2526
- if (machineRuntime && machineLog) {
2527
- const triggers = drainTriggers(state, 50);
2528
- for (const trigger of triggers) {
2529
- try {
2530
- await transition(state, machineRuntime, machineLog, trigger);
2531
- } catch (e) {
2532
- machineLog(`[cold-start] retry-drain transition error for ${trigger.kind}: ${e?.message ?? e}`);
2533
- }
2534
- }
2535
- }
2536
- return observeColdStart(state);
2537
- }
2538
-
2539
- // services/surfaces-init.ts
2540
- import * as path5 from "node:path";
2541
-
2542
1543
  // activity.ts
2543
1544
  function recordSurfaceEvent(state, surfaceId, event, dsl, agentId, metadata) {
2544
1545
  try {
@@ -2635,8 +1636,8 @@ function broadcastInput(message, sessionKey) {
2635
1636
  }
2636
1637
 
2637
1638
  // dashboard-state.ts
2638
- import { readFileSync as readFileSync3 } from "node:fs";
2639
- import { homedir as homedir3 } from "node:os";
1639
+ import { readFileSync as readFileSync2 } from "node:fs";
1640
+ import { homedir as homedir2 } from "node:os";
2640
1641
  function extractTitleAndDetail(meta) {
2641
1642
  let title = null;
2642
1643
  let detail = null;
@@ -2849,9 +1850,9 @@ ${enhanced.formatted}`;
2849
1850
  const agentFiles = [];
2850
1851
  for (const w of warnings) {
2851
1852
  const aid = w.agentId;
2852
- const filePath = `${homedir3()}/.openclaw/workspace-${aid}/AGENTS.md`;
1853
+ const filePath = `${homedir2()}/.openclaw/workspace-${aid}/AGENTS.md`;
2853
1854
  try {
2854
- const content = readFileSync3(filePath, "utf-8").slice(0, 3000);
1855
+ const content = readFileSync2(filePath, "utf-8").slice(0, 3000);
2855
1856
  agentFiles.push(`### ${aid} AGENTS.md (${w.cnt} warnings)
2856
1857
  ${content}`);
2857
1858
  } catch {}
@@ -3254,11 +2255,185 @@ var KNOWN_METADATA_KEYS = new Set([
3254
2255
  "expireAt",
3255
2256
  "expireHint"
3256
2257
  ]);
2258
+ var INPUT_SURFACE_ACTIONS = new Set([
2259
+ "choice",
2260
+ "select",
2261
+ "toggle",
2262
+ "check",
2263
+ "multichoice"
2264
+ ]);
2265
+ var TEXT_LEVELS = new Set(["h1", "h2", "h3", "h4", "h5", "body", "caption"]);
2266
+ var TEXT_HINTS = new Set(["icon", "avatar", "smallfeature", "mediumfeature", "largefeature", "header"]);
2267
+ var IMG_FITS = new Set(["contain", "cover", "fill", "none", "scale-down"]);
2268
+ var TF_TYPES = new Set(["email", "shorttext", "number", "phone", "longtext", "date", "obscured"]);
2269
+ var ALIGN_VALUES = new Set(["start", "center", "end", "stretch"]);
2270
+ var DISTRIBUTE_VALUES = new Set(["start", "center", "end", "spacebetween", "spacearound", "spaceevenly"]);
2271
+ var TREND_VALUES = new Set(["up", "down", "flat"]);
2272
+ var COMPONENT_CATALOG = {
2273
+ card: { positionalTokens: new Set, attrs: {} },
2274
+ column: {
2275
+ positionalTokens: new Set,
2276
+ attrs: {
2277
+ align: { kind: "enum", values: ALIGN_VALUES },
2278
+ distribute: { kind: "enum", values: DISTRIBUTE_VALUES }
2279
+ }
2280
+ },
2281
+ row: {
2282
+ positionalTokens: new Set,
2283
+ attrs: {
2284
+ align: { kind: "enum", values: ALIGN_VALUES },
2285
+ distribute: { kind: "enum", values: DISTRIBUTE_VALUES }
2286
+ }
2287
+ },
2288
+ list: { positionalTokens: new Set, attrs: {} },
2289
+ divider: { positionalTokens: new Set(["vertical"]), attrs: {} },
2290
+ text: {
2291
+ positionalTokens: TEXT_LEVELS,
2292
+ positionalFree: true,
2293
+ attrs: {
2294
+ color: { kind: "free" },
2295
+ hint: { kind: "enum", values: TEXT_HINTS },
2296
+ align: { kind: "free" }
2297
+ }
2298
+ },
2299
+ metric: {
2300
+ positionalTokens: new Set,
2301
+ positionalFree: true,
2302
+ attrs: {
2303
+ trend: { kind: "enum", values: TREND_VALUES },
2304
+ color: { kind: "free" }
2305
+ }
2306
+ },
2307
+ button: {
2308
+ positionalTokens: new Set(["primary"]),
2309
+ positionalFree: true,
2310
+ attrs: {
2311
+ action: { kind: "free" }
2312
+ }
2313
+ },
2314
+ image: {
2315
+ positionalTokens: new Set,
2316
+ positionalFree: true,
2317
+ attrs: {
2318
+ fit: { kind: "enum", values: IMG_FITS },
2319
+ hint: { kind: "enum", values: TEXT_HINTS }
2320
+ }
2321
+ },
2322
+ icon: { positionalTokens: new Set, positionalFree: true, attrs: {} },
2323
+ textfield: {
2324
+ positionalTokens: new Set,
2325
+ positionalFree: true,
2326
+ attrs: {
2327
+ type: { kind: "enum", values: TF_TYPES },
2328
+ placeholder: { kind: "free" }
2329
+ }
2330
+ },
2331
+ checkbox: {
2332
+ positionalTokens: new Set(["checked"]),
2333
+ positionalFree: true,
2334
+ attrs: {}
2335
+ },
2336
+ sparkline: {
2337
+ positionalTokens: new Set(["filled"]),
2338
+ positionalFree: true,
2339
+ attrs: {
2340
+ height: { kind: "int" },
2341
+ color: { kind: "free" }
2342
+ }
2343
+ },
2344
+ progress: {
2345
+ positionalTokens: new Set,
2346
+ positionalFree: true,
2347
+ attrs: {
2348
+ value: { kind: "number" },
2349
+ color: { kind: "free" }
2350
+ }
2351
+ },
2352
+ pie: {
2353
+ positionalTokens: new Set(["solid"]),
2354
+ positionalFree: true,
2355
+ attrs: {
2356
+ labels: { kind: "array" },
2357
+ colors: { kind: "array" },
2358
+ height: { kind: "int" }
2359
+ }
2360
+ },
2361
+ barchart: {
2362
+ positionalTokens: new Set,
2363
+ positionalFree: true,
2364
+ attrs: {
2365
+ labels: { kind: "array" },
2366
+ colors: { kind: "array" },
2367
+ height: { kind: "int" }
2368
+ }
2369
+ },
2370
+ badge: {
2371
+ positionalTokens: new Set(["outlined"]),
2372
+ positionalFree: true,
2373
+ attrs: {
2374
+ color: { kind: "free" }
2375
+ }
2376
+ },
2377
+ gauge: {
2378
+ positionalTokens: new Set,
2379
+ positionalFree: true,
2380
+ attrs: {
2381
+ value: { kind: "number" },
2382
+ color: { kind: "free" },
2383
+ size: { kind: "int" },
2384
+ text: { kind: "free" }
2385
+ }
2386
+ }
2387
+ };
2388
+ function tokenizeComponentLine(line) {
2389
+ let s = line.replace(/"[^"]*"/g, "");
2390
+ s = s.replace(/\[[^\]]*\]/g, "");
2391
+ return s.trim().split(/\s+/).filter(Boolean);
2392
+ }
2393
+ function validateComponentLine(line) {
2394
+ const unknownAttrs = [];
2395
+ const invalidEnumValues = [];
2396
+ const tokens = tokenizeComponentLine(line);
2397
+ if (tokens.length === 0)
2398
+ return { unknownAttrs, invalidEnumValues };
2399
+ const component = tokens[0];
2400
+ const spec = COMPONENT_CATALOG[component];
2401
+ if (!spec)
2402
+ return { unknownAttrs, invalidEnumValues };
2403
+ for (let i = 1;i < tokens.length; i++) {
2404
+ const tok = tokens[i];
2405
+ const eqIdx = tok.indexOf("=");
2406
+ if (eqIdx <= 0) {
2407
+ continue;
2408
+ }
2409
+ const attr = tok.slice(0, eqIdx);
2410
+ const value = tok.slice(eqIdx + 1);
2411
+ const attrSpec = spec.attrs[attr];
2412
+ if (!attrSpec) {
2413
+ unknownAttrs.push(`${component}.${attr}`);
2414
+ continue;
2415
+ }
2416
+ if (attrSpec.kind === "enum" && attrSpec.values && !attrSpec.values.has(value.toLowerCase())) {
2417
+ invalidEnumValues.push({
2418
+ attr: `${component}.${attr}`,
2419
+ value,
2420
+ allowed: [...attrSpec.values]
2421
+ });
2422
+ }
2423
+ }
2424
+ return { unknownAttrs, invalidEnumValues };
2425
+ }
3257
2426
  function validateBlockDsl(block) {
3258
2427
  const unknownKeywords = [];
3259
2428
  const metadataWithoutColon = [];
2429
+ const invalidInputActions = [];
2430
+ const unknownAttrsSet = new Set;
2431
+ const invalidEnumValues = [];
3260
2432
  let hasCardKeyword = false;
3261
2433
  let inDetailBlock = false;
2434
+ const headerLine = block.split(`
2435
+ `).find((l) => l.trim().startsWith("surface "))?.trim() ?? "";
2436
+ const isInputSurface = /\binput\b/.test(headerLine);
3262
2437
  for (const rawLine of block.split(`
3263
2438
  `)) {
3264
2439
  const trimmed = rawLine.trim();
@@ -3286,6 +2461,24 @@ function validateBlockDsl(block) {
3286
2461
  if (KNOWN_BODY_KEYWORDS.has(keyword)) {
3287
2462
  if (keyword === "card")
3288
2463
  hasCardKeyword = true;
2464
+ if (keyword === "button" && isInputSurface) {
2465
+ const actionMatch = trimmed.match(/\baction=(\S+)/);
2466
+ if (actionMatch) {
2467
+ const actionValue = actionMatch[1].toLowerCase();
2468
+ if (!INPUT_SURFACE_ACTIONS.has(actionValue)) {
2469
+ const labelMatch = trimmed.match(/button\s+"([^"]*)"/);
2470
+ invalidInputActions.push({
2471
+ action: actionMatch[1],
2472
+ label: labelMatch?.[1] ?? ""
2473
+ });
2474
+ }
2475
+ }
2476
+ }
2477
+ const componentCheck = validateComponentLine(trimmed);
2478
+ for (const a of componentCheck.unknownAttrs)
2479
+ unknownAttrsSet.add(a);
2480
+ for (const v of componentCheck.invalidEnumValues)
2481
+ invalidEnumValues.push(v);
3289
2482
  continue;
3290
2483
  }
3291
2484
  unknownKeywords.push(keyword);
@@ -3293,7 +2486,10 @@ function validateBlockDsl(block) {
3293
2486
  return {
3294
2487
  unknownKeywords,
3295
2488
  metadataWithoutColon,
3296
- missingCardStructure: !hasCardKeyword
2489
+ missingCardStructure: !hasCardKeyword,
2490
+ invalidInputActions,
2491
+ unknownAttrs: [...unknownAttrsSet],
2492
+ invalidEnumValues
3297
2493
  };
3298
2494
  }
3299
2495
  function processDslBlock(state, dsl) {
@@ -3402,24 +2598,65 @@ function processDslBlock(state, dsl) {
3402
2598
  goalChanged,
3403
2599
  unknownKeywords: validation.unknownKeywords.length > 0 ? validation.unknownKeywords : undefined,
3404
2600
  metadataWithoutColon: validation.metadataWithoutColon.length > 0 ? validation.metadataWithoutColon : undefined,
3405
- missingCardStructure: validation.missingCardStructure ? true : undefined
2601
+ missingCardStructure: validation.missingCardStructure ? true : undefined,
2602
+ invalidInputActions: validation.invalidInputActions.length > 0 ? validation.invalidInputActions : undefined,
2603
+ unknownAttrs: validation.unknownAttrs.length > 0 ? validation.unknownAttrs : undefined,
2604
+ invalidEnumValues: validation.invalidEnumValues.length > 0 ? validation.invalidEnumValues : undefined
3406
2605
  });
3407
2606
  recordSurfaceEvent(state, sid, existing ? "updated" : "created", block, state.surfaceDb.getAgentId(sid));
3408
- if (!existing) {
3409
- let kind = null;
3410
- if (sid.startsWith("vision-"))
3411
- kind = "vision";
3412
- else if (sid === "awaiting-dream-input")
3413
- kind = "dream-input";
3414
- else if (sid === "onboarding-domain")
3415
- kind = "onboarding";
3416
- if (kind) {
3417
- try {
3418
- emitTrigger(state, "surfacePushed", { surfaceId: sid, kind, agentId: state.surfaceDb.getAgentId(sid) ?? null });
3419
- } catch (e) {
3420
- console.warn("[agentlife] cold-start emit surfacePushed failed: %s", e?.message);
3421
- }
3422
- }
2607
+ }
2608
+ return results;
2609
+ }
2610
+ function pluginPushSurface(state, params) {
2611
+ if (!state.surfaceDb)
2612
+ return [];
2613
+ const blocksPre = params.dsl.split(/\n---(?:\n|$)/).filter((b) => b.trim().length > 0);
2614
+ const incomingInputIds = new Set;
2615
+ for (const block of blocksPre) {
2616
+ const headerLine = block.match(/^surface\s+.+$/m)?.[0] ?? "";
2617
+ if (/\binput\b/.test(headerLine)) {
2618
+ const sid = headerLine.match(/^surface\s+(\S+)/)?.[1];
2619
+ if (sid)
2620
+ incomingInputIds.add(sid);
2621
+ }
2622
+ }
2623
+ let effectiveDsl = params.dsl;
2624
+ if (incomingInputIds.size > 0) {
2625
+ const stale = [];
2626
+ for (const [existingId, meta] of state.surfaceDb.entries()) {
2627
+ if (incomingInputIds.has(existingId))
2628
+ continue;
2629
+ const existingHeader = meta.lines[0] ?? "";
2630
+ if (/\binput\b/.test(existingHeader))
2631
+ stale.push(existingId);
2632
+ }
2633
+ if (stale.length > 0) {
2634
+ console.log("[agentlife] plugin-push auto-deleting stale input(s) %s for %s", stale.join(","), [...incomingInputIds].join(","));
2635
+ effectiveDsl = [...stale.map((s) => `delete ${s}`), params.dsl].join(`
2636
+ ---
2637
+ `);
2638
+ }
2639
+ }
2640
+ const results = processDslBlock(state, effectiveDsl);
2641
+ const blocks = effectiveDsl.split(/\n---(?:\n|$)/).filter((b) => b.trim().length > 0);
2642
+ const surfaceIds = [];
2643
+ for (const block of blocks) {
2644
+ const match = block.match(/^surface\s+(\S+)/m);
2645
+ if (match)
2646
+ surfaceIds.push(match[1]);
2647
+ }
2648
+ for (const sid of surfaceIds) {
2649
+ state.surfaceDb.setAgentId(sid, params.agentId);
2650
+ }
2651
+ broadcastSurface(params.dsl);
2652
+ for (const result of results) {
2653
+ const meta = state.surfaceDb.get(result.surfaceId);
2654
+ if (!meta)
2655
+ continue;
2656
+ if (result.followupRemoved) {
2657
+ removeFollowup(state, result.surfaceId);
2658
+ } else if (meta.followup && (result.followupChanged || result.isNew)) {
2659
+ scheduleFollowup(state, result.surfaceId, meta.followup, params.agentId);
3423
2660
  }
3424
2661
  }
3425
2662
  return results;
@@ -3473,6 +2710,129 @@ function runStartupPurge(state) {
3473
2710
  console.log("[agentlife] surfaces DB: %d persisted%s%s", state.surfaceDb.size, purged > 0 ? ` (purged ${purged} past grace period)` : "", backfilled > 0 ? ` (backfilled ${backfilled} agent mappings from history)` : "");
3474
2711
  }
3475
2712
 
2713
+ // render-widgets.ts
2714
+ var PROVISIONED_IDS = new Set(PROVISIONED_AGENTS.filter((a) => !a.existingAgent).map((a) => a.id));
2715
+ function listSpecialistIds(runtime) {
2716
+ const cfg = runtime.config.loadConfig();
2717
+ const list = cfg?.agents?.list ?? [];
2718
+ return list.map((a) => a?.id).filter((id) => !!id && !PROVISIONED_IDS.has(id));
2719
+ }
2720
+ function recentActivityCount(state, agentId, sinceMs) {
2721
+ if (!state.historyDb)
2722
+ return 0;
2723
+ try {
2724
+ const row = state.historyDb.prepare("SELECT COUNT(*) as c FROM activity_log WHERE agentId = ? AND ts > ?").get(agentId, sinceMs);
2725
+ return row?.c ?? 0;
2726
+ } catch {
2727
+ return 0;
2728
+ }
2729
+ }
2730
+ function pendingFollowupCount(state, agentId) {
2731
+ if (!state.historyDb)
2732
+ return 0;
2733
+ try {
2734
+ const row = state.historyDb.prepare("SELECT COUNT(*) as c FROM followups WHERE agentId = ? AND status = 'pending'").get(agentId);
2735
+ return row?.c ?? 0;
2736
+ } catch {
2737
+ return 0;
2738
+ }
2739
+ }
2740
+ function sanitizeDslString(s) {
2741
+ return s.replace(/"/g, "'").replace(/[\r\n]+/g, " ").slice(0, 120);
2742
+ }
2743
+ function composeAgentIntroDsl(opts) {
2744
+ const surfaceId = `${opts.agentId}-intro`;
2745
+ const title = `${opts.emoji} ${opts.name}`;
2746
+ const desc = sanitizeDslString(opts.description || `Your ${opts.name} agent, ready to track and act.`);
2747
+ const activeBadge = opts.activityCount > 0 ? "Active" : "Ready";
2748
+ const activeColor = opts.activityCount > 0 ? "#10B981" : "#6366F1";
2749
+ return [
2750
+ `surface ${surfaceId} size=m`,
2751
+ ` card`,
2752
+ ` column`,
2753
+ ` text "${sanitizeDslString(title)}" h3`,
2754
+ ` text "${desc}" body`,
2755
+ ` divider`,
2756
+ ` row distribute=spaceBetween`,
2757
+ ` metric "Actions (7d)" "${opts.activityCount}"`,
2758
+ ` metric "Upcoming" "${opts.followupCount}"`,
2759
+ ` badge "${activeBadge}" color=${activeColor} outlined`,
2760
+ `goal: ${opts.name} — track progress toward user's first real outcome`,
2761
+ `followup: +24h "Check if the user interacted with the ${opts.name} agent. If yes, summarize progress. If not, push a gentle prompt relevant to the domain."`,
2762
+ `context: {"agentId":"${opts.agentId}","domain":"${opts.agentId}","phase":"intro","autoRendered":true}`
2763
+ ].join(`
2764
+ `);
2765
+ }
2766
+ function agentEmoji(runtime, agentId) {
2767
+ const cfg = runtime.config.loadConfig();
2768
+ const list = cfg?.agents?.list ?? [];
2769
+ const found = list.find((a) => a?.id === agentId);
2770
+ return found?.identity?.emoji ?? "\uD83C\uDFAF";
2771
+ }
2772
+ function renderAgentWidget(state, runtime, agentId, log) {
2773
+ if (PROVISIONED_IDS.has(agentId))
2774
+ return;
2775
+ const entry = state.agentRegistry.get(agentId);
2776
+ if (!entry) {
2777
+ log(`[render-widgets] ${agentId}: no registry entry, skipping`);
2778
+ return;
2779
+ }
2780
+ const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
2781
+ const dsl = composeAgentIntroDsl({
2782
+ agentId,
2783
+ name: entry.name,
2784
+ description: entry.description ?? "",
2785
+ emoji: agentEmoji(runtime, agentId),
2786
+ activityCount: recentActivityCount(state, agentId, sevenDaysAgo),
2787
+ followupCount: pendingFollowupCount(state, agentId)
2788
+ });
2789
+ pluginPushSurface(state, { agentId, dsl });
2790
+ log(`[render-widgets] rendered ${agentId}-intro`);
2791
+ }
2792
+ function renderAllAgentWidgets(state, runtime, log) {
2793
+ const ids = listSpecialistIds(runtime);
2794
+ if (ids.length === 0) {
2795
+ log("[render-widgets] no user agents — rendering welcome widget");
2796
+ renderWelcomeWidget(state, log);
2797
+ return;
2798
+ }
2799
+ for (const id of ids) {
2800
+ try {
2801
+ renderAgentWidget(state, runtime, id, log);
2802
+ } catch (e) {
2803
+ log(`[render-widgets] ${id} render failed: ${e?.message ?? e}`);
2804
+ }
2805
+ }
2806
+ }
2807
+ var WELCOME_SURFACE_ID = "welcome";
2808
+ function renderWelcomeWidget(state, log) {
2809
+ const dsl = [
2810
+ `surface ${WELCOME_SURFACE_ID} size=m`,
2811
+ ` card`,
2812
+ ` column`,
2813
+ ` text "\uD83D\uDC4B Welcome to Agent Life" h3`,
2814
+ ` text "Type what you want to track or automate — one line, anything. Your first agent builds itself from that intent." body`,
2815
+ ` divider`,
2816
+ ` badge "Ready" color=#6366F1 outlined`,
2817
+ `goal: Welcome the user and guide them to create their first agent`,
2818
+ `followup: +24h "If the user still has no agents, push an encouraging widget inviting them to try again. If agents now exist, this widget will already be deleted."`,
2819
+ `context: {"phase":"welcome","autoRendered":true}`
2820
+ ].join(`
2821
+ `);
2822
+ pluginPushSurface(state, { agentId: "agentlife", dsl });
2823
+ log(`[render-widgets] rendered ${WELCOME_SURFACE_ID}`);
2824
+ }
2825
+ function deleteWelcomeWidget(state, log) {
2826
+ if (!state.surfaceDb?.has(WELCOME_SURFACE_ID))
2827
+ return;
2828
+ state.surfaceDb.delete(WELCOME_SURFACE_ID);
2829
+ broadcastDelete(WELCOME_SURFACE_ID);
2830
+ log(`[render-widgets] deleted ${WELCOME_SURFACE_ID}`);
2831
+ }
2832
+
2833
+ // services/surfaces-init.ts
2834
+ import * as path4 from "node:path";
2835
+
3476
2836
  // cleanup.ts
3477
2837
  function enqueueCleanupTasks(state, surfaceId, agentId, cronId, automations) {
3478
2838
  const db = getOrCreateHistoryDb(state);
@@ -3564,25 +2924,21 @@ function registerSurfacesService(api, state) {
3564
2924
  api.registerService({
3565
2925
  id: "agentlife-surfaces",
3566
2926
  start: async (ctx) => {
3567
- const agentlifeDir = path5.join(ctx.stateDir, "agentlife");
2927
+ const agentlifeDir = path4.join(ctx.stateDir, "agentlife");
3568
2928
  state.agentlifeStateDir = agentlifeDir;
3569
- state.registryFilePath = path5.join(agentlifeDir, "agent-registry.json");
3570
- state.dbBaseDir = path5.join(agentlifeDir, "db");
3571
- state.historyDbPath = path5.join(agentlifeDir, "agentlife.db");
2929
+ state.registryFilePath = path4.join(agentlifeDir, "agent-registry.json");
2930
+ state.dbBaseDir = path4.join(agentlifeDir, "db");
2931
+ state.historyDbPath = path4.join(agentlifeDir, "agentlife.db");
3572
2932
  if (!state.surfaceDb) {
3573
2933
  const db = getOrCreateHistoryDb(state);
3574
2934
  state.surfaceDb = new SurfaceDb(db);
3575
2935
  }
3576
2936
  runStartupPurge(state);
3577
- const FLOW_INPUT_IDS = new Set(["onboarding-domain", "awaiting-dream-input"]);
3578
- const isFlowInput = (id) => FLOW_INPUT_IDS.has(id) || id.startsWith("warmup-") || id.startsWith("dismiss-alt-");
3579
2937
  let inputPurged = 0;
3580
2938
  for (const [surfaceId, meta] of state.surfaceDb.entries()) {
3581
2939
  const headerLine = meta.lines[0] ?? "";
3582
2940
  if (!/\binput\b/.test(headerLine))
3583
2941
  continue;
3584
- if (isFlowInput(surfaceId))
3585
- continue;
3586
2942
  state.surfaceDb.delete(surfaceId);
3587
2943
  inputPurged++;
3588
2944
  }
@@ -3637,22 +2993,22 @@ function registerSurfacesService(api, state) {
3637
2993
  }
3638
2994
 
3639
2995
  // services/config-optimizer.ts
3640
- import * as fs4 from "node:fs/promises";
3641
- import * as path6 from "node:path";
3642
- import * as os3 from "node:os";
2996
+ import * as fs3 from "node:fs/promises";
2997
+ import * as path5 from "node:path";
2998
+ import * as os2 from "node:os";
3643
2999
  var TARGET_MAX_PING_PONG = 1;
3644
3000
  var TARGET_MAX_CONCURRENT = 10;
3645
- var CONFIG_PATH = path6.join(os3.homedir(), ".openclaw", "openclaw.json");
3001
+ var CONFIG_PATH = path5.join(os2.homedir(), ".openclaw", "openclaw.json");
3646
3002
  async function readRawConfigFromDisk() {
3647
- const raw = await fs4.readFile(CONFIG_PATH, "utf-8");
3003
+ const raw = await fs3.readFile(CONFIG_PATH, "utf-8");
3648
3004
  return JSON.parse(raw);
3649
3005
  }
3650
3006
  async function writeRawConfigToDisk(cfg) {
3651
- await fs4.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2) + `
3007
+ await fs3.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2) + `
3652
3008
  `, "utf-8");
3653
3009
  }
3654
3010
  async function restoreConfigFromBackup(api, backupPath) {
3655
- const raw = await fs4.readFile(backupPath, "utf-8");
3011
+ const raw = await fs3.readFile(backupPath, "utf-8");
3656
3012
  const backup = JSON.parse(raw);
3657
3013
  const fileCfg = await readRawConfigFromDisk();
3658
3014
  if (backup.maxPingPongTurns != null) {
@@ -3670,7 +3026,7 @@ async function restoreConfigFromBackup(api, backupPath) {
3670
3026
  delete fileCfg.agents?.defaults?.maxConcurrent;
3671
3027
  }
3672
3028
  await writeRawConfigToDisk(fileCfg);
3673
- await fs4.unlink(backupPath);
3029
+ await fs3.unlink(backupPath);
3674
3030
  console.log("[agentlife] restored config: maxPingPongTurns=%s, maxConcurrent=%s", backup.maxPingPongTurns ?? "default", backup.maxConcurrent ?? "default");
3675
3031
  return `config restored`;
3676
3032
  }
@@ -3678,8 +3034,8 @@ function registerConfigOptimizer(api, _state) {
3678
3034
  api.registerService({
3679
3035
  id: "agentlife-config-optimizer",
3680
3036
  start: async (ctx) => {
3681
- const configBackupDir = path6.join(ctx.stateDir, "agentlife");
3682
- const configBackupPath = path6.join(configBackupDir, "config-backup.json");
3037
+ const configBackupDir = path5.join(ctx.stateDir, "agentlife");
3038
+ const configBackupPath = path5.join(configBackupDir, "config-backup.json");
3683
3039
  const cfg = api.runtime.config.loadConfig();
3684
3040
  const currentPPT = cfg.session?.agentToAgent?.maxPingPongTurns;
3685
3041
  const currentMC = cfg.agents?.defaults?.maxConcurrent;
@@ -3689,8 +3045,8 @@ function registerConfigOptimizer(api, _state) {
3689
3045
  console.log("[agentlife] config already optimized, skipping");
3690
3046
  return;
3691
3047
  }
3692
- await fs4.mkdir(configBackupDir, { recursive: true });
3693
- await fs4.writeFile(configBackupPath, JSON.stringify({
3048
+ await fs3.mkdir(configBackupDir, { recursive: true });
3049
+ await fs3.writeFile(configBackupPath, JSON.stringify({
3694
3050
  maxPingPongTurns: currentPPT ?? null,
3695
3051
  maxConcurrent: currentMC ?? null
3696
3052
  }));
@@ -3715,7 +3071,7 @@ function registerConfigOptimizer(api, _state) {
3715
3071
  },
3716
3072
  stop: async (ctx) => {
3717
3073
  try {
3718
- const backupPath = path6.join(ctx.stateDir, "agentlife", "config-backup.json");
3074
+ const backupPath = path5.join(ctx.stateDir, "agentlife", "config-backup.json");
3719
3075
  await restoreConfigFromBackup(api, backupPath);
3720
3076
  } catch {}
3721
3077
  }
@@ -4166,8 +3522,8 @@ function drainAccumulatorToSurfaces(state, sessionKey, surfaceIds) {
4166
3522
  }
4167
3523
 
4168
3524
  // notifications.ts
4169
- import * as fs5 from "node:fs";
4170
- import * as path7 from "node:path";
3525
+ import * as fs4 from "node:fs";
3526
+ import * as path6 from "node:path";
4171
3527
  var config;
4172
3528
  var recentNotifications = new Map;
4173
3529
  var DEBOUNCE_MS = 60000;
@@ -4175,8 +3531,8 @@ function loadConfig() {
4175
3531
  if (config !== undefined)
4176
3532
  return config;
4177
3533
  try {
4178
- const configPath = path7.join(process.env.HOME ?? "~", ".openclaw", "agentlife", "notification-config.json");
4179
- const raw = fs5.readFileSync(configPath, "utf-8");
3534
+ const configPath = path6.join(process.env.HOME ?? "~", ".openclaw", "agentlife", "notification-config.json");
3535
+ const raw = fs4.readFileSync(configPath, "utf-8");
4180
3536
  const parsed = JSON.parse(raw);
4181
3537
  if (parsed.serverUrl && parsed.apiKey) {
4182
3538
  config = { serverUrl: parsed.serverUrl, apiKey: parsed.apiKey };
@@ -4341,7 +3697,7 @@ function sendToInternalSession(state, agentId, message, idempotencyKey) {
4341
3697
  });
4342
3698
  }
4343
3699
  }
4344
- var SKIP_WIDGET_CHECK = new Set(["agentlife", "agentlife-builder", "agentlife-vision", "supervisor"]);
3700
+ var SKIP_WIDGET_CHECK = new Set(["agentlife", "agentlife-builder", "supervisor"]);
4345
3701
  function registerActivityHooks(api, state) {
4346
3702
  api.on("llm_output", (event, ctx) => {
4347
3703
  if (state.disabled)
@@ -4407,17 +3763,6 @@ function registerActivityHooks(api, state) {
4407
3763
  console.log("[agentlife] action redirected to %s for %s (origin=%s)", actionSessionKey, actionSurfaceId, origin ? "set" : "legacy");
4408
3764
  }
4409
3765
  }
4410
- const dreamMatch = message.match(/\[action:\S+\]\s*surfaceId=awaiting-dream-input\b/);
4411
- if (dreamMatch) {
4412
- const labelMatch = message.match(/\blabel=([^\n]+)/);
4413
- const valueMatch = message.match(/\bvalue=([^\n]+)/);
4414
- const answer = (labelMatch?.[1] ?? valueMatch?.[1] ?? "").trim();
4415
- try {
4416
- emitTrigger(state, "userAnswered", { surfaceId: "awaiting-dream-input", answer });
4417
- } catch (e) {
4418
- console.warn("[agentlife] cold-start emit userAnswered failed: %s", e?.message);
4419
- }
4420
- }
4421
3766
  });
4422
3767
  api.on("llm_output", (event, ctx) => {
4423
3768
  if (state.disabled)
@@ -4450,39 +3795,6 @@ function registerActivityHooks(api, state) {
4450
3795
  })
4451
3796
  });
4452
3797
  }
4453
- let isCurrentColdStartSession = false;
4454
- if (agentId) {
4455
- try {
4456
- const cs = readState(state);
4457
- if (cs && cs.actionSessionKey === sessionKey && cs.phase !== "READY" && cs.phase !== "FAILED") {
4458
- isCurrentColdStartSession = true;
4459
- }
4460
- } catch {}
4461
- }
4462
- if (isCurrentColdStartSession && agentId) {
4463
- const lastText = texts.length > 0 ? texts[texts.length - 1].trim() : "";
4464
- const lastLine = lastText.split(`
4465
- `).pop()?.trim() ?? "";
4466
- let marker = null;
4467
- if (/\bNO_SIGNAL\b/.test(lastText)) {
4468
- marker = "NO_SIGNAL";
4469
- } else if (TERMINAL_MARKERS.has(lastLine)) {
4470
- marker = lastLine;
4471
- } else if (TERMINAL_MARKERS.has(lastText)) {
4472
- marker = lastText;
4473
- }
4474
- try {
4475
- emitTrigger(state, "agentReplied", {
4476
- agentId,
4477
- sessionKey,
4478
- marker,
4479
- lastText: lastText.slice(0, 4000),
4480
- terminal: marker !== null
4481
- });
4482
- } catch (e) {
4483
- console.warn("[agentlife] cold-start emit agentReplied failed: %s", e?.message);
4484
- }
4485
- }
4486
3798
  }, { priority: 100 });
4487
3799
  api.on("before_tool_call", (event, ctx) => {
4488
3800
  if (state.disabled)
@@ -4527,26 +3839,6 @@ function registerActivityHooks(api, state) {
4527
3839
  if (event.toolName === "agentlife_push" && typeof event.params?.dsl === "string") {
4528
3840
  canvasDsl = event.params.dsl;
4529
3841
  }
4530
- if (!isError && agentId) {
4531
- let touchedMemory = false;
4532
- if (toolName === "exec") {
4533
- const command = event.params?.command ?? event.params?.script ?? "";
4534
- if (typeof command === "string" && /memory\/[\w.\-]+\.md/.test(command) && /(>>?|tee\b|cp\b|mv\b)/.test(command)) {
4535
- touchedMemory = true;
4536
- }
4537
- } else if (toolName === "write" || toolName === "edit" || toolName === "str_replace" || toolName === "apply_patch") {
4538
- const filePath = event.params?.path ?? event.params?.file_path ?? "";
4539
- if (/memory\/[\w.\-]+\.md$/.test(filePath))
4540
- touchedMemory = true;
4541
- }
4542
- if (touchedMemory) {
4543
- try {
4544
- emitTrigger(state, "memoryWritten", { agentId, sessionKey });
4545
- } catch (e) {
4546
- console.warn("[agentlife] cold-start emit memoryWritten failed: %s", e?.message);
4547
- }
4548
- }
4549
- }
4550
3842
  const resultStr = typeof event.result === "string" ? event.result : event.result != null ? JSON.stringify(event.result) : null;
4551
3843
  recordActivity(state, "tool_end", sessionKey, agentId, {
4552
3844
  toolName,
@@ -5332,31 +4624,31 @@ function startDailySweepService(state) {
5332
4624
  import * as crypto4 from "node:crypto";
5333
4625
  import * as fsSync3 from "node:fs";
5334
4626
  import { createRequire as createRequire3 } from "node:module";
5335
- import * as os7 from "node:os";
5336
- import * as path11 from "node:path";
4627
+ import * as os6 from "node:os";
4628
+ import * as path10 from "node:path";
5337
4629
 
5338
4630
  // gateway/web-app.ts
5339
4631
  import * as crypto3 from "node:crypto";
5340
- import * as os6 from "node:os";
5341
- import * as path10 from "node:path";
5342
- import * as fs8 from "node:fs";
4632
+ import * as os5 from "node:os";
4633
+ import * as path9 from "node:path";
4634
+ import * as fs7 from "node:fs";
5343
4635
 
5344
4636
  // services/pairing-access-token.ts
5345
4637
  import * as crypto from "node:crypto";
5346
- import * as fs6 from "node:fs";
5347
- import * as os4 from "node:os";
5348
- import * as path8 from "node:path";
4638
+ import * as fs5 from "node:fs";
4639
+ import * as os3 from "node:os";
4640
+ import * as path7 from "node:path";
5349
4641
  var cachedToken = null;
5350
4642
  function pairingAccessPath() {
5351
- return path8.join(os4.homedir(), ".openclaw", "agentlife", "pairing-access.json");
4643
+ return path7.join(os3.homedir(), ".openclaw", "agentlife", "pairing-access.json");
5352
4644
  }
5353
4645
  function loadOrCreatePairingAccessToken() {
5354
4646
  if (cachedToken)
5355
4647
  return cachedToken;
5356
4648
  const filePath = pairingAccessPath();
5357
4649
  try {
5358
- if (fs6.existsSync(filePath)) {
5359
- const raw = fs6.readFileSync(filePath, "utf-8");
4650
+ if (fs5.existsSync(filePath)) {
4651
+ const raw = fs5.readFileSync(filePath, "utf-8");
5360
4652
  const obj = JSON.parse(raw);
5361
4653
  const token2 = typeof obj?.token === "string" ? obj.token : null;
5362
4654
  if (token2 && token2.length >= 32) {
@@ -5365,12 +4657,12 @@ function loadOrCreatePairingAccessToken() {
5365
4657
  }
5366
4658
  }
5367
4659
  const token = crypto.randomBytes(32).toString("base64url");
5368
- const dir = path8.dirname(filePath);
5369
- if (!fs6.existsSync(dir))
5370
- fs6.mkdirSync(dir, { recursive: true, mode: 448 });
5371
- fs6.writeFileSync(filePath, JSON.stringify({ token, createdAtMs: Date.now() }, null, 2), { mode: 384 });
4660
+ const dir = path7.dirname(filePath);
4661
+ if (!fs5.existsSync(dir))
4662
+ fs5.mkdirSync(dir, { recursive: true, mode: 448 });
4663
+ fs5.writeFileSync(filePath, JSON.stringify({ token, createdAtMs: Date.now() }, null, 2), { mode: 384 });
5372
4664
  try {
5373
- fs6.chmodSync(filePath, 384);
4665
+ fs5.chmodSync(filePath, 384);
5374
4666
  } catch {}
5375
4667
  cachedToken = token;
5376
4668
  return token;
@@ -5383,18 +4675,18 @@ function loadOrCreatePairingAccessToken() {
5383
4675
  // services/cloudflared-supervisor.ts
5384
4676
  import { spawn, spawnSync } from "node:child_process";
5385
4677
  import * as crypto2 from "node:crypto";
5386
- import * as fs7 from "node:fs";
5387
- import * as os5 from "node:os";
5388
- import * as path9 from "node:path";
5389
- var AGENTLIFE_DIR = path9.join(os5.homedir(), ".openclaw", "agentlife");
5390
- var TUNNEL_FILE = path9.join(AGENTLIFE_DIR, "tunnel.json");
5391
- var BIN_DIR = path9.join(AGENTLIFE_DIR, "bin");
5392
- var PAIR_REQUEST_MARKER = path9.join(AGENTLIFE_DIR, "pair-requested");
5393
- var IDENTITY_DIR = path9.join(os5.homedir(), ".agentlife");
5394
- var DEVICE_FILE = path9.join(IDENTITY_DIR, "device.json");
5395
- var LEGACY_DEVICE_FILE = path9.join(AGENTLIFE_DIR, "device.json");
5396
- var CLOUDFLARED_BIN = os5.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
5397
- var CLOUDFLARED_PATH = path9.join(BIN_DIR, CLOUDFLARED_BIN);
4678
+ import * as fs6 from "node:fs";
4679
+ import * as os4 from "node:os";
4680
+ import * as path8 from "node:path";
4681
+ var AGENTLIFE_DIR = path8.join(os4.homedir(), ".openclaw", "agentlife");
4682
+ var TUNNEL_FILE = path8.join(AGENTLIFE_DIR, "tunnel.json");
4683
+ var BIN_DIR = path8.join(AGENTLIFE_DIR, "bin");
4684
+ var PAIR_REQUEST_MARKER = path8.join(AGENTLIFE_DIR, "pair-requested");
4685
+ var IDENTITY_DIR = path8.join(os4.homedir(), ".agentlife");
4686
+ var DEVICE_FILE = path8.join(IDENTITY_DIR, "device.json");
4687
+ var LEGACY_DEVICE_FILE = path8.join(AGENTLIFE_DIR, "device.json");
4688
+ var CLOUDFLARED_BIN = os4.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
4689
+ var CLOUDFLARED_PATH = path8.join(BIN_DIR, CLOUDFLARED_BIN);
5398
4690
  var API_BASE = process.env.AGENTLIFE_API_BASE || "https://api.agentlife.app";
5399
4691
  var RESTART_DELAY_MS = 5000;
5400
4692
  var STABLE_RUNTIME_MS = 60000;
@@ -5514,20 +4806,20 @@ async function doBootstrap() {
5514
4806
  return tunnelInfo;
5515
4807
  }
5516
4808
  function isPairRequested() {
5517
- return fs7.existsSync(PAIR_REQUEST_MARKER);
4809
+ return fs6.existsSync(PAIR_REQUEST_MARKER);
5518
4810
  }
5519
4811
  function requestPair() {
5520
4812
  try {
5521
- if (!fs7.existsSync(AGENTLIFE_DIR))
5522
- fs7.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
5523
- fs7.writeFileSync(PAIR_REQUEST_MARKER, String(Date.now()), { mode: 384 });
4813
+ if (!fs6.existsSync(AGENTLIFE_DIR))
4814
+ fs6.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
4815
+ fs6.writeFileSync(PAIR_REQUEST_MARKER, String(Date.now()), { mode: 384 });
5524
4816
  } catch (err) {
5525
4817
  console.warn(`[cloudflared-supervisor] failed to write pair-request marker: ${err?.message ?? err}`);
5526
4818
  }
5527
4819
  }
5528
4820
  function clearPairRequest() {
5529
4821
  try {
5530
- fs7.unlinkSync(PAIR_REQUEST_MARKER);
4822
+ fs6.unlinkSync(PAIR_REQUEST_MARKER);
5531
4823
  } catch {}
5532
4824
  }
5533
4825
  function parseRetryAfter(value) {
@@ -5551,18 +4843,18 @@ function scheduleProvisionRetry(delayMs) {
5551
4843
  }, delayMs);
5552
4844
  }
5553
4845
  function ensureDirs() {
5554
- if (!fs7.existsSync(AGENTLIFE_DIR))
5555
- fs7.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
5556
- if (!fs7.existsSync(BIN_DIR))
5557
- fs7.mkdirSync(BIN_DIR, { recursive: true, mode: 448 });
5558
- if (!fs7.existsSync(IDENTITY_DIR))
5559
- fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
4846
+ if (!fs6.existsSync(AGENTLIFE_DIR))
4847
+ fs6.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
4848
+ if (!fs6.existsSync(BIN_DIR))
4849
+ fs6.mkdirSync(BIN_DIR, { recursive: true, mode: 448 });
4850
+ if (!fs6.existsSync(IDENTITY_DIR))
4851
+ fs6.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
5560
4852
  }
5561
4853
  function readIdentityFile(filePath) {
5562
- if (!fs7.existsSync(filePath))
4854
+ if (!fs6.existsSync(filePath))
5563
4855
  return null;
5564
4856
  try {
5565
- const raw = fs7.readFileSync(filePath, "utf-8");
4857
+ const raw = fs6.readFileSync(filePath, "utf-8");
5566
4858
  const parsed = JSON.parse(raw);
5567
4859
  if (typeof parsed.deviceId === "string" && typeof parsed.deviceSecret === "string") {
5568
4860
  return { deviceId: parsed.deviceId, deviceSecret: parsed.deviceSecret };
@@ -5580,14 +4872,14 @@ function loadDeviceIdentity() {
5580
4872
  if (!legacy)
5581
4873
  return null;
5582
4874
  try {
5583
- if (!fs7.existsSync(IDENTITY_DIR))
5584
- fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
5585
- fs7.writeFileSync(DEVICE_FILE, JSON.stringify(legacy, null, 2), { mode: 384 });
4875
+ if (!fs6.existsSync(IDENTITY_DIR))
4876
+ fs6.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
4877
+ fs6.writeFileSync(DEVICE_FILE, JSON.stringify(legacy, null, 2), { mode: 384 });
5586
4878
  try {
5587
- fs7.chmodSync(DEVICE_FILE, 384);
4879
+ fs6.chmodSync(DEVICE_FILE, 384);
5588
4880
  } catch {}
5589
4881
  try {
5590
- fs7.unlinkSync(LEGACY_DEVICE_FILE);
4882
+ fs6.unlinkSync(LEGACY_DEVICE_FILE);
5591
4883
  } catch {}
5592
4884
  console.log(`[cloudflared-supervisor] migrated device.json to ${DEVICE_FILE}`);
5593
4885
  } catch (err) {
@@ -5599,25 +4891,25 @@ function loadOrCreateDeviceIdentity() {
5599
4891
  const existing = loadDeviceIdentity();
5600
4892
  if (existing)
5601
4893
  return existing;
5602
- if (!fs7.existsSync(IDENTITY_DIR))
5603
- fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
4894
+ if (!fs6.existsSync(IDENTITY_DIR))
4895
+ fs6.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
5604
4896
  const identity = {
5605
4897
  deviceId: crypto2.randomUUID(),
5606
4898
  deviceSecret: crypto2.randomBytes(32).toString("base64url")
5607
4899
  };
5608
- fs7.writeFileSync(DEVICE_FILE, JSON.stringify(identity, null, 2), { mode: 384 });
4900
+ fs6.writeFileSync(DEVICE_FILE, JSON.stringify(identity, null, 2), { mode: 384 });
5609
4901
  try {
5610
- fs7.chmodSync(DEVICE_FILE, 384);
4902
+ fs6.chmodSync(DEVICE_FILE, 384);
5611
4903
  } catch {}
5612
4904
  console.log(`[cloudflared-supervisor] generated new device identity (deviceId=${identity.deviceId})`);
5613
4905
  return identity;
5614
4906
  }
5615
4907
  var AGENTLIFE_API_BASE = API_BASE;
5616
4908
  function loadCachedTunnel() {
5617
- if (!fs7.existsSync(TUNNEL_FILE))
4909
+ if (!fs6.existsSync(TUNNEL_FILE))
5618
4910
  return null;
5619
4911
  try {
5620
- const raw = fs7.readFileSync(TUNNEL_FILE, "utf-8");
4912
+ const raw = fs6.readFileSync(TUNNEL_FILE, "utf-8");
5621
4913
  const parsed = JSON.parse(raw);
5622
4914
  if (typeof parsed.subdomain === "string" && typeof parsed.hostname === "string" && typeof parsed.tunnelUrl === "string" && typeof parsed.tunnelToken === "string" && typeof parsed.provisionedAt === "number") {
5623
4915
  return parsed;
@@ -5626,9 +4918,9 @@ function loadCachedTunnel() {
5626
4918
  return null;
5627
4919
  }
5628
4920
  function persistTunnel(info) {
5629
- fs7.writeFileSync(TUNNEL_FILE, JSON.stringify(info, null, 2), { mode: 384 });
4921
+ fs6.writeFileSync(TUNNEL_FILE, JSON.stringify(info, null, 2), { mode: 384 });
5630
4922
  try {
5631
- fs7.chmodSync(TUNNEL_FILE, 384);
4923
+ fs6.chmodSync(TUNNEL_FILE, 384);
5632
4924
  } catch {}
5633
4925
  }
5634
4926
  async function provisionTunnel(identity) {
@@ -5671,14 +4963,14 @@ async function provisionTunnel(identity) {
5671
4963
  }
5672
4964
  }
5673
4965
  function ensureCloudflaredBinary() {
5674
- if (fs7.existsSync(CLOUDFLARED_PATH)) {
4966
+ if (fs6.existsSync(CLOUDFLARED_PATH)) {
5675
4967
  try {
5676
- fs7.accessSync(CLOUDFLARED_PATH, fs7.constants.X_OK);
4968
+ fs6.accessSync(CLOUDFLARED_PATH, fs6.constants.X_OK);
5677
4969
  return CLOUDFLARED_PATH;
5678
4970
  } catch {}
5679
4971
  }
5680
- const platform2 = os5.platform();
5681
- const arch2 = os5.arch();
4972
+ const platform2 = os4.platform();
4973
+ const arch2 = os4.arch();
5682
4974
  const release = detectCloudflaredRelease(platform2, arch2);
5683
4975
  if (!release) {
5684
4976
  console.warn(`[cloudflared-supervisor] unsupported platform: ${platform2}/${arch2}`);
@@ -5691,28 +4983,28 @@ function ensureCloudflaredBinary() {
5691
4983
  if (release.kind === "tgz") {
5692
4984
  const extracted = extractTgzCloudflared(release.tempPath, BIN_DIR);
5693
4985
  try {
5694
- fs7.unlinkSync(release.tempPath);
4986
+ fs6.unlinkSync(release.tempPath);
5695
4987
  } catch {}
5696
4988
  if (!extracted)
5697
4989
  return null;
5698
4990
  } else {
5699
4991
  try {
5700
- fs7.renameSync(release.tempPath, CLOUDFLARED_PATH);
4992
+ fs6.renameSync(release.tempPath, CLOUDFLARED_PATH);
5701
4993
  } catch (err) {
5702
4994
  console.warn(`[cloudflared-supervisor] rename failed: ${err?.message}`);
5703
4995
  return null;
5704
4996
  }
5705
4997
  }
5706
4998
  try {
5707
- fs7.chmodSync(CLOUDFLARED_PATH, 493);
4999
+ fs6.chmodSync(CLOUDFLARED_PATH, 493);
5708
5000
  } catch {}
5709
- return fs7.existsSync(CLOUDFLARED_PATH) ? CLOUDFLARED_PATH : null;
5001
+ return fs6.existsSync(CLOUDFLARED_PATH) ? CLOUDFLARED_PATH : null;
5710
5002
  }
5711
5003
  function detectCloudflaredRelease(platform2, arch2) {
5712
5004
  const base = "https://github.com/cloudflare/cloudflared/releases/latest/download";
5713
5005
  if (platform2 === "darwin") {
5714
5006
  const asset = arch2 === "arm64" ? "cloudflared-darwin-arm64.tgz" : "cloudflared-darwin-amd64.tgz";
5715
- return { url: `${base}/${asset}`, kind: "tgz", tempPath: path9.join(BIN_DIR, asset) };
5007
+ return { url: `${base}/${asset}`, kind: "tgz", tempPath: path8.join(BIN_DIR, asset) };
5716
5008
  }
5717
5009
  if (platform2 === "linux") {
5718
5010
  const asset = arch2 === "arm64" ? "cloudflared-linux-arm64" : arch2 === "arm" ? "cloudflared-linux-arm" : arch2 === "x64" ? "cloudflared-linux-amd64" : null;
@@ -5736,11 +5028,11 @@ function downloadWithCurl(url, dest) {
5736
5028
  if (result.status !== 0) {
5737
5029
  console.warn(`[cloudflared-supervisor] curl failed (exit ${result.status}): ${result.stderr?.toString().slice(0, 200)}`);
5738
5030
  try {
5739
- fs7.unlinkSync(dest);
5031
+ fs6.unlinkSync(dest);
5740
5032
  } catch {}
5741
5033
  return false;
5742
5034
  }
5743
- return fs7.existsSync(dest) && fs7.statSync(dest).size > 0;
5035
+ return fs6.existsSync(dest) && fs6.statSync(dest).size > 0;
5744
5036
  }
5745
5037
  function extractTgzCloudflared(tgzPath, destDir) {
5746
5038
  const result = spawnSync("tar", ["-xzf", tgzPath, "-C", destDir], { stdio: "pipe" });
@@ -5748,7 +5040,7 @@ function extractTgzCloudflared(tgzPath, destDir) {
5748
5040
  console.warn(`[cloudflared-supervisor] tar extract failed (exit ${result.status}): ${result.stderr?.toString().slice(0, 200)}`);
5749
5041
  return false;
5750
5042
  }
5751
- return fs7.existsSync(CLOUDFLARED_PATH);
5043
+ return fs6.existsSync(CLOUDFLARED_PATH);
5752
5044
  }
5753
5045
  function startCloudflaredProcess(binPath, tunnelToken) {
5754
5046
  if (state.stopped)
@@ -5805,11 +5097,11 @@ var MIME_TYPES = {
5805
5097
  };
5806
5098
  function mintBootstrapToken() {
5807
5099
  const bootstrapToken = crypto3.randomBytes(32).toString("base64url");
5808
- const devicesDir = path10.join(os6.homedir(), ".openclaw", "devices");
5809
- const bootstrapPath = path10.join(devicesDir, "bootstrap.json");
5810
- if (!fs8.existsSync(devicesDir))
5811
- fs8.mkdirSync(devicesDir, { recursive: true });
5812
- const registry = fs8.existsSync(bootstrapPath) ? JSON.parse(fs8.readFileSync(bootstrapPath, "utf-8")) : {};
5100
+ const devicesDir = path9.join(os5.homedir(), ".openclaw", "devices");
5101
+ const bootstrapPath = path9.join(devicesDir, "bootstrap.json");
5102
+ if (!fs7.existsSync(devicesDir))
5103
+ fs7.mkdirSync(devicesDir, { recursive: true });
5104
+ const registry = fs7.existsSync(bootstrapPath) ? JSON.parse(fs7.readFileSync(bootstrapPath, "utf-8")) : {};
5813
5105
  registry[bootstrapToken] = {
5814
5106
  token: bootstrapToken,
5815
5107
  profile: {
@@ -5818,15 +5110,15 @@ function mintBootstrapToken() {
5818
5110
  },
5819
5111
  issuedAtMs: Date.now()
5820
5112
  };
5821
- fs8.writeFileSync(bootstrapPath, JSON.stringify(registry, null, 2));
5113
+ fs7.writeFileSync(bootstrapPath, JSON.stringify(registry, null, 2));
5822
5114
  return bootstrapToken;
5823
5115
  }
5824
5116
  function registerWebApp(api) {
5825
- const pluginRoot = path10.resolve(path10.dirname(api.source), "..");
5826
- const appRoot = path10.join(pluginRoot, "web-build");
5827
- const hasWebBuild = fs8.existsSync(appRoot);
5828
- const indexPath = hasWebBuild ? path10.join(appRoot, "index.html") : null;
5829
- const hasIndex = !!(indexPath && fs8.existsSync(indexPath));
5117
+ const pluginRoot = path9.resolve(path9.dirname(api.source), "..");
5118
+ const appRoot = path9.join(pluginRoot, "web-build");
5119
+ const hasWebBuild = fs7.existsSync(appRoot);
5120
+ const indexPath = hasWebBuild ? path9.join(appRoot, "index.html") : null;
5121
+ const hasIndex = !!(indexPath && fs7.existsSync(indexPath));
5830
5122
  if (!hasWebBuild) {
5831
5123
  api.logger.info("[agentlife] web-build/ not found — /agentlife/pair will still register; static dashboard disabled");
5832
5124
  } else if (!hasIndex) {
@@ -5834,8 +5126,8 @@ function registerWebApp(api) {
5834
5126
  }
5835
5127
  let gatewayToken = "";
5836
5128
  try {
5837
- const configPath = path10.join(__require("node:os").homedir(), ".openclaw", "openclaw.json");
5838
- const raw = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
5129
+ const configPath = path9.join(__require("node:os").homedir(), ".openclaw", "openclaw.json");
5130
+ const raw = JSON.parse(fs7.readFileSync(configPath, "utf-8"));
5839
5131
  gatewayToken = raw?.gateway?.auth?.token || "";
5840
5132
  } catch {}
5841
5133
  loadOrCreatePairingAccessToken();
@@ -5953,16 +5245,16 @@ function registerWebApp(api) {
5953
5245
  return true;
5954
5246
  }
5955
5247
  const relative = urlPath.replace(/^\/agentlife\/?/, "") || "index.html";
5956
- const filePath = path10.resolve(appRoot, relative);
5248
+ const filePath = path9.resolve(appRoot, relative);
5957
5249
  if (!filePath.startsWith(appRoot)) {
5958
5250
  res.writeHead(403);
5959
5251
  res.end();
5960
5252
  return true;
5961
5253
  }
5962
- const target = fs8.existsSync(filePath) && fs8.statSync(filePath).isFile() ? filePath : indexPath;
5963
- const ext = path10.extname(target).toLowerCase();
5254
+ const target = fs7.existsSync(filePath) && fs7.statSync(filePath).isFile() ? filePath : indexPath;
5255
+ const ext = path9.extname(target).toLowerCase();
5964
5256
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
5965
- let content = fs8.readFileSync(target);
5257
+ let content = fs7.readFileSync(target);
5966
5258
  if (target === indexPath) {
5967
5259
  setTimeout(approveLatest, 2000);
5968
5260
  setTimeout(approveLatest, 5000);
@@ -6051,9 +5343,9 @@ Setup code: ${setupCode}
6051
5343
  api.registerService({
6052
5344
  id: "agentlife-auto-pair",
6053
5345
  start: async (ctx) => {
6054
- const devicesDir = path11.join(os7.homedir(), ".openclaw", "devices");
6055
- const pendingPath = path11.join(devicesDir, "pending.json");
6056
- const pairedPath = path11.join(devicesDir, "paired.json");
5346
+ const devicesDir = path10.join(os6.homedir(), ".openclaw", "devices");
5347
+ const pendingPath = path10.join(devicesDir, "pending.json");
5348
+ const pairedPath = path10.join(devicesDir, "paired.json");
6057
5349
  const pollInterval = 3000;
6058
5350
  const withAdmin = (arr) => {
6059
5351
  const base = Array.isArray(arr) ? arr.filter((s) => typeof s === "string") : [];
@@ -6151,7 +5443,7 @@ Setup code: ${setupCode}
6151
5443
 
6152
5444
  // hooks/bootstrap.ts
6153
5445
  var SKIP_GUIDANCE_AGENTS = new Set(["agentlife"]);
6154
- var SKIP_AGENTS_MD_INJECTION = new Set(["agentlife", "agentlife-builder", "agentlife-vision", "quick", "supervisor"]);
5446
+ var SKIP_AGENTS_MD_INJECTION = new Set(["agentlife", "agentlife-builder", "quick", "supervisor"]);
6155
5447
  function registerBootstrapHook(api, state2) {
6156
5448
  api.registerService({
6157
5449
  id: "agentlife-bootstrap-hook",
@@ -6372,7 +5664,120 @@ function registerWidgetPushTool(api, state2) {
6372
5664
  newSurfaceIds.push(match[1]);
6373
5665
  }
6374
5666
  }
6375
- const dslResults = processDslBlock(state2, dsl);
5667
+ const ownershipViolations = [];
5668
+ const filteredBlocks = [];
5669
+ if (agentId && state2.surfaceDb) {
5670
+ for (const block of blocks) {
5671
+ const trimmed = block.trim();
5672
+ const deleteMatch = trimmed.match(/^delete\s+(\S+)/);
5673
+ const surfaceMatch = block.match(/^surface\s+(\S+)/m);
5674
+ const sid = deleteMatch?.[1] ?? surfaceMatch?.[1] ?? null;
5675
+ const action = deleteMatch ? "delete" : "push";
5676
+ if (!sid) {
5677
+ filteredBlocks.push(block);
5678
+ continue;
5679
+ }
5680
+ const existingOwner = state2.surfaceDb.getAgentId(sid);
5681
+ if (existingOwner && existingOwner !== agentId) {
5682
+ ownershipViolations.push({ surfaceId: sid, owner: existingOwner, action });
5683
+ recordActivity(state2, "quality_warning", sessionKey, agentId, {
5684
+ data: JSON.stringify({
5685
+ issue: "surface_ownership_violation",
5686
+ surfaceId: sid,
5687
+ owner: existingOwner,
5688
+ attempt: agentId,
5689
+ action
5690
+ })
5691
+ });
5692
+ continue;
5693
+ }
5694
+ filteredBlocks.push(block);
5695
+ }
5696
+ } else {
5697
+ filteredBlocks.push(...blocks);
5698
+ }
5699
+ if (filteredBlocks.length === 0 && ownershipViolations.length > 0) {
5700
+ const detail = ownershipViolations.map((v) => `${v.surfaceId} (owner=${v.owner}, attempt=${v.action})`).join("; ");
5701
+ return {
5702
+ content: [{
5703
+ type: "text",
5704
+ text: `[QUALITY ERROR] surface ownership violation: ${detail}. ` + `Each surfaceId is bound to the first agent that pushed it. ` + `You cannot push or delete surfaces owned by other agents. ` + `If you need to coordinate, delegate via sessions_send (internal) to the owning agent. ` + `If you are renaming/retiring a widget, push with a new surfaceId under your own namespace.`
5705
+ }]
5706
+ };
5707
+ }
5708
+ if (agentId === "agentlife-builder") {
5709
+ const CONFIRMATION_SUFFIXES = /-(?:created|ready|built|setup|done|welcome)$/i;
5710
+ const confirmationViolations = [];
5711
+ const stripped = [];
5712
+ for (const block of filteredBlocks) {
5713
+ const deleteMatch = block.trim().match(/^delete\s+(\S+)/);
5714
+ const surfaceMatch = block.match(/^surface\s+(\S+)/m);
5715
+ const sid = deleteMatch?.[1] ?? surfaceMatch?.[1] ?? null;
5716
+ if (!sid || deleteMatch) {
5717
+ stripped.push(block);
5718
+ continue;
5719
+ }
5720
+ if (sid === "agent-created" || CONFIRMATION_SUFFIXES.test(sid)) {
5721
+ confirmationViolations.push(sid);
5722
+ recordActivity(state2, "quality_warning", sessionKey, agentId, {
5723
+ data: JSON.stringify({ issue: "builder_confirmation_widget", surfaceId: sid })
5724
+ });
5725
+ continue;
5726
+ }
5727
+ stripped.push(block);
5728
+ }
5729
+ if (confirmationViolations.length > 0 && stripped.length === 0) {
5730
+ return {
5731
+ content: [{
5732
+ type: "text",
5733
+ text: `[QUALITY ERROR] builder pushed confirmation widget(s): ${confirmationViolations.join(", ")}. ` + `Per BUILDER_AGENTS_MD, the builder does NOT push an "agent ready / created" widget. ` + `The plugin pushes a deterministic {agentId}-intro as the confirmation the moment ` + `agentlife.createAgent returns. Delete your loading widget and emit 'done'.`
5734
+ }]
5735
+ };
5736
+ }
5737
+ if (confirmationViolations.length > 0) {
5738
+ filteredBlocks.length = 0;
5739
+ filteredBlocks.push(...stripped);
5740
+ console.log("[agentlife] stripped %d builder confirmation widget(s): %s", confirmationViolations.length, confirmationViolations.join(", "));
5741
+ }
5742
+ }
5743
+ const incomingInputIds = new Set;
5744
+ for (const block of filteredBlocks) {
5745
+ const headerLine = block.match(/^surface\s+.+$/m)?.[0] ?? "";
5746
+ if (/\binput\b/.test(headerLine)) {
5747
+ const sid = headerLine.match(/^surface\s+(\S+)/)?.[1];
5748
+ if (sid)
5749
+ incomingInputIds.add(sid);
5750
+ }
5751
+ }
5752
+ const autoDeletedInputIds = [];
5753
+ if (incomingInputIds.size > 0 && state2.surfaceDb) {
5754
+ for (const [existingId, meta] of state2.surfaceDb.entries()) {
5755
+ if (incomingInputIds.has(existingId))
5756
+ continue;
5757
+ const existingHeader = meta.lines[0] ?? "";
5758
+ if (/\binput\b/.test(existingHeader)) {
5759
+ autoDeletedInputIds.push(existingId);
5760
+ }
5761
+ }
5762
+ if (autoDeletedInputIds.length > 0) {
5763
+ console.log("[agentlife] auto-deleting %d stale input surface(s) to make room for %s: %s", autoDeletedInputIds.length, [...incomingInputIds].join(","), autoDeletedInputIds.join(","));
5764
+ }
5765
+ }
5766
+ const prelude = autoDeletedInputIds.map((sid) => `delete ${sid}`);
5767
+ const filteredDsl = [...prelude, ...filteredBlocks].join(`
5768
+ ---
5769
+ `);
5770
+ pushSurfaceIds.length = 0;
5771
+ newSurfaceIds.length = 0;
5772
+ for (const block of filteredBlocks) {
5773
+ const match = block.match(/^surface\s+(\S+)/m);
5774
+ if (match) {
5775
+ pushSurfaceIds.push(match[1]);
5776
+ if (!state2.surfaceDb?.has(match[1]))
5777
+ newSurfaceIds.push(match[1]);
5778
+ }
5779
+ }
5780
+ const dslResults = processDslBlock(state2, filteredDsl);
6376
5781
  if (state2.surfaceDb) {
6377
5782
  for (const sid of pushSurfaceIds) {
6378
5783
  if (agentId)
@@ -6389,6 +5794,9 @@ function registerWidgetPushTool(api, state2) {
6389
5794
  broadcastDelete(deleteMatch[1]);
6390
5795
  }
6391
5796
  }
5797
+ for (const sid of autoDeletedInputIds) {
5798
+ broadcastDelete(sid);
5799
+ }
6392
5800
  for (const sid of newSurfaceIds) {
6393
5801
  const meta = state2.surfaceDb?.get(sid);
6394
5802
  if (!meta)
@@ -6448,6 +5856,9 @@ function registerWidgetPushTool(api, state2) {
6448
5856
  const unknownKeywordHits = [];
6449
5857
  const metadataNoColonHits = [];
6450
5858
  const missingStructureHits = [];
5859
+ const invalidInputActionHits = [];
5860
+ const unknownAttrHits = [];
5861
+ const invalidEnumHits = [];
6451
5862
  for (const result of dslResults) {
6452
5863
  if (result.unknownKeywords?.length) {
6453
5864
  unknownKeywordHits.push({ sid: result.surfaceId, keywords: result.unknownKeywords });
@@ -6478,6 +5889,36 @@ function registerWidgetPushTool(api, state2) {
6478
5889
  })
6479
5890
  });
6480
5891
  }
5892
+ if (result.invalidInputActions?.length) {
5893
+ invalidInputActionHits.push({ sid: result.surfaceId, actions: result.invalidInputActions });
5894
+ recordActivity(state2, "quality_warning", sessionKey, agentId, {
5895
+ data: JSON.stringify({
5896
+ issue: "invalid_input_action",
5897
+ surfaceId: result.surfaceId,
5898
+ actions: result.invalidInputActions
5899
+ })
5900
+ });
5901
+ }
5902
+ if (result.unknownAttrs?.length) {
5903
+ unknownAttrHits.push({ sid: result.surfaceId, attrs: result.unknownAttrs });
5904
+ recordActivity(state2, "quality_warning", sessionKey, agentId, {
5905
+ data: JSON.stringify({
5906
+ issue: "unknown_component_attr",
5907
+ surfaceId: result.surfaceId,
5908
+ attrs: result.unknownAttrs
5909
+ })
5910
+ });
5911
+ }
5912
+ if (result.invalidEnumValues?.length) {
5913
+ invalidEnumHits.push({ sid: result.surfaceId, entries: result.invalidEnumValues });
5914
+ recordActivity(state2, "quality_warning", sessionKey, agentId, {
5915
+ data: JSON.stringify({
5916
+ issue: "invalid_enum_value",
5917
+ surfaceId: result.surfaceId,
5918
+ entries: result.invalidEnumValues
5919
+ })
5920
+ });
5921
+ }
6481
5922
  }
6482
5923
  const summary = pushSurfaceIds.length === 1 ? `Widget ${pushSurfaceIds[0]} updated` : `${pushSurfaceIds.length} widgets updated`;
6483
5924
  const errors = [];
@@ -6498,6 +5939,22 @@ function registerWidgetPushTool(api, state2) {
6498
5939
  if (missingStructureHits.length > 0) {
6499
5940
  errors.push(`[QUALITY ERROR] ${missingStructureHits.join(", ")}: missing \`card\` wrapper. ` + `Every widget must be nested: \`surface <id> size=…\\n card\\n column\\n <children>\`. ` + `Content placed directly under surface (no card) cannot render.`);
6500
5941
  }
5942
+ if (invalidInputActionHits.length > 0) {
5943
+ const detail = invalidInputActionHits.map((h) => `${h.sid}=[${h.actions.map((a) => `"${a.label}" action=${a.action}`).join(", ")}]`).join("; ");
5944
+ errors.push(`[QUALITY ERROR] input surface buttons must use a supported action value: ${detail}. ` + `Inside an \`input\` surface the only valid action values are \`choice\` / \`select\` (numbered single-select list) ` + `and \`toggle\` / \`check\` / \`multichoice\` (checkbox multi-select list). ` + `Any other value (e.g. \`action=domain\`) renders as a plain outlined button and breaks the input-bar UX. ` + `Rewrite the buttons to use \`action=choice\` (pick one) or \`action=toggle\` (pick multiple). ` + `Use custom action names only on regular dashboard widgets, not on input surfaces.`);
5945
+ }
5946
+ if (ownershipViolations.length > 0) {
5947
+ const detail = ownershipViolations.map((v) => `${v.surfaceId} (owner=${v.owner}, attempt=${v.action})`).join("; ");
5948
+ errors.push(`[QUALITY ERROR] surface ownership violation: ${detail}. ` + `Each surfaceId is bound to the first agent that pushed it. ` + `You cannot push or delete surfaces owned by other agents. ` + `The offending block(s) were skipped — the original owner's content is preserved. ` + `If you need to coordinate, delegate via sessions_send (internal) to the owning agent. ` + `If you are emitting a new widget, pick a surfaceId under your own namespace.`);
5949
+ }
5950
+ if (unknownAttrHits.length > 0) {
5951
+ const detail = unknownAttrHits.map((h) => `${h.sid}=[${h.attrs.join(",")}]`).join("; ");
5952
+ errors.push(`[QUALITY ERROR] unknown component attribute(s): ${detail}. ` + `The client parser recognises a closed set of attributes per component — anything else is either silently dropped or renders identically to a plain component (no visual effect). ` + `The notation \`component.attr\` means an unknown \`key=value\` pair; \`component:token\` means an unknown bare flag (e.g. button.primary has no visual effect, drop it). ` + `Remove the attribute, or confirm it against the current DSL grammar before re-emitting.`);
5953
+ }
5954
+ if (invalidEnumHits.length > 0) {
5955
+ const detail = invalidEnumHits.map((h) => h.entries.map((e) => `${h.sid}:${e.attr}=${e.value} (allowed: ${e.allowed.join("|")})`).join(", ")).join("; ");
5956
+ errors.push(`[QUALITY ERROR] enum value outside the allowed set: ${detail}. ` + `Pick one of the allowed values listed above for each attribute. Any other value is silently coerced to the default by the parser.`);
5957
+ }
6501
5958
  if (errors.length > 0) {
6502
5959
  return { content: [{ type: "text", text: `${summary}
6503
5960
 
@@ -6592,53 +6049,12 @@ function extractWidgetText(meta) {
6592
6049
  }
6593
6050
 
6594
6051
  // hooks/prompt-state.ts
6595
- var lastEmittedAnswerByKey = new Map;
6596
- function lastUserMessage(messages) {
6597
- for (let i = messages.length - 1;i >= 0; i--) {
6598
- const m = messages[i];
6599
- if (m?.role !== "user")
6600
- continue;
6601
- if (typeof m.content === "string")
6602
- return m.content;
6603
- if (Array.isArray(m.content)) {
6604
- const parts = [];
6605
- for (const block of m.content) {
6606
- if (typeof block === "string")
6607
- parts.push(block);
6608
- else if (block?.type === "text" && typeof block.text === "string")
6609
- parts.push(block.text);
6610
- }
6611
- return parts.join(`
6612
- `);
6613
- }
6614
- }
6615
- return "";
6616
- }
6617
6052
  function registerPromptStateHook(api, state2) {
6618
6053
  api.on("before_prompt_build", (event, ctx) => {
6619
6054
  if (state2.disabled)
6620
6055
  return;
6621
6056
  if (!isAgentlifeSession(ctx?.sessionKey))
6622
6057
  return;
6623
- const sessionKey = ctx?.sessionKey;
6624
- if (sessionKey) {
6625
- const messages = event?.messages ?? [];
6626
- const latest = lastUserMessage(messages);
6627
- if (latest && lastEmittedAnswerByKey.get(sessionKey) !== latest) {
6628
- const actionMatch = latest.match(/\[action:\S+\]\s*surfaceId=awaiting-dream-input\b/);
6629
- if (actionMatch) {
6630
- const labelMatch = latest.match(/\blabel=([^\n]+)/);
6631
- const valueMatch = latest.match(/\bvalue=([^\n]+)/);
6632
- const answer = (labelMatch?.[1] ?? valueMatch?.[1] ?? "").trim();
6633
- lastEmittedAnswerByKey.set(sessionKey, latest);
6634
- try {
6635
- emitTrigger(state2, "userAnswered", { surfaceId: "awaiting-dream-input", answer });
6636
- } catch (e) {
6637
- console.warn("[agentlife] cold-start emit userAnswered (prompt-build) failed: %s", e?.message);
6638
- }
6639
- }
6640
- }
6641
- }
6642
6058
  const agentId = ctx?.agentId;
6643
6059
  const isOrchestrator = agentId === "agentlife";
6644
6060
  if (isOrchestrator)
@@ -6658,8 +6074,8 @@ function registerPromptStateHook(api, state2) {
6658
6074
  }
6659
6075
 
6660
6076
  // gateway/agents.ts
6661
- import * as fs9 from "node:fs";
6662
- import * as path12 from "node:path";
6077
+ import * as fs8 from "node:fs";
6078
+ import * as path11 from "node:path";
6663
6079
  function registerAgentGateway(api, state2) {
6664
6080
  api.registerGatewayMethod("agentlife.createAgent", async ({ params, respond }) => {
6665
6081
  const id = typeof params?.id === "string" ? params.id.trim() : "";
@@ -6751,9 +6167,10 @@ function registerAgentGateway(api, state2) {
6751
6167
  const provisionedIds = new Set(PROVISIONED_AGENTS.map((a) => a.id));
6752
6168
  if (!provisionedIds.has(id)) {
6753
6169
  try {
6754
- emitTrigger(state2, "agentRegistered", { agentId: id, name, model });
6170
+ renderAgentWidget(state2, api.runtime, id, console.log);
6171
+ deleteWelcomeWidget(state2, console.log);
6755
6172
  } catch (e) {
6756
- console.warn("[agentlife] cold-start emit agentRegistered failed: %s", e?.message);
6173
+ console.warn("[agentlife] renderAgentWidget failed for %s: %s", id, e?.message);
6757
6174
  }
6758
6175
  }
6759
6176
  }
@@ -6819,10 +6236,10 @@ function registerAgentGateway(api, state2) {
6819
6236
  state2.agentDbs.delete(id);
6820
6237
  }
6821
6238
  if (state2.dbBaseDir) {
6822
- const dbPath = path12.join(state2.dbBaseDir, `${id}.db`);
6239
+ const dbPath = path11.join(state2.dbBaseDir, `${id}.db`);
6823
6240
  try {
6824
- if (fs9.existsSync(dbPath)) {
6825
- fs9.unlinkSync(dbPath);
6241
+ if (fs8.existsSync(dbPath)) {
6242
+ fs8.unlinkSync(dbPath);
6826
6243
  cleanup.agentDbDeleted = true;
6827
6244
  }
6828
6245
  } catch (e) {
@@ -6848,17 +6265,6 @@ function registerAgentGateway(api, state2) {
6848
6265
  await saveRegistryToDisk(state2);
6849
6266
  }
6850
6267
  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);
6851
- try {
6852
- const provisionedIds2 = new Set(PROVISIONED_AGENTS.map((a) => a.id));
6853
- let remaining = 0;
6854
- for (const aid of state2.agentRegistry.keys()) {
6855
- if (!provisionedIds2.has(aid))
6856
- remaining++;
6857
- }
6858
- emitTrigger(state2, "agentDeleted", { agentId: id, remaining });
6859
- } catch (e) {
6860
- console.warn("[agentlife] cold-start emit agentDeleted failed: %s", e?.message);
6861
- }
6862
6268
  respond(true, { status: "deleted", id, removedFromConfig, removedFromRegistry, cleanup });
6863
6269
  });
6864
6270
  api.registerGatewayMethod("agentlife.agents", ({ respond }) => {
@@ -7168,21 +6574,13 @@ function registerSurfacesGateway(api, state2) {
7168
6574
  respond(true, { surfaces: [] });
7169
6575
  return;
7170
6576
  }
7171
- const visibility = params?.visibility === "paywall" ? "paywall" : "dashboard";
7172
- const FLOW_INPUT_IDS = new Set(["onboarding-domain", "awaiting-dream-input"]);
7173
- const isFlowInput = (id) => FLOW_INPUT_IDS.has(id) || id.startsWith("warmup-") || id.startsWith("dismiss-alt-");
7174
6577
  for (const [surfaceId, meta] of state2.surfaceDb.entries()) {
7175
6578
  if (isExpired(meta, now))
7176
6579
  continue;
7177
6580
  const headerLine = meta.lines[0] ?? "";
7178
6581
  const isInput = /\binput\b/.test(headerLine);
7179
- if (isInput && !isFlowInput(surfaceId))
6582
+ if (isInput)
7180
6583
  continue;
7181
- if (visibility === "dashboard" && surfaceId.startsWith("vision-")) {
7182
- const visible = state2.surfaceDb.getDashboardVisible(surfaceId);
7183
- if (!visible)
7184
- continue;
7185
- }
7186
6584
  if (meta.lines.length > 0) {
7187
6585
  surfaceEntries.push({ surfaceId, dsl: meta.lines.join(`
7188
6586
  `) });
@@ -7320,12 +6718,7 @@ function registerSurfacesGateway(api, state2) {
7320
6718
  automations = db.prepare("SELECT id, type, name, path FROM automations WHERE surfaceId = ? AND status != 'removed'").all(surfaceId);
7321
6719
  } catch {}
7322
6720
  guidedDismissSent.delete(surfaceId);
7323
- const isVision = surfaceId.startsWith("vision-");
7324
- if (isVision) {
7325
- state2.surfaceDb.setDashboardVisible(surfaceId, false);
7326
- } else {
7327
- state2.surfaceDb.delete(surfaceId);
7328
- }
6721
+ state2.surfaceDb.delete(surfaceId);
7329
6722
  broadcastDelete(surfaceId);
7330
6723
  try {
7331
6724
  enqueueCleanupTasks(state2, surfaceId, agentId, cronId, automations);
@@ -7333,7 +6726,7 @@ function registerSurfacesGateway(api, state2) {
7333
6726
  console.error("[agentlife] dismiss: failed to enqueue cleanup tasks for %s: %s", surfaceId, err?.message);
7334
6727
  }
7335
6728
  const taskCount = (cronId ? 1 : 0) + (agentId ? 1 : 0);
7336
- console.log("[agentlife] dismiss: %s %s, %d cleanup tasks enqueued (reason=%s)", surfaceId, isVision ? "hidden from dashboard (kept for paywall)" : "deleted", taskCount, reason ?? "none");
6729
+ console.log("[agentlife] dismiss: %s deleted, %d cleanup tasks enqueued (reason=%s)", surfaceId, taskCount, reason ?? "none");
7337
6730
  respond(true, { surfaceId, dismissed: true });
7338
6731
  processCleanupTasks(state2, surfaceId).catch((e) => console.warn("[agentlife] dismiss cleanup processor error:", e?.message));
7339
6732
  }, { scope: "operator.write" });
@@ -7485,12 +6878,12 @@ function registerAutomationsGateway(api, state2) {
7485
6878
  }
7486
6879
 
7487
6880
  // gateway/admin.ts
7488
- import * as fs10 from "node:fs/promises";
6881
+ import * as fs9 from "node:fs/promises";
7489
6882
  import * as fsSync4 from "node:fs";
7490
- import * as os8 from "node:os";
7491
- import * as path13 from "node:path";
6883
+ import * as os7 from "node:os";
6884
+ import * as path12 from "node:path";
7492
6885
  function pluginConfigPath() {
7493
- return path13.join(os8.homedir(), ".openclaw", "agentlife", "plugin-config.json");
6886
+ return path12.join(os7.homedir(), ".openclaw", "agentlife", "plugin-config.json");
7494
6887
  }
7495
6888
  function readPluginConfig() {
7496
6889
  try {
@@ -7500,7 +6893,7 @@ function readPluginConfig() {
7500
6893
  }
7501
6894
  }
7502
6895
  function writePluginConfig(config2) {
7503
- const dir = path13.dirname(pluginConfigPath());
6896
+ const dir = path12.dirname(pluginConfigPath());
7504
6897
  fsSync4.mkdirSync(dir, { recursive: true });
7505
6898
  fsSync4.writeFileSync(pluginConfigPath(), JSON.stringify(config2, null, 2));
7506
6899
  }
@@ -7599,7 +6992,7 @@ function registerAdminGateway(api, state2) {
7599
6992
  api.registerGatewayMethod("agentlife.uninstall", async ({ respond }) => {
7600
6993
  try {
7601
6994
  const cleaned = [];
7602
- const baseDir = state2.agentlifeStateDir ?? path13.join(os8.homedir(), ".openclaw", "agentlife");
6995
+ const baseDir = state2.agentlifeStateDir ?? path12.join(os7.homedir(), ".openclaw", "agentlife");
7603
6996
  const identity = loadDeviceIdentity();
7604
6997
  if (!identity) {
7605
6998
  cleaned.push("server deprovision skipped (no device identity)");
@@ -7664,7 +7057,7 @@ function registerAdminGateway(api, state2) {
7664
7057
  } catch {
7665
7058
  cleaned.push("agent config cleanup skipped");
7666
7059
  }
7667
- const backupPath = path13.join(baseDir, "config-backup.json");
7060
+ const backupPath = path12.join(baseDir, "config-backup.json");
7668
7061
  try {
7669
7062
  cleaned.push(await restoreConfigFromBackup(api, backupPath));
7670
7063
  } catch {
@@ -7686,35 +7079,35 @@ function registerAdminGateway(api, state2) {
7686
7079
  dbPath,
7687
7080
  dbPath ? `${dbPath}-wal` : null,
7688
7081
  dbPath ? `${dbPath}-shm` : null,
7689
- path13.join(baseDir, "config-backup.json"),
7690
- path13.join(baseDir, "notification-config.json"),
7691
- path13.join(baseDir, "canvas-node-identity.json")
7082
+ path12.join(baseDir, "config-backup.json"),
7083
+ path12.join(baseDir, "notification-config.json"),
7084
+ path12.join(baseDir, "canvas-node-identity.json")
7692
7085
  ].filter(Boolean);
7693
7086
  for (const fp of stateFiles) {
7694
7087
  try {
7695
- await fs10.unlink(fp);
7696
- cleaned.push(`deleted ${path13.basename(fp)}`);
7088
+ await fs9.unlink(fp);
7089
+ cleaned.push(`deleted ${path12.basename(fp)}`);
7697
7090
  } catch {}
7698
7091
  }
7699
7092
  if (state2.dbBaseDir) {
7700
7093
  try {
7701
- await fs10.rm(state2.dbBaseDir, { recursive: true, force: true });
7094
+ await fs9.rm(state2.dbBaseDir, { recursive: true, force: true });
7702
7095
  cleaned.push("deleted agent databases");
7703
7096
  } catch {}
7704
7097
  }
7705
7098
  for (const agent of PROVISIONED_AGENTS) {
7706
7099
  if (agent.existingAgent)
7707
7100
  continue;
7708
- const wsDir = agent.workspaceDir ?? path13.join(os8.homedir(), ".openclaw", `workspace-${agent.id}`);
7101
+ const wsDir = agent.workspaceDir ?? path12.join(os7.homedir(), ".openclaw", `workspace-${agent.id}`);
7709
7102
  try {
7710
- await fs10.rm(wsDir, { recursive: true, force: true });
7103
+ await fs9.rm(wsDir, { recursive: true, force: true });
7711
7104
  cleaned.push(`deleted workspace ${agent.id}`);
7712
7105
  } catch {}
7713
7106
  }
7714
7107
  try {
7715
- const remaining = await fs10.readdir(baseDir);
7108
+ const remaining = await fs9.readdir(baseDir);
7716
7109
  if (remaining.length === 0) {
7717
- await fs10.rmdir(baseDir);
7110
+ await fs9.rmdir(baseDir);
7718
7111
  cleaned.push("deleted agentlife state directory");
7719
7112
  }
7720
7113
  } catch {}
@@ -7965,33 +7358,33 @@ import {
7965
7358
  } from "openclaw/plugin-sdk/provider-auth-api-key";
7966
7359
 
7967
7360
  // gateway/config-utils.ts
7968
- import * as fs11 from "node:fs";
7969
- import * as os9 from "node:os";
7970
- import * as path14 from "node:path";
7361
+ import * as fs10 from "node:fs";
7362
+ import * as os8 from "node:os";
7363
+ import * as path13 from "node:path";
7971
7364
  function configPath() {
7972
- return path14.join(os9.homedir(), ".openclaw", "openclaw.json");
7365
+ return path13.join(os8.homedir(), ".openclaw", "openclaw.json");
7973
7366
  }
7974
7367
  function readConfig() {
7975
7368
  try {
7976
- return JSON.parse(fs11.readFileSync(configPath(), "utf-8"));
7369
+ return JSON.parse(fs10.readFileSync(configPath(), "utf-8"));
7977
7370
  } catch {
7978
7371
  return {};
7979
7372
  }
7980
7373
  }
7981
7374
  function writeConfig(cfg) {
7982
- fs11.writeFileSync(configPath(), JSON.stringify(cfg, null, 2) + `
7375
+ fs10.writeFileSync(configPath(), JSON.stringify(cfg, null, 2) + `
7983
7376
  `, "utf-8");
7984
7377
  }
7985
7378
 
7986
7379
  // gateway/providers.ts
7987
- import * as fs12 from "node:fs";
7988
- import * as os10 from "node:os";
7989
- import * as path15 from "node:path";
7380
+ import * as fs11 from "node:fs";
7381
+ import * as os9 from "node:os";
7382
+ import * as path14 from "node:path";
7990
7383
  function siblingAgentDirs() {
7991
- const root = path15.join(os10.homedir(), ".openclaw", "agents");
7384
+ const root = path14.join(os9.homedir(), ".openclaw", "agents");
7992
7385
  let entries;
7993
7386
  try {
7994
- entries = fs12.readdirSync(root, { withFileTypes: true });
7387
+ entries = fs11.readdirSync(root, { withFileTypes: true });
7995
7388
  } catch {
7996
7389
  return [];
7997
7390
  }
@@ -7999,9 +7392,9 @@ function siblingAgentDirs() {
7999
7392
  for (const entry of entries) {
8000
7393
  if (!entry.isDirectory() && !entry.isSymbolicLink())
8001
7394
  continue;
8002
- const dir = path15.join(root, entry.name, "agent");
7395
+ const dir = path14.join(root, entry.name, "agent");
8003
7396
  try {
8004
- if (fs12.statSync(dir).isDirectory())
7397
+ if (fs11.statSync(dir).isDirectory())
8005
7398
  out.push(dir);
8006
7399
  } catch {}
8007
7400
  }
@@ -8071,20 +7464,20 @@ async function ensureModels(run) {
8071
7464
  }
8072
7465
  function readOAuthExpiries() {
8073
7466
  const result = new Map;
8074
- const fs13 = __require("node:fs");
8075
- const path16 = __require("node:path");
8076
- const os11 = __require("node:os");
8077
- const agentsDir = path16.join(os11.homedir(), ".openclaw", "agents");
7467
+ const fs12 = __require("node:fs");
7468
+ const path15 = __require("node:path");
7469
+ const os10 = __require("node:os");
7470
+ const agentsDir = path15.join(os10.homedir(), ".openclaw", "agents");
8078
7471
  let entries;
8079
7472
  try {
8080
- entries = fs13.readdirSync(agentsDir);
7473
+ entries = fs12.readdirSync(agentsDir);
8081
7474
  } catch {
8082
7475
  return result;
8083
7476
  }
8084
7477
  for (const name of entries) {
8085
- const filePath = path16.join(agentsDir, name, "agent", "auth-profiles.json");
7478
+ const filePath = path15.join(agentsDir, name, "agent", "auth-profiles.json");
8086
7479
  try {
8087
- const raw = fs13.readFileSync(filePath, "utf-8");
7480
+ const raw = fs12.readFileSync(filePath, "utf-8");
8088
7481
  const data = JSON.parse(raw);
8089
7482
  const profiles = data?.profiles ?? {};
8090
7483
  for (const entry of Object.values(profiles)) {
@@ -8256,9 +7649,9 @@ function registerProvidersGateway(api, state2) {
8256
7649
  }
8257
7650
  writeConfig(cfg);
8258
7651
  for (const agentDir of siblingAgentDirs()) {
8259
- const file = path15.join(agentDir, "auth-profiles.json");
7652
+ const file = path14.join(agentDir, "auth-profiles.json");
8260
7653
  try {
8261
- const raw = fs12.readFileSync(file, "utf-8");
7654
+ const raw = fs11.readFileSync(file, "utf-8");
8262
7655
  const data = JSON.parse(raw);
8263
7656
  const agentProfiles = data?.profiles;
8264
7657
  if (!agentProfiles || typeof agentProfiles !== "object")
@@ -8275,7 +7668,7 @@ function registerProvidersGateway(api, state2) {
8275
7668
  changed = true;
8276
7669
  }
8277
7670
  if (changed)
8278
- fs12.writeFileSync(file, JSON.stringify(data, null, 2), "utf-8");
7671
+ fs11.writeFileSync(file, JSON.stringify(data, null, 2), "utf-8");
8279
7672
  } catch {}
8280
7673
  }
8281
7674
  respond(true, { provider, configured: false });
@@ -8365,22 +7758,22 @@ ${result?.stderr ?? ""}`;
8365
7758
  }
8366
7759
 
8367
7760
  // gateway/models-config.ts
8368
- import * as fs13 from "node:fs";
8369
- import * as os11 from "node:os";
8370
- import * as path16 from "node:path";
7761
+ import * as fs12 from "node:fs";
7762
+ import * as os10 from "node:os";
7763
+ import * as path15 from "node:path";
8371
7764
  function pluginConfigPath2() {
8372
- return path16.join(os11.homedir(), ".openclaw", "agentlife", "plugin-config.json");
7765
+ return path15.join(os10.homedir(), ".openclaw", "agentlife", "plugin-config.json");
8373
7766
  }
8374
7767
  function readPluginConfig2() {
8375
7768
  try {
8376
- return JSON.parse(fs13.readFileSync(pluginConfigPath2(), "utf-8"));
7769
+ return JSON.parse(fs12.readFileSync(pluginConfigPath2(), "utf-8"));
8377
7770
  } catch {
8378
7771
  return {};
8379
7772
  }
8380
7773
  }
8381
7774
  function writePluginConfig2(cfg) {
8382
- fs13.mkdirSync(path16.dirname(pluginConfigPath2()), { recursive: true });
8383
- fs13.writeFileSync(pluginConfigPath2(), JSON.stringify(cfg, null, 2) + `
7775
+ fs12.mkdirSync(path15.dirname(pluginConfigPath2()), { recursive: true });
7776
+ fs12.writeFileSync(pluginConfigPath2(), JSON.stringify(cfg, null, 2) + `
8384
7777
  `, "utf-8");
8385
7778
  }
8386
7779
  function registerModelsConfigGateway(api) {
@@ -8526,7 +7919,7 @@ var stopQualityCheckPoller = null;
8526
7919
  var stopCloudflared = null;
8527
7920
  function resolveInternalModel(api) {
8528
7921
  try {
8529
- const pluginCfgPath = path17.join(homedir13(), ".openclaw", "agentlife", "plugin-config.json");
7922
+ const pluginCfgPath = path16.join(homedir12(), ".openclaw", "agentlife", "plugin-config.json");
8530
7923
  try {
8531
7924
  const raw = __require("node:fs").readFileSync(pluginCfgPath, "utf-8");
8532
7925
  const pluginCfg = JSON.parse(raw);
@@ -8562,7 +7955,7 @@ function register(api) {
8562
7955
  return;
8563
7956
  }
8564
7957
  registered = true;
8565
- const fallbackDir = path17.join(homedir13(), ".openclaw", "agentlife");
7958
+ const fallbackDir = path16.join(homedir12(), ".openclaw", "agentlife");
8566
7959
  const state2 = {
8567
7960
  surfaceDb: null,
8568
7961
  agentRegistry: new Map,
@@ -8572,9 +7965,9 @@ function register(api) {
8572
7965
  agentDbs: new Map,
8573
7966
  historyDb: null,
8574
7967
  agentlifeStateDir: fallbackDir,
8575
- registryFilePath: path17.join(fallbackDir, "agent-registry.json"),
8576
- dbBaseDir: path17.join(fallbackDir, "db"),
8577
- historyDbPath: path17.join(fallbackDir, "agentlife.db"),
7968
+ registryFilePath: path16.join(fallbackDir, "agent-registry.json"),
7969
+ dbBaseDir: path16.join(fallbackDir, "db"),
7970
+ historyDbPath: path16.join(fallbackDir, "agentlife.db"),
8578
7971
  runCommand: api.runtime.system?.runCommandWithTimeout ?? null,
8579
7972
  enqueueSystemEvent: null,
8580
7973
  requestHeartbeatNow: null,
@@ -8605,7 +7998,7 @@ function register(api) {
8605
7998
  stopObservability = startObservabilityService(state2);
8606
7999
  stopDailySweep = startDailySweepService(state2);
8607
8000
  stopQualityCheckPoller = startQualityCheckPoller(state2);
8608
- await initColdStartMachine(state2, api.runtime, console.log);
8001
+ renderAllAgentWidgets(state2, api.runtime, console.log);
8609
8002
  }
8610
8003
  });
8611
8004
  registerConfigOptimizer(api, state2);
@@ -8632,7 +8025,6 @@ function register(api) {
8632
8025
  stopCloudflared();
8633
8026
  stopCloudflared = null;
8634
8027
  }
8635
- shutdownColdStartMachine();
8636
8028
  closeAllDbs(state2);
8637
8029
  }
8638
8030
  });
@@ -8652,7 +8044,7 @@ function register(api) {
8652
8044
  registerProvidersGateway(api, state2);
8653
8045
  registerModelsConfigGateway(api);
8654
8046
  registerWebApp(api);
8655
- const notifyConfigPath = path17.join(fallbackDir, "notification-config.json");
8047
+ const notifyConfigPath = path16.join(fallbackDir, "notification-config.json");
8656
8048
  api.registerGatewayMethod("agentlife.notifications.register", ({ params, respond }) => {
8657
8049
  const serverUrl = typeof params?.serverUrl === "string" ? params.serverUrl.trim() : "";
8658
8050
  const apiKey = typeof params?.apiKey === "string" ? params.apiKey.trim() : "";
@@ -8661,44 +8053,11 @@ function register(api) {
8661
8053
  }
8662
8054
  try {
8663
8055
  const { writeFileSync: writeFileSync10, mkdirSync: mkdirSync7 } = __require("node:fs");
8664
- mkdirSync7(path17.dirname(notifyConfigPath), { recursive: true });
8056
+ mkdirSync7(path16.dirname(notifyConfigPath), { recursive: true });
8665
8057
  writeFileSync10(notifyConfigPath, JSON.stringify({ serverUrl, apiKey }));
8666
8058
  } catch {}
8667
8059
  respond(true, { registered: true });
8668
8060
  }, { scope: "operator.write" });
8669
- api.registerGatewayMethod("agentlife.coldStart.observe", ({ respond }) => {
8670
- try {
8671
- if (!currentState) {
8672
- return respond(false, { error: "plugin state not initialized" });
8673
- }
8674
- respond(true, observeColdStart(currentState));
8675
- } catch (e) {
8676
- respond(false, { code: "internal_error", message: e?.message ?? String(e) });
8677
- }
8678
- }, { scope: "operator.read" });
8679
- api.registerGatewayMethod("agentlife.coldStart.retry", async ({ respond }) => {
8680
- try {
8681
- if (!currentState) {
8682
- return respond(false, { error: "plugin state not initialized" });
8683
- }
8684
- respond(true, await retryColdStart(currentState));
8685
- } catch (e) {
8686
- respond(false, { code: "internal_error", message: e?.message ?? String(e) });
8687
- }
8688
- }, { scope: "operator.read" });
8689
- api.registerGatewayMethod("agentlife.coldStart.advance", ({ params, respond }) => {
8690
- try {
8691
- if (!currentState)
8692
- return respond(false, { error: "plugin state not initialized" });
8693
- const kind = typeof params?.kind === "string" ? params.kind : null;
8694
- if (!kind)
8695
- return respond(false, { error: "missing kind" });
8696
- emitTrigger(currentState, kind, params?.payload ?? {});
8697
- respond(true, { enqueued: true });
8698
- } catch (e) {
8699
- respond(false, { code: "internal_error", message: e?.message ?? String(e) });
8700
- }
8701
- }, { scope: "operator.admin" });
8702
8061
  }
8703
8062
  export {
8704
8063
  register as default,