@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 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 HTTP to the Identity service internal API.
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 apiUrl;
95
- let apiKey;
78
+ let client;
96
79
  try {
97
80
  const config = (0, _alfe_ai_config.resolveConfig)();
98
- apiUrl = config.apiUrl;
99
- apiKey = config.apiKey;
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
- const http = createHttpClient(apiUrl, apiKey);
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 http.request("POST", "/internal/identity/resolve", {
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 http.request("GET", `/internal/identity/${encodeURIComponent(result.identityId)}/context?tenantId=${encodeURIComponent(params.tenantId)}`);
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: 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
- }
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) => http.request("POST", "/internal/identity/resolve", {
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) => http.request("POST", `/internal/identity/${encodeURIComponent(params.survivorId)}/merge`, {
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
- 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
- })
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) => http.request("POST", "/internal/identity/resolve", {
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) => http.request("POST", `/internal/identity/${encodeURIComponent(params.identityId)}/notes`, {
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) => http.request("POST", `/internal/identity/${encodeURIComponent(params.identityId)}/tags`, {
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: 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
- }
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) => http.request("POST", `/internal/identity/${encodeURIComponent(params.identityId)}/rollback`, {
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) => http.request("POST", "/internal/identity/enforce", {
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) => http.request("POST", "/internal/identity/check-tool", {
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 tenantId = ctx.tenantId ?? "";
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 http.request("POST", "/internal/identity/enforce", {
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) 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
- };
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 ctx = args[1];
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 http.request("POST", "/internal/identity/check-tool", {
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 HTTP to the Identity service internal API.
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 apiUrl;
95
- let apiKey;
78
+ let client;
96
79
  try {
97
80
  const config = resolveConfig();
98
- apiUrl = config.apiUrl;
99
- apiKey = config.apiKey;
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
- const http = createHttpClient(apiUrl, apiKey);
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 http.request("POST", "/internal/identity/resolve", {
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 http.request("GET", `/internal/identity/${encodeURIComponent(result.identityId)}/context?tenantId=${encodeURIComponent(params.tenantId)}`);
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: 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
- }
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) => http.request("POST", "/internal/identity/resolve", {
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) => http.request("POST", `/internal/identity/${encodeURIComponent(params.survivorId)}/merge`, {
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
- 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
- })
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) => http.request("POST", "/internal/identity/resolve", {
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) => http.request("POST", `/internal/identity/${encodeURIComponent(params.identityId)}/notes`, {
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) => http.request("POST", `/internal/identity/${encodeURIComponent(params.identityId)}/tags`, {
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: 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
- }
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) => http.request("POST", `/internal/identity/${encodeURIComponent(params.identityId)}/rollback`, {
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) => http.request("POST", "/internal/identity/enforce", {
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) => http.request("POST", "/internal/identity/check-tool", {
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 tenantId = ctx.tenantId ?? "";
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 http.request("POST", "/internal/identity/enforce", {
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) 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
- };
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 ctx = args[1];
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 http.request("POST", "/internal/identity/check-tool", {
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",
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",