agentlife 2.4.2 → 2.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +33 -97
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1110,11 +1110,13 @@ Triggered when the orchestrator routes a "create X agent" / "track Y for me" / n
|
|
|
1110
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
1111
|
- \`HEARTBEAT.md\` — periodic check instructions, or empty placeholder \`# Keep empty to skip\`.
|
|
1112
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":{"
|
|
1113
|
+
\`exec openclaw gateway call agentlife.createAgent --params '{"id":"{agentId}","name":"{Name}","model":"{model}","workspace":"/abs/path","description":"one sentence","tools":{"allow":["*","agentlife_push"]},"identity":{"name":"{Name}","emoji":"{emoji}"}}'\`
|
|
1114
1114
|
- Description drives the orchestrator's routing — make it specific (domains, actions, data types).
|
|
1115
|
-
- \`tools: {
|
|
1116
|
-
|
|
1117
|
-
|
|
1115
|
+
- \`tools: {allow:["*","agentlife_push"]}\` for agents needing exec/web/write + widget push. \`{allow:["agentlife_push"]}\` for widget-only agents. Never use \`profile:"full"\` with \`alsoAllow\` — OpenClaw's policy pipeline strips plugin tools through the intersection.
|
|
1116
|
+
5. **Hand off in ONE final turn, all in this order, before \`done\`:**
|
|
1117
|
+
a. Push ONE actionable first widget for the newly-created agent under a surfaceId you own (\`{agentId}-today\`, \`{agentId}-start\`, similar). It must invite the FIRST user interaction — an input prompt, a starter metric with a CTA, a question the user can answer right now. NEVER an identity/confirmation card ("Agent ready", "Welcome to X"). The plugin no longer pushes a placeholder intro widget; this first push IS the dashboard's anchor for the new agent.
|
|
1118
|
+
b. \`delete {domain}-building\` — the loading widget from step 1.
|
|
1119
|
+
c. Emit \`done\`.
|
|
1118
1120
|
|
|
1119
1121
|
## Writing AGENTS.md (for the specialist you create)
|
|
1120
1122
|
|
|
@@ -1438,7 +1440,26 @@ async function provisionAgents(state, cfg, runtime, log) {
|
|
|
1438
1440
|
globalAllowWritten = true;
|
|
1439
1441
|
}
|
|
1440
1442
|
}
|
|
1441
|
-
|
|
1443
|
+
const currentAlsoAllow = Array.isArray(rawCfgForVisibility?.tools?.alsoAllow) ? rawCfgForVisibility.tools.alsoAllow : [];
|
|
1444
|
+
let alsoAllowWritten = false;
|
|
1445
|
+
if (!currentAlsoAllow.includes("agentlife_push")) {
|
|
1446
|
+
try {
|
|
1447
|
+
let backup = {};
|
|
1448
|
+
try {
|
|
1449
|
+
backup = JSON.parse(readFileSync(backupPath, "utf-8"));
|
|
1450
|
+
} catch {}
|
|
1451
|
+
if (backup.toolsAlsoAllow === undefined) {
|
|
1452
|
+
backup.toolsAlsoAllow = [...currentAlsoAllow];
|
|
1453
|
+
writeFileSync(backupPath, JSON.stringify(backup, null, 2) + `
|
|
1454
|
+
`, "utf-8");
|
|
1455
|
+
}
|
|
1456
|
+
} catch {}
|
|
1457
|
+
if (!rawCfgForVisibility.tools)
|
|
1458
|
+
rawCfgForVisibility.tools = {};
|
|
1459
|
+
rawCfgForVisibility.tools.alsoAllow = [...currentAlsoAllow, "agentlife_push"];
|
|
1460
|
+
alsoAllowWritten = true;
|
|
1461
|
+
}
|
|
1462
|
+
if (visibilityWritten || a2aWritten || globalAllowWritten || alsoAllowWritten) {
|
|
1442
1463
|
writeFileSync(configPath, JSON.stringify(rawCfgForVisibility, null, 2) + `
|
|
1443
1464
|
`, "utf-8");
|
|
1444
1465
|
configChanged = true;
|
|
@@ -1448,6 +1469,8 @@ async function provisionAgents(state, cfg, runtime, log) {
|
|
|
1448
1469
|
log("[agentlife] set tools.agentToAgent.enabled=true (cross-agent sends)");
|
|
1449
1470
|
if (globalAllowWritten)
|
|
1450
1471
|
log('[agentlife] added "*" to tools.allow (unblock exec/read/write for provisioned agents)');
|
|
1472
|
+
if (alsoAllowWritten)
|
|
1473
|
+
log("[agentlife] added agentlife_push to tools.alsoAllow (tool-policy pipeline intersection pass-through)");
|
|
1451
1474
|
}
|
|
1452
1475
|
if (configChanged) {
|
|
1453
1476
|
const configPath2 = path3.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
@@ -2726,91 +2749,13 @@ function snapshotOnboarding(state, runtime) {
|
|
|
2726
2749
|
}
|
|
2727
2750
|
|
|
2728
2751
|
// render-widgets.ts
|
|
2729
|
-
function recentActivityCount(state, agentId, sinceMs) {
|
|
2730
|
-
if (!state.historyDb)
|
|
2731
|
-
return 0;
|
|
2732
|
-
try {
|
|
2733
|
-
const row = state.historyDb.prepare("SELECT COUNT(*) as c FROM activity_log WHERE agentId = ? AND ts > ?").get(agentId, sinceMs);
|
|
2734
|
-
return row?.c ?? 0;
|
|
2735
|
-
} catch {
|
|
2736
|
-
return 0;
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
function pendingFollowupCount(state, agentId) {
|
|
2740
|
-
if (!state.historyDb)
|
|
2741
|
-
return 0;
|
|
2742
|
-
try {
|
|
2743
|
-
const row = state.historyDb.prepare("SELECT COUNT(*) as c FROM followups WHERE agentId = ? AND status = 'pending'").get(agentId);
|
|
2744
|
-
return row?.c ?? 0;
|
|
2745
|
-
} catch {
|
|
2746
|
-
return 0;
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
function sanitizeDslString(s) {
|
|
2750
|
-
return s.replace(/"/g, "'").replace(/[\r\n]+/g, " ").slice(0, 120);
|
|
2751
|
-
}
|
|
2752
|
-
function composeAgentIntroDsl(opts) {
|
|
2753
|
-
const surfaceId = `${opts.agentId}-intro`;
|
|
2754
|
-
const title = `${opts.emoji} ${opts.name}`;
|
|
2755
|
-
const desc = sanitizeDslString(opts.description || `Your ${opts.name} agent, ready to track and act.`);
|
|
2756
|
-
const activeBadge = opts.activityCount > 0 ? "Active" : "Ready";
|
|
2757
|
-
const activeColor = opts.activityCount > 0 ? "#10B981" : "#6366F1";
|
|
2758
|
-
return [
|
|
2759
|
-
`surface ${surfaceId} size=m`,
|
|
2760
|
-
` card`,
|
|
2761
|
-
` column`,
|
|
2762
|
-
` text "${sanitizeDslString(title)}" h3`,
|
|
2763
|
-
` text "${desc}" body`,
|
|
2764
|
-
` divider`,
|
|
2765
|
-
` row distribute=spaceBetween`,
|
|
2766
|
-
` metric "Actions (7d)" "${opts.activityCount}"`,
|
|
2767
|
-
` metric "Upcoming" "${opts.followupCount}"`,
|
|
2768
|
-
` badge "${activeBadge}" color=${activeColor} outlined`,
|
|
2769
|
-
`goal: ${opts.name} — track progress toward user's first real outcome`,
|
|
2770
|
-
`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."`,
|
|
2771
|
-
`context: {"agentId":"${opts.agentId}","domain":"${opts.agentId}","phase":"intro","autoRendered":true}`
|
|
2772
|
-
].join(`
|
|
2773
|
-
`);
|
|
2774
|
-
}
|
|
2775
|
-
function agentEmoji(runtime, agentId) {
|
|
2776
|
-
const cfg = runtime.config.loadConfig();
|
|
2777
|
-
const list = cfg?.agents?.list ?? [];
|
|
2778
|
-
const found = list.find((a) => a?.id === agentId);
|
|
2779
|
-
return found?.identity?.emoji ?? "\uD83C\uDFAF";
|
|
2780
|
-
}
|
|
2781
|
-
function renderAgentWidget(state, runtime, agentId, log) {
|
|
2782
|
-
if (!userAgentIds(runtime).includes(agentId))
|
|
2783
|
-
return;
|
|
2784
|
-
const entry = state.agentRegistry.get(agentId);
|
|
2785
|
-
if (!entry) {
|
|
2786
|
-
log(`[render-widgets] ${agentId}: no registry entry, skipping`);
|
|
2787
|
-
return;
|
|
2788
|
-
}
|
|
2789
|
-
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
2790
|
-
const dsl = composeAgentIntroDsl({
|
|
2791
|
-
agentId,
|
|
2792
|
-
name: entry.name,
|
|
2793
|
-
description: entry.description ?? "",
|
|
2794
|
-
emoji: agentEmoji(runtime, agentId),
|
|
2795
|
-
activityCount: recentActivityCount(state, agentId, sevenDaysAgo),
|
|
2796
|
-
followupCount: pendingFollowupCount(state, agentId)
|
|
2797
|
-
});
|
|
2798
|
-
pluginPushSurface(state, { agentId, dsl });
|
|
2799
|
-
log(`[render-widgets] rendered ${agentId}-intro`);
|
|
2800
|
-
}
|
|
2801
2752
|
function renderAllAgentWidgets(state, runtime, log) {
|
|
2802
2753
|
if (isOnboarding(state, runtime)) {
|
|
2803
2754
|
log("[render-widgets] onboarding active — rendering welcome widget");
|
|
2804
2755
|
renderWelcomeWidget(state, log);
|
|
2805
2756
|
return;
|
|
2806
2757
|
}
|
|
2807
|
-
|
|
2808
|
-
try {
|
|
2809
|
-
renderAgentWidget(state, runtime, id, log);
|
|
2810
|
-
} catch (e) {
|
|
2811
|
-
log(`[render-widgets] ${id} render failed: ${e?.message ?? e}`);
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2758
|
+
log("[render-widgets] onboarding complete — no plugin-pushed widgets; specialists own their surfaces");
|
|
2814
2759
|
}
|
|
2815
2760
|
var WELCOME_SURFACE_ID = "welcome";
|
|
2816
2761
|
var WELCOME_INPUT_SURFACE_ID = "welcome-input";
|
|
@@ -2830,12 +2775,12 @@ Each turn:
|
|
|
2830
2775
|
- updated \`context: {"phase":"welcome","turn":N+1,"answers":[...]}\`
|
|
2831
2776
|
- goal + followup unchanged
|
|
2832
2777
|
Respond \`done\`.
|
|
2833
|
-
3. Enough → create the generalist:
|
|
2778
|
+
3. Enough → create the generalist and hand off the dashboard:
|
|
2834
2779
|
- Workspace under \`$HOME/.openclaw/workspace-{id}\` (id = slugified short name or "me")
|
|
2835
2780
|
- \`AGENTS.md\` — generalist scope covering mentioned domains; Data Schema must include a \`domain TEXT NOT NULL\` column on every user-data table so future split by domain is trivial; add self-evolution clause ("when activity_log rows for one domain exceed a natural threshold, push a split-suggest widget")
|
|
2836
2781
|
- \`SOUL.md\`, \`IDENTITY.md\`, \`USER.md\` (user's own words), \`HEARTBEAT.md\` (empty)
|
|
2837
|
-
- \`exec openclaw gateway call agentlife.createAgent --params '{...}'\` with \`tools: {
|
|
2838
|
-
-
|
|
2782
|
+
- \`exec openclaw gateway call agentlife.createAgent --params '{...}'\` with \`tools: {allow: ["*", "agentlife_push"]}\`. Plugin auto-deletes welcome + welcome-input on success.
|
|
2783
|
+
- **Final step, same turn, before \`done\`:** push ONE actionable first widget for the newly-created agent — an input prompt ("Log your first meal", "Enter today's weight"), a starter metric, or an inviting CTA that owns a surfaceId like \`{id}-today\` / \`{id}-start\`. This is what the user sees when onboarding ends; it MUST invite interaction, not just announce existence. Then delete your \`{domain}-building\` loading widget. Then emit \`done\`.
|
|
2839
2784
|
`;
|
|
2840
2785
|
function renderWelcomeWidget(state, log) {
|
|
2841
2786
|
const welcomeDsl = [
|
|
@@ -2877,14 +2822,6 @@ function deleteWelcomeWidget(state, log) {
|
|
|
2877
2822
|
log(`[render-widgets] deleted ${id}`);
|
|
2878
2823
|
}
|
|
2879
2824
|
}
|
|
2880
|
-
if (state.runCommand) {
|
|
2881
|
-
const deleteParams = JSON.stringify({ key: ONBOARDING_SESSION_KEY, deleteTranscript: true });
|
|
2882
|
-
state.runCommand(["openclaw", "gateway", "call", "sessions.delete", "--params", deleteParams], { timeoutMs: 1e4 }).then(() => {
|
|
2883
|
-
log(`[render-widgets] deleted onboarding session`);
|
|
2884
|
-
}).catch((e) => {
|
|
2885
|
-
console.log("[agentlife] onboarding session delete skipped: %s", e?.message);
|
|
2886
|
-
});
|
|
2887
|
-
}
|
|
2888
2825
|
}
|
|
2889
2826
|
|
|
2890
2827
|
// services/surfaces-init.ts
|
|
@@ -6235,10 +6172,9 @@ function registerAgentGateway(api, state2) {
|
|
|
6235
6172
|
configFields.push("identity");
|
|
6236
6173
|
if (!existing && userAgentIds(api.runtime).includes(id)) {
|
|
6237
6174
|
try {
|
|
6238
|
-
renderAgentWidget(state2, api.runtime, id, console.log);
|
|
6239
6175
|
deleteWelcomeWidget(state2, console.log);
|
|
6240
6176
|
} catch (e) {
|
|
6241
|
-
console.warn("[agentlife]
|
|
6177
|
+
console.warn("[agentlife] deleteWelcomeWidget failed for %s: %s", id, e?.message);
|
|
6242
6178
|
}
|
|
6243
6179
|
}
|
|
6244
6180
|
respond(true, { status, id, name, model, workspace, description, ...configFields.length ? { configFields } : {} });
|