forge-openclaw-plugin 0.2.48 → 0.2.49
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/assets/index-2_tuemtU.css +1 -0
- package/dist/assets/index-BAmEvOXb.js +91 -0
- package/dist/assets/{index-Bv9FWWsZ.js.map → index-BAmEvOXb.js.map} +1 -1
- package/dist/index.html +2 -2
- package/dist/openclaw/session-registry.js +17 -0
- package/dist/server/server/migrations/052_agent_identity_tightening.sql +307 -0
- package/dist/server/server/migrations/053_agent_runtime_session_canonical_labels.sql +9 -0
- package/dist/server/server/src/app.js +23 -2
- package/dist/server/server/src/openapi.js +19 -0
- package/dist/server/server/src/repositories/agent-runtime-sessions.js +122 -16
- package/dist/server/server/src/repositories/model-settings.js +5 -0
- package/dist/server/server/src/repositories/settings.js +101 -13
- package/dist/server/server/src/repositories/users.js +23 -0
- package/dist/server/server/src/types.js +13 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +5 -2
- package/server/migrations/052_agent_identity_tightening.sql +307 -0
- package/server/migrations/053_agent_runtime_session_canonical_labels.sql +9 -0
- package/skills/forge-openclaw/entity_conversation_playbooks.md +19 -0
- package/skills/forge-openclaw/psyche_entity_playbooks.md +11 -0
- package/dist/assets/index-Bv9FWWsZ.js +0 -91
- package/dist/assets/index-DtEvFzXp.css +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { getDatabase, runInTransaction } from "../db.js";
|
|
3
3
|
import { recordActivityEvent } from "./activity-events.js";
|
|
4
4
|
import { listAgentActions } from "./collaboration.js";
|
|
5
|
+
import { ensureBotUser, getUserById } from "./users.js";
|
|
5
6
|
import { agentActionSchema, agentRuntimeEventLevelSchema, agentRuntimeReconnectPlanSchema, agentRuntimeSessionEventSchema, agentRuntimeSessionSchema, createAgentRuntimeSessionEventSchema, createAgentRuntimeSessionSchema, disconnectAgentRuntimeSessionSchema, heartbeatAgentRuntimeSessionSchema, reconnectAgentRuntimeSessionSchema } from "../types.js";
|
|
6
7
|
function parseMetadata(raw) {
|
|
7
8
|
try {
|
|
@@ -211,7 +212,95 @@ function ensureCurrentSessionInstance(row, externalSessionId) {
|
|
|
211
212
|
}
|
|
212
213
|
return true;
|
|
213
214
|
}
|
|
214
|
-
function
|
|
215
|
+
function normalizeIdentityPart(value) {
|
|
216
|
+
return (value
|
|
217
|
+
?.trim()
|
|
218
|
+
.toLowerCase()
|
|
219
|
+
.replace(/[^a-z0-9._:]+/g, "_")
|
|
220
|
+
.replace(/^_+|_+$/g, "") ?? "");
|
|
221
|
+
}
|
|
222
|
+
function shortHash(value) {
|
|
223
|
+
return createHash("sha1").update(value).digest("hex").slice(0, 12);
|
|
224
|
+
}
|
|
225
|
+
function canonicalRuntimeAgentLabel(provider) {
|
|
226
|
+
if (provider === "openclaw") {
|
|
227
|
+
return "Forge OpenClaw";
|
|
228
|
+
}
|
|
229
|
+
if (provider === "hermes") {
|
|
230
|
+
return "Forge Hermes";
|
|
231
|
+
}
|
|
232
|
+
return "Forge Codex";
|
|
233
|
+
}
|
|
234
|
+
function canonicalRuntimeDescription(provider) {
|
|
235
|
+
return `${canonicalRuntimeAgentLabel(provider)} runtime agent with stable Forge identity and linked Kanban user.`;
|
|
236
|
+
}
|
|
237
|
+
function canonicalAgentUserSpec(provider) {
|
|
238
|
+
if (provider === "openclaw") {
|
|
239
|
+
return {
|
|
240
|
+
id: "user_agent_openclaw",
|
|
241
|
+
handle: "openclaw",
|
|
242
|
+
displayName: "OpenClaw",
|
|
243
|
+
description: "OpenClaw runtime actor linked to Forge agent identity and Kanban ownership.",
|
|
244
|
+
accentColor: "#38bdf8"
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (provider === "hermes") {
|
|
248
|
+
return {
|
|
249
|
+
id: "user_agent_hermes",
|
|
250
|
+
handle: "hermes",
|
|
251
|
+
displayName: "Hermes",
|
|
252
|
+
description: "Hermes runtime actor linked to Forge agent identity and Kanban ownership.",
|
|
253
|
+
accentColor: "#a78bfa"
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
id: "user_agent_codex",
|
|
258
|
+
handle: "codex",
|
|
259
|
+
displayName: "Codex",
|
|
260
|
+
description: "Codex runtime actor linked to Forge agent identity and Kanban ownership.",
|
|
261
|
+
accentColor: "#22c55e"
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function deriveMachineKey(input) {
|
|
265
|
+
const explicit = normalizeIdentityPart(input.machineKey);
|
|
266
|
+
if (explicit) {
|
|
267
|
+
return explicit;
|
|
268
|
+
}
|
|
269
|
+
const source = [
|
|
270
|
+
normalizeText(input.dataRoot) ?? "",
|
|
271
|
+
normalizeText(input.baseUrl) ?? "local"
|
|
272
|
+
].join("|");
|
|
273
|
+
return `machine_${shortHash(source)}`;
|
|
274
|
+
}
|
|
275
|
+
function derivePersonaKey(input) {
|
|
276
|
+
return (normalizeIdentityPart(input.personaKey) ||
|
|
277
|
+
normalizeIdentityPart(input.agentType) ||
|
|
278
|
+
"default");
|
|
279
|
+
}
|
|
280
|
+
function deriveAgentIdentityKey(input) {
|
|
281
|
+
const explicit = normalizeIdentityPart(input.agentIdentityKey);
|
|
282
|
+
if (explicit) {
|
|
283
|
+
return explicit;
|
|
284
|
+
}
|
|
285
|
+
return `runtime:${input.provider}:${deriveMachineKey(input)}:${derivePersonaKey(input)}`;
|
|
286
|
+
}
|
|
287
|
+
function linkAgentIdentityUsers(agentId, provider, linkedUserIds, now) {
|
|
288
|
+
const primaryUser = ensureBotUser(canonicalAgentUserSpec(provider));
|
|
289
|
+
const normalizedUserIds = Array.from(new Set([primaryUser.id, ...linkedUserIds.map((id) => id.trim()).filter(Boolean)]));
|
|
290
|
+
for (const userId of normalizedUserIds) {
|
|
291
|
+
if (!getUserById(userId)) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
getDatabase()
|
|
295
|
+
.prepare(`INSERT INTO agent_identity_users (agent_id, user_id, role, created_at, updated_at)
|
|
296
|
+
VALUES (?, ?, ?, ?, ?)
|
|
297
|
+
ON CONFLICT(agent_id, user_id) DO UPDATE SET
|
|
298
|
+
role = excluded.role,
|
|
299
|
+
updated_at = excluded.updated_at`)
|
|
300
|
+
.run(agentId, userId, userId === primaryUser.id ? "primary" : "linked", now, now);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function disconnectSupersededSingletonSessions(parsed, sessionId, agentId, now) {
|
|
215
304
|
if (!parsed.metadata?.singleton) {
|
|
216
305
|
return;
|
|
217
306
|
}
|
|
@@ -224,11 +313,11 @@ function disconnectSupersededSingletonSessions(parsed, sessionId, now) {
|
|
|
224
313
|
created_at, updated_at
|
|
225
314
|
FROM agent_runtime_sessions
|
|
226
315
|
WHERE provider = ?
|
|
227
|
-
AND
|
|
316
|
+
AND agent_id = ?
|
|
228
317
|
AND coalesce(base_url, '') = coalesce(?, '')
|
|
229
318
|
AND coalesce(data_root, '') = coalesce(?, '')
|
|
230
319
|
AND id <> ?`)
|
|
231
|
-
.all(parsed.provider,
|
|
320
|
+
.all(parsed.provider, agentId, normalizeText(parsed.baseUrl), normalizeText(parsed.dataRoot), sessionId);
|
|
232
321
|
for (const row of rows) {
|
|
233
322
|
if (row.status === "disconnected" && row.ended_at) {
|
|
234
323
|
continue;
|
|
@@ -251,29 +340,45 @@ function disconnectSupersededSingletonSessions(parsed, sessionId, now) {
|
|
|
251
340
|
}
|
|
252
341
|
}
|
|
253
342
|
function upsertRuntimeAgentIdentity(input) {
|
|
343
|
+
const identityKey = deriveAgentIdentityKey(input);
|
|
344
|
+
const machineKey = deriveMachineKey(input);
|
|
345
|
+
const personaKey = derivePersonaKey(input);
|
|
346
|
+
const label = canonicalRuntimeAgentLabel(input.provider);
|
|
254
347
|
const existing = getDatabase()
|
|
255
348
|
.prepare(`SELECT id
|
|
256
349
|
FROM agent_identities
|
|
257
|
-
WHERE
|
|
350
|
+
WHERE identity_key = ?
|
|
351
|
+
OR (
|
|
352
|
+
(identity_key IS NULL OR machine_key IS NULL OR machine_key = 'legacy' OR identity_key LIKE 'runtime:%:legacy:%')
|
|
353
|
+
AND (
|
|
354
|
+
provider = ?
|
|
355
|
+
OR lower(agent_type) = lower(?)
|
|
356
|
+
OR lower(label) = lower(?)
|
|
357
|
+
)
|
|
358
|
+
)
|
|
258
359
|
LIMIT 1`)
|
|
259
|
-
.get(input.
|
|
360
|
+
.get(identityKey, input.provider, input.provider, label);
|
|
260
361
|
const now = new Date().toISOString();
|
|
261
|
-
const description =
|
|
362
|
+
const description = canonicalRuntimeDescription(input.provider);
|
|
262
363
|
if (existing) {
|
|
263
364
|
getDatabase()
|
|
264
365
|
.prepare(`UPDATE agent_identities
|
|
265
|
-
SET agent_type = ?,
|
|
366
|
+
SET label = ?, agent_type = ?, identity_key = ?, provider = ?,
|
|
367
|
+
machine_key = ?, persona_key = ?, description = ?, updated_at = ?
|
|
266
368
|
WHERE id = ?`)
|
|
267
|
-
.run(input.agentType || input.provider, now, existing.id);
|
|
369
|
+
.run(label, input.agentType || input.provider, identityKey, input.provider, machineKey, personaKey, description, now, existing.id);
|
|
370
|
+
linkAgentIdentityUsers(existing.id, input.provider, input.linkedUserIds, now);
|
|
268
371
|
return existing.id;
|
|
269
372
|
}
|
|
270
373
|
const agentId = `agt_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
|
|
271
374
|
getDatabase()
|
|
272
375
|
.prepare(`INSERT INTO agent_identities (
|
|
273
|
-
id, label, agent_type,
|
|
376
|
+
id, label, agent_type, identity_key, provider, machine_key, persona_key,
|
|
377
|
+
trust_level, autonomy_mode, approval_mode,
|
|
274
378
|
description, created_at, updated_at
|
|
275
|
-
) VALUES (?, ?, ?, 'trusted', 'approval_required', 'approval_by_default', ?, ?, ?)`)
|
|
276
|
-
.run(agentId,
|
|
379
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 'trusted', 'approval_required', 'approval_by_default', ?, ?, ?)`)
|
|
380
|
+
.run(agentId, label, input.agentType || input.provider, identityKey, input.provider, machineKey, personaKey, description, now, now);
|
|
381
|
+
linkAgentIdentityUsers(agentId, input.provider, input.linkedUserIds, now);
|
|
277
382
|
return agentId;
|
|
278
383
|
}
|
|
279
384
|
function insertSessionEvent(sessionId, input, now = new Date().toISOString()) {
|
|
@@ -321,6 +426,7 @@ export function registerAgentRuntimeSession(input) {
|
|
|
321
426
|
return runInTransaction(() => {
|
|
322
427
|
const now = new Date().toISOString();
|
|
323
428
|
const agentId = upsertRuntimeAgentIdentity(parsed);
|
|
429
|
+
const agentLabel = canonicalRuntimeAgentLabel(parsed.provider);
|
|
324
430
|
const existing = getSessionRowByCompositeKey(parsed.provider, parsed.sessionKey);
|
|
325
431
|
const sessionId = existing?.id ?? `ags_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
|
|
326
432
|
if (existing) {
|
|
@@ -332,7 +438,7 @@ export function registerAgentRuntimeSession(input) {
|
|
|
332
438
|
last_error = ?, last_seen_at = ?, last_heartbeat_at = ?, started_at = ?,
|
|
333
439
|
ended_at = NULL, metadata_json = ?, updated_at = ?
|
|
334
440
|
WHERE id = ?`)
|
|
335
|
-
.run(agentId,
|
|
441
|
+
.run(agentId, agentLabel, parsed.agentType || parsed.provider, parsed.sessionLabel || parsed.sessionKey, parsed.actorLabel, parsed.connectionMode, parsed.status === "error" ? "error" : "connected", normalizeText(parsed.baseUrl), normalizeText(parsed.webUrl), normalizeText(parsed.dataRoot), normalizeText(parsed.externalSessionId), parsed.staleAfterSeconds, normalizeText(parsed.lastError), now, now, now, JSON.stringify(parsed.metadata), now, sessionId);
|
|
336
442
|
insertSessionEvent(sessionId, {
|
|
337
443
|
eventType: "session_registered",
|
|
338
444
|
title: "Session re-registered",
|
|
@@ -349,7 +455,7 @@ export function registerAgentRuntimeSession(input) {
|
|
|
349
455
|
last_seen_at, last_heartbeat_at, started_at, ended_at, metadata_json,
|
|
350
456
|
created_at, updated_at
|
|
351
457
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, NULL, ?, ?, ?, ?, NULL, ?, ?, ?)`)
|
|
352
|
-
.run(sessionId, agentId,
|
|
458
|
+
.run(sessionId, agentId, agentLabel, parsed.agentType || parsed.provider, parsed.provider, parsed.sessionKey, parsed.sessionLabel || parsed.sessionKey, parsed.actorLabel, parsed.connectionMode, parsed.status === "error" ? "error" : "connected", normalizeText(parsed.baseUrl), normalizeText(parsed.webUrl), normalizeText(parsed.dataRoot), normalizeText(parsed.externalSessionId), parsed.staleAfterSeconds, normalizeText(parsed.lastError), now, now, now, JSON.stringify(parsed.metadata), now, now);
|
|
353
459
|
insertSessionEvent(sessionId, {
|
|
354
460
|
eventType: "session_registered",
|
|
355
461
|
title: "Session registered",
|
|
@@ -357,12 +463,12 @@ export function registerAgentRuntimeSession(input) {
|
|
|
357
463
|
metadata: parsed.metadata
|
|
358
464
|
}, now);
|
|
359
465
|
}
|
|
360
|
-
disconnectSupersededSingletonSessions(parsed, sessionId, now);
|
|
466
|
+
disconnectSupersededSingletonSessions(parsed, sessionId, agentId, now);
|
|
361
467
|
recordActivityEvent({
|
|
362
468
|
entityType: "session",
|
|
363
469
|
entityId: sessionId,
|
|
364
470
|
eventType: "agent_session_registered",
|
|
365
|
-
title: `Agent session registered: ${
|
|
471
|
+
title: `Agent session registered: ${agentLabel}`,
|
|
366
472
|
description: `${parsed.provider} registered a live agent session.`,
|
|
367
473
|
actor: parsed.actorLabel,
|
|
368
474
|
source: "agent",
|
|
@@ -47,6 +47,11 @@ export function buildConnectionAgentIdentity(connection) {
|
|
|
47
47
|
id: connection.agentId,
|
|
48
48
|
label: connection.agentLabel,
|
|
49
49
|
agentType: connection.provider,
|
|
50
|
+
identityKey: `model:${connection.id}`,
|
|
51
|
+
provider: null,
|
|
52
|
+
machineKey: null,
|
|
53
|
+
personaKey: connection.provider,
|
|
54
|
+
linkedUsers: [],
|
|
50
55
|
trustLevel: "trusted",
|
|
51
56
|
autonomyMode: "approval_required",
|
|
52
57
|
approvalMode: "approval_by_default",
|
|
@@ -7,6 +7,7 @@ import { recordActivityEvent } from "./activity-events.js";
|
|
|
7
7
|
import { recordEventLog } from "./event-log.js";
|
|
8
8
|
import { resolveGoogleCalendarOauthPublicConfig } from "../services/google-calendar-oauth-config.js";
|
|
9
9
|
import { buildConnectionAgentIdentity, FORGE_DEFAULT_AGENT_ID, listAiModelConnections, syncForgeManagedWikiProfile } from "./model-settings.js";
|
|
10
|
+
import { listUsersByIds } from "./users.js";
|
|
10
11
|
import { agentBootstrapPolicySchema, agentScopePolicySchema, createAgentTokenSchema, legacyAgentBootstrapPolicy, defaultAgentScopePolicy, agentIdentitySchema, customThemeSchema, settingsPayloadSchema, updateSettingsSchema } from "../types.js";
|
|
11
12
|
const settingsFileSchema = settingsPayloadSchema.deepPartial();
|
|
12
13
|
let settingsFileSyncDepth = 0;
|
|
@@ -303,11 +304,40 @@ function pickComparableOverrideSubset(source, template) {
|
|
|
303
304
|
}
|
|
304
305
|
return picked;
|
|
305
306
|
}
|
|
306
|
-
function
|
|
307
|
+
function listAgentIdentityUserLinks(agentIds) {
|
|
308
|
+
if (agentIds.length === 0) {
|
|
309
|
+
return new Map();
|
|
310
|
+
}
|
|
311
|
+
const placeholders = agentIds.map(() => "?").join(",");
|
|
312
|
+
const rows = getDatabase()
|
|
313
|
+
.prepare(`SELECT agent_id, user_id, role
|
|
314
|
+
FROM agent_identity_users
|
|
315
|
+
WHERE agent_id IN (${placeholders})
|
|
316
|
+
ORDER BY role = 'primary' DESC, created_at ASC`)
|
|
317
|
+
.all(...agentIds);
|
|
318
|
+
const usersById = new Map(listUsersByIds(rows.map((row) => row.user_id)).map((user) => [user.id, user]));
|
|
319
|
+
const linksByAgentId = new Map();
|
|
320
|
+
for (const row of rows) {
|
|
321
|
+
const current = linksByAgentId.get(row.agent_id) ?? [];
|
|
322
|
+
current.push({
|
|
323
|
+
userId: row.user_id,
|
|
324
|
+
role: row.role,
|
|
325
|
+
user: usersById.get(row.user_id) ?? null
|
|
326
|
+
});
|
|
327
|
+
linksByAgentId.set(row.agent_id, current);
|
|
328
|
+
}
|
|
329
|
+
return linksByAgentId;
|
|
330
|
+
}
|
|
331
|
+
function mapAgent(row, linkedUsers = []) {
|
|
307
332
|
return agentIdentitySchema.parse({
|
|
308
333
|
id: row.id,
|
|
309
334
|
label: row.label,
|
|
310
335
|
agentType: row.agent_type,
|
|
336
|
+
identityKey: row.identity_key,
|
|
337
|
+
provider: row.provider,
|
|
338
|
+
machineKey: row.machine_key,
|
|
339
|
+
personaKey: row.persona_key,
|
|
340
|
+
linkedUsers,
|
|
311
341
|
trustLevel: row.trust_level,
|
|
312
342
|
autonomyMode: row.autonomy_mode,
|
|
313
343
|
approvalMode: row.approval_mode,
|
|
@@ -351,6 +381,10 @@ function findAgentIdentity(agentId) {
|
|
|
351
381
|
agent_identities.id,
|
|
352
382
|
agent_identities.label,
|
|
353
383
|
agent_identities.agent_type,
|
|
384
|
+
agent_identities.identity_key,
|
|
385
|
+
agent_identities.provider,
|
|
386
|
+
agent_identities.machine_key,
|
|
387
|
+
agent_identities.persona_key,
|
|
354
388
|
agent_identities.trust_level,
|
|
355
389
|
agent_identities.autonomy_mode,
|
|
356
390
|
agent_identities.approval_mode,
|
|
@@ -358,36 +392,76 @@ function findAgentIdentity(agentId) {
|
|
|
358
392
|
agent_identities.created_at,
|
|
359
393
|
agent_identities.updated_at,
|
|
360
394
|
COUNT(agent_tokens.id) AS token_count,
|
|
361
|
-
COALESCE(SUM(CASE WHEN agent_tokens.revoked_at IS NULL THEN 1 ELSE 0 END), 0) AS active_token_count
|
|
395
|
+
COALESCE(SUM(CASE WHEN agent_tokens.id IS NOT NULL AND agent_tokens.revoked_at IS NULL THEN 1 ELSE 0 END), 0) AS active_token_count
|
|
362
396
|
FROM agent_identities
|
|
363
397
|
LEFT JOIN agent_tokens ON agent_tokens.agent_id = agent_identities.id
|
|
364
398
|
WHERE agent_identities.id = ?
|
|
365
399
|
GROUP BY agent_identities.id`)
|
|
366
400
|
.get(agentId);
|
|
367
|
-
|
|
401
|
+
const links = row ? listAgentIdentityUserLinks([row.id]) : new Map();
|
|
402
|
+
return row ? mapAgent(row, links.get(row.id) ?? []) : undefined;
|
|
403
|
+
}
|
|
404
|
+
function normalizeAgentIdentityPart(value) {
|
|
405
|
+
return value
|
|
406
|
+
?.trim()
|
|
407
|
+
.toLowerCase()
|
|
408
|
+
.replace(/[^a-z0-9._:]+/g, "_")
|
|
409
|
+
.replace(/^_+|_+$/g, "") || "";
|
|
410
|
+
}
|
|
411
|
+
function runtimeProviderFromAgentType(agentType) {
|
|
412
|
+
const normalized = normalizeAgentIdentityPart(agentType);
|
|
413
|
+
if (normalized === "openclaw" ||
|
|
414
|
+
normalized === "hermes" ||
|
|
415
|
+
normalized === "codex") {
|
|
416
|
+
return normalized;
|
|
417
|
+
}
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
function deriveTokenAgentIdentityFields(input) {
|
|
421
|
+
const provider = runtimeProviderFromAgentType(input.agentType);
|
|
422
|
+
if (!provider) {
|
|
423
|
+
return {
|
|
424
|
+
identityKey: null,
|
|
425
|
+
provider: null,
|
|
426
|
+
machineKey: null,
|
|
427
|
+
personaKey: null
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
identityKey: `runtime:${provider}:token:default`,
|
|
432
|
+
provider,
|
|
433
|
+
machineKey: "token",
|
|
434
|
+
personaKey: "default"
|
|
435
|
+
};
|
|
368
436
|
}
|
|
369
437
|
function upsertAgentIdentity(input) {
|
|
370
438
|
const now = new Date().toISOString();
|
|
439
|
+
const identityFields = deriveTokenAgentIdentityFields(input);
|
|
371
440
|
const existing = getDatabase()
|
|
372
441
|
.prepare(`SELECT id
|
|
373
442
|
FROM agent_identities
|
|
374
|
-
WHERE
|
|
443
|
+
WHERE (? IS NOT NULL AND identity_key = ?)
|
|
444
|
+
OR lower(label) = lower(?)
|
|
375
445
|
LIMIT 1`)
|
|
376
|
-
.get(input.agentLabel);
|
|
446
|
+
.get(identityFields.identityKey, identityFields.identityKey, input.agentLabel);
|
|
377
447
|
if (existing) {
|
|
378
448
|
getDatabase()
|
|
379
449
|
.prepare(`UPDATE agent_identities
|
|
380
|
-
SET agent_type = ?,
|
|
450
|
+
SET agent_type = ?, identity_key = COALESCE(identity_key, ?),
|
|
451
|
+
provider = COALESCE(provider, ?), machine_key = COALESCE(machine_key, ?),
|
|
452
|
+
persona_key = COALESCE(persona_key, ?), trust_level = ?,
|
|
453
|
+
autonomy_mode = ?, approval_mode = ?, description = ?, updated_at = ?
|
|
381
454
|
WHERE id = ?`)
|
|
382
|
-
.run(input.agentType, input.trustLevel, input.autonomyMode, input.approvalMode, input.description, now, existing.id);
|
|
455
|
+
.run(input.agentType, identityFields.identityKey, identityFields.provider, identityFields.machineKey, identityFields.personaKey, input.trustLevel, input.autonomyMode, input.approvalMode, input.description, now, existing.id);
|
|
383
456
|
return findAgentIdentity(existing.id);
|
|
384
457
|
}
|
|
385
458
|
const agentId = `agt_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
|
|
386
459
|
getDatabase()
|
|
387
460
|
.prepare(`INSERT INTO agent_identities (
|
|
388
|
-
id, label, agent_type,
|
|
389
|
-
|
|
390
|
-
|
|
461
|
+
id, label, agent_type, identity_key, provider, machine_key, persona_key,
|
|
462
|
+
trust_level, autonomy_mode, approval_mode, description, created_at, updated_at
|
|
463
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
464
|
+
.run(agentId, input.agentLabel, input.agentType, identityFields.identityKey, identityFields.provider, identityFields.machineKey, identityFields.personaKey, input.trustLevel, input.autonomyMode, input.approvalMode, input.description, now, now);
|
|
391
465
|
return findAgentIdentity(agentId);
|
|
392
466
|
}
|
|
393
467
|
function ensureSettingsRow(now = new Date().toISOString()) {
|
|
@@ -441,6 +515,10 @@ export function listAgentIdentities() {
|
|
|
441
515
|
agent_identities.id,
|
|
442
516
|
agent_identities.label,
|
|
443
517
|
agent_identities.agent_type,
|
|
518
|
+
agent_identities.identity_key,
|
|
519
|
+
agent_identities.provider,
|
|
520
|
+
agent_identities.machine_key,
|
|
521
|
+
agent_identities.persona_key,
|
|
444
522
|
agent_identities.trust_level,
|
|
445
523
|
agent_identities.autonomy_mode,
|
|
446
524
|
agent_identities.approval_mode,
|
|
@@ -448,19 +526,25 @@ export function listAgentIdentities() {
|
|
|
448
526
|
agent_identities.created_at,
|
|
449
527
|
agent_identities.updated_at,
|
|
450
528
|
COUNT(agent_tokens.id) AS token_count,
|
|
451
|
-
COALESCE(SUM(CASE WHEN agent_tokens.revoked_at IS NULL THEN 1 ELSE 0 END), 0) AS active_token_count
|
|
529
|
+
COALESCE(SUM(CASE WHEN agent_tokens.id IS NOT NULL AND agent_tokens.revoked_at IS NULL THEN 1 ELSE 0 END), 0) AS active_token_count
|
|
452
530
|
FROM agent_identities
|
|
453
531
|
LEFT JOIN agent_tokens ON agent_tokens.agent_id = agent_identities.id
|
|
454
532
|
GROUP BY agent_identities.id
|
|
455
533
|
ORDER BY agent_identities.created_at DESC`)
|
|
456
534
|
.all();
|
|
457
|
-
const
|
|
535
|
+
const links = listAgentIdentityUserLinks(rows.map((row) => row.id));
|
|
536
|
+
const manualAgents = rows.map((row) => mapAgent(row, links.get(row.id) ?? []));
|
|
458
537
|
const modelAgents = listAiModelConnections().map(buildConnectionAgentIdentity);
|
|
459
538
|
const settings = readSettingsRow();
|
|
460
539
|
const forgeAgent = agentIdentitySchema.parse({
|
|
461
540
|
id: FORGE_DEFAULT_AGENT_ID,
|
|
462
541
|
label: "Forge Agent",
|
|
463
542
|
agentType: "forge_default",
|
|
543
|
+
identityKey: "forge:default",
|
|
544
|
+
provider: null,
|
|
545
|
+
machineKey: null,
|
|
546
|
+
personaKey: "default",
|
|
547
|
+
linkedUsers: [],
|
|
464
548
|
trustLevel: "trusted",
|
|
465
549
|
autonomyMode: "approval_required",
|
|
466
550
|
approvalMode: "approval_by_default",
|
|
@@ -470,7 +554,11 @@ export function listAgentIdentities() {
|
|
|
470
554
|
createdAt: settings.created_at,
|
|
471
555
|
updatedAt: settings.updated_at
|
|
472
556
|
});
|
|
473
|
-
|
|
557
|
+
const deduped = new Map();
|
|
558
|
+
for (const agent of [forgeAgent, ...modelAgents, ...manualAgents]) {
|
|
559
|
+
deduped.set(agent.identityKey ?? agent.id, agent);
|
|
560
|
+
}
|
|
561
|
+
return Array.from(deduped.values());
|
|
474
562
|
}
|
|
475
563
|
export function isPsycheAuthRequired() {
|
|
476
564
|
ensureSettingsRow();
|
|
@@ -153,6 +153,29 @@ export function ensureSystemUsers() {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
|
+
export function ensureBotUser(input) {
|
|
157
|
+
const parsed = createUserSchema.parse({
|
|
158
|
+
kind: "bot",
|
|
159
|
+
handle: normalizeHandle(input.handle),
|
|
160
|
+
displayName: input.displayName,
|
|
161
|
+
description: input.description,
|
|
162
|
+
accentColor: input.accentColor
|
|
163
|
+
});
|
|
164
|
+
const now = new Date().toISOString();
|
|
165
|
+
getDatabase()
|
|
166
|
+
.prepare(`INSERT INTO users (id, kind, handle, display_name, description, accent_color, created_at, updated_at)
|
|
167
|
+
VALUES (?, 'bot', ?, ?, ?, ?, ?, ?)
|
|
168
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
169
|
+
kind = 'bot',
|
|
170
|
+
handle = excluded.handle,
|
|
171
|
+
display_name = excluded.display_name,
|
|
172
|
+
description = excluded.description,
|
|
173
|
+
accent_color = excluded.accent_color,
|
|
174
|
+
updated_at = excluded.updated_at`)
|
|
175
|
+
.run(input.id, parsed.handle, parsed.displayName, parsed.description, parsed.accentColor, now, now);
|
|
176
|
+
ensurePermissiveGrantsForUser(input.id, now);
|
|
177
|
+
return getUserById(input.id);
|
|
178
|
+
}
|
|
156
179
|
function ensurePermissiveGrantsForUser(userId, now) {
|
|
157
180
|
const existingUsers = listUsers();
|
|
158
181
|
for (const otherUser of existingUsers) {
|
|
@@ -2104,6 +2104,15 @@ export const agentIdentitySchema = z.object({
|
|
|
2104
2104
|
id: z.string(),
|
|
2105
2105
|
label: z.string(),
|
|
2106
2106
|
agentType: z.string(),
|
|
2107
|
+
identityKey: z.string().nullable().default(null),
|
|
2108
|
+
provider: agentRuntimeProviderSchema.nullable().default(null),
|
|
2109
|
+
machineKey: z.string().nullable().default(null),
|
|
2110
|
+
personaKey: z.string().nullable().default(null),
|
|
2111
|
+
linkedUsers: z.array(z.object({
|
|
2112
|
+
userId: z.string(),
|
|
2113
|
+
role: z.string(),
|
|
2114
|
+
user: userSummarySchema.nullable().default(null)
|
|
2115
|
+
})).default([]),
|
|
2107
2116
|
trustLevel: agentTrustLevelSchema,
|
|
2108
2117
|
autonomyMode: autonomyModeSchema,
|
|
2109
2118
|
approvalMode: approvalModeSchema,
|
|
@@ -2188,6 +2197,10 @@ export const createAgentRuntimeSessionSchema = z.object({
|
|
|
2188
2197
|
provider: agentRuntimeProviderSchema,
|
|
2189
2198
|
agentLabel: nonEmptyTrimmedString,
|
|
2190
2199
|
agentType: trimmedString.default("assistant"),
|
|
2200
|
+
agentIdentityKey: trimmedString.optional(),
|
|
2201
|
+
machineKey: trimmedString.optional(),
|
|
2202
|
+
personaKey: trimmedString.optional(),
|
|
2203
|
+
linkedUserIds: uniqueStringArraySchema.default([]),
|
|
2191
2204
|
actorLabel: nonEmptyTrimmedString,
|
|
2192
2205
|
sessionKey: nonEmptyTrimmedString,
|
|
2193
2206
|
sessionLabel: trimmedString.default(""),
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forge-openclaw-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.49",
|
|
4
4
|
"description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -95,10 +95,13 @@
|
|
|
95
95
|
"zustand": "^5.0.5"
|
|
96
96
|
},
|
|
97
97
|
"overrides": {
|
|
98
|
+
"@aws-sdk/xml-builder": "^3.972.19",
|
|
98
99
|
"basic-ftp": "^5.3.0",
|
|
99
100
|
"axios": "^1.15.0",
|
|
101
|
+
"fast-xml-parser": "^5.7.1",
|
|
100
102
|
"follow-redirects": "^1.16.0",
|
|
101
|
-
"hono": "4.12.14"
|
|
103
|
+
"hono": "4.12.14",
|
|
104
|
+
"uuid": "^14.0.0"
|
|
102
105
|
},
|
|
103
106
|
"scripts": {
|
|
104
107
|
"build": "node ./scripts/build.mjs"
|