@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.
- package/bin/BuiltinPlugins.d.ts +24 -2
- package/bin/BuiltinPlugins.d.ts.map +1 -1
- package/bin/BuiltinPlugins.js +18 -5
- package/bin/BuiltinPlugins.js.map +1 -1
- package/bin/auth/types/AuthPlugin.d.ts +12 -16
- package/bin/auth/types/AuthPlugin.d.ts.map +1 -1
- package/bin/auth/types/AuthPlugin.js +9 -13
- package/bin/auth/types/AuthPlugin.js.map +1 -1
- package/bin/chat/ChatPlugin.d.ts +37 -6
- package/bin/chat/ChatPlugin.d.ts.map +1 -1
- package/bin/chat/ChatPlugin.js +58 -80
- package/bin/chat/ChatPlugin.js.map +1 -1
- package/bin/chat/Index.d.ts +4 -1
- package/bin/chat/Index.d.ts.map +1 -1
- package/bin/chat/Index.js +2 -1
- package/bin/chat/Index.js.map +1 -1
- package/bin/chat/channels/Configuration.d.ts +1 -1
- package/bin/chat/channels/Configuration.js +1 -1
- package/bin/chat/channels/RuntimeChannel.d.ts +145 -0
- package/bin/chat/channels/RuntimeChannel.d.ts.map +1 -0
- package/bin/chat/channels/RuntimeChannel.js +148 -0
- package/bin/chat/channels/RuntimeChannel.js.map +1 -0
- package/bin/chat/runtime/ChatAuthorizationRuntime.d.ts +22 -0
- package/bin/chat/runtime/ChatAuthorizationRuntime.d.ts.map +1 -0
- package/bin/chat/runtime/ChatAuthorizationRuntime.js +189 -0
- package/bin/chat/runtime/ChatAuthorizationRuntime.js.map +1 -0
- package/bin/chat/runtime/ChatChannelActions.d.ts.map +1 -1
- package/bin/chat/runtime/ChatChannelActions.js +19 -9
- package/bin/chat/runtime/ChatChannelActions.js.map +1 -1
- package/bin/chat/runtime/ChatChannelConfig.d.ts +2 -18
- package/bin/chat/runtime/ChatChannelConfig.d.ts.map +1 -1
- package/bin/chat/runtime/ChatChannelConfig.js +2 -73
- package/bin/chat/runtime/ChatChannelConfig.js.map +1 -1
- package/bin/chat/runtime/ChatChannelCore.d.ts +22 -1
- package/bin/chat/runtime/ChatChannelCore.d.ts.map +1 -1
- package/bin/chat/runtime/ChatChannelCore.js +5 -5
- package/bin/chat/runtime/ChatChannelCore.js.map +1 -1
- package/bin/chat/runtime/ChatPluginActionRegistry.js +1 -1
- package/bin/chat/runtime/ChatPluginActionRegistry.js.map +1 -1
- package/bin/chat/runtime/PluginPoints.d.ts +3 -3
- package/bin/chat/runtime/PluginPoints.js +3 -3
- package/bin/chat/types/ChannelStatus.d.ts +1 -1
- package/bin/chat/types/ChatPluginOptions.d.ts +76 -0
- package/bin/chat/types/ChatPluginOptions.d.ts.map +1 -0
- package/bin/chat/types/ChatPluginOptions.js +10 -0
- package/bin/chat/types/ChatPluginOptions.js.map +1 -0
- package/bin/contact/ContactPlugin.d.ts +6 -1
- package/bin/contact/ContactPlugin.d.ts.map +1 -1
- package/bin/contact/ContactPlugin.js +9 -8
- package/bin/contact/ContactPlugin.js.map +1 -1
- package/bin/contact/types/ContactPluginOptions.d.ts +29 -0
- package/bin/contact/types/ContactPluginOptions.d.ts.map +1 -0
- package/bin/contact/types/ContactPluginOptions.js +9 -0
- package/bin/contact/types/ContactPluginOptions.js.map +1 -0
- package/bin/index.d.ts +7 -4
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +3 -2
- package/bin/index.js.map +1 -1
- package/bin/skill/Action.d.ts.map +1 -1
- package/bin/skill/Action.js +2 -2
- package/bin/skill/Action.js.map +1 -1
- package/bin/skill/Command.js +5 -5
- package/bin/skill/Command.js.map +1 -1
- package/bin/skill/Plugin.js +1 -1
- package/bin/skill/Plugin.js.map +1 -1
- package/bin/skill/runtime/Discovery.d.ts +4 -4
- package/bin/skill/runtime/Discovery.d.ts.map +1 -1
- package/bin/skill/runtime/Discovery.js +5 -5
- package/bin/skill/runtime/Discovery.js.map +1 -1
- package/bin/skill/runtime/Paths.d.ts +1 -5
- package/bin/skill/runtime/Paths.d.ts.map +1 -1
- package/bin/skill/runtime/Paths.js +1 -9
- package/bin/skill/runtime/Paths.js.map +1 -1
- package/bin/skill/runtime/Prompt.d.ts +2 -2
- package/bin/skill/runtime/Prompt.d.ts.map +1 -1
- package/bin/skill/runtime/Prompt.js +3 -3
- package/bin/skill/runtime/Prompt.js.map +1 -1
- package/bin/skill/runtime/Store.d.ts +2 -2
- package/bin/skill/runtime/Store.d.ts.map +1 -1
- package/bin/skill/runtime/Store.js.map +1 -1
- package/bin/skill/runtime/SystemProvider.js +4 -4
- package/bin/skill/runtime/SystemProvider.js.map +1 -1
- package/bin/skill/runtime/Types.d.ts +3 -3
- package/bin/skill/runtime/Types.d.ts.map +1 -1
- package/bin/skill/types/{ClaudeSkill.d.ts → SkillDefinition.d.ts} +4 -4
- package/bin/skill/types/SkillDefinition.d.ts.map +1 -0
- package/bin/skill/types/{ClaudeSkill.js → SkillDefinition.js} +2 -2
- package/bin/skill/types/SkillDefinition.js.map +1 -0
- package/bin/skill/types/SkillPlugin.d.ts +2 -2
- package/bin/skill/types/SkillPlugin.d.ts.map +1 -1
- package/bin/web/Plugin.js +1 -1
- package/bin/web/Plugin.js.map +1 -1
- package/bin/web/runtime/Install.js +2 -2
- package/bin/web/runtime/Install.js.map +1 -1
- package/bin/web/types/WebPlugin.d.ts +2 -1
- package/bin/web/types/WebPlugin.d.ts.map +1 -1
- package/bin/workboard/Plugin.d.ts +23 -3
- package/bin/workboard/Plugin.d.ts.map +1 -1
- package/bin/workboard/Plugin.js +66 -85
- package/bin/workboard/Plugin.js.map +1 -1
- package/package.json +2 -2
- package/src/BuiltinPlugins.ts +41 -5
- package/src/auth/types/AuthPlugin.ts +12 -17
- package/src/chat/ChatPlugin.ts +92 -89
- package/src/chat/Index.ts +18 -1
- package/src/chat/channels/Configuration.ts +1 -1
- package/src/chat/channels/RuntimeChannel.ts +264 -0
- package/src/chat/runtime/ChatAuthorizationRuntime.ts +229 -0
- package/src/chat/runtime/ChatChannelActions.ts +24 -9
- package/src/chat/runtime/ChatChannelConfig.ts +2 -100
- package/src/chat/runtime/ChatChannelCore.ts +20 -8
- package/src/chat/runtime/ChatPluginActionRegistry.ts +1 -1
- package/src/chat/runtime/PluginPoints.ts +3 -3
- package/src/chat/types/ChannelStatus.ts +1 -1
- package/src/chat/types/ChatPluginOptions.ts +79 -0
- package/src/contact/ContactPlugin.ts +17 -9
- package/src/contact/types/ContactPluginOptions.ts +30 -0
- package/src/index.ts +23 -7
- package/src/skill/Action.ts +10 -7
- package/src/skill/Command.ts +5 -5
- package/src/skill/Plugin.ts +1 -1
- package/src/skill/runtime/Discovery.ts +14 -11
- package/src/skill/runtime/Paths.ts +1 -13
- package/src/skill/runtime/Prompt.ts +5 -5
- package/src/skill/runtime/Store.ts +6 -3
- package/src/skill/runtime/SystemProvider.ts +4 -4
- package/src/skill/runtime/Types.ts +3 -3
- package/src/skill/types/{ClaudeSkill.ts → SkillDefinition.ts} +3 -3
- package/src/skill/types/SkillPlugin.ts +2 -2
- package/src/web/Plugin.ts +1 -1
- package/src/web/runtime/Install.ts +3 -3
- package/src/web/types/WebPlugin.ts +2 -2
- package/src/workboard/Plugin.ts +80 -95
- package/bin/auth/Plugin.d.ts +0 -17
- package/bin/auth/Plugin.d.ts.map +0 -1
- package/bin/auth/Plugin.js +0 -199
- package/bin/auth/Plugin.js.map +0 -1
- package/bin/chat/ChatPluginTypes.d.ts +0 -122
- package/bin/chat/ChatPluginTypes.d.ts.map +0 -1
- package/bin/chat/ChatPluginTypes.js +0 -10
- package/bin/chat/ChatPluginTypes.js.map +0 -1
- package/bin/skill/types/ClaudeSkill.d.ts.map +0 -1
- package/bin/skill/types/ClaudeSkill.js.map +0 -1
- package/src/auth/Plugin.ts +0 -237
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
284
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
* -
|
|
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
|
|
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
|
|
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
|
-
* -
|
|
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
|
-
* -
|
|
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
|
-
* -
|
|
75
|
+
* - ChatPlugin 内置授权能力会使用该点补齐用户角色。
|
|
76
76
|
*/
|
|
77
77
|
resolveUserRole: "chat.resolveUserRole",
|
|
78
78
|
} as const;
|
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
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,
|