@downcity/plugins 1.0.57 → 1.0.60

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 (145) hide show
  1. package/bin/BuiltinPlugins.d.ts +24 -2
  2. package/bin/BuiltinPlugins.d.ts.map +1 -1
  3. package/bin/BuiltinPlugins.js +18 -5
  4. package/bin/BuiltinPlugins.js.map +1 -1
  5. package/bin/auth/types/AuthPlugin.d.ts +12 -16
  6. package/bin/auth/types/AuthPlugin.d.ts.map +1 -1
  7. package/bin/auth/types/AuthPlugin.js +9 -13
  8. package/bin/auth/types/AuthPlugin.js.map +1 -1
  9. package/bin/chat/ChatPlugin.d.ts +37 -6
  10. package/bin/chat/ChatPlugin.d.ts.map +1 -1
  11. package/bin/chat/ChatPlugin.js +58 -80
  12. package/bin/chat/ChatPlugin.js.map +1 -1
  13. package/bin/chat/Index.d.ts +4 -1
  14. package/bin/chat/Index.d.ts.map +1 -1
  15. package/bin/chat/Index.js +2 -1
  16. package/bin/chat/Index.js.map +1 -1
  17. package/bin/chat/channels/Configuration.d.ts +1 -1
  18. package/bin/chat/channels/Configuration.js +1 -1
  19. package/bin/chat/channels/RuntimeChannel.d.ts +145 -0
  20. package/bin/chat/channels/RuntimeChannel.d.ts.map +1 -0
  21. package/bin/chat/channels/RuntimeChannel.js +148 -0
  22. package/bin/chat/channels/RuntimeChannel.js.map +1 -0
  23. package/bin/chat/runtime/ChatAuthorizationRuntime.d.ts +22 -0
  24. package/bin/chat/runtime/ChatAuthorizationRuntime.d.ts.map +1 -0
  25. package/bin/chat/runtime/ChatAuthorizationRuntime.js +189 -0
  26. package/bin/chat/runtime/ChatAuthorizationRuntime.js.map +1 -0
  27. package/bin/chat/runtime/ChatChannelActions.d.ts.map +1 -1
  28. package/bin/chat/runtime/ChatChannelActions.js +19 -9
  29. package/bin/chat/runtime/ChatChannelActions.js.map +1 -1
  30. package/bin/chat/runtime/ChatChannelConfig.d.ts +2 -18
  31. package/bin/chat/runtime/ChatChannelConfig.d.ts.map +1 -1
  32. package/bin/chat/runtime/ChatChannelConfig.js +2 -73
  33. package/bin/chat/runtime/ChatChannelConfig.js.map +1 -1
  34. package/bin/chat/runtime/ChatChannelCore.d.ts +22 -1
  35. package/bin/chat/runtime/ChatChannelCore.d.ts.map +1 -1
  36. package/bin/chat/runtime/ChatChannelCore.js +5 -5
  37. package/bin/chat/runtime/ChatChannelCore.js.map +1 -1
  38. package/bin/chat/runtime/ChatPluginActionRegistry.js +1 -1
  39. package/bin/chat/runtime/ChatPluginActionRegistry.js.map +1 -1
  40. package/bin/chat/runtime/PluginPoints.d.ts +3 -3
  41. package/bin/chat/runtime/PluginPoints.js +3 -3
  42. package/bin/chat/types/ChannelStatus.d.ts +1 -1
  43. package/bin/chat/types/ChatPluginOptions.d.ts +76 -0
  44. package/bin/chat/types/ChatPluginOptions.d.ts.map +1 -0
  45. package/bin/chat/types/ChatPluginOptions.js +10 -0
  46. package/bin/chat/types/ChatPluginOptions.js.map +1 -0
  47. package/bin/contact/ContactPlugin.d.ts +6 -1
  48. package/bin/contact/ContactPlugin.d.ts.map +1 -1
  49. package/bin/contact/ContactPlugin.js +9 -8
  50. package/bin/contact/ContactPlugin.js.map +1 -1
  51. package/bin/contact/types/ContactPluginOptions.d.ts +29 -0
  52. package/bin/contact/types/ContactPluginOptions.d.ts.map +1 -0
  53. package/bin/contact/types/ContactPluginOptions.js +9 -0
  54. package/bin/contact/types/ContactPluginOptions.js.map +1 -0
  55. package/bin/index.d.ts +7 -4
  56. package/bin/index.d.ts.map +1 -1
  57. package/bin/index.js +3 -2
  58. package/bin/index.js.map +1 -1
  59. package/bin/skill/Action.d.ts.map +1 -1
  60. package/bin/skill/Action.js +2 -2
  61. package/bin/skill/Action.js.map +1 -1
  62. package/bin/skill/Command.js +5 -5
  63. package/bin/skill/Command.js.map +1 -1
  64. package/bin/skill/Plugin.js +1 -1
  65. package/bin/skill/Plugin.js.map +1 -1
  66. package/bin/skill/runtime/Discovery.d.ts +4 -4
  67. package/bin/skill/runtime/Discovery.d.ts.map +1 -1
  68. package/bin/skill/runtime/Discovery.js +5 -5
  69. package/bin/skill/runtime/Discovery.js.map +1 -1
  70. package/bin/skill/runtime/Paths.d.ts +1 -5
  71. package/bin/skill/runtime/Paths.d.ts.map +1 -1
  72. package/bin/skill/runtime/Paths.js +1 -9
  73. package/bin/skill/runtime/Paths.js.map +1 -1
  74. package/bin/skill/runtime/Prompt.d.ts +2 -2
  75. package/bin/skill/runtime/Prompt.d.ts.map +1 -1
  76. package/bin/skill/runtime/Prompt.js +3 -3
  77. package/bin/skill/runtime/Prompt.js.map +1 -1
  78. package/bin/skill/runtime/Store.d.ts +2 -2
  79. package/bin/skill/runtime/Store.d.ts.map +1 -1
  80. package/bin/skill/runtime/Store.js.map +1 -1
  81. package/bin/skill/runtime/SystemProvider.js +4 -4
  82. package/bin/skill/runtime/SystemProvider.js.map +1 -1
  83. package/bin/skill/runtime/Types.d.ts +3 -3
  84. package/bin/skill/runtime/Types.d.ts.map +1 -1
  85. package/bin/skill/types/{ClaudeSkill.d.ts → SkillDefinition.d.ts} +4 -4
  86. package/bin/skill/types/SkillDefinition.d.ts.map +1 -0
  87. package/bin/skill/types/{ClaudeSkill.js → SkillDefinition.js} +2 -2
  88. package/bin/skill/types/SkillDefinition.js.map +1 -0
  89. package/bin/skill/types/SkillPlugin.d.ts +2 -2
  90. package/bin/skill/types/SkillPlugin.d.ts.map +1 -1
  91. package/bin/web/Plugin.js +1 -1
  92. package/bin/web/Plugin.js.map +1 -1
  93. package/bin/web/runtime/Install.js +2 -2
  94. package/bin/web/runtime/Install.js.map +1 -1
  95. package/bin/web/types/WebPlugin.d.ts +2 -1
  96. package/bin/web/types/WebPlugin.d.ts.map +1 -1
  97. package/bin/workboard/Plugin.d.ts +23 -3
  98. package/bin/workboard/Plugin.d.ts.map +1 -1
  99. package/bin/workboard/Plugin.js +66 -85
  100. package/bin/workboard/Plugin.js.map +1 -1
  101. package/package.json +2 -2
  102. package/src/BuiltinPlugins.ts +41 -5
  103. package/src/auth/types/AuthPlugin.ts +12 -17
  104. package/src/chat/ChatPlugin.ts +92 -89
  105. package/src/chat/Index.ts +18 -1
  106. package/src/chat/channels/Configuration.ts +1 -1
  107. package/src/chat/channels/RuntimeChannel.ts +264 -0
  108. package/src/chat/runtime/ChatAuthorizationRuntime.ts +229 -0
  109. package/src/chat/runtime/ChatChannelActions.ts +24 -9
  110. package/src/chat/runtime/ChatChannelConfig.ts +2 -100
  111. package/src/chat/runtime/ChatChannelCore.ts +20 -8
  112. package/src/chat/runtime/ChatPluginActionRegistry.ts +1 -1
  113. package/src/chat/runtime/PluginPoints.ts +3 -3
  114. package/src/chat/types/ChannelStatus.ts +1 -1
  115. package/src/chat/types/ChatPluginOptions.ts +79 -0
  116. package/src/contact/ContactPlugin.ts +17 -9
  117. package/src/contact/types/ContactPluginOptions.ts +30 -0
  118. package/src/index.ts +23 -7
  119. package/src/skill/Action.ts +10 -7
  120. package/src/skill/Command.ts +5 -5
  121. package/src/skill/Plugin.ts +1 -1
  122. package/src/skill/runtime/Discovery.ts +14 -11
  123. package/src/skill/runtime/Paths.ts +1 -13
  124. package/src/skill/runtime/Prompt.ts +5 -5
  125. package/src/skill/runtime/Store.ts +6 -3
  126. package/src/skill/runtime/SystemProvider.ts +4 -4
  127. package/src/skill/runtime/Types.ts +3 -3
  128. package/src/skill/types/{ClaudeSkill.ts → SkillDefinition.ts} +3 -3
  129. package/src/skill/types/SkillPlugin.ts +2 -2
  130. package/src/web/Plugin.ts +1 -1
  131. package/src/web/runtime/Install.ts +3 -3
  132. package/src/web/types/WebPlugin.ts +2 -2
  133. package/src/workboard/Plugin.ts +80 -95
  134. package/bin/auth/Plugin.d.ts +0 -17
  135. package/bin/auth/Plugin.d.ts.map +0 -1
  136. package/bin/auth/Plugin.js +0 -199
  137. package/bin/auth/Plugin.js.map +0 -1
  138. package/bin/chat/ChatPluginTypes.d.ts +0 -122
  139. package/bin/chat/ChatPluginTypes.d.ts.map +0 -1
  140. package/bin/chat/ChatPluginTypes.js +0 -10
  141. package/bin/chat/ChatPluginTypes.js.map +0 -1
  142. package/bin/skill/types/ClaudeSkill.d.ts.map +0 -1
  143. package/bin/skill/types/ClaudeSkill.js.map +0 -1
  144. package/src/auth/Plugin.ts +0 -237
  145. package/src/chat/ChatPluginTypes.ts +0 -126
@@ -0,0 +1,229 @@
1
+ /**
2
+ * ChatAuthorizationRuntime:ChatPlugin 内置授权能力。
3
+ *
4
+ * 关键点(中文)
5
+ * - 授权能力归属于 ChatPlugin,不再作为独立 plugin 注册。
6
+ * - chat runtime 仍通过通用 plugin hook / resolve 点调用,保持执行链路统一。
7
+ * - action 名称统一带 `authorization-` 前缀,避免和 chat 会话动作混淆。
8
+ */
9
+
10
+ import type {
11
+ PluginActions,
12
+ PluginHooks,
13
+ PluginResolves,
14
+ } from "@downcity/agent/internal/plugin/types/Plugin.js";
15
+ import type { JsonValue } from "@downcity/agent/internal/types/common/Json.js";
16
+ import { CHAT_PLUGIN_POINTS } from "@/chat/runtime/PluginPoints.js";
17
+ import type { ChatDispatchChannel } from "@/chat/types/ChatDispatcher.js";
18
+ import {
19
+ CHAT_AUTHORIZATION_ACTIONS,
20
+ CHAT_AUTHORIZATION_CATALOG,
21
+ type ChatAuthorizationConfig,
22
+ type ChatAuthorizationEvaluateInput,
23
+ type ChatAuthorizationObservePrincipalPayload,
24
+ type ChatAuthorizationSetUserRolePayload,
25
+ type ChatAuthorizationSnapshot,
26
+ type ChatAuthorizationWriteConfigPayload,
27
+ } from "@/auth/types/AuthPlugin.js";
28
+ import {
29
+ readChatAuthorizationConfig,
30
+ setChatAuthorizationUserRole,
31
+ writeChatAuthorizationConfig,
32
+ } from "@/auth/runtime/AuthorizationConfig.js";
33
+ import {
34
+ evaluateIncomingChatAuthorization,
35
+ resolveAuthorizedUserRole,
36
+ } from "@/auth/runtime/AuthorizationPolicy.js";
37
+ import {
38
+ readAuthorizationSnapshot,
39
+ recordObservedAuthorizationPrincipal,
40
+ } from "@/auth/runtime/AuthorizationStore.js";
41
+
42
+ function toChannel(value: unknown): ChatDispatchChannel | null {
43
+ const text = String(value || "").trim().toLowerCase();
44
+ if (text === "telegram" || text === "feishu" || text === "qq") return text;
45
+ return null;
46
+ }
47
+
48
+ function toRecord(value: unknown): Record<string, unknown> {
49
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
50
+ return value as Record<string, unknown>;
51
+ }
52
+
53
+ function toEvaluateInput(payload: Record<string, unknown>): ChatAuthorizationEvaluateInput {
54
+ const channel = toChannel(payload.channel);
55
+ if (!channel) {
56
+ throw new Error("chat.authorizeIncoming requires a valid channel");
57
+ }
58
+ return {
59
+ channel,
60
+ chatId: String(payload.chatId || "").trim(),
61
+ ...(typeof payload.chatType === "string" ? { chatType: payload.chatType.trim() } : {}),
62
+ ...(typeof payload.userId === "string" ? { userId: payload.userId.trim() } : {}),
63
+ ...(typeof payload.username === "string" ? { username: payload.username.trim() } : {}),
64
+ ...(typeof payload.chatTitle === "string" ? { chatTitle: payload.chatTitle.trim() } : {}),
65
+ };
66
+ }
67
+
68
+ function toSnapshotData(snapshot: ChatAuthorizationSnapshot): JsonValue {
69
+ return {
70
+ catalog: CHAT_AUTHORIZATION_CATALOG as unknown as JsonValue,
71
+ config: snapshot.config as unknown as JsonValue,
72
+ users: snapshot.users as unknown as JsonValue,
73
+ chats: snapshot.chats as unknown as JsonValue,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * 创建 ChatPlugin 内置授权 hooks。
79
+ */
80
+ export function createChatAuthorizationHooks(): PluginHooks {
81
+ return {
82
+ guard: {
83
+ [CHAT_PLUGIN_POINTS.authorizeIncoming]: [
84
+ async ({ context, value }) => {
85
+ const input =
86
+ value && typeof value === "object" && !Array.isArray(value)
87
+ ? (value as Record<string, unknown>)
88
+ : {};
89
+ const evaluateInput = toEvaluateInput(input);
90
+ const authorizationConfig = readChatAuthorizationConfig(context);
91
+ const result = evaluateIncomingChatAuthorization({
92
+ config: context.config,
93
+ channel: evaluateInput.channel,
94
+ input: evaluateInput,
95
+ authorizationConfig,
96
+ });
97
+ if (result.decision !== "allow") {
98
+ throw new Error(result.reason || "chat authorization blocked");
99
+ }
100
+ },
101
+ ],
102
+ },
103
+ effect: {
104
+ [CHAT_PLUGIN_POINTS.observePrincipal]: [
105
+ async ({ context, value }) => {
106
+ const input = toRecord(
107
+ value,
108
+ ) as unknown as ChatAuthorizationObservePrincipalPayload;
109
+ const channel = toChannel(input.channel);
110
+ if (!channel) {
111
+ throw new Error("chat.observePrincipal requires a valid channel");
112
+ }
113
+ await recordObservedAuthorizationPrincipal({
114
+ context,
115
+ channel,
116
+ chatId: String(input.chatId || "").trim(),
117
+ ...(typeof input.chatType === "string"
118
+ ? { chatType: input.chatType.trim() }
119
+ : {}),
120
+ ...(typeof input.chatTitle === "string"
121
+ ? { chatTitle: input.chatTitle.trim() }
122
+ : {}),
123
+ ...(typeof input.userId === "string"
124
+ ? { userId: input.userId.trim() }
125
+ : {}),
126
+ ...(typeof input.username === "string"
127
+ ? { username: input.username.trim() }
128
+ : {}),
129
+ });
130
+ },
131
+ ],
132
+ },
133
+ };
134
+ }
135
+
136
+ /**
137
+ * 创建 ChatPlugin 内置授权 resolve 点。
138
+ */
139
+ export function createChatAuthorizationResolves(): PluginResolves {
140
+ return {
141
+ [CHAT_PLUGIN_POINTS.resolveUserRole]: async ({ context, value }) => {
142
+ const input =
143
+ value && typeof value === "object" && !Array.isArray(value)
144
+ ? (value as Record<string, unknown>)
145
+ : {};
146
+ const channel = toChannel(input.channel);
147
+ if (!channel) {
148
+ throw new Error("chat.resolveUserRole requires a valid channel");
149
+ }
150
+ const role = resolveAuthorizedUserRole({
151
+ channel,
152
+ userId: String(input.userId || "").trim(),
153
+ rootPath: context.rootPath,
154
+ });
155
+ return ((role || null) as unknown) as JsonValue;
156
+ },
157
+ };
158
+ }
159
+
160
+ /**
161
+ * 创建 ChatPlugin 内置授权 actions。
162
+ */
163
+ export function createChatAuthorizationActions(): PluginActions {
164
+ return {
165
+ [CHAT_AUTHORIZATION_ACTIONS.snapshot]: {
166
+ execute: async ({ context }) => {
167
+ const snapshot = await readAuthorizationSnapshot({
168
+ context,
169
+ });
170
+ return {
171
+ success: true,
172
+ data: toSnapshotData(snapshot),
173
+ };
174
+ },
175
+ },
176
+ [CHAT_AUTHORIZATION_ACTIONS.readConfig]: {
177
+ execute: async ({ context }) => {
178
+ return {
179
+ success: true,
180
+ data: readChatAuthorizationConfig(context) as unknown as JsonValue,
181
+ };
182
+ },
183
+ },
184
+ [CHAT_AUTHORIZATION_ACTIONS.writeConfig]: {
185
+ execute: async ({ context, payload }) => {
186
+ const body = toRecord(
187
+ payload,
188
+ ) as unknown as ChatAuthorizationWriteConfigPayload;
189
+ const nextConfig =
190
+ body.config && typeof body.config === "object" && !Array.isArray(body.config)
191
+ ? (body.config as ChatAuthorizationConfig)
192
+ : {};
193
+ await writeChatAuthorizationConfig({
194
+ context,
195
+ nextConfig,
196
+ });
197
+ return {
198
+ success: true,
199
+ data: readChatAuthorizationConfig(context) as unknown as JsonValue,
200
+ };
201
+ },
202
+ },
203
+ [CHAT_AUTHORIZATION_ACTIONS.setUserRole]: {
204
+ execute: async ({ context, payload }) => {
205
+ const body = toRecord(
206
+ payload,
207
+ ) as unknown as ChatAuthorizationSetUserRolePayload;
208
+ const channel = toChannel(body.channel);
209
+ if (!channel) {
210
+ return {
211
+ success: false,
212
+ error: "authorization-set-user-role requires a valid channel",
213
+ message: "authorization-set-user-role requires a valid channel",
214
+ };
215
+ }
216
+ await setChatAuthorizationUserRole({
217
+ context,
218
+ channel,
219
+ userId: String(body.userId || "").trim(),
220
+ roleId: String(body.roleId || "").trim(),
221
+ });
222
+ return {
223
+ success: true,
224
+ data: readChatAuthorizationConfig(context) as unknown as JsonValue,
225
+ };
226
+ },
227
+ },
228
+ };
229
+ }
@@ -25,11 +25,11 @@ import {
25
25
  getChatChannelStatus,
26
26
  listChatChannelConfigurationDescriptions,
27
27
  normalizeChatChannelConfigPatch,
28
- setChatChannelConfig,
29
- setChatChannelEnabled,
30
28
  } from "./ChatChannelConfig.js";
31
29
  import {
30
+ type ChatRuntimeBindings,
32
31
  getChatChannelBot,
32
+ resolveChatPluginBindings,
33
33
  resolveTargetChannels,
34
34
  } from "./ChatChannelCore.js";
35
35
  import {
@@ -37,6 +37,18 @@ import {
37
37
  stopSingleChatChannel,
38
38
  } from "./ChatChannelLifecycle.js";
39
39
 
40
+ type ChatRuntimeControlBindings = ChatRuntimeBindings & {
41
+ applyChannelRuntimePatch: NonNullable<ChatRuntimeBindings["applyChannelRuntimePatch"]>;
42
+ };
43
+
44
+ function getChatRuntimeBindings(context: AgentContext): ChatRuntimeControlBindings {
45
+ const plugin = resolveChatPluginBindings(context);
46
+ if (!plugin?.applyChannelRuntimePatch) {
47
+ throw new Error("ChatPlugin runtime instance is not available");
48
+ }
49
+ return plugin as ChatRuntimeControlBindings;
50
+ }
51
+
40
52
  /**
41
53
  * 执行 `chat.status` action。
42
54
  */
@@ -163,9 +175,9 @@ export async function executeChatOpenAction(params: {
163
175
  payload: ChatOpenActionPayload;
164
176
  }) {
165
177
  const targets = resolveTargetChannels(params.payload.channel);
178
+ const plugin = getChatRuntimeBindings(params.context);
166
179
  for (const channel of targets) {
167
- await setChatChannelEnabled({
168
- context: params.context,
180
+ plugin.applyChannelRuntimePatch({
169
181
  channel,
170
182
  enabled: true,
171
183
  });
@@ -198,10 +210,10 @@ export async function executeChatCloseAction(params: {
198
210
  payload: ChatCloseActionPayload;
199
211
  }) {
200
212
  const targets = resolveTargetChannels(params.payload.channel);
213
+ const plugin = getChatRuntimeBindings(params.context);
201
214
  for (const channel of targets) {
202
215
  await stopSingleChatChannel(params.state, channel);
203
- await setChatChannelEnabled({
204
- context: params.context,
216
+ plugin.applyChannelRuntimePatch({
205
217
  channel,
206
218
  enabled: false,
207
219
  });
@@ -280,10 +292,13 @@ export async function executeChatConfigureAction(params: {
280
292
  }
281
293
  }
282
294
 
283
- await setChatChannelConfig({
284
- context: params.context,
295
+ const plugin = getChatRuntimeBindings(params.context);
296
+ plugin.applyChannelRuntimePatch({
285
297
  channel,
286
- patch,
298
+ ...(typeof patch.enabled === "boolean" ? { enabled: patch.enabled } : {}),
299
+ ...(Object.prototype.hasOwnProperty.call(patch, "channelAccountId")
300
+ ? { channelAccountId: String(patch.channelAccountId || "").trim() || null }
301
+ : {}),
287
302
  });
288
303
 
289
304
  // 关键点(中文):默认重载一次目标渠道,让新配置立刻生效。
@@ -2,13 +2,11 @@
2
2
  * ChatChannelConfig:chat 渠道配置与状态快照模块。
3
3
  *
4
4
  * 关键点(中文)
5
- * - 渠道配置摘要、状态快照、patch 归一化、downcity.json 落盘都收敛在这里。
6
- * - 所有配置写入都遵循“先改内存,再落盘”的一致性顺序。
5
+ * - 渠道配置摘要、状态快照、patch 归一化都收敛在这里。
6
+ * - chat.configure 的运行态写入由 ChatPlugin 实例承载,不再落盘到 downcity.json。
7
7
  * - 该模块不直接负责 action 流程控制,只提供可复用的底层能力。
8
8
  */
9
9
 
10
- import fs from "node:fs/promises";
11
- import path from "node:path";
12
10
  import type { JsonObject, JsonValue } from "@downcity/agent/internal/types/common/Json.js";
13
11
  import type { AgentContext } from "@downcity/agent/internal/types/runtime/agent/AgentContext.js";
14
12
  import type { StoredChannelAccount } from "@downcity/agent/internal/types/platform/Store.js";
@@ -109,47 +107,6 @@ export function getChatChannelStatus(
109
107
  };
110
108
  }
111
109
 
112
- /**
113
- * 更新内存配置与 downcity.json 中的 channel enabled 状态。
114
- */
115
- export async function setChatChannelEnabled(params: {
116
- context: AgentContext;
117
- channel: ChatChannelName;
118
- enabled: boolean;
119
- }): Promise<void> {
120
- const { context, channel, enabled } = params;
121
-
122
- const configPlugins = ((context.config.plugins ??= {}) as {
123
- chat?: {
124
- channels?: Record<string, Record<string, unknown>>;
125
- };
126
- });
127
- const chatConfig = (configPlugins.chat ??= {});
128
- const channelConfigs = (chatConfig.channels ??= {});
129
- const channelConfig = (channelConfigs[channel] ??= {});
130
- channelConfig.enabled = enabled;
131
-
132
- const shipPath = path.join(context.rootPath, "downcity.json");
133
- let shipJson: Record<string, unknown> = {};
134
- try {
135
- const raw = await fs.readFile(shipPath, "utf-8");
136
- const parsed = JSON.parse(raw) as unknown;
137
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
138
- shipJson = parsed as Record<string, unknown>;
139
- }
140
- } catch {
141
- shipJson = {};
142
- }
143
-
144
- const shipPlugins = ((shipJson.plugins ??= {}) as Record<string, unknown>);
145
- const shipChat = ((shipPlugins.chat ??= {}) as Record<string, unknown>);
146
- const shipChannels = ((shipChat.channels ??= {}) as Record<string, unknown>);
147
- const shipChannel = ((shipChannels[channel] ??= {}) as Record<string, unknown>);
148
- shipChannel.enabled = enabled;
149
-
150
- await fs.writeFile(shipPath, `${JSON.stringify(shipJson, null, 2)}\n`, "utf-8");
151
- }
152
-
153
110
  function isJsonObject(value: JsonValue): value is JsonObject {
154
111
  return !!value && typeof value === "object" && !Array.isArray(value);
155
112
  }
@@ -253,61 +210,6 @@ export function normalizeChatChannelConfigPatch(params: {
253
210
  return patch;
254
211
  }
255
212
 
256
- function applyChannelPatch(
257
- target: Record<string, unknown>,
258
- patch: Record<string, string | number | boolean | null>,
259
- ): void {
260
- for (const [key, value] of Object.entries(patch)) {
261
- if (value === null) {
262
- delete target[key];
263
- continue;
264
- }
265
- target[key] = value;
266
- }
267
- }
268
-
269
- /**
270
- * 更新单个 channel 配置(内存 + downcity.json)。
271
- */
272
- export async function setChatChannelConfig(params: {
273
- context: AgentContext;
274
- channel: ChatChannelName;
275
- patch: Record<string, string | number | boolean | null>;
276
- }): Promise<void> {
277
- const { context, channel, patch } = params;
278
- if (Object.keys(patch).length === 0) return;
279
-
280
- const configPlugins = ((context.config.plugins ??= {}) as {
281
- chat?: {
282
- channels?: Record<string, Record<string, unknown>>;
283
- };
284
- });
285
- const chatConfig = (configPlugins.chat ??= {});
286
- const channelConfigs = (chatConfig.channels ??= {});
287
- const channelConfig = (channelConfigs[channel] ??= {});
288
- applyChannelPatch(channelConfig, patch);
289
-
290
- const shipPath = path.join(context.rootPath, "downcity.json");
291
- let shipJson: Record<string, unknown> = {};
292
- try {
293
- const raw = await fs.readFile(shipPath, "utf-8");
294
- const parsed = JSON.parse(raw) as unknown;
295
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
296
- shipJson = parsed as Record<string, unknown>;
297
- }
298
- } catch {
299
- shipJson = {};
300
- }
301
-
302
- const shipPlugins = ((shipJson.plugins ??= {}) as Record<string, unknown>);
303
- const shipChat = ((shipPlugins.chat ??= {}) as Record<string, unknown>);
304
- const shipChannels = ((shipChat.channels ??= {}) as Record<string, unknown>);
305
- const shipChannel = ((shipChannels[channel] ??= {}) as Record<string, unknown>);
306
- applyChannelPatch(shipChannel, patch);
307
-
308
- await fs.writeFile(shipPath, `${JSON.stringify(shipJson, null, 2)}\n`, "utf-8");
309
- }
310
-
311
213
  /**
312
214
  * 读取渠道 configuration 描述。
313
215
  */
@@ -15,16 +15,30 @@ import { getStoredChannelAccountSync } from "@/chat/accounts/Store.js";
15
15
 
16
16
  const CHAT_CHANNEL_NAMES: ChatChannelName[] = ["telegram", "feishu", "qq"];
17
17
 
18
- type ChatRuntimeBindings = {
18
+ export type ChatRuntimeBindings = {
19
19
  getChannelAccountId?(context: AgentContext, channel: ChatChannelName): string;
20
20
  resolveChannelAccount?(
21
21
  context: AgentContext,
22
22
  channel: ChatChannelName,
23
23
  ): StoredChannelAccount | null;
24
24
  isChannelEnabled?(context: AgentContext, channel: ChatChannelName): boolean;
25
+ applyChannelRuntimePatch?(params: {
26
+ /**
27
+ * 目标渠道。
28
+ */
29
+ channel: ChatChannelName;
30
+ /**
31
+ * 是否启用该渠道。
32
+ */
33
+ enabled?: boolean;
34
+ /**
35
+ * 绑定的账号池记录 ID;传入 null 表示清空绑定。
36
+ */
37
+ channelAccountId?: string | null;
38
+ }): void;
25
39
  };
26
40
 
27
- function resolveChatPluginBindings(
41
+ export function resolveChatPluginBindings(
28
42
  context: AgentContext,
29
43
  ): ChatRuntimeBindings | null {
30
44
  const candidate = context.agent?.pluginInstances?.get?.("chat") as
@@ -78,10 +92,7 @@ export function resolveChannelAccountId(
78
92
  const plugin = resolveChatPluginBindings(context);
79
93
  const explicit = String(plugin?.getChannelAccountId?.(context, channel) || "").trim();
80
94
  if (explicit) return explicit;
81
- const config = context.config.plugins?.chat?.channels?.[channel] as
82
- | { channelAccountId?: unknown }
83
- | undefined;
84
- return String(config?.channelAccountId || "").trim();
95
+ return "";
85
96
  }
86
97
 
87
98
  /**
@@ -89,7 +100,8 @@ export function resolveChannelAccountId(
89
100
  *
90
101
  * 关键点(中文)
91
102
  * - 优先使用 ChatPlugin 实例上的显式解析逻辑。
92
- * - 若未命中,再回退到默认全局账号池 `~/.downcity/downcity.db`。
103
+ * - 若实例只提供 channelAccountId,再从默认全局账号池读取对应账号。
104
+ * - 不再从 downcity.json 隐式推断运行时账号。
93
105
  */
94
106
  export function resolveChannelAccount(
95
107
  context: AgentContext,
@@ -132,7 +144,7 @@ export function isChatChannelEnabled(
132
144
  if (typeof plugin?.isChannelEnabled === "function") {
133
145
  return plugin.isChannelEnabled(context, channel);
134
146
  }
135
- return context.config.plugins?.chat?.channels?.[channel]?.enabled === true;
147
+ return false;
136
148
  }
137
149
 
138
150
  /**
@@ -198,7 +198,7 @@ export function createChatPluginActions(params: {
198
198
  },
199
199
  configure: {
200
200
  command: {
201
- description: "更新 chat 渠道参数(写入 downcity.json,可选立即重载)",
201
+ description: "更新 chat 渠道运行态参数(可选立即重载)",
202
202
  configure(command: Command) {
203
203
  command
204
204
  .requiredOption("--channel <name>", "指定渠道(telegram|feishu|qq)")
@@ -56,7 +56,7 @@ export const CHAT_PLUGIN_POINTS = {
56
56
  *
57
57
  * 说明(中文)
58
58
  * - 仅做副作用记录,不返回值。
59
- * - 典型实现方是 chat-authorization plugin。
59
+ * - ChatPlugin 内置授权能力会使用该点记录主体快照。
60
60
  */
61
61
  observePrincipal: "chat.observePrincipal",
62
62
  /**
@@ -64,7 +64,7 @@ export const CHAT_PLUGIN_POINTS = {
64
64
  *
65
65
  * 说明(中文)
66
66
  * - 由 chat plugin runtime 在 ingress 阶段显式调用。
67
- * - 典型实现方是 chat-authorization plugin。
67
+ * - ChatPlugin 内置授权能力会使用该点执行角色与权限判定。
68
68
  */
69
69
  authorizeIncoming: "chat.authorizeIncoming",
70
70
  /**
@@ -72,7 +72,7 @@ export const CHAT_PLUGIN_POINTS = {
72
72
  *
73
73
  * 说明(中文)
74
74
  * - 主要用于在 history / queue metadata 中补齐授权上下文。
75
- * - 典型实现方是 chat-authorization plugin。
75
+ * - ChatPlugin 内置授权能力会使用该点补齐用户角色。
76
76
  */
77
77
  resolveUserRole: "chat.resolveUserRole",
78
78
  } as const;
@@ -42,7 +42,7 @@ export type ChatChannelStateSnapshot = {
42
42
  */
43
43
  channel: ChatChannelName;
44
44
  /**
45
- * 是否在配置中启用(`downcity.json` 的 `plugins.chat.channels.<channel>.enabled`)。
45
+ * 当前运行态是否启用该渠道。
46
46
  */
47
47
  enabled: boolean;
48
48
  /**
@@ -0,0 +1,79 @@
1
+ /**
2
+ * ChatPlugin SDK / runtime 配置类型。
3
+ *
4
+ * 关键点(中文)
5
+ * - ChatPlugin 只接收 queue 行为与 channels 列表。
6
+ * - 每个 channel 对象自己负责 env / 凭据 / 账号池绑定解析。
7
+ * - 这样 ChatPlugin 不再理解 Telegram、Feishu、QQ 的具体配置字段。
8
+ */
9
+
10
+ import type { AgentContext } from "@downcity/agent/internal/types/runtime/agent/AgentContext.js";
11
+ import type { StoredChannelAccount } from "@downcity/agent/internal/types/platform/Store.js";
12
+ import type { ChatChannelName } from "@/chat/types/ChannelStatus.js";
13
+ import type { ChatQueueWorkerConfig } from "@/chat/types/ChatQueueWorker.js";
14
+
15
+ /**
16
+ * Chat channel 运行态 patch。
17
+ */
18
+ export interface ChatChannelRuntimePatch {
19
+ /**
20
+ * 是否启用该 channel。
21
+ */
22
+ enabled?: boolean;
23
+ /**
24
+ * 绑定的账号池记录 ID。
25
+ *
26
+ * 说明(中文)
27
+ * - 传入字符串表示绑定到该账号池记录。
28
+ * - 传入 `null` 表示清空账号池绑定。
29
+ */
30
+ channelAccountId?: string | null;
31
+ }
32
+
33
+ /**
34
+ * Chat channel 对象协议。
35
+ */
36
+ export interface ChatChannel {
37
+ /**
38
+ * channel 名称。
39
+ */
40
+ readonly name: ChatChannelName;
41
+ /**
42
+ * 当前 channel 是否启用。
43
+ */
44
+ isEnabled(context: AgentContext): boolean;
45
+ /**
46
+ * 当前 channel 绑定的账号池记录 ID。
47
+ */
48
+ getChannelAccountId(context: AgentContext): string;
49
+ /**
50
+ * 解析当前 channel 的运行态账号。
51
+ */
52
+ getAccount(context: AgentContext): StoredChannelAccount | null;
53
+ /**
54
+ * 应用运行态配置 patch。
55
+ */
56
+ applyRuntimePatch(patch: ChatChannelRuntimePatch): void;
57
+ }
58
+
59
+ /**
60
+ * ChatPlugin 显式构造参数。
61
+ */
62
+ export interface ChatPluginOptions {
63
+ /**
64
+ * Chat queue worker 运行配置。
65
+ *
66
+ * 说明(中文)
67
+ * - 可用于不同 client 定制并发、突发合并等行为。
68
+ * - 这是 queue 行为的唯一运行配置入口。
69
+ */
70
+ queue?: Partial<ChatQueueWorkerConfig>;
71
+ /**
72
+ * 当前 agent 持有的 chat channels。
73
+ *
74
+ * 说明(中文)
75
+ * - 每个 channel 对象自己负责 env、凭据与账号池绑定解析。
76
+ * - 未传入时不启用任何 chat channel。
77
+ */
78
+ channels?: ChatChannel[];
79
+ }
@@ -30,6 +30,7 @@ import type { ContactPingResponse } from "@/contact/types/ContactCheck.js";
30
30
  import type { ContactChatResponse } from "@/contact/types/ContactChat.js";
31
31
  import type { AgentContact } from "@/contact/types/Contact.js";
32
32
  import type { SaveContactInboxShareInput } from "@/contact/types/ContactShare.js";
33
+ import type { ContactPluginOptions } from "@/contact/types/ContactPluginOptions.js";
33
34
  import {
34
35
  appendContactMessage,
35
36
  createStableContactId,
@@ -145,8 +146,14 @@ export class ContactPlugin extends BasePlugin {
145
146
  */
146
147
  readonly actions: PluginActions;
147
148
 
148
- constructor() {
149
+ /**
150
+ * 当前实例持有的显式配置。
151
+ */
152
+ public readonly options: ContactPluginOptions;
153
+
154
+ constructor(options?: ContactPluginOptions) {
149
155
  super();
156
+ this.options = options || {};
150
157
  this.actions = createContactActions({
151
158
  link: async (context, payload) =>
152
159
  (await this.link(context, payload)) as unknown as JsonValue,
@@ -164,11 +171,6 @@ export class ContactPlugin extends BasePlugin {
164
171
  remoteShare: async (context, payload) =>
165
172
  (await this.remoteShare(context, payload)) as unknown as JsonValue,
166
173
  });
167
-
168
- this.lifecycle = {
169
- start: () => undefined,
170
- stop: () => undefined,
171
- };
172
174
  }
173
175
 
174
176
  /**
@@ -180,10 +182,16 @@ export class ContactPlugin extends BasePlugin {
180
182
 
181
183
  private async link(context: AgentContext, payload: ContactLinkCommandPayload) {
182
184
  const now = Date.now();
183
- const ttlSeconds = Math.max(60, Number(payload.ttlSeconds || 600));
185
+ const ttlSeconds = Math.max(
186
+ 60,
187
+ Number(payload.ttlSeconds || this.options.ttlSeconds || 600),
188
+ );
184
189
  const linkId = createContactId("link");
185
190
  const secret = createContactToken();
186
- const endpoint = await resolveSelfEndpoint(context, payload.endpoint);
191
+ const endpoint = await resolveSelfEndpoint(
192
+ context,
193
+ payload.endpoint || this.options.endpoint,
194
+ );
187
195
  const agentName = getAgentName(context);
188
196
 
189
197
  await saveContactLinkRecord(context.rootPath, {
@@ -228,7 +236,7 @@ export class ContactPlugin extends BasePlugin {
228
236
  targetReachability === "loopback" ||
229
237
  targetReachability === "private";
230
238
  const requesterEndpointCandidate = shouldResolveRequesterEndpoint
231
- ? await resolveSelfEndpoint(context, payload.endpoint)
239
+ ? await resolveSelfEndpoint(context, payload.endpoint || this.options.endpoint)
232
240
  : undefined;
233
241
  const callbackDecision = buildContactApproveCallbackDecision({
234
242
  targetEndpoint: parsed.endpoint,