@downcity/plugins 1.0.153 → 1.0.156

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.
Files changed (175) hide show
  1. package/bin/chat/ChatPlugin.d.ts +2 -10
  2. package/bin/chat/ChatPlugin.d.ts.map +1 -1
  3. package/bin/chat/ChatPlugin.js +3 -13
  4. package/bin/chat/ChatPlugin.js.map +1 -1
  5. package/bin/chat/PROMPT.direct.d.ts +1 -1
  6. package/bin/chat/PROMPT.direct.d.ts.map +1 -1
  7. package/bin/chat/PROMPT.direct.js +1 -1
  8. package/bin/chat/PROMPT.direct.js.map +1 -1
  9. package/bin/chat/access/ChatAccessActions.d.ts +11 -0
  10. package/bin/chat/access/ChatAccessActions.d.ts.map +1 -0
  11. package/bin/chat/access/ChatAccessActions.js +137 -0
  12. package/bin/chat/access/ChatAccessActions.js.map +1 -0
  13. package/bin/chat/access/ChatAccessRuntime.d.ts +17 -0
  14. package/bin/chat/access/ChatAccessRuntime.d.ts.map +1 -0
  15. package/bin/chat/access/ChatAccessRuntime.js +31 -0
  16. package/bin/chat/access/ChatAccessRuntime.js.map +1 -0
  17. package/bin/chat/access/ChatAccessSchema.d.ts +16 -0
  18. package/bin/chat/access/ChatAccessSchema.d.ts.map +1 -0
  19. package/bin/chat/access/ChatAccessSchema.js +99 -0
  20. package/bin/chat/access/ChatAccessSchema.js.map +1 -0
  21. package/bin/chat/access/ChatAccessService.d.ts +44 -0
  22. package/bin/chat/access/ChatAccessService.d.ts.map +1 -0
  23. package/bin/chat/access/ChatAccessService.js +281 -0
  24. package/bin/chat/access/ChatAccessService.js.map +1 -0
  25. package/bin/chat/access/ChatAccessStore.d.ts +67 -0
  26. package/bin/chat/access/ChatAccessStore.d.ts.map +1 -0
  27. package/bin/chat/access/ChatAccessStore.js +367 -0
  28. package/bin/chat/access/ChatAccessStore.js.map +1 -0
  29. package/bin/chat/accounts/Crypto.d.ts +1 -1
  30. package/bin/chat/accounts/Crypto.js +1 -1
  31. package/bin/chat/accounts/Store.d.ts +1 -1
  32. package/bin/chat/accounts/Store.js +1 -1
  33. package/bin/chat/channels/BaseChatChannel.d.ts +18 -21
  34. package/bin/chat/channels/BaseChatChannel.d.ts.map +1 -1
  35. package/bin/chat/channels/BaseChatChannel.js +48 -58
  36. package/bin/chat/channels/BaseChatChannel.js.map +1 -1
  37. package/bin/chat/channels/BaseChatChannelQueue.d.ts +1 -1
  38. package/bin/chat/channels/BaseChatChannelQueue.d.ts.map +1 -1
  39. package/bin/chat/channels/BaseChatChannelQueue.js +2 -14
  40. package/bin/chat/channels/BaseChatChannelQueue.js.map +1 -1
  41. package/bin/chat/channels/Configuration.d.ts +2 -2
  42. package/bin/chat/channels/Configuration.d.ts.map +1 -1
  43. package/bin/chat/channels/Configuration.js +3 -3
  44. package/bin/chat/channels/Configuration.js.map +1 -1
  45. package/bin/chat/channels/feishu/Configuration.js +4 -4
  46. package/bin/chat/channels/feishu/Configuration.js.map +1 -1
  47. package/bin/chat/channels/feishu/Feishu.d.ts +2 -0
  48. package/bin/chat/channels/feishu/Feishu.d.ts.map +1 -1
  49. package/bin/chat/channels/feishu/Feishu.js +8 -7
  50. package/bin/chat/channels/feishu/Feishu.js.map +1 -1
  51. package/bin/chat/channels/feishu/FeishuMessageHandler.d.ts +9 -17
  52. package/bin/chat/channels/feishu/FeishuMessageHandler.d.ts.map +1 -1
  53. package/bin/chat/channels/feishu/FeishuMessageHandler.js +6 -17
  54. package/bin/chat/channels/feishu/FeishuMessageHandler.js.map +1 -1
  55. package/bin/chat/channels/qq/Configuration.js +4 -4
  56. package/bin/chat/channels/qq/Configuration.js.map +1 -1
  57. package/bin/chat/channels/qq/QQ.d.ts +2 -0
  58. package/bin/chat/channels/qq/QQ.d.ts.map +1 -1
  59. package/bin/chat/channels/qq/QQ.js +8 -7
  60. package/bin/chat/channels/qq/QQ.js.map +1 -1
  61. package/bin/chat/channels/qq/QQMessageHandler.d.ts +9 -17
  62. package/bin/chat/channels/qq/QQMessageHandler.d.ts.map +1 -1
  63. package/bin/chat/channels/qq/QQMessageHandler.js +7 -25
  64. package/bin/chat/channels/qq/QQMessageHandler.js.map +1 -1
  65. package/bin/chat/channels/telegram/Bot.d.ts +2 -0
  66. package/bin/chat/channels/telegram/Bot.d.ts.map +1 -1
  67. package/bin/chat/channels/telegram/Bot.js +8 -7
  68. package/bin/chat/channels/telegram/Bot.js.map +1 -1
  69. package/bin/chat/channels/telegram/Configuration.js +4 -4
  70. package/bin/chat/channels/telegram/Configuration.js.map +1 -1
  71. package/bin/chat/channels/telegram/TelegramMessageHandler.d.ts +9 -17
  72. package/bin/chat/channels/telegram/TelegramMessageHandler.d.ts.map +1 -1
  73. package/bin/chat/channels/telegram/TelegramMessageHandler.js +5 -16
  74. package/bin/chat/channels/telegram/TelegramMessageHandler.js.map +1 -1
  75. package/bin/chat/runtime/ChatChannelActions.d.ts +3 -2
  76. package/bin/chat/runtime/ChatChannelActions.d.ts.map +1 -1
  77. package/bin/chat/runtime/ChatChannelActions.js +57 -0
  78. package/bin/chat/runtime/ChatChannelActions.js.map +1 -1
  79. package/bin/chat/runtime/ChatChannelConfig.d.ts +2 -2
  80. package/bin/chat/runtime/ChatChannelConfig.js +3 -3
  81. package/bin/chat/runtime/ChatChannelConfig.js.map +1 -1
  82. package/bin/chat/runtime/ChatChannelCore.d.ts +2 -2
  83. package/bin/chat/runtime/ChatChannelCore.js +1 -1
  84. package/bin/chat/runtime/ChatChannelLifecycle.d.ts +1 -1
  85. package/bin/chat/runtime/ChatChannelLifecycle.js +1 -1
  86. package/bin/chat/runtime/PluginPoints.d.ts +0 -24
  87. package/bin/chat/runtime/PluginPoints.d.ts.map +1 -1
  88. package/bin/chat/runtime/PluginPoints.js +0 -24
  89. package/bin/chat/runtime/PluginPoints.js.map +1 -1
  90. package/bin/chat/runtime/QueuedUserMessage.d.ts.map +1 -1
  91. package/bin/chat/runtime/QueuedUserMessage.js +0 -2
  92. package/bin/chat/runtime/QueuedUserMessage.js.map +1 -1
  93. package/bin/chat/types/ChannelConfiguration.d.ts +4 -4
  94. package/bin/chat/types/ChannelConfiguration.d.ts.map +1 -1
  95. package/bin/chat/types/ChatAccess.d.ts +300 -0
  96. package/bin/chat/types/ChatAccess.d.ts.map +1 -0
  97. package/bin/chat/types/ChatAccess.js +22 -0
  98. package/bin/chat/types/ChatAccess.js.map +1 -0
  99. package/bin/chat/types/ChatPromptContext.d.ts +0 -15
  100. package/bin/chat/types/ChatPromptContext.d.ts.map +1 -1
  101. package/bin/index.d.ts +4 -4
  102. package/bin/index.d.ts.map +1 -1
  103. package/bin/index.js +3 -3
  104. package/bin/index.js.map +1 -1
  105. package/bin/skill/Config.d.ts +1 -1
  106. package/bin/skill/Config.js +1 -1
  107. package/bin/skill/runtime/Paths.d.ts +1 -1
  108. package/bin/skill/runtime/Paths.js +1 -1
  109. package/package.json +6 -4
  110. package/scripts/chat-access.test.mjs +90 -0
  111. package/scripts/chat-channel-config.test.mjs +82 -0
  112. package/src/chat/ChatPlugin.ts +4 -24
  113. package/src/chat/PROMPT.direct.ts +1 -1
  114. package/src/chat/PROMPT.direct.ts.txt +1 -1
  115. package/src/chat/access/ChatAccessActions.ts +159 -0
  116. package/src/chat/access/ChatAccessRuntime.ts +41 -0
  117. package/src/chat/access/ChatAccessSchema.ts +104 -0
  118. package/src/chat/access/ChatAccessService.ts +311 -0
  119. package/src/chat/access/ChatAccessStore.ts +462 -0
  120. package/src/chat/accounts/Crypto.ts +1 -1
  121. package/src/chat/accounts/Store.ts +1 -1
  122. package/src/chat/channels/BaseChatChannel.ts +59 -80
  123. package/src/chat/channels/BaseChatChannelQueue.ts +2 -14
  124. package/src/chat/channels/Configuration.ts +3 -3
  125. package/src/chat/channels/feishu/Configuration.ts +4 -4
  126. package/src/chat/channels/feishu/Feishu.ts +11 -9
  127. package/src/chat/channels/feishu/FeishuMessageHandler.ts +16 -40
  128. package/src/chat/channels/qq/Configuration.ts +4 -4
  129. package/src/chat/channels/qq/QQ.ts +11 -9
  130. package/src/chat/channels/qq/QQMessageHandler.ts +17 -49
  131. package/src/chat/channels/telegram/Bot.ts +11 -9
  132. package/src/chat/channels/telegram/Configuration.ts +4 -4
  133. package/src/chat/channels/telegram/TelegramMessageHandler.ts +15 -39
  134. package/src/chat/runtime/ChatChannelActions.ts +72 -1
  135. package/src/chat/runtime/ChatChannelConfig.ts +3 -3
  136. package/src/chat/runtime/ChatChannelCore.ts +1 -1
  137. package/src/chat/runtime/ChatChannelLifecycle.ts +1 -1
  138. package/src/chat/runtime/PluginPoints.ts +0 -24
  139. package/src/chat/runtime/QueuedUserMessage.ts +0 -2
  140. package/src/chat/types/ChannelConfiguration.ts +4 -4
  141. package/src/chat/types/ChatAccess.ts +335 -0
  142. package/src/chat/types/ChatPromptContext.ts +0 -17
  143. package/src/index.ts +27 -31
  144. package/src/skill/Config.ts +1 -1
  145. package/src/skill/runtime/Paths.ts +1 -1
  146. package/bin/auth/runtime/AuthorizationConfig.d.ts +0 -41
  147. package/bin/auth/runtime/AuthorizationConfig.d.ts.map +0 -1
  148. package/bin/auth/runtime/AuthorizationConfig.js +0 -185
  149. package/bin/auth/runtime/AuthorizationConfig.js.map +0 -1
  150. package/bin/auth/runtime/AuthorizationPolicy.d.ts +0 -28
  151. package/bin/auth/runtime/AuthorizationPolicy.d.ts.map +0 -1
  152. package/bin/auth/runtime/AuthorizationPolicy.js +0 -124
  153. package/bin/auth/runtime/AuthorizationPolicy.js.map +0 -1
  154. package/bin/auth/runtime/AuthorizationStore.d.ts +0 -29
  155. package/bin/auth/runtime/AuthorizationStore.d.ts.map +0 -1
  156. package/bin/auth/runtime/AuthorizationStore.js +0 -179
  157. package/bin/auth/runtime/AuthorizationStore.js.map +0 -1
  158. package/bin/auth/types/AuthPlugin.d.ts +0 -403
  159. package/bin/auth/types/AuthPlugin.d.ts.map +0 -1
  160. package/bin/auth/types/AuthPlugin.js +0 -111
  161. package/bin/auth/types/AuthPlugin.js.map +0 -1
  162. package/bin/chat/runtime/ChatAuthorizationRuntime.d.ts +0 -22
  163. package/bin/chat/runtime/ChatAuthorizationRuntime.d.ts.map +0 -1
  164. package/bin/chat/runtime/ChatAuthorizationRuntime.js +0 -230
  165. package/bin/chat/runtime/ChatAuthorizationRuntime.js.map +0 -1
  166. package/bin/chat/runtime/PluginDispatch.d.ts +0 -43
  167. package/bin/chat/runtime/PluginDispatch.d.ts.map +0 -1
  168. package/bin/chat/runtime/PluginDispatch.js +0 -57
  169. package/bin/chat/runtime/PluginDispatch.js.map +0 -1
  170. package/src/auth/runtime/AuthorizationConfig.ts +0 -247
  171. package/src/auth/runtime/AuthorizationPolicy.ts +0 -167
  172. package/src/auth/runtime/AuthorizationStore.ts +0 -220
  173. package/src/auth/types/AuthPlugin.ts +0 -540
  174. package/src/chat/runtime/ChatAuthorizationRuntime.ts +0 -270
  175. package/src/chat/runtime/PluginDispatch.ts +0 -89
@@ -1,167 +0,0 @@
1
- /**
2
- * Auth 授权判定逻辑。
3
- *
4
- * 关键点(中文)
5
- * - 角色模型替代旧的 allowlist/pairing 模型。
6
- * - 判定统一走“用户角色 + 权限集合”,群聊只看发言用户自身权限。
7
- */
8
-
9
- import type { DowncityConfig } from "@downcity/agent";
10
- import type {
11
- ChatAuthorizationConfig,
12
- ChatAuthorizationChannel,
13
- ChatAuthorizationEvaluateInput,
14
- ChatAuthorizationEvaluateResult,
15
- ChatAuthorizationPermission,
16
- ChatAuthorizationRole,
17
- ChatChannelAuthorizationConfig,
18
- } from "@/auth/types/AuthPlugin.js";
19
- import { createDefaultChatAuthorizationRoles } from "@/auth/types/AuthPlugin.js";
20
- import { readChatAuthorizationConfigSync } from "@/auth/runtime/AuthorizationConfig.js";
21
-
22
- function normalizeText(value: unknown): string | undefined {
23
- const text = String(value || "").trim();
24
- return text ? text : undefined;
25
- }
26
-
27
- function isDirectMessage(channel: ChatAuthorizationChannel, chatType?: string): boolean {
28
- const type = String(chatType || "").trim().toLowerCase();
29
- if (!type) return true;
30
- if (channel === "telegram") return type === "private";
31
- if (channel === "feishu") return type === "p2p";
32
- if (channel === "qq") return type === "private" || type === "c2c";
33
- return false;
34
- }
35
-
36
- function resolveAuthorizationRoles(
37
- authorizationConfig?: ChatAuthorizationConfig,
38
- ): Record<string, ChatAuthorizationRole> {
39
- const raw = authorizationConfig?.roles;
40
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
41
- return createDefaultChatAuthorizationRoles();
42
- }
43
- return Object.keys(raw).length > 0 ? raw : createDefaultChatAuthorizationRoles();
44
- }
45
-
46
- function resolveChannelAuthorizationConfig(
47
- channel: ChatAuthorizationChannel,
48
- authorizationConfig?: ChatAuthorizationConfig,
49
- ): ChatChannelAuthorizationConfig {
50
- const raw = authorizationConfig?.channels?.[channel];
51
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
52
- return {
53
- defaultUserRoleId: "default",
54
- userRoles: {},
55
- };
56
- }
57
- return raw;
58
- }
59
-
60
- function resolveRole(
61
- roleId: string | undefined,
62
- roles: Record<string, ChatAuthorizationRole> | undefined,
63
- ): ChatAuthorizationRole {
64
- if (roleId && roles?.[roleId]) return roles[roleId];
65
- if (roles?.default) return roles.default;
66
- return { roleId: "default", name: "Default", permissions: [] };
67
- }
68
-
69
- function hasPermission(
70
- role: ChatAuthorizationRole,
71
- permission: ChatAuthorizationPermission,
72
- ): boolean {
73
- return role.permissions.includes(permission);
74
- }
75
-
76
- function resolveUserRole(params: {
77
- roles: Record<string, ChatAuthorizationRole>;
78
- channelConfig: ChatChannelAuthorizationConfig;
79
- userId?: string;
80
- }): ChatAuthorizationRole {
81
- const userId = normalizeText(params.userId);
82
- const defaultRoleId = normalizeText(params.channelConfig.defaultUserRoleId) || "default";
83
- const boundRoleId = userId ? normalizeText(params.channelConfig.userRoles?.[userId]) : undefined;
84
- return resolveRole(boundRoleId || defaultRoleId, params.roles);
85
- }
86
-
87
- /**
88
- * 解析用户授权角色。
89
- */
90
- export function resolveAuthorizedUserRole(params: {
91
- channel: ChatAuthorizationChannel;
92
- userId?: string;
93
- authorizationConfig?: ChatAuthorizationConfig;
94
- rootPath?: string;
95
- }): ChatAuthorizationRole | undefined {
96
- const userId = normalizeText(params.userId);
97
- if (!userId) return undefined;
98
- const authorizationConfig =
99
- params.authorizationConfig ||
100
- (params.rootPath
101
- ? readChatAuthorizationConfigSync(params.rootPath)
102
- : undefined);
103
- const roles = resolveAuthorizationRoles(authorizationConfig);
104
- const channelConfig = resolveChannelAuthorizationConfig(params.channel, authorizationConfig);
105
- return resolveUserRole({
106
- roles,
107
- channelConfig,
108
- userId,
109
- });
110
- }
111
-
112
- /**
113
- * 执行入站授权判定。
114
- */
115
- export function evaluateIncomingChatAuthorization(params: {
116
- config: DowncityConfig;
117
- channel: ChatAuthorizationChannel;
118
- input: ChatAuthorizationEvaluateInput;
119
- authorizationConfig?: ChatAuthorizationConfig;
120
- }): ChatAuthorizationEvaluateResult {
121
- const channelConfig = resolveChannelAuthorizationConfig(
122
- params.channel,
123
- params.authorizationConfig,
124
- );
125
- const roles = resolveAuthorizationRoles(params.authorizationConfig);
126
- const direct = isDirectMessage(params.channel, params.input.chatType);
127
- const userRole = resolveUserRole({
128
- roles,
129
- channelConfig,
130
- userId: params.input.userId,
131
- });
132
- const isOwner = hasPermission(userRole, "agent.manage");
133
-
134
- if (direct) {
135
- return hasPermission(userRole, "chat.dm.use")
136
- ? {
137
- decision: "allow",
138
- isOwner,
139
- userRoleId: userRole.roleId,
140
- userPermissions: [...userRole.permissions],
141
- reason: "dm_role_allowed",
142
- }
143
- : {
144
- decision: "block",
145
- isOwner,
146
- userRoleId: userRole.roleId,
147
- userPermissions: [...userRole.permissions],
148
- reason: "dm_role_blocked",
149
- };
150
- }
151
-
152
- return hasPermission(userRole, "chat.group.use")
153
- ? {
154
- decision: "allow",
155
- isOwner,
156
- userRoleId: userRole.roleId,
157
- userPermissions: [...userRole.permissions],
158
- reason: "group_user_role_allowed",
159
- }
160
- : {
161
- decision: "block",
162
- isOwner,
163
- userRoleId: userRole.roleId,
164
- userPermissions: [...userRole.permissions],
165
- reason: "user_group_permission_blocked",
166
- };
167
- }
@@ -1,220 +0,0 @@
1
- /**
2
- * Auth 授权运行时状态存储。
3
- *
4
- * 关键点(中文)
5
- * - 所有动态状态(观测用户 / 会话)统一落盘到 `.downcity/chat/authorization/state.json`。
6
- * - 静态授权配置保存在同目录下的 `config.json`,不和运行时观测数据混写。
7
- */
8
-
9
- import fs from "fs-extra";
10
- import path from "node:path";
11
- import type {
12
- ChatAuthorizationObservedChat,
13
- ChatAuthorizationObservedUser,
14
- ChatAuthorizationSnapshot,
15
- ChatAuthorizationStateFile,
16
- } from "@/auth/types/AuthPlugin.js";
17
- import type { AgentContext } from "@downcity/agent";
18
- import type { ChatDispatchChannel } from "@/chat/types/ChatDispatcher.js";
19
- import { readChatAuthorizationConfig } from "@/auth/runtime/AuthorizationConfig.js";
20
-
21
- function getAuthorizationStatePath(rootPath: string): string {
22
- return path.join(rootPath, ".downcity", "chat", "authorization", "state.json");
23
- }
24
-
25
- function normalizeText(value: unknown): string | undefined {
26
- const text = String(value || "").trim();
27
- return text ? text : undefined;
28
- }
29
-
30
- function buildUserKey(channel: ChatDispatchChannel, userId: string): string {
31
- return `${channel}:${userId}`;
32
- }
33
-
34
- function buildChatKey(channel: ChatDispatchChannel, chatId: string): string {
35
- return `${channel}:${chatId}`;
36
- }
37
-
38
- function createEmptyState(): ChatAuthorizationStateFile {
39
- return {
40
- v: 1,
41
- updatedAt: Date.now(),
42
- usersByKey: {},
43
- chatsByKey: {},
44
- };
45
- }
46
-
47
- function normalizeObservedUser(
48
- input: Partial<ChatAuthorizationObservedUser> | null | undefined,
49
- ): ChatAuthorizationObservedUser | null {
50
- if (!input || typeof input !== "object") return null;
51
- const channel = normalizeText(input.channel) as ChatDispatchChannel | undefined;
52
- const userId = normalizeText(input.userId);
53
- if (!channel || !userId) return null;
54
- return {
55
- v: 1,
56
- channel,
57
- userId,
58
- ...(normalizeText(input.username) ? { username: normalizeText(input.username) } : {}),
59
- ...(normalizeText(input.lastChatId) ? { lastChatId: normalizeText(input.lastChatId) } : {}),
60
- ...(normalizeText(input.lastChatTitle)
61
- ? { lastChatTitle: normalizeText(input.lastChatTitle) }
62
- : {}),
63
- ...(normalizeText(input.lastChatType)
64
- ? { lastChatType: normalizeText(input.lastChatType) }
65
- : {}),
66
- firstSeenAt:
67
- typeof input.firstSeenAt === "number" && Number.isFinite(input.firstSeenAt)
68
- ? input.firstSeenAt
69
- : Date.now(),
70
- lastSeenAt:
71
- typeof input.lastSeenAt === "number" && Number.isFinite(input.lastSeenAt)
72
- ? input.lastSeenAt
73
- : Date.now(),
74
- };
75
- }
76
-
77
- function normalizeObservedChat(
78
- input: Partial<ChatAuthorizationObservedChat> | null | undefined,
79
- ): ChatAuthorizationObservedChat | null {
80
- if (!input || typeof input !== "object") return null;
81
- const channel = normalizeText(input.channel) as ChatDispatchChannel | undefined;
82
- const chatId = normalizeText(input.chatId);
83
- if (!channel || !chatId) return null;
84
- return {
85
- v: 1,
86
- channel,
87
- chatId,
88
- ...(normalizeText(input.chatTitle) ? { chatTitle: normalizeText(input.chatTitle) } : {}),
89
- ...(normalizeText(input.chatType) ? { chatType: normalizeText(input.chatType) } : {}),
90
- ...(normalizeText(input.lastActorId)
91
- ? { lastActorId: normalizeText(input.lastActorId) }
92
- : {}),
93
- ...(normalizeText(input.lastActorName)
94
- ? { lastActorName: normalizeText(input.lastActorName) }
95
- : {}),
96
- firstSeenAt:
97
- typeof input.firstSeenAt === "number" && Number.isFinite(input.firstSeenAt)
98
- ? input.firstSeenAt
99
- : Date.now(),
100
- lastSeenAt:
101
- typeof input.lastSeenAt === "number" && Number.isFinite(input.lastSeenAt)
102
- ? input.lastSeenAt
103
- : Date.now(),
104
- };
105
- }
106
-
107
- function normalizeStateFile(
108
- input: Partial<ChatAuthorizationStateFile> | null | undefined,
109
- ): ChatAuthorizationStateFile {
110
- const out = createEmptyState();
111
- out.updatedAt =
112
- typeof input?.updatedAt === "number" && Number.isFinite(input.updatedAt)
113
- ? input.updatedAt
114
- : Date.now();
115
-
116
- const users = input?.usersByKey;
117
- if (users && typeof users === "object") {
118
- for (const [key, value] of Object.entries(users)) {
119
- const normalized = normalizeObservedUser(value);
120
- if (!normalized) continue;
121
- out.usersByKey[key] = normalized;
122
- }
123
- }
124
-
125
- const chats = input?.chatsByKey;
126
- if (chats && typeof chats === "object") {
127
- for (const [key, value] of Object.entries(chats)) {
128
- const normalized = normalizeObservedChat(value);
129
- if (!normalized) continue;
130
- out.chatsByKey[key] = normalized;
131
- }
132
- }
133
- return out;
134
- }
135
-
136
- async function readState(rootPath: string): Promise<ChatAuthorizationStateFile> {
137
- const file = getAuthorizationStatePath(rootPath);
138
- const raw = (await fs.readJson(file).catch(() => null)) as Partial<ChatAuthorizationStateFile> | null;
139
- return normalizeStateFile(raw);
140
- }
141
-
142
- async function writeState(rootPath: string, state: ChatAuthorizationStateFile): Promise<void> {
143
- const file = getAuthorizationStatePath(rootPath);
144
- await fs.ensureDir(path.dirname(file));
145
- await fs.writeJson(file, state, { spaces: 2 });
146
- }
147
-
148
- function readAuthorizationConfig(
149
- context: Pick<AgentContext, "rootPath">,
150
- ): ChatAuthorizationSnapshot["config"] {
151
- return readChatAuthorizationConfig(context.rootPath);
152
- }
153
-
154
- /**
155
- * 记录观测到的用户 / 会话。
156
- */
157
- export async function recordObservedAuthorizationPrincipal(params: {
158
- context: Pick<AgentContext, "rootPath">;
159
- channel: ChatDispatchChannel;
160
- chatId: string;
161
- chatType?: string;
162
- chatTitle?: string;
163
- userId?: string;
164
- username?: string;
165
- }): Promise<void> {
166
- const chatId = normalizeText(params.chatId);
167
- if (!chatId) return;
168
- const now = Date.now();
169
- const state = await readState(params.context.rootPath);
170
-
171
- const chatKey = buildChatKey(params.channel, chatId);
172
- const prevChat = state.chatsByKey[chatKey];
173
- state.chatsByKey[chatKey] = {
174
- v: 1,
175
- channel: params.channel,
176
- chatId,
177
- ...(normalizeText(params.chatTitle) ? { chatTitle: normalizeText(params.chatTitle) } : {}),
178
- ...(normalizeText(params.chatType) ? { chatType: normalizeText(params.chatType) } : {}),
179
- ...(normalizeText(params.userId) ? { lastActorId: normalizeText(params.userId) } : {}),
180
- ...(normalizeText(params.username) ? { lastActorName: normalizeText(params.username) } : {}),
181
- firstSeenAt: prevChat?.firstSeenAt ?? now,
182
- lastSeenAt: now,
183
- };
184
-
185
- const userId = normalizeText(params.userId);
186
- if (userId) {
187
- const userKey = buildUserKey(params.channel, userId);
188
- const prevUser = state.usersByKey[userKey];
189
- state.usersByKey[userKey] = {
190
- v: 1,
191
- channel: params.channel,
192
- userId,
193
- ...(normalizeText(params.username) ? { username: normalizeText(params.username) } : {}),
194
- lastChatId: chatId,
195
- ...(normalizeText(params.chatTitle)
196
- ? { lastChatTitle: normalizeText(params.chatTitle) }
197
- : {}),
198
- ...(normalizeText(params.chatType) ? { lastChatType: normalizeText(params.chatType) } : {}),
199
- firstSeenAt: prevUser?.firstSeenAt ?? now,
200
- lastSeenAt: now,
201
- };
202
- }
203
-
204
- state.updatedAt = now;
205
- await writeState(params.context.rootPath, state);
206
- }
207
-
208
- /**
209
- * 读取授权快照(配置 + 动态状态)。
210
- */
211
- export async function readAuthorizationSnapshot(params: {
212
- context: Pick<AgentContext, "rootPath">;
213
- }): Promise<ChatAuthorizationSnapshot> {
214
- const state = await readState(params.context.rootPath);
215
- return {
216
- config: readAuthorizationConfig(params.context),
217
- users: Object.values(state.usersByKey).sort((a, b) => b.lastSeenAt - a.lastSeenAt),
218
- chats: Object.values(state.chatsByKey).sort((a, b) => b.lastSeenAt - a.lastSeenAt),
219
- };
220
- }