@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 +112 -38
- package/dist/plugin2.js +112 -38
- package/package.json +3 -3
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
|
-
|
|
184
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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:
|
|
361
|
-
requestingIdentityId:
|
|
362
|
-
requestingProvider:
|
|
363
|
-
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
|
-
|
|
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) =>
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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:
|
|
361
|
-
requestingIdentityId:
|
|
362
|
-
requestingProvider:
|
|
363
|
-
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
|
-
|
|
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) =>
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
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
|
|
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/
|
|
33
|
-
"@alfe.ai/
|
|
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": {
|