@alfe.ai/openclaw-identity 0.0.1
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.cjs +2 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/plugin.cjs +404 -0
- package/dist/plugin.d.cts +39 -0
- package/dist/plugin.d.ts +39 -0
- package/dist/plugin.js +404 -0
- package/openclaw.plugin.json +7 -0
- package/package.json +40 -0
package/dist/index.cjs
ADDED
package/dist/index.d.cts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/plugin.cjs
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
let _alfe_ai_config = require("@alfe.ai/config");
|
|
2
|
+
let _sinclair_typebox = require("@sinclair/typebox");
|
|
3
|
+
//#region src/plugin.ts
|
|
4
|
+
/**
|
|
5
|
+
* @alfe.ai/openclaw-identity — OpenClaw native plugin
|
|
6
|
+
*
|
|
7
|
+
* HTTP-based identity resolution, permission enforcement, and CRM tools.
|
|
8
|
+
* Installed as part of the core alfe integration on every agent.
|
|
9
|
+
*
|
|
10
|
+
* Hooks:
|
|
11
|
+
* - message_received → resolve sender identity via HTTP, cache, gate access
|
|
12
|
+
* - before_tool_call → enforce permissions from cached policy
|
|
13
|
+
* - after_tool_call → log tool execution audit
|
|
14
|
+
*
|
|
15
|
+
* All data access via HTTP to the Identity service internal API.
|
|
16
|
+
* Uses the agent's own API key (from ~/.alfe/config.toml) for authentication.
|
|
17
|
+
*/
|
|
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
|
+
const CACHE_TTL_MS = 6e4;
|
|
37
|
+
const sessionCache = /* @__PURE__ */ new Map();
|
|
38
|
+
function getCached(key) {
|
|
39
|
+
const entry = sessionCache.get(key);
|
|
40
|
+
if (!entry) return null;
|
|
41
|
+
if (Date.now() > entry.expiresAt) {
|
|
42
|
+
sessionCache.delete(key);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return entry.value;
|
|
46
|
+
}
|
|
47
|
+
function setCached(key, value) {
|
|
48
|
+
sessionCache.set(key, {
|
|
49
|
+
value,
|
|
50
|
+
expiresAt: Date.now() + CACHE_TTL_MS
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function ok(data) {
|
|
54
|
+
return {
|
|
55
|
+
content: [{
|
|
56
|
+
type: "text",
|
|
57
|
+
text: JSON.stringify(data)
|
|
58
|
+
}],
|
|
59
|
+
details: data
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function errResult(message) {
|
|
63
|
+
return {
|
|
64
|
+
content: [{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: JSON.stringify({ error: message })
|
|
67
|
+
}],
|
|
68
|
+
details: { error: message }
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function defineTool(def) {
|
|
72
|
+
return {
|
|
73
|
+
name: def.name,
|
|
74
|
+
description: def.description,
|
|
75
|
+
label: def.name,
|
|
76
|
+
parameters: def.parameters,
|
|
77
|
+
execute: async (_toolCallId, params) => {
|
|
78
|
+
try {
|
|
79
|
+
return ok(await def.handler(params));
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return errResult(e.message);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const plugin = {
|
|
87
|
+
id: "alfe-identity",
|
|
88
|
+
name: "Alfe Identity",
|
|
89
|
+
description: "Identity resolution, access gating, and permission enforcement",
|
|
90
|
+
version: "0.0.1",
|
|
91
|
+
activate(api) {
|
|
92
|
+
const log = api.logger;
|
|
93
|
+
log.info("Alfe Identity plugin activating...");
|
|
94
|
+
let apiUrl;
|
|
95
|
+
let apiKey;
|
|
96
|
+
try {
|
|
97
|
+
const config = (0, _alfe_ai_config.resolveConfig)();
|
|
98
|
+
apiUrl = config.apiUrl;
|
|
99
|
+
apiKey = config.apiKey;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
log.error(`Identity plugin: failed to resolve config — ${err instanceof Error ? err.message : String(err)}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const http = createHttpClient(apiUrl, apiKey);
|
|
105
|
+
const identityToolNames = /* @__PURE__ */ new Set();
|
|
106
|
+
const tools = [
|
|
107
|
+
defineTool({
|
|
108
|
+
name: "who_is_this",
|
|
109
|
+
description: "Look up full identity context by platform and ID — returns profile, notes, tags, platforms, and recent changelog",
|
|
110
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
111
|
+
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()
|
|
114
|
+
}),
|
|
115
|
+
handler: async (params) => {
|
|
116
|
+
const result = await http.request("POST", "/internal/identity/resolve", {
|
|
117
|
+
platform: params.platform,
|
|
118
|
+
platformId: params.platformId,
|
|
119
|
+
tenantId: params.tenantId,
|
|
120
|
+
agentId: "plugin"
|
|
121
|
+
});
|
|
122
|
+
if (!result.identityId) return { found: false };
|
|
123
|
+
return http.request("GET", `/internal/identity/${encodeURIComponent(result.identityId)}/context?tenantId=${encodeURIComponent(params.tenantId)}`);
|
|
124
|
+
}
|
|
125
|
+
}),
|
|
126
|
+
defineTool({
|
|
127
|
+
name: "lookup_identity",
|
|
128
|
+
description: "Search identities by name, email, phone, tag, or platform. Returns multiple matches.",
|
|
129
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
130
|
+
tenantId: _sinclair_typebox.Type.String(),
|
|
131
|
+
query: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "Text search query" })),
|
|
132
|
+
status: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String()),
|
|
133
|
+
tag: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String()),
|
|
134
|
+
platform: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String())
|
|
135
|
+
}),
|
|
136
|
+
handler: async (params) => {
|
|
137
|
+
const qs = new URLSearchParams({ tenantId: params.tenantId });
|
|
138
|
+
if (params.query) qs.set("q", params.query);
|
|
139
|
+
if (params.status) qs.set("status", params.status);
|
|
140
|
+
if (params.tag) qs.set("tag", params.tag);
|
|
141
|
+
if (params.platform) qs.set("platform", params.platform);
|
|
142
|
+
return http.request("GET", `/internal/identity/search?${qs.toString()}`);
|
|
143
|
+
}
|
|
144
|
+
}),
|
|
145
|
+
defineTool({
|
|
146
|
+
name: "create_identity",
|
|
147
|
+
description: "Create a new identity record with profile fields",
|
|
148
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
149
|
+
tenantId: _sinclair_typebox.Type.String(),
|
|
150
|
+
platform: _sinclair_typebox.Type.String(),
|
|
151
|
+
platformId: _sinclair_typebox.Type.String(),
|
|
152
|
+
displayName: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String())
|
|
153
|
+
}),
|
|
154
|
+
handler: (params) => http.request("POST", "/internal/identity/resolve", {
|
|
155
|
+
platform: params.platform,
|
|
156
|
+
platformId: params.platformId,
|
|
157
|
+
displayName: params.displayName,
|
|
158
|
+
tenantId: params.tenantId,
|
|
159
|
+
agentId: "plugin"
|
|
160
|
+
})
|
|
161
|
+
}),
|
|
162
|
+
defineTool({
|
|
163
|
+
name: "merge_identities",
|
|
164
|
+
description: "Merge two identity records — transfers notes, tags, aliases, platforms to survivor",
|
|
165
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
166
|
+
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()
|
|
169
|
+
}),
|
|
170
|
+
handler: (params) => http.request("POST", `/internal/identity/${encodeURIComponent(params.survivorId)}/merge`, {
|
|
171
|
+
tenantId: params.tenantId,
|
|
172
|
+
mergedId: params.mergedId,
|
|
173
|
+
changedBy: {
|
|
174
|
+
type: "agent",
|
|
175
|
+
id: "plugin"
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
}),
|
|
179
|
+
defineTool({
|
|
180
|
+
name: "unmerge_identities",
|
|
181
|
+
description: "Reverse a merge — restore previously merged identity",
|
|
182
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
183
|
+
mergedId: _sinclair_typebox.Type.String({ description: "Identity that was merged (has mergedInto pointer)" }),
|
|
184
|
+
tenantId: _sinclair_typebox.Type.String()
|
|
185
|
+
}),
|
|
186
|
+
handler: (params) => http.request("POST", `/internal/identity/${encodeURIComponent(params.mergedId)}/unmerge`, {
|
|
187
|
+
tenantId: params.tenantId,
|
|
188
|
+
changedBy: {
|
|
189
|
+
type: "agent",
|
|
190
|
+
id: "plugin"
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
}),
|
|
194
|
+
defineTool({
|
|
195
|
+
name: "link_platform",
|
|
196
|
+
description: "Link a platform identity to an existing identity record",
|
|
197
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
198
|
+
identityId: _sinclair_typebox.Type.String(),
|
|
199
|
+
platform: _sinclair_typebox.Type.String(),
|
|
200
|
+
platformId: _sinclair_typebox.Type.String(),
|
|
201
|
+
tenantId: _sinclair_typebox.Type.String()
|
|
202
|
+
}),
|
|
203
|
+
handler: (params) => http.request("POST", "/internal/identity/resolve", {
|
|
204
|
+
platform: params.platform,
|
|
205
|
+
platformId: params.platformId,
|
|
206
|
+
tenantId: params.tenantId,
|
|
207
|
+
agentId: "plugin"
|
|
208
|
+
})
|
|
209
|
+
}),
|
|
210
|
+
defineTool({
|
|
211
|
+
name: "add_identity_note",
|
|
212
|
+
description: "Add an observation or note about a contact",
|
|
213
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
214
|
+
identityId: _sinclair_typebox.Type.String(),
|
|
215
|
+
tenantId: _sinclair_typebox.Type.String(),
|
|
216
|
+
content: _sinclair_typebox.Type.String(),
|
|
217
|
+
category: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "observation, preference, relationship, context, or warning" }))
|
|
218
|
+
}),
|
|
219
|
+
handler: (params) => http.request("POST", `/internal/identity/${encodeURIComponent(params.identityId)}/notes`, {
|
|
220
|
+
tenantId: params.tenantId,
|
|
221
|
+
content: params.content,
|
|
222
|
+
category: params.category,
|
|
223
|
+
changedBy: {
|
|
224
|
+
type: "agent",
|
|
225
|
+
id: "plugin"
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
}),
|
|
229
|
+
defineTool({
|
|
230
|
+
name: "tag_identity",
|
|
231
|
+
description: "Add or remove a tag on an identity",
|
|
232
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
233
|
+
identityId: _sinclair_typebox.Type.String(),
|
|
234
|
+
tenantId: _sinclair_typebox.Type.String(),
|
|
235
|
+
tag: _sinclair_typebox.Type.String(),
|
|
236
|
+
action: _sinclair_typebox.Type.String({ description: "'add' or 'remove'" })
|
|
237
|
+
}),
|
|
238
|
+
handler: (params) => http.request("POST", `/internal/identity/${encodeURIComponent(params.identityId)}/tags`, {
|
|
239
|
+
tenantId: params.tenantId,
|
|
240
|
+
tag: params.tag,
|
|
241
|
+
action: params.action,
|
|
242
|
+
changedBy: {
|
|
243
|
+
type: "agent",
|
|
244
|
+
id: "plugin"
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
}),
|
|
248
|
+
defineTool({
|
|
249
|
+
name: "get_identity_changelog",
|
|
250
|
+
description: "Get full changelog for an identity — all versions, diffs, who changed what",
|
|
251
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
252
|
+
identityId: _sinclair_typebox.Type.String(),
|
|
253
|
+
tenantId: _sinclair_typebox.Type.String(),
|
|
254
|
+
limit: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.Number())
|
|
255
|
+
}),
|
|
256
|
+
handler: async (params) => {
|
|
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
|
+
}
|
|
261
|
+
}),
|
|
262
|
+
defineTool({
|
|
263
|
+
name: "rollback_identity",
|
|
264
|
+
description: "Revert an identity to a previous version",
|
|
265
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
266
|
+
identityId: _sinclair_typebox.Type.String(),
|
|
267
|
+
tenantId: _sinclair_typebox.Type.String(),
|
|
268
|
+
targetVersion: _sinclair_typebox.Type.Number()
|
|
269
|
+
}),
|
|
270
|
+
handler: (params) => http.request("POST", `/internal/identity/${encodeURIComponent(params.identityId)}/rollback`, {
|
|
271
|
+
tenantId: params.tenantId,
|
|
272
|
+
targetVersion: params.targetVersion,
|
|
273
|
+
changedBy: {
|
|
274
|
+
type: "agent",
|
|
275
|
+
id: "plugin"
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
}),
|
|
279
|
+
defineTool({
|
|
280
|
+
name: "enforce_policy",
|
|
281
|
+
description: "Resolve sender identity and return their tool policy",
|
|
282
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
283
|
+
platform: _sinclair_typebox.Type.String(),
|
|
284
|
+
senderId: _sinclair_typebox.Type.String(),
|
|
285
|
+
channelId: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String()),
|
|
286
|
+
tenantId: _sinclair_typebox.Type.String()
|
|
287
|
+
}),
|
|
288
|
+
handler: (params) => http.request("POST", "/internal/identity/enforce", {
|
|
289
|
+
platform: params.platform,
|
|
290
|
+
senderId: params.senderId,
|
|
291
|
+
channelId: params.channelId,
|
|
292
|
+
tenantId: params.tenantId
|
|
293
|
+
})
|
|
294
|
+
}),
|
|
295
|
+
defineTool({
|
|
296
|
+
name: "check_permission",
|
|
297
|
+
description: "Check if a specific tool call is allowed for a sender",
|
|
298
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
299
|
+
platform: _sinclair_typebox.Type.String(),
|
|
300
|
+
senderId: _sinclair_typebox.Type.String(),
|
|
301
|
+
toolName: _sinclair_typebox.Type.String(),
|
|
302
|
+
tenantId: _sinclair_typebox.Type.String()
|
|
303
|
+
}),
|
|
304
|
+
handler: (params) => http.request("POST", "/internal/identity/check-tool", {
|
|
305
|
+
platform: params.platform,
|
|
306
|
+
senderId: params.senderId,
|
|
307
|
+
toolName: params.toolName,
|
|
308
|
+
tenantId: params.tenantId
|
|
309
|
+
})
|
|
310
|
+
})
|
|
311
|
+
];
|
|
312
|
+
for (const tool of tools) {
|
|
313
|
+
api.registerTool(tool);
|
|
314
|
+
identityToolNames.add(tool.name);
|
|
315
|
+
}
|
|
316
|
+
log.info(`Registered ${String(tools.length)} identity tools`);
|
|
317
|
+
api.on("message_received", async (...args) => {
|
|
318
|
+
const event = args[0];
|
|
319
|
+
const ctx = args[1];
|
|
320
|
+
const platform = ctx.channelId ?? "unknown";
|
|
321
|
+
const senderId = event.from;
|
|
322
|
+
if (!senderId) return;
|
|
323
|
+
const cacheKey = ctx.conversationId ?? `${platform}:${senderId}`;
|
|
324
|
+
const cached = getCached(cacheKey);
|
|
325
|
+
if (cached) {
|
|
326
|
+
if (!cached.accessAllowed) return {
|
|
327
|
+
block: true,
|
|
328
|
+
blockReason: "Identity: access denied for this sender"
|
|
329
|
+
};
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
const tenantId = ctx.tenantId ?? "";
|
|
334
|
+
const agentId = ctx.agentId ?? "";
|
|
335
|
+
const resolveResult = await http.request("POST", "/internal/identity/resolve", {
|
|
336
|
+
platform,
|
|
337
|
+
platformId: senderId,
|
|
338
|
+
tenantId,
|
|
339
|
+
agentId
|
|
340
|
+
});
|
|
341
|
+
setCached(cacheKey, {
|
|
342
|
+
...await http.request("POST", "/internal/identity/enforce", {
|
|
343
|
+
platform,
|
|
344
|
+
senderId,
|
|
345
|
+
tenantId
|
|
346
|
+
}),
|
|
347
|
+
accessAllowed: resolveResult.accessAllowed
|
|
348
|
+
});
|
|
349
|
+
if (!resolveResult.accessAllowed) return {
|
|
350
|
+
block: true,
|
|
351
|
+
blockReason: "Identity: access denied for this sender"
|
|
352
|
+
};
|
|
353
|
+
log.info(`Identity resolved: ${senderId} → ${resolveResult.identityId ?? "unknown"} (${resolveResult.status})`);
|
|
354
|
+
} catch (e) {
|
|
355
|
+
log.error(`Identity resolution failed for ${senderId}: ${e.message}`);
|
|
356
|
+
}
|
|
357
|
+
}, { priority: 100 });
|
|
358
|
+
api.on("before_tool_call", async (...args) => {
|
|
359
|
+
const event = args[0];
|
|
360
|
+
const ctx = args[1];
|
|
361
|
+
if (identityToolNames.has(event.toolName)) return;
|
|
362
|
+
const sessionKey = ctx.sessionKey;
|
|
363
|
+
if (!sessionKey) return;
|
|
364
|
+
const perms = getCached(sessionKey);
|
|
365
|
+
if (!perms) return {
|
|
366
|
+
block: true,
|
|
367
|
+
blockReason: "Identity: no identity context established — tool access denied"
|
|
368
|
+
};
|
|
369
|
+
if (!perms.identified) return {
|
|
370
|
+
block: true,
|
|
371
|
+
blockReason: "Identity: unknown sender identity — tool access denied"
|
|
372
|
+
};
|
|
373
|
+
if (perms.deniedTools.includes("*") || perms.deniedTools.includes(event.toolName)) return {
|
|
374
|
+
block: true,
|
|
375
|
+
blockReason: `Identity: tool '${event.toolName}' is denied for your role`
|
|
376
|
+
};
|
|
377
|
+
if (perms.allowedTools.length > 0 && !perms.allowedTools.includes(event.toolName)) return {
|
|
378
|
+
block: true,
|
|
379
|
+
blockReason: `Identity: tool '${event.toolName}' is not in your allowed tools`
|
|
380
|
+
};
|
|
381
|
+
}, { priority: 100 });
|
|
382
|
+
api.on("after_tool_call", async (...args) => {
|
|
383
|
+
const event = args[0];
|
|
384
|
+
const ctx = args[1];
|
|
385
|
+
const sessionKey = ctx.sessionKey;
|
|
386
|
+
if (!sessionKey) return;
|
|
387
|
+
const perms = getCached(sessionKey);
|
|
388
|
+
if (!perms?.identityId) return;
|
|
389
|
+
try {
|
|
390
|
+
await http.request("POST", "/internal/identity/check-tool", {
|
|
391
|
+
platform: "tool_audit",
|
|
392
|
+
senderId: perms.identityId,
|
|
393
|
+
toolName: event.toolName,
|
|
394
|
+
tenantId: ctx.tenantId ?? ""
|
|
395
|
+
});
|
|
396
|
+
} catch (e) {
|
|
397
|
+
log.error(`Audit logging failed: ${e.message}`);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
log.info("Alfe Identity plugin activated");
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
//#endregion
|
|
404
|
+
module.exports = plugin;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { TSchema } from "@sinclair/typebox";
|
|
2
|
+
|
|
3
|
+
//#region src/plugin.d.ts
|
|
4
|
+
|
|
5
|
+
interface PluginLogger {
|
|
6
|
+
info: (...args: unknown[]) => void;
|
|
7
|
+
warn: (...args: unknown[]) => void;
|
|
8
|
+
error: (...args: unknown[]) => void;
|
|
9
|
+
debug: (...args: unknown[]) => void;
|
|
10
|
+
}
|
|
11
|
+
interface ToolDef {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
label: string;
|
|
15
|
+
parameters: TSchema;
|
|
16
|
+
execute: (toolCallId: string, params: Record<string, unknown>) => Promise<{
|
|
17
|
+
content: {
|
|
18
|
+
type: "text";
|
|
19
|
+
text: string;
|
|
20
|
+
}[];
|
|
21
|
+
details: unknown;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
interface PluginApi {
|
|
25
|
+
logger: PluginLogger;
|
|
26
|
+
registerTool: (tool: ToolDef) => void;
|
|
27
|
+
on: (event: string, handler: (...args: unknown[]) => Promise<unknown>, options?: {
|
|
28
|
+
priority?: number;
|
|
29
|
+
}) => void;
|
|
30
|
+
}
|
|
31
|
+
declare const plugin: {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
version: string;
|
|
36
|
+
activate(api: PluginApi): void;
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
export { plugin as default };
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { TSchema } from "@sinclair/typebox";
|
|
2
|
+
|
|
3
|
+
//#region src/plugin.d.ts
|
|
4
|
+
|
|
5
|
+
interface PluginLogger {
|
|
6
|
+
info: (...args: unknown[]) => void;
|
|
7
|
+
warn: (...args: unknown[]) => void;
|
|
8
|
+
error: (...args: unknown[]) => void;
|
|
9
|
+
debug: (...args: unknown[]) => void;
|
|
10
|
+
}
|
|
11
|
+
interface ToolDef {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
label: string;
|
|
15
|
+
parameters: TSchema;
|
|
16
|
+
execute: (toolCallId: string, params: Record<string, unknown>) => Promise<{
|
|
17
|
+
content: {
|
|
18
|
+
type: "text";
|
|
19
|
+
text: string;
|
|
20
|
+
}[];
|
|
21
|
+
details: unknown;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
interface PluginApi {
|
|
25
|
+
logger: PluginLogger;
|
|
26
|
+
registerTool: (tool: ToolDef) => void;
|
|
27
|
+
on: (event: string, handler: (...args: unknown[]) => Promise<unknown>, options?: {
|
|
28
|
+
priority?: number;
|
|
29
|
+
}) => void;
|
|
30
|
+
}
|
|
31
|
+
declare const plugin: {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
version: string;
|
|
36
|
+
activate(api: PluginApi): void;
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
export { plugin as default };
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { resolveConfig } from "@alfe.ai/config";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
//#region src/plugin.ts
|
|
4
|
+
/**
|
|
5
|
+
* @alfe.ai/openclaw-identity — OpenClaw native plugin
|
|
6
|
+
*
|
|
7
|
+
* HTTP-based identity resolution, permission enforcement, and CRM tools.
|
|
8
|
+
* Installed as part of the core alfe integration on every agent.
|
|
9
|
+
*
|
|
10
|
+
* Hooks:
|
|
11
|
+
* - message_received → resolve sender identity via HTTP, cache, gate access
|
|
12
|
+
* - before_tool_call → enforce permissions from cached policy
|
|
13
|
+
* - after_tool_call → log tool execution audit
|
|
14
|
+
*
|
|
15
|
+
* All data access via HTTP to the Identity service internal API.
|
|
16
|
+
* Uses the agent's own API key (from ~/.alfe/config.toml) for authentication.
|
|
17
|
+
*/
|
|
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
|
+
const CACHE_TTL_MS = 6e4;
|
|
37
|
+
const sessionCache = /* @__PURE__ */ new Map();
|
|
38
|
+
function getCached(key) {
|
|
39
|
+
const entry = sessionCache.get(key);
|
|
40
|
+
if (!entry) return null;
|
|
41
|
+
if (Date.now() > entry.expiresAt) {
|
|
42
|
+
sessionCache.delete(key);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return entry.value;
|
|
46
|
+
}
|
|
47
|
+
function setCached(key, value) {
|
|
48
|
+
sessionCache.set(key, {
|
|
49
|
+
value,
|
|
50
|
+
expiresAt: Date.now() + CACHE_TTL_MS
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function ok(data) {
|
|
54
|
+
return {
|
|
55
|
+
content: [{
|
|
56
|
+
type: "text",
|
|
57
|
+
text: JSON.stringify(data)
|
|
58
|
+
}],
|
|
59
|
+
details: data
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function errResult(message) {
|
|
63
|
+
return {
|
|
64
|
+
content: [{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: JSON.stringify({ error: message })
|
|
67
|
+
}],
|
|
68
|
+
details: { error: message }
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function defineTool(def) {
|
|
72
|
+
return {
|
|
73
|
+
name: def.name,
|
|
74
|
+
description: def.description,
|
|
75
|
+
label: def.name,
|
|
76
|
+
parameters: def.parameters,
|
|
77
|
+
execute: async (_toolCallId, params) => {
|
|
78
|
+
try {
|
|
79
|
+
return ok(await def.handler(params));
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return errResult(e.message);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const plugin = {
|
|
87
|
+
id: "alfe-identity",
|
|
88
|
+
name: "Alfe Identity",
|
|
89
|
+
description: "Identity resolution, access gating, and permission enforcement",
|
|
90
|
+
version: "0.0.1",
|
|
91
|
+
activate(api) {
|
|
92
|
+
const log = api.logger;
|
|
93
|
+
log.info("Alfe Identity plugin activating...");
|
|
94
|
+
let apiUrl;
|
|
95
|
+
let apiKey;
|
|
96
|
+
try {
|
|
97
|
+
const config = resolveConfig();
|
|
98
|
+
apiUrl = config.apiUrl;
|
|
99
|
+
apiKey = config.apiKey;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
log.error(`Identity plugin: failed to resolve config — ${err instanceof Error ? err.message : String(err)}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const http = createHttpClient(apiUrl, apiKey);
|
|
105
|
+
const identityToolNames = /* @__PURE__ */ new Set();
|
|
106
|
+
const tools = [
|
|
107
|
+
defineTool({
|
|
108
|
+
name: "who_is_this",
|
|
109
|
+
description: "Look up full identity context by platform and ID — returns profile, notes, tags, platforms, and recent changelog",
|
|
110
|
+
parameters: Type.Object({
|
|
111
|
+
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()
|
|
114
|
+
}),
|
|
115
|
+
handler: async (params) => {
|
|
116
|
+
const result = await http.request("POST", "/internal/identity/resolve", {
|
|
117
|
+
platform: params.platform,
|
|
118
|
+
platformId: params.platformId,
|
|
119
|
+
tenantId: params.tenantId,
|
|
120
|
+
agentId: "plugin"
|
|
121
|
+
});
|
|
122
|
+
if (!result.identityId) return { found: false };
|
|
123
|
+
return http.request("GET", `/internal/identity/${encodeURIComponent(result.identityId)}/context?tenantId=${encodeURIComponent(params.tenantId)}`);
|
|
124
|
+
}
|
|
125
|
+
}),
|
|
126
|
+
defineTool({
|
|
127
|
+
name: "lookup_identity",
|
|
128
|
+
description: "Search identities by name, email, phone, tag, or platform. Returns multiple matches.",
|
|
129
|
+
parameters: Type.Object({
|
|
130
|
+
tenantId: Type.String(),
|
|
131
|
+
query: Type.Optional(Type.String({ description: "Text search query" })),
|
|
132
|
+
status: Type.Optional(Type.String()),
|
|
133
|
+
tag: Type.Optional(Type.String()),
|
|
134
|
+
platform: Type.Optional(Type.String())
|
|
135
|
+
}),
|
|
136
|
+
handler: async (params) => {
|
|
137
|
+
const qs = new URLSearchParams({ tenantId: params.tenantId });
|
|
138
|
+
if (params.query) qs.set("q", params.query);
|
|
139
|
+
if (params.status) qs.set("status", params.status);
|
|
140
|
+
if (params.tag) qs.set("tag", params.tag);
|
|
141
|
+
if (params.platform) qs.set("platform", params.platform);
|
|
142
|
+
return http.request("GET", `/internal/identity/search?${qs.toString()}`);
|
|
143
|
+
}
|
|
144
|
+
}),
|
|
145
|
+
defineTool({
|
|
146
|
+
name: "create_identity",
|
|
147
|
+
description: "Create a new identity record with profile fields",
|
|
148
|
+
parameters: Type.Object({
|
|
149
|
+
tenantId: Type.String(),
|
|
150
|
+
platform: Type.String(),
|
|
151
|
+
platformId: Type.String(),
|
|
152
|
+
displayName: Type.Optional(Type.String())
|
|
153
|
+
}),
|
|
154
|
+
handler: (params) => http.request("POST", "/internal/identity/resolve", {
|
|
155
|
+
platform: params.platform,
|
|
156
|
+
platformId: params.platformId,
|
|
157
|
+
displayName: params.displayName,
|
|
158
|
+
tenantId: params.tenantId,
|
|
159
|
+
agentId: "plugin"
|
|
160
|
+
})
|
|
161
|
+
}),
|
|
162
|
+
defineTool({
|
|
163
|
+
name: "merge_identities",
|
|
164
|
+
description: "Merge two identity records — transfers notes, tags, aliases, platforms to survivor",
|
|
165
|
+
parameters: Type.Object({
|
|
166
|
+
survivorId: Type.String({ description: "Identity to keep" }),
|
|
167
|
+
mergedId: Type.String({ description: "Identity to merge into survivor" }),
|
|
168
|
+
tenantId: Type.String()
|
|
169
|
+
}),
|
|
170
|
+
handler: (params) => http.request("POST", `/internal/identity/${encodeURIComponent(params.survivorId)}/merge`, {
|
|
171
|
+
tenantId: params.tenantId,
|
|
172
|
+
mergedId: params.mergedId,
|
|
173
|
+
changedBy: {
|
|
174
|
+
type: "agent",
|
|
175
|
+
id: "plugin"
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
}),
|
|
179
|
+
defineTool({
|
|
180
|
+
name: "unmerge_identities",
|
|
181
|
+
description: "Reverse a merge — restore previously merged identity",
|
|
182
|
+
parameters: Type.Object({
|
|
183
|
+
mergedId: Type.String({ description: "Identity that was merged (has mergedInto pointer)" }),
|
|
184
|
+
tenantId: Type.String()
|
|
185
|
+
}),
|
|
186
|
+
handler: (params) => http.request("POST", `/internal/identity/${encodeURIComponent(params.mergedId)}/unmerge`, {
|
|
187
|
+
tenantId: params.tenantId,
|
|
188
|
+
changedBy: {
|
|
189
|
+
type: "agent",
|
|
190
|
+
id: "plugin"
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
}),
|
|
194
|
+
defineTool({
|
|
195
|
+
name: "link_platform",
|
|
196
|
+
description: "Link a platform identity to an existing identity record",
|
|
197
|
+
parameters: Type.Object({
|
|
198
|
+
identityId: Type.String(),
|
|
199
|
+
platform: Type.String(),
|
|
200
|
+
platformId: Type.String(),
|
|
201
|
+
tenantId: Type.String()
|
|
202
|
+
}),
|
|
203
|
+
handler: (params) => http.request("POST", "/internal/identity/resolve", {
|
|
204
|
+
platform: params.platform,
|
|
205
|
+
platformId: params.platformId,
|
|
206
|
+
tenantId: params.tenantId,
|
|
207
|
+
agentId: "plugin"
|
|
208
|
+
})
|
|
209
|
+
}),
|
|
210
|
+
defineTool({
|
|
211
|
+
name: "add_identity_note",
|
|
212
|
+
description: "Add an observation or note about a contact",
|
|
213
|
+
parameters: Type.Object({
|
|
214
|
+
identityId: Type.String(),
|
|
215
|
+
tenantId: Type.String(),
|
|
216
|
+
content: Type.String(),
|
|
217
|
+
category: Type.Optional(Type.String({ description: "observation, preference, relationship, context, or warning" }))
|
|
218
|
+
}),
|
|
219
|
+
handler: (params) => http.request("POST", `/internal/identity/${encodeURIComponent(params.identityId)}/notes`, {
|
|
220
|
+
tenantId: params.tenantId,
|
|
221
|
+
content: params.content,
|
|
222
|
+
category: params.category,
|
|
223
|
+
changedBy: {
|
|
224
|
+
type: "agent",
|
|
225
|
+
id: "plugin"
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
}),
|
|
229
|
+
defineTool({
|
|
230
|
+
name: "tag_identity",
|
|
231
|
+
description: "Add or remove a tag on an identity",
|
|
232
|
+
parameters: Type.Object({
|
|
233
|
+
identityId: Type.String(),
|
|
234
|
+
tenantId: Type.String(),
|
|
235
|
+
tag: Type.String(),
|
|
236
|
+
action: Type.String({ description: "'add' or 'remove'" })
|
|
237
|
+
}),
|
|
238
|
+
handler: (params) => http.request("POST", `/internal/identity/${encodeURIComponent(params.identityId)}/tags`, {
|
|
239
|
+
tenantId: params.tenantId,
|
|
240
|
+
tag: params.tag,
|
|
241
|
+
action: params.action,
|
|
242
|
+
changedBy: {
|
|
243
|
+
type: "agent",
|
|
244
|
+
id: "plugin"
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
}),
|
|
248
|
+
defineTool({
|
|
249
|
+
name: "get_identity_changelog",
|
|
250
|
+
description: "Get full changelog for an identity — all versions, diffs, who changed what",
|
|
251
|
+
parameters: Type.Object({
|
|
252
|
+
identityId: Type.String(),
|
|
253
|
+
tenantId: Type.String(),
|
|
254
|
+
limit: Type.Optional(Type.Number())
|
|
255
|
+
}),
|
|
256
|
+
handler: async (params) => {
|
|
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
|
+
}
|
|
261
|
+
}),
|
|
262
|
+
defineTool({
|
|
263
|
+
name: "rollback_identity",
|
|
264
|
+
description: "Revert an identity to a previous version",
|
|
265
|
+
parameters: Type.Object({
|
|
266
|
+
identityId: Type.String(),
|
|
267
|
+
tenantId: Type.String(),
|
|
268
|
+
targetVersion: Type.Number()
|
|
269
|
+
}),
|
|
270
|
+
handler: (params) => http.request("POST", `/internal/identity/${encodeURIComponent(params.identityId)}/rollback`, {
|
|
271
|
+
tenantId: params.tenantId,
|
|
272
|
+
targetVersion: params.targetVersion,
|
|
273
|
+
changedBy: {
|
|
274
|
+
type: "agent",
|
|
275
|
+
id: "plugin"
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
}),
|
|
279
|
+
defineTool({
|
|
280
|
+
name: "enforce_policy",
|
|
281
|
+
description: "Resolve sender identity and return their tool policy",
|
|
282
|
+
parameters: Type.Object({
|
|
283
|
+
platform: Type.String(),
|
|
284
|
+
senderId: Type.String(),
|
|
285
|
+
channelId: Type.Optional(Type.String()),
|
|
286
|
+
tenantId: Type.String()
|
|
287
|
+
}),
|
|
288
|
+
handler: (params) => http.request("POST", "/internal/identity/enforce", {
|
|
289
|
+
platform: params.platform,
|
|
290
|
+
senderId: params.senderId,
|
|
291
|
+
channelId: params.channelId,
|
|
292
|
+
tenantId: params.tenantId
|
|
293
|
+
})
|
|
294
|
+
}),
|
|
295
|
+
defineTool({
|
|
296
|
+
name: "check_permission",
|
|
297
|
+
description: "Check if a specific tool call is allowed for a sender",
|
|
298
|
+
parameters: Type.Object({
|
|
299
|
+
platform: Type.String(),
|
|
300
|
+
senderId: Type.String(),
|
|
301
|
+
toolName: Type.String(),
|
|
302
|
+
tenantId: Type.String()
|
|
303
|
+
}),
|
|
304
|
+
handler: (params) => http.request("POST", "/internal/identity/check-tool", {
|
|
305
|
+
platform: params.platform,
|
|
306
|
+
senderId: params.senderId,
|
|
307
|
+
toolName: params.toolName,
|
|
308
|
+
tenantId: params.tenantId
|
|
309
|
+
})
|
|
310
|
+
})
|
|
311
|
+
];
|
|
312
|
+
for (const tool of tools) {
|
|
313
|
+
api.registerTool(tool);
|
|
314
|
+
identityToolNames.add(tool.name);
|
|
315
|
+
}
|
|
316
|
+
log.info(`Registered ${String(tools.length)} identity tools`);
|
|
317
|
+
api.on("message_received", async (...args) => {
|
|
318
|
+
const event = args[0];
|
|
319
|
+
const ctx = args[1];
|
|
320
|
+
const platform = ctx.channelId ?? "unknown";
|
|
321
|
+
const senderId = event.from;
|
|
322
|
+
if (!senderId) return;
|
|
323
|
+
const cacheKey = ctx.conversationId ?? `${platform}:${senderId}`;
|
|
324
|
+
const cached = getCached(cacheKey);
|
|
325
|
+
if (cached) {
|
|
326
|
+
if (!cached.accessAllowed) return {
|
|
327
|
+
block: true,
|
|
328
|
+
blockReason: "Identity: access denied for this sender"
|
|
329
|
+
};
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
const tenantId = ctx.tenantId ?? "";
|
|
334
|
+
const agentId = ctx.agentId ?? "";
|
|
335
|
+
const resolveResult = await http.request("POST", "/internal/identity/resolve", {
|
|
336
|
+
platform,
|
|
337
|
+
platformId: senderId,
|
|
338
|
+
tenantId,
|
|
339
|
+
agentId
|
|
340
|
+
});
|
|
341
|
+
setCached(cacheKey, {
|
|
342
|
+
...await http.request("POST", "/internal/identity/enforce", {
|
|
343
|
+
platform,
|
|
344
|
+
senderId,
|
|
345
|
+
tenantId
|
|
346
|
+
}),
|
|
347
|
+
accessAllowed: resolveResult.accessAllowed
|
|
348
|
+
});
|
|
349
|
+
if (!resolveResult.accessAllowed) return {
|
|
350
|
+
block: true,
|
|
351
|
+
blockReason: "Identity: access denied for this sender"
|
|
352
|
+
};
|
|
353
|
+
log.info(`Identity resolved: ${senderId} → ${resolveResult.identityId ?? "unknown"} (${resolveResult.status})`);
|
|
354
|
+
} catch (e) {
|
|
355
|
+
log.error(`Identity resolution failed for ${senderId}: ${e.message}`);
|
|
356
|
+
}
|
|
357
|
+
}, { priority: 100 });
|
|
358
|
+
api.on("before_tool_call", async (...args) => {
|
|
359
|
+
const event = args[0];
|
|
360
|
+
const ctx = args[1];
|
|
361
|
+
if (identityToolNames.has(event.toolName)) return;
|
|
362
|
+
const sessionKey = ctx.sessionKey;
|
|
363
|
+
if (!sessionKey) return;
|
|
364
|
+
const perms = getCached(sessionKey);
|
|
365
|
+
if (!perms) return {
|
|
366
|
+
block: true,
|
|
367
|
+
blockReason: "Identity: no identity context established — tool access denied"
|
|
368
|
+
};
|
|
369
|
+
if (!perms.identified) return {
|
|
370
|
+
block: true,
|
|
371
|
+
blockReason: "Identity: unknown sender identity — tool access denied"
|
|
372
|
+
};
|
|
373
|
+
if (perms.deniedTools.includes("*") || perms.deniedTools.includes(event.toolName)) return {
|
|
374
|
+
block: true,
|
|
375
|
+
blockReason: `Identity: tool '${event.toolName}' is denied for your role`
|
|
376
|
+
};
|
|
377
|
+
if (perms.allowedTools.length > 0 && !perms.allowedTools.includes(event.toolName)) return {
|
|
378
|
+
block: true,
|
|
379
|
+
blockReason: `Identity: tool '${event.toolName}' is not in your allowed tools`
|
|
380
|
+
};
|
|
381
|
+
}, { priority: 100 });
|
|
382
|
+
api.on("after_tool_call", async (...args) => {
|
|
383
|
+
const event = args[0];
|
|
384
|
+
const ctx = args[1];
|
|
385
|
+
const sessionKey = ctx.sessionKey;
|
|
386
|
+
if (!sessionKey) return;
|
|
387
|
+
const perms = getCached(sessionKey);
|
|
388
|
+
if (!perms?.identityId) return;
|
|
389
|
+
try {
|
|
390
|
+
await http.request("POST", "/internal/identity/check-tool", {
|
|
391
|
+
platform: "tool_audit",
|
|
392
|
+
senderId: perms.identityId,
|
|
393
|
+
toolName: event.toolName,
|
|
394
|
+
tenantId: ctx.tenantId ?? ""
|
|
395
|
+
});
|
|
396
|
+
} catch (e) {
|
|
397
|
+
log.error(`Audit logging failed: ${e.message}`);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
log.info("Alfe Identity plugin activated");
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
//#endregion
|
|
404
|
+
export { plugin as default };
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alfe.ai/openclaw-identity",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "OpenClaw identity plugin — identity resolution, access gating, permission enforcement",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/plugin.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"require": "./dist/index.cjs",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./plugin": {
|
|
15
|
+
"types": "./dist/plugin.d.ts",
|
|
16
|
+
"require": "./dist/plugin.cjs",
|
|
17
|
+
"import": "./dist/plugin.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"openclaw": {
|
|
21
|
+
"extensions": [
|
|
22
|
+
"./dist/plugin.js"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"openclaw.plugin.json"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@sinclair/typebox": "^0.34.48",
|
|
31
|
+
"@alfe.ai/config": "0.0.7"
|
|
32
|
+
},
|
|
33
|
+
"license": "UNLICENSED",
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsdown",
|
|
36
|
+
"dev": "tsdown --watch",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"lint": "eslint ."
|
|
39
|
+
}
|
|
40
|
+
}
|