@downcity/plugins 1.0.152 → 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
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Chat channel 配置持久化公开行为测试。
3
+ */
4
+ import assert from "node:assert/strict";
5
+ import test from "node:test";
6
+ import { ChatPlugin } from "../bin/index.js";
7
+
8
+ function create_channel(name) {
9
+ let enabled = false;
10
+ let channel_account_id = "";
11
+ return {
12
+ name,
13
+ isEnabled: () => enabled,
14
+ getChannelAccountId: () => channel_account_id,
15
+ getAccount: () => null,
16
+ applyRuntimePatch: (patch) => {
17
+ if (typeof patch.enabled === "boolean") enabled = patch.enabled;
18
+ if (Object.prototype.hasOwnProperty.call(patch, "channelAccountId")) {
19
+ channel_account_id = String(patch.channelAccountId || "").trim();
20
+ }
21
+ },
22
+ };
23
+ }
24
+
25
+ function create_context(plugin) {
26
+ const writes = [];
27
+ const context = {
28
+ config: {
29
+ id: "agent_test",
30
+ version: "1.0.0",
31
+ plugins: { memory: { enabled: true } },
32
+ },
33
+ pluginInstances: new Map([["chat", plugin]]),
34
+ pluginConfig: {
35
+ persistProjectPlugins: async (plugins) => {
36
+ writes.push(structuredClone(plugins));
37
+ return "/tmp/agent_test";
38
+ },
39
+ },
40
+ };
41
+ return { context, writes };
42
+ }
43
+
44
+ async function execute(plugin, action_name, context, input) {
45
+ return await plugin.actions[action_name].execute({
46
+ context,
47
+ input,
48
+ pluginName: "chat",
49
+ actionName: action_name,
50
+ });
51
+ }
52
+
53
+ test("chat.open/close/configure 持久化完整 plugins 配置", async () => {
54
+ const telegram = create_channel("telegram");
55
+ const plugin = new ChatPlugin({ channels: [telegram] });
56
+ const { context, writes } = create_context(plugin);
57
+
58
+ await execute(plugin, "open", context, { channel: "telegram" });
59
+ assert.equal(writes.length, 1);
60
+ assert.equal(writes[0].memory.enabled, true);
61
+ assert.equal(writes[0].chat.channels.telegram.enabled, true);
62
+ assert.equal(plugin.isChannelEnabled(context, "telegram"), true);
63
+
64
+ await execute(plugin, "configure", context, {
65
+ channel: "telegram",
66
+ config: { enabled: false },
67
+ restart: false,
68
+ });
69
+ assert.equal(writes.length, 2);
70
+ assert.equal(writes[1].chat.channels.telegram.enabled, false);
71
+ assert.equal(plugin.isChannelEnabled(context, "telegram"), false);
72
+
73
+ await execute(plugin, "close", context, { channel: "telegram" });
74
+ assert.equal(writes.length, 3);
75
+ assert.equal(context.config.plugins.chat.channels.telegram.enabled, false);
76
+ });
77
+
78
+ test("ChatPlugin queue 只读取 constructor 配置", () => {
79
+ const queue = { maxConcurrency: 7, mergeDebounceMs: 123 };
80
+ const plugin = new ChatPlugin({ queue, channels: [] });
81
+ assert.deepEqual(plugin.getQueueWorkerConfig({}), queue);
82
+ });
@@ -9,11 +9,7 @@
9
9
  */
10
10
 
11
11
  import { BasePlugin } from "@downcity/agent";
12
- import type {
13
- PluginActions,
14
- PluginHooks,
15
- PluginResolves,
16
- } from "@downcity/agent";
12
+ import type { PluginActions } from "@downcity/agent";
17
13
  import type { AgentContext } from "@downcity/agent";
18
14
  import type { ChatChannelState } from "@/chat/types/ChatRuntime.js";
19
15
  import type { ChatQueueWorkerConfig } from "@/chat/types/ChatQueueWorker.js";
@@ -33,11 +29,7 @@ import {
33
29
  stopChatChannels,
34
30
  } from "./runtime/ChatChannelFacade.js";
35
31
  import { createChatPluginActions } from "./runtime/ChatPluginActions.js";
36
- import {
37
- createChatAuthorizationActions,
38
- createChatAuthorizationHooks,
39
- createChatAuthorizationResolves,
40
- } from "./runtime/ChatAuthorizationRuntime.js";
32
+ import { create_chat_access_actions } from "./access/ChatAccessActions.js";
41
33
  import { ChatQueueWorker } from "./runtime/ChatQueueWorker.js";
42
34
  import { buildChatPluginSystem } from "./runtime/ChatPluginSystem.js";
43
35
  import { ChatQueueStore } from "./runtime/ChatQueueStore.js";
@@ -104,16 +96,6 @@ export class ChatPlugin extends BasePlugin {
104
96
  */
105
97
  readonly actions: PluginActions;
106
98
 
107
- /**
108
- * 当前 plugin 的 hook 定义表。
109
- */
110
- readonly hooks: PluginHooks;
111
-
112
- /**
113
- * 当前 plugin 的 resolve 定义表。
114
- */
115
- readonly resolves: PluginResolves;
116
-
117
99
  /**
118
100
  * 启动当前实例的 queue worker。
119
101
  */
@@ -149,10 +131,8 @@ export class ChatPlugin extends BasePlugin {
149
131
  ...createChatPluginActions({
150
132
  channelState: this.channelState,
151
133
  }),
152
- ...createChatAuthorizationActions(),
134
+ ...create_chat_access_actions(),
153
135
  };
154
- this.hooks = createChatAuthorizationHooks();
155
- this.resolves = createChatAuthorizationResolves();
156
136
  this.lifecycle = {
157
137
  start: async (context) => {
158
138
  this.startQueueWorker(context);
@@ -196,7 +176,7 @@ export class ChatPlugin extends BasePlugin {
196
176
  * 更新当前实例的渠道运行态配置。
197
177
  *
198
178
  * 关键点(中文)
199
- * - chat.open/close/configure 只修改当前 plugin 实例,不写 downcity.json。
179
+ * - chat.open/close/configure action 先调用宿主持久化能力,再修改当前实例。
200
180
  * - 当前运行态只允许更新 `enabled` 与 `channelAccountId`,密钥仍来自 constructor 或账号池。
201
181
  */
202
182
  applyChannelRuntimePatch(params: {
@@ -4,6 +4,6 @@
4
4
  */
5
5
 
6
6
  // Source: src/chat/PROMPT.direct.ts.txt
7
- const TEXT_MODULE_CONTENT = "# Chat Plugin Instructions\n\n## User-Visible Reply Rules\n\n- In the current mode, your direct assistant output is sent as a message to the user's corresponding channel.\n\n## Output Protocol\n\n- For a normal reply, output plain text directly. Optional frontmatter metadata may appear at the very top of the text. Field semantics match the chat plugin direct-send protocol:\n\n- `delay` / `delayMs`: delay before sending, in milliseconds.\n- `time` / `sendAt` / `sendAtMs`: scheduled send time.\n- `reply`: whether to send with reply semantics.\n- `messageId`: target `message_id`, recommended for group chats.\n- `react`: send an emoji reaction.\n - Single string: `react: \"thumbs up\"`\n - Or an object/array: `emoji/big`\n - When `messageId` is set, `react` reuses that message as the target first.\n- Attachments use `<file type=\"...\">path</file>` and support `document/photo/voice/audio/video`.\n- Attachment paths must be relative paths accessible inside the project. For multiple attachments, output multiple `<file>` tags.\n\n### Example\n\n```text\n---\nreply: true\nmessageId: \"128\"\nreact:\n - emoji: \"done\"\n---\nHere is today's report.\n<file type=\"document\">reports/daily.md</file>\n```\n\n### Protocol Constraints\n\n- Frontmatter must be at the very beginning of the text and wrapped with `---`.\n- Except for `<file>` attachment tags, do not use angle-bracket syntax for control parameters.\n\n## Cross-Protocol Or Cross-Platform Operations\n\n- For replies inside the current conversation, prefer outputting text directly. The system will send it to the current channel automatically.\n- When a task needs cross-conversation, cross-platform, or complex routing, use chat plugin actions.\n- If `plugin_call` is available, call `plugin_call({ plugin: \"chat\", action: \"send\", payload })` or `plugin_call({ plugin: \"chat\", action: \"react\", payload })`.\n- Metadata applies only to direct outbound messages in the current conversation. Do not use metadata for cross-chat routing.\n- If cross-chat routing parameters are unclear, first read the current chat context or call chat plugin query actions instead of guessing.\n\n## Inbound Message Structure\n\n- Each queued user message contains an `<info>...</info>` metadata block plus the user text.\n- `<info>` keeps only user/request metadata such as `user_id`, `username`, `message_id`, `permissions`, and `received_at`.\n- The current chat routing environment, such as `channel`, `session_id`, `chat_key`, and `chat_id`, is injected separately through the system prompt and is no longer mixed into `<info>`.\n- Do not echo `<info>` back to the user.\n";
7
+ const TEXT_MODULE_CONTENT = "# Chat Plugin Instructions\n\n## User-Visible Reply Rules\n\n- In the current mode, your direct assistant output is sent as a message to the user's corresponding channel.\n\n## Output Protocol\n\n- For a normal reply, output plain text directly. Optional frontmatter metadata may appear at the very top of the text. Field semantics match the chat plugin direct-send protocol:\n\n- `delay` / `delayMs`: delay before sending, in milliseconds.\n- `time` / `sendAt` / `sendAtMs`: scheduled send time.\n- `reply`: whether to send with reply semantics.\n- `messageId`: target `message_id`, recommended for group chats.\n- `react`: send an emoji reaction.\n - Single string: `react: \"thumbs up\"`\n - Or an object/array: `emoji/big`\n - When `messageId` is set, `react` reuses that message as the target first.\n- Attachments use `<file type=\"...\">path</file>` and support `document/photo/voice/audio/video`.\n- Attachment paths must be relative paths accessible inside the project. For multiple attachments, output multiple `<file>` tags.\n\n### Example\n\n```text\n---\nreply: true\nmessageId: \"128\"\nreact:\n - emoji: \"done\"\n---\nHere is today's report.\n<file type=\"document\">reports/daily.md</file>\n```\n\n### Protocol Constraints\n\n- Frontmatter must be at the very beginning of the text and wrapped with `---`.\n- Except for `<file>` attachment tags, do not use angle-bracket syntax for control parameters.\n\n## Cross-Protocol Or Cross-Platform Operations\n\n- For replies inside the current conversation, prefer outputting text directly. The system will send it to the current channel automatically.\n- When a task needs cross-conversation, cross-platform, or complex routing, use chat plugin actions.\n- If `plugin_call` is available, call `plugin_call({ plugin: \"chat\", action: \"send\", payload })` or `plugin_call({ plugin: \"chat\", action: \"react\", payload })`.\n- Metadata applies only to direct outbound messages in the current conversation. Do not use metadata for cross-chat routing.\n- If cross-chat routing parameters are unclear, first read the current chat context or call chat plugin query actions instead of guessing.\n\n## Inbound Message Structure\n\n- Each queued user message contains an `<info>...</info>` metadata block plus the user text.\n- `<info>` keeps only user/request metadata such as `user_id`, `username`, `message_id`, and `received_at`.\n- The current chat routing environment, such as `channel`, `session_id`, `chat_key`, and `chat_id`, is injected separately through the system prompt and is no longer mixed into `<info>`.\n- Do not echo `<info>` back to the user.\n";
8
8
 
9
9
  export default TEXT_MODULE_CONTENT;
@@ -48,6 +48,6 @@ Here is today's report.
48
48
  ## Inbound Message Structure
49
49
 
50
50
  - Each queued user message contains an `<info>...</info>` metadata block plus the user text.
51
- - `<info>` keeps only user/request metadata such as `user_id`, `username`, `message_id`, `permissions`, and `received_at`.
51
+ - `<info>` keeps only user/request metadata such as `user_id`, `username`, `message_id`, and `received_at`.
52
52
  - The current chat routing environment, such as `channel`, `session_id`, `chat_key`, and `chat_id`, is injected separately through the system prompt and is no longer mixed into `<info>`.
53
53
  - Do not echo `<info>` back to the user.
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Chat Access Plugin Actions。
3
+ *
4
+ * 关键点(中文)
5
+ * - Control API 通过 Chat Plugin Action 管理当前 Agent 的 Access Store。
6
+ * - CLI 可以直接复用 ChatAccessService,不依赖运行中 Agent。
7
+ */
8
+
9
+ import { createAction } from "@downcity/agent";
10
+ import type { PluginActions } from "@downcity/agent";
11
+ import type { JsonValue } from "@downcity/agent";
12
+ import { z } from "zod";
13
+ import { create_chat_access_service } from "@/chat/access/ChatAccessRuntime.js";
14
+ import { CHAT_ACCESS_ACTIONS } from "@/chat/types/ChatAccess.js";
15
+ import type { ChatAccessScopeInput } from "@/chat/types/ChatAccess.js";
16
+
17
+ const scope_schema = z.enum(["direct", "group", "all"]);
18
+
19
+ /** 创建 Chat Access Action 集合。 */
20
+ export function create_chat_access_actions(): PluginActions {
21
+ return {
22
+ [CHAT_ACCESS_ACTIONS.snapshot]: createAction({
23
+ description: "Read Chat Access principals, grants, and requests for the current Agent.",
24
+ input_schema: {
25
+ zod: z.object({}).passthrough(),
26
+ json_schema: { type: "object", properties: {}, additionalProperties: true },
27
+ },
28
+ execute: async ({ context }) => ({
29
+ success: true,
30
+ data: create_chat_access_service(context).snapshot() as unknown as JsonValue,
31
+ }),
32
+ }),
33
+ [CHAT_ACCESS_ACTIONS.approve]: createAction({
34
+ description: "Approve a pending Chat Access request.",
35
+ input_schema: {
36
+ zod: z.object({
37
+ request_id: z.string().min(1),
38
+ scope: scope_schema.optional(),
39
+ operator: z.string().min(1),
40
+ }),
41
+ json_schema: {
42
+ type: "object",
43
+ required: ["request_id", "operator"],
44
+ properties: {
45
+ request_id: { type: "string" },
46
+ scope: { type: "string", enum: ["direct", "group", "all"] },
47
+ operator: { type: "string" },
48
+ },
49
+ },
50
+ },
51
+ execute: async ({ context, input }) => {
52
+ const body = input as {
53
+ request_id: string;
54
+ scope?: ChatAccessScopeInput;
55
+ operator: string;
56
+ };
57
+ return {
58
+ success: true,
59
+ data: create_chat_access_service(context).approve_request(body) as unknown as JsonValue,
60
+ };
61
+ },
62
+ }),
63
+ [CHAT_ACCESS_ACTIONS.deny]: createAction({
64
+ description: "Deny a pending Chat Access request.",
65
+ input_schema: {
66
+ zod: z.object({
67
+ request_id: z.string().min(1),
68
+ scope: scope_schema.optional(),
69
+ operator: z.string().min(1),
70
+ }),
71
+ json_schema: {
72
+ type: "object",
73
+ required: ["request_id", "operator"],
74
+ properties: {
75
+ request_id: { type: "string" },
76
+ scope: { type: "string", enum: ["direct", "group", "all"] },
77
+ operator: { type: "string" },
78
+ },
79
+ },
80
+ },
81
+ execute: async ({ context, input }) => {
82
+ const body = input as {
83
+ request_id: string;
84
+ scope?: ChatAccessScopeInput;
85
+ operator: string;
86
+ };
87
+ return {
88
+ success: true,
89
+ data: create_chat_access_service(context).deny_request(body) as unknown as JsonValue,
90
+ };
91
+ },
92
+ }),
93
+ [CHAT_ACCESS_ACTIONS.set]: createAction({
94
+ description: "Set allow or deny for a known Chat Access principal.",
95
+ input_schema: {
96
+ zod: z.object({
97
+ principal_id: z.string().min(1),
98
+ scope: scope_schema,
99
+ effect: z.enum(["allow", "deny"]),
100
+ operator: z.string().min(1),
101
+ }),
102
+ json_schema: {
103
+ type: "object",
104
+ required: ["principal_id", "scope", "effect", "operator"],
105
+ properties: {
106
+ principal_id: { type: "string" },
107
+ scope: { type: "string", enum: ["direct", "group", "all"] },
108
+ effect: { type: "string", enum: ["allow", "deny"] },
109
+ operator: { type: "string" },
110
+ },
111
+ },
112
+ },
113
+ execute: async ({ context, input }) => {
114
+ const body = input as {
115
+ principal_id: string;
116
+ scope: ChatAccessScopeInput;
117
+ effect: "allow" | "deny";
118
+ operator: string;
119
+ };
120
+ return {
121
+ success: true,
122
+ data: create_chat_access_service(context).set_principal_effect(body) as unknown as JsonValue,
123
+ };
124
+ },
125
+ }),
126
+ [CHAT_ACCESS_ACTIONS.revoke]: createAction({
127
+ description: "Revoke Chat Access grants for a known principal.",
128
+ input_schema: {
129
+ zod: z.object({
130
+ principal_id: z.string().min(1),
131
+ scope: scope_schema,
132
+ operator: z.string().min(1),
133
+ }),
134
+ json_schema: {
135
+ type: "object",
136
+ required: ["principal_id", "scope", "operator"],
137
+ properties: {
138
+ principal_id: { type: "string" },
139
+ scope: { type: "string", enum: ["direct", "group", "all"] },
140
+ operator: { type: "string" },
141
+ },
142
+ },
143
+ },
144
+ execute: async ({ context, input }) => {
145
+ const body = input as {
146
+ principal_id: string;
147
+ scope: ChatAccessScopeInput;
148
+ operator: string;
149
+ };
150
+ return {
151
+ success: true,
152
+ data: {
153
+ removed_count: create_chat_access_service(context).revoke_grant(body),
154
+ },
155
+ };
156
+ },
157
+ }),
158
+ };
159
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Chat Access Runtime 装配辅助。
3
+ *
4
+ * 关键点(中文)
5
+ * - 从当前 ChatPlugin channel 配置解析稳定 issuer。
6
+ * - Channel Adapter、Plugin Action 和 CLI 共享同一个 Service 构造规则。
7
+ */
8
+
9
+ import type { AgentContext } from "@downcity/agent";
10
+ import { ChatAccessService } from "@/chat/access/ChatAccessService.js";
11
+ import { resolveChannelAccount } from "@/chat/runtime/ChatChannelCore.js";
12
+ import type { ChatDispatchChannel } from "@/chat/types/ChatDispatcher.js";
13
+
14
+ const CHANNELS: ChatDispatchChannel[] = ["telegram", "feishu", "qq"];
15
+
16
+ /** 解析当前 Agent 各渠道的稳定 Issuer。 */
17
+ export function resolve_chat_access_issuer_map(
18
+ context: AgentContext,
19
+ ): Partial<Record<ChatDispatchChannel, string>> {
20
+ const issuer_by_channel: Partial<Record<ChatDispatchChannel, string>> = {};
21
+ for (const channel of CHANNELS) {
22
+ const issuer = String(resolveChannelAccount(context, channel)?.id || "").trim();
23
+ if (issuer) issuer_by_channel[channel] = issuer;
24
+ }
25
+ return issuer_by_channel;
26
+ }
27
+
28
+ /** 创建当前 Agent 的 ChatAccessService。 */
29
+ export function create_chat_access_service(context: AgentContext): ChatAccessService {
30
+ return new ChatAccessService({
31
+ project_root: context.rootPath,
32
+ });
33
+ }
34
+
35
+ /** 解析指定渠道当前稳定 Issuer。 */
36
+ export function resolve_chat_access_issuer(
37
+ context: AgentContext,
38
+ channel: ChatDispatchChannel,
39
+ ): string {
40
+ return String(resolve_chat_access_issuer_map(context)[channel] || "").trim();
41
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Chat Access SQLite Schema。
3
+ *
4
+ * 关键点(中文)
5
+ * - 所有表只保存当前 Agent 的 Chat 准入数据。
6
+ * - 不保存 Bot Token、App Secret 或消息正文。
7
+ * - WAL 与 busy_timeout 保证 Agent runtime 和 CLI 可以并发读写。
8
+ */
9
+
10
+ import type Database from "better-sqlite3";
11
+
12
+ /** 当前 Chat Access Schema 版本。 */
13
+ export const CHAT_ACCESS_SCHEMA_VERSION = "1";
14
+
15
+ /**
16
+ * 初始化 Chat Access Schema。
17
+ */
18
+ export function ensure_chat_access_schema(database: Database.Database): void {
19
+ database.pragma("journal_mode = WAL");
20
+ database.pragma("foreign_keys = ON");
21
+ database.pragma("busy_timeout = 5000");
22
+
23
+ database.exec(`
24
+ CREATE TABLE IF NOT EXISTS chat_access_meta (
25
+ key TEXT PRIMARY KEY NOT NULL,
26
+ value TEXT NOT NULL,
27
+ updated_at TEXT NOT NULL
28
+ );
29
+
30
+ CREATE TABLE IF NOT EXISTS chat_access_principals (
31
+ principal_id TEXT PRIMARY KEY NOT NULL,
32
+ channel TEXT NOT NULL,
33
+ issuer TEXT NOT NULL,
34
+ subject_id TEXT NOT NULL,
35
+ display_name TEXT,
36
+ first_seen_at TEXT NOT NULL,
37
+ last_seen_at TEXT NOT NULL,
38
+ last_chat_id TEXT,
39
+ last_chat_type TEXT,
40
+ UNIQUE (channel, issuer, subject_id)
41
+ );
42
+
43
+ CREATE TABLE IF NOT EXISTS chat_access_grants (
44
+ grant_id TEXT PRIMARY KEY NOT NULL,
45
+ principal_id TEXT NOT NULL,
46
+ scope TEXT NOT NULL CHECK (scope IN ('direct', 'group')),
47
+ effect TEXT NOT NULL CHECK (effect IN ('allow', 'deny')),
48
+ created_by TEXT NOT NULL,
49
+ created_at TEXT NOT NULL,
50
+ updated_at TEXT NOT NULL,
51
+ UNIQUE (principal_id, scope),
52
+ FOREIGN KEY (principal_id)
53
+ REFERENCES chat_access_principals(principal_id)
54
+ ON DELETE CASCADE
55
+ );
56
+
57
+ CREATE TABLE IF NOT EXISTS chat_access_requests (
58
+ request_id TEXT PRIMARY KEY NOT NULL,
59
+ principal_id TEXT NOT NULL,
60
+ scope TEXT NOT NULL CHECK (scope IN ('direct', 'group')),
61
+ chat_id TEXT NOT NULL,
62
+ chat_type TEXT NOT NULL,
63
+ status TEXT NOT NULL CHECK (status IN ('pending', 'approved', 'denied', 'expired')),
64
+ resolved_by TEXT,
65
+ created_at TEXT NOT NULL,
66
+ last_requested_at TEXT NOT NULL,
67
+ resolved_at TEXT,
68
+ FOREIGN KEY (principal_id)
69
+ REFERENCES chat_access_principals(principal_id)
70
+ ON DELETE CASCADE
71
+ );
72
+
73
+ CREATE UNIQUE INDEX IF NOT EXISTS chat_access_requests_pending_uq
74
+ ON chat_access_requests(principal_id, scope)
75
+ WHERE status = 'pending';
76
+
77
+ CREATE INDEX IF NOT EXISTS chat_access_requests_status_idx
78
+ ON chat_access_requests(status, last_requested_at DESC);
79
+
80
+ CREATE TABLE IF NOT EXISTS chat_access_audit_events (
81
+ event_id TEXT PRIMARY KEY NOT NULL,
82
+ principal_id TEXT,
83
+ request_id TEXT,
84
+ action TEXT NOT NULL,
85
+ scope TEXT,
86
+ decision TEXT,
87
+ operator TEXT,
88
+ detail_json TEXT,
89
+ created_at TEXT NOT NULL
90
+ );
91
+
92
+ CREATE INDEX IF NOT EXISTS chat_access_audit_created_idx
93
+ ON chat_access_audit_events(created_at DESC);
94
+ `);
95
+
96
+ const current_time = new Date().toISOString();
97
+ database.prepare(`
98
+ INSERT INTO chat_access_meta (key, value, updated_at)
99
+ VALUES ('schema_version', ?, ?)
100
+ ON CONFLICT(key) DO UPDATE SET
101
+ value = excluded.value,
102
+ updated_at = excluded.updated_at
103
+ `).run(CHAT_ACCESS_SCHEMA_VERSION, current_time);
104
+ }