agentlife 2.6.24 → 2.6.26

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 +359 -48
  2. package/package.json +8 -2
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 homedir14 } from "node:os";
6
- import * as path17 from "node:path";
5
+ import { homedir as homedir15 } from "node:os";
6
+ import * as path18 from "node:path";
7
7
  import { existsSync as existsSync7 } from "node:fs";
8
8
 
9
9
  // db.ts
@@ -424,7 +424,8 @@ function getOrCreateHistoryDb(state) {
424
424
  agentId TEXT NOT NULL,
425
425
  filename TEXT NOT NULL,
426
426
  content TEXT NOT NULL,
427
- createdAt INTEGER NOT NULL
427
+ createdAt INTEGER NOT NULL,
428
+ note TEXT
428
429
  );
429
430
  CREATE INDEX IF NOT EXISTS idx_bv_agent ON bootstrap_versions(agentId, filename, createdAt DESC);
430
431
 
@@ -449,6 +450,9 @@ function getOrCreateHistoryDb(state) {
449
450
  );
450
451
  CREATE INDEX IF NOT EXISTS idx_pqc_createdAt ON pending_quality_checks(createdAt);
451
452
  `);
453
+ try {
454
+ state.historyDb.exec(`ALTER TABLE bootstrap_versions ADD COLUMN note TEXT`);
455
+ } catch {}
452
456
  SurfaceIndex.createSchema(state.historyDb);
453
457
  FollowupQueue.createSchema(state.historyDb);
454
458
  const retentionMs = 90 * 24 * 60 * 60 * 1000;
@@ -1085,6 +1089,45 @@ The platform tracks your health: \`healthy\` → \`watch\` → \`review\`.
1085
1089
  - Max 3 bootstrap rewrites per 7 days — the platform blocks excess rewrites.
1086
1090
  - 48h post-rewrite validation: if dismiss rate increases >20 points or followup effectiveness drops >25 points, the rewrite is flagged as regression and Supervisor is notified for rollback.
1087
1091
  - If your metrics have drifted significantly from baseline, further rewrites are flagged. Stabilize before rewriting again.
1092
+
1093
+ ### Annotating Refinements (\`agentlife.evolutions.note\`)
1094
+
1095
+ When a rewrite meaningfully changes how you behave for the user, immediately call \`agentlife.evolutions.note\` with a one-line plain-language summary. The note attaches to the snapshot of the file taken before your rewrite and surfaces in the user's Refinements rail on your agent detail page. The user reads it to understand what you changed and why.
1096
+
1097
+ Params: \`{ agentId, filename, note }\`. Filename must be one of \`AGENTS.md\`, \`USER.md\`, \`SOUL.md\`, \`TOOLS.md\`, \`IDENTITY.md\`. Note is capped at 280 characters.
1098
+
1099
+ Notes must:
1100
+ - Be written for the user, not for you. Plain language only — no internal terms like signal weights, drift heuristics, or rule names. Describe the user-visible behavior change.
1101
+ - Name the concrete behavior shift and the trigger that prompted it. The user wants to know what's different now and why you decided to change it.
1102
+ - Be skipped entirely for housekeeping rewrites (formatting, dead-rule cleanup, comment edits). Silent rewrites are still snapshotted for audit but won't surface in the rail. Don't pad the rail with noise — the bar is "the user would notice this change in their next interaction with me".
1103
+
1104
+ ### Handling \`user_reverted\`
1105
+
1106
+ When you receive a \`[system] User reverted ...\` message on this session, the user has rolled back one of your bootstrap files to a prior version. Their judgment is authoritative — they preferred the older version to whatever you wrote.
1107
+
1108
+ 1. Read the current file (now the restored older content) and recall what you changed in the rewrite they undid.
1109
+ 2. Treat this as a strong signal that the change you made was wrong for this user, even if your metrics looked fine.
1110
+ 3. Do not re-rewrite the same change. If a similar refinement is genuinely needed later, it must take a different shape — different framing, different trigger, different scope.
1111
+ 4. If the message includes a \`User note\`, weight it heavily — it's the qualitative reason the metrics didn't capture.
1112
+ 5. Respond with a short text summary of what you understood and what you'll do differently. Terminate with \`NO_REPLY\`.
1113
+
1114
+ Do not call \`agentlife.evolutions.restore\` from here — restores are a user action, never an agent action.
1115
+
1116
+ ### Handling \`Rewrite regression detected\` (Supervisor only)
1117
+
1118
+ When a \`[system] Rewrite regression detected for {agentId}/{filename}\` message arrives, the platform's 48h validation has determined that a recent rewrite worsened the affected agent's metrics. The user does not yet know.
1119
+
1120
+ Your job in this internal session is to relay a structured handoff to the affected agent's DIRECT session so it can surface the regression to the user as a widget. Do NOT push a widget yourself — this internal session has no Widget DSL catalog, and the affected agent owns its own surfaces.
1121
+
1122
+ 1. Parse the message: extract \`agentId\`, \`filename\`, the before/after dismiss rate and followup percentage.
1123
+ 2. Look up the most recent snapshot's \`versionId\` for that file via \`agentlife.evolutions.list { agentId, filename, limit: 1 }\` — this is the version the user would restore.
1124
+ 3. Send a structured handoff to the affected agent's direct session via \`sessions_send\` with \`sessionKey="agent:{agentId}:agentlife:direct:operator"\` and a message of the exact form:
1125
+ \`\`\`
1126
+ [system] regression-revert filename={filename} versionId={N} dismissBefore={X} dismissAfter={Y} followupBefore={Z} followupAfter={W}
1127
+ \`\`\`
1128
+ 4. Use \`timeoutSeconds=0\` (fire-and-forget). Then \`NO_REPLY\`.
1129
+
1130
+ Do not push your own widget, do not summarize the regression to the user, do not modify the affected agent's files. Your role here is the relay only — the affected agent decides how to frame the regression for its user.
1088
1131
  `;
1089
1132
  var WIDGET_DSL_GUIDANCE = `## WidgetDSL — How to Build Widgets
1090
1133
 
@@ -1229,6 +1272,41 @@ The platform provides two storage systems. Do NOT use OpenClaw memory files (mem
1229
1272
  - OpenClaw memory files (memory_search, memory_get, memory/*.md)
1230
1273
  - Workspace files for state
1231
1274
  - Session history as knowledge
1275
+
1276
+ ### Handling \`[system] regression-revert\`
1277
+
1278
+ The Supervisor relays this when the platform's 48h post-rewrite validation determines that a recent rewrite of one of YOUR bootstrap files worsened your metrics. The user does not yet know.
1279
+
1280
+ Message shape:
1281
+ \`\`\`
1282
+ [system] regression-revert filename={filename} versionId={N} dismissBefore={X} dismissAfter={Y} followupBefore={Z} followupAfter={W}
1283
+ \`\`\`
1284
+
1285
+ Push exactly one widget for this — same surfaceId is reserved for this purpose, never reuse it for other goals:
1286
+
1287
+ \`\`\`
1288
+ surface {agentId}-regression-revert size=m priority=high
1289
+ card
1290
+ column
1291
+ text "I think a recent change made things worse" h3
1292
+ row distribute=spaceBetween
1293
+ metric "Dismiss" "{X}% → {Y}%"
1294
+ metric "Followups" "{Z}% → {W}%"
1295
+ divider
1296
+ text "I rewrote {filename} and the numbers got worse, not better. Revert returns to the prior version. Keep dismisses this notice and lets me try a different approach next time." body
1297
+ row distribute=spaceBetween
1298
+ button "Revert" action=revert primary
1299
+ button "Keep" action=keep
1300
+ goal: Decide whether to revert the recent {filename} rewrite based on the user's judgment, since metrics suggest it was worse.
1301
+ detail:
1302
+ Plain-language summary of what changed in your last rewrite of {filename}, in user-facing terms (no signal-weight or metric jargon). Include the dismiss-rate and followup-effectiveness deltas. Do not blame the user.
1303
+ followup: +24h "If still alive, the user has neither reverted nor explicitly kept. Re-evaluate metrics — if they've recovered, delete this widget; if still degraded, keep it but do not nag."
1304
+ context: {"filename":"{filename}","versionId":{N}}
1305
+ \`\`\`
1306
+
1307
+ Set the version into \`context:\` so the action handler can read it back. When you receive \`[action:revert]\` on this surface: call \`agentlife.evolutions.restore { versionId, userNote: null }\` (the user already gave their judgment by clicking — no extra note needed), then delete the surface in the same turn. When you receive \`[action:keep]\`: just delete the surface — no platform call needed; the regression flag remains in the audit trail but you stop asking.
1308
+
1309
+ Do not push a regression-revert widget without receiving the \`[system] regression-revert\` message — it must be triggered by the platform, never speculative.
1232
1310
  `;
1233
1311
  var DISMISS_ALTERNATIVES_GUIDANCE = `### Crafting Dismiss Alternatives
1234
1312
 
@@ -6322,33 +6400,80 @@ function loadConfig() {
6322
6400
  }
6323
6401
  function notifyWidgetEvent(_state, event, surfaceId, title, body) {
6324
6402
  broadcastNotification(event, surfaceId, title, body);
6403
+ postToMobile(event, surfaceId, title, body).catch((err) => {
6404
+ console.warn("[agentlife:notify] mobile push pipeline error: %s", err?.message ?? err);
6405
+ });
6406
+ }
6407
+ async function postToMobile(event, surfaceId, title, body) {
6408
+ const identity = loadDeviceIdentity();
6409
+ let deliveredViaTunnels = false;
6410
+ if (identity) {
6411
+ deliveredViaTunnels = await tryTunnelsNotify(identity.deviceId, identity.deviceSecret, event, surfaceId, title, body);
6412
+ } else {
6413
+ console.log("[agentlife:notify] no device identity — skipping /tunnels/notify");
6414
+ }
6415
+ if (!deliveredViaTunnels) {
6416
+ await tryApiKeyNotify(event, surfaceId, title, body);
6417
+ }
6418
+ }
6419
+ async function tryTunnelsNotify(deviceId, deviceSecret, event, surfaceId, title, body) {
6420
+ const url = `${AGENTLIFE_API_BASE.replace(/\/+$/, "")}/api/v1/tunnels/notify`;
6421
+ try {
6422
+ const res = await fetch(url, {
6423
+ method: "POST",
6424
+ headers: { "Content-Type": "application/json" },
6425
+ body: JSON.stringify({
6426
+ deviceId,
6427
+ deviceSecret,
6428
+ gatewayId: deviceId,
6429
+ kind: event,
6430
+ title,
6431
+ body,
6432
+ data: { surfaceId }
6433
+ }),
6434
+ signal: AbortSignal.timeout(1e4)
6435
+ });
6436
+ if (!res.ok) {
6437
+ console.warn("[agentlife:notify] /tunnels/notify status=%d event=%s surfaceId=%s", res.status, event, surfaceId);
6438
+ return false;
6439
+ }
6440
+ const parsed = await res.json().catch(() => ({}));
6441
+ if (parsed.skipped === "unclaimed") {
6442
+ console.log("[agentlife:notify] /tunnels/notify unclaimed event=%s surfaceId=%s — trying apiKey fallback", event, surfaceId);
6443
+ return false;
6444
+ }
6445
+ const delivered = parsed.delivered ?? 0;
6446
+ console.log("[agentlife:notify] /tunnels/notify event=%s surfaceId=%s delivered=%d", event, surfaceId, delivered);
6447
+ return delivered > 0;
6448
+ } catch (err) {
6449
+ console.warn("[agentlife:notify] /tunnels/notify error: %s", err?.message ?? err);
6450
+ return false;
6451
+ }
6452
+ }
6453
+ async function tryApiKeyNotify(event, surfaceId, title, body) {
6325
6454
  const cfg = loadConfig();
6326
6455
  if (!cfg)
6327
6456
  return;
6328
- console.log("[agentlife:notify] fire event=%s surfaceId=%s title=%s", event, surfaceId, title);
6329
6457
  const url = `${cfg.serverUrl.replace(/\/+$/, "")}/api/v1/notifications/send`;
6330
- fetch(url, {
6331
- method: "POST",
6332
- headers: {
6333
- "Content-Type": "application/json",
6334
- Authorization: `Bearer ${cfg.apiKey}`
6335
- },
6336
- body: JSON.stringify({ title, body, data: { event, surfaceId } }),
6337
- signal: AbortSignal.timeout(1e4)
6338
- }).then(async (res) => {
6458
+ try {
6459
+ const res = await fetch(url, {
6460
+ method: "POST",
6461
+ headers: {
6462
+ "Content-Type": "application/json",
6463
+ Authorization: `Bearer ${cfg.apiKey}`
6464
+ },
6465
+ body: JSON.stringify({ title, body, data: { event, surfaceId } }),
6466
+ signal: AbortSignal.timeout(1e4)
6467
+ });
6339
6468
  if (!res.ok) {
6340
- console.warn("[agentlife:notify] Server responded %d for %s (event=%s)", res.status, surfaceId, event);
6469
+ console.warn("[agentlife:notify] /notifications/send status=%d event=%s surfaceId=%s", res.status, event, surfaceId);
6341
6470
  return;
6342
6471
  }
6343
- try {
6344
- const parsed = await res.json();
6345
- console.log("[agentlife:notify] sent event=%s surfaceId=%s messageId=%s", event, surfaceId, parsed.messageId ?? "(none)");
6346
- } catch {
6347
- console.log("[agentlife:notify] sent event=%s surfaceId=%s (no body)", event, surfaceId);
6348
- }
6349
- }).catch((err) => {
6350
- console.warn("[agentlife:notify] Server failed for %s (event=%s): %s", surfaceId, event, err?.message);
6351
- });
6472
+ const parsed = await res.json().catch(() => null);
6473
+ console.log("[agentlife:notify] /notifications/send event=%s surfaceId=%s messageId=%s", event, surfaceId, parsed?.messageId ?? "(none)");
6474
+ } catch (err) {
6475
+ console.warn("[agentlife:notify] /notifications/send error: %s", err?.message ?? err);
6476
+ }
6352
6477
  }
6353
6478
 
6354
6479
  // tools/widget-push.ts
@@ -7830,6 +7955,17 @@ function registerAdminGateway(api, state2) {
7830
7955
  respond(false, { error: err?.message ?? "quality metrics unavailable" });
7831
7956
  }
7832
7957
  }, { scope: "operator.read" });
7958
+ api.registerGatewayMethod("agentlife.health.list", ({ respond }) => {
7959
+ try {
7960
+ const agents = {};
7961
+ for (const [agentId, h] of agentHealth.entries()) {
7962
+ agents[agentId] = { state: h.state, enteredAt: h.enteredAt };
7963
+ }
7964
+ respond(true, { agents });
7965
+ } catch (err) {
7966
+ respond(false, undefined, { code: "HEALTH_LIST_FAILED", message: err?.message ?? "health list failed" });
7967
+ }
7968
+ }, { scope: "operator.read" });
7833
7969
  api.registerGatewayMethod("agentlife.observability.runCycle", ({ params, respond }) => {
7834
7970
  try {
7835
7971
  const day = typeof params?.day === "string" && /^\d{4}-\d{2}-\d{2}$/.test(params.day) ? params.day : new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
@@ -8039,6 +8175,180 @@ function registerAdminGateway(api, state2) {
8039
8175
  }, { scope: "operator.write" });
8040
8176
  }
8041
8177
 
8178
+ // gateway/evolutions.ts
8179
+ import * as fsSync5 from "node:fs";
8180
+ import * as os9 from "node:os";
8181
+ import * as path14 from "node:path";
8182
+ var ALLOWED_FILENAMES = new Set(["AGENTS.md", "USER.md", "SOUL.md", "TOOLS.md", "IDENTITY.md"]);
8183
+ function workspaceFilePath(agentId, filename) {
8184
+ return path14.join(os9.homedir(), ".openclaw", `workspace-${agentId}`, filename);
8185
+ }
8186
+ function hasRegressionFlag(db, agentId, filename, snapshotAt) {
8187
+ const horizon = snapshotAt + 7 * 24 * 60 * 60 * 1000;
8188
+ const row = db.prepare(`SELECT 1 FROM activity_log
8189
+ WHERE event='quality_warning' AND agentId = ? AND ts BETWEEN ? AND ?
8190
+ AND data LIKE '%"rewrite_regression"%' AND data LIKE ?
8191
+ LIMIT 1`).get(agentId, snapshotAt, horizon, `%"filename":"${filename}"%`);
8192
+ return !!row;
8193
+ }
8194
+ function registerEvolutionsGateway(api, state2) {
8195
+ api.registerGatewayMethod("agentlife.evolutions.list", ({ params, respond }) => {
8196
+ try {
8197
+ const agentId = String(params?.agentId ?? "").trim();
8198
+ if (!agentId) {
8199
+ respond(false, undefined, { code: "BAD_PARAMS", message: "agentId required" });
8200
+ return;
8201
+ }
8202
+ const filename = typeof params?.filename === "string" ? params.filename.trim() : null;
8203
+ const limit = Math.min(Math.max(Number(params?.limit) || 50, 1), 100);
8204
+ const db = getOrCreateHistoryDb(state2);
8205
+ const rows = filename ? db.prepare(`SELECT id, agentId, filename, createdAt, note, length(content) AS charCount
8206
+ FROM bootstrap_versions WHERE agentId = ? AND filename = ?
8207
+ ORDER BY createdAt DESC LIMIT ?`).all(agentId, filename, limit) : db.prepare(`SELECT id, agentId, filename, createdAt, note, length(content) AS charCount
8208
+ FROM bootstrap_versions WHERE agentId = ?
8209
+ ORDER BY createdAt DESC LIMIT ?`).all(agentId, limit);
8210
+ const versions = rows.map((r) => ({
8211
+ versionId: r.id,
8212
+ agentId: r.agentId,
8213
+ filename: r.filename,
8214
+ createdAt: r.createdAt,
8215
+ charCount: r.charCount,
8216
+ note: r.note ?? null,
8217
+ regressionFlag: hasRegressionFlag(db, r.agentId, r.filename, r.createdAt)
8218
+ }));
8219
+ respond(true, { versions });
8220
+ } catch (err) {
8221
+ respond(false, undefined, { code: "EVOLUTIONS_LIST_FAILED", message: err?.message ?? "list failed" });
8222
+ }
8223
+ }, { scope: "operator.read" });
8224
+ api.registerGatewayMethod("agentlife.evolutions.get", ({ params, respond }) => {
8225
+ try {
8226
+ const versionId = Number(params?.versionId);
8227
+ if (!Number.isFinite(versionId) || versionId <= 0) {
8228
+ respond(false, undefined, { code: "BAD_PARAMS", message: "versionId required" });
8229
+ return;
8230
+ }
8231
+ const db = getOrCreateHistoryDb(state2);
8232
+ const row = db.prepare(`SELECT id, agentId, filename, content, createdAt, note FROM bootstrap_versions WHERE id = ?`).get(versionId);
8233
+ if (!row) {
8234
+ respond(false, undefined, { code: "NOT_FOUND", message: `version ${versionId} not found` });
8235
+ return;
8236
+ }
8237
+ respond(true, {
8238
+ versionId: row.id,
8239
+ agentId: row.agentId,
8240
+ filename: row.filename,
8241
+ content: row.content,
8242
+ createdAt: row.createdAt,
8243
+ note: row.note ?? null
8244
+ });
8245
+ } catch (err) {
8246
+ respond(false, undefined, { code: "EVOLUTIONS_GET_FAILED", message: err?.message ?? "get failed" });
8247
+ }
8248
+ }, { scope: "operator.read" });
8249
+ api.registerGatewayMethod("agentlife.evolutions.recent", ({ params, respond }) => {
8250
+ try {
8251
+ const agentId = String(params?.agentId ?? "").trim();
8252
+ if (!agentId) {
8253
+ respond(false, undefined, { code: "BAD_PARAMS", message: "agentId required" });
8254
+ return;
8255
+ }
8256
+ const limit = Math.min(Math.max(Number(params?.limit) || 5, 1), 20);
8257
+ const db = getOrCreateHistoryDb(state2);
8258
+ const rows = db.prepare(`SELECT id, agentId, filename, createdAt, note FROM bootstrap_versions
8259
+ WHERE agentId = ? AND note IS NOT NULL AND note != ''
8260
+ ORDER BY createdAt DESC LIMIT ?`).all(agentId, limit);
8261
+ const versions = rows.map((r) => ({
8262
+ versionId: r.id,
8263
+ agentId: r.agentId,
8264
+ filename: r.filename,
8265
+ createdAt: r.createdAt,
8266
+ note: r.note
8267
+ }));
8268
+ respond(true, { versions });
8269
+ } catch (err) {
8270
+ respond(false, undefined, { code: "EVOLUTIONS_RECENT_FAILED", message: err?.message ?? "recent failed" });
8271
+ }
8272
+ }, { scope: "operator.read" });
8273
+ api.registerGatewayMethod("agentlife.evolutions.restore", ({ params, respond }) => {
8274
+ try {
8275
+ const versionId = Number(params?.versionId);
8276
+ if (!Number.isFinite(versionId) || versionId <= 0) {
8277
+ respond(false, undefined, { code: "BAD_PARAMS", message: "versionId required" });
8278
+ return;
8279
+ }
8280
+ const userNote = typeof params?.userNote === "string" ? params.userNote.trim().slice(0, 500) : null;
8281
+ const db = getOrCreateHistoryDb(state2);
8282
+ const row = db.prepare(`SELECT id, agentId, filename, content, createdAt FROM bootstrap_versions WHERE id = ?`).get(versionId);
8283
+ if (!row) {
8284
+ respond(false, undefined, { code: "NOT_FOUND", message: `version ${versionId} not found` });
8285
+ return;
8286
+ }
8287
+ if (!ALLOWED_FILENAMES.has(row.filename)) {
8288
+ respond(false, undefined, { code: "FORBIDDEN", message: `cannot restore ${row.filename}` });
8289
+ return;
8290
+ }
8291
+ const currentSnap = db.prepare(`SELECT id, createdAt FROM bootstrap_versions WHERE agentId = ? AND filename = ?
8292
+ ORDER BY createdAt DESC LIMIT 1`).get(row.agentId, row.filename);
8293
+ const filePath = workspaceFilePath(row.agentId, row.filename);
8294
+ fsSync5.writeFileSync(filePath, row.content, "utf-8");
8295
+ const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
8296
+ const currentMetrics = computeEnhancedMetrics(db, row.agentId, sevenDaysAgo);
8297
+ const payload = {
8298
+ issue: "user_reverted",
8299
+ filename: row.filename,
8300
+ restoredVersionId: row.id,
8301
+ revertedFromVersionId: currentSnap?.id ?? null,
8302
+ currentMetrics: currentMetrics ?? null,
8303
+ userNote
8304
+ };
8305
+ recordActivity(state2, "quality_warning", null, row.agentId, { data: JSON.stringify(payload) });
8306
+ const noteLine = userNote ? ` User note: "${userNote}".` : "";
8307
+ const message = `[system] User reverted ${row.filename} to a prior snapshot. ` + `The user judged the version that was live worse than the version they restored.${noteLine} ` + `Read the current ${row.filename} (now the older content) and the diff against your last write to understand what the user preferred. ` + `Treat this as authoritative — do not re-rewrite the same change. Update your understanding before any future refinement.`;
8308
+ sendToInternalSession(state2, row.agentId, message, `user-reverted-${row.id}-${Date.now()}`);
8309
+ console.log("[agentlife:evolutions] restored %s/%s to version %d (userNote=%s)", row.agentId, row.filename, row.id, userNote ? "yes" : "no");
8310
+ respond(true, {
8311
+ restoredVersionId: row.id,
8312
+ filename: row.filename,
8313
+ agentId: row.agentId,
8314
+ revertedFromVersionId: currentSnap?.id ?? null
8315
+ });
8316
+ } catch (err) {
8317
+ respond(false, undefined, { code: "EVOLUTIONS_RESTORE_FAILED", message: err?.message ?? "restore failed" });
8318
+ }
8319
+ }, { scope: "operator.write" });
8320
+ api.registerGatewayMethod("agentlife.evolutions.note", ({ params, respond }) => {
8321
+ try {
8322
+ const agentId = typeof params?.agentId === "string" ? params.agentId.trim() : "";
8323
+ if (!agentId) {
8324
+ respond(false, undefined, { code: "BAD_PARAMS", message: "agentId required" });
8325
+ return;
8326
+ }
8327
+ const filename = typeof params?.filename === "string" ? params.filename.trim() : "";
8328
+ if (!filename || !ALLOWED_FILENAMES.has(filename)) {
8329
+ respond(false, undefined, { code: "BAD_PARAMS", message: "filename must be one of " + [...ALLOWED_FILENAMES].join(", ") });
8330
+ return;
8331
+ }
8332
+ const note = typeof params?.note === "string" ? params.note.trim().slice(0, 280) : "";
8333
+ if (!note) {
8334
+ respond(false, undefined, { code: "BAD_PARAMS", message: "note required" });
8335
+ return;
8336
+ }
8337
+ const db = getOrCreateHistoryDb(state2);
8338
+ const row = db.prepare(`SELECT id FROM bootstrap_versions WHERE agentId = ? AND filename = ?
8339
+ ORDER BY createdAt DESC LIMIT 1`).get(agentId, filename);
8340
+ if (!row) {
8341
+ respond(false, undefined, { code: "NOT_FOUND", message: `no snapshot for ${agentId}/${filename}` });
8342
+ return;
8343
+ }
8344
+ db.prepare(`UPDATE bootstrap_versions SET note = ? WHERE id = ?`).run(note, row.id);
8345
+ respond(true, { versionId: row.id, note });
8346
+ } catch (err) {
8347
+ respond(false, undefined, { code: "EVOLUTIONS_NOTE_FAILED", message: err?.message ?? "note failed" });
8348
+ }
8349
+ }, { scope: "operator.write" });
8350
+ }
8351
+
8042
8352
  // gateway/followups-gateway.ts
8043
8353
  function registerFollowupsGateway(api, state2) {
8044
8354
  api.registerGatewayMethod("agentlife.followups", ({ params, respond }) => {
@@ -8253,10 +8563,10 @@ import {
8253
8563
 
8254
8564
  // gateway/config-utils.ts
8255
8565
  import * as fs15 from "node:fs";
8256
- import * as os9 from "node:os";
8257
- import * as path14 from "node:path";
8566
+ import * as os10 from "node:os";
8567
+ import * as path15 from "node:path";
8258
8568
  function configPath() {
8259
- return path14.join(os9.homedir(), ".openclaw", "openclaw.json");
8569
+ return path15.join(os10.homedir(), ".openclaw", "openclaw.json");
8260
8570
  }
8261
8571
  function readConfig() {
8262
8572
  try {
@@ -8272,10 +8582,10 @@ function writeConfig(cfg) {
8272
8582
 
8273
8583
  // gateway/providers.ts
8274
8584
  import * as fs16 from "node:fs";
8275
- import * as os10 from "node:os";
8276
- import * as path15 from "node:path";
8585
+ import * as os11 from "node:os";
8586
+ import * as path16 from "node:path";
8277
8587
  function siblingAgentDirs() {
8278
- const root = path15.join(os10.homedir(), ".openclaw", "agents");
8588
+ const root = path16.join(os11.homedir(), ".openclaw", "agents");
8279
8589
  let entries;
8280
8590
  try {
8281
8591
  entries = fs16.readdirSync(root, { withFileTypes: true });
@@ -8286,7 +8596,7 @@ function siblingAgentDirs() {
8286
8596
  for (const entry of entries) {
8287
8597
  if (!entry.isDirectory() && !entry.isSymbolicLink())
8288
8598
  continue;
8289
- const dir = path15.join(root, entry.name, "agent");
8599
+ const dir = path16.join(root, entry.name, "agent");
8290
8600
  try {
8291
8601
  if (fs16.statSync(dir).isDirectory())
8292
8602
  out.push(dir);
@@ -8359,9 +8669,9 @@ async function ensureModels(run) {
8359
8669
  function readOAuthExpiries() {
8360
8670
  const result = new Map;
8361
8671
  const fs17 = __require("node:fs");
8362
- const path16 = __require("node:path");
8363
- const os11 = __require("node:os");
8364
- const agentsDir = path16.join(os11.homedir(), ".openclaw", "agents");
8672
+ const path17 = __require("node:path");
8673
+ const os12 = __require("node:os");
8674
+ const agentsDir = path17.join(os12.homedir(), ".openclaw", "agents");
8365
8675
  let entries;
8366
8676
  try {
8367
8677
  entries = fs17.readdirSync(agentsDir);
@@ -8369,7 +8679,7 @@ function readOAuthExpiries() {
8369
8679
  return result;
8370
8680
  }
8371
8681
  for (const name of entries) {
8372
- const filePath = path16.join(agentsDir, name, "agent", "auth-profiles.json");
8682
+ const filePath = path17.join(agentsDir, name, "agent", "auth-profiles.json");
8373
8683
  try {
8374
8684
  const raw = fs17.readFileSync(filePath, "utf-8");
8375
8685
  const data = JSON.parse(raw);
@@ -8543,7 +8853,7 @@ function registerProvidersGateway(api, state2) {
8543
8853
  }
8544
8854
  writeConfig(cfg);
8545
8855
  for (const agentDir of siblingAgentDirs()) {
8546
- const file = path15.join(agentDir, "auth-profiles.json");
8856
+ const file = path16.join(agentDir, "auth-profiles.json");
8547
8857
  try {
8548
8858
  const raw = fs16.readFileSync(file, "utf-8");
8549
8859
  const data = JSON.parse(raw);
@@ -8653,10 +8963,10 @@ ${result?.stderr ?? ""}`;
8653
8963
 
8654
8964
  // gateway/models-config.ts
8655
8965
  import * as fs17 from "node:fs";
8656
- import * as os11 from "node:os";
8657
- import * as path16 from "node:path";
8966
+ import * as os12 from "node:os";
8967
+ import * as path17 from "node:path";
8658
8968
  function pluginConfigPath2() {
8659
- return path16.join(os11.homedir(), ".openclaw", "agentlife", "plugin-config.json");
8969
+ return path17.join(os12.homedir(), ".openclaw", "agentlife", "plugin-config.json");
8660
8970
  }
8661
8971
  function readPluginConfig2() {
8662
8972
  try {
@@ -8666,7 +8976,7 @@ function readPluginConfig2() {
8666
8976
  }
8667
8977
  }
8668
8978
  function writePluginConfig2(cfg) {
8669
- fs17.mkdirSync(path16.dirname(pluginConfigPath2()), { recursive: true });
8979
+ fs17.mkdirSync(path17.dirname(pluginConfigPath2()), { recursive: true });
8670
8980
  fs17.writeFileSync(pluginConfigPath2(), JSON.stringify(cfg, null, 2) + `
8671
8981
  `, "utf-8");
8672
8982
  }
@@ -8813,7 +9123,7 @@ var stopQualityCheckPoller = null;
8813
9123
  var stopCloudflared = null;
8814
9124
  function resolveInternalModel(api) {
8815
9125
  try {
8816
- const pluginCfgPath = path17.join(homedir14(), ".openclaw", "agentlife", "plugin-config.json");
9126
+ const pluginCfgPath = path18.join(homedir15(), ".openclaw", "agentlife", "plugin-config.json");
8817
9127
  try {
8818
9128
  const raw = __require("node:fs").readFileSync(pluginCfgPath, "utf-8");
8819
9129
  const pluginCfg = JSON.parse(raw);
@@ -8849,7 +9159,7 @@ function register(api) {
8849
9159
  return;
8850
9160
  }
8851
9161
  registered = true;
8852
- const fallbackDir = path17.join(homedir14(), ".openclaw", "agentlife");
9162
+ const fallbackDir = path18.join(homedir15(), ".openclaw", "agentlife");
8853
9163
  const state2 = {
8854
9164
  fileSurfaceStorage: null,
8855
9165
  surfaceIndex: null,
@@ -8861,9 +9171,9 @@ function register(api) {
8861
9171
  agentDbs: new Map,
8862
9172
  historyDb: null,
8863
9173
  agentlifeStateDir: fallbackDir,
8864
- registryFilePath: path17.join(fallbackDir, "agent-registry.json"),
8865
- dbBaseDir: path17.join(fallbackDir, "db"),
8866
- historyDbPath: path17.join(fallbackDir, "agentlife.db"),
9174
+ registryFilePath: path18.join(fallbackDir, "agent-registry.json"),
9175
+ dbBaseDir: path18.join(fallbackDir, "db"),
9176
+ historyDbPath: path18.join(fallbackDir, "agentlife.db"),
8867
9177
  runCommand: api.runtime.system?.runCommandWithTimeout ?? null,
8868
9178
  enqueueSystemEvent: null,
8869
9179
  requestHeartbeatNow: null,
@@ -8940,10 +9250,11 @@ function register(api) {
8940
9250
  registerAutomationsGateway(api, state2);
8941
9251
  registerFollowupsGateway(api, state2);
8942
9252
  registerAdminGateway(api, state2);
9253
+ registerEvolutionsGateway(api, state2);
8943
9254
  registerProvidersGateway(api, state2);
8944
9255
  registerModelsConfigGateway(api);
8945
9256
  registerWebApp(api);
8946
- const notifyConfigPath = path17.join(fallbackDir, "notification-config.json");
9257
+ const notifyConfigPath = path18.join(fallbackDir, "notification-config.json");
8947
9258
  api.registerGatewayMethod("agentlife.notifications.register", ({ params, respond }) => {
8948
9259
  const serverUrl = typeof params?.serverUrl === "string" ? params.serverUrl.trim() : "";
8949
9260
  const apiKey = typeof params?.apiKey === "string" ? params.apiKey.trim() : "";
@@ -8951,9 +9262,9 @@ function register(api) {
8951
9262
  return respond(false, { error: "missing serverUrl or apiKey" });
8952
9263
  }
8953
9264
  try {
8954
- const { writeFileSync: writeFileSync10, mkdirSync: mkdirSync8 } = __require("node:fs");
8955
- mkdirSync8(path17.dirname(notifyConfigPath), { recursive: true });
8956
- writeFileSync10(notifyConfigPath, JSON.stringify({ serverUrl, apiKey }));
9265
+ const { writeFileSync: writeFileSync11, mkdirSync: mkdirSync8 } = __require("node:fs");
9266
+ mkdirSync8(path18.dirname(notifyConfigPath), { recursive: true });
9267
+ writeFileSync11(notifyConfigPath, JSON.stringify({ serverUrl, apiKey }));
8957
9268
  } catch {}
8958
9269
  respond(true, { registered: true });
8959
9270
  }, { scope: "operator.write" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlife",
3
- "version": "2.6.24",
3
+ "version": "2.6.26",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "bun build index.ts --outfile dist/index.js --target node --external openclaw/plugin-sdk",
@@ -10,7 +10,13 @@
10
10
  "openclaw": {
11
11
  "extensions": [
12
12
  "./dist/index.js"
13
- ]
13
+ ],
14
+ "compat": {
15
+ "pluginApi": "2026.4"
16
+ },
17
+ "build": {
18
+ "openclawVersion": "2026.4.22"
19
+ }
14
20
  },
15
21
  "files": [
16
22
  "dist/index.js",