agentlife 2.6.32 → 2.6.33
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
CHANGED
|
@@ -296,6 +296,9 @@ function getOrCreateAgentDb(state, agentId) {
|
|
|
296
296
|
return db;
|
|
297
297
|
if (!state.dbBaseDir)
|
|
298
298
|
throw new Error("DB base dir not initialized");
|
|
299
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(agentId)) {
|
|
300
|
+
throw new Error(`invalid agentId for DB path: ${agentId}`);
|
|
301
|
+
}
|
|
299
302
|
fsSync.mkdirSync(state.dbBaseDir, { recursive: true });
|
|
300
303
|
const { DatabaseSync } = requireSqlite();
|
|
301
304
|
db = new DatabaseSync(path.join(state.dbBaseDir, `${agentId}.db`));
|
|
@@ -697,11 +700,21 @@ class TransientSurfaces {
|
|
|
697
700
|
this.store.set(block.surfaceId, entry);
|
|
698
701
|
return entry;
|
|
699
702
|
}
|
|
700
|
-
|
|
701
|
-
return
|
|
703
|
+
isExpired(entry, now) {
|
|
704
|
+
return (entry.isOverlay || entry.isInput) && now - entry.updatedAt > OVERLAY_TTL_MS;
|
|
702
705
|
}
|
|
703
|
-
|
|
704
|
-
|
|
706
|
+
get(surfaceId, now = Date.now()) {
|
|
707
|
+
const entry = this.store.get(surfaceId);
|
|
708
|
+
if (!entry)
|
|
709
|
+
return null;
|
|
710
|
+
if (this.isExpired(entry, now)) {
|
|
711
|
+
this.store.delete(surfaceId);
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
return entry;
|
|
715
|
+
}
|
|
716
|
+
has(surfaceId, now = Date.now()) {
|
|
717
|
+
return this.get(surfaceId, now) !== null;
|
|
705
718
|
}
|
|
706
719
|
delete(surfaceId) {
|
|
707
720
|
return this.store.delete(surfaceId);
|
|
@@ -709,7 +722,7 @@ class TransientSurfaces {
|
|
|
709
722
|
list(now = Date.now()) {
|
|
710
723
|
const alive = [];
|
|
711
724
|
for (const [sid, entry] of this.store) {
|
|
712
|
-
if (entry
|
|
725
|
+
if (this.isExpired(entry, now)) {
|
|
713
726
|
this.store.delete(sid);
|
|
714
727
|
continue;
|
|
715
728
|
}
|
|
@@ -1344,7 +1357,7 @@ Check AGENT_REGISTRY.md and agent descriptions. Match the user's intent to the r
|
|
|
1344
1357
|
|
|
1345
1358
|
### 2. Fallback — No matching agent
|
|
1346
1359
|
- **Throwaway** ("32 x 44", "what time is it in Tokyo", "convert 5 miles to km") → route to "quick" agent
|
|
1347
|
-
- **Everything else** → route to "agentlife-builder". The builder decides whether to create a new agent or
|
|
1360
|
+
- **Everything else** → route to "agentlife-builder". The builder decides whether to create a new agent, expand an existing one, or forward to an existing specialist — it never does the work itself. NEVER route to "main" — the main agent does not exist in AgentLife.
|
|
1348
1361
|
|
|
1349
1362
|
## ONE Agent Per Request
|
|
1350
1363
|
|
|
@@ -1445,8 +1458,9 @@ You create and improve specialist agents for the AgentLife dashboard.
|
|
|
1445
1458
|
## Rules
|
|
1446
1459
|
|
|
1447
1460
|
- 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.
|
|
1448
|
-
-
|
|
1449
|
-
-
|
|
1461
|
+
- **You never do domain work and never own domain widgets.** You are the factory, not a specialist. If a routed message belongs to an EXISTING specialist's domain, do NOT handle it — forward it via \`sessions_send\` to that specialist's direct session (\`sessionKey="agent:{agentId}:agentlife:direct:operator"\`, \`timeoutSeconds=0\`) and emit \`NO_REPLY\`. The platform rejects builder pushes outside your lane: only \`{domain}-building\` loading stubs, \`agentlife-builder-*\` agent-management widgets, input surfaces, and deletes are allowed.
|
|
1462
|
+
- NEVER push a confirmation / "ready" / "created" widget. The new agent's own first widget IS the confirmation.
|
|
1463
|
+
- Emit \`NO_REPLY\` ONLY after \`agentlife.createAgent\` has succeeded (or after forwarding to an existing specialist).
|
|
1450
1464
|
|
|
1451
1465
|
## Creating a New Agent
|
|
1452
1466
|
|
|
@@ -1465,7 +1479,7 @@ Triggered when the orchestrator routes a "create X agent" / "track Y for me" / n
|
|
|
1465
1479
|
- Description drives the orchestrator's routing — make it specific (domains, actions, data types).
|
|
1466
1480
|
- \`tools: {profile:"full", alsoAllow:["agentlife_push"]}\` for agents needing every core tool plus widget push. \`{allow:["agentlife_push"]}\` for widget-only agents.
|
|
1467
1481
|
5. **Hand off in ONE final turn, all in this order, before \`NO_REPLY\`:**
|
|
1468
|
-
a.
|
|
1482
|
+
a. Forward the user's ORIGINAL request to the new agent's direct session via \`sessions_send\` (\`sessionKey="agent:{agentId}:agentlife:direct:operator"\`, \`timeoutSeconds=0\`). Message shape: \`[system] You were just created for this request. Push your first actionable widget for it now — an input prompt, a starter metric with a CTA, or a question the user can answer right now. NEVER an identity/confirmation card. Original request: <the user's request verbatim>\`. The new agent owns its first widget from the start — you NEVER push widgets on its behalf (the platform rejects builder-owned domain surfaces).
|
|
1469
1483
|
b. \`delete {domain}-building\` — the loading widget from step 1.
|
|
1470
1484
|
c. Emit \`NO_REPLY\`.
|
|
1471
1485
|
|
|
@@ -1483,11 +1497,11 @@ Keep under 8k chars. Required sections (omit what's not relevant):
|
|
|
1483
1497
|
|
|
1484
1498
|
## Improving Existing Agents
|
|
1485
1499
|
|
|
1486
|
-
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.
|
|
1500
|
+
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 under your namespace (\`agentlife-builder-improve-{agentId}\`).
|
|
1487
1501
|
|
|
1488
1502
|
## Deleting an Agent
|
|
1489
1503
|
|
|
1490
|
-
\`agentlife.deleteAgent: {"id":"{agentId}"}\` then optionally \`exec rm -rf ~/.openclaw/workspace-{agentId}\`. Push a confirmation widget.
|
|
1504
|
+
\`agentlife.deleteAgent: {"id":"{agentId}"}\` then optionally \`exec rm -rf ~/.openclaw/workspace-{agentId}\`. Push a confirmation widget under your namespace (\`agentlife-builder-deleted-{agentId}\`).
|
|
1491
1505
|
|
|
1492
1506
|
## Registry Enrichment (system task)
|
|
1493
1507
|
|
|
@@ -3222,15 +3236,18 @@ function rescheduleFailedFollowup(state, row, error) {
|
|
|
3222
3236
|
function buildFireInstruction(state, row) {
|
|
3223
3237
|
const view2 = getSurface(state, row.surfaceId);
|
|
3224
3238
|
const goalText = view2?.goal ?? (view2 ? extractTitleAndDetail({ lines: view2.lines }).title : null);
|
|
3225
|
-
const
|
|
3239
|
+
const goalSafe = goalText ? goalText.replace(/[\r\n]+/g, " ").replace(/"/g, "'").slice(0, 200) : null;
|
|
3240
|
+
const goalLabel = goalSafe ? ` (goal: "${goalSafe}")` : "";
|
|
3241
|
+
const sanitizeFire = (s) => s.replace(/[\r\n]+/g, " ").replace(/"/g, "'");
|
|
3242
|
+
const instructionSafe = sanitizeFire(row.instruction).slice(0, 500);
|
|
3226
3243
|
const recentFires = lookupRecentFiredFollowups(state, row.surfaceId, 5);
|
|
3227
3244
|
const prior = recentFires.filter((m) => m !== row.instruction);
|
|
3228
3245
|
const priorBlock = prior.length > 0 ? `
|
|
3229
3246
|
Prior followups on this surface — your next must not match any of these with parameters rotated:
|
|
3230
|
-
${prior.slice(0, 3).map((m) => ` • "${m}"`).join(`
|
|
3247
|
+
${prior.slice(0, 3).map((m) => ` • "${sanitizeFire(m).slice(0, 300)}"`).join(`
|
|
3231
3248
|
`)}` : "";
|
|
3232
3249
|
return [
|
|
3233
|
-
`Widget followup for ${row.surfaceId}${goalLabel}: ${
|
|
3250
|
+
`Widget followup for ${row.surfaceId}${goalLabel}: ${instructionSafe}`,
|
|
3234
3251
|
`Stay on this surfaceId — update it, push a transient input surface, or delete it. New dashboard surfaceId only if this goal is complete and a successor begins (delete in the same turn).${priorBlock}`
|
|
3235
3252
|
].join(`
|
|
3236
3253
|
`);
|
|
@@ -4163,6 +4180,7 @@ function runInstallBootstrap(home) {
|
|
|
4163
4180
|
["sessionsVisibility", cfg?.tools?.sessions?.visibility ?? null],
|
|
4164
4181
|
["agentToAgentEnabled", cfg?.tools?.agentToAgent?.enabled ?? null],
|
|
4165
4182
|
["gatewayBind", cfg?.gateway?.bind ?? null],
|
|
4183
|
+
["gatewayMode", cfg?.gateway?.mode ?? null],
|
|
4166
4184
|
["hooksAllowConversationAccess", cfg?.plugins?.entries?.agentlife?.hooks?.allowConversationAccess ?? null],
|
|
4167
4185
|
["maxPingPongTurns", cfg?.session?.agentToAgent?.maxPingPongTurns ?? null],
|
|
4168
4186
|
["maxConcurrent", cfg?.agents?.defaults?.maxConcurrent ?? null]
|
|
@@ -6282,7 +6300,7 @@ function mintBootstrapToken() {
|
|
|
6282
6300
|
const devicesDir = path11.join(resolveStateDir(), "devices");
|
|
6283
6301
|
const bootstrapPath = path11.join(devicesDir, "bootstrap.json");
|
|
6284
6302
|
if (!fs11.existsSync(devicesDir))
|
|
6285
|
-
fs11.mkdirSync(devicesDir, { recursive: true });
|
|
6303
|
+
fs11.mkdirSync(devicesDir, { recursive: true, mode: 448 });
|
|
6286
6304
|
const registry = fs11.existsSync(bootstrapPath) ? JSON.parse(fs11.readFileSync(bootstrapPath, "utf-8")) : {};
|
|
6287
6305
|
registry[bootstrapToken] = {
|
|
6288
6306
|
token: bootstrapToken,
|
|
@@ -6292,7 +6310,8 @@ function mintBootstrapToken() {
|
|
|
6292
6310
|
},
|
|
6293
6311
|
issuedAtMs: Date.now()
|
|
6294
6312
|
};
|
|
6295
|
-
fs11.writeFileSync(bootstrapPath, JSON.stringify(registry, null, 2));
|
|
6313
|
+
fs11.writeFileSync(bootstrapPath, JSON.stringify(registry, null, 2), { mode: 384 });
|
|
6314
|
+
fs11.chmodSync(bootstrapPath, 384);
|
|
6296
6315
|
return bootstrapToken;
|
|
6297
6316
|
}
|
|
6298
6317
|
function registerWebApp(api) {
|
|
@@ -6962,22 +6981,29 @@ function registerWidgetPushTool(api, state2) {
|
|
|
6962
6981
|
const trimmed = rawBlock.trim();
|
|
6963
6982
|
if (!trimmed)
|
|
6964
6983
|
continue;
|
|
6965
|
-
const
|
|
6984
|
+
const deleteSids = [...rawBlock.matchAll(/^\s*delete\s+(\S+)/gm)].map((m) => m[1]);
|
|
6966
6985
|
const surfaceMatch = rawBlock.match(/^surface\s+(\S+)/m);
|
|
6967
|
-
const
|
|
6968
|
-
|
|
6986
|
+
const checks = [
|
|
6987
|
+
...deleteSids.map((sid) => ({ sid, action: "delete" })),
|
|
6988
|
+
...surfaceMatch ? [{ sid: surfaceMatch[1], action: "push" }] : []
|
|
6989
|
+
];
|
|
6990
|
+
if (checks.length === 0) {
|
|
6969
6991
|
filteredRawBlocks.push(rawBlock);
|
|
6970
6992
|
continue;
|
|
6971
6993
|
}
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
const
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6994
|
+
let violated = false;
|
|
6995
|
+
for (const { sid, action } of checks) {
|
|
6996
|
+
const owner = getAgentId(state2, sid);
|
|
6997
|
+
if (owner && owner !== agentId) {
|
|
6998
|
+
ownershipViolations.push({ surfaceId: sid, owner, action });
|
|
6999
|
+
recordActivity(state2, "quality_warning", sessionKey, agentId, {
|
|
7000
|
+
data: JSON.stringify({ issue: "surface_ownership_violation", surfaceId: sid, owner, attempt: agentId, action })
|
|
7001
|
+
});
|
|
7002
|
+
violated = true;
|
|
7003
|
+
}
|
|
6980
7004
|
}
|
|
7005
|
+
if (violated)
|
|
7006
|
+
continue;
|
|
6981
7007
|
filteredRawBlocks.push(rawBlock);
|
|
6982
7008
|
}
|
|
6983
7009
|
if (filteredRawBlocks.length === 0 && ownershipViolations.length > 0) {
|
|
@@ -6986,13 +7012,15 @@ function registerWidgetPushTool(api, state2) {
|
|
|
6986
7012
|
}
|
|
6987
7013
|
let finalBlocks = filteredRawBlocks;
|
|
6988
7014
|
const confirmationViolations = [];
|
|
7015
|
+
const domainViolations = [];
|
|
6989
7016
|
if (agentId === "agentlife-builder") {
|
|
6990
7017
|
const CONFIRMATION_SUFFIXES = /-(?:created|ready|built|setup|done|welcome)$/i;
|
|
7018
|
+
const BUILDER_ALLOWED = /(?:^agentlife-builder-|^dismiss-alt-|-building$)/i;
|
|
6991
7019
|
const kept = [];
|
|
6992
7020
|
for (const block of filteredRawBlocks) {
|
|
6993
7021
|
const deleteMatch = block.trim().match(/^delete\s+(\S+)/);
|
|
6994
|
-
const
|
|
6995
|
-
const sid = deleteMatch?.[1] ??
|
|
7022
|
+
const surfaceLine = block.match(/^surface\s+(\S+)([^\n]*)/m);
|
|
7023
|
+
const sid = deleteMatch?.[1] ?? surfaceLine?.[1] ?? null;
|
|
6996
7024
|
if (!sid || deleteMatch) {
|
|
6997
7025
|
kept.push(block);
|
|
6998
7026
|
continue;
|
|
@@ -7004,10 +7032,25 @@ function registerWidgetPushTool(api, state2) {
|
|
|
7004
7032
|
});
|
|
7005
7033
|
continue;
|
|
7006
7034
|
}
|
|
7035
|
+
const isInputSurface = /\binput\b/.test(surfaceLine?.[2] ?? "");
|
|
7036
|
+
if (!isInputSurface && !BUILDER_ALLOWED.test(sid)) {
|
|
7037
|
+
domainViolations.push(sid);
|
|
7038
|
+
recordActivity(state2, "quality_warning", sessionKey, agentId, {
|
|
7039
|
+
data: JSON.stringify({ issue: "builder_domain_widget", surfaceId: sid })
|
|
7040
|
+
});
|
|
7041
|
+
continue;
|
|
7042
|
+
}
|
|
7007
7043
|
kept.push(block);
|
|
7008
7044
|
}
|
|
7009
|
-
if (confirmationViolations.length > 0 && kept.length === 0) {
|
|
7010
|
-
|
|
7045
|
+
if ((confirmationViolations.length > 0 || domainViolations.length > 0) && kept.length === 0) {
|
|
7046
|
+
const errs = [];
|
|
7047
|
+
if (confirmationViolations.length > 0)
|
|
7048
|
+
errs.push(buildBuilderConfirmationError(confirmationViolations));
|
|
7049
|
+
if (domainViolations.length > 0)
|
|
7050
|
+
errs.push(buildBuilderDomainWidgetError(domainViolations));
|
|
7051
|
+
return { content: [{ type: "text", text: errs.join(`
|
|
7052
|
+
|
|
7053
|
+
`) }] };
|
|
7011
7054
|
}
|
|
7012
7055
|
finalBlocks = kept;
|
|
7013
7056
|
}
|
|
@@ -7128,6 +7171,10 @@ function registerWidgetPushTool(api, state2) {
|
|
|
7128
7171
|
const detail = ownershipViolations.map((v) => `${v.surfaceId} (owner=${v.owner}, attempt=${v.action})`).join("; ");
|
|
7129
7172
|
errors.push(buildOwnershipError(detail, true));
|
|
7130
7173
|
}
|
|
7174
|
+
if (confirmationViolations.length > 0)
|
|
7175
|
+
errors.push(buildBuilderConfirmationError(confirmationViolations));
|
|
7176
|
+
if (domainViolations.length > 0)
|
|
7177
|
+
errors.push(buildBuilderDomainWidgetError(domainViolations));
|
|
7131
7178
|
const summary = pushedSurfaceIds.length === 1 ? `Widget ${pushedSurfaceIds[0]} updated` : `${pushedSurfaceIds.length} widgets updated`;
|
|
7132
7179
|
if (errors.length > 0) {
|
|
7133
7180
|
return { content: [{ type: "text", text: `${summary}
|
|
@@ -7145,7 +7192,10 @@ function buildOwnershipError(detail, skipped = false) {
|
|
|
7145
7192
|
return `[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. ` + (skipped ? `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.`;
|
|
7146
7193
|
}
|
|
7147
7194
|
function buildBuilderConfirmationError(violations) {
|
|
7148
|
-
return `[QUALITY ERROR] builder pushed confirmation widget(s): ${violations.join(", ")}. ` + `Per BUILDER_AGENTS_MD, the builder does NOT push an "agent ready / created" widget. ` + `The
|
|
7195
|
+
return `[QUALITY ERROR] builder pushed confirmation widget(s): ${violations.join(", ")}. ` + `Per BUILDER_AGENTS_MD, the builder does NOT push an "agent ready / created" widget. ` + `The NEW agent pushes its own first widget — hand the user's request off to it via ` + `sessions_send (sessionKey="agent:{agentId}:agentlife:direct:operator"). ` + `Delete your loading widget and emit NO_REPLY.`;
|
|
7196
|
+
}
|
|
7197
|
+
function buildBuilderDomainWidgetError(violations) {
|
|
7198
|
+
return `[QUALITY ERROR] builder pushed domain widget(s): ${violations.join(", ")}. ` + `You are the agent factory — you never do domain work or own domain widgets. ` + `Allowed surfaces: '{domain}-building' loading stubs, 'agentlife-builder-*' agent-management widgets, ` + `input surfaces, and deletes. For this request: if a specialist for the domain exists, forward the ` + `request via sessions_send to sessionKey="agent:{agentId}:agentlife:direct:operator" and emit NO_REPLY; ` + `if none exists, create one via agentlife.createAgent, then hand off — the NEW agent pushes its own first widget.`;
|
|
7149
7199
|
}
|
|
7150
7200
|
function buildGoalChangedError(ids) {
|
|
7151
7201
|
return `[QUALITY ERROR] ${ids.join(", ")}: goal changed — use a new surfaceId for a new goal. One widget, one goal. Delete the old widget and push a new one with a different surfaceId.`;
|
|
@@ -7459,7 +7509,11 @@ function findSiblingAuthProfile(provider, targetAgentId) {
|
|
|
7459
7509
|
function writeAgentAuthProfile(agentId, provider, match) {
|
|
7460
7510
|
if (!match)
|
|
7461
7511
|
return false;
|
|
7462
|
-
const
|
|
7512
|
+
const agentsRoot = path14.join(resolveOpenClawRoot2(), "agents");
|
|
7513
|
+
const agentDir = path14.join(agentsRoot, agentId, "agent");
|
|
7514
|
+
if (!agentDir.startsWith(agentsRoot + path14.sep)) {
|
|
7515
|
+
throw new Error(`writeAgentAuthProfile: path traversal rejected for agentId: ${agentId}`);
|
|
7516
|
+
}
|
|
7463
7517
|
fs13.mkdirSync(agentDir, { recursive: true });
|
|
7464
7518
|
const credential = { ...match.profile };
|
|
7465
7519
|
if (credential.type === "api_key" && typeof credential.key !== "string" && typeof credential.apiKey === "string") {
|
|
@@ -7483,6 +7537,10 @@ function registerAgentGateway(api, state2) {
|
|
|
7483
7537
|
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing agent id" });
|
|
7484
7538
|
return;
|
|
7485
7539
|
}
|
|
7540
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(id)) {
|
|
7541
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: "invalid agent id: must match ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$ (no path separators or dots)" });
|
|
7542
|
+
return;
|
|
7543
|
+
}
|
|
7486
7544
|
if (!workspace) {
|
|
7487
7545
|
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing workspace path" });
|
|
7488
7546
|
return;
|
|
@@ -7587,6 +7645,10 @@ function registerAgentGateway(api, state2) {
|
|
|
7587
7645
|
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing agent id" });
|
|
7588
7646
|
return;
|
|
7589
7647
|
}
|
|
7648
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(id)) {
|
|
7649
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: "invalid agent id: must match ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$ (no path separators or dots)" });
|
|
7650
|
+
return;
|
|
7651
|
+
}
|
|
7590
7652
|
const provisionedIds = new Set(PROVISIONED_AGENTS.map((a) => a.id));
|
|
7591
7653
|
if (provisionedIds.has(id)) {
|
|
7592
7654
|
respond(false, undefined, { code: "FAILED", message: "cannot delete provisioned agent" });
|
|
@@ -8079,11 +8141,12 @@ function registerSurfacesGateway(api, state2) {
|
|
|
8079
8141
|
if (guided) {
|
|
8080
8142
|
const agentId2 = view2.agentId;
|
|
8081
8143
|
if (agentId2 && state2.runCommand) {
|
|
8082
|
-
const
|
|
8083
|
-
const
|
|
8144
|
+
const sanitizeMeta = (s) => s.replace(/[\r\n]+/g, " ").replace(/"/g, "'").slice(0, 200);
|
|
8145
|
+
const goalSnippet = view2.goal ? ` goal="${sanitizeMeta(view2.goal)}"` : "";
|
|
8146
|
+
const reasonSnippet = reason ? ` reason="${sanitizeMeta(reason)}"` : "";
|
|
8084
8147
|
const sessionKey = buildAgentSessionKey(agentId2);
|
|
8085
8148
|
state2.pendingReactiveGuidance.set(sessionKey, DISMISS_ALTERNATIVES_GUIDANCE);
|
|
8086
|
-
const message = `[system:dismiss-requested] surfaceId=${surfaceId}${goalSnippet}${reasonSnippet}`;
|
|
8149
|
+
const message = `[system:dismiss-requested] surfaceId=${sanitizeMeta(surfaceId)}${goalSnippet}${reasonSnippet}`;
|
|
8087
8150
|
state2.runCommand([
|
|
8088
8151
|
"openclaw",
|
|
8089
8152
|
"gateway",
|
|
@@ -8140,8 +8203,19 @@ function registerSurfacesGateway(api, state2) {
|
|
|
8140
8203
|
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing surfaceId or event" });
|
|
8141
8204
|
return;
|
|
8142
8205
|
}
|
|
8206
|
+
const ALLOWED_ENGAGE_EVENTS = new Set([
|
|
8207
|
+
"viewed",
|
|
8208
|
+
"detail_viewed",
|
|
8209
|
+
"action_clicked",
|
|
8210
|
+
"input_closed",
|
|
8211
|
+
"input_skipped"
|
|
8212
|
+
]);
|
|
8213
|
+
if (!ALLOWED_ENGAGE_EVENTS.has(event)) {
|
|
8214
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: `invalid engagement event: ${event}` });
|
|
8215
|
+
return;
|
|
8216
|
+
}
|
|
8143
8217
|
const agentId = getAgentId(state2, surfaceId) ?? undefined;
|
|
8144
|
-
const metadata = typeof params?.metadata === "string" ? params.metadata.trim() : undefined;
|
|
8218
|
+
const metadata = typeof params?.metadata === "string" ? params.metadata.trim().slice(0, 1024) : undefined;
|
|
8145
8219
|
recordSurfaceEvent(state2, surfaceId, event, undefined, agentId, metadata);
|
|
8146
8220
|
respond(true, { surfaceId, event });
|
|
8147
8221
|
}, { scope: "operator.read" });
|
|
@@ -8189,11 +8263,15 @@ function registerSurfacesGateway(api, state2) {
|
|
|
8189
8263
|
const agentId = view2?.agentId ?? null;
|
|
8190
8264
|
const ctx = view2?.context;
|
|
8191
8265
|
if (ctx && ctx.escalation === true && typeof ctx.target === "string") {
|
|
8192
|
-
const target = ctx.target;
|
|
8193
|
-
const
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8266
|
+
const target = ctx.target.trim();
|
|
8267
|
+
const shapeOk = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(target);
|
|
8268
|
+
if (shapeOk) {
|
|
8269
|
+
const sessionKey2 = buildAgentSessionKey(target);
|
|
8270
|
+
console.log("[agentlife] action escalation: surface=%s routing to target=%s session=%s", surfaceId, target, sessionKey2);
|
|
8271
|
+
respond(true, { surfaceId, agentId: target, sessionKey: sessionKey2 });
|
|
8272
|
+
return;
|
|
8273
|
+
}
|
|
8274
|
+
console.warn("[agentlife] action escalation REJECTED: surface=%s malformed target=%s — falling back to owner routing", surfaceId, target);
|
|
8197
8275
|
}
|
|
8198
8276
|
const origin = getOriginSessionKey(state2, surfaceId);
|
|
8199
8277
|
let sessionKey;
|
|
@@ -8277,8 +8355,8 @@ function registerAutomationsGateway(api, state2) {
|
|
|
8277
8355
|
return;
|
|
8278
8356
|
}
|
|
8279
8357
|
const surfaceId = typeof params?.surfaceId === "string" ? params.surfaceId.trim() : "";
|
|
8280
|
-
const agentId = typeof params?.agentId === "string" ? params.agentId.trim() :
|
|
8281
|
-
const id = registerAutomation(state2,
|
|
8358
|
+
const agentId = surfaceId ? getAgentId(state2, surfaceId) ?? "" : typeof params?.agentId === "string" ? params.agentId.trim() : "";
|
|
8359
|
+
const id = registerAutomation(state2, surfaceId || null, agentId || null, type, name, automationPath);
|
|
8282
8360
|
respond(true, { id, surfaceId, agentId, type, name, path: automationPath, status: "running" });
|
|
8283
8361
|
} catch (err) {
|
|
8284
8362
|
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "register failed" });
|
|
@@ -8538,7 +8616,7 @@ function registerAdminGateway(api, state2) {
|
|
|
8538
8616
|
snapshot = fromDb;
|
|
8539
8617
|
}
|
|
8540
8618
|
if (snapshot.length === 0) {
|
|
8541
|
-
respond(true, { status: "no bootstrap snapshot for this session", files: []
|
|
8619
|
+
respond(true, { status: "no bootstrap snapshot for this session", files: [] });
|
|
8542
8620
|
return;
|
|
8543
8621
|
}
|
|
8544
8622
|
respond(true, {
|
|
@@ -8681,7 +8759,7 @@ function registerAdminGateway(api, state2) {
|
|
|
8681
8759
|
console.error("[agentlife] uninstall failed:", err);
|
|
8682
8760
|
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "unknown error during uninstall" });
|
|
8683
8761
|
}
|
|
8684
|
-
});
|
|
8762
|
+
}, { scope: "operator.admin" });
|
|
8685
8763
|
api.registerGatewayMethod("agentlife.config.get", ({ respond }) => {
|
|
8686
8764
|
respond(true, readPluginConfig());
|
|
8687
8765
|
}, { scope: "operator.read" });
|
|
@@ -8834,7 +8912,8 @@ function registerEvolutionsGateway(api, state2) {
|
|
|
8834
8912
|
userNote
|
|
8835
8913
|
};
|
|
8836
8914
|
recordActivity(state2, "quality_warning", null, row.agentId, { data: JSON.stringify(payload) });
|
|
8837
|
-
const
|
|
8915
|
+
const noteSafe = userNote ? userNote.replace(/[\r\n]+/g, " ").replace(/"/g, "'").slice(0, 200) : null;
|
|
8916
|
+
const noteLine = noteSafe ? ` User note: "${noteSafe}".` : "";
|
|
8838
8917
|
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.`;
|
|
8839
8918
|
sendToInternalSession(state2, row.agentId, message, `user-reverted-${row.id}-${Date.now()}`);
|
|
8840
8919
|
console.log("[agentlife:evolutions] restored %s/%s to version %d (userNote=%s)", row.agentId, row.filename, row.id, userNote ? "yes" : "no");
|
|
@@ -9837,11 +9916,23 @@ function register(api) {
|
|
|
9837
9916
|
if (!serverUrl || !apiKey) {
|
|
9838
9917
|
return respond(false, undefined, { code: "INVALID_REQUEST", message: "missing serverUrl or apiKey" });
|
|
9839
9918
|
}
|
|
9919
|
+
let parsedUrl;
|
|
9840
9920
|
try {
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
}
|
|
9921
|
+
parsedUrl = new URL(serverUrl);
|
|
9922
|
+
} catch {
|
|
9923
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "serverUrl must be a valid URL" });
|
|
9924
|
+
}
|
|
9925
|
+
if (parsedUrl.protocol !== "https:") {
|
|
9926
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "serverUrl must use https://" });
|
|
9927
|
+
}
|
|
9928
|
+
try {
|
|
9929
|
+
const { writeFileSync: writeFileSync12, mkdirSync: mkdirSync10, chmodSync: chmodSync4 } = __require("node:fs");
|
|
9930
|
+
mkdirSync10(path20.dirname(notifyConfigPath), { recursive: true, mode: 448 });
|
|
9931
|
+
writeFileSync12(notifyConfigPath, JSON.stringify({ serverUrl, apiKey }), { mode: 384 });
|
|
9932
|
+
chmodSync4(notifyConfigPath, 384);
|
|
9933
|
+
} catch (e) {
|
|
9934
|
+
return respond(false, undefined, { code: "INTERNAL", message: `failed to persist notification config: ${e?.message ?? "write error"}` });
|
|
9935
|
+
}
|
|
9845
9936
|
respond(true, { registered: true });
|
|
9846
9937
|
}, { scope: "operator.write" });
|
|
9847
9938
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlife",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.33",
|
|
4
4
|
"description": "Native mobile dashboard for OpenClaw — agents push live widgets, automations, and remote-access surfaces to your phone via the AgentLife plugin relay.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openclaw",
|
|
Binary file
|