@michengai/dsh-agency-agents 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  ## Features
20
20
 
21
- - Browse, enable, and disable bundled experts across 17 divisions in **Settings → Experts**.
21
+ - Filter by category or search, then enable or disable bundled experts in **Settings → Experts**.
22
22
  - Summon enabled experts by name or slug from the composer's **Experts** picker.
23
23
  - Use `list_experts` to discover experts and `summon_expert` to start a one-shot specialist subagent.
24
24
  - Use 271 bundled personas immediately, or connect a separately synchronized expert directory.
package/README.zh-CN.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  ## 功能概览
20
20
 
21
- - 在「设置 → 专家」中按 17 个分区浏览、启用或停用内置专家。
21
+ - 在「设置 → 专家」中按分类筛选或搜索,再启用或停用内置专家。
22
22
  - 在输入框的「专家」中按名称或 slug 召唤已启用的专家处理完整任务。
23
23
  - 提供 `list_experts` 与 `summon_expert` 工具,分别用于发现专家和启动一次性子代理。
24
24
  - 内置 271 份 persona,无需额外下载;也可接入自行同步的专家目录。
@@ -1,6 +1,6 @@
1
1
  # 技术架构基线
2
2
 
3
- 更新时间:2026-08-16 00:10(Asia/Shanghai)
3
+ 更新时间:2026-08-17 03:00(Asia/Shanghai)
4
4
 
5
5
  - 运行时:Node.js ESM、TypeScript 严格模式、DSH/Cordis 插件机制。
6
6
  - 智能体资产:`assets\agency-agents` 是默认根目录;显式 `root` 优先,其次读取 `AGENCY_AGENTS_ROOT`。
@@ -9,7 +9,8 @@
9
9
  - 深度:`maxDepth` 为可选正整数绝对上限;未配置时不改变宿主的既有深度策略。
10
10
  - 启停与持久化:通过 `@deepseek-ai/dsh-settings` 的 settings namespace(`agency-agents`,schema `{ enabled: string[] }`)在宿主侧持久化;**白名单语义,默认全停用**,用户在设置「专家」标签页启用。宿主工具按「已启用」过滤。
11
11
  - 客户端取数:启用状态不经过 `api.settings`(那是 Host 自有配置面,`WEB_SETTINGS_NAMESPACES` 白名单对第三方插件硬编码封死),改走插件自有的 **Typert Remote** 通道 —— 宿主 `AgencyAgentsRemote extends TypertRemoteService`(`@Remote getEnabled/setEnabled`)由 gateway SRC 自动发现,客户端 `$mount(TYPERT_REMOTE)` 后经 `ctx.remote.agencyAgents` 读写(`src/client/remote.ts` 手写 strict 贡献)。第三方安装无需修改或重编 DSH 源码。
12
- - 客户端:client bundle(`lib/client.js`,`window.__ModuleLoader__.load` 格式)提供 composer「专家」按钮 + 弹窗、设置「专家」标签页、@ 菜单;静态花名册与中文名打包进 client(`src/client/roster.ts`、`src/names.ts`)。
12
+ - 客户端:client bundle(`lib/client.js`,`window.__ModuleLoader__.load` 格式)提供 composer「专家」按钮 + 弹窗、设置「专家」标签页、@ 菜单;静态花名册与中英文名打包进 client(`src/client/roster.ts`、`src/names.ts`)。界面词条走 `agency` 命名空间;专家名/简介/排序跟随 `locale` 的 `zh`/`en`。
13
+ - 宿主文案:`list_experts` 的名单、分区标题和用户可见错误读取 settings `locale.preference`(仅显式 `en` 走英文,其余回退 `zh`)。分区筛选同时认 key、中文名和英文名。
13
14
  - 发布:包必须包含 `lib`(含 `client.js`)、智能体资产、`cordis.patch.yml`、Apache `LICENSE` 和 `NOTICE`。
14
15
 
15
16
  ## 与早期临时动态插件(agency-1)的差异
@@ -7,6 +7,9 @@
7
7
  ## 组件职责
8
8
 
9
9
  - `src\index.ts`:解析 frontmatter、加载目录、注册工具和系统提示。
10
+ - `src\i18n.ts`:宿主侧中英文词条,以及分区/专家名的语言切换。
11
+ - `src\names.ts`:中英文分区名与中文专家名。
12
+ - `src\client\locales.ts`:设置页、按钮、`@` 菜单的客户端词条。
10
13
  - `cordis.patch.yml`:通过 DSH bundle 把插件挂载进 profile。
11
14
  - `scripts\verify.mjs`:校验构建入口、发布文件、bundle patch 和许可证资产。
12
15
  - `assets\agency-agents`:上游 persona 快照;仅其中的合法 frontmatter Markdown 会进入目录。
package/lib/client.js CHANGED
@@ -49,6 +49,26 @@ window.__ModuleLoader__.load({
49
49
  support: "支持",
50
50
  testing: "测试"
51
51
  };
52
+ /** 分区目录名 → 英文分区名。 */
53
+ const EN_DIVISION = {
54
+ academic: "Academic",
55
+ design: "Design",
56
+ engineering: "Engineering",
57
+ finance: "Finance",
58
+ "game-development": "Game Development",
59
+ gis: "GIS",
60
+ healthcare: "Healthcare",
61
+ marketing: "Marketing",
62
+ "paid-media": "Paid Media",
63
+ product: "Product",
64
+ "project-management": "Project Management",
65
+ sales: "Sales",
66
+ security: "Security",
67
+ "spatial-computing": "Spatial Computing",
68
+ specialized: "Specialized",
69
+ support: "Support",
70
+ testing: "Testing"
71
+ };
52
72
  /** 智能体 slug(文件名去 .md)→ 中文名(现实岗位)。缺省时回退英文 frontmatter name。 */
53
73
  const ZH_NAME = {
54
74
  "academic-anthropologist": "人类学家",
@@ -2509,6 +2529,16 @@ window.__ModuleLoader__.load({
2509
2529
  "settings.loading": "正在加载专家…",
2510
2530
  "settings.enabled": "已启用",
2511
2531
  "settings.disabled": "已停用",
2532
+ "settings.filter.category": "分类",
2533
+ "settings.filter.all": "全部",
2534
+ "settings.filter.option": "{name}({count})",
2535
+ "settings.filter.showing.one": "显示 {count} 位",
2536
+ "settings.filter.showing.other": "显示 {count} 位",
2537
+ "settings.search": "搜索",
2538
+ "settings.search.placeholder": "名称、slug 或简介",
2539
+ "settings.search.clear": "清除搜索",
2540
+ "settings.empty": "没有匹配的专家。试试其他关键词,或切换到「{all}」。",
2541
+ "settings.empty.reset": "清除筛选",
2512
2542
  "error.conflict": "配置已被其他窗口修改。",
2513
2543
  "error.conflict.refreshed": "配置已被其他窗口修改,已为您刷新。",
2514
2544
  "error.conflict.refreshFailed": "配置已被其他窗口修改,但刷新失败,请手动刷新。",
@@ -2522,7 +2552,6 @@ window.__ModuleLoader__.load({
2522
2552
  "summary.group": "{count} 个",
2523
2553
  "button.title": "召唤专家",
2524
2554
  "menu.empty": "暂无可召唤的专家",
2525
- "group.count": "{division}({count})",
2526
2555
  "summon.instruction": "召唤专家「{name}」({slug})处理以下任务:",
2527
2556
  "summon.instruction.withTask": "召唤专家「{name}」({slug})处理以下任务:\n{task}",
2528
2557
  "division.academic": ZH_DIVISION.academic,
@@ -2550,6 +2579,16 @@ window.__ModuleLoader__.load({
2550
2579
  "settings.loading": "Loading experts…",
2551
2580
  "settings.enabled": "Enabled",
2552
2581
  "settings.disabled": "Disabled",
2582
+ "settings.filter.category": "Category",
2583
+ "settings.filter.all": "All",
2584
+ "settings.filter.option": "{name} ({count})",
2585
+ "settings.filter.showing.one": "Showing {count} expert",
2586
+ "settings.filter.showing.other": "Showing {count} experts",
2587
+ "settings.search": "Search",
2588
+ "settings.search.placeholder": "Name, slug, or description",
2589
+ "settings.search.clear": "Clear search",
2590
+ "settings.empty": "No matching experts. Try another keyword, or switch to {all}.",
2591
+ "settings.empty.reset": "Clear filters",
2553
2592
  "error.conflict": "Settings were changed in another window.",
2554
2593
  "error.conflict.refreshed": "Settings were changed in another window and have been refreshed.",
2555
2594
  "error.conflict.refreshFailed": "Settings were changed in another window, but refresh failed. Please refresh manually.",
@@ -2563,26 +2602,25 @@ window.__ModuleLoader__.load({
2563
2602
  "summary.group": "{count} items",
2564
2603
  "button.title": "Summon expert",
2565
2604
  "menu.empty": "No experts available yet",
2566
- "group.count": "{division} ({count})",
2567
2605
  "summon.instruction": "Summon expert \"{name}\" ({slug}) to handle the following task:",
2568
2606
  "summon.instruction.withTask": "Summon expert \"{name}\" ({slug}) to handle the following task:\n{task}",
2569
- "division.academic": "Academic",
2570
- "division.design": "Design",
2571
- "division.engineering": "Engineering",
2572
- "division.finance": "Finance",
2573
- "division.game-development": "Game Development",
2574
- "division.gis": "GIS",
2575
- "division.healthcare": "Healthcare",
2576
- "division.marketing": "Marketing",
2577
- "division.paid-media": "Paid Media",
2578
- "division.product": "Product",
2579
- "division.project-management": "Project Management",
2580
- "division.sales": "Sales",
2581
- "division.security": "Security",
2582
- "division.spatial-computing": "Spatial Computing",
2583
- "division.specialized": "Specialized",
2584
- "division.support": "Support",
2585
- "division.testing": "Testing"
2607
+ "division.academic": EN_DIVISION.academic,
2608
+ "division.design": EN_DIVISION.design,
2609
+ "division.engineering": EN_DIVISION.engineering,
2610
+ "division.finance": EN_DIVISION.finance,
2611
+ "division.game-development": EN_DIVISION["game-development"],
2612
+ "division.gis": EN_DIVISION.gis,
2613
+ "division.healthcare": EN_DIVISION.healthcare,
2614
+ "division.marketing": EN_DIVISION.marketing,
2615
+ "division.paid-media": EN_DIVISION["paid-media"],
2616
+ "division.product": EN_DIVISION.product,
2617
+ "division.project-management": EN_DIVISION["project-management"],
2618
+ "division.sales": EN_DIVISION.sales,
2619
+ "division.security": EN_DIVISION.security,
2620
+ "division.spatial-computing": EN_DIVISION["spatial-computing"],
2621
+ "division.specialized": EN_DIVISION.specialized,
2622
+ "division.support": EN_DIVISION.support,
2623
+ "division.testing": EN_DIVISION.testing
2586
2624
  };
2587
2625
  //#endregion
2588
2626
  //#region node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
@@ -6627,10 +6665,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6627
6665
  emoji: e.emoji,
6628
6666
  division: e.division,
6629
6667
  divisionZh: ZH_DIVISION[e.division] ?? e.division,
6668
+ divisionEn: EN_DIVISION[e.division] ?? e.division,
6630
6669
  description: e.description,
6631
6670
  descriptionEn: e.descriptionEn
6632
6671
  })).sort((a, b) => a.division.localeCompare(b.division) || a.slug.localeCompare(b.slug));
6633
- function groupByDivision(list) {
6672
+ /** 按当前语言比较专家显示名,供设置页和菜单分组排序。 */
6673
+ function compareExpertName(a, b, active) {
6674
+ const left = active === "en" ? a.nameEn : a.name;
6675
+ const right = active === "en" ? b.nameEn : b.name;
6676
+ return left.localeCompare(right, active === "en" ? "en" : "zh");
6677
+ }
6678
+ function groupByDivision(list, active) {
6634
6679
  const groups = /* @__PURE__ */ new Map();
6635
6680
  for (const e of list) {
6636
6681
  const arr = groups.get(e.division) ?? [];
@@ -6640,9 +6685,34 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6640
6685
  return DIVISION_ORDER.filter((d) => groups.has(d)).map((d) => ({
6641
6686
  division: d,
6642
6687
  divisionZh: ZH_DIVISION[d] ?? d,
6643
- experts: (groups.get(d) ?? []).slice().sort((a, b) => a.name.localeCompare(b.name, "zh"))
6688
+ experts: (groups.get(d) ?? []).slice().sort((a, b) => compareExpertName(a, b, active))
6644
6689
  }));
6645
6690
  }
6691
+ /** 规范化检索词:去首尾空白并转小写,便于中英和 slug 统一匹配。 */
6692
+ function normalizeExpertQuery(query) {
6693
+ return query.trim().toLowerCase();
6694
+ }
6695
+ /** 按名称、slug、分区或简介做包含匹配;空检索视为全部命中。 */
6696
+ function matchExpertQuery(expert, query) {
6697
+ const q = normalizeExpertQuery(query);
6698
+ if (q === "") return true;
6699
+ return [
6700
+ expert.slug,
6701
+ expert.name,
6702
+ expert.nameEn,
6703
+ expert.division,
6704
+ expert.divisionZh,
6705
+ expert.divisionEn,
6706
+ expert.description,
6707
+ expert.descriptionEn
6708
+ ].some((field) => field.toLowerCase().includes(q));
6709
+ }
6710
+ /** 先按分区收窄,再按检索词过滤。division 为空表示全部分类。 */
6711
+ function filterExperts(list, options) {
6712
+ const division = options.division ?? "";
6713
+ return list.filter((expert) => (division === "" || expert.division === division) && matchExpertQuery(expert, options.query ?? ""));
6714
+ }
6715
+ const DIVISION_COUNTS = Object.fromEntries(DIVISION_ORDER.map((division) => [division, EXPERTS.filter((expert) => expert.division === division).length]));
6646
6716
  /** 按当前 locale 取专家显示名:en 用花名册英文名,其余用中文名。 */
6647
6717
  function displayName(e, active) {
6648
6718
  return active === "en" ? e.nameEn : e.name;
@@ -6651,7 +6721,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6651
6721
  function displayDescription(e, active) {
6652
6722
  return active === "en" && e.descriptionEn !== "" ? e.descriptionEn : e.description;
6653
6723
  }
6654
- const CSS = ".aag-btn-wrap{position:relative;order:1;margin-right:-8px}.aag-btn{display:inline-flex;align-items:center;gap:4px;height:28px;padding:0 4px 0 8px;border:none;border-radius:24px;background:transparent;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;font-weight:500;cursor:pointer}.aag-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.aag-menu{position:absolute;bottom:calc(100% + 4px);left:0;box-sizing:border-box;padding:4px;display:flex;flex-direction:column;gap:0;width:300px;max-width:360px;max-height:calc(100vh - 24px);overflow-y:auto;border:1px solid var(--dsw-alias-border-inverted);border-radius:12px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3);z-index:10000}.aag-menu-title{padding:8px 10px;font-size:12px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.aag-menu-item{display:flex;align-items:center;gap:8px;width:100%;min-height:40px;padding:8px 10px;border:none;border-radius:10px;background:transparent;cursor:pointer;text-align:left;font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary);box-sizing:border-box}.aag-menu-item:hover{background:var(--dsw-alias-interactive-bg-hover)}.aag-emoji{flex:0 0 auto;font-size:16px}.aag-menu-empty{padding:8px 10px;color:var(--dsw-alias-label-secondary);font-size:13px}[data-composer-card] :has(> button[aria-haspopup=\"listbox\"]) > :nth-child(2){order:2}\n.aag-section{box-sizing:border-box;display:flex;min-width:0;max-width:760px;width:100%;margin:0 auto;flex-direction:column;gap:16px;padding:0 0 32px;color:var(--dsw-alias-label-primary)}\n.aag-toolbar{display:flex;align-items:flex-start;gap:16px;padding-bottom:12px}\n.aag-title{margin:0;font-size:20px;line-height:28px;font-weight:650;letter-spacing:-.2px}\n.aag-desc{margin:4px 0 0;max-width:42em;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}\n.aag-actions{display:flex;align-items:center;gap:8px;margin-left:auto}\n.aag-action{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:32px;padding:0 12px;border:1px solid transparent;border-radius:8px;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);font:inherit;font-size:13px;font-weight:550;cursor:pointer;transition:opacity 180ms ease,background 180ms ease,border-color 180ms ease}\n.aag-action:hover:not(:disabled){opacity:.9}.aag-action:disabled{opacity:.5;cursor:default}\n.aag-action-secondary{background:transparent;border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}\n.aag-action:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}\n.aag-summary{display:flex;align-items:center;gap:12px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}\n.aag-summary-count{font-size:18px;font-weight:650}.aag-summary-label{color:var(--dsw-alias-label-secondary);font-size:12px}.aag-summary-separator{width:1px;height:24px;background:var(--dsw-alias-border-l2)}\n.aag-group{display:flex;flex-direction:column;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-2)}\n.aag-group-head{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid var(--dsw-alias-border-l1)}\n.aag-group-title{margin:0;font-size:14px;font-weight:600}.aag-count{color:var(--dsw-alias-label-tertiary);font-size:12px}\n.aag-row{display:flex;align-items:center;gap:12px;padding:13px 16px;border-bottom:1px solid var(--dsw-alias-border-l1)}\n.aag-row:last-child{border-bottom:0}\n.aag-row-main{display:flex;min-width:0;flex:1;flex-direction:column;gap:3px}\n.aag-row-id{display:flex;align-items:center;gap:7px;min-width:0}\n.aag-row-name{overflow:hidden;font-size:13px;font-weight:500;line-height:20px;text-overflow:ellipsis;white-space:nowrap}\n.aag-tag{flex:none;padding:1px 6px;border:1px solid var(--dsw-alias-border-l3);border-radius:4px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px}\n.aag-tag-on{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}\n.aag-tag-off{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}\n.aag-note{overflow:hidden;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}\n.aag-error{color:var(--dsw-alias-state-error-primary);font-size:13px;line-height:20px}\n@media (max-width:560px){.aag-toolbar{flex-wrap:wrap}.aag-actions{margin-left:0}.aag-row{align-items:flex-start;flex-wrap:wrap}.aag-row>.aag-action{margin-left:auto}}\n@media (prefers-reduced-motion:reduce){.aag-action{transition:none}}\n" + DIVISION_ORDER.map((d) => `[role="listbox"] div[data-source="division.${d}"] ~ button span:last-child`).join(",") + "{flex:1 1 auto;max-width:none;min-width:0}";
6724
+ const CSS = ".aag-btn-wrap{position:relative;order:1;margin-right:-8px}.aag-btn{display:inline-flex;align-items:center;gap:4px;height:28px;padding:0 4px 0 8px;border:none;border-radius:24px;background:transparent;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;font-weight:500;cursor:pointer}.aag-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.aag-menu{position:absolute;bottom:calc(100% + 4px);left:0;box-sizing:border-box;padding:4px;display:flex;flex-direction:column;gap:0;width:300px;max-width:360px;max-height:calc(100vh - 24px);overflow-y:auto;border:1px solid var(--dsw-alias-border-inverted);border-radius:12px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3);z-index:10000}.aag-menu-title{padding:8px 10px;font-size:12px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.aag-menu-item{display:flex;align-items:center;gap:8px;width:100%;min-height:40px;padding:8px 10px;border:none;border-radius:10px;background:transparent;cursor:pointer;text-align:left;font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary);box-sizing:border-box}.aag-menu-item:hover{background:var(--dsw-alias-interactive-bg-hover)}.aag-emoji{flex:0 0 auto;font-size:16px}.aag-menu-empty{padding:8px 10px;color:var(--dsw-alias-label-secondary);font-size:13px}[data-composer-card] :has(> button[aria-haspopup=\"listbox\"]) > :nth-child(2){order:2}\n.aag-section{box-sizing:border-box;display:flex;min-width:0;max-width:760px;width:100%;margin:0 auto;flex-direction:column;gap:16px;padding:0 0 32px;color:var(--dsw-alias-label-primary)}\n.aag-toolbar{display:flex;align-items:flex-start;gap:16px;padding-bottom:12px}\n.aag-title{margin:0;font-size:20px;line-height:28px;font-weight:650;letter-spacing:-.2px}\n.aag-desc{margin:4px 0 0;max-width:42em;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}\n.aag-actions{display:flex;align-items:center;gap:8px;margin-left:auto}\n.aag-action{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:32px;padding:0 12px;border:1px solid transparent;border-radius:8px;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);font:inherit;font-size:13px;font-weight:550;cursor:pointer;transition:opacity 180ms ease,background 180ms ease,border-color 180ms ease}\n.aag-action:hover:not(:disabled){opacity:.9}.aag-action:disabled{opacity:.5;cursor:default}\n.aag-action-secondary{background:transparent;border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}\n.aag-action:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}\n.aag-summary{display:flex;align-items:center;gap:12px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}\n.aag-summary-count{font-size:18px;font-weight:650}.aag-summary-label{color:var(--dsw-alias-label-secondary);font-size:12px}.aag-summary-separator{width:1px;height:24px;background:var(--dsw-alias-border-l2)}\n.aag-group{display:flex;flex-direction:column;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-2)}\n.aag-group-head{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid var(--dsw-alias-border-l1)}\n.aag-group-title{margin:0;font-size:14px;font-weight:600}.aag-count{color:var(--dsw-alias-label-tertiary);font-size:12px}\n.aag-row{display:flex;align-items:center;gap:12px;padding:13px 16px;border-bottom:1px solid var(--dsw-alias-border-l1)}\n.aag-row:last-child{border-bottom:0}\n.aag-row-main{display:flex;min-width:0;flex:1;flex-direction:column;gap:3px}\n.aag-row-id{display:flex;align-items:center;gap:7px;min-width:0}\n.aag-row-name{overflow:hidden;font-size:13px;font-weight:500;line-height:20px;text-overflow:ellipsis;white-space:nowrap}\n.aag-tag{flex:none;padding:1px 6px;border:1px solid var(--dsw-alias-border-l3);border-radius:4px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px}\n.aag-tag-on{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}\n.aag-tag-off{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}\n.aag-note{overflow:hidden;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}\n.aag-error{color:var(--dsw-alias-state-error-primary);font-size:13px;line-height:20px}\n.aag-filters{display:flex;align-items:flex-end;gap:10px}\n.aag-field{display:flex;min-width:0;flex:1;flex-direction:column;gap:6px}\n.aag-field-category{flex:0 1 220px}\n.aag-field-search{flex:1 1 240px}\n.aag-label{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:16px}\n.aag-control{box-sizing:border-box;width:100%;min-height:32px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}\n.aag-control:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}\n.aag-search-wrap{position:relative;display:flex;align-items:center}\n.aag-search{padding-right:32px}.aag-search::-webkit-search-cancel-button,.aag-search::-webkit-search-decoration{-webkit-appearance:none;appearance:none}\n.aag-search-clear{position:absolute;right:4px;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:16px;line-height:1;cursor:pointer}\n.aag-search-clear:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}\n.aag-search-clear:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}\n.aag-empty{display:flex;flex-direction:column;align-items:center;gap:12px;padding:28px 16px;border:1px dashed var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;text-align:center}\n.aag-filter-meta{margin-left:auto;color:var(--dsw-alias-label-tertiary);font-size:12px}\n@media (max-width:560px){.aag-toolbar{flex-wrap:wrap}.aag-actions{margin-left:0}.aag-filters{flex-direction:column;align-items:stretch}.aag-field-category,.aag-field-search{flex:none}.aag-row{align-items:flex-start;flex-wrap:wrap}.aag-row>.aag-action{margin-left:auto}.aag-filter-meta{margin-left:0}}\n@media (prefers-reduced-motion:reduce){.aag-action{transition:none}}\n" + DIVISION_ORDER.map((d) => `[role="listbox"] div[data-source="division.${d}"] ~ button span:last-child`).join(",") + "{flex:1 1 auto;max-width:none;min-width:0}";
6655
6725
  /** 将写失败映射到 agency 词条;非冲突错误返回 null,由调用方展示原始消息。 */
6656
6726
  function writeErrorKey(error, options) {
6657
6727
  if (error instanceof Error && error.message.includes("changed since it was read")) {
@@ -6773,7 +6843,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6773
6843
  });
6774
6844
  setOpen(false);
6775
6845
  };
6776
- const groups = groupByDivision(EXPERTS.filter((e) => enabled.has(e.slug)));
6846
+ const groups = groupByDivision(EXPERTS.filter((e) => enabled.has(e.slug)), props.getActive());
6777
6847
  const menu = open ? react.default.createElement("div", { className: "aag-menu" }, groups.length === 0 ? react.default.createElement("div", { className: "aag-menu-empty" }, props.t("menu.empty")) : groups.map((g) => menuGroup(g, pick, props.t, props.getActive))) : null;
6778
6848
  return react.default.createElement("div", { className: "aag-btn-wrap" }, react.default.createElement("button", {
6779
6849
  type: "button",
@@ -6785,6 +6855,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6785
6855
  function AgentsSettings(props) {
6786
6856
  const [state, setState] = react.default.useState(null);
6787
6857
  const [error, setError] = react.default.useState(null);
6858
+ const [query, setQuery] = react.default.useState("");
6859
+ const [division, setDivision] = react.default.useState("");
6788
6860
  const saving = react.default.useRef(false);
6789
6861
  const [isSaving, setIsSaving] = react.default.useState(false);
6790
6862
  const load = react.default.useCallback(() => {
@@ -6852,9 +6924,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6852
6924
  className: "aag-note"
6853
6925
  }, props.t("settings.loading")));
6854
6926
  else {
6855
- const groups = groupByDivision(EXPERTS);
6927
+ const filtered = filterExperts(EXPERTS, {
6928
+ query,
6929
+ division
6930
+ });
6931
+ const groups = groupByDivision(filtered, props.getActive());
6856
6932
  const enabledCount = [...state.enabled].filter((slug) => EXPERTS.some((e) => e.slug === slug)).length;
6857
6933
  const total = EXPERTS.length;
6934
+ const filteredCount = filtered.length;
6935
+ const hasFilter = normalizeExpertQuery(query) !== "" || division !== "";
6936
+ const resetFilters = () => {
6937
+ setQuery("");
6938
+ setDivision("");
6939
+ };
6858
6940
  nodes.push(react.default.createElement("div", {
6859
6941
  key: "toolbar",
6860
6942
  className: "aag-toolbar"
@@ -6867,7 +6949,56 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6867
6949
  nodes.push(react.default.createElement("div", {
6868
6950
  key: "summary",
6869
6951
  className: "aag-summary"
6870
- }, react.default.createElement("span", { className: "aag-summary-count" }, total), react.default.createElement("span", { className: "aag-summary-label" }, props.t(total === 1 ? "summary.total.one" : "summary.total.other", { count: total })), react.default.createElement("span", { className: "aag-summary-separator" }), react.default.createElement("span", { className: "aag-summary-count" }, enabledCount), react.default.createElement("span", { className: "aag-summary-label" }, props.t(enabledCount === 1 ? "summary.enabled.one" : "summary.enabled.other", { count: enabledCount }))));
6952
+ }, react.default.createElement("span", { className: "aag-summary-count" }, total), react.default.createElement("span", { className: "aag-summary-label" }, props.t(total === 1 ? "summary.total.one" : "summary.total.other", { count: total })), react.default.createElement("span", { className: "aag-summary-separator" }), react.default.createElement("span", { className: "aag-summary-count" }, enabledCount), react.default.createElement("span", { className: "aag-summary-label" }, props.t(enabledCount === 1 ? "summary.enabled.one" : "summary.enabled.other", { count: enabledCount })), hasFilter ? react.default.createElement("span", {
6953
+ className: "aag-filter-meta",
6954
+ "aria-live": "polite"
6955
+ }, props.t(filteredCount === 1 ? "settings.filter.showing.one" : "settings.filter.showing.other", { count: filteredCount })) : null));
6956
+ nodes.push(react.default.createElement("div", {
6957
+ key: "filters",
6958
+ className: "aag-filters"
6959
+ }, react.default.createElement("div", { className: "aag-field aag-field-category" }, react.default.createElement("label", {
6960
+ className: "aag-label",
6961
+ htmlFor: "aag-filter-category"
6962
+ }, props.t("settings.filter.category")), react.default.createElement("select", {
6963
+ id: "aag-filter-category",
6964
+ className: "aag-control",
6965
+ value: division,
6966
+ onChange: (ev) => setDivision(ev.currentTarget.value)
6967
+ }, react.default.createElement("option", { value: "" }, props.t("settings.filter.option", {
6968
+ name: props.t("settings.filter.all"),
6969
+ count: total
6970
+ })), DIVISION_ORDER.map((key) => react.default.createElement("option", {
6971
+ key,
6972
+ value: key
6973
+ }, props.t("settings.filter.option", {
6974
+ name: props.t(`division.${key}`),
6975
+ count: DIVISION_COUNTS[key] ?? 0
6976
+ }))))), react.default.createElement("div", { className: "aag-field aag-field-search" }, react.default.createElement("label", {
6977
+ className: "aag-label",
6978
+ htmlFor: "aag-filter-search"
6979
+ }, props.t("settings.search")), react.default.createElement("div", { className: "aag-search-wrap" }, react.default.createElement("input", {
6980
+ id: "aag-filter-search",
6981
+ className: "aag-control aag-search",
6982
+ type: "search",
6983
+ value: query,
6984
+ autoComplete: "off",
6985
+ spellCheck: false,
6986
+ placeholder: props.t("settings.search.placeholder"),
6987
+ onChange: (ev) => setQuery(ev.currentTarget.value)
6988
+ }), query !== "" ? react.default.createElement("button", {
6989
+ type: "button",
6990
+ className: "aag-search-clear",
6991
+ "aria-label": props.t("settings.search.clear"),
6992
+ onClick: () => setQuery("")
6993
+ }, "×") : null))));
6994
+ if (groups.length === 0) nodes.push(react.default.createElement("div", {
6995
+ key: "empty",
6996
+ className: "aag-empty"
6997
+ }, react.default.createElement("div", null, props.t("settings.empty", { all: props.t("settings.filter.all") })), react.default.createElement("button", {
6998
+ type: "button",
6999
+ className: "aag-action aag-action-secondary",
7000
+ onClick: resetFilters
7001
+ }, props.t("settings.empty.reset"))));
6871
7002
  for (const g of groups) nodes.push(react.default.createElement("div", {
6872
7003
  key: g.division,
6873
7004
  className: "aag-group"
@@ -6965,7 +7096,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6965
7096
  exports.apply = apply;
6966
7097
  exports.applyExpertSummon = applyExpertSummon;
6967
7098
  exports.buildSummonInstruction = buildSummonInstruction;
7099
+ exports.compareExpertName = compareExpertName;
7100
+ exports.filterExperts = filterExperts;
6968
7101
  exports.inject = inject;
7102
+ exports.matchExpertQuery = matchExpertQuery;
7103
+ exports.normalizeExpertQuery = normalizeExpertQuery;
6969
7104
  exports.writeErrorKey = writeErrorKey;
6970
7105
  exports.writeErrorMessage = writeErrorMessage;
6971
7106
  return module.exports;