@alfe.ai/openclaw-identity 0.0.3 → 0.0.5
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/plugin.cjs +73 -113
- package/dist/plugin.d.cts +1 -0
- package/dist/plugin.d.ts +1 -0
- package/dist/plugin.js +73 -113
- package/package.json +2 -1
package/dist/plugin.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
let _alfe_ai_config = require("@alfe.ai/config");
|
|
2
|
+
let _alfe_ai_agent_api_client = require("@alfe.ai/agent-api-client");
|
|
2
3
|
let _sinclair_typebox = require("@sinclair/typebox");
|
|
3
4
|
//#region src/plugin.ts
|
|
4
5
|
/**
|
|
@@ -12,27 +13,9 @@ let _sinclair_typebox = require("@sinclair/typebox");
|
|
|
12
13
|
* - before_tool_call → enforce permissions from cached policy
|
|
13
14
|
* - after_tool_call → log tool execution audit
|
|
14
15
|
*
|
|
15
|
-
* All data access via
|
|
16
|
+
* All data access via AgentApiClient (/agent/identity/* routes).
|
|
16
17
|
* Uses the agent's own API key (from ~/.alfe/config.toml) for authentication.
|
|
17
18
|
*/
|
|
18
|
-
function createHttpClient(apiUrl, apiKey) {
|
|
19
|
-
async function request(method, path, body) {
|
|
20
|
-
const res = await fetch(`${apiUrl}${path}`, {
|
|
21
|
-
method,
|
|
22
|
-
headers: {
|
|
23
|
-
Authorization: `Bearer ${apiKey}`,
|
|
24
|
-
"Content-Type": "application/json"
|
|
25
|
-
},
|
|
26
|
-
...body ? { body: JSON.stringify(body) } : {}
|
|
27
|
-
});
|
|
28
|
-
if (!res.ok) {
|
|
29
|
-
const text = await res.text().catch(() => "");
|
|
30
|
-
throw new Error(`Identity API ${method} ${path}: ${String(res.status)} ${text}`);
|
|
31
|
-
}
|
|
32
|
-
return (await res.json()).data;
|
|
33
|
-
}
|
|
34
|
-
return { request };
|
|
35
|
-
}
|
|
36
19
|
const CACHE_TTL_MS = 6e4;
|
|
37
20
|
const sessionCache = /* @__PURE__ */ new Map();
|
|
38
21
|
function getCached(key) {
|
|
@@ -90,18 +73,27 @@ const plugin = {
|
|
|
90
73
|
version: "0.0.1",
|
|
91
74
|
activate(api) {
|
|
92
75
|
const log = api.logger;
|
|
76
|
+
if (api.registrationMode && api.registrationMode !== "full") return;
|
|
93
77
|
log.info("Alfe Identity plugin activating...");
|
|
94
|
-
let
|
|
95
|
-
let apiKey;
|
|
78
|
+
let client;
|
|
96
79
|
try {
|
|
97
80
|
const config = (0, _alfe_ai_config.resolveConfig)();
|
|
98
|
-
|
|
99
|
-
|
|
81
|
+
client = new _alfe_ai_agent_api_client.AgentApiClient({
|
|
82
|
+
apiKey: config.apiKey,
|
|
83
|
+
apiUrl: config.apiUrl
|
|
84
|
+
});
|
|
100
85
|
} catch (err) {
|
|
101
86
|
log.error(`Identity plugin: failed to resolve config — ${err instanceof Error ? err.message : String(err)}`);
|
|
102
87
|
return;
|
|
103
88
|
}
|
|
104
|
-
|
|
89
|
+
let failureMode = "open";
|
|
90
|
+
client.getIntegrationConfig("alfe").then((alfeConfig) => {
|
|
91
|
+
const mode = alfeConfig.config.identity_failure_mode;
|
|
92
|
+
if (mode === "open" || mode === "closed" || mode === "permissive") failureMode = mode;
|
|
93
|
+
log.info(`Identity failure mode: ${failureMode}`);
|
|
94
|
+
}).catch(() => {
|
|
95
|
+
log.info(`Identity failure mode: ${failureMode} (default — config fetch failed)`);
|
|
96
|
+
});
|
|
105
97
|
const identityToolNames = /* @__PURE__ */ new Set();
|
|
106
98
|
const tools = [
|
|
107
99
|
defineTool({
|
|
@@ -109,54 +101,45 @@ const plugin = {
|
|
|
109
101
|
description: "Look up full identity context by platform and ID — returns profile, notes, tags, platforms, and recent changelog",
|
|
110
102
|
parameters: _sinclair_typebox.Type.Object({
|
|
111
103
|
platform: _sinclair_typebox.Type.String({ description: "Platform name (discord, slack, chat, sms, whatsapp, etc.)" }),
|
|
112
|
-
platformId: _sinclair_typebox.Type.String({ description: "Platform-specific user identifier" })
|
|
113
|
-
tenantId: _sinclair_typebox.Type.String()
|
|
104
|
+
platformId: _sinclair_typebox.Type.String({ description: "Platform-specific user identifier" })
|
|
114
105
|
}),
|
|
115
106
|
handler: async (params) => {
|
|
116
|
-
const result = await
|
|
107
|
+
const result = await client.resolveIdentity({
|
|
117
108
|
platform: params.platform,
|
|
118
|
-
platformId: params.platformId
|
|
119
|
-
tenantId: params.tenantId,
|
|
120
|
-
agentId: "plugin"
|
|
109
|
+
platformId: params.platformId
|
|
121
110
|
});
|
|
122
111
|
if (!result.identityId) return { found: false };
|
|
123
|
-
return
|
|
112
|
+
return client.getIdentityContext(result.identityId);
|
|
124
113
|
}
|
|
125
114
|
}),
|
|
126
115
|
defineTool({
|
|
127
116
|
name: "lookup_identity",
|
|
128
117
|
description: "Search identities by name, email, phone, tag, or platform. Returns multiple matches.",
|
|
129
118
|
parameters: _sinclair_typebox.Type.Object({
|
|
130
|
-
tenantId: _sinclair_typebox.Type.String(),
|
|
131
119
|
query: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "Text search query" })),
|
|
132
120
|
status: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String()),
|
|
133
121
|
tag: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String()),
|
|
134
122
|
platform: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String())
|
|
135
123
|
}),
|
|
136
|
-
handler:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return http.request("GET", `/internal/identity/search?${qs.toString()}`);
|
|
143
|
-
}
|
|
124
|
+
handler: (params) => client.searchIdentities({
|
|
125
|
+
q: params.query,
|
|
126
|
+
status: params.status,
|
|
127
|
+
tag: params.tag,
|
|
128
|
+
platform: params.platform
|
|
129
|
+
})
|
|
144
130
|
}),
|
|
145
131
|
defineTool({
|
|
146
132
|
name: "create_identity",
|
|
147
133
|
description: "Create a new identity record with profile fields",
|
|
148
134
|
parameters: _sinclair_typebox.Type.Object({
|
|
149
|
-
tenantId: _sinclair_typebox.Type.String(),
|
|
150
135
|
platform: _sinclair_typebox.Type.String(),
|
|
151
136
|
platformId: _sinclair_typebox.Type.String(),
|
|
152
137
|
displayName: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String())
|
|
153
138
|
}),
|
|
154
|
-
handler: (params) =>
|
|
139
|
+
handler: (params) => client.resolveIdentity({
|
|
155
140
|
platform: params.platform,
|
|
156
141
|
platformId: params.platformId,
|
|
157
|
-
displayName: params.displayName
|
|
158
|
-
tenantId: params.tenantId,
|
|
159
|
-
agentId: "plugin"
|
|
142
|
+
displayName: params.displayName
|
|
160
143
|
})
|
|
161
144
|
}),
|
|
162
145
|
defineTool({
|
|
@@ -164,11 +147,9 @@ const plugin = {
|
|
|
164
147
|
description: "Merge two identity records — transfers notes, tags, aliases, platforms to survivor",
|
|
165
148
|
parameters: _sinclair_typebox.Type.Object({
|
|
166
149
|
survivorId: _sinclair_typebox.Type.String({ description: "Identity to keep" }),
|
|
167
|
-
mergedId: _sinclair_typebox.Type.String({ description: "Identity to merge into survivor" })
|
|
168
|
-
tenantId: _sinclair_typebox.Type.String()
|
|
150
|
+
mergedId: _sinclair_typebox.Type.String({ description: "Identity to merge into survivor" })
|
|
169
151
|
}),
|
|
170
|
-
handler: (params) =>
|
|
171
|
-
tenantId: params.tenantId,
|
|
152
|
+
handler: (params) => client.mergeIdentities(params.survivorId, {
|
|
172
153
|
mergedId: params.mergedId,
|
|
173
154
|
changedBy: {
|
|
174
155
|
type: "agent",
|
|
@@ -179,17 +160,11 @@ const plugin = {
|
|
|
179
160
|
defineTool({
|
|
180
161
|
name: "unmerge_identities",
|
|
181
162
|
description: "Reverse a merge — restore previously merged identity",
|
|
182
|
-
parameters: _sinclair_typebox.Type.Object({
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
tenantId: params.tenantId,
|
|
188
|
-
changedBy: {
|
|
189
|
-
type: "agent",
|
|
190
|
-
id: "plugin"
|
|
191
|
-
}
|
|
192
|
-
})
|
|
163
|
+
parameters: _sinclair_typebox.Type.Object({ mergedId: _sinclair_typebox.Type.String({ description: "Identity that was merged (has mergedInto pointer)" }) }),
|
|
164
|
+
handler: (params) => client.unmergeIdentity(params.mergedId, { changedBy: {
|
|
165
|
+
type: "agent",
|
|
166
|
+
id: "plugin"
|
|
167
|
+
} })
|
|
193
168
|
}),
|
|
194
169
|
defineTool({
|
|
195
170
|
name: "link_platform",
|
|
@@ -197,14 +172,11 @@ const plugin = {
|
|
|
197
172
|
parameters: _sinclair_typebox.Type.Object({
|
|
198
173
|
identityId: _sinclair_typebox.Type.String(),
|
|
199
174
|
platform: _sinclair_typebox.Type.String(),
|
|
200
|
-
platformId: _sinclair_typebox.Type.String()
|
|
201
|
-
tenantId: _sinclair_typebox.Type.String()
|
|
175
|
+
platformId: _sinclair_typebox.Type.String()
|
|
202
176
|
}),
|
|
203
|
-
handler: (params) =>
|
|
177
|
+
handler: (params) => client.resolveIdentity({
|
|
204
178
|
platform: params.platform,
|
|
205
|
-
platformId: params.platformId
|
|
206
|
-
tenantId: params.tenantId,
|
|
207
|
-
agentId: "plugin"
|
|
179
|
+
platformId: params.platformId
|
|
208
180
|
})
|
|
209
181
|
}),
|
|
210
182
|
defineTool({
|
|
@@ -212,12 +184,10 @@ const plugin = {
|
|
|
212
184
|
description: "Add an observation or note about a contact",
|
|
213
185
|
parameters: _sinclair_typebox.Type.Object({
|
|
214
186
|
identityId: _sinclair_typebox.Type.String(),
|
|
215
|
-
tenantId: _sinclair_typebox.Type.String(),
|
|
216
187
|
content: _sinclair_typebox.Type.String(),
|
|
217
188
|
category: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "observation, preference, relationship, context, or warning" }))
|
|
218
189
|
}),
|
|
219
|
-
handler: (params) =>
|
|
220
|
-
tenantId: params.tenantId,
|
|
190
|
+
handler: (params) => client.addIdentityNote(params.identityId, {
|
|
221
191
|
content: params.content,
|
|
222
192
|
category: params.category,
|
|
223
193
|
changedBy: {
|
|
@@ -231,12 +201,10 @@ const plugin = {
|
|
|
231
201
|
description: "Add or remove a tag on an identity",
|
|
232
202
|
parameters: _sinclair_typebox.Type.Object({
|
|
233
203
|
identityId: _sinclair_typebox.Type.String(),
|
|
234
|
-
tenantId: _sinclair_typebox.Type.String(),
|
|
235
204
|
tag: _sinclair_typebox.Type.String(),
|
|
236
205
|
action: _sinclair_typebox.Type.String({ description: "'add' or 'remove'" })
|
|
237
206
|
}),
|
|
238
|
-
handler: (params) =>
|
|
239
|
-
tenantId: params.tenantId,
|
|
207
|
+
handler: (params) => client.tagIdentity(params.identityId, {
|
|
240
208
|
tag: params.tag,
|
|
241
209
|
action: params.action,
|
|
242
210
|
changedBy: {
|
|
@@ -250,25 +218,18 @@ const plugin = {
|
|
|
250
218
|
description: "Get full changelog for an identity — all versions, diffs, who changed what",
|
|
251
219
|
parameters: _sinclair_typebox.Type.Object({
|
|
252
220
|
identityId: _sinclair_typebox.Type.String(),
|
|
253
|
-
tenantId: _sinclair_typebox.Type.String(),
|
|
254
221
|
limit: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.Number())
|
|
255
222
|
}),
|
|
256
|
-
handler:
|
|
257
|
-
const qs = new URLSearchParams({ tenantId: params.tenantId });
|
|
258
|
-
if (params.limit) qs.set("limit", String(Number(params.limit)));
|
|
259
|
-
return http.request("GET", `/internal/identity/${encodeURIComponent(params.identityId)}/changelog?${qs.toString()}`);
|
|
260
|
-
}
|
|
223
|
+
handler: (params) => client.getIdentityChangelog(params.identityId, { limit: params.limit })
|
|
261
224
|
}),
|
|
262
225
|
defineTool({
|
|
263
226
|
name: "rollback_identity",
|
|
264
227
|
description: "Revert an identity to a previous version",
|
|
265
228
|
parameters: _sinclair_typebox.Type.Object({
|
|
266
229
|
identityId: _sinclair_typebox.Type.String(),
|
|
267
|
-
tenantId: _sinclair_typebox.Type.String(),
|
|
268
230
|
targetVersion: _sinclair_typebox.Type.Number()
|
|
269
231
|
}),
|
|
270
|
-
handler: (params) =>
|
|
271
|
-
tenantId: params.tenantId,
|
|
232
|
+
handler: (params) => client.rollbackIdentity(params.identityId, {
|
|
272
233
|
targetVersion: params.targetVersion,
|
|
273
234
|
changedBy: {
|
|
274
235
|
type: "agent",
|
|
@@ -282,14 +243,12 @@ const plugin = {
|
|
|
282
243
|
parameters: _sinclair_typebox.Type.Object({
|
|
283
244
|
platform: _sinclair_typebox.Type.String(),
|
|
284
245
|
senderId: _sinclair_typebox.Type.String(),
|
|
285
|
-
channelId: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String())
|
|
286
|
-
tenantId: _sinclair_typebox.Type.String()
|
|
246
|
+
channelId: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String())
|
|
287
247
|
}),
|
|
288
|
-
handler: (params) =>
|
|
248
|
+
handler: (params) => client.enforcePolicy({
|
|
289
249
|
platform: params.platform,
|
|
290
250
|
senderId: params.senderId,
|
|
291
|
-
channelId: params.channelId
|
|
292
|
-
tenantId: params.tenantId
|
|
251
|
+
channelId: params.channelId
|
|
293
252
|
})
|
|
294
253
|
}),
|
|
295
254
|
defineTool({
|
|
@@ -298,14 +257,12 @@ const plugin = {
|
|
|
298
257
|
parameters: _sinclair_typebox.Type.Object({
|
|
299
258
|
platform: _sinclair_typebox.Type.String(),
|
|
300
259
|
senderId: _sinclair_typebox.Type.String(),
|
|
301
|
-
toolName: _sinclair_typebox.Type.String()
|
|
302
|
-
tenantId: _sinclair_typebox.Type.String()
|
|
260
|
+
toolName: _sinclair_typebox.Type.String()
|
|
303
261
|
}),
|
|
304
|
-
handler: (params) =>
|
|
262
|
+
handler: (params) => client.checkToolPermission({
|
|
305
263
|
platform: params.platform,
|
|
306
264
|
senderId: params.senderId,
|
|
307
|
-
toolName: params.toolName
|
|
308
|
-
tenantId: params.tenantId
|
|
265
|
+
toolName: params.toolName
|
|
309
266
|
})
|
|
310
267
|
})
|
|
311
268
|
];
|
|
@@ -330,19 +287,14 @@ const plugin = {
|
|
|
330
287
|
return;
|
|
331
288
|
}
|
|
332
289
|
try {
|
|
333
|
-
const
|
|
334
|
-
const agentId = ctx.agentId ?? "";
|
|
335
|
-
const resolveResult = await http.request("POST", "/internal/identity/resolve", {
|
|
290
|
+
const resolveResult = await client.resolveIdentity({
|
|
336
291
|
platform,
|
|
337
|
-
platformId: senderId
|
|
338
|
-
tenantId,
|
|
339
|
-
agentId
|
|
292
|
+
platformId: senderId
|
|
340
293
|
});
|
|
341
294
|
setCached(cacheKey, {
|
|
342
|
-
...await
|
|
295
|
+
...await client.enforcePolicy({
|
|
343
296
|
platform,
|
|
344
|
-
senderId
|
|
345
|
-
tenantId
|
|
297
|
+
senderId
|
|
346
298
|
}),
|
|
347
299
|
accessAllowed: resolveResult.accessAllowed
|
|
348
300
|
});
|
|
@@ -353,6 +305,10 @@ const plugin = {
|
|
|
353
305
|
log.info(`Identity resolved: ${senderId} → ${resolveResult.identityId ?? "unknown"} (${resolveResult.status})`);
|
|
354
306
|
} catch (e) {
|
|
355
307
|
log.error(`Identity resolution failed for ${senderId}: ${e.message}`);
|
|
308
|
+
if (failureMode === "closed") return {
|
|
309
|
+
block: true,
|
|
310
|
+
blockReason: "Identity: identity service unavailable — access denied (closed mode)"
|
|
311
|
+
};
|
|
356
312
|
}
|
|
357
313
|
}, { priority: 100 });
|
|
358
314
|
api.on("before_tool_call", async (...args) => {
|
|
@@ -362,14 +318,20 @@ const plugin = {
|
|
|
362
318
|
const sessionKey = ctx.sessionKey;
|
|
363
319
|
if (!sessionKey) return;
|
|
364
320
|
const perms = getCached(sessionKey);
|
|
365
|
-
if (!perms)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
321
|
+
if (!perms) {
|
|
322
|
+
if (failureMode === "permissive") return;
|
|
323
|
+
return {
|
|
324
|
+
block: true,
|
|
325
|
+
blockReason: "Identity: no identity context established — tool access denied"
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (!perms.identified) {
|
|
329
|
+
if (failureMode === "permissive") return;
|
|
330
|
+
return {
|
|
331
|
+
block: true,
|
|
332
|
+
blockReason: "Identity: unknown sender identity — tool access denied"
|
|
333
|
+
};
|
|
334
|
+
}
|
|
373
335
|
if (perms.deniedTools.includes("*") || perms.deniedTools.includes(event.toolName)) return {
|
|
374
336
|
block: true,
|
|
375
337
|
blockReason: `Identity: tool '${event.toolName}' is denied for your role`
|
|
@@ -381,17 +343,15 @@ const plugin = {
|
|
|
381
343
|
}, { priority: 100 });
|
|
382
344
|
api.on("after_tool_call", async (...args) => {
|
|
383
345
|
const event = args[0];
|
|
384
|
-
const
|
|
385
|
-
const sessionKey = ctx.sessionKey;
|
|
346
|
+
const sessionKey = args[1].sessionKey;
|
|
386
347
|
if (!sessionKey) return;
|
|
387
348
|
const perms = getCached(sessionKey);
|
|
388
349
|
if (!perms?.identityId) return;
|
|
389
350
|
try {
|
|
390
|
-
await
|
|
351
|
+
await client.checkToolPermission({
|
|
391
352
|
platform: "tool_audit",
|
|
392
353
|
senderId: perms.identityId,
|
|
393
|
-
toolName: event.toolName
|
|
394
|
-
tenantId: ctx.tenantId ?? ""
|
|
354
|
+
toolName: event.toolName
|
|
395
355
|
});
|
|
396
356
|
} catch (e) {
|
|
397
357
|
log.error(`Audit logging failed: ${e.message}`);
|
package/dist/plugin.d.cts
CHANGED
|
@@ -23,6 +23,7 @@ interface ToolDef {
|
|
|
23
23
|
}
|
|
24
24
|
interface PluginApi {
|
|
25
25
|
logger: PluginLogger;
|
|
26
|
+
registrationMode?: "full" | "setup-only" | "setup-runtime" | "cli-metadata";
|
|
26
27
|
registerTool: (tool: ToolDef) => void;
|
|
27
28
|
on: (event: string, handler: (...args: unknown[]) => Promise<unknown>, options?: {
|
|
28
29
|
priority?: number;
|
package/dist/plugin.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ interface ToolDef {
|
|
|
23
23
|
}
|
|
24
24
|
interface PluginApi {
|
|
25
25
|
logger: PluginLogger;
|
|
26
|
+
registrationMode?: "full" | "setup-only" | "setup-runtime" | "cli-metadata";
|
|
26
27
|
registerTool: (tool: ToolDef) => void;
|
|
27
28
|
on: (event: string, handler: (...args: unknown[]) => Promise<unknown>, options?: {
|
|
28
29
|
priority?: number;
|
package/dist/plugin.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveConfig } from "@alfe.ai/config";
|
|
2
|
+
import { AgentApiClient } from "@alfe.ai/agent-api-client";
|
|
2
3
|
import { Type } from "@sinclair/typebox";
|
|
3
4
|
//#region src/plugin.ts
|
|
4
5
|
/**
|
|
@@ -12,27 +13,9 @@ import { Type } from "@sinclair/typebox";
|
|
|
12
13
|
* - before_tool_call → enforce permissions from cached policy
|
|
13
14
|
* - after_tool_call → log tool execution audit
|
|
14
15
|
*
|
|
15
|
-
* All data access via
|
|
16
|
+
* All data access via AgentApiClient (/agent/identity/* routes).
|
|
16
17
|
* Uses the agent's own API key (from ~/.alfe/config.toml) for authentication.
|
|
17
18
|
*/
|
|
18
|
-
function createHttpClient(apiUrl, apiKey) {
|
|
19
|
-
async function request(method, path, body) {
|
|
20
|
-
const res = await fetch(`${apiUrl}${path}`, {
|
|
21
|
-
method,
|
|
22
|
-
headers: {
|
|
23
|
-
Authorization: `Bearer ${apiKey}`,
|
|
24
|
-
"Content-Type": "application/json"
|
|
25
|
-
},
|
|
26
|
-
...body ? { body: JSON.stringify(body) } : {}
|
|
27
|
-
});
|
|
28
|
-
if (!res.ok) {
|
|
29
|
-
const text = await res.text().catch(() => "");
|
|
30
|
-
throw new Error(`Identity API ${method} ${path}: ${String(res.status)} ${text}`);
|
|
31
|
-
}
|
|
32
|
-
return (await res.json()).data;
|
|
33
|
-
}
|
|
34
|
-
return { request };
|
|
35
|
-
}
|
|
36
19
|
const CACHE_TTL_MS = 6e4;
|
|
37
20
|
const sessionCache = /* @__PURE__ */ new Map();
|
|
38
21
|
function getCached(key) {
|
|
@@ -90,18 +73,27 @@ const plugin = {
|
|
|
90
73
|
version: "0.0.1",
|
|
91
74
|
activate(api) {
|
|
92
75
|
const log = api.logger;
|
|
76
|
+
if (api.registrationMode && api.registrationMode !== "full") return;
|
|
93
77
|
log.info("Alfe Identity plugin activating...");
|
|
94
|
-
let
|
|
95
|
-
let apiKey;
|
|
78
|
+
let client;
|
|
96
79
|
try {
|
|
97
80
|
const config = resolveConfig();
|
|
98
|
-
|
|
99
|
-
|
|
81
|
+
client = new AgentApiClient({
|
|
82
|
+
apiKey: config.apiKey,
|
|
83
|
+
apiUrl: config.apiUrl
|
|
84
|
+
});
|
|
100
85
|
} catch (err) {
|
|
101
86
|
log.error(`Identity plugin: failed to resolve config — ${err instanceof Error ? err.message : String(err)}`);
|
|
102
87
|
return;
|
|
103
88
|
}
|
|
104
|
-
|
|
89
|
+
let failureMode = "open";
|
|
90
|
+
client.getIntegrationConfig("alfe").then((alfeConfig) => {
|
|
91
|
+
const mode = alfeConfig.config.identity_failure_mode;
|
|
92
|
+
if (mode === "open" || mode === "closed" || mode === "permissive") failureMode = mode;
|
|
93
|
+
log.info(`Identity failure mode: ${failureMode}`);
|
|
94
|
+
}).catch(() => {
|
|
95
|
+
log.info(`Identity failure mode: ${failureMode} (default — config fetch failed)`);
|
|
96
|
+
});
|
|
105
97
|
const identityToolNames = /* @__PURE__ */ new Set();
|
|
106
98
|
const tools = [
|
|
107
99
|
defineTool({
|
|
@@ -109,54 +101,45 @@ const plugin = {
|
|
|
109
101
|
description: "Look up full identity context by platform and ID — returns profile, notes, tags, platforms, and recent changelog",
|
|
110
102
|
parameters: Type.Object({
|
|
111
103
|
platform: Type.String({ description: "Platform name (discord, slack, chat, sms, whatsapp, etc.)" }),
|
|
112
|
-
platformId: Type.String({ description: "Platform-specific user identifier" })
|
|
113
|
-
tenantId: Type.String()
|
|
104
|
+
platformId: Type.String({ description: "Platform-specific user identifier" })
|
|
114
105
|
}),
|
|
115
106
|
handler: async (params) => {
|
|
116
|
-
const result = await
|
|
107
|
+
const result = await client.resolveIdentity({
|
|
117
108
|
platform: params.platform,
|
|
118
|
-
platformId: params.platformId
|
|
119
|
-
tenantId: params.tenantId,
|
|
120
|
-
agentId: "plugin"
|
|
109
|
+
platformId: params.platformId
|
|
121
110
|
});
|
|
122
111
|
if (!result.identityId) return { found: false };
|
|
123
|
-
return
|
|
112
|
+
return client.getIdentityContext(result.identityId);
|
|
124
113
|
}
|
|
125
114
|
}),
|
|
126
115
|
defineTool({
|
|
127
116
|
name: "lookup_identity",
|
|
128
117
|
description: "Search identities by name, email, phone, tag, or platform. Returns multiple matches.",
|
|
129
118
|
parameters: Type.Object({
|
|
130
|
-
tenantId: Type.String(),
|
|
131
119
|
query: Type.Optional(Type.String({ description: "Text search query" })),
|
|
132
120
|
status: Type.Optional(Type.String()),
|
|
133
121
|
tag: Type.Optional(Type.String()),
|
|
134
122
|
platform: Type.Optional(Type.String())
|
|
135
123
|
}),
|
|
136
|
-
handler:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return http.request("GET", `/internal/identity/search?${qs.toString()}`);
|
|
143
|
-
}
|
|
124
|
+
handler: (params) => client.searchIdentities({
|
|
125
|
+
q: params.query,
|
|
126
|
+
status: params.status,
|
|
127
|
+
tag: params.tag,
|
|
128
|
+
platform: params.platform
|
|
129
|
+
})
|
|
144
130
|
}),
|
|
145
131
|
defineTool({
|
|
146
132
|
name: "create_identity",
|
|
147
133
|
description: "Create a new identity record with profile fields",
|
|
148
134
|
parameters: Type.Object({
|
|
149
|
-
tenantId: Type.String(),
|
|
150
135
|
platform: Type.String(),
|
|
151
136
|
platformId: Type.String(),
|
|
152
137
|
displayName: Type.Optional(Type.String())
|
|
153
138
|
}),
|
|
154
|
-
handler: (params) =>
|
|
139
|
+
handler: (params) => client.resolveIdentity({
|
|
155
140
|
platform: params.platform,
|
|
156
141
|
platformId: params.platformId,
|
|
157
|
-
displayName: params.displayName
|
|
158
|
-
tenantId: params.tenantId,
|
|
159
|
-
agentId: "plugin"
|
|
142
|
+
displayName: params.displayName
|
|
160
143
|
})
|
|
161
144
|
}),
|
|
162
145
|
defineTool({
|
|
@@ -164,11 +147,9 @@ const plugin = {
|
|
|
164
147
|
description: "Merge two identity records — transfers notes, tags, aliases, platforms to survivor",
|
|
165
148
|
parameters: Type.Object({
|
|
166
149
|
survivorId: Type.String({ description: "Identity to keep" }),
|
|
167
|
-
mergedId: Type.String({ description: "Identity to merge into survivor" })
|
|
168
|
-
tenantId: Type.String()
|
|
150
|
+
mergedId: Type.String({ description: "Identity to merge into survivor" })
|
|
169
151
|
}),
|
|
170
|
-
handler: (params) =>
|
|
171
|
-
tenantId: params.tenantId,
|
|
152
|
+
handler: (params) => client.mergeIdentities(params.survivorId, {
|
|
172
153
|
mergedId: params.mergedId,
|
|
173
154
|
changedBy: {
|
|
174
155
|
type: "agent",
|
|
@@ -179,17 +160,11 @@ const plugin = {
|
|
|
179
160
|
defineTool({
|
|
180
161
|
name: "unmerge_identities",
|
|
181
162
|
description: "Reverse a merge — restore previously merged identity",
|
|
182
|
-
parameters: Type.Object({
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
tenantId: params.tenantId,
|
|
188
|
-
changedBy: {
|
|
189
|
-
type: "agent",
|
|
190
|
-
id: "plugin"
|
|
191
|
-
}
|
|
192
|
-
})
|
|
163
|
+
parameters: Type.Object({ mergedId: Type.String({ description: "Identity that was merged (has mergedInto pointer)" }) }),
|
|
164
|
+
handler: (params) => client.unmergeIdentity(params.mergedId, { changedBy: {
|
|
165
|
+
type: "agent",
|
|
166
|
+
id: "plugin"
|
|
167
|
+
} })
|
|
193
168
|
}),
|
|
194
169
|
defineTool({
|
|
195
170
|
name: "link_platform",
|
|
@@ -197,14 +172,11 @@ const plugin = {
|
|
|
197
172
|
parameters: Type.Object({
|
|
198
173
|
identityId: Type.String(),
|
|
199
174
|
platform: Type.String(),
|
|
200
|
-
platformId: Type.String()
|
|
201
|
-
tenantId: Type.String()
|
|
175
|
+
platformId: Type.String()
|
|
202
176
|
}),
|
|
203
|
-
handler: (params) =>
|
|
177
|
+
handler: (params) => client.resolveIdentity({
|
|
204
178
|
platform: params.platform,
|
|
205
|
-
platformId: params.platformId
|
|
206
|
-
tenantId: params.tenantId,
|
|
207
|
-
agentId: "plugin"
|
|
179
|
+
platformId: params.platformId
|
|
208
180
|
})
|
|
209
181
|
}),
|
|
210
182
|
defineTool({
|
|
@@ -212,12 +184,10 @@ const plugin = {
|
|
|
212
184
|
description: "Add an observation or note about a contact",
|
|
213
185
|
parameters: Type.Object({
|
|
214
186
|
identityId: Type.String(),
|
|
215
|
-
tenantId: Type.String(),
|
|
216
187
|
content: Type.String(),
|
|
217
188
|
category: Type.Optional(Type.String({ description: "observation, preference, relationship, context, or warning" }))
|
|
218
189
|
}),
|
|
219
|
-
handler: (params) =>
|
|
220
|
-
tenantId: params.tenantId,
|
|
190
|
+
handler: (params) => client.addIdentityNote(params.identityId, {
|
|
221
191
|
content: params.content,
|
|
222
192
|
category: params.category,
|
|
223
193
|
changedBy: {
|
|
@@ -231,12 +201,10 @@ const plugin = {
|
|
|
231
201
|
description: "Add or remove a tag on an identity",
|
|
232
202
|
parameters: Type.Object({
|
|
233
203
|
identityId: Type.String(),
|
|
234
|
-
tenantId: Type.String(),
|
|
235
204
|
tag: Type.String(),
|
|
236
205
|
action: Type.String({ description: "'add' or 'remove'" })
|
|
237
206
|
}),
|
|
238
|
-
handler: (params) =>
|
|
239
|
-
tenantId: params.tenantId,
|
|
207
|
+
handler: (params) => client.tagIdentity(params.identityId, {
|
|
240
208
|
tag: params.tag,
|
|
241
209
|
action: params.action,
|
|
242
210
|
changedBy: {
|
|
@@ -250,25 +218,18 @@ const plugin = {
|
|
|
250
218
|
description: "Get full changelog for an identity — all versions, diffs, who changed what",
|
|
251
219
|
parameters: Type.Object({
|
|
252
220
|
identityId: Type.String(),
|
|
253
|
-
tenantId: Type.String(),
|
|
254
221
|
limit: Type.Optional(Type.Number())
|
|
255
222
|
}),
|
|
256
|
-
handler:
|
|
257
|
-
const qs = new URLSearchParams({ tenantId: params.tenantId });
|
|
258
|
-
if (params.limit) qs.set("limit", String(Number(params.limit)));
|
|
259
|
-
return http.request("GET", `/internal/identity/${encodeURIComponent(params.identityId)}/changelog?${qs.toString()}`);
|
|
260
|
-
}
|
|
223
|
+
handler: (params) => client.getIdentityChangelog(params.identityId, { limit: params.limit })
|
|
261
224
|
}),
|
|
262
225
|
defineTool({
|
|
263
226
|
name: "rollback_identity",
|
|
264
227
|
description: "Revert an identity to a previous version",
|
|
265
228
|
parameters: Type.Object({
|
|
266
229
|
identityId: Type.String(),
|
|
267
|
-
tenantId: Type.String(),
|
|
268
230
|
targetVersion: Type.Number()
|
|
269
231
|
}),
|
|
270
|
-
handler: (params) =>
|
|
271
|
-
tenantId: params.tenantId,
|
|
232
|
+
handler: (params) => client.rollbackIdentity(params.identityId, {
|
|
272
233
|
targetVersion: params.targetVersion,
|
|
273
234
|
changedBy: {
|
|
274
235
|
type: "agent",
|
|
@@ -282,14 +243,12 @@ const plugin = {
|
|
|
282
243
|
parameters: Type.Object({
|
|
283
244
|
platform: Type.String(),
|
|
284
245
|
senderId: Type.String(),
|
|
285
|
-
channelId: Type.Optional(Type.String())
|
|
286
|
-
tenantId: Type.String()
|
|
246
|
+
channelId: Type.Optional(Type.String())
|
|
287
247
|
}),
|
|
288
|
-
handler: (params) =>
|
|
248
|
+
handler: (params) => client.enforcePolicy({
|
|
289
249
|
platform: params.platform,
|
|
290
250
|
senderId: params.senderId,
|
|
291
|
-
channelId: params.channelId
|
|
292
|
-
tenantId: params.tenantId
|
|
251
|
+
channelId: params.channelId
|
|
293
252
|
})
|
|
294
253
|
}),
|
|
295
254
|
defineTool({
|
|
@@ -298,14 +257,12 @@ const plugin = {
|
|
|
298
257
|
parameters: Type.Object({
|
|
299
258
|
platform: Type.String(),
|
|
300
259
|
senderId: Type.String(),
|
|
301
|
-
toolName: Type.String()
|
|
302
|
-
tenantId: Type.String()
|
|
260
|
+
toolName: Type.String()
|
|
303
261
|
}),
|
|
304
|
-
handler: (params) =>
|
|
262
|
+
handler: (params) => client.checkToolPermission({
|
|
305
263
|
platform: params.platform,
|
|
306
264
|
senderId: params.senderId,
|
|
307
|
-
toolName: params.toolName
|
|
308
|
-
tenantId: params.tenantId
|
|
265
|
+
toolName: params.toolName
|
|
309
266
|
})
|
|
310
267
|
})
|
|
311
268
|
];
|
|
@@ -330,19 +287,14 @@ const plugin = {
|
|
|
330
287
|
return;
|
|
331
288
|
}
|
|
332
289
|
try {
|
|
333
|
-
const
|
|
334
|
-
const agentId = ctx.agentId ?? "";
|
|
335
|
-
const resolveResult = await http.request("POST", "/internal/identity/resolve", {
|
|
290
|
+
const resolveResult = await client.resolveIdentity({
|
|
336
291
|
platform,
|
|
337
|
-
platformId: senderId
|
|
338
|
-
tenantId,
|
|
339
|
-
agentId
|
|
292
|
+
platformId: senderId
|
|
340
293
|
});
|
|
341
294
|
setCached(cacheKey, {
|
|
342
|
-
...await
|
|
295
|
+
...await client.enforcePolicy({
|
|
343
296
|
platform,
|
|
344
|
-
senderId
|
|
345
|
-
tenantId
|
|
297
|
+
senderId
|
|
346
298
|
}),
|
|
347
299
|
accessAllowed: resolveResult.accessAllowed
|
|
348
300
|
});
|
|
@@ -353,6 +305,10 @@ const plugin = {
|
|
|
353
305
|
log.info(`Identity resolved: ${senderId} → ${resolveResult.identityId ?? "unknown"} (${resolveResult.status})`);
|
|
354
306
|
} catch (e) {
|
|
355
307
|
log.error(`Identity resolution failed for ${senderId}: ${e.message}`);
|
|
308
|
+
if (failureMode === "closed") return {
|
|
309
|
+
block: true,
|
|
310
|
+
blockReason: "Identity: identity service unavailable — access denied (closed mode)"
|
|
311
|
+
};
|
|
356
312
|
}
|
|
357
313
|
}, { priority: 100 });
|
|
358
314
|
api.on("before_tool_call", async (...args) => {
|
|
@@ -362,14 +318,20 @@ const plugin = {
|
|
|
362
318
|
const sessionKey = ctx.sessionKey;
|
|
363
319
|
if (!sessionKey) return;
|
|
364
320
|
const perms = getCached(sessionKey);
|
|
365
|
-
if (!perms)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
321
|
+
if (!perms) {
|
|
322
|
+
if (failureMode === "permissive") return;
|
|
323
|
+
return {
|
|
324
|
+
block: true,
|
|
325
|
+
blockReason: "Identity: no identity context established — tool access denied"
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (!perms.identified) {
|
|
329
|
+
if (failureMode === "permissive") return;
|
|
330
|
+
return {
|
|
331
|
+
block: true,
|
|
332
|
+
blockReason: "Identity: unknown sender identity — tool access denied"
|
|
333
|
+
};
|
|
334
|
+
}
|
|
373
335
|
if (perms.deniedTools.includes("*") || perms.deniedTools.includes(event.toolName)) return {
|
|
374
336
|
block: true,
|
|
375
337
|
blockReason: `Identity: tool '${event.toolName}' is denied for your role`
|
|
@@ -381,17 +343,15 @@ const plugin = {
|
|
|
381
343
|
}, { priority: 100 });
|
|
382
344
|
api.on("after_tool_call", async (...args) => {
|
|
383
345
|
const event = args[0];
|
|
384
|
-
const
|
|
385
|
-
const sessionKey = ctx.sessionKey;
|
|
346
|
+
const sessionKey = args[1].sessionKey;
|
|
386
347
|
if (!sessionKey) return;
|
|
387
348
|
const perms = getCached(sessionKey);
|
|
388
349
|
if (!perms?.identityId) return;
|
|
389
350
|
try {
|
|
390
|
-
await
|
|
351
|
+
await client.checkToolPermission({
|
|
391
352
|
platform: "tool_audit",
|
|
392
353
|
senderId: perms.identityId,
|
|
393
|
-
toolName: event.toolName
|
|
394
|
-
tenantId: ctx.tenantId ?? ""
|
|
354
|
+
toolName: event.toolName
|
|
395
355
|
});
|
|
396
356
|
} catch (e) {
|
|
397
357
|
log.error(`Audit logging failed: ${e.message}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/openclaw-identity",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "OpenClaw identity plugin — identity resolution, access gating, permission enforcement",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/plugin.js",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@sinclair/typebox": "^0.34.48",
|
|
31
|
+
"@alfe.ai/agent-api-client": "0.0.9",
|
|
31
32
|
"@alfe.ai/config": "0.0.8"
|
|
32
33
|
},
|
|
33
34
|
"license": "UNLICENSED",
|