@alfe.ai/openclaw-identity 0.0.18 → 0.1.0

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/plugin2.cjs CHANGED
@@ -147,6 +147,75 @@ function setCachedResolve(key, value) {
147
147
  expiresAt: Date.now() + RESOLVE_CACHE_TTL_MS
148
148
  });
149
149
  }
150
+ function resolveCachedSession(params) {
151
+ const conversationId = params.conversationId;
152
+ const explicitClaimed = params.claimedIdentityId;
153
+ if (!conversationId) return {
154
+ hit: false,
155
+ conversationId: null
156
+ };
157
+ const cached = getCachedResolve(conversationId);
158
+ if (cached?.identityId == null) return {
159
+ hit: false,
160
+ conversationId,
161
+ error: "session_identity_unavailable: inbound identity cache miss for this conversationId. Pass claimedIdentityId explicitly, or wait for the next inbound message."
162
+ };
163
+ if (explicitClaimed && explicitClaimed !== cached.identityId) return {
164
+ hit: false,
165
+ conversationId,
166
+ error: "claimedIdentityId conflicts with the inbound session identity. Either omit claimedIdentityId (recommended) or remove conversationId."
167
+ };
168
+ return {
169
+ hit: true,
170
+ cached: {
171
+ ...cached,
172
+ identityId: cached.identityId
173
+ }
174
+ };
175
+ }
176
+ /**
177
+ * Resolution order for the request flow (needs the full requester tuple):
178
+ * 1. conversationId hits cache → derive all four from cached entry.
179
+ * 2. conversationId set but cache miss → reject session_identity_unavailable.
180
+ * 3. No conversationId AND no claimedIdentityId → reject missing_identity.
181
+ * 4. claimedIdentityId + full requester tuple supplied → legacy explicit
182
+ * flow. Layer 1's server-side `no_op_already_verified` guard catches
183
+ * the common misuse case (looking up an existing identity by name).
184
+ */
185
+ function resolveRequesterContext(params) {
186
+ const session = resolveCachedSession(params);
187
+ if (session.hit) return {
188
+ claimedIdentityId: session.cached.identityId,
189
+ requestingIdentityId: session.cached.identityId,
190
+ requestingProvider: session.cached.senderProvider,
191
+ requestingPlatformId: session.cached.senderPlatformId
192
+ };
193
+ if (session.conversationId) return { error: session.error };
194
+ const claimedIdentityId = params.claimedIdentityId;
195
+ const requestingIdentityId = params.requestingIdentityId;
196
+ const requestingProvider = params.requestingProvider;
197
+ const requestingPlatformId = params.requestingPlatformId;
198
+ if (!claimedIdentityId) return { error: "missing_identity: pass conversationId (recommended — auto-binds to the inbound sender) or claimedIdentityId + requestingIdentityId + requestingProvider + requestingPlatformId explicitly." };
199
+ if (!requestingIdentityId || !requestingProvider || !requestingPlatformId) return { error: "missing_identity: when claimedIdentityId is supplied without conversationId, requestingIdentityId / requestingProvider / requestingPlatformId are also required. Prefer passing conversationId so all four are auto-derived." };
200
+ return {
201
+ claimedIdentityId,
202
+ requestingIdentityId,
203
+ requestingProvider,
204
+ requestingPlatformId
205
+ };
206
+ }
207
+ /**
208
+ * Confirm flow only needs `claimedIdentityId` — same conflict / cache-miss
209
+ * branches as the requester resolver, but no requester tuple.
210
+ */
211
+ function resolveClaimedIdentity(params) {
212
+ const session = resolveCachedSession(params);
213
+ if (session.hit) return { claimedIdentityId: session.cached.identityId };
214
+ if (session.conversationId) return { error: session.error };
215
+ const claimedIdentityId = params.claimedIdentityId;
216
+ if (!claimedIdentityId) return { error: "missing_identity: pass conversationId (recommended — auto-binds to the inbound sender) or claimedIdentityId explicitly." };
217
+ return { claimedIdentityId };
218
+ }
150
219
  function ok(data) {
151
220
  return {
152
221
  content: [{
@@ -180,8 +249,16 @@ function defineTool(def) {
180
249
  }
181
250
  };
182
251
  }
183
- const ACTIVATED_KEY = "__alfeIdentityPluginActivated";
184
- const g = globalThis;
252
+ let cachedClient = null;
253
+ function getClient() {
254
+ if (cachedClient) return cachedClient;
255
+ const config = (0, _alfe_ai_config.resolveConfig)();
256
+ cachedClient = new _alfe_ai_agent_api_client.AgentApiClient({
257
+ apiKey: config.apiKey,
258
+ apiUrl: config.apiUrl
259
+ });
260
+ return cachedClient;
261
+ }
185
262
  const plugin = {
186
263
  id: "@alfe.ai/openclaw-identity",
187
264
  name: "Alfe Identity",
@@ -189,24 +266,11 @@ const plugin = {
189
266
  version: pkg.version,
190
267
  activate(api) {
191
268
  const log = api.logger;
192
- if (g[ACTIVATED_KEY] === true) {
193
- log.debug("Alfe Identity plugin already activated — skipping duplicate");
194
- return;
195
- }
196
- g[ACTIVATED_KEY] = true;
197
- log.info("Alfe Identity plugin activating...");
198
- let client;
199
- try {
200
- const config = (0, _alfe_ai_config.resolveConfig)();
201
- client = new _alfe_ai_agent_api_client.AgentApiClient({
202
- apiKey: config.apiKey,
203
- apiUrl: config.apiUrl
204
- });
205
- } catch (err) {
206
- g[ACTIVATED_KEY] = false;
207
- log.error(`Identity plugin: failed to resolve config — ${err instanceof Error ? err.message : String(err)}`);
208
- return;
209
- }
269
+ const client = new Proxy({}, { get(_t, prop) {
270
+ const c = getClient();
271
+ const v = c[prop];
272
+ return typeof v === "function" ? v.bind(c) : v;
273
+ } });
210
274
  const tools = [
211
275
  defineTool({
212
276
  name: "who_is_this",
@@ -335,12 +399,13 @@ const plugin = {
335
399
  }),
336
400
  defineTool({
337
401
  name: "request_identity_verification",
338
- description: "Send a verification phrase to verify the user controls an email or mobile endpoint. Use this when the user has just told you their email or mobile number and you want them to confirm it. Pass exactly one of `contactEmail` or `contactMobile`. The person must relay the phrase back via `confirm_identity_verification`.",
402
+ description: "Send a verification phrase to the inbound user's email or mobile to prove they own that contact endpoint. Use when the user has just told you their email or mobile number and you want them to confirm it. PASS conversationId (from the inbound message metadata) that auto-binds the verification to the inbound sender's identity (the WhatsApp / SMS / Discord / Google Chat user who just spoke). Any merge into a pre-existing account with the same contact happens automatically. Do NOT pass claimedIdentityId yourself unless you have a specific reason to verify a different identity than the inbound sender; looking up an identity by name and passing it here is a footgun and the server will reject with no_op_already_verified.",
339
403
  parameters: _sinclair_typebox.Type.Object({
340
- claimedIdentityId: _sinclair_typebox.Type.String({ description: "Identity being claimed" }),
341
- requestingIdentityId: _sinclair_typebox.Type.String({ description: "Identity of the person making the claim" }),
342
- requestingProvider: _sinclair_typebox.Type.String({ description: "Provider the requester is on (discord, slack, chat, etc.)" }),
343
- requestingPlatformId: _sinclair_typebox.Type.String({ description: "Requester's provider-specific user ID" }),
404
+ conversationId: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "Conversation ID from the inbound message — the daemon attaches this to every message. Pass it to auto-bind the verification to the inbound sender's identity. When set you should NOT also pass claimedIdentityId / requestingIdentityId / requestingProvider / requestingPlatformId." })),
405
+ claimedIdentityId: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "Override for the inbound sender's identity. NEVER set this from a lookup_identity result — that's a footgun (the verification will no-op). Leave unset and pass conversationId so the inbound identity is used automatically. Only set this for explicit non-inbound verification flows (rare — dashboard / admin only)." })),
406
+ requestingIdentityId: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "Identity of the person making the claim. Auto-derived from conversationId; only set explicitly when conversationId is omitted." })),
407
+ requestingProvider: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "Provider the requester is on (discord, slack, whatsapp, etc.). Auto-derived from conversationId; only set explicitly when conversationId is omitted." })),
408
+ requestingPlatformId: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "Requester's provider-specific user ID. Auto-derived from conversationId; only set explicitly when conversationId is omitted." })),
344
409
  contactEmail: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "Email to verify (mutually exclusive with contactMobile)" })),
345
410
  contactMobile: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "E.164 mobile to verify (mutually exclusive with contactEmail)" })),
346
411
  preferredChannel: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "When neither contactEmail nor contactMobile is provided, pick which existing verified contact to deliver to: 'mobile' or 'email'" }))
@@ -356,11 +421,13 @@ const plugin = {
356
421
  channel: "mobile",
357
422
  value: contactMobile
358
423
  } : void 0;
424
+ const resolved = resolveRequesterContext(params);
425
+ if ("error" in resolved) return Promise.resolve({ error: resolved.error });
359
426
  return client.requestIdentityVerification({
360
- claimedIdentityId: params.claimedIdentityId,
361
- requestingIdentityId: params.requestingIdentityId,
362
- requestingProvider: params.requestingProvider,
363
- requestingPlatformId: params.requestingPlatformId,
427
+ claimedIdentityId: resolved.claimedIdentityId,
428
+ requestingIdentityId: resolved.requestingIdentityId,
429
+ requestingProvider: resolved.requestingProvider,
430
+ requestingPlatformId: resolved.requestingPlatformId,
364
431
  preferredChannel: params.preferredChannel,
365
432
  contact
366
433
  });
@@ -368,17 +435,22 @@ const plugin = {
368
435
  }),
369
436
  defineTool({
370
437
  name: "confirm_identity_verification",
371
- description: "Confirm a verification by submitting the phrase the user received. Returns `{ verified, identityId, action }` where `action` is 'merged' (the verified contact already lived on another identity, which is now the survivor) or 'contact_verified' (the contact was attached to the claimed identity).",
438
+ description: "Confirm a verification by submitting the phrase the user received. Returns `{ verified, identityId, action }` where `action` is 'merged' (the verified contact already lived on another identity, which is now the survivor) or 'contact_verified' (the contact was attached to the claimed identity). PASS the same conversationId you used on request_identity_verification — claimedIdentityId is auto-derived from it.",
372
439
  parameters: _sinclair_typebox.Type.Object({
373
- claimedIdentityId: _sinclair_typebox.Type.String({ description: "Identity being claimed (matches request)" }),
440
+ conversationId: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "Conversation ID from the inbound message — same one passed to request_identity_verification. Auto-binds claimedIdentityId to the inbound sender." })),
441
+ claimedIdentityId: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "Override for the inbound sender's identity. Must match the value used on the request. Leave unset and pass conversationId." })),
374
442
  verificationId: _sinclair_typebox.Type.String({ description: "Verification ID returned from request_identity_verification" }),
375
443
  phrase: _sinclair_typebox.Type.String({ description: "Three-word phrase the person received via mobile or email" })
376
444
  }),
377
- handler: (params) => client.confirmIdentityVerification({
378
- claimedIdentityId: params.claimedIdentityId,
379
- verificationId: params.verificationId,
380
- phrase: params.phrase
381
- })
445
+ handler: (params) => {
446
+ const resolved = resolveClaimedIdentity(params);
447
+ if ("error" in resolved) return Promise.resolve({ error: resolved.error });
448
+ return client.confirmIdentityVerification({
449
+ claimedIdentityId: resolved.claimedIdentityId,
450
+ verificationId: params.verificationId,
451
+ phrase: params.phrase
452
+ });
453
+ }
382
454
  })
383
455
  ];
384
456
  for (const tool of tools) api.registerTool(tool);
@@ -419,7 +491,9 @@ const plugin = {
419
491
  setCachedResolve(cacheKey, {
420
492
  identityId: r.identityId,
421
493
  status: r.status,
422
- permissions: [...r.permissions, ...forwarded]
494
+ permissions: [...r.permissions, ...forwarded],
495
+ senderProvider: provider,
496
+ senderPlatformId: senderId
423
497
  });
424
498
  if (r.identityId == null) {
425
499
  log.warn(`Identity not provisioned for ${provider}:${senderId} — blocking inbound message`);
@@ -475,7 +549,7 @@ const plugin = {
475
549
  log.info("Alfe Identity plugin activated");
476
550
  },
477
551
  deactivate(api) {
478
- g[ACTIVATED_KEY] = false;
552
+ cachedClient = null;
479
553
  api.logger.info("Alfe Identity plugin deactivated");
480
554
  }
481
555
  };
package/dist/plugin2.js CHANGED
@@ -147,6 +147,75 @@ function setCachedResolve(key, value) {
147
147
  expiresAt: Date.now() + RESOLVE_CACHE_TTL_MS
148
148
  });
149
149
  }
150
+ function resolveCachedSession(params) {
151
+ const conversationId = params.conversationId;
152
+ const explicitClaimed = params.claimedIdentityId;
153
+ if (!conversationId) return {
154
+ hit: false,
155
+ conversationId: null
156
+ };
157
+ const cached = getCachedResolve(conversationId);
158
+ if (cached?.identityId == null) return {
159
+ hit: false,
160
+ conversationId,
161
+ error: "session_identity_unavailable: inbound identity cache miss for this conversationId. Pass claimedIdentityId explicitly, or wait for the next inbound message."
162
+ };
163
+ if (explicitClaimed && explicitClaimed !== cached.identityId) return {
164
+ hit: false,
165
+ conversationId,
166
+ error: "claimedIdentityId conflicts with the inbound session identity. Either omit claimedIdentityId (recommended) or remove conversationId."
167
+ };
168
+ return {
169
+ hit: true,
170
+ cached: {
171
+ ...cached,
172
+ identityId: cached.identityId
173
+ }
174
+ };
175
+ }
176
+ /**
177
+ * Resolution order for the request flow (needs the full requester tuple):
178
+ * 1. conversationId hits cache → derive all four from cached entry.
179
+ * 2. conversationId set but cache miss → reject session_identity_unavailable.
180
+ * 3. No conversationId AND no claimedIdentityId → reject missing_identity.
181
+ * 4. claimedIdentityId + full requester tuple supplied → legacy explicit
182
+ * flow. Layer 1's server-side `no_op_already_verified` guard catches
183
+ * the common misuse case (looking up an existing identity by name).
184
+ */
185
+ function resolveRequesterContext(params) {
186
+ const session = resolveCachedSession(params);
187
+ if (session.hit) return {
188
+ claimedIdentityId: session.cached.identityId,
189
+ requestingIdentityId: session.cached.identityId,
190
+ requestingProvider: session.cached.senderProvider,
191
+ requestingPlatformId: session.cached.senderPlatformId
192
+ };
193
+ if (session.conversationId) return { error: session.error };
194
+ const claimedIdentityId = params.claimedIdentityId;
195
+ const requestingIdentityId = params.requestingIdentityId;
196
+ const requestingProvider = params.requestingProvider;
197
+ const requestingPlatformId = params.requestingPlatformId;
198
+ if (!claimedIdentityId) return { error: "missing_identity: pass conversationId (recommended — auto-binds to the inbound sender) or claimedIdentityId + requestingIdentityId + requestingProvider + requestingPlatformId explicitly." };
199
+ if (!requestingIdentityId || !requestingProvider || !requestingPlatformId) return { error: "missing_identity: when claimedIdentityId is supplied without conversationId, requestingIdentityId / requestingProvider / requestingPlatformId are also required. Prefer passing conversationId so all four are auto-derived." };
200
+ return {
201
+ claimedIdentityId,
202
+ requestingIdentityId,
203
+ requestingProvider,
204
+ requestingPlatformId
205
+ };
206
+ }
207
+ /**
208
+ * Confirm flow only needs `claimedIdentityId` — same conflict / cache-miss
209
+ * branches as the requester resolver, but no requester tuple.
210
+ */
211
+ function resolveClaimedIdentity(params) {
212
+ const session = resolveCachedSession(params);
213
+ if (session.hit) return { claimedIdentityId: session.cached.identityId };
214
+ if (session.conversationId) return { error: session.error };
215
+ const claimedIdentityId = params.claimedIdentityId;
216
+ if (!claimedIdentityId) return { error: "missing_identity: pass conversationId (recommended — auto-binds to the inbound sender) or claimedIdentityId explicitly." };
217
+ return { claimedIdentityId };
218
+ }
150
219
  function ok(data) {
151
220
  return {
152
221
  content: [{
@@ -180,8 +249,16 @@ function defineTool(def) {
180
249
  }
181
250
  };
182
251
  }
183
- const ACTIVATED_KEY = "__alfeIdentityPluginActivated";
184
- const g = globalThis;
252
+ let cachedClient = null;
253
+ function getClient() {
254
+ if (cachedClient) return cachedClient;
255
+ const config = resolveConfig();
256
+ cachedClient = new AgentApiClient({
257
+ apiKey: config.apiKey,
258
+ apiUrl: config.apiUrl
259
+ });
260
+ return cachedClient;
261
+ }
185
262
  const plugin = {
186
263
  id: "@alfe.ai/openclaw-identity",
187
264
  name: "Alfe Identity",
@@ -189,24 +266,11 @@ const plugin = {
189
266
  version: pkg.version,
190
267
  activate(api) {
191
268
  const log = api.logger;
192
- if (g[ACTIVATED_KEY] === true) {
193
- log.debug("Alfe Identity plugin already activated — skipping duplicate");
194
- return;
195
- }
196
- g[ACTIVATED_KEY] = true;
197
- log.info("Alfe Identity plugin activating...");
198
- let client;
199
- try {
200
- const config = resolveConfig();
201
- client = new AgentApiClient({
202
- apiKey: config.apiKey,
203
- apiUrl: config.apiUrl
204
- });
205
- } catch (err) {
206
- g[ACTIVATED_KEY] = false;
207
- log.error(`Identity plugin: failed to resolve config — ${err instanceof Error ? err.message : String(err)}`);
208
- return;
209
- }
269
+ const client = new Proxy({}, { get(_t, prop) {
270
+ const c = getClient();
271
+ const v = c[prop];
272
+ return typeof v === "function" ? v.bind(c) : v;
273
+ } });
210
274
  const tools = [
211
275
  defineTool({
212
276
  name: "who_is_this",
@@ -335,12 +399,13 @@ const plugin = {
335
399
  }),
336
400
  defineTool({
337
401
  name: "request_identity_verification",
338
- description: "Send a verification phrase to verify the user controls an email or mobile endpoint. Use this when the user has just told you their email or mobile number and you want them to confirm it. Pass exactly one of `contactEmail` or `contactMobile`. The person must relay the phrase back via `confirm_identity_verification`.",
402
+ description: "Send a verification phrase to the inbound user's email or mobile to prove they own that contact endpoint. Use when the user has just told you their email or mobile number and you want them to confirm it. PASS conversationId (from the inbound message metadata) that auto-binds the verification to the inbound sender's identity (the WhatsApp / SMS / Discord / Google Chat user who just spoke). Any merge into a pre-existing account with the same contact happens automatically. Do NOT pass claimedIdentityId yourself unless you have a specific reason to verify a different identity than the inbound sender; looking up an identity by name and passing it here is a footgun and the server will reject with no_op_already_verified.",
339
403
  parameters: Type.Object({
340
- claimedIdentityId: Type.String({ description: "Identity being claimed" }),
341
- requestingIdentityId: Type.String({ description: "Identity of the person making the claim" }),
342
- requestingProvider: Type.String({ description: "Provider the requester is on (discord, slack, chat, etc.)" }),
343
- requestingPlatformId: Type.String({ description: "Requester's provider-specific user ID" }),
404
+ conversationId: Type.Optional(Type.String({ description: "Conversation ID from the inbound message — the daemon attaches this to every message. Pass it to auto-bind the verification to the inbound sender's identity. When set you should NOT also pass claimedIdentityId / requestingIdentityId / requestingProvider / requestingPlatformId." })),
405
+ claimedIdentityId: Type.Optional(Type.String({ description: "Override for the inbound sender's identity. NEVER set this from a lookup_identity result — that's a footgun (the verification will no-op). Leave unset and pass conversationId so the inbound identity is used automatically. Only set this for explicit non-inbound verification flows (rare — dashboard / admin only)." })),
406
+ requestingIdentityId: Type.Optional(Type.String({ description: "Identity of the person making the claim. Auto-derived from conversationId; only set explicitly when conversationId is omitted." })),
407
+ requestingProvider: Type.Optional(Type.String({ description: "Provider the requester is on (discord, slack, whatsapp, etc.). Auto-derived from conversationId; only set explicitly when conversationId is omitted." })),
408
+ requestingPlatformId: Type.Optional(Type.String({ description: "Requester's provider-specific user ID. Auto-derived from conversationId; only set explicitly when conversationId is omitted." })),
344
409
  contactEmail: Type.Optional(Type.String({ description: "Email to verify (mutually exclusive with contactMobile)" })),
345
410
  contactMobile: Type.Optional(Type.String({ description: "E.164 mobile to verify (mutually exclusive with contactEmail)" })),
346
411
  preferredChannel: Type.Optional(Type.String({ description: "When neither contactEmail nor contactMobile is provided, pick which existing verified contact to deliver to: 'mobile' or 'email'" }))
@@ -356,11 +421,13 @@ const plugin = {
356
421
  channel: "mobile",
357
422
  value: contactMobile
358
423
  } : void 0;
424
+ const resolved = resolveRequesterContext(params);
425
+ if ("error" in resolved) return Promise.resolve({ error: resolved.error });
359
426
  return client.requestIdentityVerification({
360
- claimedIdentityId: params.claimedIdentityId,
361
- requestingIdentityId: params.requestingIdentityId,
362
- requestingProvider: params.requestingProvider,
363
- requestingPlatformId: params.requestingPlatformId,
427
+ claimedIdentityId: resolved.claimedIdentityId,
428
+ requestingIdentityId: resolved.requestingIdentityId,
429
+ requestingProvider: resolved.requestingProvider,
430
+ requestingPlatformId: resolved.requestingPlatformId,
364
431
  preferredChannel: params.preferredChannel,
365
432
  contact
366
433
  });
@@ -368,17 +435,22 @@ const plugin = {
368
435
  }),
369
436
  defineTool({
370
437
  name: "confirm_identity_verification",
371
- description: "Confirm a verification by submitting the phrase the user received. Returns `{ verified, identityId, action }` where `action` is 'merged' (the verified contact already lived on another identity, which is now the survivor) or 'contact_verified' (the contact was attached to the claimed identity).",
438
+ description: "Confirm a verification by submitting the phrase the user received. Returns `{ verified, identityId, action }` where `action` is 'merged' (the verified contact already lived on another identity, which is now the survivor) or 'contact_verified' (the contact was attached to the claimed identity). PASS the same conversationId you used on request_identity_verification — claimedIdentityId is auto-derived from it.",
372
439
  parameters: Type.Object({
373
- claimedIdentityId: Type.String({ description: "Identity being claimed (matches request)" }),
440
+ conversationId: Type.Optional(Type.String({ description: "Conversation ID from the inbound message — same one passed to request_identity_verification. Auto-binds claimedIdentityId to the inbound sender." })),
441
+ claimedIdentityId: Type.Optional(Type.String({ description: "Override for the inbound sender's identity. Must match the value used on the request. Leave unset and pass conversationId." })),
374
442
  verificationId: Type.String({ description: "Verification ID returned from request_identity_verification" }),
375
443
  phrase: Type.String({ description: "Three-word phrase the person received via mobile or email" })
376
444
  }),
377
- handler: (params) => client.confirmIdentityVerification({
378
- claimedIdentityId: params.claimedIdentityId,
379
- verificationId: params.verificationId,
380
- phrase: params.phrase
381
- })
445
+ handler: (params) => {
446
+ const resolved = resolveClaimedIdentity(params);
447
+ if ("error" in resolved) return Promise.resolve({ error: resolved.error });
448
+ return client.confirmIdentityVerification({
449
+ claimedIdentityId: resolved.claimedIdentityId,
450
+ verificationId: params.verificationId,
451
+ phrase: params.phrase
452
+ });
453
+ }
382
454
  })
383
455
  ];
384
456
  for (const tool of tools) api.registerTool(tool);
@@ -419,7 +491,9 @@ const plugin = {
419
491
  setCachedResolve(cacheKey, {
420
492
  identityId: r.identityId,
421
493
  status: r.status,
422
- permissions: [...r.permissions, ...forwarded]
494
+ permissions: [...r.permissions, ...forwarded],
495
+ senderProvider: provider,
496
+ senderPlatformId: senderId
423
497
  });
424
498
  if (r.identityId == null) {
425
499
  log.warn(`Identity not provisioned for ${provider}:${senderId} — blocking inbound message`);
@@ -475,7 +549,7 @@ const plugin = {
475
549
  log.info("Alfe Identity plugin activated");
476
550
  },
477
551
  deactivate(api) {
478
- g[ACTIVATED_KEY] = false;
552
+ cachedClient = null;
479
553
  api.logger.info("Alfe Identity plugin deactivated");
480
554
  }
481
555
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-identity",
3
- "version": "0.0.18",
3
+ "version": "0.1.0",
4
4
  "description": "OpenClaw identity plugin — identity resolution, access gating, permission enforcement",
5
5
  "type": "module",
6
6
  "main": "./dist/plugin.js",
@@ -29,8 +29,8 @@
29
29
  "dependencies": {
30
30
  "@auriclabs/roles": "0.1.1",
31
31
  "@sinclair/typebox": "^0.34.48",
32
- "@alfe.ai/agent-api-client": "0.1.2",
33
- "@alfe.ai/config": "0.0.8"
32
+ "@alfe.ai/config": "0.0.8",
33
+ "@alfe.ai/agent-api-client": "0.1.2"
34
34
  },
35
35
  "license": "UNLICENSED",
36
36
  "scripts": {