agentlife 2.6.31 → 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
|
|
|
@@ -1838,12 +1852,15 @@ async function provisionAgents(state, cfg, runtime, log) {
|
|
|
1838
1852
|
}
|
|
1839
1853
|
}
|
|
1840
1854
|
const provisionedIds = new Set(PROVISIONED_AGENTS.map((a) => a.id));
|
|
1855
|
+
const backfilledSubagentIds = [];
|
|
1841
1856
|
for (const agent of currentList) {
|
|
1842
1857
|
if (provisionedIds.has(agent.id))
|
|
1843
1858
|
continue;
|
|
1844
1859
|
if (!agent.subagents) {
|
|
1845
1860
|
agent.subagents = { allowAgents: ["*"] };
|
|
1846
1861
|
configChanged = true;
|
|
1862
|
+
if (agent.id)
|
|
1863
|
+
backfilledSubagentIds.push(agent.id);
|
|
1847
1864
|
log(`[agentlife] backfilled subagents for ${agent.id}`);
|
|
1848
1865
|
}
|
|
1849
1866
|
}
|
|
@@ -1856,6 +1873,18 @@ async function provisionAgents(state, cfg, runtime, log) {
|
|
|
1856
1873
|
const configPath = path4.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
1857
1874
|
const rawCfgForVisibility = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
1858
1875
|
const backupPath = path4.join(os.homedir(), ".openclaw", "agentlife", "config-backup.json");
|
|
1876
|
+
if (backfilledSubagentIds.length > 0) {
|
|
1877
|
+
try {
|
|
1878
|
+
let backup = {};
|
|
1879
|
+
try {
|
|
1880
|
+
backup = JSON.parse(readFileSync2(backupPath, "utf-8"));
|
|
1881
|
+
} catch {}
|
|
1882
|
+
const existing = Array.isArray(backup.subagentsBackfilledIds) ? backup.subagentsBackfilledIds : [];
|
|
1883
|
+
backup.subagentsBackfilledIds = [...new Set([...existing, ...backfilledSubagentIds])];
|
|
1884
|
+
writeFileSync(backupPath, JSON.stringify(backup, null, 2) + `
|
|
1885
|
+
`, "utf-8");
|
|
1886
|
+
} catch {}
|
|
1887
|
+
}
|
|
1859
1888
|
let visibilityWritten = false;
|
|
1860
1889
|
const currentVisibility = rawCfgForVisibility?.tools?.sessions?.visibility;
|
|
1861
1890
|
if (currentVisibility !== "all") {
|
|
@@ -3207,15 +3236,18 @@ function rescheduleFailedFollowup(state, row, error) {
|
|
|
3207
3236
|
function buildFireInstruction(state, row) {
|
|
3208
3237
|
const view2 = getSurface(state, row.surfaceId);
|
|
3209
3238
|
const goalText = view2?.goal ?? (view2 ? extractTitleAndDetail({ lines: view2.lines }).title : null);
|
|
3210
|
-
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);
|
|
3211
3243
|
const recentFires = lookupRecentFiredFollowups(state, row.surfaceId, 5);
|
|
3212
3244
|
const prior = recentFires.filter((m) => m !== row.instruction);
|
|
3213
3245
|
const priorBlock = prior.length > 0 ? `
|
|
3214
3246
|
Prior followups on this surface — your next must not match any of these with parameters rotated:
|
|
3215
|
-
${prior.slice(0, 3).map((m) => ` • "${m}"`).join(`
|
|
3247
|
+
${prior.slice(0, 3).map((m) => ` • "${sanitizeFire(m).slice(0, 300)}"`).join(`
|
|
3216
3248
|
`)}` : "";
|
|
3217
3249
|
return [
|
|
3218
|
-
`Widget followup for ${row.surfaceId}${goalLabel}: ${
|
|
3250
|
+
`Widget followup for ${row.surfaceId}${goalLabel}: ${instructionSafe}`,
|
|
3219
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}`
|
|
3220
3252
|
].join(`
|
|
3221
3253
|
`);
|
|
@@ -3277,17 +3309,21 @@ function pollFollowups(state) {
|
|
|
3277
3309
|
}
|
|
3278
3310
|
sweepCounter++;
|
|
3279
3311
|
if (sweepCounter % 5 === 0 && state.surfaceIndex && state.followupQueue && state.fileSurfaceStorage) {
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3312
|
+
try {
|
|
3313
|
+
sweepExpiredSurfaces({
|
|
3314
|
+
index: state.surfaceIndex,
|
|
3315
|
+
queue: state.followupQueue,
|
|
3316
|
+
storage: state.fileSurfaceStorage
|
|
3317
|
+
}).then((r) => {
|
|
3318
|
+
for (const sid of r.purged)
|
|
3319
|
+
broadcastDelete(sid);
|
|
3320
|
+
if (r.purged.length > 0) {
|
|
3321
|
+
console.log("[agentlife:sweep] purged %d expired surfaces", r.purged.length);
|
|
3322
|
+
}
|
|
3323
|
+
}).catch((e) => console.warn("[agentlife:sweep] error: %s", e?.message));
|
|
3324
|
+
} catch (e) {
|
|
3325
|
+
console.warn("[agentlife] sweep invocation failed synchronously: %s", e?.message);
|
|
3326
|
+
}
|
|
3291
3327
|
}
|
|
3292
3328
|
}
|
|
3293
3329
|
|
|
@@ -3983,7 +4019,6 @@ async function restoreConfigFromBackup(api, backupPath) {
|
|
|
3983
4019
|
delete fileCfg.agents?.defaults?.maxConcurrent;
|
|
3984
4020
|
}
|
|
3985
4021
|
await writeRawConfigToDisk(fileCfg);
|
|
3986
|
-
await fs5.unlink(backupPath);
|
|
3987
4022
|
console.log("[agentlife] restored config: maxPingPongTurns=%s, maxConcurrent=%s", backup.maxPingPongTurns ?? "default", backup.maxConcurrent ?? "default");
|
|
3988
4023
|
return `config restored`;
|
|
3989
4024
|
}
|
|
@@ -4145,6 +4180,7 @@ function runInstallBootstrap(home) {
|
|
|
4145
4180
|
["sessionsVisibility", cfg?.tools?.sessions?.visibility ?? null],
|
|
4146
4181
|
["agentToAgentEnabled", cfg?.tools?.agentToAgent?.enabled ?? null],
|
|
4147
4182
|
["gatewayBind", cfg?.gateway?.bind ?? null],
|
|
4183
|
+
["gatewayMode", cfg?.gateway?.mode ?? null],
|
|
4148
4184
|
["hooksAllowConversationAccess", cfg?.plugins?.entries?.agentlife?.hooks?.allowConversationAccess ?? null],
|
|
4149
4185
|
["maxPingPongTurns", cfg?.session?.agentToAgent?.maxPingPongTurns ?? null],
|
|
4150
4186
|
["maxConcurrent", cfg?.agents?.defaults?.maxConcurrent ?? null]
|
|
@@ -6264,7 +6300,7 @@ function mintBootstrapToken() {
|
|
|
6264
6300
|
const devicesDir = path11.join(resolveStateDir(), "devices");
|
|
6265
6301
|
const bootstrapPath = path11.join(devicesDir, "bootstrap.json");
|
|
6266
6302
|
if (!fs11.existsSync(devicesDir))
|
|
6267
|
-
fs11.mkdirSync(devicesDir, { recursive: true });
|
|
6303
|
+
fs11.mkdirSync(devicesDir, { recursive: true, mode: 448 });
|
|
6268
6304
|
const registry = fs11.existsSync(bootstrapPath) ? JSON.parse(fs11.readFileSync(bootstrapPath, "utf-8")) : {};
|
|
6269
6305
|
registry[bootstrapToken] = {
|
|
6270
6306
|
token: bootstrapToken,
|
|
@@ -6274,7 +6310,8 @@ function mintBootstrapToken() {
|
|
|
6274
6310
|
},
|
|
6275
6311
|
issuedAtMs: Date.now()
|
|
6276
6312
|
};
|
|
6277
|
-
fs11.writeFileSync(bootstrapPath, JSON.stringify(registry, null, 2));
|
|
6313
|
+
fs11.writeFileSync(bootstrapPath, JSON.stringify(registry, null, 2), { mode: 384 });
|
|
6314
|
+
fs11.chmodSync(bootstrapPath, 384);
|
|
6278
6315
|
return bootstrapToken;
|
|
6279
6316
|
}
|
|
6280
6317
|
function registerWebApp(api) {
|
|
@@ -6944,22 +6981,29 @@ function registerWidgetPushTool(api, state2) {
|
|
|
6944
6981
|
const trimmed = rawBlock.trim();
|
|
6945
6982
|
if (!trimmed)
|
|
6946
6983
|
continue;
|
|
6947
|
-
const
|
|
6984
|
+
const deleteSids = [...rawBlock.matchAll(/^\s*delete\s+(\S+)/gm)].map((m) => m[1]);
|
|
6948
6985
|
const surfaceMatch = rawBlock.match(/^surface\s+(\S+)/m);
|
|
6949
|
-
const
|
|
6950
|
-
|
|
6986
|
+
const checks = [
|
|
6987
|
+
...deleteSids.map((sid) => ({ sid, action: "delete" })),
|
|
6988
|
+
...surfaceMatch ? [{ sid: surfaceMatch[1], action: "push" }] : []
|
|
6989
|
+
];
|
|
6990
|
+
if (checks.length === 0) {
|
|
6951
6991
|
filteredRawBlocks.push(rawBlock);
|
|
6952
6992
|
continue;
|
|
6953
6993
|
}
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
const
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
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
|
+
}
|
|
6962
7004
|
}
|
|
7005
|
+
if (violated)
|
|
7006
|
+
continue;
|
|
6963
7007
|
filteredRawBlocks.push(rawBlock);
|
|
6964
7008
|
}
|
|
6965
7009
|
if (filteredRawBlocks.length === 0 && ownershipViolations.length > 0) {
|
|
@@ -6968,13 +7012,15 @@ function registerWidgetPushTool(api, state2) {
|
|
|
6968
7012
|
}
|
|
6969
7013
|
let finalBlocks = filteredRawBlocks;
|
|
6970
7014
|
const confirmationViolations = [];
|
|
7015
|
+
const domainViolations = [];
|
|
6971
7016
|
if (agentId === "agentlife-builder") {
|
|
6972
7017
|
const CONFIRMATION_SUFFIXES = /-(?:created|ready|built|setup|done|welcome)$/i;
|
|
7018
|
+
const BUILDER_ALLOWED = /(?:^agentlife-builder-|^dismiss-alt-|-building$)/i;
|
|
6973
7019
|
const kept = [];
|
|
6974
7020
|
for (const block of filteredRawBlocks) {
|
|
6975
7021
|
const deleteMatch = block.trim().match(/^delete\s+(\S+)/);
|
|
6976
|
-
const
|
|
6977
|
-
const sid = deleteMatch?.[1] ??
|
|
7022
|
+
const surfaceLine = block.match(/^surface\s+(\S+)([^\n]*)/m);
|
|
7023
|
+
const sid = deleteMatch?.[1] ?? surfaceLine?.[1] ?? null;
|
|
6978
7024
|
if (!sid || deleteMatch) {
|
|
6979
7025
|
kept.push(block);
|
|
6980
7026
|
continue;
|
|
@@ -6986,10 +7032,25 @@ function registerWidgetPushTool(api, state2) {
|
|
|
6986
7032
|
});
|
|
6987
7033
|
continue;
|
|
6988
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
|
+
}
|
|
6989
7043
|
kept.push(block);
|
|
6990
7044
|
}
|
|
6991
|
-
if (confirmationViolations.length > 0 && kept.length === 0) {
|
|
6992
|
-
|
|
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
|
+
`) }] };
|
|
6993
7054
|
}
|
|
6994
7055
|
finalBlocks = kept;
|
|
6995
7056
|
}
|
|
@@ -7110,6 +7171,10 @@ function registerWidgetPushTool(api, state2) {
|
|
|
7110
7171
|
const detail = ownershipViolations.map((v) => `${v.surfaceId} (owner=${v.owner}, attempt=${v.action})`).join("; ");
|
|
7111
7172
|
errors.push(buildOwnershipError(detail, true));
|
|
7112
7173
|
}
|
|
7174
|
+
if (confirmationViolations.length > 0)
|
|
7175
|
+
errors.push(buildBuilderConfirmationError(confirmationViolations));
|
|
7176
|
+
if (domainViolations.length > 0)
|
|
7177
|
+
errors.push(buildBuilderDomainWidgetError(domainViolations));
|
|
7113
7178
|
const summary = pushedSurfaceIds.length === 1 ? `Widget ${pushedSurfaceIds[0]} updated` : `${pushedSurfaceIds.length} widgets updated`;
|
|
7114
7179
|
if (errors.length > 0) {
|
|
7115
7180
|
return { content: [{ type: "text", text: `${summary}
|
|
@@ -7127,7 +7192,10 @@ function buildOwnershipError(detail, skipped = false) {
|
|
|
7127
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.`;
|
|
7128
7193
|
}
|
|
7129
7194
|
function buildBuilderConfirmationError(violations) {
|
|
7130
|
-
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.`;
|
|
7131
7199
|
}
|
|
7132
7200
|
function buildGoalChangedError(ids) {
|
|
7133
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.`;
|
|
@@ -7441,7 +7509,11 @@ function findSiblingAuthProfile(provider, targetAgentId) {
|
|
|
7441
7509
|
function writeAgentAuthProfile(agentId, provider, match) {
|
|
7442
7510
|
if (!match)
|
|
7443
7511
|
return false;
|
|
7444
|
-
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
|
+
}
|
|
7445
7517
|
fs13.mkdirSync(agentDir, { recursive: true });
|
|
7446
7518
|
const credential = { ...match.profile };
|
|
7447
7519
|
if (credential.type === "api_key" && typeof credential.key !== "string" && typeof credential.apiKey === "string") {
|
|
@@ -7462,11 +7534,15 @@ function registerAgentGateway(api, state2) {
|
|
|
7462
7534
|
const subagents = params?.subagents && typeof params.subagents === "object" ? params.subagents : undefined;
|
|
7463
7535
|
const identity = params?.identity && typeof params.identity === "object" ? params.identity : undefined;
|
|
7464
7536
|
if (!id) {
|
|
7465
|
-
respond(false, {
|
|
7537
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing agent id" });
|
|
7538
|
+
return;
|
|
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)" });
|
|
7466
7542
|
return;
|
|
7467
7543
|
}
|
|
7468
7544
|
if (!workspace) {
|
|
7469
|
-
respond(false, {
|
|
7545
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing workspace path" });
|
|
7470
7546
|
return;
|
|
7471
7547
|
}
|
|
7472
7548
|
const cfg = api.runtime.config.loadConfig();
|
|
@@ -7474,9 +7550,9 @@ function registerAgentGateway(api, state2) {
|
|
|
7474
7550
|
const providerInConfig = provider ? authenticatedProvidersFromConfig(cfg).has(provider) : false;
|
|
7475
7551
|
const siblingAuth = provider ? findSiblingAuthProfile(provider, id) : null;
|
|
7476
7552
|
if (provider && !providerInConfig && !siblingAuth) {
|
|
7477
|
-
respond(false, {
|
|
7478
|
-
error: `missing provider credentials for ${provider}; configure ${provider} before creating an agent with ${model}`,
|
|
7553
|
+
respond(false, undefined, {
|
|
7479
7554
|
code: "missing_provider_auth",
|
|
7555
|
+
message: `missing provider credentials for ${provider}; configure ${provider} before creating an agent with ${model}`,
|
|
7480
7556
|
provider,
|
|
7481
7557
|
model
|
|
7482
7558
|
});
|
|
@@ -7562,16 +7638,20 @@ function registerAgentGateway(api, state2) {
|
|
|
7562
7638
|
}
|
|
7563
7639
|
}
|
|
7564
7640
|
respond(true, { status, id, name, model, workspace, description, ...authCopied ? { authCopied: true } : {}, ...configFields.length ? { configFields } : {} });
|
|
7565
|
-
});
|
|
7641
|
+
}, { scope: "operator.admin" });
|
|
7566
7642
|
api.registerGatewayMethod("agentlife.deleteAgent", async ({ params, respond }) => {
|
|
7567
7643
|
const id = typeof params?.id === "string" ? params.id.trim() : "";
|
|
7568
7644
|
if (!id) {
|
|
7569
|
-
respond(false, {
|
|
7645
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing agent id" });
|
|
7646
|
+
return;
|
|
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)" });
|
|
7570
7650
|
return;
|
|
7571
7651
|
}
|
|
7572
7652
|
const provisionedIds = new Set(PROVISIONED_AGENTS.map((a) => a.id));
|
|
7573
7653
|
if (provisionedIds.has(id)) {
|
|
7574
|
-
respond(false, {
|
|
7654
|
+
respond(false, undefined, { code: "FAILED", message: "cannot delete provisioned agent" });
|
|
7575
7655
|
return;
|
|
7576
7656
|
}
|
|
7577
7657
|
const cleanup = {
|
|
@@ -7709,7 +7789,7 @@ function registerAgentGateway(api, state2) {
|
|
|
7709
7789
|
}
|
|
7710
7790
|
console.log("[agentlife] deleted agent: %s (config=%s, registry=%s, surfaces=%d, sessions=%d, cron=%d, historyRows=%d, agentDb=%s, state=%s, workspace=%s)", id, removedFromConfig, removedFromRegistry, cleanup.surfacesCleared, cleanup.sessions, cleanup.cronJobs, cleanup.historyRows, cleanup.agentDbDeleted, cleanup.stateDeleted, cleanup.workspaceDeleted);
|
|
7711
7791
|
respond(true, { status: "deleted", id, removedFromConfig, removedFromRegistry, cleanup });
|
|
7712
|
-
});
|
|
7792
|
+
}, { scope: "operator.admin" });
|
|
7713
7793
|
api.registerGatewayMethod("agentlife.agents", ({ respond }) => {
|
|
7714
7794
|
const agents = {};
|
|
7715
7795
|
for (const [id, entry] of state2.agentRegistry.entries()) {
|
|
@@ -7726,11 +7806,11 @@ function registerDbGateway(api, state2) {
|
|
|
7726
7806
|
const sql = typeof params?.sql === "string" ? params.sql.trim() : "";
|
|
7727
7807
|
const sqlParams = Array.isArray(params?.params) ? params.params : [];
|
|
7728
7808
|
if (!agentId)
|
|
7729
|
-
return respond(false, {
|
|
7809
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "missing agentId" });
|
|
7730
7810
|
if (!sql)
|
|
7731
|
-
return respond(false, {
|
|
7811
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "missing sql" });
|
|
7732
7812
|
if (/^\s*SELECT\b/i.test(sql))
|
|
7733
|
-
return respond(false, {
|
|
7813
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "use agentlife.db.query for SELECT" });
|
|
7734
7814
|
try {
|
|
7735
7815
|
const db = getOrCreateAgentDb(state2, agentId);
|
|
7736
7816
|
if (sqlParams.length > 0) {
|
|
@@ -7741,19 +7821,19 @@ function registerDbGateway(api, state2) {
|
|
|
7741
7821
|
respond(true, { changes: 0, lastInsertRowid: 0 });
|
|
7742
7822
|
}
|
|
7743
7823
|
} catch (err) {
|
|
7744
|
-
respond(false, {
|
|
7824
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "db error" });
|
|
7745
7825
|
}
|
|
7746
|
-
});
|
|
7826
|
+
}, { scope: "operator.admin" });
|
|
7747
7827
|
api.registerGatewayMethod("agentlife.db.query", ({ params, respond }) => {
|
|
7748
7828
|
const agentId = typeof params?.agentId === "string" ? params.agentId.trim() : "";
|
|
7749
7829
|
const sql = typeof params?.sql === "string" ? params.sql.trim() : "";
|
|
7750
7830
|
const sqlParams = Array.isArray(params?.params) ? params.params : [];
|
|
7751
7831
|
if (!agentId)
|
|
7752
|
-
return respond(false, {
|
|
7832
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "missing agentId" });
|
|
7753
7833
|
if (!sql)
|
|
7754
|
-
return respond(false, {
|
|
7834
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "missing sql" });
|
|
7755
7835
|
if (!/^\s*SELECT\b/i.test(sql))
|
|
7756
|
-
return respond(false, {
|
|
7836
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "use agentlife.db.exec for non-SELECT" });
|
|
7757
7837
|
try {
|
|
7758
7838
|
const db = getOrCreateAgentDb(state2, agentId);
|
|
7759
7839
|
const rows = db.prepare(sql).all(...sqlParams);
|
|
@@ -7763,9 +7843,9 @@ function registerDbGateway(api, state2) {
|
|
|
7763
7843
|
const columns = result.length > 0 ? Object.keys(result[0]) : [];
|
|
7764
7844
|
respond(true, { rows: result, columns, count: result.length, truncated });
|
|
7765
7845
|
} catch (err) {
|
|
7766
|
-
respond(false, {
|
|
7846
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "db error" });
|
|
7767
7847
|
}
|
|
7768
|
-
});
|
|
7848
|
+
}, { scope: "operator.admin" });
|
|
7769
7849
|
}
|
|
7770
7850
|
|
|
7771
7851
|
// gateway/history.ts
|
|
@@ -7806,7 +7886,7 @@ function registerHistoryGateway(api, state2) {
|
|
|
7806
7886
|
const rows = db.prepare(`SELECT * FROM surface_events ${where} ORDER BY createdAt DESC LIMIT ? OFFSET ?`).all(...bind);
|
|
7807
7887
|
respond(true, { events: rows, count: rows.length });
|
|
7808
7888
|
} catch (err) {
|
|
7809
|
-
respond(false, {
|
|
7889
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "history error" });
|
|
7810
7890
|
}
|
|
7811
7891
|
}, { scope: "operator.read" });
|
|
7812
7892
|
api.registerGatewayMethod("agentlife.history.widgets", ({ params, respond }) => {
|
|
@@ -7847,7 +7927,7 @@ function registerHistoryGateway(api, state2) {
|
|
|
7847
7927
|
`).all(...bind, limit, offset);
|
|
7848
7928
|
respond(true, { widgets: rows, count: rows.length, hasMore: rows.length >= limit });
|
|
7849
7929
|
} catch (err) {
|
|
7850
|
-
respond(false, {
|
|
7930
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "history.widgets error" });
|
|
7851
7931
|
}
|
|
7852
7932
|
}, { scope: "operator.read" });
|
|
7853
7933
|
api.registerGatewayMethod("agentlife.trace", ({ params, respond }) => {
|
|
@@ -7904,7 +7984,7 @@ function registerHistoryGateway(api, state2) {
|
|
|
7904
7984
|
}));
|
|
7905
7985
|
respond(true, { entries, hasMore });
|
|
7906
7986
|
} catch (err) {
|
|
7907
|
-
respond(false, {
|
|
7987
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "trace query failed" });
|
|
7908
7988
|
}
|
|
7909
7989
|
}, { scope: "operator.read" });
|
|
7910
7990
|
}
|
|
@@ -7977,13 +8057,13 @@ function registerUsageGateway(api, state2) {
|
|
|
7977
8057
|
bySession: bySessionRows
|
|
7978
8058
|
});
|
|
7979
8059
|
} catch (err) {
|
|
7980
|
-
respond(false, {
|
|
8060
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "usage query error" });
|
|
7981
8061
|
}
|
|
7982
8062
|
}, { scope: "operator.read" });
|
|
7983
8063
|
api.registerGatewayMethod("agentlife.surfaceUsage", ({ params, respond }) => {
|
|
7984
8064
|
const surfaceId = typeof params?.surfaceId === "string" ? params.surfaceId.trim() : "";
|
|
7985
8065
|
if (!surfaceId)
|
|
7986
|
-
return respond(false, {
|
|
8066
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "missing surfaceId" });
|
|
7987
8067
|
try {
|
|
7988
8068
|
const db = getOrCreateHistoryDb(state2);
|
|
7989
8069
|
const rows = db.prepare(`SELECT * FROM surface_usage WHERE surfaceId = ? ORDER BY createdAt DESC`).all(surfaceId);
|
|
@@ -7999,7 +8079,7 @@ function registerUsageGateway(api, state2) {
|
|
|
7999
8079
|
}, { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, totalTokens: 0, estimatedCost: 0, llmCalls: 0 });
|
|
8000
8080
|
respond(true, { surfaceId, entries: rows, totals });
|
|
8001
8081
|
} catch (err) {
|
|
8002
|
-
respond(false, {
|
|
8082
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "surface usage error" });
|
|
8003
8083
|
}
|
|
8004
8084
|
}, { scope: "operator.read" });
|
|
8005
8085
|
}
|
|
@@ -8048,7 +8128,7 @@ function registerSurfacesGateway(api, state2) {
|
|
|
8048
8128
|
api.registerGatewayMethod("agentlife.dismiss", async ({ params, respond }) => {
|
|
8049
8129
|
const surfaceId = params?.surfaceId;
|
|
8050
8130
|
if (!surfaceId || typeof surfaceId !== "string") {
|
|
8051
|
-
respond(false, {
|
|
8131
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing surfaceId" });
|
|
8052
8132
|
return;
|
|
8053
8133
|
}
|
|
8054
8134
|
const view2 = getSurface(state2, surfaceId);
|
|
@@ -8061,11 +8141,12 @@ function registerSurfacesGateway(api, state2) {
|
|
|
8061
8141
|
if (guided) {
|
|
8062
8142
|
const agentId2 = view2.agentId;
|
|
8063
8143
|
if (agentId2 && state2.runCommand) {
|
|
8064
|
-
const
|
|
8065
|
-
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)}"` : "";
|
|
8066
8147
|
const sessionKey = buildAgentSessionKey(agentId2);
|
|
8067
8148
|
state2.pendingReactiveGuidance.set(sessionKey, DISMISS_ALTERNATIVES_GUIDANCE);
|
|
8068
|
-
const message = `[system:dismiss-requested] surfaceId=${surfaceId}${goalSnippet}${reasonSnippet}`;
|
|
8149
|
+
const message = `[system:dismiss-requested] surfaceId=${sanitizeMeta(surfaceId)}${goalSnippet}${reasonSnippet}`;
|
|
8069
8150
|
state2.runCommand([
|
|
8070
8151
|
"openclaw",
|
|
8071
8152
|
"gateway",
|
|
@@ -8119,11 +8200,22 @@ function registerSurfacesGateway(api, state2) {
|
|
|
8119
8200
|
const surfaceId = typeof params?.surfaceId === "string" ? params.surfaceId.trim() : "";
|
|
8120
8201
|
const event = typeof params?.event === "string" ? params.event.trim() : "";
|
|
8121
8202
|
if (!surfaceId || !event) {
|
|
8122
|
-
respond(false, {
|
|
8203
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing surfaceId or event" });
|
|
8204
|
+
return;
|
|
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}` });
|
|
8123
8215
|
return;
|
|
8124
8216
|
}
|
|
8125
8217
|
const agentId = getAgentId(state2, surfaceId) ?? undefined;
|
|
8126
|
-
const metadata = typeof params?.metadata === "string" ? params.metadata.trim() : undefined;
|
|
8218
|
+
const metadata = typeof params?.metadata === "string" ? params.metadata.trim().slice(0, 1024) : undefined;
|
|
8127
8219
|
recordSurfaceEvent(state2, surfaceId, event, undefined, agentId, metadata);
|
|
8128
8220
|
respond(true, { surfaceId, event });
|
|
8129
8221
|
}, { scope: "operator.read" });
|
|
@@ -8131,7 +8223,7 @@ function registerSurfacesGateway(api, state2) {
|
|
|
8131
8223
|
const message = typeof params?.message === "string" ? params.message : "";
|
|
8132
8224
|
const sessionKey = typeof params?.sessionKey === "string" ? params.sessionKey : "";
|
|
8133
8225
|
if (!message) {
|
|
8134
|
-
respond(false, {
|
|
8226
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing message" });
|
|
8135
8227
|
return;
|
|
8136
8228
|
}
|
|
8137
8229
|
broadcastInput(message, sessionKey);
|
|
@@ -8140,7 +8232,7 @@ function registerSurfacesGateway(api, state2) {
|
|
|
8140
8232
|
api.registerGatewayMethod("agentlife.chain.get", ({ params, respond }) => {
|
|
8141
8233
|
const root = typeof params?.root === "string" ? params.root.trim() : "";
|
|
8142
8234
|
if (!root) {
|
|
8143
|
-
respond(false, {
|
|
8235
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing root" });
|
|
8144
8236
|
return;
|
|
8145
8237
|
}
|
|
8146
8238
|
if (!state2.surfaceIndex) {
|
|
@@ -8164,18 +8256,22 @@ function registerSurfacesGateway(api, state2) {
|
|
|
8164
8256
|
api.registerGatewayMethod("agentlife.action", ({ params, respond }) => {
|
|
8165
8257
|
const surfaceId = typeof params?.surfaceId === "string" ? params.surfaceId.trim() : "";
|
|
8166
8258
|
if (!surfaceId) {
|
|
8167
|
-
respond(false, {
|
|
8259
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing surfaceId" });
|
|
8168
8260
|
return;
|
|
8169
8261
|
}
|
|
8170
8262
|
const view2 = getSurface(state2, surfaceId);
|
|
8171
8263
|
const agentId = view2?.agentId ?? null;
|
|
8172
8264
|
const ctx = view2?.context;
|
|
8173
8265
|
if (ctx && ctx.escalation === true && typeof ctx.target === "string") {
|
|
8174
|
-
const target = ctx.target;
|
|
8175
|
-
const
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
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);
|
|
8179
8275
|
}
|
|
8180
8276
|
const origin = getOriginSessionKey(state2, surfaceId);
|
|
8181
8277
|
let sessionKey;
|
|
@@ -8255,15 +8351,15 @@ function registerAutomationsGateway(api, state2) {
|
|
|
8255
8351
|
const name = typeof params?.name === "string" ? params.name.trim() : "";
|
|
8256
8352
|
const automationPath = typeof params?.path === "string" ? params.path.trim() : "";
|
|
8257
8353
|
if (!type || !name || !automationPath) {
|
|
8258
|
-
respond(false, {
|
|
8354
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing required fields: type, name, path" });
|
|
8259
8355
|
return;
|
|
8260
8356
|
}
|
|
8261
8357
|
const surfaceId = typeof params?.surfaceId === "string" ? params.surfaceId.trim() : "";
|
|
8262
|
-
const agentId = typeof params?.agentId === "string" ? params.agentId.trim() :
|
|
8263
|
-
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);
|
|
8264
8360
|
respond(true, { id, surfaceId, agentId, type, name, path: automationPath, status: "running" });
|
|
8265
8361
|
} catch (err) {
|
|
8266
|
-
respond(false, {
|
|
8362
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "register failed" });
|
|
8267
8363
|
}
|
|
8268
8364
|
}, { scope: "operator.write" });
|
|
8269
8365
|
api.registerGatewayMethod("agentlife.automations.list", ({ params, respond }) => {
|
|
@@ -8304,7 +8400,7 @@ function registerAutomationsGateway(api, state2) {
|
|
|
8304
8400
|
}))
|
|
8305
8401
|
});
|
|
8306
8402
|
} catch (err) {
|
|
8307
|
-
respond(false, {
|
|
8403
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "list failed" });
|
|
8308
8404
|
}
|
|
8309
8405
|
}, { scope: "operator.read" });
|
|
8310
8406
|
api.registerGatewayMethod("agentlife.automations.update", ({ params, respond }) => {
|
|
@@ -8312,28 +8408,28 @@ function registerAutomationsGateway(api, state2) {
|
|
|
8312
8408
|
const id = typeof params?.id === "number" ? params.id : parseInt(params?.id, 10);
|
|
8313
8409
|
const status = typeof params?.status === "string" ? params.status.trim() : "";
|
|
8314
8410
|
if (!id || !status || !["running", "stopped", "error"].includes(status)) {
|
|
8315
|
-
respond(false, {
|
|
8411
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing id or invalid status (running|stopped|error)" });
|
|
8316
8412
|
return;
|
|
8317
8413
|
}
|
|
8318
8414
|
const db = getOrCreateHistoryDb(state2);
|
|
8319
8415
|
db.prepare("UPDATE automations SET status = ?, updatedAt = ? WHERE id = ?").run(status, Date.now(), id);
|
|
8320
8416
|
respond(true, { id, status });
|
|
8321
8417
|
} catch (err) {
|
|
8322
|
-
respond(false, {
|
|
8418
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "update failed" });
|
|
8323
8419
|
}
|
|
8324
8420
|
}, { scope: "operator.write" });
|
|
8325
8421
|
api.registerGatewayMethod("agentlife.automations.remove", ({ params, respond }) => {
|
|
8326
8422
|
try {
|
|
8327
8423
|
const id = typeof params?.id === "number" ? params.id : parseInt(params?.id, 10);
|
|
8328
8424
|
if (!id) {
|
|
8329
|
-
respond(false, {
|
|
8425
|
+
respond(false, undefined, { code: "INVALID_REQUEST", message: "missing id" });
|
|
8330
8426
|
return;
|
|
8331
8427
|
}
|
|
8332
8428
|
const db = getOrCreateHistoryDb(state2);
|
|
8333
8429
|
db.prepare("UPDATE automations SET status = 'removed', updatedAt = ? WHERE id = ?").run(Date.now(), id);
|
|
8334
8430
|
respond(true, { id, status: "removed" });
|
|
8335
8431
|
} catch (err) {
|
|
8336
|
-
respond(false, {
|
|
8432
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "remove failed" });
|
|
8337
8433
|
}
|
|
8338
8434
|
}, { scope: "operator.write" });
|
|
8339
8435
|
}
|
|
@@ -8466,7 +8562,7 @@ function registerAdminGateway(api, state2) {
|
|
|
8466
8562
|
cleanup: { pending: cleanupPending, failed: cleanupFailed, exhausted: cleanupExhausted }
|
|
8467
8563
|
});
|
|
8468
8564
|
} catch (err) {
|
|
8469
|
-
respond(false, {
|
|
8565
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "quality metrics unavailable" });
|
|
8470
8566
|
}
|
|
8471
8567
|
}, { scope: "operator.read" });
|
|
8472
8568
|
api.registerGatewayMethod("agentlife.health.list", ({ respond }) => {
|
|
@@ -8487,7 +8583,7 @@ function registerAdminGateway(api, state2) {
|
|
|
8487
8583
|
const rows = runDailyCycle(db, day);
|
|
8488
8584
|
respond(true, { day, rows });
|
|
8489
8585
|
} catch (err) {
|
|
8490
|
-
respond(false, {
|
|
8586
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "observability runCycle failed" });
|
|
8491
8587
|
}
|
|
8492
8588
|
}, { scope: "operator.write" });
|
|
8493
8589
|
api.registerGatewayMethod("agentlife.sweep.run", ({ respond }) => {
|
|
@@ -8495,7 +8591,7 @@ function registerAdminGateway(api, state2) {
|
|
|
8495
8591
|
const result = runDailySweep(state2);
|
|
8496
8592
|
respond(true, result);
|
|
8497
8593
|
} catch (err) {
|
|
8498
|
-
respond(false, {
|
|
8594
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "daily sweep failed" });
|
|
8499
8595
|
}
|
|
8500
8596
|
}, { scope: "operator.write" });
|
|
8501
8597
|
api.registerGatewayMethod("agentlife.bootstrap", ({ params, respond }) => {
|
|
@@ -8520,7 +8616,7 @@ function registerAdminGateway(api, state2) {
|
|
|
8520
8616
|
snapshot = fromDb;
|
|
8521
8617
|
}
|
|
8522
8618
|
if (snapshot.length === 0) {
|
|
8523
|
-
respond(true, { status: "no bootstrap snapshot for this session", files: []
|
|
8619
|
+
respond(true, { status: "no bootstrap snapshot for this session", files: [] });
|
|
8524
8620
|
return;
|
|
8525
8621
|
}
|
|
8526
8622
|
respond(true, {
|
|
@@ -8623,7 +8719,6 @@ function registerAdminGateway(api, state2) {
|
|
|
8623
8719
|
dbPath,
|
|
8624
8720
|
dbPath ? `${dbPath}-wal` : null,
|
|
8625
8721
|
dbPath ? `${dbPath}-shm` : null,
|
|
8626
|
-
path15.join(baseDir, "config-backup.json"),
|
|
8627
8722
|
path15.join(baseDir, "notification-config.json"),
|
|
8628
8723
|
path15.join(baseDir, "canvas-node-identity.json")
|
|
8629
8724
|
].filter(Boolean);
|
|
@@ -8662,9 +8757,9 @@ function registerAdminGateway(api, state2) {
|
|
|
8662
8757
|
});
|
|
8663
8758
|
} catch (err) {
|
|
8664
8759
|
console.error("[agentlife] uninstall failed:", err);
|
|
8665
|
-
respond(false, {
|
|
8760
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? "unknown error during uninstall" });
|
|
8666
8761
|
}
|
|
8667
|
-
});
|
|
8762
|
+
}, { scope: "operator.admin" });
|
|
8668
8763
|
api.registerGatewayMethod("agentlife.config.get", ({ respond }) => {
|
|
8669
8764
|
respond(true, readPluginConfig());
|
|
8670
8765
|
}, { scope: "operator.read" });
|
|
@@ -8817,7 +8912,8 @@ function registerEvolutionsGateway(api, state2) {
|
|
|
8817
8912
|
userNote
|
|
8818
8913
|
};
|
|
8819
8914
|
recordActivity(state2, "quality_warning", null, row.agentId, { data: JSON.stringify(payload) });
|
|
8820
|
-
const
|
|
8915
|
+
const noteSafe = userNote ? userNote.replace(/[\r\n]+/g, " ").replace(/"/g, "'").slice(0, 200) : null;
|
|
8916
|
+
const noteLine = noteSafe ? ` User note: "${noteSafe}".` : "";
|
|
8821
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.`;
|
|
8822
8918
|
sendToInternalSession(state2, row.agentId, message, `user-reverted-${row.id}-${Date.now()}`);
|
|
8823
8919
|
console.log("[agentlife:evolutions] restored %s/%s to version %d (userNote=%s)", row.agentId, row.filename, row.id, userNote ? "yes" : "no");
|
|
@@ -9316,20 +9412,20 @@ function registerProvidersGateway(api, state2) {
|
|
|
9316
9412
|
}));
|
|
9317
9413
|
respond(true, { providers });
|
|
9318
9414
|
} catch (err) {
|
|
9319
|
-
respond(false, {
|
|
9415
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? String(err) });
|
|
9320
9416
|
}
|
|
9321
9417
|
}, { scope: "operator.read" });
|
|
9322
9418
|
api.registerGatewayMethod("agentlife.providers.set", async ({ params, respond }) => {
|
|
9323
9419
|
const provider = typeof params?.provider === "string" ? params.provider.trim() : "";
|
|
9324
9420
|
const apiKey = typeof params?.apiKey === "string" ? params.apiKey.trim() : "";
|
|
9325
9421
|
if (!provider)
|
|
9326
|
-
return respond(false, {
|
|
9422
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "provider is empty" });
|
|
9327
9423
|
if (!apiKey)
|
|
9328
|
-
return respond(false, {
|
|
9424
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "apiKey is empty" });
|
|
9329
9425
|
try {
|
|
9330
9426
|
const catalog = await ensureCatalog(run ?? null);
|
|
9331
9427
|
if (catalog.length > 0 && !catalog.some((e) => e.provider === provider)) {
|
|
9332
|
-
return respond(false, {
|
|
9428
|
+
return respond(false, undefined, { code: "NOT_FOUND", message: `unknown provider: ${provider}` });
|
|
9333
9429
|
}
|
|
9334
9430
|
} catch {}
|
|
9335
9431
|
try {
|
|
@@ -9350,13 +9446,13 @@ function registerProvidersGateway(api, state2) {
|
|
|
9350
9446
|
writeConfig(nextCfg);
|
|
9351
9447
|
respond(true, { provider, configured: true, profileId });
|
|
9352
9448
|
} catch (err) {
|
|
9353
|
-
respond(false, {
|
|
9449
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? String(err) });
|
|
9354
9450
|
}
|
|
9355
9451
|
}, { scope: "operator.write" });
|
|
9356
9452
|
api.registerGatewayMethod("agentlife.providers.delete", ({ params, respond }) => {
|
|
9357
9453
|
const provider = typeof params?.provider === "string" ? params.provider.trim() : "";
|
|
9358
9454
|
if (!provider)
|
|
9359
|
-
return respond(false, {
|
|
9455
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "provider is empty" });
|
|
9360
9456
|
try {
|
|
9361
9457
|
const cfg = readConfig();
|
|
9362
9458
|
const profiles = cfg?.auth?.profiles;
|
|
@@ -9395,14 +9491,14 @@ function registerProvidersGateway(api, state2) {
|
|
|
9395
9491
|
}
|
|
9396
9492
|
respond(true, { provider, configured: false });
|
|
9397
9493
|
} catch (err) {
|
|
9398
|
-
respond(false, {
|
|
9494
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? String(err) });
|
|
9399
9495
|
}
|
|
9400
9496
|
}, { scope: "operator.write" });
|
|
9401
9497
|
api.registerGatewayMethod("agentlife.providers.test", async ({ params, respond }) => {
|
|
9402
9498
|
const provider = typeof params?.provider === "string" ? params.provider.trim() : "";
|
|
9403
9499
|
const apiKey = typeof params?.apiKey === "string" ? params.apiKey.trim() : "";
|
|
9404
9500
|
if (!provider)
|
|
9405
|
-
return respond(false, {
|
|
9501
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "provider is empty" });
|
|
9406
9502
|
if (!apiKey)
|
|
9407
9503
|
return respond(true, { ok: false, error: "apiKey required — type a key before testing" });
|
|
9408
9504
|
try {
|
|
@@ -9428,7 +9524,7 @@ function registerProvidersGateway(api, state2) {
|
|
|
9428
9524
|
});
|
|
9429
9525
|
respond(true, { models: deduped });
|
|
9430
9526
|
} catch (err) {
|
|
9431
|
-
respond(false, {
|
|
9527
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? String(err) });
|
|
9432
9528
|
}
|
|
9433
9529
|
}, { scope: "operator.read" });
|
|
9434
9530
|
api.registerGatewayMethod("agentlife.providers.health", ({ respond }) => {
|
|
@@ -9458,23 +9554,23 @@ function registerProvidersGateway(api, state2) {
|
|
|
9458
9554
|
}
|
|
9459
9555
|
respond(true, { probes });
|
|
9460
9556
|
} catch (err) {
|
|
9461
|
-
respond(false, {
|
|
9557
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? String(err) });
|
|
9462
9558
|
}
|
|
9463
9559
|
}, { scope: "operator.read" });
|
|
9464
9560
|
api.registerGatewayMethod("agentlife.providers.loginCodex", async ({ respond }) => {
|
|
9465
9561
|
if (!run)
|
|
9466
|
-
return respond(false, {
|
|
9562
|
+
return respond(false, undefined, { code: "UNAVAILABLE", message: "runCommand unavailable" });
|
|
9467
9563
|
try {
|
|
9468
9564
|
const result = await run(["openclaw", "capability", "model", "auth", "login", "--provider", "openai-codex"], { timeoutMs: 180000 });
|
|
9469
9565
|
const combined = `${result?.stdout ?? ""}
|
|
9470
9566
|
${result?.stderr ?? ""}`;
|
|
9471
9567
|
const urlMatch = combined.match(/https:\/\/\S+/);
|
|
9472
9568
|
if ((result?.code ?? 0) !== 0 && !urlMatch) {
|
|
9473
|
-
return respond(false, {
|
|
9569
|
+
return respond(false, undefined, { code: "FAILED", message: (result?.stderr ?? "").trim() || "codex login failed" });
|
|
9474
9570
|
}
|
|
9475
9571
|
respond(true, { url: urlMatch?.[0] ?? null, completed: (result?.code ?? 0) === 0 });
|
|
9476
9572
|
} catch (err) {
|
|
9477
|
-
respond(false, {
|
|
9573
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? String(err) });
|
|
9478
9574
|
}
|
|
9479
9575
|
}, { scope: "operator.write" });
|
|
9480
9576
|
}
|
|
@@ -9510,13 +9606,13 @@ function registerModelsConfigGateway(api) {
|
|
|
9510
9606
|
fallbacks: Array.isArray(m.fallbacks) ? m.fallbacks.filter((x) => typeof x === "string") : []
|
|
9511
9607
|
});
|
|
9512
9608
|
} catch (err) {
|
|
9513
|
-
respond(false, {
|
|
9609
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? String(err) });
|
|
9514
9610
|
}
|
|
9515
9611
|
}, { scope: "operator.read" });
|
|
9516
9612
|
api.registerGatewayMethod("agentlife.models.defaults.setPrimary", ({ params, respond }) => {
|
|
9517
9613
|
const model = typeof params?.model === "string" ? params.model.trim() : "";
|
|
9518
9614
|
if (!model)
|
|
9519
|
-
return respond(false, {
|
|
9615
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "model is required" });
|
|
9520
9616
|
try {
|
|
9521
9617
|
const cfg = readConfig();
|
|
9522
9618
|
cfg.agents = cfg.agents ?? {};
|
|
@@ -9526,26 +9622,26 @@ function registerModelsConfigGateway(api) {
|
|
|
9526
9622
|
writeConfig(cfg);
|
|
9527
9623
|
respond(true, { ok: true });
|
|
9528
9624
|
} catch (err) {
|
|
9529
|
-
respond(false, {
|
|
9625
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? String(err) });
|
|
9530
9626
|
}
|
|
9531
9627
|
}, { scope: "operator.write" });
|
|
9532
9628
|
api.registerGatewayMethod("agentlife.models.defaults.setInternal", ({ params, respond }) => {
|
|
9533
9629
|
const model = typeof params?.model === "string" ? params.model.trim() : "";
|
|
9534
9630
|
if (!model)
|
|
9535
|
-
return respond(false, {
|
|
9631
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "model is required" });
|
|
9536
9632
|
try {
|
|
9537
9633
|
const cfg = readPluginConfig2();
|
|
9538
9634
|
cfg.internalModel = model;
|
|
9539
9635
|
writePluginConfig2(cfg);
|
|
9540
9636
|
respond(true, { ok: true });
|
|
9541
9637
|
} catch (err) {
|
|
9542
|
-
respond(false, {
|
|
9638
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? String(err) });
|
|
9543
9639
|
}
|
|
9544
9640
|
}, { scope: "operator.write" });
|
|
9545
9641
|
api.registerGatewayMethod("agentlife.models.fallbacks.set", ({ params, respond }) => {
|
|
9546
9642
|
const models = Array.isArray(params?.models) ? params.models.filter((x) => typeof x === "string") : null;
|
|
9547
9643
|
if (!models)
|
|
9548
|
-
return respond(false, {
|
|
9644
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "models must be an array of strings" });
|
|
9549
9645
|
try {
|
|
9550
9646
|
const cfg = readConfig();
|
|
9551
9647
|
cfg.agents = cfg.agents ?? {};
|
|
@@ -9555,7 +9651,7 @@ function registerModelsConfigGateway(api) {
|
|
|
9555
9651
|
writeConfig(cfg);
|
|
9556
9652
|
respond(true, { ok: true });
|
|
9557
9653
|
} catch (err) {
|
|
9558
|
-
respond(false, {
|
|
9654
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? String(err) });
|
|
9559
9655
|
}
|
|
9560
9656
|
}, { scope: "operator.write" });
|
|
9561
9657
|
api.registerGatewayMethod("agentlife.agents.list", ({ respond }) => {
|
|
@@ -9569,23 +9665,23 @@ function registerModelsConfigGateway(api) {
|
|
|
9569
9665
|
})).filter((a) => a.id !== null && a.name !== null);
|
|
9570
9666
|
respond(true, { agents });
|
|
9571
9667
|
} catch (err) {
|
|
9572
|
-
respond(false, {
|
|
9668
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? String(err) });
|
|
9573
9669
|
}
|
|
9574
9670
|
}, { scope: "operator.read" });
|
|
9575
9671
|
api.registerGatewayMethod("agentlife.agents.setModel", ({ params, respond }) => {
|
|
9576
9672
|
const agentId = typeof params?.agentId === "string" ? params.agentId.trim() : "";
|
|
9577
9673
|
if (!agentId)
|
|
9578
|
-
return respond(false, {
|
|
9674
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "agentId is required" });
|
|
9579
9675
|
const model = params?.model;
|
|
9580
9676
|
if (model !== null && typeof model !== "string") {
|
|
9581
|
-
return respond(false, {
|
|
9677
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "model must be string or null" });
|
|
9582
9678
|
}
|
|
9583
9679
|
try {
|
|
9584
9680
|
const cfg = readConfig();
|
|
9585
9681
|
const list = Array.isArray(cfg?.agents?.list) ? cfg.agents.list : [];
|
|
9586
9682
|
const idx = list.findIndex((a) => a?.id === agentId);
|
|
9587
9683
|
if (idx < 0)
|
|
9588
|
-
return respond(false, {
|
|
9684
|
+
return respond(false, undefined, { code: "NOT_FOUND", message: `agent not found: ${agentId}` });
|
|
9589
9685
|
if (model === null) {
|
|
9590
9686
|
delete list[idx].model;
|
|
9591
9687
|
} else {
|
|
@@ -9594,19 +9690,19 @@ function registerModelsConfigGateway(api) {
|
|
|
9594
9690
|
writeConfig(cfg);
|
|
9595
9691
|
respond(true, { ok: true });
|
|
9596
9692
|
} catch (err) {
|
|
9597
|
-
respond(false, {
|
|
9693
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? String(err) });
|
|
9598
9694
|
}
|
|
9599
9695
|
}, { scope: "operator.write" });
|
|
9600
9696
|
api.registerGatewayMethod("agentlife.agents.setModel.bulk", ({ params, respond }) => {
|
|
9601
9697
|
const updates = Array.isArray(params?.updates) ? params.updates : null;
|
|
9602
9698
|
if (!updates)
|
|
9603
|
-
return respond(false, {
|
|
9699
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "updates must be an array" });
|
|
9604
9700
|
for (const u of updates) {
|
|
9605
9701
|
if (!u || typeof u.agentId !== "string" || !u.agentId.trim()) {
|
|
9606
|
-
return respond(false, {
|
|
9702
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "each update needs agentId (string)" });
|
|
9607
9703
|
}
|
|
9608
9704
|
if (u.model !== null && typeof u.model !== "string") {
|
|
9609
|
-
return respond(false, {
|
|
9705
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "each update model must be string or null" });
|
|
9610
9706
|
}
|
|
9611
9707
|
}
|
|
9612
9708
|
try {
|
|
@@ -9627,7 +9723,7 @@ function registerModelsConfigGateway(api) {
|
|
|
9627
9723
|
writeConfig(cfg);
|
|
9628
9724
|
respond(true, { ok: true, applied });
|
|
9629
9725
|
} catch (err) {
|
|
9630
|
-
respond(false, {
|
|
9726
|
+
respond(false, undefined, { code: "INTERNAL", message: err?.message ?? String(err) });
|
|
9631
9727
|
}
|
|
9632
9728
|
}, { scope: "operator.write" });
|
|
9633
9729
|
}
|
|
@@ -9818,13 +9914,25 @@ function register(api) {
|
|
|
9818
9914
|
const serverUrl = typeof params?.serverUrl === "string" ? params.serverUrl.trim() : "";
|
|
9819
9915
|
const apiKey = typeof params?.apiKey === "string" ? params.apiKey.trim() : "";
|
|
9820
9916
|
if (!serverUrl || !apiKey) {
|
|
9821
|
-
return respond(false, {
|
|
9917
|
+
return respond(false, undefined, { code: "INVALID_REQUEST", message: "missing serverUrl or apiKey" });
|
|
9822
9918
|
}
|
|
9919
|
+
let parsedUrl;
|
|
9823
9920
|
try {
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
}
|
|
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
|
+
}
|
|
9828
9936
|
respond(true, { registered: true });
|
|
9829
9937
|
}, { scope: "operator.write" });
|
|
9830
9938
|
}
|