@bamdra/bamdra-user-bind 0.1.2 → 0.1.4
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.js +332 -28
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42,16 +42,16 @@ var GLOBAL_API_KEY = "__OPENCLAW_BAMDRA_USER_BIND__";
|
|
|
42
42
|
var PROFILE_SKILL_ID = "bamdra-user-bind-profile";
|
|
43
43
|
var ADMIN_SKILL_ID = "bamdra-user-bind-admin";
|
|
44
44
|
var SELF_TOOL_NAMES = [
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
45
|
+
"bamdra_user_bind_get_my_profile",
|
|
46
|
+
"bamdra_user_bind_update_my_profile",
|
|
47
|
+
"bamdra_user_bind_refresh_my_binding"
|
|
48
48
|
];
|
|
49
49
|
var ADMIN_TOOL_NAMES = [
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
50
|
+
"bamdra_user_bind_admin_query",
|
|
51
|
+
"bamdra_user_bind_admin_edit",
|
|
52
|
+
"bamdra_user_bind_admin_merge",
|
|
53
|
+
"bamdra_user_bind_admin_list_issues",
|
|
54
|
+
"bamdra_user_bind_admin_sync"
|
|
55
55
|
];
|
|
56
56
|
var TABLES = {
|
|
57
57
|
profiles: "bamdra_user_bind_profiles",
|
|
@@ -59,6 +59,17 @@ var TABLES = {
|
|
|
59
59
|
issues: "bamdra_user_bind_issues",
|
|
60
60
|
audits: "bamdra_user_bind_audits"
|
|
61
61
|
};
|
|
62
|
+
var REQUIRED_FEISHU_IDENTITY_SCOPES = [
|
|
63
|
+
"contact:user.employee_id:readonly",
|
|
64
|
+
"contact:user.base:readonly"
|
|
65
|
+
];
|
|
66
|
+
function logUserBindEvent(event, details = {}) {
|
|
67
|
+
try {
|
|
68
|
+
console.info("[bamdra-user-bind]", event, JSON.stringify(details));
|
|
69
|
+
} catch {
|
|
70
|
+
console.info("[bamdra-user-bind]", event);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
62
73
|
var UserBindStore = class {
|
|
63
74
|
constructor(dbPath, exportPath, profileMarkdownRoot) {
|
|
64
75
|
this.dbPath = dbPath;
|
|
@@ -412,6 +423,8 @@ var UserBindRuntime = class {
|
|
|
412
423
|
config;
|
|
413
424
|
sessionCache = /* @__PURE__ */ new Map();
|
|
414
425
|
bindingCache = /* @__PURE__ */ new Map();
|
|
426
|
+
feishuScopeStatus = null;
|
|
427
|
+
bitableMirror = null;
|
|
415
428
|
close() {
|
|
416
429
|
this.store.close();
|
|
417
430
|
}
|
|
@@ -446,13 +459,26 @@ var UserBindRuntime = class {
|
|
|
446
459
|
const binding = this.store.findBinding(parsed.channelType, parsed.openId);
|
|
447
460
|
let userId = binding?.userId ?? null;
|
|
448
461
|
let source = binding?.source ?? "local";
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
462
|
+
let remoteProfilePatch = {};
|
|
463
|
+
if (parsed.channelType === "feishu" && parsed.openId) {
|
|
464
|
+
const scopeStatus = await this.ensureFeishuScopeStatus();
|
|
465
|
+
if (scopeStatus.missingIdentityScopes.length > 0) {
|
|
466
|
+
const details = `Missing Feishu scopes: ${scopeStatus.missingIdentityScopes.join(", ")}`;
|
|
467
|
+
logUserBindEvent("feishu-scope-missing", {
|
|
468
|
+
openId: parsed.openId,
|
|
469
|
+
missingScopes: scopeStatus.missingIdentityScopes
|
|
470
|
+
});
|
|
471
|
+
this.store.recordIssue("feishu-scope-missing", details);
|
|
472
|
+
}
|
|
473
|
+
if (!userId) {
|
|
474
|
+
const remote = await this.tryResolveFeishuUser(parsed.openId);
|
|
475
|
+
if (remote?.userId) {
|
|
476
|
+
userId = remote.userId;
|
|
477
|
+
source = remote.source;
|
|
478
|
+
remoteProfilePatch = remote.profilePatch;
|
|
479
|
+
} else {
|
|
480
|
+
this.store.recordIssue("feishu-resolution", `Failed to resolve real user id for ${parsed.openId}`);
|
|
481
|
+
}
|
|
456
482
|
}
|
|
457
483
|
}
|
|
458
484
|
if (!userId) {
|
|
@@ -465,7 +491,8 @@ var UserBindRuntime = class {
|
|
|
465
491
|
openId: parsed.openId,
|
|
466
492
|
source,
|
|
467
493
|
profilePatch: {
|
|
468
|
-
name: parsed.senderName
|
|
494
|
+
name: parsed.senderName,
|
|
495
|
+
...remoteProfilePatch
|
|
469
496
|
}
|
|
470
497
|
});
|
|
471
498
|
const identity = {
|
|
@@ -483,6 +510,9 @@ var UserBindRuntime = class {
|
|
|
483
510
|
expiresAt: Date.now() + this.config.cacheTtlMs,
|
|
484
511
|
identity
|
|
485
512
|
});
|
|
513
|
+
if (parsed.channelType === "feishu") {
|
|
514
|
+
await this.syncFeishuMirror(identity);
|
|
515
|
+
}
|
|
486
516
|
return identity;
|
|
487
517
|
}
|
|
488
518
|
async getMyProfile(context) {
|
|
@@ -609,7 +639,7 @@ var UserBindRuntime = class {
|
|
|
609
639
|
return;
|
|
610
640
|
}
|
|
611
641
|
registerTool({
|
|
612
|
-
name: "
|
|
642
|
+
name: "bamdra_user_bind_get_my_profile",
|
|
613
643
|
description: "Get the current user's bound profile",
|
|
614
644
|
parameters: {
|
|
615
645
|
type: "object",
|
|
@@ -621,7 +651,7 @@ var UserBindRuntime = class {
|
|
|
621
651
|
execute: async (_id, params) => asTextResult(await this.getMyProfile(params))
|
|
622
652
|
});
|
|
623
653
|
registerTool({
|
|
624
|
-
name: "
|
|
654
|
+
name: "bamdra_user_bind_update_my_profile",
|
|
625
655
|
description: "Update the current user's own profile fields",
|
|
626
656
|
parameters: {
|
|
627
657
|
type: "object",
|
|
@@ -640,7 +670,7 @@ var UserBindRuntime = class {
|
|
|
640
670
|
execute: async (_id, params) => asTextResult(await this.updateMyProfile(params, sanitizeProfilePatch(params)))
|
|
641
671
|
});
|
|
642
672
|
registerTool({
|
|
643
|
-
name: "
|
|
673
|
+
name: "bamdra_user_bind_refresh_my_binding",
|
|
644
674
|
description: "Refresh the current user's identity binding",
|
|
645
675
|
parameters: {
|
|
646
676
|
type: "object",
|
|
@@ -654,7 +684,7 @@ var UserBindRuntime = class {
|
|
|
654
684
|
for (const toolName of ADMIN_TOOL_NAMES) {
|
|
655
685
|
registerTool({
|
|
656
686
|
name: toolName,
|
|
657
|
-
description: `Administrative natural-language tool for ${toolName.replace("
|
|
687
|
+
description: `Administrative natural-language tool for ${toolName.replace("bamdra_user_bind_admin_", "")}`,
|
|
658
688
|
parameters: {
|
|
659
689
|
type: "object",
|
|
660
690
|
additionalProperties: false,
|
|
@@ -679,9 +709,11 @@ var UserBindRuntime = class {
|
|
|
679
709
|
async tryResolveFeishuUser(openId) {
|
|
680
710
|
const executor = this.host.callTool ?? this.host.invokeTool;
|
|
681
711
|
if (typeof executor !== "function") {
|
|
712
|
+
logUserBindEvent("feishu-resolution-skipped", { reason: "tool-executor-unavailable" });
|
|
682
713
|
return null;
|
|
683
714
|
}
|
|
684
715
|
try {
|
|
716
|
+
logUserBindEvent("feishu-resolution-start", { openId });
|
|
685
717
|
const result = await executor.call(this.host, "feishu_user_get", {
|
|
686
718
|
user_id_type: "open_id",
|
|
687
719
|
user_id: openId
|
|
@@ -691,12 +723,161 @@ var UserBindRuntime = class {
|
|
|
691
723
|
["user", "user_id"],
|
|
692
724
|
["data", "user_id"]
|
|
693
725
|
]);
|
|
694
|
-
|
|
726
|
+
if (!candidate) {
|
|
727
|
+
logUserBindEvent("feishu-resolution-empty", { openId });
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
logUserBindEvent("feishu-resolution-success", { openId, userId: candidate });
|
|
731
|
+
return {
|
|
732
|
+
userId: candidate,
|
|
733
|
+
source: "feishu-api",
|
|
734
|
+
profilePatch: {
|
|
735
|
+
name: extractDeepString(result, [["data", "user", "name"], ["user", "name"]]),
|
|
736
|
+
email: extractDeepString(result, [["data", "user", "email"], ["user", "email"]]),
|
|
737
|
+
avatar: extractDeepString(result, [["data", "user", "avatar", "avatar_origin"], ["user", "avatar", "avatar_origin"]])
|
|
738
|
+
}
|
|
739
|
+
};
|
|
695
740
|
} catch (error) {
|
|
696
|
-
|
|
741
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
742
|
+
logUserBindEvent("feishu-resolution-failed", { openId, message });
|
|
743
|
+
this.store.recordIssue("feishu-resolution", message);
|
|
697
744
|
return null;
|
|
698
745
|
}
|
|
699
746
|
}
|
|
747
|
+
async ensureFeishuScopeStatus() {
|
|
748
|
+
if (this.feishuScopeStatus) {
|
|
749
|
+
return this.feishuScopeStatus;
|
|
750
|
+
}
|
|
751
|
+
const executor = this.host.callTool ?? this.host.invokeTool;
|
|
752
|
+
if (typeof executor !== "function") {
|
|
753
|
+
this.feishuScopeStatus = {
|
|
754
|
+
scopes: [],
|
|
755
|
+
missingIdentityScopes: [...REQUIRED_FEISHU_IDENTITY_SCOPES],
|
|
756
|
+
hasDocumentAccess: false
|
|
757
|
+
};
|
|
758
|
+
return this.feishuScopeStatus;
|
|
759
|
+
}
|
|
760
|
+
try {
|
|
761
|
+
const result = await executor.call(this.host, "feishu_app_scopes", {});
|
|
762
|
+
const scopes = extractScopes(result);
|
|
763
|
+
this.feishuScopeStatus = {
|
|
764
|
+
scopes,
|
|
765
|
+
missingIdentityScopes: REQUIRED_FEISHU_IDENTITY_SCOPES.filter((scope) => !scopes.includes(scope)),
|
|
766
|
+
hasDocumentAccess: scopes.some((scope) => scope.startsWith("bitable:") || scope.startsWith("drive:") || scope.startsWith("docx:") || scope.startsWith("docs:"))
|
|
767
|
+
};
|
|
768
|
+
logUserBindEvent("feishu-scopes-read", this.feishuScopeStatus);
|
|
769
|
+
return this.feishuScopeStatus;
|
|
770
|
+
} catch (error) {
|
|
771
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
772
|
+
logUserBindEvent("feishu-scopes-failed", { message });
|
|
773
|
+
this.store.recordIssue("feishu-scope-read", message);
|
|
774
|
+
this.feishuScopeStatus = {
|
|
775
|
+
scopes: [],
|
|
776
|
+
missingIdentityScopes: [...REQUIRED_FEISHU_IDENTITY_SCOPES],
|
|
777
|
+
hasDocumentAccess: false
|
|
778
|
+
};
|
|
779
|
+
return this.feishuScopeStatus;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
async syncFeishuMirror(identity) {
|
|
783
|
+
const scopeStatus = await this.ensureFeishuScopeStatus();
|
|
784
|
+
if (!scopeStatus.hasDocumentAccess) {
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const executor = this.host.callTool ?? this.host.invokeTool;
|
|
788
|
+
if (typeof executor !== "function") {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
try {
|
|
792
|
+
const mirror = await this.ensureFeishuBitableMirror(executor.bind(this.host));
|
|
793
|
+
if (!mirror.appToken || !mirror.tableId) {
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
const existing = await executor.call(this.host, "feishu_bitable_list_records", {
|
|
797
|
+
app_token: mirror.appToken,
|
|
798
|
+
table_id: mirror.tableId
|
|
799
|
+
});
|
|
800
|
+
const recordId = findBitableRecordId(existing, identity.userId);
|
|
801
|
+
const fields = {
|
|
802
|
+
user_id: identity.userId,
|
|
803
|
+
channel_type: identity.channelType,
|
|
804
|
+
open_id: identity.senderOpenId,
|
|
805
|
+
name: identity.profile.name,
|
|
806
|
+
nickname: identity.profile.nickname,
|
|
807
|
+
preferences: identity.profile.preferences,
|
|
808
|
+
personality: identity.profile.personality,
|
|
809
|
+
role: identity.profile.role,
|
|
810
|
+
timezone: identity.profile.timezone,
|
|
811
|
+
email: identity.profile.email,
|
|
812
|
+
avatar: identity.profile.avatar
|
|
813
|
+
};
|
|
814
|
+
if (recordId) {
|
|
815
|
+
await executor.call(this.host, "feishu_bitable_update_record", {
|
|
816
|
+
app_token: mirror.appToken,
|
|
817
|
+
table_id: mirror.tableId,
|
|
818
|
+
record_id: recordId,
|
|
819
|
+
fields
|
|
820
|
+
});
|
|
821
|
+
} else {
|
|
822
|
+
await executor.call(this.host, "feishu_bitable_create_record", {
|
|
823
|
+
app_token: mirror.appToken,
|
|
824
|
+
table_id: mirror.tableId,
|
|
825
|
+
fields
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
logUserBindEvent("feishu-bitable-sync-success", { userId: identity.userId });
|
|
829
|
+
} catch (error) {
|
|
830
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
831
|
+
logUserBindEvent("feishu-bitable-sync-failed", { userId: identity.userId, message });
|
|
832
|
+
this.store.recordIssue("feishu-bitable-sync", message, identity.userId);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
async ensureFeishuBitableMirror(executor) {
|
|
836
|
+
if (this.bitableMirror?.appToken && this.bitableMirror?.tableId) {
|
|
837
|
+
return this.bitableMirror;
|
|
838
|
+
}
|
|
839
|
+
try {
|
|
840
|
+
const app = await executor("feishu_bitable_create_app", { name: "Bamdra User Bind" });
|
|
841
|
+
const appToken = extractDeepString(app, [
|
|
842
|
+
["data", "app", "app_token"],
|
|
843
|
+
["data", "app_token"],
|
|
844
|
+
["app", "app_token"],
|
|
845
|
+
["app_token"]
|
|
846
|
+
]);
|
|
847
|
+
if (!appToken) {
|
|
848
|
+
return { appToken: null, tableId: null };
|
|
849
|
+
}
|
|
850
|
+
const meta = await executor("feishu_bitable_get_meta", { app_token: appToken });
|
|
851
|
+
const tableId = extractDeepString(meta, [
|
|
852
|
+
["data", "tables", "0", "table_id"],
|
|
853
|
+
["data", "items", "0", "table_id"],
|
|
854
|
+
["tables", "0", "table_id"]
|
|
855
|
+
]);
|
|
856
|
+
if (!tableId) {
|
|
857
|
+
this.store.recordIssue("feishu-bitable-init", "Unable to determine users table id from Feishu bitable metadata");
|
|
858
|
+
return { appToken, tableId: null };
|
|
859
|
+
}
|
|
860
|
+
for (const fieldName of ["user_id", "channel_type", "open_id", "name", "nickname", "preferences", "personality", "role", "timezone", "email", "avatar"]) {
|
|
861
|
+
try {
|
|
862
|
+
await executor("feishu_bitable_create_field", {
|
|
863
|
+
app_token: appToken,
|
|
864
|
+
table_id: tableId,
|
|
865
|
+
field_name: fieldName,
|
|
866
|
+
type: 1
|
|
867
|
+
});
|
|
868
|
+
} catch {
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
this.bitableMirror = { appToken, tableId };
|
|
872
|
+
logUserBindEvent("feishu-bitable-ready", this.bitableMirror);
|
|
873
|
+
return this.bitableMirror;
|
|
874
|
+
} catch (error) {
|
|
875
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
876
|
+
logUserBindEvent("feishu-bitable-init-failed", { message });
|
|
877
|
+
this.store.recordIssue("feishu-bitable-init", message);
|
|
878
|
+
return { appToken: null, tableId: null };
|
|
879
|
+
}
|
|
880
|
+
}
|
|
700
881
|
};
|
|
701
882
|
function createUserBindPlugin(api) {
|
|
702
883
|
return new UserBindRuntime(api, api.pluginConfig ?? api.config ?? api.plugin?.config);
|
|
@@ -885,12 +1066,20 @@ function mapProfileRow(row) {
|
|
|
885
1066
|
}
|
|
886
1067
|
function parseIdentityContext(context) {
|
|
887
1068
|
const record = context && typeof context === "object" ? context : {};
|
|
888
|
-
const sender = record
|
|
889
|
-
const message = record
|
|
890
|
-
const
|
|
891
|
-
const
|
|
892
|
-
const
|
|
893
|
-
const
|
|
1069
|
+
const sender = findNestedRecord(record, ["sender"], ["message", "sender"], ["event", "sender"], ["payload", "sender"]);
|
|
1070
|
+
const message = findNestedRecord(record, ["message"], ["event", "message"], ["payload", "message"]);
|
|
1071
|
+
const session = findNestedRecord(record, ["session"], ["context", "session"]);
|
|
1072
|
+
const channel = findNestedRecord(record, ["channel"], ["message", "channel"], ["event", "channel"], ["payload", "channel"]);
|
|
1073
|
+
const metadataText = asNullableString(record.text) ?? asNullableString(message.text) ?? asNullableString(record.content) ?? asNullableString(findNestedValue(record, ["message", "content", "text"]));
|
|
1074
|
+
const conversationInfo = metadataText ? extractTaggedJsonBlock(metadataText, "Conversation info (untrusted metadata)") : null;
|
|
1075
|
+
const senderInfo = metadataText ? extractTaggedJsonBlock(metadataText, "Sender (untrusted metadata)") : null;
|
|
1076
|
+
const senderIdFromText = metadataText ? extractRegexValue(metadataText, /"sender_id"\s*:\s*"([^"]+)"/) : null;
|
|
1077
|
+
const senderNameFromText = metadataText ? extractRegexValue(metadataText, /"sender"\s*:\s*"([^"]+)"/) : null;
|
|
1078
|
+
const senderNameFromMessageLine = metadataText ? extractRegexValue(metadataText, /\]\s*([^\n::]{1,40})\s*[::]/) : null;
|
|
1079
|
+
const sessionId = asNullableString(record.sessionId) ?? asNullableString(record.sessionKey) ?? asNullableString(session.id) ?? asNullableString(record.context?.sessionId) ?? asNullableString(conversationInfo?.session_id) ?? asNullableString(conversationInfo?.message_id);
|
|
1080
|
+
const channelType = asNullableString(record.channelType) ?? asNullableString(channel.type) ?? asNullableString(record.provider) ?? asNullableString(conversationInfo?.provider) ?? inferChannelTypeFromSessionId(sessionId);
|
|
1081
|
+
const openId = asNullableString(sender.id) ?? asNullableString(sender.open_id) ?? asNullableString(sender.openId) ?? asNullableString(sender.user_id) ?? asNullableString(senderInfo?.id) ?? asNullableString(conversationInfo?.sender_id) ?? senderIdFromText ?? extractOpenIdFromSessionId(sessionId);
|
|
1082
|
+
const senderName = asNullableString(sender.name) ?? asNullableString(sender.display_name) ?? asNullableString(senderInfo?.name) ?? asNullableString(conversationInfo?.sender) ?? senderNameFromText ?? senderNameFromMessageLine;
|
|
894
1083
|
return {
|
|
895
1084
|
sessionId,
|
|
896
1085
|
channelType,
|
|
@@ -898,6 +1087,67 @@ function parseIdentityContext(context) {
|
|
|
898
1087
|
senderName
|
|
899
1088
|
};
|
|
900
1089
|
}
|
|
1090
|
+
function findNestedRecord(root, ...paths) {
|
|
1091
|
+
for (const path of paths) {
|
|
1092
|
+
const value = findNestedValue(root, path);
|
|
1093
|
+
if (value && typeof value === "object") {
|
|
1094
|
+
return value;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return {};
|
|
1098
|
+
}
|
|
1099
|
+
function findNestedValue(root, path) {
|
|
1100
|
+
let current = root;
|
|
1101
|
+
for (const part of path) {
|
|
1102
|
+
if (!current || typeof current !== "object") {
|
|
1103
|
+
return null;
|
|
1104
|
+
}
|
|
1105
|
+
current = current[part];
|
|
1106
|
+
}
|
|
1107
|
+
return current;
|
|
1108
|
+
}
|
|
1109
|
+
function extractTaggedJsonBlock(text, label) {
|
|
1110
|
+
const start = text.indexOf(label);
|
|
1111
|
+
if (start < 0) {
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
const block = text.slice(start).match(/```json\s*([\s\S]*?)\s*```/i);
|
|
1115
|
+
if (!block) {
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
try {
|
|
1119
|
+
const parsed = JSON.parse(block[1]);
|
|
1120
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
1121
|
+
} catch {
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
function inferChannelTypeFromSessionId(sessionId) {
|
|
1126
|
+
if (!sessionId) {
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
if (sessionId.includes(":feishu:")) {
|
|
1130
|
+
return "feishu";
|
|
1131
|
+
}
|
|
1132
|
+
if (sessionId.includes(":telegram:")) {
|
|
1133
|
+
return "telegram";
|
|
1134
|
+
}
|
|
1135
|
+
if (sessionId.includes(":whatsapp:")) {
|
|
1136
|
+
return "whatsapp";
|
|
1137
|
+
}
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
function extractRegexValue(text, pattern) {
|
|
1141
|
+
const match = text.match(pattern);
|
|
1142
|
+
return match?.[1]?.trim() || null;
|
|
1143
|
+
}
|
|
1144
|
+
function extractOpenIdFromSessionId(sessionId) {
|
|
1145
|
+
if (!sessionId) {
|
|
1146
|
+
return null;
|
|
1147
|
+
}
|
|
1148
|
+
const match = sessionId.match(/:([A-Za-z0-9_-]+)$/);
|
|
1149
|
+
return match?.[1] ?? null;
|
|
1150
|
+
}
|
|
901
1151
|
function getAgentIdFromContext(context) {
|
|
902
1152
|
const record = context && typeof context === "object" ? context : {};
|
|
903
1153
|
return asNullableString(record.agentId) ?? asNullableString(record.agent?.id) ?? asNullableString(record.agent?.name);
|
|
@@ -1141,6 +1391,60 @@ function ensureArrayIncludes(parent, key, value) {
|
|
|
1141
1391
|
parent[key] = current;
|
|
1142
1392
|
return true;
|
|
1143
1393
|
}
|
|
1394
|
+
function extractScopes(result) {
|
|
1395
|
+
const candidates = [
|
|
1396
|
+
findNestedValue(result, ["data", "scopes"]),
|
|
1397
|
+
findNestedValue(result, ["scopes"]),
|
|
1398
|
+
findNestedValue(result, ["data", "items"])
|
|
1399
|
+
];
|
|
1400
|
+
for (const candidate of candidates) {
|
|
1401
|
+
if (!Array.isArray(candidate)) {
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
const scopes = candidate.map((item) => {
|
|
1405
|
+
if (typeof item === "string") {
|
|
1406
|
+
return item;
|
|
1407
|
+
}
|
|
1408
|
+
if (item && typeof item === "object") {
|
|
1409
|
+
const record = item;
|
|
1410
|
+
const scope = record.scope ?? record.name;
|
|
1411
|
+
return typeof scope === "string" ? scope : "";
|
|
1412
|
+
}
|
|
1413
|
+
return "";
|
|
1414
|
+
}).filter(Boolean);
|
|
1415
|
+
if (scopes.length > 0) {
|
|
1416
|
+
return scopes;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return [];
|
|
1420
|
+
}
|
|
1421
|
+
function findBitableRecordId(result, userId) {
|
|
1422
|
+
const candidates = [
|
|
1423
|
+
findNestedValue(result, ["data", "items"]),
|
|
1424
|
+
findNestedValue(result, ["items"]),
|
|
1425
|
+
findNestedValue(result, ["data", "records"])
|
|
1426
|
+
];
|
|
1427
|
+
for (const candidate of candidates) {
|
|
1428
|
+
if (!Array.isArray(candidate)) {
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
for (const item of candidate) {
|
|
1432
|
+
if (!item || typeof item !== "object") {
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
const record = item;
|
|
1436
|
+
const fields = record.fields && typeof record.fields === "object" ? record.fields : {};
|
|
1437
|
+
if (String(fields.user_id ?? "") !== userId) {
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
const recordId = record.record_id ?? record.recordId ?? record.id;
|
|
1441
|
+
if (typeof recordId === "string" && recordId.trim()) {
|
|
1442
|
+
return recordId;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return null;
|
|
1447
|
+
}
|
|
1144
1448
|
function getConfiguredAgentId(agent) {
|
|
1145
1449
|
return asNullableString(agent.id) ?? asNullableString(agent.name) ?? asNullableString(agent.agentId);
|
|
1146
1450
|
}
|
package/package.json
CHANGED