@xmanrui/dsh-im 0.17.0 → 0.18.0

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 (49) hide show
  1. package/README.en.md +3 -2
  2. package/README.md +3 -2
  3. package/lib/client.js +964 -538
  4. package/lib/index.js +189 -144
  5. package/package.json +1 -1
  6. package/plugin-src/client/agent-preset.js +110 -0
  7. package/plugin-src/client/channels/dingtalk/api.js +5 -0
  8. package/plugin-src/client/channels/dingtalk/index.js +48 -2
  9. package/plugin-src/client/channels/feishu/api.js +5 -0
  10. package/plugin-src/client/channels/feishu/index.js +39 -2
  11. package/plugin-src/client/channels/qq/api.js +5 -0
  12. package/plugin-src/client/channels/qq/index.js +37 -6
  13. package/plugin-src/client/channels/shared/token-api.js +5 -0
  14. package/plugin-src/client/channels/shared/token-channel.js +34 -6
  15. package/plugin-src/client/channels/wecom/api.js +5 -0
  16. package/plugin-src/client/channels/wecom/index.js +37 -6
  17. package/plugin-src/client/channels/weixin/api.js +15 -0
  18. package/plugin-src/client/channels/weixin/index.js +54 -4
  19. package/plugin-src/client/channels/whatsapp/api.js +5 -0
  20. package/plugin-src/client/channels/whatsapp/index.js +30 -4
  21. package/plugin-src/client/i18n.js +29 -0
  22. package/plugin-src/client/styles.js +8 -0
  23. package/plugin-src/host/channels/dingtalk/production.mjs +10 -4
  24. package/plugin-src/host/channels/dingtalk/rpc.mjs +12 -0
  25. package/plugin-src/host/channels/feishu/connection-supervisor.mjs +1 -1
  26. package/plugin-src/host/channels/feishu/production.mjs +6 -3
  27. package/plugin-src/host/channels/feishu/rpc.mjs +17 -0
  28. package/plugin-src/host/channels/qq/production.mjs +10 -4
  29. package/plugin-src/host/channels/qq/rpc.mjs +12 -0
  30. package/plugin-src/host/channels/shared/agent-preset-rpc.mjs +25 -0
  31. package/plugin-src/host/channels/shared/production.mjs +10 -4
  32. package/plugin-src/host/channels/shared/rpc.mjs +12 -0
  33. package/plugin-src/host/channels/shared/workspace-rpc.mjs +2 -0
  34. package/plugin-src/host/channels/slack/production.mjs +10 -4
  35. package/plugin-src/host/channels/slack/rpc.mjs +13 -0
  36. package/plugin-src/host/channels/wecom/production.mjs +10 -4
  37. package/plugin-src/host/channels/wecom/rpc.mjs +12 -0
  38. package/plugin-src/host/channels/weixin/production.mjs +10 -4
  39. package/plugin-src/host/channels/weixin/rpc.mjs +15 -0
  40. package/plugin-src/host/channels/whatsapp/production.mjs +10 -4
  41. package/plugin-src/host/channels/whatsapp/rpc.mjs +12 -0
  42. package/src/channels/discord/discord-api.mjs +1 -1
  43. package/src/channels/shared/agent-preset.mjs +74 -0
  44. package/src/channels/shared/bot-workspace-store.mjs +141 -14
  45. package/src/channels/shared/harness-client.mjs +6 -4
  46. package/src/channels/shared/image-prompt.mjs +32 -1
  47. package/src/channels/weixin/weixin-api.mjs +1 -1
  48. package/src/channels/weixin/weixin-bridge.mjs +23 -3
  49. package/src/channels/weixin/weixin-controller.mjs +15 -0
package/lib/client.js CHANGED
@@ -40,7 +40,7 @@ __export(index_exports, {
40
40
  name: () => name
41
41
  });
42
42
  module.exports = __toCommonJS(index_exports);
43
- var React18 = __toESM(require("react"), 1);
43
+ var React19 = __toESM(require("react"), 1);
44
44
 
45
45
  // plugin-src/client/channel-logos.js
46
46
  var React = __toESM(require("react"), 1);
@@ -186,207 +186,7 @@ function OfficeLogoGlyph({ size } = {}) {
186
186
  );
187
187
  }
188
188
 
189
- // plugin-src/client/channels/dingtalk/api.js
190
- var DINGTALK_RPC_CHANNEL = "/dingtalk";
191
- var DINGTALK_ENDPOINTS = Object.freeze({
192
- status: "connection.status",
193
- beginProvisioning: "provision.begin",
194
- pollProvisioning: "provision.poll",
195
- cancelProvisioning: "provision.cancel",
196
- bindCredentials: "bot.bind-credentials",
197
- reconnectBot: "bot.reconnect",
198
- deleteBot: "bot.delete",
199
- setWorkspace: "bot.workspace.set"
200
- });
201
- var ACCOUNT_STATES = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
202
- var SNAPSHOT_STATES = /* @__PURE__ */ new Set(["disconnected", "offline", "provisioning", "connected", "degraded"]);
203
- var PROVISION_STATES = /* @__PURE__ */ new Set([
204
- "starting",
205
- "pending",
206
- "scanned",
207
- "authorizing",
208
- "creating",
209
- "connecting",
210
- "connected",
211
- "expired",
212
- "failed",
213
- "cancelled"
214
- ]);
215
- var HEALTH_STATES = /* @__PURE__ */ new Set(["healthy", "checking", "degraded", "offline"]);
216
- var FORBIDDEN_ERROR_FIELDS = /(client[_-]?secret|secret[_-]?ref|device[_-]?code|app[_-]?secret|access[_-]?token|token)/i;
217
- var QR_DATA_URL = /^data:image\/(?:png|webp);base64,[a-z\d+/]+={0,2}$/i;
218
- var MAX_QR_SOURCE_LENGTH = 2 * 1024 * 1024;
219
- function isRecord(value) {
220
- return value !== null && typeof value === "object" && !Array.isArray(value);
221
- }
222
- function optionalString(value, maxLength = 240) {
223
- if (typeof value !== "string") return void 0;
224
- const trimmed = value.trim();
225
- return trimmed ? trimmed.slice(0, maxLength) : void 0;
226
- }
227
- function opaqueId(value) {
228
- const id5 = optionalString(value, 128);
229
- return id5 && /^[a-z\d_-]+$/i.test(id5) ? id5 : void 0;
230
- }
231
- function timestamp(value) {
232
- if (typeof value === "number" && Number.isFinite(value)) return value;
233
- if (typeof value === "string" && value.trim()) {
234
- const parsed = Date.parse(value);
235
- return Number.isNaN(parsed) ? void 0 : parsed;
236
- }
237
- return void 0;
238
- }
239
- function nonNegativeInteger(value) {
240
- const number = Number(value);
241
- return Number.isSafeInteger(number) && number >= 0 ? number : 0;
242
- }
243
- function clamp(value, min, max, fallback) {
244
- const number = Number(value);
245
- return Number.isFinite(number) ? Math.min(max, Math.max(min, number)) : fallback;
246
- }
247
- function safeErrorCode(value, fallback) {
248
- const code = optionalString(value, 80);
249
- return code && /^[a-z][a-z\d_.:-]*$/i.test(code) && !FORBIDDEN_ERROR_FIELDS.test(code) ? code : fallback;
250
- }
251
- function sanitizeMessage(value, fallback) {
252
- const message = optionalString(value, 480) ?? fallback;
253
- if (FORBIDDEN_ERROR_FIELDS.test(message)) return fallback;
254
- return message.replace(/([=:]\s*)[^\s,;,。]+/g, "$1\u2022\u2022\u2022\u2022\u2022\u2022").slice(0, 240);
255
- }
256
- function normalizeError(value, fallbackCode, fallbackMessage) {
257
- if (!isRecord(value)) return void 0;
258
- return {
259
- code: safeErrorCode(value.code, fallbackCode),
260
- message: sanitizeMessage(value.message, fallbackMessage)
261
- };
262
- }
263
- function normalizeTestMessage(value) {
264
- if (!isRecord(value)) return null;
265
- if (value.sent === true) return { sent: true };
266
- if (value.sent !== false) return null;
267
- const code = value.code === "test-target-unavailable" ? "test-target-unavailable" : "test-message-failed";
268
- return { sent: false, code };
269
- }
270
- function unwrapRpcResult(result) {
271
- if (!isRecord(result) || typeof result.ok !== "boolean") {
272
- throw new Error("\u9489\u9489\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94");
273
- }
274
- if (!result.ok) {
275
- const error = new Error(sanitizeMessage(result.error?.message, "\u9489\u9489\u64CD\u4F5C\u5931\u8D25"));
276
- error.code = safeErrorCode(result.error?.code, "DINGTALK_RPC_ERROR");
277
- throw error;
278
- }
279
- return result.value;
280
- }
281
- function safeQrSource(value) {
282
- if (typeof value !== "string" || value.length > MAX_QR_SOURCE_LENGTH) return void 0;
283
- return QR_DATA_URL.test(value) ? value : void 0;
284
- }
285
- function normalizeProvisioning(value, now = Date.now()) {
286
- const source = isRecord(value?.provisioning) ? value.provisioning : value;
287
- if (!isRecord(source)) throw new Error("\u9489\u9489\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u626B\u7801\u7ED1\u5B9A\u8FDB\u5EA6");
288
- const attemptId = opaqueId(source.attemptId);
289
- if (!attemptId) throw new Error("\u9489\u9489\u626B\u7801\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u7ED1\u5B9A\u4EFB\u52A1");
290
- const reportedStatus = optionalString(source.status, 32) ?? optionalString(source.state, 32);
291
- const status = PROVISION_STATES.has(reportedStatus) ? reportedStatus : "failed";
292
- const expiresAt = timestamp(source.expiresAt) ?? now + clamp(source.expiresIn, 1, 2 * 60 * 60, 10 * 60) * 1e3;
293
- const result = {
294
- attemptId,
295
- status,
296
- expiresAt,
297
- pollIntervalMs: clamp(source.pollIntervalMs, 1e3, 1e4, 3e3)
298
- };
299
- const qrCodeDataUrl = safeQrSource(source.qrCodeDataUrl);
300
- if (qrCodeDataUrl) result.qrCodeDataUrl = qrCodeDataUrl;
301
- if (opaqueId(source.botId)) result.botId = opaqueId(source.botId);
302
- if (source.alreadyConnected === true) result.alreadyConnected = true;
303
- const error = normalizeError(
304
- source.error,
305
- "DINGTALK_PROVISION_FAILED",
306
- "\u9489\u9489\u673A\u5668\u4EBA\u6CA1\u6709\u63A5\u5165\u5B8C\u6210"
307
- );
308
- if (error) result.error = error;
309
- return result;
310
- }
311
- function normalizeBot(value) {
312
- if (!isRecord(value)) return void 0;
313
- const botId = opaqueId(value.botId);
314
- if (!botId) return void 0;
315
- const bot = isRecord(value.bot) ? value.bot : {};
316
- const connected = value.connected === true;
317
- const reportedState = ACCOUNT_STATES.has(value.state) ? value.state : "offline";
318
- const state = connected ? "connected" : reportedState === "connected" ? "connecting" : reportedState;
319
- const health = isRecord(value.health) ? value.health : {};
320
- const stats = isRecord(value.stats) ? value.stats : {};
321
- return {
322
- botId,
323
- state,
324
- connected,
325
- configured: value.configured !== false,
326
- workspace: optionalString(value.workspace, 4096) ?? "",
327
- bot: {
328
- name: optionalString(bot.name, 100) ?? "\u9489\u9489\u673A\u5668\u4EBA",
329
- clientIdMasked: optionalString(bot.clientIdMasked, 140) ?? "\u5DF2\u5B89\u5168\u4FDD\u5B58"
330
- },
331
- health: {
332
- status: HEALTH_STATES.has(health.status) ? health.status : connected ? "healthy" : "offline",
333
- summary: optionalString(health.summary, 200) ?? (connected ? "\u9489\u9489 Stream \u957F\u8FDE\u63A5\u8FD0\u884C\u6B63\u5E38" : "\u9489\u9489\u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA"),
334
- lastCheckedAt: timestamp(health.lastCheckedAt),
335
- lastConnectedAt: timestamp(health.lastConnectedAt)
336
- },
337
- stats: {
338
- messagesReceived: nonNegativeInteger(stats.messagesReceived),
339
- messagesReplied: nonNegativeInteger(stats.messagesReplied)
340
- },
341
- error: normalizeError(value.error, "DINGTALK_ACCOUNT_ERROR", "\u9489\u9489\u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA") ?? null
342
- };
343
- }
344
- function normalizeSnapshot(value) {
345
- const source = isRecord(value?.snapshot) ? value.snapshot : value;
346
- if (!isRecord(source) || !Array.isArray(source.bots)) {
347
- throw new Error("\u9489\u9489\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u673A\u5668\u4EBA\u5217\u8868");
348
- }
349
- const seen = /* @__PURE__ */ new Set();
350
- const bots = source.bots.map(normalizeBot).filter((bot) => {
351
- if (!bot || seen.has(bot.botId)) return false;
352
- seen.add(bot.botId);
353
- return true;
354
- });
355
- return {
356
- schemaVersion: Number.isSafeInteger(source.schemaVersion) ? source.schemaVersion : 1,
357
- revision: nonNegativeInteger(source.revision),
358
- state: SNAPSHOT_STATES.has(source.state) ? source.state : "offline",
359
- bots,
360
- totals: {
361
- configured: bots.length,
362
- connected: bots.filter((bot) => bot.connected).length
363
- },
364
- provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null,
365
- testMessage: normalizeTestMessage(source.testMessage)
366
- };
367
- }
368
- function connectionTestFeedback(result) {
369
- if (result?.sent === true) return "\u9489\u9489\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\u3002";
370
- if (result?.code === "test-target-unavailable") {
371
- return "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002\u673A\u5668\u4EBA\u5C1A\u672A\u6536\u5230\u53EF\u7528\u4E8E\u6D4B\u8BD5\u7684\u79C1\u804A\u6D88\u606F\u3002";
372
- }
373
- return result ? "\u9489\u9489\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002" : null;
374
- }
375
- function presentError(error) {
376
- return {
377
- code: safeErrorCode(error?.code, "DINGTALK_ERROR"),
378
- message: sanitizeMessage(error?.message, "\u9489\u9489\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5")
379
- };
380
- }
381
- function formatRemaining(milliseconds) {
382
- const seconds = Math.max(0, Math.ceil(Number(milliseconds) / 1e3) || 0);
383
- return `${String(Math.floor(seconds / 60)).padStart(2, "0")}:${String(seconds % 60).padStart(2, "0")}`;
384
- }
385
-
386
- // plugin-src/client/channels/dingtalk/index.js
387
- var React7 = __toESM(require("react"), 1);
388
-
389
- // plugin-src/client/credential-binding.js
189
+ // plugin-src/client/agent-preset.js
390
190
  var React3 = __toESM(require("react"), 1);
391
191
 
392
192
  // plugin-src/client/i18n.js
@@ -507,6 +307,15 @@ var EN = Object.freeze({
507
307
  "\u5DE5\u4F5C\u533A\u8DEF\u5F84\u4E0D\u5B58\u5728\u3002": "The workspace path does not exist.",
508
308
  "\u5DE5\u4F5C\u533A\u8DEF\u5F84\u5FC5\u987B\u6307\u5411\u4E00\u4E2A\u76EE\u5F55\u3002": "The workspace path must point to a directory.",
509
309
  "\u627E\u4E0D\u5230\u8981\u4FEE\u6539\u7684\u673A\u5668\u4EBA\u3002": "The bot could not be found.",
310
+ "Agent Preset": "Agent Preset",
311
+ "\u8DDF\u968F Host \u9ED8\u8BA4": "Follow the Host default",
312
+ "\uFF08\u5DF2\u4E0D\u53EF\u7528\uFF09": " (unavailable)",
313
+ "\u53EA\u5F71\u54CD\u65B0\u5EFA\u4F1A\u8BDD\uFF1B\u82E5\u5F53\u524D\u804A\u5929\u5DF2\u6709\u4F1A\u8BDD\uFF0C\u5148\u53D1\u9001 /new\uFF0C\u518D\u53D1\u9001\u666E\u901A\u6D88\u606F\u751F\u6548\u3002": "This affects only new sessions. If the current chat already has a session, send /new, then send a regular message to apply it.",
314
+ "\u5F53\u524D Agent Preset \u5DF2\u4E0D\u53EF\u7528\uFF0C\u8BF7\u9009\u62E9\u5176\u4ED6 Preset \u6216\u8DDF\u968F Host \u9ED8\u8BA4\u3002": "The current Agent Preset is unavailable. Choose another preset or follow the Host default.",
315
+ "Agent Preset \u4FEE\u6539\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002": "Could not update the Agent Preset. Try again.",
316
+ "\u8BF7\u9009\u62E9 Agent Preset\u3002": "Choose an Agent Preset.",
317
+ "Agent Preset \u65E0\u6548\u3002": "The Agent Preset is invalid.",
318
+ "Agent Preset \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u7528\u3002": "The Agent Preset does not exist or is unavailable.",
510
319
  "\u5C1A\u672A\u68C0\u67E5": "Not checked yet",
511
320
  "\u521A\u521A": "Just now",
512
321
  "\u68C0\u67E5\u8FDE\u63A5": "Check connection",
@@ -839,6 +648,20 @@ var EN = Object.freeze({
839
648
  "\u5FAE\u4FE1\u7ED1\u5B9A\u6CA1\u6709\u5B8C\u6210": "WeChat setup did not complete",
840
649
  "\u5FAE\u4FE1\u8FDE\u63A5\u6B63\u5E38": "WeChat connection is healthy",
841
650
  "\u5FAE\u4FE1\u8FDE\u63A5\u672A\u5C31\u7EEA": "WeChat connection is not ready",
651
+ "\u5F53\u524D\u6A21\u578B\u4E0D\u652F\u6301\u56FE\u7247\uFF0C\u8BF7\u7528 /models \u67E5\u770B\u53EF\u7528\u6A21\u578B\uFF0C\u518D\u7528 /model <\u5E8F\u53F7> \u5207\u6362\u540E\u91CD\u53D1\u3002": "The current model does not support images. Use /models to list models, then /model <number> to switch and resend.",
652
+ "\u56FE\u7247\u8D85\u8FC7\u5BBF\u4E3B\u5141\u8BB8\u7684\u5927\u5C0F\uFF0C\u8BF7\u538B\u7F29\u540E\u91CD\u8BD5\u3002": "The image exceeds the Host size limit. Compress it and try again.",
653
+ "\u56FE\u7247\u5206\u8FA8\u7387\u8FC7\u9AD8\uFF0C\u8BF7\u538B\u7F29\u540E\u91CD\u8BD5\u3002": "The image resolution is too high. Compress it and try again.",
654
+ "\u56FE\u7247\u5185\u5BB9\u65E0\u6548\u6216\u683C\u5F0F\u4E0D\u53D7\u652F\u6301\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u3002": "The image is invalid or unsupported. Send it again.",
655
+ "\u672A\u80FD\u8BFB\u53D6\u56FE\u7247\u5185\u5BB9\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u3002": "The image could not be read. Send it again.",
656
+ "\u56FE\u7247\u683C\u5F0F\u4E0E\u5B9E\u9645\u5185\u5BB9\u4E0D\u4E00\u81F4\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u3002": "The declared image format does not match its content. Send it again.",
657
+ "\u4E00\u6B21\u53D1\u9001\u7684\u56FE\u7247\u6570\u91CF\u8D85\u8FC7\u5BBF\u4E3B\u9650\u5236\uFF0C\u8BF7\u51CF\u5C11\u540E\u91CD\u8BD5\u3002": "The message exceeds the Host image-count limit. Remove some images and try again.",
658
+ "\u56FE\u7247\u603B\u5927\u5C0F\u8D85\u8FC7\u5BBF\u4E3B\u9650\u5236\uFF0C\u8BF7\u51CF\u5C11\u56FE\u7247\u6216\u538B\u7F29\u540E\u91CD\u8BD5\u3002": "The images exceed the Host total-size limit. Remove or compress some images and try again.",
659
+ "\u56FE\u7247\u4E0B\u8F7D\u5730\u5740\u53D1\u751F\u4E86\u91CD\u5B9A\u5411\uFF0C\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u3002": "The image download redirected and cannot be read.",
660
+ "\u56FE\u7247\u8D85\u8FC7 5 MB\uFF0C\u8BF7\u538B\u7F29\u540E\u91CD\u8BD5\u3002": "The image exceeds 5 MB. Compress it and try again.",
661
+ "\u4E00\u6B21\u53D1\u9001\u7684\u56FE\u7247\u603B\u5927\u5C0F\u8FC7\u5927\uFF0C\u8BF7\u51CF\u5C11\u56FE\u7247\u6570\u91CF\u6216\u538B\u7F29\u540E\u91CD\u8BD5\u3002": "The images are too large in total. Remove or compress some images and try again.",
662
+ "\u56FE\u7247\u4E0B\u8F7D\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u540E\u518D\u8BD5\u3002": "The image download failed. Send it again.",
663
+ "\u6682\u4E0D\u652F\u6301\u8BE5\u56FE\u7247\u683C\u5F0F\uFF0C\u8BF7\u53D1\u9001 JPEG\u3001PNG\u3001WebP \u6216 GIF \u56FE\u7247\u3002": "This image format is unsupported. Send a JPEG, PNG, WebP, or GIF image.",
664
+ "\u6D88\u606F\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002": "Message processing failed. Try again later.",
842
665
  "\u5FAE\u4FE1\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u8D26\u53F7\u5217\u8868": "WeChat did not return a valid account list",
843
666
  "\u5C1A\u672A\u7ED1\u5B9A\u5FAE\u4FE1": "No WeChat account connected yet",
844
667
  "\u7528\u4E8E\u628A\u5FAE\u4FE1\u673A\u5668\u4EBA\u7ED1\u5B9A\u5230 DeepSeek Harness \u7684\u4E00\u6B21\u6027\u4E8C\u7EF4\u7801": "One-time QR code for connecting a WeChat bot to DeepSeek Harness",
@@ -906,6 +729,12 @@ function translateDynamic(text5) {
906
729
  }
907
730
  match = /^二维码剩余 (.+)$/.exec(text5);
908
731
  if (match) return `QR code expires in ${match[1]}`;
732
+ match = /^最近一条消息处理失败:(.+)$/.exec(text5);
733
+ if (match) return `Latest message failed: ${localizeText(match[1])}`;
734
+ match = /^图片下载失败(HTTP (.+)),请重新发送后再试。$/.exec(text5);
735
+ if (match) return `The image download failed (HTTP ${match[1]}). Send it again.`;
736
+ match = /^一次最多只能处理 (\d+) 张图片。$/.exec(text5);
737
+ if (match) return `A message can contain at most ${match[1]} images.`;
909
738
  match = /^状态刷新失败:(.+)$/.exec(text5);
910
739
  if (match) return `Status refresh failed: ${match[1]}`;
911
740
  match = /^状态自动刷新失败:(.+)$/.exec(text5);
@@ -985,7 +814,310 @@ function h2(type, props, ...children) {
985
814
  return React2.createElement(type, localizedProps, ...children.map(localizeChild));
986
815
  }
987
816
 
817
+ // plugin-src/client/agent-preset.js
818
+ var SET_AGENT_PRESET_ENDPOINT = "bot.preset.set";
819
+ var PRESET_ID = /^[a-z0-9][a-z0-9-]*$/;
820
+ var EMPTY_AGENT_PRESET_CATALOG = Object.freeze({
821
+ defaultId: "",
822
+ items: Object.freeze([])
823
+ });
824
+ var AgentPresetCatalogContext = React3.createContext(EMPTY_AGENT_PRESET_CATALOG);
825
+ function normalizeAgentPresetId(value) {
826
+ if (typeof value !== "string") return "";
827
+ const id5 = value.trim();
828
+ return PRESET_ID.test(id5) ? id5 : "";
829
+ }
830
+ function normalizeAgentPresetCatalog(value) {
831
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
832
+ return { defaultId: "", items: [] };
833
+ }
834
+ const items = [];
835
+ const seen = /* @__PURE__ */ new Set();
836
+ for (const entry of Array.isArray(value.items) ? value.items : []) {
837
+ const id5 = typeof entry === "string" ? normalizeAgentPresetId(entry) : normalizeAgentPresetId(entry?.id);
838
+ if (!id5 || seen.has(id5)) continue;
839
+ seen.add(id5);
840
+ const label = typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim().slice(0, 128) : typeof entry?.name === "string" && entry.name.trim() ? entry.name.trim().slice(0, 128) : id5;
841
+ items.push({ id: id5, label });
842
+ }
843
+ return {
844
+ defaultId: normalizeAgentPresetId(value.defaultId),
845
+ items
846
+ };
847
+ }
848
+ function AgentPresetEditor({ agentPreset = "", disabled = false, onSave }) {
849
+ const catalog = React3.useContext(AgentPresetCatalogContext) ?? EMPTY_AGENT_PRESET_CATALOG;
850
+ const current = normalizeAgentPresetId(agentPreset);
851
+ const [saving, setSaving] = React3.useState(false);
852
+ const [error, setError] = React3.useState(null);
853
+ const items = [];
854
+ const seen = /* @__PURE__ */ new Set();
855
+ for (const item of Array.isArray(catalog.items) ? catalog.items : []) {
856
+ if (!item?.id || seen.has(item.id)) continue;
857
+ seen.add(item.id);
858
+ items.push(item);
859
+ }
860
+ const currentUnavailable = Boolean(current && !seen.has(current));
861
+ if (currentUnavailable) items.push({ id: current, label: current, unavailable: true });
862
+ const inheritLabel = "\u8DDF\u968F Host \u9ED8\u8BA4";
863
+ const change = async (event) => {
864
+ const next = event.target.value;
865
+ if (next === current || saving || disabled) return;
866
+ setSaving(true);
867
+ setError(null);
868
+ try {
869
+ await onSave?.(next || null);
870
+ } catch (cause) {
871
+ setError(cause?.message ?? "Agent Preset \u4FEE\u6539\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002");
872
+ } finally {
873
+ setSaving(false);
874
+ }
875
+ };
876
+ return h2(
877
+ "div",
878
+ { className: "dim-preset" },
879
+ h2(
880
+ "div",
881
+ { className: "dim-presetHeader" },
882
+ h2("span", null, "Agent Preset"),
883
+ saving ? h2("span", { className: "dim-presetStatus" }, "\u4FDD\u5B58\u4E2D\u2026") : null
884
+ ),
885
+ React3.createElement(
886
+ "select",
887
+ {
888
+ className: "dim-presetSelect",
889
+ value: current,
890
+ disabled: disabled || saving,
891
+ "aria-label": "Agent Preset",
892
+ onChange: (event) => {
893
+ void change(event);
894
+ }
895
+ },
896
+ h2("option", { value: "" }, inheritLabel),
897
+ ...items.map((item) => h2(
898
+ "option",
899
+ { key: item.id, value: item.id },
900
+ item.unavailable ? [item.id, "\uFF08\u5DF2\u4E0D\u53EF\u7528\uFF09"] : item.label && item.label !== item.id ? `${item.label}\uFF08${item.id}\uFF09` : item.id
901
+ ))
902
+ ),
903
+ h2(
904
+ "small",
905
+ { className: "dim-presetHelp" },
906
+ "\u53EA\u5F71\u54CD\u65B0\u5EFA\u4F1A\u8BDD\uFF1B\u82E5\u5F53\u524D\u804A\u5929\u5DF2\u6709\u4F1A\u8BDD\uFF0C\u5148\u53D1\u9001 /new\uFF0C\u518D\u53D1\u9001\u666E\u901A\u6D88\u606F\u751F\u6548\u3002"
907
+ ),
908
+ error || currentUnavailable ? h2(
909
+ "p",
910
+ { className: "dim-presetError", role: error ? "alert" : "status" },
911
+ error ?? "\u5F53\u524D Agent Preset \u5DF2\u4E0D\u53EF\u7528\uFF0C\u8BF7\u9009\u62E9\u5176\u4ED6 Preset \u6216\u8DDF\u968F Host \u9ED8\u8BA4\u3002"
912
+ ) : null
913
+ );
914
+ }
915
+
916
+ // plugin-src/client/channels/dingtalk/api.js
917
+ var DINGTALK_RPC_CHANNEL = "/dingtalk";
918
+ var DINGTALK_ENDPOINTS = Object.freeze({
919
+ status: "connection.status",
920
+ beginProvisioning: "provision.begin",
921
+ pollProvisioning: "provision.poll",
922
+ cancelProvisioning: "provision.cancel",
923
+ bindCredentials: "bot.bind-credentials",
924
+ reconnectBot: "bot.reconnect",
925
+ deleteBot: "bot.delete",
926
+ setWorkspace: "bot.workspace.set",
927
+ setAgentPreset: SET_AGENT_PRESET_ENDPOINT
928
+ });
929
+ var ACCOUNT_STATES = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
930
+ var SNAPSHOT_STATES = /* @__PURE__ */ new Set(["disconnected", "offline", "provisioning", "connected", "degraded"]);
931
+ var PROVISION_STATES = /* @__PURE__ */ new Set([
932
+ "starting",
933
+ "pending",
934
+ "scanned",
935
+ "authorizing",
936
+ "creating",
937
+ "connecting",
938
+ "connected",
939
+ "expired",
940
+ "failed",
941
+ "cancelled"
942
+ ]);
943
+ var HEALTH_STATES = /* @__PURE__ */ new Set(["healthy", "checking", "degraded", "offline"]);
944
+ var FORBIDDEN_ERROR_FIELDS = /(client[_-]?secret|secret[_-]?ref|device[_-]?code|app[_-]?secret|access[_-]?token|token)/i;
945
+ var QR_DATA_URL = /^data:image\/(?:png|webp);base64,[a-z\d+/]+={0,2}$/i;
946
+ var MAX_QR_SOURCE_LENGTH = 2 * 1024 * 1024;
947
+ function isRecord(value) {
948
+ return value !== null && typeof value === "object" && !Array.isArray(value);
949
+ }
950
+ function optionalString(value, maxLength = 240) {
951
+ if (typeof value !== "string") return void 0;
952
+ const trimmed = value.trim();
953
+ return trimmed ? trimmed.slice(0, maxLength) : void 0;
954
+ }
955
+ function opaqueId(value) {
956
+ const id5 = optionalString(value, 128);
957
+ return id5 && /^[a-z\d_-]+$/i.test(id5) ? id5 : void 0;
958
+ }
959
+ function timestamp(value) {
960
+ if (typeof value === "number" && Number.isFinite(value)) return value;
961
+ if (typeof value === "string" && value.trim()) {
962
+ const parsed = Date.parse(value);
963
+ return Number.isNaN(parsed) ? void 0 : parsed;
964
+ }
965
+ return void 0;
966
+ }
967
+ function nonNegativeInteger(value) {
968
+ const number = Number(value);
969
+ return Number.isSafeInteger(number) && number >= 0 ? number : 0;
970
+ }
971
+ function clamp(value, min, max, fallback) {
972
+ const number = Number(value);
973
+ return Number.isFinite(number) ? Math.min(max, Math.max(min, number)) : fallback;
974
+ }
975
+ function safeErrorCode(value, fallback) {
976
+ const code = optionalString(value, 80);
977
+ return code && /^[a-z][a-z\d_.:-]*$/i.test(code) && !FORBIDDEN_ERROR_FIELDS.test(code) ? code : fallback;
978
+ }
979
+ function sanitizeMessage(value, fallback) {
980
+ const message = optionalString(value, 480) ?? fallback;
981
+ if (FORBIDDEN_ERROR_FIELDS.test(message)) return fallback;
982
+ return message.replace(/([=:]\s*)[^\s,;,。]+/g, "$1\u2022\u2022\u2022\u2022\u2022\u2022").slice(0, 240);
983
+ }
984
+ function normalizeError(value, fallbackCode, fallbackMessage) {
985
+ if (!isRecord(value)) return void 0;
986
+ return {
987
+ code: safeErrorCode(value.code, fallbackCode),
988
+ message: sanitizeMessage(value.message, fallbackMessage)
989
+ };
990
+ }
991
+ function normalizeTestMessage(value) {
992
+ if (!isRecord(value)) return null;
993
+ if (value.sent === true) return { sent: true };
994
+ if (value.sent !== false) return null;
995
+ const code = value.code === "test-target-unavailable" ? "test-target-unavailable" : "test-message-failed";
996
+ return { sent: false, code };
997
+ }
998
+ function unwrapRpcResult(result) {
999
+ if (!isRecord(result) || typeof result.ok !== "boolean") {
1000
+ throw new Error("\u9489\u9489\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94");
1001
+ }
1002
+ if (!result.ok) {
1003
+ const error = new Error(sanitizeMessage(result.error?.message, "\u9489\u9489\u64CD\u4F5C\u5931\u8D25"));
1004
+ error.code = safeErrorCode(result.error?.code, "DINGTALK_RPC_ERROR");
1005
+ throw error;
1006
+ }
1007
+ return result.value;
1008
+ }
1009
+ function safeQrSource(value) {
1010
+ if (typeof value !== "string" || value.length > MAX_QR_SOURCE_LENGTH) return void 0;
1011
+ return QR_DATA_URL.test(value) ? value : void 0;
1012
+ }
1013
+ function normalizeProvisioning(value, now = Date.now()) {
1014
+ const source = isRecord(value?.provisioning) ? value.provisioning : value;
1015
+ if (!isRecord(source)) throw new Error("\u9489\u9489\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u626B\u7801\u7ED1\u5B9A\u8FDB\u5EA6");
1016
+ const attemptId = opaqueId(source.attemptId);
1017
+ if (!attemptId) throw new Error("\u9489\u9489\u626B\u7801\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u7ED1\u5B9A\u4EFB\u52A1");
1018
+ const reportedStatus = optionalString(source.status, 32) ?? optionalString(source.state, 32);
1019
+ const status = PROVISION_STATES.has(reportedStatus) ? reportedStatus : "failed";
1020
+ const expiresAt = timestamp(source.expiresAt) ?? now + clamp(source.expiresIn, 1, 2 * 60 * 60, 10 * 60) * 1e3;
1021
+ const result = {
1022
+ attemptId,
1023
+ status,
1024
+ expiresAt,
1025
+ pollIntervalMs: clamp(source.pollIntervalMs, 1e3, 1e4, 3e3)
1026
+ };
1027
+ const qrCodeDataUrl = safeQrSource(source.qrCodeDataUrl);
1028
+ if (qrCodeDataUrl) result.qrCodeDataUrl = qrCodeDataUrl;
1029
+ if (opaqueId(source.botId)) result.botId = opaqueId(source.botId);
1030
+ if (source.alreadyConnected === true) result.alreadyConnected = true;
1031
+ const error = normalizeError(
1032
+ source.error,
1033
+ "DINGTALK_PROVISION_FAILED",
1034
+ "\u9489\u9489\u673A\u5668\u4EBA\u6CA1\u6709\u63A5\u5165\u5B8C\u6210"
1035
+ );
1036
+ if (error) result.error = error;
1037
+ return result;
1038
+ }
1039
+ function normalizeBot(value) {
1040
+ if (!isRecord(value)) return void 0;
1041
+ const botId = opaqueId(value.botId);
1042
+ if (!botId) return void 0;
1043
+ const bot = isRecord(value.bot) ? value.bot : {};
1044
+ const connected = value.connected === true;
1045
+ const reportedState = ACCOUNT_STATES.has(value.state) ? value.state : "offline";
1046
+ const state = connected ? "connected" : reportedState === "connected" ? "connecting" : reportedState;
1047
+ const health = isRecord(value.health) ? value.health : {};
1048
+ const stats = isRecord(value.stats) ? value.stats : {};
1049
+ return {
1050
+ botId,
1051
+ state,
1052
+ connected,
1053
+ configured: value.configured !== false,
1054
+ workspace: optionalString(value.workspace, 4096) ?? "",
1055
+ agentPreset: normalizeAgentPresetId(value.agentPreset),
1056
+ bot: {
1057
+ name: optionalString(bot.name, 100) ?? "\u9489\u9489\u673A\u5668\u4EBA",
1058
+ clientIdMasked: optionalString(bot.clientIdMasked, 140) ?? "\u5DF2\u5B89\u5168\u4FDD\u5B58"
1059
+ },
1060
+ health: {
1061
+ status: HEALTH_STATES.has(health.status) ? health.status : connected ? "healthy" : "offline",
1062
+ summary: optionalString(health.summary, 200) ?? (connected ? "\u9489\u9489 Stream \u957F\u8FDE\u63A5\u8FD0\u884C\u6B63\u5E38" : "\u9489\u9489\u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA"),
1063
+ lastCheckedAt: timestamp(health.lastCheckedAt),
1064
+ lastConnectedAt: timestamp(health.lastConnectedAt)
1065
+ },
1066
+ stats: {
1067
+ messagesReceived: nonNegativeInteger(stats.messagesReceived),
1068
+ messagesReplied: nonNegativeInteger(stats.messagesReplied)
1069
+ },
1070
+ error: normalizeError(value.error, "DINGTALK_ACCOUNT_ERROR", "\u9489\u9489\u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA") ?? null
1071
+ };
1072
+ }
1073
+ function normalizeSnapshot(value) {
1074
+ const source = isRecord(value?.snapshot) ? value.snapshot : value;
1075
+ if (!isRecord(source) || !Array.isArray(source.bots)) {
1076
+ throw new Error("\u9489\u9489\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u673A\u5668\u4EBA\u5217\u8868");
1077
+ }
1078
+ const seen = /* @__PURE__ */ new Set();
1079
+ const bots = source.bots.map(normalizeBot).filter((bot) => {
1080
+ if (!bot || seen.has(bot.botId)) return false;
1081
+ seen.add(bot.botId);
1082
+ return true;
1083
+ });
1084
+ return {
1085
+ schemaVersion: Number.isSafeInteger(source.schemaVersion) ? source.schemaVersion : 1,
1086
+ revision: nonNegativeInteger(source.revision),
1087
+ state: SNAPSHOT_STATES.has(source.state) ? source.state : "offline",
1088
+ bots,
1089
+ totals: {
1090
+ configured: bots.length,
1091
+ connected: bots.filter((bot) => bot.connected).length
1092
+ },
1093
+ provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null,
1094
+ testMessage: normalizeTestMessage(source.testMessage),
1095
+ agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog)
1096
+ };
1097
+ }
1098
+ function connectionTestFeedback(result) {
1099
+ if (result?.sent === true) return "\u9489\u9489\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\u3002";
1100
+ if (result?.code === "test-target-unavailable") {
1101
+ return "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002\u673A\u5668\u4EBA\u5C1A\u672A\u6536\u5230\u53EF\u7528\u4E8E\u6D4B\u8BD5\u7684\u79C1\u804A\u6D88\u606F\u3002";
1102
+ }
1103
+ return result ? "\u9489\u9489\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002" : null;
1104
+ }
1105
+ function presentError(error) {
1106
+ return {
1107
+ code: safeErrorCode(error?.code, "DINGTALK_ERROR"),
1108
+ message: sanitizeMessage(error?.message, "\u9489\u9489\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5")
1109
+ };
1110
+ }
1111
+ function formatRemaining(milliseconds) {
1112
+ const seconds = Math.max(0, Math.ceil(Number(milliseconds) / 1e3) || 0);
1113
+ return `${String(Math.floor(seconds / 60)).padStart(2, "0")}:${String(seconds % 60).padStart(2, "0")}`;
1114
+ }
1115
+
1116
+ // plugin-src/client/channels/dingtalk/index.js
1117
+ var React8 = __toESM(require("react"), 1);
1118
+
988
1119
  // plugin-src/client/credential-binding.js
1120
+ var React4 = __toESM(require("react"), 1);
989
1121
  function ActionIcon({ children }) {
990
1122
  return h2("svg", {
991
1123
  className: "dim-actionIcon",
@@ -1045,9 +1177,9 @@ function CredentialBindingPanel({
1045
1177
  onSubmit,
1046
1178
  onCancel
1047
1179
  }) {
1048
- const [identity, setIdentity] = React3.useState("");
1049
- const [secret, setSecret] = React3.useState("");
1050
- const headingId = React3.useId();
1180
+ const [identity, setIdentity] = React4.useState("");
1181
+ const [secret, setSecret] = React4.useState("");
1182
+ const headingId = React4.useId();
1051
1183
  const hasIdentity = Boolean(identityLabel);
1052
1184
  const submit = (event) => {
1053
1185
  event.preventDefault();
@@ -1126,10 +1258,10 @@ function CredentialBindingPanel({
1126
1258
  }
1127
1259
 
1128
1260
  // plugin-src/client/workspace-editor.js
1129
- var React5 = __toESM(require("react"), 1);
1261
+ var React6 = __toESM(require("react"), 1);
1130
1262
 
1131
1263
  // plugin-src/client/workspace-directory-picker.js
1132
- var React4 = __toESM(require("react"), 1);
1264
+ var React5 = __toESM(require("react"), 1);
1133
1265
  var import_react_dom = require("react-dom");
1134
1266
  function pickerErrorCode(error) {
1135
1267
  return error?.rpcError?.code ?? error?.code;
@@ -1141,7 +1273,7 @@ function pickerErrorMessage(error) {
1141
1273
  return error?.rpcError?.message ?? error?.message ?? "\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF0C\u8BF7\u91CD\u8BD5\u3002";
1142
1274
  }
1143
1275
  function FolderIcon() {
1144
- return React4.createElement(
1276
+ return React5.createElement(
1145
1277
  "svg",
1146
1278
  {
1147
1279
  viewBox: "0 0 24 24",
@@ -1152,11 +1284,11 @@ function FolderIcon() {
1152
1284
  strokeLinejoin: "round",
1153
1285
  "aria-hidden": "true"
1154
1286
  },
1155
- React4.createElement("path", { d: "M3.5 7.25A2.25 2.25 0 0 1 5.75 5h4.1l1.8 2h6.6a2.25 2.25 0 0 1 2.25 2.25v7A2.75 2.75 0 0 1 17.75 19h-12A2.25 2.25 0 0 1 3.5 16.75v-9.5Z" })
1287
+ React5.createElement("path", { d: "M3.5 7.25A2.25 2.25 0 0 1 5.75 5h4.1l1.8 2h6.6a2.25 2.25 0 0 1 2.25 2.25v7A2.75 2.75 0 0 1 17.75 19h-12A2.25 2.25 0 0 1 3.5 16.75v-9.5Z" })
1156
1288
  );
1157
1289
  }
1158
1290
  function ChevronIcon() {
1159
- return React4.createElement("svg", {
1291
+ return React5.createElement("svg", {
1160
1292
  viewBox: "0 0 20 20",
1161
1293
  fill: "none",
1162
1294
  stroke: "currentColor",
@@ -1164,7 +1296,7 @@ function ChevronIcon() {
1164
1296
  strokeLinecap: "round",
1165
1297
  strokeLinejoin: "round",
1166
1298
  "aria-hidden": "true"
1167
- }, React4.createElement("path", { d: "m7.5 4.5 5 5.5-5 5.5" }));
1299
+ }, React5.createElement("path", { d: "m7.5 4.5 5 5.5-5 5.5" }));
1168
1300
  }
1169
1301
  function displayCrumbs(listing) {
1170
1302
  const homeIndex = listing.crumbs.findIndex((crumb) => crumb.path === listing.home);
@@ -1180,25 +1312,25 @@ function WorkspaceDirectoryPicker({
1180
1312
  onPicked,
1181
1313
  onCancel
1182
1314
  }) {
1183
- const [listing, setListing] = React4.useState(null);
1184
- const [loading, setLoading] = React4.useState(false);
1185
- const [error, setError] = React4.useState(null);
1186
- const [showHidden, setShowHidden] = React4.useState(false);
1187
- const [retryKey, setRetryKey] = React4.useState(0);
1188
- const requestRef = React4.useRef(0);
1189
- const controllerRef = React4.useRef(null);
1190
- const dialogRef = React4.useRef(null);
1191
- const bodyRef = React4.useRef(null);
1192
- const titleId = React4.useId();
1193
- const noticeId = React4.useId();
1194
- const initialPathRef = React4.useRef(startPath);
1195
- const onPickedRef = React4.useRef(onPicked);
1196
- const onCancelRef = React4.useRef(onCancel);
1197
- const busyRef = React4.useRef(busy);
1315
+ const [listing, setListing] = React5.useState(null);
1316
+ const [loading, setLoading] = React5.useState(false);
1317
+ const [error, setError] = React5.useState(null);
1318
+ const [showHidden, setShowHidden] = React5.useState(false);
1319
+ const [retryKey, setRetryKey] = React5.useState(0);
1320
+ const requestRef = React5.useRef(0);
1321
+ const controllerRef = React5.useRef(null);
1322
+ const dialogRef = React5.useRef(null);
1323
+ const bodyRef = React5.useRef(null);
1324
+ const titleId = React5.useId();
1325
+ const noticeId = React5.useId();
1326
+ const initialPathRef = React5.useRef(startPath);
1327
+ const onPickedRef = React5.useRef(onPicked);
1328
+ const onCancelRef = React5.useRef(onCancel);
1329
+ const busyRef = React5.useRef(busy);
1198
1330
  onPickedRef.current = onPicked;
1199
1331
  onCancelRef.current = onCancel;
1200
1332
  busyRef.current = busy;
1201
- const loadDirectory = React4.useCallback(async (path, { reportError = true } = {}) => {
1333
+ const loadDirectory = React5.useCallback(async (path, { reportError = true } = {}) => {
1202
1334
  const request = requestRef.current + 1;
1203
1335
  requestRef.current = request;
1204
1336
  controllerRef.current?.abort();
@@ -1221,7 +1353,7 @@ function WorkspaceDirectoryPicker({
1221
1353
  if (request === requestRef.current) setLoading(false);
1222
1354
  }
1223
1355
  }, [picker]);
1224
- React4.useEffect(() => {
1356
+ React5.useEffect(() => {
1225
1357
  if (!open) return void 0;
1226
1358
  let active = true;
1227
1359
  setListing(null);
@@ -1299,10 +1431,10 @@ function WorkspaceDirectoryPicker({
1299
1431
  "nav",
1300
1432
  { className: "dim-directoryCrumbs", "aria-label": "\u5F53\u524D\u76EE\u5F55" },
1301
1433
  crumbs.map((crumb, index) => h2(
1302
- React4.Fragment,
1434
+ React5.Fragment,
1303
1435
  { key: crumb.path },
1304
1436
  index > 0 ? h2("span", { className: "dim-directoryCrumbSeparator", "aria-hidden": "true" }, "\u203A") : null,
1305
- React4.createElement("button", {
1437
+ React5.createElement("button", {
1306
1438
  type: "button",
1307
1439
  title: crumb.path,
1308
1440
  disabled: loading || busy,
@@ -1323,7 +1455,7 @@ function WorkspaceDirectoryPicker({
1323
1455
  ) : listing ? entries.length > 0 ? h2("ul", { className: "dim-directoryList" }, entries.map((entry) => h2(
1324
1456
  "li",
1325
1457
  { key: entry.path },
1326
- React4.createElement(
1458
+ React5.createElement(
1327
1459
  "button",
1328
1460
  {
1329
1461
  type: "button",
@@ -1332,7 +1464,7 @@ function WorkspaceDirectoryPicker({
1332
1464
  onClick: () => void loadDirectory(entry.path)
1333
1465
  },
1334
1466
  h2("span", { className: "dim-directoryFolder" }, h2(FolderIcon)),
1335
- React4.createElement("span", { className: "dim-directoryName" }, entry.name),
1467
+ React5.createElement("span", { className: "dim-directoryName" }, entry.name),
1336
1468
  h2("span", { className: "dim-directoryChevron" }, h2(ChevronIcon))
1337
1469
  )
1338
1470
  ))) : h2(
@@ -1385,21 +1517,21 @@ function WorkspaceDirectoryPicker({
1385
1517
  }
1386
1518
 
1387
1519
  // plugin-src/client/workspace-editor.js
1388
- var WorkspaceDirectoryPickerContext = React5.createContext(null);
1520
+ var WorkspaceDirectoryPickerContext = React6.createContext(null);
1389
1521
  function WorkspaceEditor({ workspace, directoryPicker, disabled = false, onSave }) {
1390
- const sharedDirectoryPicker = React5.useContext(WorkspaceDirectoryPickerContext);
1522
+ const sharedDirectoryPicker = React6.useContext(WorkspaceDirectoryPickerContext);
1391
1523
  const activeDirectoryPicker = directoryPicker ?? sharedDirectoryPicker;
1392
- const [open, setOpen] = React5.useState(false);
1393
- const [saving, setSaving] = React5.useState(false);
1394
- const [error, setError] = React5.useState(null);
1395
- const editButtonRef = React5.useRef(null);
1396
- const savingRef = React5.useRef(false);
1397
- const close = React5.useCallback(() => {
1524
+ const [open, setOpen] = React6.useState(false);
1525
+ const [saving, setSaving] = React6.useState(false);
1526
+ const [error, setError] = React6.useState(null);
1527
+ const editButtonRef = React6.useRef(null);
1528
+ const savingRef = React6.useRef(false);
1529
+ const close = React6.useCallback(() => {
1398
1530
  setOpen(false);
1399
1531
  setError(null);
1400
1532
  queueMicrotask(() => editButtonRef.current?.focus?.());
1401
1533
  }, []);
1402
- const pick = React5.useCallback(async (value) => {
1534
+ const pick = React6.useCallback(async (value) => {
1403
1535
  if (!value || savingRef.current || disabled) return;
1404
1536
  if (value === workspace) {
1405
1537
  close();
@@ -1436,7 +1568,7 @@ function WorkspaceEditor({ workspace, directoryPicker, disabled = false, onSave
1436
1568
  disabled: disabled || !activeDirectoryPicker
1437
1569
  }, "\u9009\u62E9\u76EE\u5F55")
1438
1570
  ),
1439
- workspace ? React5.createElement("code", {
1571
+ workspace ? React6.createElement("code", {
1440
1572
  className: "dim-workspacePath",
1441
1573
  title: workspace
1442
1574
  }, workspace) : h2("code", { className: "dim-workspacePath" }, "\u672A\u8BBE\u7F6E"),
@@ -1453,10 +1585,10 @@ function WorkspaceEditor({ workspace, directoryPicker, disabled = false, onSave
1453
1585
  }
1454
1586
 
1455
1587
  // plugin-src/client/workspace-snapshot-fence.js
1456
- var React6 = __toESM(require("react"), 1);
1588
+ var React7 = __toESM(require("react"), 1);
1457
1589
  function useWorkspaceSnapshotFence() {
1458
- const state = React6.useRef({ version: 0, pendingMutations: 0 });
1459
- return React6.useMemo(() => Object.freeze({
1590
+ const state = React7.useRef({ version: 0, pendingMutations: 0 });
1591
+ return React7.useMemo(() => Object.freeze({
1460
1592
  beginStatus() {
1461
1593
  return state.current.pendingMutations === 0 ? state.current.version : null;
1462
1594
  },
@@ -1636,7 +1768,7 @@ function DingtalkIcon({ size = 28 }) {
1636
1768
  d: "M37.05 22.783c-6.758-5.216-14.378-12.128-22.73-19.538-.655-.585-1.242-.354-1.536.42-1.88 4.973-.058 9.386 2.889 11.932s7.368 4.912 10.058 6.155c.105.049.013.203-.093.163-4.953-2.182-8.397-3.765-13.07-7.368-.497-.388-1.01-.242-1.07.521-.384 4.748 2.657 8.483 6.058 9.745 2.1.781 4.398 1.212 6.53 1.474.109.015.084.178-.027.178-2.747.01-6.058-.654-8.935-1.751-.606-.233-.818.25-.722.633.491 2.008 2.974 5.076 6.926 5.73a12 12 0 0 0 2.228.115c.164 0 .208.089.154.217q-2.685 4.6-2.803 4.797c-.091.152-.036.275.156.275h3.543c.164 0 .264.106.18.246l-4.958 8.196c-.191.328.035.565.395.301s15.212-11.133 15.636-11.448c.195-.142.148-.327-.124-.327h-3.18c-.206 0-.252-.14-.111-.28.14-.141 3.602-3.594 4.837-4.888 1.283-1.35 1.938-3.825-.231-5.498"
1637
1769
  }));
1638
1770
  }
1639
- var Button = React7.forwardRef(function Button2({ children, kind = "secondary", className = "", ...props }, ref) {
1771
+ var Button = React8.forwardRef(function Button2({ children, kind = "secondary", className = "", ...props }, ref) {
1640
1772
  return h2("button", {
1641
1773
  ...props,
1642
1774
  ref,
@@ -1732,13 +1864,13 @@ function EmptyView({ busy, onStart }) {
1732
1864
  );
1733
1865
  }
1734
1866
  function QrPanel({ provision, now, busy, onRefresh, onCancel }) {
1735
- const [imageFailed, setImageFailed] = React7.useState(false);
1867
+ const [imageFailed, setImageFailed] = React8.useState(false);
1736
1868
  const source = safeQrSource(provision.qrCodeDataUrl);
1737
1869
  const remaining = Math.max(0, provision.expiresAt - now);
1738
1870
  const expired = remaining === 0 || provision.status === "expired";
1739
1871
  const duration = Math.max(1, provision.durationMs ?? 10 * 6e4);
1740
1872
  const progress = Math.round(Math.min(1, remaining / duration) * 100);
1741
- React7.useEffect(() => setImageFailed(false), [source]);
1873
+ React8.useEffect(() => setImageFailed(false), [source]);
1742
1874
  return h2(
1743
1875
  "div",
1744
1876
  { className: "ddt-card dim-surfaceCard" },
@@ -1855,8 +1987,8 @@ function checkedTime(value) {
1855
1987
  }
1856
1988
  }
1857
1989
  function RemoveConfirmation({ account, busy, onConfirm, onCancel }) {
1858
- const cancelRef = React7.useRef(null);
1859
- React7.useEffect(() => cancelRef.current?.focus(), []);
1990
+ const cancelRef = React8.useRef(null);
1991
+ React8.useEffect(() => cancelRef.current?.focus(), []);
1860
1992
  return h2(
1861
1993
  "div",
1862
1994
  {
@@ -1888,6 +2020,7 @@ function AccountCard({
1888
2020
  removing,
1889
2021
  onReconnect,
1890
2022
  onWorkspaceSave,
2023
+ onAgentPresetSave,
1891
2024
  onRequestRemove,
1892
2025
  onConfirmRemove,
1893
2026
  onCancelRemove
@@ -1944,6 +2077,11 @@ function AccountCard({
1944
2077
  disabled: Boolean(busy),
1945
2078
  onSave: onWorkspaceSave
1946
2079
  }),
2080
+ h2(AgentPresetEditor, {
2081
+ agentPreset: account.agentPreset,
2082
+ disabled: Boolean(busy),
2083
+ onSave: onAgentPresetSave
2084
+ }),
1947
2085
  h2(
1948
2086
  "div",
1949
2087
  { className: "ddt-accountFooter dim-cardFooter" },
@@ -1995,6 +2133,7 @@ function AccountList(props) {
1995
2133
  removing: props.removeTarget === account.botId,
1996
2134
  onReconnect: () => props.onReconnect(account),
1997
2135
  onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
2136
+ onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(account, agentPreset),
1998
2137
  onRequestRemove: () => props.onRequestRemove(account),
1999
2138
  onConfirmRemove: () => props.onConfirmRemove(account),
2000
2139
  onCancelRemove: props.onCancelRemove
@@ -2004,29 +2143,30 @@ function AccountList(props) {
2004
2143
  }
2005
2144
  var EMPTY_TOTALS = Object.freeze({ configured: 0, connected: 0 });
2006
2145
  function DingtalkSettingsTab({ rpcCall }) {
2007
- const [model, setModel] = React7.useState({
2146
+ const [model, setModel] = React8.useState({
2008
2147
  phase: "loading",
2009
2148
  bots: [],
2010
2149
  totals: EMPTY_TOTALS,
2011
2150
  revision: 0,
2012
- error: null
2151
+ error: null,
2152
+ agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG
2013
2153
  });
2014
- const [provision, setProvision] = React7.useState(null);
2015
- const [busy, setBusy] = React7.useState(false);
2016
- const [busyByBot, setBusyByBot] = React7.useState({});
2017
- const [feedbackByBot, setFeedbackByBot] = React7.useState({});
2018
- const [removeTarget, setRemoveTarget] = React7.useState(null);
2019
- const [credentialOpen, setCredentialOpen] = React7.useState(false);
2020
- const [credentialError, setCredentialError] = React7.useState(null);
2021
- const [notice, setNotice] = React7.useState("");
2022
- const [now, setNow] = React7.useState(() => Date.now());
2023
- const addButtonRef = React7.useRef(null);
2024
- const mountedRef = React7.useRef(true);
2025
- const statusRequestRef = React7.useRef(0);
2154
+ const [provision, setProvision] = React8.useState(null);
2155
+ const [busy, setBusy] = React8.useState(false);
2156
+ const [busyByBot, setBusyByBot] = React8.useState({});
2157
+ const [feedbackByBot, setFeedbackByBot] = React8.useState({});
2158
+ const [removeTarget, setRemoveTarget] = React8.useState(null);
2159
+ const [credentialOpen, setCredentialOpen] = React8.useState(false);
2160
+ const [credentialError, setCredentialError] = React8.useState(null);
2161
+ const [notice, setNotice] = React8.useState("");
2162
+ const [now, setNow] = React8.useState(() => Date.now());
2163
+ const addButtonRef = React8.useRef(null);
2164
+ const mountedRef = React8.useRef(true);
2165
+ const statusRequestRef = React8.useRef(0);
2026
2166
  const workspaceFence = useWorkspaceSnapshotFence();
2027
- const noticeFrameRef = React7.useRef(null);
2028
- const focusFrameRef = React7.useRef(null);
2029
- React7.useEffect(() => {
2167
+ const noticeFrameRef = React8.useRef(null);
2168
+ const focusFrameRef = React8.useRef(null);
2169
+ React8.useEffect(() => {
2030
2170
  mountedRef.current = true;
2031
2171
  return () => {
2032
2172
  mountedRef.current = false;
@@ -2041,8 +2181,8 @@ function DingtalkSettingsTab({ rpcCall }) {
2041
2181
  }
2042
2182
  };
2043
2183
  }, []);
2044
- React7.useEffect(() => installDingtalkStyles(), []);
2045
- const announce = React7.useCallback((message) => {
2184
+ React8.useEffect(() => installDingtalkStyles(), []);
2185
+ const announce = React8.useCallback((message) => {
2046
2186
  if (!mountedRef.current) return;
2047
2187
  if (noticeFrameRef.current !== null) {
2048
2188
  window.cancelAnimationFrame(noticeFrameRef.current);
@@ -2056,7 +2196,7 @@ function DingtalkSettingsTab({ rpcCall }) {
2056
2196
  });
2057
2197
  }
2058
2198
  }, []);
2059
- const discardStaleFeedback = React7.useCallback((snapshot) => {
2199
+ const discardStaleFeedback = React8.useCallback((snapshot) => {
2060
2200
  const botsById = new Map(snapshot.bots.map((bot) => [bot.botId, bot]));
2061
2201
  setFeedbackByBot((current) => {
2062
2202
  let changed = false;
@@ -2071,7 +2211,7 @@ function DingtalkSettingsTab({ rpcCall }) {
2071
2211
  return changed ? next : current;
2072
2212
  });
2073
2213
  }, []);
2074
- const focusAddButton = React7.useCallback(() => {
2214
+ const focusAddButton = React8.useCallback(() => {
2075
2215
  if (!mountedRef.current) return;
2076
2216
  if (focusFrameRef.current !== null) window.cancelAnimationFrame(focusFrameRef.current);
2077
2217
  focusFrameRef.current = window.requestAnimationFrame(() => {
@@ -2079,11 +2219,11 @@ function DingtalkSettingsTab({ rpcCall }) {
2079
2219
  if (mountedRef.current) addButtonRef.current?.focus();
2080
2220
  });
2081
2221
  }, []);
2082
- const invoke = React7.useCallback(async (endpoint, payload = {}, signal) => {
2222
+ const invoke = React8.useCallback(async (endpoint, payload = {}, signal) => {
2083
2223
  if (typeof rpcCall !== "function") throw new TypeError("\u9489\u9489\u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5");
2084
2224
  return unwrapRpcResult(await rpcCall(endpoint, payload, signal));
2085
2225
  }, [rpcCall]);
2086
- const loadStatus = React7.useCallback(async ({
2226
+ const loadStatus = React8.useCallback(async ({
2087
2227
  signal,
2088
2228
  silent = false,
2089
2229
  restoreProvisioning = false
@@ -2105,7 +2245,8 @@ function DingtalkSettingsTab({ rpcCall }) {
2105
2245
  bots: snapshot.bots,
2106
2246
  totals: snapshot.totals,
2107
2247
  revision: snapshot.revision,
2108
- error: null
2248
+ error: null,
2249
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
2109
2250
  });
2110
2251
  discardStaleFeedback(snapshot);
2111
2252
  if (restoreProvisioning && snapshot.provisioning) {
@@ -2126,12 +2267,12 @@ function DingtalkSettingsTab({ rpcCall }) {
2126
2267
  return void 0;
2127
2268
  }
2128
2269
  }, [discardStaleFeedback, invoke, workspaceFence]);
2129
- React7.useEffect(() => {
2270
+ React8.useEffect(() => {
2130
2271
  const controller = new AbortController();
2131
2272
  void loadStatus({ signal: controller.signal, restoreProvisioning: true });
2132
2273
  return () => controller.abort();
2133
2274
  }, [loadStatus]);
2134
- React7.useEffect(() => {
2275
+ React8.useEffect(() => {
2135
2276
  if (model.phase !== "ready") return void 0;
2136
2277
  const controller = new AbortController();
2137
2278
  let running = false;
@@ -2150,14 +2291,14 @@ function DingtalkSettingsTab({ rpcCall }) {
2150
2291
  window.clearInterval(timer);
2151
2292
  };
2152
2293
  }, [loadStatus, model.phase]);
2153
- React7.useEffect(() => {
2294
+ React8.useEffect(() => {
2154
2295
  if (!provision || !ACTIVE_PROVISION_STATES.has(provision.status)) return void 0;
2155
2296
  const timer = window.setInterval(() => {
2156
2297
  if (mountedRef.current) setNow(Date.now());
2157
2298
  }, 1e3);
2158
2299
  return () => window.clearInterval(timer);
2159
2300
  }, [provision?.attemptId, provision?.status]);
2160
- const startProvisioning = React7.useCallback(async ({ replace = false } = {}) => {
2301
+ const startProvisioning = React8.useCallback(async ({ replace = false } = {}) => {
2161
2302
  if (!mountedRef.current) return;
2162
2303
  setCredentialOpen(false);
2163
2304
  setCredentialError(null);
@@ -2195,7 +2336,7 @@ function DingtalkSettingsTab({ rpcCall }) {
2195
2336
  if (mountedRef.current) setBusy(false);
2196
2337
  }
2197
2338
  }, [announce, invoke, provision?.attemptId]);
2198
- const bindCredentials = React7.useCallback(async ({ identity, secret }) => {
2339
+ const bindCredentials = React8.useCallback(async ({ identity, secret }) => {
2199
2340
  if (!mountedRef.current) return;
2200
2341
  const snapshotVersion = workspaceFence.beginMutation();
2201
2342
  setBusy(true);
@@ -2212,7 +2353,8 @@ function DingtalkSettingsTab({ rpcCall }) {
2212
2353
  bots: snapshot.bots,
2213
2354
  totals: snapshot.totals,
2214
2355
  revision: snapshot.revision,
2215
- error: null
2356
+ error: null,
2357
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
2216
2358
  });
2217
2359
  discardStaleFeedback(snapshot);
2218
2360
  }
@@ -2226,7 +2368,7 @@ function DingtalkSettingsTab({ rpcCall }) {
2226
2368
  if (mountedRef.current) setBusy(false);
2227
2369
  }
2228
2370
  }, [announce, discardStaleFeedback, invoke, loadStatus, workspaceFence]);
2229
- const cancelProvisioning = React7.useCallback(async () => {
2371
+ const cancelProvisioning = React8.useCallback(async () => {
2230
2372
  if (!mountedRef.current) return;
2231
2373
  setBusy(true);
2232
2374
  try {
@@ -2244,7 +2386,7 @@ function DingtalkSettingsTab({ rpcCall }) {
2244
2386
  if (mountedRef.current) setBusy(false);
2245
2387
  }
2246
2388
  }, [announce, focusAddButton, invoke, provision?.attemptId, provision?.status]);
2247
- React7.useEffect(() => {
2389
+ React8.useEffect(() => {
2248
2390
  const attemptId = provision?.attemptId;
2249
2391
  if (!attemptId || !ACTIVE_PROVISION_STATES.has(provision.status)) return void 0;
2250
2392
  const controller = new AbortController();
@@ -2303,7 +2445,7 @@ function DingtalkSettingsTab({ rpcCall }) {
2303
2445
  timer = null;
2304
2446
  };
2305
2447
  }, [announce, invoke, loadStatus, provision?.attemptId, provision?.pollIntervalMs, provision?.status]);
2306
- const setBotBusy = React7.useCallback((botId, operation) => {
2448
+ const setBotBusy = React8.useCallback((botId, operation) => {
2307
2449
  if (!mountedRef.current) return;
2308
2450
  setBusyByBot((current) => {
2309
2451
  const next = { ...current };
@@ -2312,7 +2454,7 @@ function DingtalkSettingsTab({ rpcCall }) {
2312
2454
  return next;
2313
2455
  });
2314
2456
  }, []);
2315
- const runBotAction = React7.useCallback(async ({ account, operation, endpoint, payload, success }) => {
2457
+ const runBotAction = React8.useCallback(async ({ account, operation, endpoint, payload, success }) => {
2316
2458
  if (!mountedRef.current) return void 0;
2317
2459
  const snapshotVersion = workspaceFence.beginMutation();
2318
2460
  setBotBusy(account.botId, operation);
@@ -2332,7 +2474,8 @@ function DingtalkSettingsTab({ rpcCall }) {
2332
2474
  bots: snapshot.bots,
2333
2475
  totals: snapshot.totals,
2334
2476
  revision: snapshot.revision,
2335
- error: null
2477
+ error: null,
2478
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
2336
2479
  });
2337
2480
  discardStaleFeedback(snapshot);
2338
2481
  }
@@ -2365,7 +2508,7 @@ function DingtalkSettingsTab({ rpcCall }) {
2365
2508
  if (mountedRef.current) setBotBusy(account.botId, null);
2366
2509
  }
2367
2510
  }, [announce, discardStaleFeedback, invoke, loadStatus, setBotBusy, workspaceFence]);
2368
- const reconnect = React7.useCallback((account) => runBotAction({
2511
+ const reconnect = React8.useCallback((account) => runBotAction({
2369
2512
  account,
2370
2513
  operation: "reconnect",
2371
2514
  endpoint: DINGTALK_ENDPOINTS.reconnectBot,
@@ -2376,7 +2519,7 @@ function DingtalkSettingsTab({ rpcCall }) {
2376
2519
  return connectionTestFeedback(snapshot.testMessage) ?? "\u9489\u9489\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002";
2377
2520
  }
2378
2521
  }), [runBotAction]);
2379
- const saveWorkspace = React7.useCallback(async (account, workspace) => {
2522
+ const saveWorkspace = React8.useCallback(async (account, workspace) => {
2380
2523
  const workspaceVersion = workspaceFence.beginMutation();
2381
2524
  setBotBusy(account.botId, "workspace");
2382
2525
  try {
@@ -2390,7 +2533,8 @@ function DingtalkSettingsTab({ rpcCall }) {
2390
2533
  bots: snapshot.bots,
2391
2534
  totals: snapshot.totals,
2392
2535
  revision: snapshot.revision,
2393
- error: null
2536
+ error: null,
2537
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
2394
2538
  });
2395
2539
  discardStaleFeedback(snapshot);
2396
2540
  }
@@ -2400,7 +2544,32 @@ function DingtalkSettingsTab({ rpcCall }) {
2400
2544
  if (mountedRef.current) setBotBusy(account.botId, null);
2401
2545
  }
2402
2546
  }, [discardStaleFeedback, invoke, loadStatus, setBotBusy, workspaceFence]);
2403
- const remove = React7.useCallback(async (account) => {
2547
+ const saveAgentPreset = React8.useCallback(async (account, agentPreset) => {
2548
+ const snapshotVersion = workspaceFence.beginMutation();
2549
+ setBotBusy(account.botId, "preset");
2550
+ try {
2551
+ const snapshot = normalizeSnapshot(await invoke(
2552
+ DINGTALK_ENDPOINTS.setAgentPreset,
2553
+ { botId: account.botId, agentPreset }
2554
+ ));
2555
+ if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
2556
+ setModel({
2557
+ phase: "ready",
2558
+ bots: snapshot.bots,
2559
+ totals: snapshot.totals,
2560
+ revision: snapshot.revision,
2561
+ error: null,
2562
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
2563
+ });
2564
+ discardStaleFeedback(snapshot);
2565
+ }
2566
+ } finally {
2567
+ const shouldRefresh = workspaceFence.endMutation();
2568
+ if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
2569
+ if (mountedRef.current) setBotBusy(account.botId, null);
2570
+ }
2571
+ }, [discardStaleFeedback, invoke, loadStatus, setBotBusy, workspaceFence]);
2572
+ const remove = React8.useCallback(async (account) => {
2404
2573
  const snapshot = await runBotAction({
2405
2574
  account,
2406
2575
  operation: "delete",
@@ -2454,7 +2623,9 @@ function DingtalkSettingsTab({ rpcCall }) {
2454
2623
  setCredentialError(null);
2455
2624
  }
2456
2625
  }) : null;
2457
- return h2(
2626
+ return h2(AgentPresetCatalogContext.Provider, {
2627
+ value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
2628
+ }, h2(
2458
2629
  "section",
2459
2630
  { className: "ddt-page dim-channelPage", "aria-label": "\u9489\u9489\u8BBE\u7F6E" },
2460
2631
  h2(Heading, {
@@ -2482,7 +2653,7 @@ function DingtalkSettingsTab({ rpcCall }) {
2482
2653
  h2(Button, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6")
2483
2654
  )
2484
2655
  ) : h2(
2485
- React7.Fragment,
2656
+ React8.Fragment,
2486
2657
  null,
2487
2658
  credentialView,
2488
2659
  provisionView,
@@ -2494,12 +2665,13 @@ function DingtalkSettingsTab({ rpcCall }) {
2494
2665
  removeTarget,
2495
2666
  onReconnect: (account) => void reconnect(account),
2496
2667
  onWorkspaceSave: saveWorkspace,
2668
+ onAgentPresetSave: saveAgentPreset,
2497
2669
  onRequestRemove: (account) => setRemoveTarget(account.botId),
2498
2670
  onConfirmRemove: (account) => void remove(account),
2499
2671
  onCancelRemove: () => setRemoveTarget(null)
2500
2672
  }) : null
2501
2673
  )
2502
- );
2674
+ ));
2503
2675
  }
2504
2676
 
2505
2677
  // plugin-src/client/channels/shared/token-api.js
@@ -2524,7 +2696,8 @@ var TOKEN_BOT_ENDPOINTS = Object.freeze({
2524
2696
  bindCredentials: "bot.bind-credentials",
2525
2697
  reconnectBot: "bot.reconnect",
2526
2698
  deleteBot: "bot.delete",
2527
- setWorkspace: "bot.workspace.set"
2699
+ setWorkspace: "bot.workspace.set",
2700
+ setAgentPreset: SET_AGENT_PRESET_ENDPOINT
2528
2701
  });
2529
2702
  function createTokenChannelApi(channel4, connectionSummary, {
2530
2703
  normalizeBotExtension = () => ({})
@@ -2550,6 +2723,7 @@ function createTokenChannelApi(channel4, connectionSummary, {
2550
2723
  connected,
2551
2724
  state: connected ? "connected" : state,
2552
2725
  workspace: text(value.workspace, "", 4096),
2726
+ agentPreset: normalizeAgentPresetId(value.agentPreset),
2553
2727
  bot: {
2554
2728
  name: text(value.bot?.name, `${channel4}\u673A\u5668\u4EBA`, 100),
2555
2729
  username: text(value.bot?.username, "", 100),
@@ -2578,7 +2752,8 @@ function createTokenChannelApi(channel4, connectionSummary, {
2578
2752
  return {
2579
2753
  revision: Number.isSafeInteger(source.revision) ? source.revision : 0,
2580
2754
  bots,
2581
- totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length }
2755
+ totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
2756
+ agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog)
2582
2757
  };
2583
2758
  };
2584
2759
  const presentError10 = (error) => ({
@@ -2597,8 +2772,8 @@ var normalizeSnapshot2 = api.normalizeSnapshot;
2597
2772
  var presentError2 = api.presentError;
2598
2773
 
2599
2774
  // plugin-src/client/channels/shared/token-channel.js
2600
- var React8 = __toESM(require("react"), 1);
2601
- var Button3 = React8.forwardRef(function Button4({ children, kind = "secondary", className = "", ...props }, ref) {
2775
+ var React9 = __toESM(require("react"), 1);
2776
+ var Button3 = React9.forwardRef(function Button4({ children, kind = "secondary", className = "", ...props }, ref) {
2602
2777
  return h2("button", {
2603
2778
  ...props,
2604
2779
  ref,
@@ -2650,7 +2825,7 @@ function createTokenChannelSettings(definition) {
2650
2825
  AccountSettings = null,
2651
2826
  accountSettingsEndpoint = null
2652
2827
  } = definition;
2653
- function AccountCard5({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
2828
+ function AccountCard5({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onAgentPresetSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
2654
2829
  const state = busy === "reconnect" ? "connecting" : account.state;
2655
2830
  const tone = account.connected ? "success" : state === "error" ? "error" : "warning";
2656
2831
  const stateLabel2 = account.connected ? "\u8FD0\u884C\u6B63\u5E38" : state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA";
@@ -2708,6 +2883,11 @@ function createTokenChannelSettings(definition) {
2708
2883
  disabled: Boolean(busy),
2709
2884
  onSave: onWorkspaceSave
2710
2885
  }),
2886
+ h2(AgentPresetEditor, {
2887
+ agentPreset: account.agentPreset,
2888
+ disabled: Boolean(busy),
2889
+ onSave: onAgentPresetSave
2890
+ }),
2711
2891
  AccountSettings ? h2(AccountSettings, {
2712
2892
  account,
2713
2893
  busy: Boolean(busy),
@@ -2754,21 +2934,22 @@ function createTokenChannelSettings(definition) {
2754
2934
  );
2755
2935
  }
2756
2936
  function SettingsTab({ rpcCall }) {
2757
- const [model, setModel] = React8.useState({
2937
+ const [model, setModel] = React9.useState({
2758
2938
  phase: "loading",
2759
2939
  bots: [],
2760
2940
  totals: { configured: 0, connected: 0 },
2761
- error: null
2941
+ error: null,
2942
+ agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG
2762
2943
  });
2763
- const [credentialOpen, setCredentialOpen] = React8.useState(false);
2764
- const [credentialError, setCredentialError] = React8.useState(null);
2765
- const [busy, setBusy] = React8.useState(false);
2766
- const [busyByBot, setBusyByBot] = React8.useState({});
2767
- const [testNoticeByBot, setTestNoticeByBot] = React8.useState({});
2768
- const [removeTarget, setRemoveTarget] = React8.useState(null);
2769
- const mounted = React8.useRef(true);
2944
+ const [credentialOpen, setCredentialOpen] = React9.useState(false);
2945
+ const [credentialError, setCredentialError] = React9.useState(null);
2946
+ const [busy, setBusy] = React9.useState(false);
2947
+ const [busyByBot, setBusyByBot] = React9.useState({});
2948
+ const [testNoticeByBot, setTestNoticeByBot] = React9.useState({});
2949
+ const [removeTarget, setRemoveTarget] = React9.useState(null);
2950
+ const mounted = React9.useRef(true);
2770
2951
  const workspaceFence = useWorkspaceSnapshotFence();
2771
- React8.useEffect(() => {
2952
+ React9.useEffect(() => {
2772
2953
  const disposeDingtalk = installDingtalkStyles();
2773
2954
  const disposeChannel = installStyles();
2774
2955
  mounted.current = true;
@@ -2778,18 +2959,24 @@ function createTokenChannelSettings(definition) {
2778
2959
  disposeDingtalk();
2779
2960
  };
2780
2961
  }, []);
2781
- const invoke = React8.useCallback(async (endpoint, payload = {}, signal) => {
2962
+ const invoke = React9.useCallback(async (endpoint, payload = {}, signal) => {
2782
2963
  if (typeof rpcCall !== "function") throw new TypeError(`${channel4} \u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5`);
2783
2964
  return api4.unwrapRpcResult(await rpcCall(endpoint, payload, signal));
2784
2965
  }, [rpcCall]);
2785
- const loadStatus = React8.useCallback(async ({ signal, silent = false } = {}) => {
2966
+ const loadStatus = React9.useCallback(async ({ signal, silent = false } = {}) => {
2786
2967
  const workspaceVersion = workspaceFence.beginStatus();
2787
2968
  if (workspaceVersion === null) return;
2788
2969
  if (!silent && mounted.current) setModel((current) => ({ ...current, phase: "loading", error: null }));
2789
2970
  try {
2790
2971
  const snapshot = api4.normalizeSnapshot(await invoke(endpoints.status, {}, signal));
2791
2972
  if (!mounted.current || signal?.aborted || !workspaceFence.canCommitStatus(workspaceVersion)) return;
2792
- setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
2973
+ setModel({
2974
+ phase: "ready",
2975
+ bots: snapshot.bots,
2976
+ totals: snapshot.totals,
2977
+ error: null,
2978
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
2979
+ });
2793
2980
  } catch (error) {
2794
2981
  if (error?.name !== "AbortError" && mounted.current && !signal?.aborted && workspaceFence.canCommitStatus(workspaceVersion)) {
2795
2982
  setModel((current) => ({
@@ -2800,12 +2987,12 @@ function createTokenChannelSettings(definition) {
2800
2987
  }
2801
2988
  }
2802
2989
  }, [invoke, workspaceFence]);
2803
- React8.useEffect(() => {
2990
+ React9.useEffect(() => {
2804
2991
  const controller = new AbortController();
2805
2992
  void loadStatus({ signal: controller.signal });
2806
2993
  return () => controller.abort();
2807
2994
  }, [loadStatus]);
2808
- React8.useEffect(() => {
2995
+ React9.useEffect(() => {
2809
2996
  if (model.phase !== "ready") return void 0;
2810
2997
  const controller = new AbortController();
2811
2998
  const timer = window.setInterval(
@@ -2817,7 +3004,7 @@ function createTokenChannelSettings(definition) {
2817
3004
  window.clearInterval(timer);
2818
3005
  };
2819
3006
  }, [loadStatus, model.phase]);
2820
- const bindCredentials = React8.useCallback(async (values) => {
3007
+ const bindCredentials = React9.useCallback(async (values) => {
2821
3008
  const snapshotVersion = workspaceFence.beginMutation();
2822
3009
  setBusy(true);
2823
3010
  setCredentialError(null);
@@ -2828,7 +3015,13 @@ function createTokenChannelSettings(definition) {
2828
3015
  ));
2829
3016
  if (!mounted.current) return;
2830
3017
  if (workspaceFence.canCommitMutation(snapshotVersion)) {
2831
- setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
3018
+ setModel({
3019
+ phase: "ready",
3020
+ bots: snapshot.bots,
3021
+ totals: snapshot.totals,
3022
+ error: null,
3023
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
3024
+ });
2832
3025
  }
2833
3026
  setCredentialOpen(false);
2834
3027
  } catch (error) {
@@ -2839,14 +3032,20 @@ function createTokenChannelSettings(definition) {
2839
3032
  if (mounted.current) setBusy(false);
2840
3033
  }
2841
3034
  }, [invoke, loadStatus, workspaceFence]);
2842
- const botAction = React8.useCallback(async (account, operation, endpoint, payload) => {
3035
+ const botAction = React9.useCallback(async (account, operation, endpoint, payload) => {
2843
3036
  const snapshotVersion = workspaceFence.beginMutation();
2844
3037
  setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
2845
3038
  try {
2846
3039
  const value = await invoke(endpoint, payload);
2847
3040
  const snapshot = api4.normalizeSnapshot(value);
2848
3041
  if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
2849
- setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
3042
+ setModel({
3043
+ phase: "ready",
3044
+ bots: snapshot.bots,
3045
+ totals: snapshot.totals,
3046
+ error: null,
3047
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
3048
+ });
2850
3049
  }
2851
3050
  if (mounted.current && operation === "reconnect") {
2852
3051
  setTestNoticeByBot((current) => ({
@@ -2897,6 +3096,12 @@ function createTokenChannelSettings(definition) {
2897
3096
  endpoints.setWorkspace,
2898
3097
  { botId: account.botId, workspace }
2899
3098
  ),
3099
+ onAgentPresetSave: (agentPreset) => botAction(
3100
+ account,
3101
+ "preset",
3102
+ endpoints.setAgentPreset,
3103
+ { botId: account.botId, agentPreset }
3104
+ ),
2900
3105
  onAccountSettingsSave: AccountSettings && accountSettingsEndpoint ? (payload) => botAction(
2901
3106
  account,
2902
3107
  "settings",
@@ -2914,7 +3119,9 @@ function createTokenChannelSettings(definition) {
2914
3119
  }
2915
3120
  }))))
2916
3121
  ) : null;
2917
- return h2(
3122
+ return h2(AgentPresetCatalogContext.Provider, {
3123
+ value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
3124
+ }, h2(
2918
3125
  "section",
2919
3126
  {
2920
3127
  className: `ddt-page ${pageClass} dim-channelPage`,
@@ -2962,7 +3169,7 @@ function createTokenChannelSettings(definition) {
2962
3169
  h2(Button3, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6")
2963
3170
  )
2964
3171
  ) : h2(
2965
- React8.Fragment,
3172
+ React9.Fragment,
2966
3173
  null,
2967
3174
  credentialOpen ? CredentialPanel ? h2(CredentialPanel, {
2968
3175
  busy,
@@ -3018,7 +3225,7 @@ function createTokenChannelSettings(definition) {
3018
3225
  ) : null,
3019
3226
  botList
3020
3227
  )
3021
- );
3228
+ ));
3022
3229
  }
3023
3230
  return { SettingsTab, AccountCard: AccountCard5 };
3024
3231
  }
@@ -3063,7 +3270,7 @@ var DiscordSettingsTab = channel.SettingsTab;
3063
3270
  var DiscordAccountCard = channel.AccountCard;
3064
3271
 
3065
3272
  // plugin-src/client/channels/feishu/index.js
3066
- var React10 = __toESM(require("react"), 1);
3273
+ var React11 = __toESM(require("react"), 1);
3067
3274
 
3068
3275
  // plugin-src/client/channels/feishu/api.js
3069
3276
  var FEISHU_RPC_CHANNEL = "/feishu";
@@ -3078,6 +3285,7 @@ var FEISHU_ENDPOINTS = Object.freeze({
3078
3285
  disconnectBot: "bot.disconnect",
3079
3286
  deleteBot: "bot.delete",
3080
3287
  setWorkspace: "bot.workspace.set",
3288
+ setAgentPreset: "bot.preset.set",
3081
3289
  // Kept for rolling upgrades. The multi-bot UI never calls these endpoints.
3082
3290
  testConnection: "connection.test",
3083
3291
  disconnect: "connection.disconnect"
@@ -3211,6 +3419,7 @@ function normalizeBotConnection(value, fallbackBotId) {
3211
3419
  connected,
3212
3420
  configured: value.configured !== false,
3213
3421
  workspace: optionalString2(value.workspace)?.slice(0, 4096) ?? "",
3422
+ agentPreset: normalizeAgentPresetId(value.agentPreset),
3214
3423
  bot: normalizeBot2(value.bot),
3215
3424
  health: normalizeHealth(value.health, connected),
3216
3425
  error: normalizeError2(value.error)
@@ -3251,7 +3460,8 @@ function normalizeBotsSnapshot(value) {
3251
3460
  // make the UI claim that an unavailable bot is online.
3252
3461
  totals: { configured, connected },
3253
3462
  provisioning: value.provisioning ? normalizeProvisioning2(value.provisioning) : void 0,
3254
- error: normalizeError2(value.error)
3463
+ error: normalizeError2(value.error),
3464
+ agentPresetCatalog: normalizeAgentPresetCatalog(value.agentPresetCatalog)
3255
3465
  };
3256
3466
  }
3257
3467
  function normalizeConnectionSnapshot(value) {
@@ -3301,7 +3511,7 @@ function formatRemaining2(milliseconds) {
3301
3511
  }
3302
3512
 
3303
3513
  // plugin-src/client/lifecycle.js
3304
- var React9 = __toESM(require("react"), 1);
3514
+ var React10 = __toESM(require("react"), 1);
3305
3515
  function createPollScheduler({ setTimeoutFn, clearTimeoutFn }) {
3306
3516
  let disposed = false;
3307
3517
  let timer;
@@ -3363,8 +3573,8 @@ function createAnimationFrameScheduler({ requestFrame, cancelFrame }) {
3363
3573
  };
3364
3574
  }
3365
3575
  function useAnimationFrameScheduler() {
3366
- const schedulerRef = React9.useRef(null);
3367
- React9.useEffect(() => {
3576
+ const schedulerRef = React10.useRef(null);
3577
+ React10.useEffect(() => {
3368
3578
  const scheduler = createAnimationFrameScheduler({
3369
3579
  requestFrame: (callback) => window.requestAnimationFrame(callback),
3370
3580
  cancelFrame: (frame) => window.cancelAnimationFrame(frame)
@@ -3375,7 +3585,7 @@ function useAnimationFrameScheduler() {
3375
3585
  if (schedulerRef.current === scheduler) schedulerRef.current = null;
3376
3586
  };
3377
3587
  }, []);
3378
- return React9.useCallback(
3588
+ return React10.useCallback(
3379
3589
  (callback, key) => schedulerRef.current?.schedule(callback, key) ?? false,
3380
3590
  []
3381
3591
  );
@@ -3949,7 +4159,7 @@ function QrIcon({ size = 58 }) {
3949
4159
  fill: "currentColor"
3950
4160
  }));
3951
4161
  }
3952
- var Button5 = React10.forwardRef(function Button6({ children, kind = "secondary", size, icon, className = "", ...props }, ref) {
4162
+ var Button5 = React11.forwardRef(function Button6({ children, kind = "secondary", size, icon, className = "", ...props }, ref) {
3953
4163
  return h2("button", {
3954
4164
  ...props,
3955
4165
  ref,
@@ -4066,7 +4276,7 @@ function safeQrSource2(value) {
4066
4276
  return /^data:image\/(?:png|webp|svg\+xml)(?:;charset=[^;,]+)?;base64,/i.test(value) ? value : void 0;
4067
4277
  }
4068
4278
  function QrPane({ provision, now, onRefresh, onCancel, busy }) {
4069
- const [imageFailed, setImageFailed] = React10.useState(false);
4279
+ const [imageFailed, setImageFailed] = React11.useState(false);
4070
4280
  const qrSource = safeQrSource2(provision.qrCodeDataUrl);
4071
4281
  const href = safeVerificationHref(provision.verificationUrl);
4072
4282
  const remaining = Math.max(0, provision.expiresAt - now);
@@ -4074,7 +4284,7 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
4074
4284
  const progress = Math.min(1, remaining / Math.max(1, provision.durationMs ?? remaining));
4075
4285
  const repairing = isCallbackRepair(provision);
4076
4286
  const botName = provision.botName ?? "\u6B64\u673A\u5668\u4EBA";
4077
- React10.useEffect(() => setImageFailed(false), [qrSource]);
4287
+ React11.useEffect(() => setImageFailed(false), [qrSource]);
4078
4288
  return h2(
4079
4289
  "div",
4080
4290
  { className: "bxf-card bxf-provisionCard dim-surfaceCard" },
@@ -4235,11 +4445,11 @@ function connectionTestNotice2(value) {
4235
4445
  return value?.testMessage ? "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002" : null;
4236
4446
  }
4237
4447
  function RemoveConfirmation2({ bot, busy, onConfirm, onCancel }) {
4238
- const cancelRef = React10.useRef(null);
4448
+ const cancelRef = React11.useRef(null);
4239
4449
  const idPart = bot.botId.replace(/[^a-zA-Z0-9_-]/g, "-");
4240
4450
  const titleId = `bxf-remove-title-${idPart}`;
4241
4451
  const descriptionId = `bxf-remove-description-${idPart}`;
4242
- React10.useEffect(() => cancelRef.current?.focus(), []);
4452
+ React11.useEffect(() => cancelRef.current?.focus(), []);
4243
4453
  return h2(
4244
4454
  "div",
4245
4455
  {
@@ -4282,6 +4492,7 @@ function BotCard({
4282
4492
  onReconnect,
4283
4493
  onRepairCallback,
4284
4494
  onWorkspaceSave,
4495
+ onAgentPresetSave,
4285
4496
  onRequestRemove,
4286
4497
  onConfirmRemove,
4287
4498
  onCancelRemove,
@@ -4351,6 +4562,11 @@ function BotCard({
4351
4562
  disabled: Boolean(busy),
4352
4563
  onSave: onWorkspaceSave
4353
4564
  }),
4565
+ h2(AgentPresetEditor, {
4566
+ agentPreset: connection.agentPreset,
4567
+ disabled: Boolean(busy),
4568
+ onSave: onAgentPresetSave
4569
+ }),
4354
4570
  h2(
4355
4571
  "div",
4356
4572
  { className: "bxf-connectedFooter dim-cardFooter" },
@@ -4424,6 +4640,7 @@ function BotList(props) {
4424
4640
  onReconnect: () => props.onReconnect(bot),
4425
4641
  onRepairCallback: () => props.onRepairCallback(bot),
4426
4642
  onWorkspaceSave: (workspace) => props.onWorkspaceSave(bot, workspace),
4643
+ onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(bot, agentPreset),
4427
4644
  onRequestRemove: () => props.onRequestRemove(bot),
4428
4645
  onConfirmRemove: () => props.onConfirmRemove(bot),
4429
4646
  onCancelRemove: props.onCancelRemove,
@@ -4481,53 +4698,55 @@ function mergeFeishuSnapshotState(current, snapshot, { restoreProvisioning = fal
4481
4698
  totals: snapshot.totals,
4482
4699
  provisioning,
4483
4700
  pageError: null,
4484
- statusError: null
4701
+ statusError: null,
4702
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? current.agentPresetCatalog
4485
4703
  };
4486
4704
  }
4487
4705
  function FeishuSettingsTab({ rpcCall }) {
4488
- const [model, setModel] = React10.useState({
4706
+ const [model, setModel] = React11.useState({
4489
4707
  phase: "loading",
4490
4708
  revision: 0,
4491
4709
  bots: [],
4492
4710
  totals: EMPTY_TOTALS2,
4493
4711
  provisioning: null,
4494
4712
  pageError: null,
4495
- statusError: null
4713
+ statusError: null,
4714
+ agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG
4496
4715
  });
4497
- const [pageBusy, setPageBusy] = React10.useState(false);
4498
- const [provisionBusy, setProvisionBusy] = React10.useState(false);
4499
- const [credentialOpen, setCredentialOpen] = React10.useState(false);
4500
- const [credentialBusy, setCredentialBusy] = React10.useState(false);
4501
- const [credentialError, setCredentialError] = React10.useState(null);
4502
- const [busyByBot, setBusyByBot] = React10.useState({});
4503
- const [errorsByBot, setErrorsByBot] = React10.useState({});
4504
- const [testNoticesByBot, setTestNoticesByBot] = React10.useState({});
4505
- const [removeTargetId, setRemoveTargetId] = React10.useState(null);
4506
- const [announcement, setAnnouncement] = React10.useState("");
4507
- const [now, setNow] = React10.useState(() => Date.now());
4508
- const [focusBotId, setFocusBotId] = React10.useState(null);
4509
- const cardRefs = React10.useRef(/* @__PURE__ */ new Map());
4510
- const removeButtonRefs = React10.useRef(/* @__PURE__ */ new Map());
4511
- const addButtonRef = React10.useRef(null);
4512
- const mountedRef = React10.useRef(true);
4716
+ const [pageBusy, setPageBusy] = React11.useState(false);
4717
+ const [provisionBusy, setProvisionBusy] = React11.useState(false);
4718
+ const [credentialOpen, setCredentialOpen] = React11.useState(false);
4719
+ const [credentialBusy, setCredentialBusy] = React11.useState(false);
4720
+ const [credentialError, setCredentialError] = React11.useState(null);
4721
+ const [busyByBot, setBusyByBot] = React11.useState({});
4722
+ const [errorsByBot, setErrorsByBot] = React11.useState({});
4723
+ const [testNoticesByBot, setTestNoticesByBot] = React11.useState({});
4724
+ const [removeTargetId, setRemoveTargetId] = React11.useState(null);
4725
+ const [announcement, setAnnouncement] = React11.useState("");
4726
+ const [now, setNow] = React11.useState(() => Date.now());
4727
+ const [focusBotId, setFocusBotId] = React11.useState(null);
4728
+ const cardRefs = React11.useRef(/* @__PURE__ */ new Map());
4729
+ const removeButtonRefs = React11.useRef(/* @__PURE__ */ new Map());
4730
+ const addButtonRef = React11.useRef(null);
4731
+ const mountedRef = React11.useRef(true);
4513
4732
  const workspaceFence = useWorkspaceSnapshotFence();
4514
4733
  const scheduleAnimationFrame = useAnimationFrameScheduler();
4515
- React10.useEffect(() => {
4734
+ React11.useEffect(() => {
4516
4735
  mountedRef.current = true;
4517
4736
  return () => {
4518
4737
  mountedRef.current = false;
4519
4738
  };
4520
4739
  }, []);
4521
- const announce = React10.useCallback((message) => {
4740
+ const announce = React11.useCallback((message) => {
4522
4741
  setAnnouncement("");
4523
4742
  scheduleAnimationFrame(() => {
4524
4743
  if (message) setAnnouncement(message);
4525
4744
  }, "announcement");
4526
4745
  }, [scheduleAnimationFrame]);
4527
- const invoke = React10.useCallback(async (endpoint, payload = {}, signal) => {
4746
+ const invoke = React11.useCallback(async (endpoint, payload = {}, signal) => {
4528
4747
  return unwrapRpcResult3(await rpcCall(endpoint, payload, signal));
4529
4748
  }, [rpcCall]);
4530
- const mergeSnapshot = React10.useCallback((snapshot, { restoreProvisioning = false } = {}) => {
4749
+ const mergeSnapshot = React11.useCallback((snapshot, { restoreProvisioning = false } = {}) => {
4531
4750
  const now2 = Date.now();
4532
4751
  setModel((current) => mergeFeishuSnapshotState(
4533
4752
  current,
@@ -4535,7 +4754,7 @@ function FeishuSettingsTab({ rpcCall }) {
4535
4754
  { restoreProvisioning, now: now2 }
4536
4755
  ));
4537
4756
  }, []);
4538
- const loadStatus = React10.useCallback(async ({ signal, silent = false, restoreProvisioning = false } = {}) => {
4757
+ const loadStatus = React11.useCallback(async ({ signal, silent = false, restoreProvisioning = false } = {}) => {
4539
4758
  const workspaceVersion = workspaceFence.beginStatus();
4540
4759
  if (workspaceVersion === null || !mountedRef.current) return void 0;
4541
4760
  if (!silent) setPageBusy(true);
@@ -4553,12 +4772,12 @@ function FeishuSettingsTab({ rpcCall }) {
4553
4772
  if (!silent && !signal?.aborted && mountedRef.current) setPageBusy(false);
4554
4773
  }
4555
4774
  }, [invoke, mergeSnapshot, workspaceFence]);
4556
- React10.useEffect(() => {
4775
+ React11.useEffect(() => {
4557
4776
  const controller = new AbortController();
4558
4777
  void loadStatus({ signal: controller.signal, restoreProvisioning: true });
4559
4778
  return () => controller.abort();
4560
4779
  }, [loadStatus]);
4561
- React10.useEffect(() => {
4780
+ React11.useEffect(() => {
4562
4781
  if (model.phase !== "ready") return void 0;
4563
4782
  const controller = new AbortController();
4564
4783
  let inFlight = false;
@@ -4577,7 +4796,7 @@ function FeishuSettingsTab({ rpcCall }) {
4577
4796
  window.clearInterval(timer);
4578
4797
  };
4579
4798
  }, [loadStatus, model.phase]);
4580
- React10.useEffect(() => {
4799
+ React11.useEffect(() => {
4581
4800
  if (!focusBotId) return;
4582
4801
  const node = cardRefs.current.get(focusBotId);
4583
4802
  if (!node) return;
@@ -4585,7 +4804,7 @@ function FeishuSettingsTab({ rpcCall }) {
4585
4804
  node.focus({ preventScroll: true });
4586
4805
  setFocusBotId(null);
4587
4806
  }, [focusBotId, model.bots]);
4588
- const startProvisioning = React10.useCallback(async ({
4807
+ const startProvisioning = React11.useCallback(async ({
4589
4808
  replace = false,
4590
4809
  operation = FEISHU_REGISTRATION_OPERATIONS.PROVISION,
4591
4810
  bot
@@ -4658,7 +4877,7 @@ function FeishuSettingsTab({ rpcCall }) {
4658
4877
  model.provisioning?.botId,
4659
4878
  model.provisioning?.botName
4660
4879
  ]);
4661
- const bindCredentials = React10.useCallback(async ({ identity, secret }) => {
4880
+ const bindCredentials = React11.useCallback(async ({ identity, secret }) => {
4662
4881
  const snapshotVersion = workspaceFence.beginMutation();
4663
4882
  setCredentialBusy(true);
4664
4883
  setCredentialError(null);
@@ -4680,7 +4899,7 @@ function FeishuSettingsTab({ rpcCall }) {
4680
4899
  setCredentialBusy(false);
4681
4900
  }
4682
4901
  }, [announce, invoke, loadStatus, mergeSnapshot, workspaceFence]);
4683
- const cancelProvisioning = React10.useCallback(async () => {
4902
+ const cancelProvisioning = React11.useCallback(async () => {
4684
4903
  const activeProvision = model.provisioning;
4685
4904
  const attemptId = activeProvision?.attemptId;
4686
4905
  const repairing = isCallbackRepair(activeProvision);
@@ -4743,7 +4962,7 @@ function FeishuSettingsTab({ rpcCall }) {
4743
4962
  const countdownPhase = model.provisioning?.phase;
4744
4963
  const countdownExpiresAt = model.provisioning?.expiresAt;
4745
4964
  const countdownExpired = model.provisioning?.expired;
4746
- React10.useEffect(() => {
4965
+ React11.useEffect(() => {
4747
4966
  if (!countdownAttemptId || countdownPhase !== "qr" || countdownExpired) return void 0;
4748
4967
  const tick = () => {
4749
4968
  const timestamp7 = Date.now();
@@ -4756,7 +4975,7 @@ function FeishuSettingsTab({ rpcCall }) {
4756
4975
  const timer = window.setInterval(tick, 1e3);
4757
4976
  return () => window.clearInterval(timer);
4758
4977
  }, [countdownAttemptId, countdownPhase, countdownExpiresAt, countdownExpired]);
4759
- React10.useEffect(() => {
4978
+ React11.useEffect(() => {
4760
4979
  const provision2 = model.provisioning;
4761
4980
  if (!provision2 || !["qr", "connecting"].includes(provision2.phase) || !provision2.attemptId || provision2.expired) return void 0;
4762
4981
  const controller = new AbortController();
@@ -4824,7 +5043,7 @@ function FeishuSettingsTab({ rpcCall }) {
4824
5043
  window.clearTimeout(timer);
4825
5044
  };
4826
5045
  }, [announce, invoke, loadStatus, model.provisioning]);
4827
- const setBotBusy = React10.useCallback((botId, value) => {
5046
+ const setBotBusy = React11.useCallback((botId, value) => {
4828
5047
  setBusyByBot((current) => {
4829
5048
  const next = { ...current };
4830
5049
  if (value) next[botId] = value;
@@ -4832,7 +5051,7 @@ function FeishuSettingsTab({ rpcCall }) {
4832
5051
  return next;
4833
5052
  });
4834
5053
  }, []);
4835
- const setBotError = React10.useCallback((botId, error) => {
5054
+ const setBotError = React11.useCallback((botId, error) => {
4836
5055
  setErrorsByBot((current) => {
4837
5056
  const next = { ...current };
4838
5057
  if (error) next[botId] = presentError3(error);
@@ -4840,7 +5059,7 @@ function FeishuSettingsTab({ rpcCall }) {
4840
5059
  return next;
4841
5060
  });
4842
5061
  }, []);
4843
- const repairCallback = React10.useCallback((connection) => {
5062
+ const repairCallback = React11.useCallback((connection) => {
4844
5063
  if (model.provisioning) return;
4845
5064
  setRemoveTargetId(null);
4846
5065
  setBotError(connection.botId, null);
@@ -4854,7 +5073,7 @@ function FeishuSettingsTab({ rpcCall }) {
4854
5073
  bot: connection
4855
5074
  });
4856
5075
  }, [model.provisioning, setBotError, startProvisioning]);
4857
- const reconnectOneBot = React10.useCallback(async (connection) => {
5076
+ const reconnectOneBot = React11.useCallback(async (connection) => {
4858
5077
  const { botId, bot } = connection;
4859
5078
  const snapshotVersion = workspaceFence.beginMutation();
4860
5079
  setBotBusy(botId, "reconnect");
@@ -4894,7 +5113,7 @@ function FeishuSettingsTab({ rpcCall }) {
4894
5113
  setBotBusy(botId, null);
4895
5114
  }
4896
5115
  }, [announce, invoke, loadStatus, mergeSnapshot, setBotBusy, setBotError, workspaceFence]);
4897
- const saveWorkspace = React10.useCallback(async (connection, workspace) => {
5116
+ const saveWorkspace = React11.useCallback(async (connection, workspace) => {
4898
5117
  const { botId } = connection;
4899
5118
  const workspaceVersion = workspaceFence.beginMutation();
4900
5119
  setBotBusy(botId, "workspace");
@@ -4913,15 +5132,34 @@ function FeishuSettingsTab({ rpcCall }) {
4913
5132
  if (mountedRef.current) setBotBusy(botId, null);
4914
5133
  }
4915
5134
  }, [invoke, loadStatus, mergeSnapshot, setBotBusy, setBotError, workspaceFence]);
4916
- const requestRemove = React10.useCallback((connection) => {
5135
+ const saveAgentPreset = React11.useCallback(async (connection, agentPreset) => {
5136
+ const { botId } = connection;
5137
+ const snapshotVersion = workspaceFence.beginMutation();
5138
+ setBotBusy(botId, "preset");
5139
+ setBotError(botId, null);
5140
+ try {
5141
+ const snapshot = normalizeBotsSnapshot(await invoke(
5142
+ FEISHU_ENDPOINTS.setAgentPreset,
5143
+ { botId, agentPreset }
5144
+ ));
5145
+ if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
5146
+ mergeSnapshot(snapshot);
5147
+ }
5148
+ } finally {
5149
+ const shouldRefresh = workspaceFence.endMutation();
5150
+ if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
5151
+ if (mountedRef.current) setBotBusy(botId, null);
5152
+ }
5153
+ }, [invoke, loadStatus, mergeSnapshot, setBotBusy, setBotError, workspaceFence]);
5154
+ const requestRemove = React11.useCallback((connection) => {
4917
5155
  setRemoveTargetId(connection.botId);
4918
5156
  }, []);
4919
- const cancelRemove = React10.useCallback(() => {
5157
+ const cancelRemove = React11.useCallback(() => {
4920
5158
  const botId = removeTargetId;
4921
5159
  setRemoveTargetId(null);
4922
5160
  scheduleAnimationFrame(() => removeButtonRefs.current.get(botId)?.focus(), "focus");
4923
5161
  }, [removeTargetId, scheduleAnimationFrame]);
4924
- const confirmRemove = React10.useCallback(async (connection) => {
5162
+ const confirmRemove = React11.useCallback(async (connection) => {
4925
5163
  const { botId, bot } = connection;
4926
5164
  const snapshotVersion = workspaceFence.beginMutation();
4927
5165
  setBotBusy(botId, "delete");
@@ -5006,15 +5244,17 @@ function FeishuSettingsTab({ rpcCall }) {
5006
5244
  setCredentialError(null);
5007
5245
  }
5008
5246
  }) : null;
5009
- const setCardRef = React10.useCallback((botId, node) => {
5247
+ const setCardRef = React11.useCallback((botId, node) => {
5010
5248
  if (node) cardRefs.current.set(botId, node);
5011
5249
  else cardRefs.current.delete(botId);
5012
5250
  }, []);
5013
- const setRemoveButtonRef = React10.useCallback((botId, node) => {
5251
+ const setRemoveButtonRef = React11.useCallback((botId, node) => {
5014
5252
  if (node) removeButtonRefs.current.set(botId, node);
5015
5253
  else removeButtonRefs.current.delete(botId);
5016
5254
  }, []);
5017
- return h2(
5255
+ return h2(AgentPresetCatalogContext.Provider, {
5256
+ value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
5257
+ }, h2(
5018
5258
  "section",
5019
5259
  { className: "bxf-page dim-channelPage", "aria-label": "\u98DE\u4E66\u673A\u5668\u4EBA\u8BBE\u7F6E" },
5020
5260
  h2(Heading2, {
@@ -5047,7 +5287,7 @@ function FeishuSettingsTab({ rpcCall }) {
5047
5287
  onRetry: () => void loadStatus(),
5048
5288
  busy: pageBusy
5049
5289
  }) : h2(
5050
- React10.Fragment,
5290
+ React11.Fragment,
5051
5291
  null,
5052
5292
  credentialContent,
5053
5293
  provisionContent,
@@ -5062,6 +5302,7 @@ function FeishuSettingsTab({ rpcCall }) {
5062
5302
  onReconnect: (bot) => void reconnectOneBot(bot),
5063
5303
  onRepairCallback: repairCallback,
5064
5304
  onWorkspaceSave: saveWorkspace,
5305
+ onAgentPresetSave: saveAgentPreset,
5065
5306
  onRequestRemove: requestRemove,
5066
5307
  onConfirmRemove: (bot) => void confirmRemove(bot),
5067
5308
  onCancelRemove: cancelRemove,
@@ -5069,7 +5310,7 @@ function FeishuSettingsTab({ rpcCall }) {
5069
5310
  setRemoveButtonRef
5070
5311
  }) : null
5071
5312
  )
5072
- );
5313
+ ));
5073
5314
  }
5074
5315
 
5075
5316
  // plugin-src/client/channels/qq/api.js
@@ -5082,7 +5323,8 @@ var QQ_ENDPOINTS = Object.freeze({
5082
5323
  bindCredentials: "bot.bind-credentials",
5083
5324
  reconnectBot: "bot.reconnect",
5084
5325
  deleteBot: "bot.delete",
5085
- setWorkspace: "bot.workspace.set"
5326
+ setWorkspace: "bot.workspace.set",
5327
+ setAgentPreset: SET_AGENT_PRESET_ENDPOINT
5086
5328
  });
5087
5329
  var PROVISION_STATES2 = /* @__PURE__ */ new Set(["starting", "pending", "refreshing", "connecting", "connected", "failed", "cancelled"]);
5088
5330
  var ACCOUNT_STATES3 = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
@@ -5146,6 +5388,7 @@ function normalizeBot3(value) {
5146
5388
  connected,
5147
5389
  state: connected ? "connected" : state,
5148
5390
  workspace: text2(value.workspace, "", 4096),
5391
+ agentPreset: normalizeAgentPresetId(value.agentPreset),
5149
5392
  bot: {
5150
5393
  name: text2(value.bot?.name, "QQ\u673A\u5668\u4EBA", 100),
5151
5394
  appIdMasked: text2(value.bot?.appIdMasked, "\u5E94\u7528\u6807\u8BC6\u5DF2\u5B89\u5168\u4FDD\u5B58", 140)
@@ -5179,6 +5422,7 @@ function normalizeSnapshot3(value) {
5179
5422
  bots,
5180
5423
  totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
5181
5424
  provisioning: source.provisioning ? normalizeProvisioning3(source.provisioning) : null,
5425
+ agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
5182
5426
  ...testMessage ? { testMessage } : {}
5183
5427
  };
5184
5428
  }
@@ -5201,7 +5445,7 @@ function formatRemaining3(milliseconds) {
5201
5445
  }
5202
5446
 
5203
5447
  // plugin-src/client/channels/qq/index.js
5204
- var React11 = __toESM(require("react"), 1);
5448
+ var React12 = __toESM(require("react"), 1);
5205
5449
 
5206
5450
  // plugin-src/client/channels/qq/styles.js
5207
5451
  var QQ_STYLE_ID = "xmanrui-dsh-im-qq-settings";
@@ -5226,7 +5470,7 @@ function installQqStyles() {
5226
5470
 
5227
5471
  // plugin-src/client/channels/qq/index.js
5228
5472
  var ACTIVE_STATES = /* @__PURE__ */ new Set(["pending", "refreshing", "connecting"]);
5229
- var Button7 = React11.forwardRef(function Button8({ children, kind = "secondary", className = "", ...props }, ref) {
5473
+ var Button7 = React12.forwardRef(function Button8({ children, kind = "secondary", className = "", ...props }, ref) {
5230
5474
  return h2("button", {
5231
5475
  ...props,
5232
5476
  ref,
@@ -5440,6 +5684,7 @@ function AccountCard2({
5440
5684
  removing,
5441
5685
  onReconnect,
5442
5686
  onWorkspaceSave,
5687
+ onAgentPresetSave,
5443
5688
  onRequestRemove,
5444
5689
  onConfirmRemove,
5445
5690
  onCancelRemove
@@ -5485,6 +5730,11 @@ function AccountCard2({
5485
5730
  disabled: Boolean(busy),
5486
5731
  onSave: onWorkspaceSave
5487
5732
  }),
5733
+ h2(AgentPresetEditor, {
5734
+ agentPreset: account.agentPreset,
5735
+ disabled: Boolean(busy),
5736
+ onSave: onAgentPresetSave
5737
+ }),
5488
5738
  h2(
5489
5739
  "div",
5490
5740
  { className: "ddt-accountFooter dim-cardFooter" },
@@ -5511,19 +5761,25 @@ function AccountCard2({
5511
5761
  );
5512
5762
  }
5513
5763
  function QqSettingsTab({ rpcCall }) {
5514
- const [model, setModel] = React11.useState({ phase: "loading", bots: [], totals: { configured: 0, connected: 0 }, error: null });
5515
- const [provision, setProvision] = React11.useState(null);
5516
- const [busy, setBusy] = React11.useState(false);
5517
- const [busyByBot, setBusyByBot] = React11.useState({});
5518
- const [feedbackByBot, setFeedbackByBot] = React11.useState({});
5519
- const [removeTarget, setRemoveTarget] = React11.useState(null);
5520
- const [credentialOpen, setCredentialOpen] = React11.useState(false);
5521
- const [credentialError, setCredentialError] = React11.useState(null);
5522
- const [now, setNow] = React11.useState(Date.now());
5523
- const mounted = React11.useRef(true);
5764
+ const [model, setModel] = React12.useState({
5765
+ phase: "loading",
5766
+ bots: [],
5767
+ totals: { configured: 0, connected: 0 },
5768
+ error: null,
5769
+ agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG
5770
+ });
5771
+ const [provision, setProvision] = React12.useState(null);
5772
+ const [busy, setBusy] = React12.useState(false);
5773
+ const [busyByBot, setBusyByBot] = React12.useState({});
5774
+ const [feedbackByBot, setFeedbackByBot] = React12.useState({});
5775
+ const [removeTarget, setRemoveTarget] = React12.useState(null);
5776
+ const [credentialOpen, setCredentialOpen] = React12.useState(false);
5777
+ const [credentialError, setCredentialError] = React12.useState(null);
5778
+ const [now, setNow] = React12.useState(Date.now());
5779
+ const mounted = React12.useRef(true);
5524
5780
  const workspaceFence = useWorkspaceSnapshotFence();
5525
- const addButtonRef = React11.useRef(null);
5526
- React11.useEffect(() => {
5781
+ const addButtonRef = React12.useRef(null);
5782
+ React12.useEffect(() => {
5527
5783
  const disposeDingtalk = installDingtalkStyles();
5528
5784
  const disposeQq = installQqStyles();
5529
5785
  mounted.current = true;
@@ -5533,18 +5789,24 @@ function QqSettingsTab({ rpcCall }) {
5533
5789
  disposeDingtalk();
5534
5790
  };
5535
5791
  }, []);
5536
- const invoke = React11.useCallback(async (endpoint, payload = {}, signal) => {
5792
+ const invoke = React12.useCallback(async (endpoint, payload = {}, signal) => {
5537
5793
  if (typeof rpcCall !== "function") throw new TypeError("QQ \u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5");
5538
5794
  return unwrapRpcResult4(await rpcCall(endpoint, payload, signal));
5539
5795
  }, [rpcCall]);
5540
- const loadStatus = React11.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
5796
+ const loadStatus = React12.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
5541
5797
  const workspaceVersion = workspaceFence.beginStatus();
5542
5798
  if (workspaceVersion === null) return void 0;
5543
5799
  if (!silent && mounted.current) setModel((current) => ({ ...current, phase: "loading", error: null }));
5544
5800
  try {
5545
5801
  const snapshot = normalizeSnapshot3(await invoke(QQ_ENDPOINTS.status, {}, signal));
5546
5802
  if (!mounted.current || signal?.aborted || !workspaceFence.canCommitStatus(workspaceVersion)) return void 0;
5547
- setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
5803
+ setModel({
5804
+ phase: "ready",
5805
+ bots: snapshot.bots,
5806
+ totals: snapshot.totals,
5807
+ error: null,
5808
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
5809
+ });
5548
5810
  if (restore && snapshot.provisioning) setProvision({
5549
5811
  ...snapshot.provisioning,
5550
5812
  durationMs: Math.max(1, snapshot.provisioning.expiresAt - Date.now())
@@ -5557,12 +5819,12 @@ function QqSettingsTab({ rpcCall }) {
5557
5819
  return void 0;
5558
5820
  }
5559
5821
  }, [invoke, workspaceFence]);
5560
- React11.useEffect(() => {
5822
+ React12.useEffect(() => {
5561
5823
  const controller = new AbortController();
5562
5824
  void loadStatus({ signal: controller.signal, restore: true });
5563
5825
  return () => controller.abort();
5564
5826
  }, [loadStatus]);
5565
- React11.useEffect(() => {
5827
+ React12.useEffect(() => {
5566
5828
  if (model.phase !== "ready") return void 0;
5567
5829
  const controller = new AbortController();
5568
5830
  const timer = window.setInterval(() => void loadStatus({ signal: controller.signal, silent: true }), 15e3);
@@ -5571,12 +5833,12 @@ function QqSettingsTab({ rpcCall }) {
5571
5833
  window.clearInterval(timer);
5572
5834
  };
5573
5835
  }, [loadStatus, model.phase]);
5574
- React11.useEffect(() => {
5836
+ React12.useEffect(() => {
5575
5837
  if (!provision || !ACTIVE_STATES.has(provision.status)) return void 0;
5576
5838
  const timer = window.setInterval(() => mounted.current && setNow(Date.now()), 1e3);
5577
5839
  return () => window.clearInterval(timer);
5578
5840
  }, [provision?.attemptId, provision?.status]);
5579
- const startProvisioning = React11.useCallback(async (replace = false) => {
5841
+ const startProvisioning = React12.useCallback(async (replace = false) => {
5580
5842
  setCredentialOpen(false);
5581
5843
  setCredentialError(null);
5582
5844
  setBusy(true);
@@ -5594,7 +5856,7 @@ function QqSettingsTab({ rpcCall }) {
5594
5856
  if (mounted.current) setBusy(false);
5595
5857
  }
5596
5858
  }, [invoke, provision?.attemptId]);
5597
- const bindCredentials = React11.useCallback(async ({ identity, secret }) => {
5859
+ const bindCredentials = React12.useCallback(async ({ identity, secret }) => {
5598
5860
  const snapshotVersion = workspaceFence.beginMutation();
5599
5861
  setBusy(true);
5600
5862
  setCredentialError(null);
@@ -5605,7 +5867,13 @@ function QqSettingsTab({ rpcCall }) {
5605
5867
  ));
5606
5868
  if (!mounted.current) return;
5607
5869
  if (workspaceFence.canCommitMutation(snapshotVersion)) {
5608
- setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
5870
+ setModel({
5871
+ phase: "ready",
5872
+ bots: snapshot.bots,
5873
+ totals: snapshot.totals,
5874
+ error: null,
5875
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
5876
+ });
5609
5877
  }
5610
5878
  setCredentialOpen(false);
5611
5879
  } catch (error) {
@@ -5616,7 +5884,7 @@ function QqSettingsTab({ rpcCall }) {
5616
5884
  if (mounted.current) setBusy(false);
5617
5885
  }
5618
5886
  }, [invoke, loadStatus, workspaceFence]);
5619
- const closeProvision = React11.useCallback(async () => {
5887
+ const closeProvision = React12.useCallback(async () => {
5620
5888
  setBusy(true);
5621
5889
  try {
5622
5890
  if (provision?.attemptId && ACTIVE_STATES.has(provision.status)) {
@@ -5627,7 +5895,7 @@ function QqSettingsTab({ rpcCall }) {
5627
5895
  if (mounted.current) setBusy(false);
5628
5896
  }
5629
5897
  }, [invoke, provision?.attemptId, provision?.status]);
5630
- React11.useEffect(() => {
5898
+ React12.useEffect(() => {
5631
5899
  const attemptId = provision?.attemptId;
5632
5900
  if (!attemptId || !ACTIVE_STATES.has(provision.status)) return void 0;
5633
5901
  const controller = new AbortController();
@@ -5657,13 +5925,19 @@ function QqSettingsTab({ rpcCall }) {
5657
5925
  window.clearTimeout(timer);
5658
5926
  };
5659
5927
  }, [invoke, loadStatus, provision?.attemptId, provision?.pollIntervalMs, provision?.status]);
5660
- const botAction = React11.useCallback(async (account, operation, endpoint, payload) => {
5928
+ const botAction = React12.useCallback(async (account, operation, endpoint, payload) => {
5661
5929
  const snapshotVersion = workspaceFence.beginMutation();
5662
5930
  setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
5663
5931
  try {
5664
5932
  const snapshot = normalizeSnapshot3(await invoke(endpoint, payload));
5665
5933
  if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
5666
- setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
5934
+ setModel({
5935
+ phase: "ready",
5936
+ bots: snapshot.bots,
5937
+ totals: snapshot.totals,
5938
+ error: null,
5939
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
5940
+ });
5667
5941
  }
5668
5942
  return snapshot;
5669
5943
  } finally {
@@ -5676,7 +5950,7 @@ function QqSettingsTab({ rpcCall }) {
5676
5950
  });
5677
5951
  }
5678
5952
  }, [invoke, loadStatus, workspaceFence]);
5679
- const reconnect = React11.useCallback(async (account) => {
5953
+ const reconnect = React12.useCallback(async (account) => {
5680
5954
  setFeedbackByBot((current) => {
5681
5955
  const next = { ...current };
5682
5956
  delete next[account.botId];
@@ -5739,6 +6013,12 @@ function QqSettingsTab({ rpcCall }) {
5739
6013
  QQ_ENDPOINTS.setWorkspace,
5740
6014
  { botId: account.botId, workspace }
5741
6015
  ),
6016
+ onAgentPresetSave: (agentPreset) => botAction(
6017
+ account,
6018
+ "preset",
6019
+ QQ_ENDPOINTS.setAgentPreset,
6020
+ { botId: account.botId, agentPreset }
6021
+ ),
5742
6022
  onRequestRemove: () => setRemoveTarget(account.botId),
5743
6023
  onCancelRemove: () => setRemoveTarget(null),
5744
6024
  onConfirmRemove: async () => {
@@ -5761,7 +6041,9 @@ function QqSettingsTab({ rpcCall }) {
5761
6041
  setCredentialError(null);
5762
6042
  }
5763
6043
  }) : null;
5764
- return h2(
6044
+ return h2(AgentPresetCatalogContext.Provider, {
6045
+ value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
6046
+ }, h2(
5765
6047
  "section",
5766
6048
  { className: "ddt-page dqq-page dim-channelPage", "aria-label": "QQ \u8BBE\u7F6E" },
5767
6049
  h2(Heading3, {
@@ -5777,14 +6059,14 @@ function QqSettingsTab({ rpcCall }) {
5777
6059
  addButtonRef
5778
6060
  }),
5779
6061
  model.phase === "loading" ? h2(LoadingView3) : model.phase === "error" ? h2("div", { className: "ddt-card dim-surfaceCard" }, h2("div", { className: "ddt-inlineError dim-inlineError" }, h2("h3", null, "\u65E0\u6CD5\u8BFB\u53D6 QQ \u673A\u5668\u4EBA\u72B6\u6001"), h2("p", null, model.error?.message), h2(Button7, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6"))) : h2(
5780
- React11.Fragment,
6062
+ React12.Fragment,
5781
6063
  null,
5782
6064
  credentialView,
5783
6065
  provisionView,
5784
6066
  model.bots.length === 0 && !provision && !credentialOpen ? h2(EmptyView3, { busy, onStart: () => void startProvisioning() }) : null,
5785
6067
  botList
5786
6068
  )
5787
- );
6069
+ ));
5788
6070
  }
5789
6071
 
5790
6072
  // src/channels/office/protocol.mjs
@@ -5866,7 +6148,7 @@ function normalizeOfficeStatus(value) {
5866
6148
  }
5867
6149
 
5868
6150
  // plugin-src/client/channels/office/index.js
5869
- var React12 = __toESM(require("react"), 1);
6151
+ var React13 = __toESM(require("react"), 1);
5870
6152
  function Button9({ children, kind = "secondary", ...props }) {
5871
6153
  return h2("button", { ...props, type: "button", className: "ddt-button", "data-kind": kind }, children);
5872
6154
  }
@@ -5895,12 +6177,12 @@ function stateLabel(model) {
5895
6177
  return "\u5DF2\u914D\u7F6E";
5896
6178
  }
5897
6179
  function OfficeSettingsTab({ rpcCall, initialStatus }) {
5898
- const [model, setModel] = React12.useState(normalizeOfficeStatus(initialStatus));
5899
- const [phase, setPhase] = React12.useState(initialStatus === void 0 ? "loading" : "ready");
5900
- const [busy, setBusy] = React12.useState("");
5901
- const [error, setError] = React12.useState("");
5902
- const [notice, setNotice] = React12.useState("");
5903
- const [form, setForm] = React12.useState({
6180
+ const [model, setModel] = React13.useState(normalizeOfficeStatus(initialStatus));
6181
+ const [phase, setPhase] = React13.useState(initialStatus === void 0 ? "loading" : "ready");
6182
+ const [busy, setBusy] = React13.useState("");
6183
+ const [error, setError] = React13.useState("");
6184
+ const [notice, setNotice] = React13.useState("");
6185
+ const [form, setForm] = React13.useState({
5904
6186
  baseUrl: "",
5905
6187
  deviceId: "local-harness",
5906
6188
  deviceToken: "",
@@ -5909,11 +6191,11 @@ function OfficeSettingsTab({ rpcCall, initialStatus }) {
5909
6191
  workspaces: "",
5910
6192
  instructionPresets: ""
5911
6193
  });
5912
- const invoke = React12.useCallback(async (endpoint, payload = {}) => {
6194
+ const invoke = React13.useCallback(async (endpoint, payload = {}) => {
5913
6195
  if (typeof rpcCall !== "function") throw new Error("AI Office \u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5");
5914
6196
  return unwrapOfficeRpc(await rpcCall(endpoint, payload));
5915
6197
  }, [rpcCall]);
5916
- const adopt = React12.useCallback((value) => {
6198
+ const adopt = React13.useCallback((value) => {
5917
6199
  const next = normalizeOfficeStatus(value?.snapshot ?? value);
5918
6200
  setModel(next);
5919
6201
  if (next.config) setForm((current) => ({
@@ -5928,7 +6210,7 @@ function OfficeSettingsTab({ rpcCall, initialStatus }) {
5928
6210
  }));
5929
6211
  return next;
5930
6212
  }, []);
5931
- const load = React12.useCallback(async () => {
6213
+ const load = React13.useCallback(async () => {
5932
6214
  try {
5933
6215
  adopt(await invoke(OFFICE_RPC_ENDPOINTS.status));
5934
6216
  setPhase("ready");
@@ -5938,7 +6220,7 @@ function OfficeSettingsTab({ rpcCall, initialStatus }) {
5938
6220
  setError(caught.message);
5939
6221
  }
5940
6222
  }, [adopt, invoke]);
5941
- React12.useEffect(() => {
6223
+ React13.useEffect(() => {
5942
6224
  void load();
5943
6225
  }, [load]);
5944
6226
  const run = async (name2, operation) => {
@@ -5955,7 +6237,7 @@ function OfficeSettingsTab({ rpcCall, initialStatus }) {
5955
6237
  setBusy("");
5956
6238
  }
5957
6239
  };
5958
- const hooks = React12.useMemo(() => {
6240
+ const hooks = React13.useMemo(() => {
5959
6241
  try {
5960
6242
  return officeHookUrls(form.baseUrl);
5961
6243
  } catch {
@@ -6140,7 +6422,7 @@ var normalizeSnapshot4 = api2.normalizeSnapshot;
6140
6422
  var presentError5 = api2.presentError;
6141
6423
 
6142
6424
  // plugin-src/client/channels/slack/index.js
6143
- var React13 = __toESM(require("react"), 1);
6425
+ var React14 = __toESM(require("react"), 1);
6144
6426
 
6145
6427
  // src/channels/slack/manifest.mjs
6146
6428
  var SLACK_APP_MANIFEST_YAML = `_metadata:
@@ -6213,10 +6495,10 @@ function installSlackStyles() {
6213
6495
 
6214
6496
  // plugin-src/client/channels/slack/index.js
6215
6497
  function SlackCredentialPanel({ busy, error, onSubmit, onCancel }) {
6216
- const [botToken, setBotToken] = React13.useState("");
6217
- const [appToken, setAppToken] = React13.useState("");
6218
- const [copied, setCopied] = React13.useState(false);
6219
- const headingId = React13.useId();
6498
+ const [botToken, setBotToken] = React14.useState("");
6499
+ const [appToken, setAppToken] = React14.useState("");
6500
+ const [copied, setCopied] = React14.useState(false);
6501
+ const headingId = React14.useId();
6220
6502
  const copyManifest = async () => {
6221
6503
  try {
6222
6504
  await navigator.clipboard.writeText(SLACK_APP_MANIFEST_YAML);
@@ -6372,7 +6654,7 @@ var normalizeSnapshot5 = api3.normalizeSnapshot;
6372
6654
  var presentError6 = api3.presentError;
6373
6655
 
6374
6656
  // plugin-src/client/channels/telegram/index.js
6375
- var React14 = __toESM(require("react"), 1);
6657
+ var React15 = __toESM(require("react"), 1);
6376
6658
 
6377
6659
  // plugin-src/client/channels/telegram/styles.js
6378
6660
  var TELEGRAM_STYLE_ID = "xmanrui-dsh-im-telegram-settings";
@@ -6438,11 +6720,11 @@ function allowedUsersFromText(value) {
6438
6720
  function TelegramAccessSettings({ account, busy = false, onSave }) {
6439
6721
  const policy = policyFor(account);
6440
6722
  const sourceUsers = policy.allowedUsers.join("\n");
6441
- const accessHelpId = React14.useId();
6442
- const [accessMode, setAccessMode] = React14.useState(policy.accessMode);
6443
- const [allowedUsers, setAllowedUsers] = React14.useState(sourceUsers);
6444
- const [error, setError] = React14.useState(null);
6445
- React14.useEffect(() => {
6723
+ const accessHelpId = React15.useId();
6724
+ const [accessMode, setAccessMode] = React15.useState(policy.accessMode);
6725
+ const [allowedUsers, setAllowedUsers] = React15.useState(sourceUsers);
6726
+ const [error, setError] = React15.useState(null);
6727
+ React15.useEffect(() => {
6446
6728
  setAccessMode(policy.accessMode);
6447
6729
  setAllowedUsers(sourceUsers);
6448
6730
  setError(null);
@@ -6591,7 +6873,8 @@ var WECOM_ENDPOINTS = Object.freeze({
6591
6873
  bindCredentials: "bot.bind-credentials",
6592
6874
  reconnectBot: "bot.reconnect",
6593
6875
  deleteBot: "bot.delete",
6594
- setWorkspace: "bot.workspace.set"
6876
+ setWorkspace: "bot.workspace.set",
6877
+ setAgentPreset: SET_AGENT_PRESET_ENDPOINT
6595
6878
  });
6596
6879
  var PROVISION_STATES3 = /* @__PURE__ */ new Set(["starting", "pending", "refreshing", "connecting", "connected", "failed", "cancelled"]);
6597
6880
  var ACCOUNT_STATES4 = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
@@ -6661,6 +6944,7 @@ function normalizeBot4(value) {
6661
6944
  connected,
6662
6945
  state: connected ? "connected" : state,
6663
6946
  workspace: text3(value.workspace, "", 4096),
6947
+ agentPreset: normalizeAgentPresetId(value.agentPreset),
6664
6948
  bot: {
6665
6949
  name: text3(value.bot?.name, "\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA", 100),
6666
6950
  appIdMasked: text3(value.bot?.appIdMasked, "\u5E94\u7528\u6807\u8BC6\u5DF2\u5B89\u5168\u4FDD\u5B58", 140)
@@ -6684,7 +6968,8 @@ function normalizeSnapshot6(value) {
6684
6968
  bots,
6685
6969
  totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
6686
6970
  provisioning: source.provisioning ? normalizeProvisioning4(source.provisioning) : null,
6687
- testMessage: normalizeTestMessage3(source.testMessage)
6971
+ testMessage: normalizeTestMessage3(source.testMessage),
6972
+ agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog)
6688
6973
  };
6689
6974
  }
6690
6975
  function presentError7(error) {
@@ -6699,7 +6984,7 @@ function formatRemaining4(milliseconds) {
6699
6984
  }
6700
6985
 
6701
6986
  // plugin-src/client/channels/wecom/index.js
6702
- var React15 = __toESM(require("react"), 1);
6987
+ var React16 = __toESM(require("react"), 1);
6703
6988
 
6704
6989
  // plugin-src/client/channels/wecom/styles.js
6705
6990
  var WECOM_STYLE_ID = "xmanrui-dsh-im-wecom-settings";
@@ -6724,7 +7009,7 @@ function installWecomStyles() {
6724
7009
 
6725
7010
  // plugin-src/client/channels/wecom/index.js
6726
7011
  var ACTIVE_STATES2 = /* @__PURE__ */ new Set(["pending", "refreshing", "connecting"]);
6727
- var Button10 = React15.forwardRef(function Button11({ children, kind = "secondary", className = "", ...props }, ref) {
7012
+ var Button10 = React16.forwardRef(function Button11({ children, kind = "secondary", className = "", ...props }, ref) {
6728
7013
  return h2("button", {
6729
7014
  ...props,
6730
7015
  ref,
@@ -6938,6 +7223,7 @@ function AccountCard3({
6938
7223
  removing,
6939
7224
  onReconnect,
6940
7225
  onWorkspaceSave,
7226
+ onAgentPresetSave,
6941
7227
  onRequestRemove,
6942
7228
  onConfirmRemove,
6943
7229
  onCancelRemove
@@ -6983,6 +7269,11 @@ function AccountCard3({
6983
7269
  disabled: Boolean(busy),
6984
7270
  onSave: onWorkspaceSave
6985
7271
  }),
7272
+ h2(AgentPresetEditor, {
7273
+ agentPreset: account.agentPreset,
7274
+ disabled: Boolean(busy),
7275
+ onSave: onAgentPresetSave
7276
+ }),
6986
7277
  h2(
6987
7278
  "div",
6988
7279
  { className: "ddt-accountFooter dim-cardFooter" },
@@ -7009,21 +7300,27 @@ function AccountCard3({
7009
7300
  );
7010
7301
  }
7011
7302
  function WecomSettingsTab({ rpcCall }) {
7012
- const [model, setModel] = React15.useState({ phase: "loading", bots: [], totals: { configured: 0, connected: 0 }, error: null });
7013
- const [provision, setProvision] = React15.useState(null);
7014
- const [busy, setBusy] = React15.useState(false);
7015
- const [busyByBot, setBusyByBot] = React15.useState({});
7016
- const [feedbackByBot, setFeedbackByBot] = React15.useState({});
7017
- const [removeTarget, setRemoveTarget] = React15.useState(null);
7018
- const [credentialOpen, setCredentialOpen] = React15.useState(false);
7019
- const [credentialError, setCredentialError] = React15.useState(null);
7020
- const [notice, setNotice] = React15.useState("");
7021
- const [now, setNow] = React15.useState(Date.now());
7022
- const mounted = React15.useRef(true);
7303
+ const [model, setModel] = React16.useState({
7304
+ phase: "loading",
7305
+ bots: [],
7306
+ totals: { configured: 0, connected: 0 },
7307
+ error: null,
7308
+ agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG
7309
+ });
7310
+ const [provision, setProvision] = React16.useState(null);
7311
+ const [busy, setBusy] = React16.useState(false);
7312
+ const [busyByBot, setBusyByBot] = React16.useState({});
7313
+ const [feedbackByBot, setFeedbackByBot] = React16.useState({});
7314
+ const [removeTarget, setRemoveTarget] = React16.useState(null);
7315
+ const [credentialOpen, setCredentialOpen] = React16.useState(false);
7316
+ const [credentialError, setCredentialError] = React16.useState(null);
7317
+ const [notice, setNotice] = React16.useState("");
7318
+ const [now, setNow] = React16.useState(Date.now());
7319
+ const mounted = React16.useRef(true);
7023
7320
  const workspaceFence = useWorkspaceSnapshotFence();
7024
- const addButtonRef = React15.useRef(null);
7025
- const noticeFrameRef = React15.useRef(null);
7026
- const announce = React15.useCallback((message) => {
7321
+ const addButtonRef = React16.useRef(null);
7322
+ const noticeFrameRef = React16.useRef(null);
7323
+ const announce = React16.useCallback((message) => {
7027
7324
  if (!mounted.current) return;
7028
7325
  if (noticeFrameRef.current !== null) {
7029
7326
  window.cancelAnimationFrame(noticeFrameRef.current);
@@ -7037,7 +7334,7 @@ function WecomSettingsTab({ rpcCall }) {
7037
7334
  });
7038
7335
  }
7039
7336
  }, []);
7040
- React15.useEffect(() => {
7337
+ React16.useEffect(() => {
7041
7338
  const disposeDingtalk = installDingtalkStyles();
7042
7339
  const disposeWecom = installWecomStyles();
7043
7340
  mounted.current = true;
@@ -7051,18 +7348,24 @@ function WecomSettingsTab({ rpcCall }) {
7051
7348
  disposeDingtalk();
7052
7349
  };
7053
7350
  }, []);
7054
- const invoke = React15.useCallback(async (endpoint, payload = {}, signal) => {
7351
+ const invoke = React16.useCallback(async (endpoint, payload = {}, signal) => {
7055
7352
  if (typeof rpcCall !== "function") throw new TypeError("\u4F01\u4E1A\u5FAE\u4FE1\u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5");
7056
7353
  return unwrapRpcResult7(await rpcCall(endpoint, payload, signal));
7057
7354
  }, [rpcCall]);
7058
- const loadStatus = React15.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
7355
+ const loadStatus = React16.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
7059
7356
  const workspaceVersion = workspaceFence.beginStatus();
7060
7357
  if (workspaceVersion === null) return void 0;
7061
7358
  if (!silent && mounted.current) setModel((current) => ({ ...current, phase: "loading", error: null }));
7062
7359
  try {
7063
7360
  const snapshot = normalizeSnapshot6(await invoke(WECOM_ENDPOINTS.status, {}, signal));
7064
7361
  if (!mounted.current || signal?.aborted || !workspaceFence.canCommitStatus(workspaceVersion)) return void 0;
7065
- setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
7362
+ setModel({
7363
+ phase: "ready",
7364
+ bots: snapshot.bots,
7365
+ totals: snapshot.totals,
7366
+ error: null,
7367
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
7368
+ });
7066
7369
  if (restore && snapshot.provisioning) setProvision({
7067
7370
  ...snapshot.provisioning,
7068
7371
  durationMs: Math.max(1, snapshot.provisioning.expiresAt - Date.now())
@@ -7075,12 +7378,12 @@ function WecomSettingsTab({ rpcCall }) {
7075
7378
  return void 0;
7076
7379
  }
7077
7380
  }, [invoke, workspaceFence]);
7078
- React15.useEffect(() => {
7381
+ React16.useEffect(() => {
7079
7382
  const controller = new AbortController();
7080
7383
  void loadStatus({ signal: controller.signal, restore: true });
7081
7384
  return () => controller.abort();
7082
7385
  }, [loadStatus]);
7083
- React15.useEffect(() => {
7386
+ React16.useEffect(() => {
7084
7387
  if (model.phase !== "ready") return void 0;
7085
7388
  const controller = new AbortController();
7086
7389
  const timer = window.setInterval(() => void loadStatus({ signal: controller.signal, silent: true }), 15e3);
@@ -7089,12 +7392,12 @@ function WecomSettingsTab({ rpcCall }) {
7089
7392
  window.clearInterval(timer);
7090
7393
  };
7091
7394
  }, [loadStatus, model.phase]);
7092
- React15.useEffect(() => {
7395
+ React16.useEffect(() => {
7093
7396
  if (!provision || !ACTIVE_STATES2.has(provision.status)) return void 0;
7094
7397
  const timer = window.setInterval(() => mounted.current && setNow(Date.now()), 1e3);
7095
7398
  return () => window.clearInterval(timer);
7096
7399
  }, [provision?.attemptId, provision?.status]);
7097
- const startProvisioning = React15.useCallback(async (replace = false) => {
7400
+ const startProvisioning = React16.useCallback(async (replace = false) => {
7098
7401
  setCredentialOpen(false);
7099
7402
  setCredentialError(null);
7100
7403
  setBusy(true);
@@ -7112,7 +7415,7 @@ function WecomSettingsTab({ rpcCall }) {
7112
7415
  if (mounted.current) setBusy(false);
7113
7416
  }
7114
7417
  }, [invoke, provision?.attemptId]);
7115
- const bindCredentials = React15.useCallback(async ({ identity, secret }) => {
7418
+ const bindCredentials = React16.useCallback(async ({ identity, secret }) => {
7116
7419
  const snapshotVersion = workspaceFence.beginMutation();
7117
7420
  setBusy(true);
7118
7421
  setCredentialError(null);
@@ -7123,7 +7426,13 @@ function WecomSettingsTab({ rpcCall }) {
7123
7426
  ));
7124
7427
  if (!mounted.current) return;
7125
7428
  if (workspaceFence.canCommitMutation(snapshotVersion)) {
7126
- setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
7429
+ setModel({
7430
+ phase: "ready",
7431
+ bots: snapshot.bots,
7432
+ totals: snapshot.totals,
7433
+ error: null,
7434
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
7435
+ });
7127
7436
  }
7128
7437
  setCredentialOpen(false);
7129
7438
  } catch (error) {
@@ -7134,7 +7443,7 @@ function WecomSettingsTab({ rpcCall }) {
7134
7443
  if (mounted.current) setBusy(false);
7135
7444
  }
7136
7445
  }, [invoke, loadStatus, workspaceFence]);
7137
- const closeProvision = React15.useCallback(async () => {
7446
+ const closeProvision = React16.useCallback(async () => {
7138
7447
  setBusy(true);
7139
7448
  try {
7140
7449
  if (provision?.attemptId && ACTIVE_STATES2.has(provision.status)) {
@@ -7145,7 +7454,7 @@ function WecomSettingsTab({ rpcCall }) {
7145
7454
  if (mounted.current) setBusy(false);
7146
7455
  }
7147
7456
  }, [invoke, provision?.attemptId, provision?.status]);
7148
- React15.useEffect(() => {
7457
+ React16.useEffect(() => {
7149
7458
  const attemptId = provision?.attemptId;
7150
7459
  if (!attemptId || !ACTIVE_STATES2.has(provision.status)) return void 0;
7151
7460
  const controller = new AbortController();
@@ -7175,13 +7484,19 @@ function WecomSettingsTab({ rpcCall }) {
7175
7484
  window.clearTimeout(timer);
7176
7485
  };
7177
7486
  }, [invoke, loadStatus, provision?.attemptId, provision?.pollIntervalMs, provision?.status]);
7178
- const botAction = React15.useCallback(async (account, operation, endpoint, payload) => {
7487
+ const botAction = React16.useCallback(async (account, operation, endpoint, payload) => {
7179
7488
  const snapshotVersion = workspaceFence.beginMutation();
7180
7489
  setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
7181
7490
  try {
7182
7491
  const snapshot = normalizeSnapshot6(await invoke(endpoint, payload));
7183
7492
  if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
7184
- setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
7493
+ setModel({
7494
+ phase: "ready",
7495
+ bots: snapshot.bots,
7496
+ totals: snapshot.totals,
7497
+ error: null,
7498
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
7499
+ });
7185
7500
  }
7186
7501
  return snapshot;
7187
7502
  } finally {
@@ -7194,7 +7509,7 @@ function WecomSettingsTab({ rpcCall }) {
7194
7509
  });
7195
7510
  }
7196
7511
  }, [invoke, loadStatus, workspaceFence]);
7197
- const reconnect = React15.useCallback(async (account) => {
7512
+ const reconnect = React16.useCallback(async (account) => {
7198
7513
  setFeedbackByBot((current) => {
7199
7514
  const next = { ...current };
7200
7515
  delete next[account.botId];
@@ -7268,6 +7583,12 @@ function WecomSettingsTab({ rpcCall }) {
7268
7583
  WECOM_ENDPOINTS.setWorkspace,
7269
7584
  { botId: account.botId, workspace }
7270
7585
  ),
7586
+ onAgentPresetSave: (agentPreset) => botAction(
7587
+ account,
7588
+ "preset",
7589
+ WECOM_ENDPOINTS.setAgentPreset,
7590
+ { botId: account.botId, agentPreset }
7591
+ ),
7271
7592
  onRequestRemove: () => setRemoveTarget(account.botId),
7272
7593
  onCancelRemove: () => setRemoveTarget(null),
7273
7594
  onConfirmRemove: async () => {
@@ -7290,7 +7611,9 @@ function WecomSettingsTab({ rpcCall }) {
7290
7611
  setCredentialError(null);
7291
7612
  }
7292
7613
  }) : null;
7293
- return h2(
7614
+ return h2(AgentPresetCatalogContext.Provider, {
7615
+ value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
7616
+ }, h2(
7294
7617
  "section",
7295
7618
  { className: "ddt-page dwecom-page dim-channelPage", "aria-label": "\u4F01\u4E1A\u5FAE\u4FE1\u8BBE\u7F6E" },
7296
7619
  h2(Heading4, {
@@ -7307,18 +7630,18 @@ function WecomSettingsTab({ rpcCall }) {
7307
7630
  }),
7308
7631
  h2("div", { className: "ddt-visuallyHidden", role: "status", "aria-live": "polite" }, notice),
7309
7632
  model.phase === "loading" ? h2(LoadingView4) : model.phase === "error" ? h2("div", { className: "ddt-card dim-surfaceCard" }, h2("div", { className: "ddt-inlineError dim-inlineError" }, h2("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA\u72B6\u6001"), h2("p", null, model.error?.message), h2(Button10, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6"))) : h2(
7310
- React15.Fragment,
7633
+ React16.Fragment,
7311
7634
  null,
7312
7635
  credentialView,
7313
7636
  provisionView,
7314
7637
  model.bots.length === 0 && !provision && !credentialOpen ? h2(EmptyView4, { busy, onStart: () => void startProvisioning() }) : null,
7315
7638
  botList
7316
7639
  )
7317
- );
7640
+ ));
7318
7641
  }
7319
7642
 
7320
7643
  // plugin-src/client/channels/weixin/index.js
7321
- var React16 = __toESM(require("react"), 1);
7644
+ var React17 = __toESM(require("react"), 1);
7322
7645
 
7323
7646
  // plugin-src/client/channels/weixin/api.js
7324
7647
  var WEIXIN_RPC_CHANNEL = "/weixin";
@@ -7330,7 +7653,8 @@ var WEIXIN_ENDPOINTS = Object.freeze({
7330
7653
  cancelProvisioning: "provision.cancel",
7331
7654
  reconnectBot: "bot.reconnect",
7332
7655
  deleteBot: "bot.delete",
7333
- setWorkspace: "bot.workspace.set"
7656
+ setWorkspace: "bot.workspace.set",
7657
+ setAgentPreset: SET_AGENT_PRESET_ENDPOINT
7334
7658
  });
7335
7659
  var ACCOUNT_STATES5 = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
7336
7660
  var PROVISION_STATES4 = /* @__PURE__ */ new Set([
@@ -7360,6 +7684,14 @@ function normalizeTestMessage4(value) {
7360
7684
  const code = value.code === "test-target-unavailable" ? "test-target-unavailable" : "test-message-failed";
7361
7685
  return { sent: false, code };
7362
7686
  }
7687
+ function normalizeMessageError(value) {
7688
+ if (!isRecord6(value)) return null;
7689
+ const code = string(value.code).slice(0, 64);
7690
+ const reason = string(value.reason).slice(0, 128);
7691
+ const message = string(value.message).slice(0, 500);
7692
+ const at = timestamp5(value.at);
7693
+ return code && reason && message && at !== null ? { code, reason, message, at } : null;
7694
+ }
7363
7695
  function unwrapRpcResult8(result) {
7364
7696
  if (!isRecord6(result) || typeof result.ok !== "boolean") {
7365
7697
  throw new Error("\u5FAE\u4FE1\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94");
@@ -7420,6 +7752,7 @@ function normalizeBot5(value) {
7420
7752
  connected,
7421
7753
  configured: value.configured === true,
7422
7754
  workspace: string(value.workspace).slice(0, 4096),
7755
+ agentPreset: normalizeAgentPresetId(value.agentPreset),
7423
7756
  bot: {
7424
7757
  name: string(value.bot.name, "\u5FAE\u4FE1\u673A\u5668\u4EBA"),
7425
7758
  accountIdMasked: string(value.bot.accountIdMasked, "\u5DF2\u5B89\u5168\u4FDD\u5B58")
@@ -7433,6 +7766,7 @@ function normalizeBot5(value) {
7433
7766
  messagesReceived: Math.max(0, Number(value.stats?.messagesReceived) || 0),
7434
7767
  messagesReplied: Math.max(0, Number(value.stats?.messagesReplied) || 0)
7435
7768
  },
7769
+ lastMessageError: normalizeMessageError(value.lastMessageError),
7436
7770
  error: isRecord6(value.error) ? {
7437
7771
  code: string(value.error.code, "WEIXIN_ACCOUNT_ERROR"),
7438
7772
  message: string(value.error.message, "\u5FAE\u4FE1\u8FDE\u63A5\u672A\u5C31\u7EEA")
@@ -7454,7 +7788,8 @@ function normalizeSnapshot7(value) {
7454
7788
  connected: bots.filter((bot) => bot.connected).length
7455
7789
  },
7456
7790
  provisioning: value.provisioning ? normalizeProvisioning5(value.provisioning) : null,
7457
- testMessage: normalizeTestMessage4(value.testMessage)
7791
+ testMessage: normalizeTestMessage4(value.testMessage),
7792
+ agentPresetCatalog: normalizeAgentPresetCatalog(value.agentPresetCatalog)
7458
7793
  };
7459
7794
  }
7460
7795
  function presentError8(error) {
@@ -7591,7 +7926,7 @@ function installWeixinStyles() {
7591
7926
  }
7592
7927
 
7593
7928
  // plugin-src/client/channels/weixin/index.js
7594
- var Button12 = React16.forwardRef(function Button13({ children, kind = "secondary", className = "", ...props }, ref) {
7929
+ var Button12 = React17.forwardRef(function Button13({ children, kind = "secondary", className = "", ...props }, ref) {
7595
7930
  return h2("button", {
7596
7931
  ...props,
7597
7932
  ref,
@@ -7664,14 +7999,14 @@ function EmptyView5({ onStart, busy }) {
7664
7999
  );
7665
8000
  }
7666
8001
  function QrPanel4({ provision, now, busy, onRefresh, onCancel }) {
7667
- const [imageFailed, setImageFailed] = React16.useState(false);
8002
+ const [imageFailed, setImageFailed] = React17.useState(false);
7668
8003
  const source = safeQrSource5(provision.qrCodeDataUrl);
7669
8004
  const href = safeVerificationUrl(provision.verificationUrl);
7670
8005
  const remaining = Math.max(0, provision.expiresAt - now);
7671
8006
  const expired = remaining === 0 || provision.status === "expired";
7672
8007
  const duration = Math.max(1, provision.durationMs ?? 5 * 6e4);
7673
8008
  const progress = Math.round(Math.min(1, remaining / duration) * 100);
7674
- React16.useEffect(() => setImageFailed(false), [source]);
8009
+ React17.useEffect(() => setImageFailed(false), [source]);
7675
8010
  return h2(
7676
8011
  "div",
7677
8012
  { className: "dxw-card dim-surfaceCard" },
@@ -7738,9 +8073,9 @@ function QrPanel4({ provision, now, busy, onRefresh, onCancel }) {
7738
8073
  );
7739
8074
  }
7740
8075
  function VerificationPanel({ provision, busy, onSubmit, onCancel }) {
7741
- const [code, setCode] = React16.useState("");
8076
+ const [code, setCode] = React17.useState("");
7742
8077
  const valid = /^\d{4,8}$/.test(code);
7743
- React16.useEffect(() => setCode(""), [provision.attemptId]);
8078
+ React17.useEffect(() => setCode(""), [provision.attemptId]);
7744
8079
  return h2(
7745
8080
  "div",
7746
8081
  { className: "dxw-card dim-surfaceCard" },
@@ -7838,6 +8173,7 @@ function AccountCard4({
7838
8173
  removing,
7839
8174
  onReconnect,
7840
8175
  onWorkspaceSave,
8176
+ onAgentPresetSave,
7841
8177
  onRequestRemove,
7842
8178
  onConfirmRemove,
7843
8179
  onCancelRemove
@@ -7888,10 +8224,19 @@ function AccountCard4({
7888
8224
  disabled: Boolean(busy),
7889
8225
  onSave: onWorkspaceSave
7890
8226
  }),
8227
+ h2(AgentPresetEditor, {
8228
+ agentPreset: account.agentPreset,
8229
+ disabled: Boolean(busy),
8230
+ onSave: onAgentPresetSave
8231
+ }),
7891
8232
  h2(
7892
8233
  "div",
7893
8234
  { className: "dxw-accountFooter dim-cardFooter" },
7894
8235
  summary ? h2("div", { className: "dxw-summary dim-cardSummary" }, summary) : null,
8236
+ account.lastMessageError ? h2("div", {
8237
+ className: "dxw-summary dim-cardSummary",
8238
+ role: "status"
8239
+ }, `\u6700\u8FD1\u4E00\u6761\u6D88\u606F\u5904\u7406\u5931\u8D25\uFF1A${account.lastMessageError.message}`) : null,
7895
8240
  feedback ? h2("div", {
7896
8241
  className: "dxw-summary dim-cardSummary",
7897
8242
  role: "status",
@@ -7942,6 +8287,7 @@ function AccountList2(props) {
7942
8287
  removing: props.removeTarget === account.botId,
7943
8288
  onReconnect: () => props.onReconnect(account),
7944
8289
  onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
8290
+ onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(account, agentPreset),
7945
8291
  onRequestRemove: () => props.onRequestRemove(account),
7946
8292
  onConfirmRemove: () => props.onConfirmRemove(account),
7947
8293
  onCancelRemove: props.onCancelRemove
@@ -7960,40 +8306,41 @@ function mergeWeixinProvisioningSnapshot(current, incoming, { restoreProvisionin
7960
8306
  };
7961
8307
  }
7962
8308
  function WeixinSettingsTab({ rpcCall }) {
7963
- const [model, setModel] = React16.useState({
8309
+ const [model, setModel] = React17.useState({
7964
8310
  phase: "loading",
7965
8311
  bots: [],
7966
8312
  totals: EMPTY_TOTALS3,
7967
8313
  revision: 0,
7968
- error: null
8314
+ error: null,
8315
+ agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG
7969
8316
  });
7970
- const [provision, setProvision] = React16.useState(null);
7971
- const [busy, setBusy] = React16.useState(false);
7972
- const [busyByBot, setBusyByBot] = React16.useState({});
7973
- const [feedbackByBot, setFeedbackByBot] = React16.useState({});
7974
- const [removeTarget, setRemoveTarget] = React16.useState(null);
7975
- const [notice, setNotice] = React16.useState("");
7976
- const [now, setNow] = React16.useState(() => Date.now());
7977
- const addButtonRef = React16.useRef(null);
7978
- const mountedRef = React16.useRef(true);
8317
+ const [provision, setProvision] = React17.useState(null);
8318
+ const [busy, setBusy] = React17.useState(false);
8319
+ const [busyByBot, setBusyByBot] = React17.useState({});
8320
+ const [feedbackByBot, setFeedbackByBot] = React17.useState({});
8321
+ const [removeTarget, setRemoveTarget] = React17.useState(null);
8322
+ const [notice, setNotice] = React17.useState("");
8323
+ const [now, setNow] = React17.useState(() => Date.now());
8324
+ const addButtonRef = React17.useRef(null);
8325
+ const mountedRef = React17.useRef(true);
7979
8326
  const workspaceFence = useWorkspaceSnapshotFence();
7980
8327
  const scheduleAnimationFrame = useAnimationFrameScheduler();
7981
- React16.useEffect(() => {
8328
+ React17.useEffect(() => {
7982
8329
  mountedRef.current = true;
7983
8330
  return () => {
7984
8331
  mountedRef.current = false;
7985
8332
  };
7986
8333
  }, []);
7987
- const announce = React16.useCallback((value) => {
8334
+ const announce = React17.useCallback((value) => {
7988
8335
  setNotice("");
7989
8336
  scheduleAnimationFrame(() => {
7990
8337
  if (value) setNotice(value);
7991
8338
  }, "announcement");
7992
8339
  }, [scheduleAnimationFrame]);
7993
- const invoke = React16.useCallback(async (endpoint, payload = {}, signal) => {
8340
+ const invoke = React17.useCallback(async (endpoint, payload = {}, signal) => {
7994
8341
  return unwrapRpcResult8(await rpcCall(endpoint, payload, signal));
7995
8342
  }, [rpcCall]);
7996
- const loadStatus = React16.useCallback(async ({
8343
+ const loadStatus = React17.useCallback(async ({
7997
8344
  signal,
7998
8345
  silent = false,
7999
8346
  restoreProvisioning = false
@@ -8009,7 +8356,8 @@ function WeixinSettingsTab({ rpcCall }) {
8009
8356
  bots: snapshot.bots,
8010
8357
  totals: snapshot.totals,
8011
8358
  revision: snapshot.revision,
8012
- error: null
8359
+ error: null,
8360
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
8013
8361
  });
8014
8362
  if (snapshot.provisioning) {
8015
8363
  setProvision((current) => mergeWeixinProvisioningSnapshot(
@@ -8029,12 +8377,12 @@ function WeixinSettingsTab({ rpcCall }) {
8029
8377
  return void 0;
8030
8378
  }
8031
8379
  }, [invoke, workspaceFence]);
8032
- React16.useEffect(() => {
8380
+ React17.useEffect(() => {
8033
8381
  const controller = new AbortController();
8034
8382
  void loadStatus({ signal: controller.signal, restoreProvisioning: true });
8035
8383
  return () => controller.abort();
8036
8384
  }, [loadStatus]);
8037
- React16.useEffect(() => {
8385
+ React17.useEffect(() => {
8038
8386
  if (model.phase !== "ready") return void 0;
8039
8387
  const controller = new AbortController();
8040
8388
  let running = false;
@@ -8053,12 +8401,12 @@ function WeixinSettingsTab({ rpcCall }) {
8053
8401
  window.clearInterval(timer);
8054
8402
  };
8055
8403
  }, [loadStatus, model.phase]);
8056
- React16.useEffect(() => {
8404
+ React17.useEffect(() => {
8057
8405
  if (!provision || !["pending", "scanned"].includes(provision.status)) return void 0;
8058
8406
  const timer = window.setInterval(() => setNow(Date.now()), 1e3);
8059
8407
  return () => window.clearInterval(timer);
8060
8408
  }, [provision?.attemptId, provision?.status]);
8061
- const startProvisioning = React16.useCallback(async ({ replace = false } = {}) => {
8409
+ const startProvisioning = React17.useCallback(async ({ replace = false } = {}) => {
8062
8410
  setBusy(true);
8063
8411
  try {
8064
8412
  if (replace && provision?.attemptId) {
@@ -8079,7 +8427,7 @@ function WeixinSettingsTab({ rpcCall }) {
8079
8427
  setBusy(false);
8080
8428
  }
8081
8429
  }, [announce, invoke, provision?.attemptId]);
8082
- const cancelProvisioning = React16.useCallback(async () => {
8430
+ const cancelProvisioning = React17.useCallback(async () => {
8083
8431
  setBusy(true);
8084
8432
  try {
8085
8433
  if (provision?.attemptId && !["failed", "expired", "cancelled"].includes(provision.status)) {
@@ -8094,7 +8442,7 @@ function WeixinSettingsTab({ rpcCall }) {
8094
8442
  setBusy(false);
8095
8443
  }
8096
8444
  }, [announce, invoke, provision?.attemptId, provision?.status, scheduleAnimationFrame]);
8097
- const submitVerification = React16.useCallback(async (verifyCode) => {
8445
+ const submitVerification = React17.useCallback(async (verifyCode) => {
8098
8446
  if (!provision?.attemptId) return;
8099
8447
  setBusy(true);
8100
8448
  try {
@@ -8110,7 +8458,7 @@ function WeixinSettingsTab({ rpcCall }) {
8110
8458
  setBusy(false);
8111
8459
  }
8112
8460
  }, [announce, invoke, provision?.attemptId]);
8113
- React16.useEffect(() => {
8461
+ React17.useEffect(() => {
8114
8462
  const attemptId = provision?.attemptId;
8115
8463
  if (!attemptId || !["pending", "scanned", "connecting"].includes(provision.status)) return void 0;
8116
8464
  const controller = new AbortController();
@@ -8158,7 +8506,7 @@ function WeixinSettingsTab({ rpcCall }) {
8158
8506
  controller.abort();
8159
8507
  };
8160
8508
  }, [announce, invoke, loadStatus, provision?.attemptId, provision?.status, provision?.pollIntervalMs]);
8161
- const setBotBusy = React16.useCallback((botId, value) => {
8509
+ const setBotBusy = React17.useCallback((botId, value) => {
8162
8510
  setBusyByBot((current) => {
8163
8511
  const next = { ...current };
8164
8512
  if (value) next[botId] = value;
@@ -8166,7 +8514,7 @@ function WeixinSettingsTab({ rpcCall }) {
8166
8514
  return next;
8167
8515
  });
8168
8516
  }, []);
8169
- const reconnect = React16.useCallback(async (account) => {
8517
+ const reconnect = React17.useCallback(async (account) => {
8170
8518
  const snapshotVersion = workspaceFence.beginMutation();
8171
8519
  setBotBusy(account.botId, "reconnect");
8172
8520
  setFeedbackByBot((current) => {
@@ -8180,7 +8528,13 @@ function WeixinSettingsTab({ rpcCall }) {
8180
8528
  { botId: account.botId, sendTest: true }
8181
8529
  ));
8182
8530
  if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
8183
- setModel((current) => ({ ...current, bots: snapshot.bots, totals: snapshot.totals, revision: snapshot.revision }));
8531
+ setModel((current) => ({
8532
+ ...current,
8533
+ bots: snapshot.bots,
8534
+ totals: snapshot.totals,
8535
+ revision: snapshot.revision,
8536
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? current.agentPresetCatalog
8537
+ }));
8184
8538
  }
8185
8539
  const refreshed = snapshot.bots.find((bot) => bot.botId === account.botId);
8186
8540
  let feedback;
@@ -8211,7 +8565,7 @@ function WeixinSettingsTab({ rpcCall }) {
8211
8565
  setBotBusy(account.botId, null);
8212
8566
  }
8213
8567
  }, [announce, invoke, loadStatus, setBotBusy, workspaceFence]);
8214
- const saveWorkspace = React16.useCallback(async (account, workspace) => {
8568
+ const saveWorkspace = React17.useCallback(async (account, workspace) => {
8215
8569
  const workspaceVersion = workspaceFence.beginMutation();
8216
8570
  setBotBusy(account.botId, "workspace");
8217
8571
  try {
@@ -8225,7 +8579,32 @@ function WeixinSettingsTab({ rpcCall }) {
8225
8579
  bots: snapshot.bots,
8226
8580
  totals: snapshot.totals,
8227
8581
  revision: snapshot.revision,
8228
- error: null
8582
+ error: null,
8583
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
8584
+ });
8585
+ }
8586
+ } finally {
8587
+ const shouldRefresh = workspaceFence.endMutation();
8588
+ if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
8589
+ if (mountedRef.current) setBotBusy(account.botId, null);
8590
+ }
8591
+ }, [invoke, loadStatus, setBotBusy, workspaceFence]);
8592
+ const saveAgentPreset = React17.useCallback(async (account, agentPreset) => {
8593
+ const snapshotVersion = workspaceFence.beginMutation();
8594
+ setBotBusy(account.botId, "preset");
8595
+ try {
8596
+ const snapshot = normalizeSnapshot7(await invoke(
8597
+ WEIXIN_ENDPOINTS.setAgentPreset,
8598
+ { botId: account.botId, agentPreset }
8599
+ ));
8600
+ if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
8601
+ setModel({
8602
+ phase: "ready",
8603
+ bots: snapshot.bots,
8604
+ totals: snapshot.totals,
8605
+ revision: snapshot.revision,
8606
+ error: null,
8607
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
8229
8608
  });
8230
8609
  }
8231
8610
  } finally {
@@ -8234,7 +8613,7 @@ function WeixinSettingsTab({ rpcCall }) {
8234
8613
  if (mountedRef.current) setBotBusy(account.botId, null);
8235
8614
  }
8236
8615
  }, [invoke, loadStatus, setBotBusy, workspaceFence]);
8237
- const remove = React16.useCallback(async (account) => {
8616
+ const remove = React17.useCallback(async (account) => {
8238
8617
  const snapshotVersion = workspaceFence.beginMutation();
8239
8618
  setBotBusy(account.botId, "delete");
8240
8619
  try {
@@ -8243,7 +8622,13 @@ function WeixinSettingsTab({ rpcCall }) {
8243
8622
  confirm: true
8244
8623
  }));
8245
8624
  if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
8246
- setModel((current) => ({ ...current, bots: snapshot.bots, totals: snapshot.totals, revision: snapshot.revision }));
8625
+ setModel((current) => ({
8626
+ ...current,
8627
+ bots: snapshot.bots,
8628
+ totals: snapshot.totals,
8629
+ revision: snapshot.revision,
8630
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? current.agentPresetCatalog
8631
+ }));
8247
8632
  }
8248
8633
  setRemoveTarget(null);
8249
8634
  announce("\u5FAE\u4FE1\u8D26\u53F7\u53CA\u672C\u673A\u51ED\u636E\u5DF2\u79FB\u9664\u3002");
@@ -8287,7 +8672,9 @@ function WeixinSettingsTab({ rpcCall }) {
8287
8672
  onClose: () => void cancelProvisioning()
8288
8673
  });
8289
8674
  }
8290
- return h2(
8675
+ return h2(AgentPresetCatalogContext.Provider, {
8676
+ value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
8677
+ }, h2(
8291
8678
  "section",
8292
8679
  { className: "dxw-page dim-channelPage", "aria-label": "\u5FAE\u4FE1\u8BBE\u7F6E" },
8293
8680
  h2(Heading5, {
@@ -8310,7 +8697,7 @@ function WeixinSettingsTab({ rpcCall }) {
8310
8697
  h2(Button12, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6")
8311
8698
  )
8312
8699
  ) : h2(
8313
- React16.Fragment,
8700
+ React17.Fragment,
8314
8701
  null,
8315
8702
  provisionView,
8316
8703
  model.bots.length === 0 && !provision ? h2(EmptyView5, { onStart: () => void startProvisioning(), busy }) : null,
@@ -8321,12 +8708,13 @@ function WeixinSettingsTab({ rpcCall }) {
8321
8708
  removeTarget,
8322
8709
  onReconnect: (account) => void reconnect(account),
8323
8710
  onWorkspaceSave: saveWorkspace,
8711
+ onAgentPresetSave: saveAgentPreset,
8324
8712
  onRequestRemove: (account) => setRemoveTarget(account.botId),
8325
8713
  onConfirmRemove: (account) => void remove(account),
8326
8714
  onCancelRemove: () => setRemoveTarget(null)
8327
8715
  }) : null
8328
8716
  )
8329
- );
8717
+ ));
8330
8718
  }
8331
8719
 
8332
8720
  // plugin-src/client/channels/whatsapp/api.js
@@ -8338,7 +8726,8 @@ var WHATSAPP_ENDPOINTS = Object.freeze({
8338
8726
  cancelProvisioning: "provision.cancel",
8339
8727
  reconnectBot: "bot.reconnect",
8340
8728
  deleteBot: "bot.delete",
8341
- setWorkspace: "bot.workspace.set"
8729
+ setWorkspace: "bot.workspace.set",
8730
+ setAgentPreset: SET_AGENT_PRESET_ENDPOINT
8342
8731
  });
8343
8732
  var PROVISION_STATES5 = /* @__PURE__ */ new Set(["starting", "pending", "connecting", "connected", "failed", "cancelled"]);
8344
8733
  var BOT_STATES = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
@@ -8403,6 +8792,7 @@ function normalizeBot6(value) {
8403
8792
  connected,
8404
8793
  state: connected ? "connected" : state,
8405
8794
  workspace: text4(value.workspace, "", 4096),
8795
+ agentPreset: normalizeAgentPresetId(value.agentPreset),
8406
8796
  bot: {
8407
8797
  name: text4(value.bot?.name, "WhatsApp\u673A\u5668\u4EBA", 100),
8408
8798
  idMasked: text4(value.bot?.idMasked, "WhatsApp\u8D26\u53F7", 140)
@@ -8427,7 +8817,8 @@ function normalizeSnapshot8(value) {
8427
8817
  revision: Number.isSafeInteger(source.revision) ? source.revision : 0,
8428
8818
  bots,
8429
8819
  totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
8430
- provisioning: source.provisioning ? normalizeProvisioning6(source.provisioning) : null
8820
+ provisioning: source.provisioning ? normalizeProvisioning6(source.provisioning) : null,
8821
+ agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog)
8431
8822
  };
8432
8823
  }
8433
8824
  function presentError9(error) {
@@ -8442,7 +8833,7 @@ function formatRemaining6(milliseconds) {
8442
8833
  }
8443
8834
 
8444
8835
  // plugin-src/client/channels/whatsapp/index.js
8445
- var React17 = __toESM(require("react"), 1);
8836
+ var React18 = __toESM(require("react"), 1);
8446
8837
 
8447
8838
  // plugin-src/client/channels/whatsapp/styles.js
8448
8839
  var WHATSAPP_STYLE_ID = "xmanrui-dsh-im-whatsapp-settings";
@@ -8467,7 +8858,7 @@ function installWhatsappStyles() {
8467
8858
 
8468
8859
  // plugin-src/client/channels/whatsapp/index.js
8469
8860
  var ACTIVE_STATES3 = /* @__PURE__ */ new Set(["pending", "connecting"]);
8470
- var Button14 = React17.forwardRef(function Button15({ children, kind = "secondary", className = "", ...props }, ref) {
8861
+ var Button14 = React18.forwardRef(function Button15({ children, kind = "secondary", className = "", ...props }, ref) {
8471
8862
  return h2("button", {
8472
8863
  ...props,
8473
8864
  ref,
@@ -8689,6 +9080,7 @@ function WhatsappAccountCard({
8689
9080
  removing,
8690
9081
  onReconnect,
8691
9082
  onWorkspaceSave,
9083
+ onAgentPresetSave,
8692
9084
  onRequestRemove,
8693
9085
  onConfirmRemove,
8694
9086
  onCancelRemove
@@ -8748,6 +9140,11 @@ function WhatsappAccountCard({
8748
9140
  disabled: Boolean(busy),
8749
9141
  onSave: onWorkspaceSave
8750
9142
  }),
9143
+ h2(AgentPresetEditor, {
9144
+ agentPreset: account.agentPreset,
9145
+ disabled: Boolean(busy),
9146
+ onSave: onAgentPresetSave
9147
+ }),
8751
9148
  h2(
8752
9149
  "div",
8753
9150
  { className: "ddt-accountFooter dim-cardFooter" },
@@ -8782,22 +9179,23 @@ function WhatsappAccountCard({
8782
9179
  );
8783
9180
  }
8784
9181
  function WhatsappSettingsTab({ rpcCall }) {
8785
- const [model, setModel] = React17.useState({
9182
+ const [model, setModel] = React18.useState({
8786
9183
  phase: "loading",
8787
9184
  bots: [],
8788
9185
  totals: { configured: 0, connected: 0 },
8789
- error: null
9186
+ error: null,
9187
+ agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG
8790
9188
  });
8791
- const [provision, setProvision] = React17.useState(null);
8792
- const [busy, setBusy] = React17.useState(false);
8793
- const [busyByBot, setBusyByBot] = React17.useState({});
8794
- const [testNoticeByBot, setTestNoticeByBot] = React17.useState({});
8795
- const [removeTarget, setRemoveTarget] = React17.useState(null);
8796
- const [now, setNow] = React17.useState(Date.now());
8797
- const mounted = React17.useRef(true);
9189
+ const [provision, setProvision] = React18.useState(null);
9190
+ const [busy, setBusy] = React18.useState(false);
9191
+ const [busyByBot, setBusyByBot] = React18.useState({});
9192
+ const [testNoticeByBot, setTestNoticeByBot] = React18.useState({});
9193
+ const [removeTarget, setRemoveTarget] = React18.useState(null);
9194
+ const [now, setNow] = React18.useState(Date.now());
9195
+ const mounted = React18.useRef(true);
8798
9196
  const workspaceFence = useWorkspaceSnapshotFence();
8799
- const addButtonRef = React17.useRef(null);
8800
- React17.useEffect(() => {
9197
+ const addButtonRef = React18.useRef(null);
9198
+ React18.useEffect(() => {
8801
9199
  const disposeDingtalk = installDingtalkStyles();
8802
9200
  const disposeWhatsapp = installWhatsappStyles();
8803
9201
  mounted.current = true;
@@ -8807,18 +9205,24 @@ function WhatsappSettingsTab({ rpcCall }) {
8807
9205
  disposeDingtalk();
8808
9206
  };
8809
9207
  }, []);
8810
- const invoke = React17.useCallback(async (endpoint, payload = {}, signal) => {
9208
+ const invoke = React18.useCallback(async (endpoint, payload = {}, signal) => {
8811
9209
  if (typeof rpcCall !== "function") throw new TypeError("WhatsApp \u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5");
8812
9210
  return unwrapRpcResult9(await rpcCall(endpoint, payload, signal));
8813
9211
  }, [rpcCall]);
8814
- const loadStatus = React17.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
9212
+ const loadStatus = React18.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
8815
9213
  const workspaceVersion = workspaceFence.beginStatus();
8816
9214
  if (workspaceVersion === null) return void 0;
8817
9215
  if (!silent && mounted.current) setModel((current) => ({ ...current, phase: "loading", error: null }));
8818
9216
  try {
8819
9217
  const snapshot = normalizeSnapshot8(await invoke(WHATSAPP_ENDPOINTS.status, {}, signal));
8820
9218
  if (!mounted.current || signal?.aborted || !workspaceFence.canCommitStatus(workspaceVersion)) return void 0;
8821
- setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
9219
+ setModel({
9220
+ phase: "ready",
9221
+ bots: snapshot.bots,
9222
+ totals: snapshot.totals,
9223
+ error: null,
9224
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
9225
+ });
8822
9226
  if (restore && snapshot.provisioning) setProvision({
8823
9227
  ...snapshot.provisioning,
8824
9228
  durationMs: Math.max(1, snapshot.provisioning.expiresAt - Date.now())
@@ -8835,12 +9239,12 @@ function WhatsappSettingsTab({ rpcCall }) {
8835
9239
  return void 0;
8836
9240
  }
8837
9241
  }, [invoke, workspaceFence]);
8838
- React17.useEffect(() => {
9242
+ React18.useEffect(() => {
8839
9243
  const controller = new AbortController();
8840
9244
  void loadStatus({ signal: controller.signal, restore: true });
8841
9245
  return () => controller.abort();
8842
9246
  }, [loadStatus]);
8843
- React17.useEffect(() => {
9247
+ React18.useEffect(() => {
8844
9248
  if (model.phase !== "ready") return void 0;
8845
9249
  const controller = new AbortController();
8846
9250
  const timer = window.setInterval(
@@ -8852,12 +9256,12 @@ function WhatsappSettingsTab({ rpcCall }) {
8852
9256
  window.clearInterval(timer);
8853
9257
  };
8854
9258
  }, [loadStatus, model.phase]);
8855
- React17.useEffect(() => {
9259
+ React18.useEffect(() => {
8856
9260
  if (!provision || !ACTIVE_STATES3.has(provision.status)) return void 0;
8857
9261
  const timer = window.setInterval(() => mounted.current && setNow(Date.now()), 1e3);
8858
9262
  return () => window.clearInterval(timer);
8859
9263
  }, [provision?.attemptId, provision?.status]);
8860
- const startProvisioning = React17.useCallback(async (replace = false) => {
9264
+ const startProvisioning = React18.useCallback(async (replace = false) => {
8861
9265
  setBusy(true);
8862
9266
  try {
8863
9267
  if (replace && provision?.attemptId) {
@@ -8875,7 +9279,7 @@ function WhatsappSettingsTab({ rpcCall }) {
8875
9279
  if (mounted.current) setBusy(false);
8876
9280
  }
8877
9281
  }, [invoke, provision?.attemptId]);
8878
- const closeProvision = React17.useCallback(async () => {
9282
+ const closeProvision = React18.useCallback(async () => {
8879
9283
  setBusy(true);
8880
9284
  try {
8881
9285
  if (provision?.attemptId && ACTIVE_STATES3.has(provision.status)) {
@@ -8886,7 +9290,7 @@ function WhatsappSettingsTab({ rpcCall }) {
8886
9290
  if (mounted.current) setBusy(false);
8887
9291
  }
8888
9292
  }, [invoke, provision?.attemptId, provision?.status]);
8889
- React17.useEffect(() => {
9293
+ React18.useEffect(() => {
8890
9294
  const attemptId = provision?.attemptId;
8891
9295
  if (!attemptId || !ACTIVE_STATES3.has(provision.status)) return void 0;
8892
9296
  const controller = new AbortController();
@@ -8927,7 +9331,7 @@ function WhatsappSettingsTab({ rpcCall }) {
8927
9331
  if (timer) window.clearTimeout(timer);
8928
9332
  };
8929
9333
  }, [invoke, loadStatus, provision?.attemptId, provision?.status]);
8930
- const botAction = React17.useCallback(async (account, operation, endpoint, payload) => {
9334
+ const botAction = React18.useCallback(async (account, operation, endpoint, payload) => {
8931
9335
  const snapshotVersion = workspaceFence.beginMutation();
8932
9336
  setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
8933
9337
  if (operation === "reconnect") {
@@ -8941,7 +9345,13 @@ function WhatsappSettingsTab({ rpcCall }) {
8941
9345
  const value = await invoke(endpoint, payload);
8942
9346
  const snapshot = normalizeSnapshot8(value);
8943
9347
  if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
8944
- setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
9348
+ setModel({
9349
+ phase: "ready",
9350
+ bots: snapshot.bots,
9351
+ totals: snapshot.totals,
9352
+ error: null,
9353
+ agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
9354
+ });
8945
9355
  if (operation === "reconnect") {
8946
9356
  setTestNoticeByBot((current) => ({
8947
9357
  ...current,
@@ -8992,6 +9402,12 @@ function WhatsappSettingsTab({ rpcCall }) {
8992
9402
  WHATSAPP_ENDPOINTS.setWorkspace,
8993
9403
  { botId: account.botId, workspace }
8994
9404
  ),
9405
+ onAgentPresetSave: (agentPreset) => botAction(
9406
+ account,
9407
+ "preset",
9408
+ WHATSAPP_ENDPOINTS.setAgentPreset,
9409
+ { botId: account.botId, agentPreset }
9410
+ ),
8995
9411
  onRequestRemove: () => setRemoveTarget(account.botId),
8996
9412
  onCancelRemove: () => setRemoveTarget(null),
8997
9413
  onConfirmRemove: async () => {
@@ -9003,7 +9419,9 @@ function WhatsappSettingsTab({ rpcCall }) {
9003
9419
  }
9004
9420
  }))))
9005
9421
  ) : null;
9006
- return h2(
9422
+ return h2(AgentPresetCatalogContext.Provider, {
9423
+ value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG
9424
+ }, h2(
9007
9425
  "section",
9008
9426
  {
9009
9427
  className: "ddt-page dwa-page dim-channelPage",
@@ -9026,7 +9444,7 @@ function WhatsappSettingsTab({ rpcCall }) {
9026
9444
  h2(Button14, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6")
9027
9445
  )
9028
9446
  ) : h2(
9029
- React17.Fragment,
9447
+ React18.Fragment,
9030
9448
  null,
9031
9449
  provision?.status === "pending" ? h2(QrPanel5, {
9032
9450
  provision,
@@ -9042,7 +9460,7 @@ function WhatsappSettingsTab({ rpcCall }) {
9042
9460
  }) : model.bots.length === 0 ? h2(EmptyView6, { busy, onStart: () => void startProvisioning(false) }) : null,
9043
9461
  botList
9044
9462
  )
9045
- );
9463
+ ));
9046
9464
  }
9047
9465
 
9048
9466
  // plugin-src/client/styles.js
@@ -9189,6 +9607,14 @@ var CSS11 = String.raw`
9189
9607
  .dim-panel .dim-workspaceEdit { grid-column: 2; grid-row: 1; padding: 0; border: 0; color: #1677ff; background: transparent; font: inherit; font-weight: 560; white-space: nowrap; cursor: pointer; }
9190
9608
  .dim-panel .dim-workspaceEdit:disabled { cursor: not-allowed; opacity: .55; }
9191
9609
  .dim-panel .dim-workspacePath { min-width: 0; max-width: 100%; grid-column: 1 / -1; grid-row: 2; display: block; overflow-x: auto; overflow-y: hidden; color: var(--dsw-alias-label-primary, #1f2329); font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap; }
9610
+ .dim-panel .dim-preset { min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) max-content; align-items: center; column-gap: 10px; row-gap: 4px; margin-top: 6px; padding: 6px 10px; border: 1px solid var(--dsw-alias-border-l1, #eef0f3); border-radius: 9px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); }
9611
+ .dim-panel .dim-presetHeader { display: contents; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: normal; }
9612
+ .dim-panel .dim-presetHeader > span { grid-column: 1; grid-row: 1; white-space: nowrap; }
9613
+ .dim-panel .dim-presetStatus { grid-column: 2; grid-row: 1; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; white-space: nowrap; }
9614
+ .dim-panel .dim-presetSelect { min-width: 0; max-width: 100%; grid-column: 1 / -1; grid-row: 2; height: 30px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 12px; }
9615
+ .dim-panel .dim-presetSelect:disabled { cursor: not-allowed; opacity: .55; }
9616
+ .dim-panel .dim-presetHelp { grid-column: 1 / -1; grid-row: 3; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 1.4; }
9617
+ .dim-panel .dim-presetError { grid-column: 1 / -1; grid-row: 4; margin: 0; color: var(--dsw-alias-state-error-primary, #d54941); font-size: 12px; line-height: 1.4; }
9192
9618
  .dim-directoryPickerBackdrop { --dim-blue: var(--dsw-alias-state-business-primary, #3370ff); --dim-blue-soft: color-mix(in srgb, var(--dim-blue) 9%, transparent); position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 24px; background: rgb(15 17 21 / 42%); backdrop-filter: blur(3px); }
9193
9619
  .dim-directoryPickerBackdrop, .dim-directoryPickerBackdrop *, .dim-directoryPickerBackdrop *::before, .dim-directoryPickerBackdrop *::after { box-sizing: border-box; }
9194
9620
  .dim-directoryPicker { width: min(720px, 100%); height: min(620px, calc(100vh - 48px)); min-height: 420px; display: grid; grid-template-rows: auto minmax(0, 1fr) auto; overflow: hidden; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 18px; outline: none; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 24px 72px rgb(15 17 21 / 24%); }
@@ -9419,8 +9845,8 @@ function IMSettingsTab({
9419
9845
  officeRpcCall,
9420
9846
  workspaceDirectoryPicker
9421
9847
  }) {
9422
- const [selected, setSelected] = React18.useState("weixin");
9423
- const githubTooltipId = React18.useId();
9848
+ const [selected, setSelected] = React19.useState("weixin");
9849
+ const githubTooltipId = React19.useId();
9424
9850
  const active = CHANNELS.find((channel4) => channel4.id === selected) ?? CHANNELS[0];
9425
9851
  return h2(
9426
9852
  WorkspaceDirectoryPickerContext.Provider,