@yottameta/yotta-skills-plugin 0.29.0 → 0.29.2

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.
@@ -12,7 +12,7 @@
12
12
  "authentication": "ON_INSTALL"
13
13
  },
14
14
  "category": "Developer Tools",
15
- "version": "0.29.0",
15
+ "version": "0.29.2",
16
16
  "description": "YuanGe (元阁) - orchestration routing, inventory, and one-command installer for the YottaMeta skill family (zero dependencies, Node.js only).",
17
17
  "interface": {
18
18
  "displayName": "元阁 yotta-skills",
@@ -12,7 +12,7 @@
12
12
  "authentication": "ON_INSTALL"
13
13
  },
14
14
  "category": "Developer Tools",
15
- "version": "0.29.0",
15
+ "version": "0.29.2",
16
16
  "description": "YuanGe (元阁) - orchestration routing, inventory, and one-command installer for the YottaMeta skill family (zero dependencies, Node.js only).",
17
17
  "interface": {
18
18
  "displayName": "元阁 yotta-skills",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottameta/yotta-skills-plugin",
3
- "version": "0.29.0",
3
+ "version": "0.29.2",
4
4
  "description": "YuanGe (元阁) — orchestration and routing for the YottaMeta skill family, packaged as a YottaMeta Agent Plugin and DeepSeek Harness profile bundle.",
5
5
  "license": "MIT",
6
6
  "keywords": [
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "yotta-skills",
4
- "version": "0.29.0",
4
+ "version": "0.29.2",
5
5
  "description": "YuanGe (元阁) - orchestration routing, inventory, and one-command installer for the YottaMeta skill family (zero dependencies, Node.js only).",
6
6
  "author": {
7
7
  "name": "YottaMeta",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: yotta-skills
3
- version: 0.29.0
4
- description: 元阁 -- 元阁全家技能的总编排策划 + 编排路由 + 一键安装器 + 技能盘点 + 本机技能 Hub + 运行时 hook 适配。Hub 层:hub hosts 只读发现本机已装智能体与技能目录(文件系统优先,不读元忆;已核实 / 自动发现 / 桥接分类 + 状态细分:可用 / 残留(实体未确认)/ 未创建 / 仅标记);hub hosts add/remove/list/mark 注册自定义宿主目录与手动标记(只注册不建目录、移除不删目录;残留清理 hub hosts remove --purge 默认预览、目录入回收站 7 天);install-self / where 把元阁管理引擎独立安装(默认 ~/.yottaskills/yotta-skills,--dir 指定)并查看运行位置;hub install / update 把技能装到 ~/.yottaskills/hub 单点真源(清单 27 + 特殊家族 5,特殊家族跟随各自 npm latest);hub link --all 用 Windows junction / POSIX symlink 分发到全部已核实宿主(默认范围;--include-discovered 显式纳入自动发现目录;锁 / 数据桥接目录永不链接),元技能在链接时收敛宿主旧副本(移入 ~/.yottaskills/trash 保留 7 天、版本闸门 Hub ≥ 宿主);hub unlink 只删链接、fail-closed;非元技能不参与更新(用户自行处理);hub status 显示来源、版本、链接与异常;view 启动本机技能枢纽面板(默认 127.0.0.1:8789,六视图 + 高级 CLI 页,写操作需页面令牌与确认);兼容 agentskills.io 技能格式、Vercel Labs skills CLI 的 .agents/skills 通用目录与 .skill-lock.json v3(只读)。路由层:--route / route_request 按需求摘要给出候选组合、调用顺序、角色、置信度、依据、已装/缺失状态与安装命令,只建议不自动安装;非元阁家族已装技能按 frontmatter description 机械匹配作并列候选(标注来源与未扫描状态,只读不自动调用);M1 记忆裁决层:usage enable/mark 本地结构化记录 + decide-memory / MCP decide_memory 输出 promote / hold / demote 只读建议(需授权 provider;只建议不删除、不自动写元忆);策划层:按场景给出「该组合哪几个元技能、组合强在哪、怎么组合使用(安装与调用均由用户确认后执行)」;安装层:一条命令把 YottaMeta 已发布的全部 yotta-* 技能装进指定智能体或目录(默认 --pin 锁死清单精确版本);盘点层:--inventory / --reindex 扫描本机已装技能生成/更新注册表,新装技能自动被发现(install/update 后自动 re-index,会话开工只建议跑本地 --reindex;更新检查走手动 --check 或后台 --check --scheduled);运行时适配层:hook capabilities / evaluate / bind / unbind 按宿主能力矩阵执行六个统一事件并留证降级,元信 before_install 已接入安装管线;自包含零依赖,不依赖任何元技能;MCP 按需加载且需用户确认后写入配置(可选:list_installed_skills/describe_skill/reindex/route_request/decide_memory,不常驻,未加载降级 CLI)。支持 --list 清单 / --route 路由 / usage 使用记录 / decide-memory 记忆裁决 / hub 技能 Hub / install-self 独立安装 / where 位置查看 / install / update / update --check(只读检查)/ update --check --scheduled(后台周检)/ update --auto(家族自动更新)/ hook 适配 / --inventory / --reindex / --dry-run 预览 / --pin(默认)/ --range。触发:需要批量安装或更新元阁全家技能、单点安装并分发到多个智能体、查看本机装了哪些智能体与技能目录、接管非元技能、按场景组合多个元技能、路由或判断该用哪些技能、判断哪些技能值得长期记忆、查看或记录技能使用信号、盘点或查看本机已装技能、重扫技能注册表、给某个智能体或目录一次性铺齐 yotta-* 技能、评估宿主 hook 能力、预览安装清单、锁版本安装、或用户说 元阁/装全家/一次装齐/yotta-skills/install-all/更新全家/检查更新/自动更新/hook 适配/该用哪个技能/路由技能/记忆裁决/技能该不该记住/盘点技能/查看已装技能/技能 Hub/单点安装/链接分发/接管技能/技能枢纽面板/图形化面板/独立安装元阁/查看元阁位置/自定义宿主/残留清理/宿主状态 等。边界(Do NOT trigger):只做「组合策划 + 静态路由建议 + M1 记忆裁决只读建议 + 清单 + 下载 + 落位 + 汇总 + 盘点 + re-index + Hub 本机真源与链接分发 + hook 适配」,不含技能本体、不做技能内容开发、不 -g 污染全局、不自动安装缺失技能、不静默写宿主配置或全局记忆、不自动删除技能或记忆;家族安装先自举或调用元信装前门禁,DO NOT INSTALL 阻断,非元阁家族包不自动安装。
3
+ version: 0.29.2
4
+ description: 元阁 -- 元阁全家技能的总编排策划 + 编排路由 + 一键安装器 + 技能盘点 + 本机技能 Hub + 运行时 hook 适配。Hub 层:hub hosts 只读发现本机已装智能体与技能目录(文件系统优先,不读元忆;已核实 / 自动发现 / 桥接分类 + 状态细分:可用 / 残留(实体未确认)/ 未创建 / 仅标记);hub hosts add/remove/list/mark/exclude/include/set 注册自定义宿主目录、手动标记、不接管开关与目录覆盖(只注册不建目录、移除不删目录;残留清理 hub hosts remove --purge 默认预览、目录入回收站 7 天;YottaCode 不纳入接管);install-self / where 把元阁管理引擎独立安装(默认 ~/.yottaskills/yotta-skills,--dir 指定)并查看运行位置;hub config get/set/clear 持久化 Hub 位置(config.json;优先级 --hub > YOTTA_SKILLS_HUB > config > 默认;--move 迁移旧 Hub + 重链 + 回收站);hub install / update 把技能装到 ~/.yottaskills/hub 单点真源(清单 27 + 特殊家族 5,特殊家族跟随各自 npm latest;update / hub update 只升不降,--force 才显式降级);hub link --all 用 Windows junction / POSIX symlink 分发到全部已核实宿主(默认范围;--include-discovered 显式纳入自动发现目录;锁 / 数据桥接目录永不链接),元技能在链接时收敛宿主旧副本(移入 ~/.yottaskills/trash 保留 7 天、版本闸门 Hub ≥ 宿主);hub unlink 只删链接、fail-closed;非元技能不参与更新(用户自行处理);hub status 显示来源、版本、链接与异常;view 启动本机技能枢纽面板(默认 127.0.0.1:8789,六视图 + 高级 CLI 页,写操作需页面令牌与确认);兼容 agentskills.io 技能格式、Vercel Labs skills CLI 的 .agents/skills 通用目录与 .skill-lock.json v3(只读)。路由层:--route / route_request 按需求摘要给出候选组合、调用顺序、角色、置信度、依据、已装/缺失状态与安装命令,只建议不自动安装;非元阁家族已装技能按 frontmatter description 机械匹配作并列候选(标注来源与未扫描状态,只读不自动调用);M1 记忆裁决层:usage enable/mark 本地结构化记录 + decide-memory / MCP decide_memory 输出 promote / hold / demote 只读建议(需授权 provider;只建议不删除、不自动写元忆);策划层:按场景给出「该组合哪几个元技能、组合强在哪、怎么组合使用(安装与调用均由用户确认后执行)」;安装层:一条命令把 YottaMeta 已发布的全部 yotta-* 技能装进指定智能体或目录(默认 --pin 锁死清单精确版本);盘点层:--inventory / --reindex 扫描本机已装技能生成/更新注册表,新装技能自动被发现(install/update 后自动 re-index,会话开工只建议跑本地 --reindex;更新检查走手动 --check 或后台 --check --scheduled);运行时适配层:hook capabilities / evaluate / bind / unbind 按宿主能力矩阵执行六个统一事件并留证降级,元信 before_install 已接入安装管线;自包含零依赖,不依赖任何元技能;MCP 按需加载且需用户确认后写入配置(可选:list_installed_skills/describe_skill/reindex/route_request/decide_memory,不常驻,未加载降级 CLI)。支持 --list 清单 / --route 路由 / usage 使用记录 / decide-memory 记忆裁决 / hub 技能 Hub / install-self 独立安装 / where 位置查看 / install / update / update --check(只读检查)/ update --check --scheduled(后台周检)/ update --auto(家族自动更新)/ hook 适配 / --inventory / --reindex / --dry-run 预览 / --pin(默认)/ --range。触发:需要批量安装或更新元阁全家技能、单点安装并分发到多个智能体、查看本机装了哪些智能体与技能目录、接管非元技能、按场景组合多个元技能、路由或判断该用哪些技能、判断哪些技能值得长期记忆、查看或记录技能使用信号、盘点或查看本机已装技能、重扫技能注册表、给某个智能体或目录一次性铺齐 yotta-* 技能、评估宿主 hook 能力、预览安装清单、锁版本安装、或用户说 元阁/装全家/一次装齐/yotta-skills/install-all/更新全家/检查更新/自动更新/hook 适配/该用哪个技能/路由技能/记忆裁决/技能该不该记住/盘点技能/查看已装技能/技能 Hub/单点安装/链接分发/接管技能/技能枢纽面板/图形化面板/独立安装元阁/查看元阁位置/持久化 Hub 位置/换 Hub 目录/迁移 Hub/自定义宿主/残留清理/宿主状态 等。边界(Do NOT trigger):只做「组合策划 + 静态路由建议 + M1 记忆裁决只读建议 + 清单 + 下载 + 落位 + 汇总 + 盘点 + re-index + Hub 本机真源与链接分发 + hook 适配」,不含技能本体、不做技能内容开发、不 -g 污染全局、不自动安装缺失技能、不静默写宿主配置或全局记忆、不自动删除技能或记忆;家族安装先自举或调用元信装前门禁,DO NOT INSTALL 阻断,非元阁家族包不自动安装。
5
5
  license: MIT
6
6
  metadata:
7
7
  zh_name: 元阁
@@ -121,6 +121,15 @@ npx -y @yottameta/yotta-skills hub link --all
121
121
  npx -y @yottameta/yotta-skills view
122
122
  ```
123
123
 
124
+ **换 Hub 存储位置(持久化;`--move` 迁移已有技能并重链)**:
125
+
126
+ ```bash
127
+ # 持久化配置(写入 ~/.yottaskills/config.json;优先级 --hub > YOTTA_SKILLS_HUB > config > 默认)
128
+ npx -y @yottameta/yotta-skills hub config set --hub "D:\my-hub" --move
129
+ npx -y @yottameta/yotta-skills hub config get # 查看生效位置与来源
130
+ npx -y @yottameta/yotta-skills hub config clear # 清除覆盖
131
+ ```
132
+
124
133
  **元阁独立安装(把管理引擎装成独立 CLI,不依赖任何智能体)**:
125
134
 
126
135
  ```bash
@@ -147,7 +156,7 @@ npx -y @yottameta/yotta-skills install --dir ~/my-skills
147
156
  # 只装单个 / 多个技能(按编排策划选组合时常用)
148
157
  npx -y @yottameta/yotta-skills install yotta-memory yotta-verify --dir ~/my-skills
149
158
 
150
- # 增量更新已装技能(补齐缺失 / 版本不一致)
159
+ # 增量更新已装技能(补齐缺失 + 升级;只升不降:本地更高保留不降级,--force 才显式降级)
151
160
  npx -y @yottameta/yotta-skills update --agent codex
152
161
 
153
162
  # 只读检查有没有更新(联网对 npm 最新,不改动;0=全部最新 / 3=有更新 / 1=查失败)
@@ -227,6 +236,12 @@ npx -y @yottameta/yotta-skills hub hosts mark ~/my-agent/skills --state orphan
227
236
  npx -y @yottameta/yotta-skills hub hosts remove ~/my-agent/skills --purge
228
237
  npx -y @yottameta/yotta-skills hub hosts remove ~/my-agent/skills --purge --yes
229
238
 
239
+ # 5d) 不接管 / 目录覆盖(发现 / 显示 / 链接三层跳过;恢复用 include;set 覆盖内置映射)
240
+ npx -y @yottameta/yotta-skills hub hosts exclude box
241
+ npx -y @yottameta/yotta-skills hub hosts include box
242
+ npx -y @yottameta/yotta-skills hub hosts set box --dir "D:\box-agent\skills"
243
+ npx -y @yottameta/yotta-skills hub hosts set box --clear
244
+
230
245
  # 6) 本地面板:预览、确认、执行、留证(仅 127.0.0.1:8789)
231
246
  npx -y @yottameta/yotta-skills view
232
247
 
@@ -254,6 +269,15 @@ npx -y @yottameta/yotta-skills hub remove my-skill --dry-run
254
269
  不判 fail。残留清理 = `hub hosts remove <目录> --purge`(默认预览,`--yes` 执行;
255
270
  只删指向 Hub 的链接,非 Hub 链接 / 非技能内容随目录保留在回收站 7 天)。
256
271
 
272
+ 不接管与目录覆盖(0.29.1 起):`hub hosts exclude <agentId|目录>` 把目标从发现、
273
+ 显示、链接三层全部跳过(`--include-discovered` 也不纳入;已建链接不受影响,可
274
+ `hub unlink` 清理),`hub hosts include` 恢复接管;`hub hosts set <agentId> --dir <目录>`
275
+ 用户级覆盖内置映射(被覆盖旧目录不再进入默认发现 / 链接),`--clear` 恢复默认。
276
+ 面板「宿主矩阵」提供等价动作(不接管 / 恢复接管 / 编辑目录 / 标记可用 / 忽略),
277
+ 写操作均写 Hub 审计。**YottaCode 不纳入接管**(自带三层技能管理;`.yottacode` 与
278
+ `YOTTACODE_HOME` 在映射 / 扫描 / 注册 / 标记 / 链接全部排除)。未核实映射显示
279
+ 「映射·未核实」,「自动发现」只用于文件系统扫描结果。
280
+
257
281
  宿主发现兼容 `agentskills.io` 技能格式与 Vercel Labs `skills` CLI 的
258
282
  `.agents/skills` 通用目录;已收录 81 条宿主映射,并只读兼容
259
283
  `.skill-lock.json` v3(`$XDG_STATE_HOME/skills/` 或 `~/.agents/`)。
@@ -287,6 +311,7 @@ Hub。默认范围为已核实宿主 ∪ 本技能链接台账;`--agent` / `--
287
311
  | `--inventory` | 盘点本机已装技能:扫描技能目录生成/更新注册表(自包含,不依赖元技能);`--json` 输出 JSON、`--project` 附扫项目级目录 |
288
312
  | `--reindex` | 重扫注册表:扫描技能目录并增量合并变化(install / update 完成后 CLI 自动重扫;也可在会话开工等时机手动运行);`--json` 输出 JSON、`--project` 附扫项目级目录;`--rescan` 同义 |
289
313
  | `hub hosts` | 发现本机已装智能体与技能目录(文件系统 + 环境变量;不读元忆 / 注册表);按已核实 / 自动发现 / 桥接分类;`--json` 输出 JSON |
314
+ | `hub config get \| set --hub <path> [--move] \| clear` | Hub 位置持久化(`config.json`;优先级 `--hub` > `YOTTA_SKILLS_HUB` > config > 默认);`--move` = 复制校验 → 重链 → 旧 Hub 入回收站(7 天),fail-closed;`where` / 面板「Hub 位置」显示来源 |
290
315
  | `hub install [skill...]` | 把元技能安装到 Hub 真源(清单 27 + 特殊家族 5;特殊家族跟随各自 npm latest;默认 `~/.yottaskills/hub`;`--hub` / `YOTTA_SKILLS_HUB` 覆盖) |
291
316
  | `hub update` | 更新 Hub 内的元技能;非元技能不参与(来源无统一安装源,用户自行处理) |
292
317
  | `hub adopt --scan` | 只读预演:扫描各宿主现有技能,输出收编候选、多副本冲突、已在 Hub 状态 |
@@ -204,11 +204,14 @@
204
204
  <div class="section panel">
205
205
  <h3>Hub 位置</h3>
206
206
  <div class="mono" id="ovHubDir">-</div>
207
+ <div class="faint" id="ovHubMeta"></div>
207
208
  <div class="toolbar section">
208
209
  <button class="btn small" data-goto="adopt">去收编</button>
209
210
  <button class="btn small" data-goto="links">去链接与体检</button>
210
211
  <button class="btn small" data-goto="records">查看记录</button>
211
212
  <button class="btn small" id="copyHubDir">复制路径</button>
213
+ <button class="btn small" id="hubConfigBtn">设置新位置</button>
214
+ <button class="btn small" id="hubConfigClearBtn" hidden>清除覆盖</button>
212
215
  </div>
213
216
  </div>
214
217
  </section>
@@ -225,6 +228,11 @@
225
228
  <div class="empty" id="hostEmpty" hidden>没有匹配的宿主目录。</div>
226
229
  </div>
227
230
  <div class="section panel"><h3>已安装标记(无技能目录的宿主)</h3><div class="list" id="hostMarks"></div></div>
231
+ <div class="section panel" id="hostExcludedWrap" hidden>
232
+ <h3>不接管名单</h3>
233
+ <div class="faint">这些宿主 / 目录在发现、显示、链接三层全部跳过(--include-discovered 也不纳入)。</div>
234
+ <div class="list" id="hostExcluded"></div>
235
+ </div>
228
236
  </section>
229
237
 
230
238
  <section class="view" id="view-adopt">
@@ -478,8 +486,44 @@
478
486
  }).join('') : '<div class="empty">还没有动作记录。</div>';
479
487
  $('#ovHubDir').textContent = d.hubDir;
480
488
  $('#copyHubDir').onclick = function () { copyText(d.hubDir); };
489
+ var hubInfo = d.hub || {};
490
+ var sourceNames = { flag: '--hub 参数', env: 'YOTTA_SKILLS_HUB 环境变量', config: 'config.json 配置', default: '默认位置' };
491
+ var metaParts = ['来源: ' + (sourceNames[hubInfo.source] || hubInfo.source || '默认位置')];
492
+ if (hubInfo.configured) metaParts.push('配置覆盖: ' + hubInfo.configured);
493
+ if (hubInfo.restartRequired) metaParts.push('配置已指向 ' + (hubInfo.effectiveNow || '') + ',重启 view 后生效');
494
+ $('#ovHubMeta').textContent = metaParts.join(' · ');
495
+ $('#hubConfigClearBtn').hidden = !hubInfo.configured;
481
496
  }
482
497
 
498
+ $('#hubConfigBtn').onclick = function () {
499
+ var current = (state.overview && state.overview.hubDir) || '';
500
+ var body = '<p class="label">写入 config.json 持久覆盖(优先级低于 --hub 参数 / YOTTA_SKILLS_HUB)。不会自动迁移旧 Hub 内容;迁移用 CLI:yotta-skills hub config set --hub &lt;path&gt; --move。</p>' +
501
+ '<label class="label">新的 Hub 目录</label>' +
502
+ '<input id="hubNewPath" placeholder="D:\\my-hub" value="">' +
503
+ '<div class="note mono">当前: ' + esc(current) + '</div>';
504
+ openModal('设置 Hub 位置', body, {
505
+ okText: '写入配置',
506
+ onOk: async function () {
507
+ var value = ($('#hubNewPath').value || '').trim();
508
+ if (!value) { toast('先填写新的 Hub 目录'); return; }
509
+ var r = await api('/api/hub/config', { body: { hub: value, confirm: 'hub-config' } });
510
+ if (r.error) { toast(r.error); return; }
511
+ closeModal();
512
+ toast('已写入配置;重启 view 后生效');
513
+ loadOverview();
514
+ },
515
+ });
516
+ };
517
+ $('#hubConfigClearBtn').onclick = function () {
518
+ confirmModal('清除 Hub 位置覆盖?', '<p class="label">将删除 config.json 中的 Hub 覆盖,恢复 --hub &gt; 环境变量 &gt; 默认位置的解析。不会移动任何文件。</p>', '清除覆盖', true, async function () {
519
+ var r = await api('/api/hub/config', { body: { clear: true, confirm: 'hub-config' } });
520
+ if (r.error) { toast(r.error); return; }
521
+ closeModal();
522
+ toast('已清除覆盖;重启 view 后生效');
523
+ loadOverview();
524
+ });
525
+ };
526
+
483
527
  function renderOverviewSkills() {
484
528
  var d = state.overview;
485
529
  if (!d) return;
@@ -532,13 +576,20 @@
532
576
  if (d.error) { toast(d.error); return; }
533
577
  state.hosts = d;
534
578
  var s = d.summary.states || {};
535
- $('#hostSummary').textContent = '可用 ' + (s.available || 0) + ' · 残留 ' + (s.orphan || 0) + ' · 未创建 ' + (s.missing || 0) + ' · 仅标记 ' + (s.markerOnly || 0) + (s.ignored ? ' · 已忽略 ' + s.ignored : '') + ' · 链接 ' + d.summary.links + '(异常 ' + d.summary.brokenLinks + ')';
579
+ $('#hostSummary').textContent = '可用 ' + (s.available || 0) + ' · 残留 ' + (s.orphan || 0) + ' · 未创建 ' + (s.missing || 0) + ' · 仅标记 ' + (s.markerOnly || 0) + (s.ignored ? ' · 已忽略 ' + s.ignored : '') + (d.summary.excluded ? ' · 不接管 ' + d.summary.excluded : '') + ' · 链接 ' + d.summary.links + '(异常 ' + d.summary.brokenLinks + ')';
536
580
  renderHostStateChips();
537
581
  renderHosts();
538
- var marks = d.installed || [];
582
+ var marks = d.unpaired || [];
539
583
  $('#hostMarks').innerHTML = marks.length ? marks.map(function (m) {
540
- return '<div class="row"><div class="row-main"><div class="row-title">' + esc(m.label) + '</div><div class="row-meta">' + esc(m.source || '') + (m.hasSkillsDir ? ' · 技能目录:' + esc(m.skillDir || '') : ' · 未发现技能目录') + '</div></div><span class="badge ' + (m.hasSkillsDir ? 'ok' : 'info') + '">' + esc(m.hasSkillsDir ? '可链接' : '仅标记') + '</span></div>';
541
- }).join('') : '<div class="empty">没有检测到额外宿主标记。</div>';
584
+ return '<div class="row"><div class="row-main"><div class="row-title">' + esc(m.label) + '</div><div class="row-meta">' + esc(m.source || '') + ' · 未发现技能目录</div></div><span class="badge info">仅标记</span></div>';
585
+ }).join('') : '<div class="empty">没有未配对的宿主标记。</div>';
586
+ renderHostExcluded(d.excluded || []);
587
+ }
588
+ function renderHostExcluded(list) {
589
+ $('#hostExcludedWrap').hidden = list.length === 0;
590
+ $('#hostExcluded').innerHTML = list.map(function (item) {
591
+ return '<div class="row"><div class="row-main"><div class="row-title">' + esc(item.label || item.value) + '</div><div class="row-meta mono">' + esc(item.value) + ' · ' + esc(item.kind === 'agent' ? '智能体' : '目录') + '</div></div><div class="row-actions"><button class="btn small" data-host-include="' + esc(item.value) + '">恢复接管</button></div></div>';
592
+ }).join('');
542
593
  }
543
594
  function renderHostStateChips() {
544
595
  var d = state.hosts; if (!d) return;
@@ -570,10 +621,23 @@
570
621
  return String(h.label || '').toLowerCase().includes(q) || String(h.dir || '').toLowerCase().includes(q);
571
622
  });
572
623
  $('#hostEmpty').hidden = rows.length > 0;
624
+ var overrideIds = {};
625
+ ((d.overrides) || []).forEach(function (o) { overrideIds[String(o.agentId).toLowerCase()] = true; });
573
626
  $('#hostRows').innerHTML = rows.map(function (h) {
574
- var scopeBadge = h.bridgeOnly ? '<span class="badge info">桥接·不链</span>' : (h.detection === 'user-registry' ? '<span class="badge info">用户注册</span>' : h.verified ? '<span class="badge ok">已核实</span>' : '<span class="badge warn">自动发现</span>');
627
+ var scopeBadge = h.bridgeOnly ? '<span class="badge info">桥接·不链</span>'
628
+ : (h.detection === 'user-registry' ? '<span class="badge info">用户注册</span>'
629
+ : (h.detection === 'override' ? '<span class="badge ok">目录覆盖</span>'
630
+ : (h.verified ? '<span class="badge ok">已核实</span>'
631
+ : ((h.detection === 'discovered' || h.detection === 'workspace') ? '<span class="badge warn">自动发现</span>' : '<span class="badge warn">映射·未核实</span>'))));
575
632
  var action = '';
576
633
  if (h.exists && h.state === 'orphan' && !h.bridgeOnly) action += '<button class="btn small danger" data-host-purge="' + esc(h.dir) + '">清理</button> ';
634
+ if (h.state === 'ignored') action += '<button class="btn small" data-host-mark="' + esc(h.dir) + '" data-mark-state="available">标记可用</button> ';
635
+ else if (h.exists && !h.bridgeOnly) action += '<button class="btn small" data-host-mark="' + esc(h.dir) + '" data-mark-state="ignored">忽略</button> ';
636
+ if (h.agentId && !h.bridgeOnly) {
637
+ if (overrideIds[String(h.agentId).toLowerCase()]) action += '<button class="btn small" data-host-clear="' + esc(h.agentId) + '">恢复默认</button> ';
638
+ else action += '<button class="btn small" data-host-edit="' + esc(h.agentId) + '" data-host-dir="' + esc(h.dir) + '" data-host-label="' + esc(h.label) + '">编辑目录</button> ';
639
+ }
640
+ if (!h.bridgeOnly) action += '<button class="btn small" data-host-exclude="' + esc(h.agentId || h.dir) + '">不接管</button> ';
577
641
  if (h.detection === 'user-registry') action += '<button class="btn small" data-host-unregister="' + esc(h.dir) + '">移除注册</button>';
578
642
  return '<tr><td><div class="cell-main">' + esc(h.label) + '</div><div class="cell-sub">' + esc(h.agentId || (h.known ? '已知宿主' : h.detection || '自动发现')) + '</div></td>' +
579
643
  '<td><div class="mono">' + esc(h.dir) + '</div><button class="btn small" data-copy="' + esc(h.dir) + '">复制</button></td>' +
@@ -624,11 +688,64 @@
624
688
  loadHosts();
625
689
  });
626
690
  }
691
+ function openHostEditModal(agentId, dir, label) {
692
+ var body = '<p class="label">覆盖「' + esc(label) + '」的技能目录(写入 hosts.json,不改宿主配置)。被覆盖的旧目录不再进入默认发现 / 链接。</p>' +
693
+ '<div class="section"><label class="label">新目录(必须已存在)</label><input id="hostEditDir" value="' + esc(dir) + '"></div>';
694
+ openModal('编辑目录 · ' + esc(agentId), body, {
695
+ okText: '保存覆盖',
696
+ onOk: async function () {
697
+ var value = ($('#hostEditDir') && $('#hostEditDir').value || '').trim();
698
+ if (!value) { toast('请填写目录'); return; }
699
+ var r = await api('/api/hosts/set', { body: { agentId: agentId, dir: value } });
700
+ if (r.error) { toast(r.error); return; }
701
+ closeModal();
702
+ toast('已覆盖目录');
703
+ loadHosts();
704
+ },
705
+ });
706
+ }
627
707
  $('#hostRows').addEventListener('click', async function (e) {
628
708
  var copyBtn = e.target.closest('[data-copy]');
629
709
  if (copyBtn) { copyText(copyBtn.dataset.copy); return; }
630
710
  var purgeBtn = e.target.closest('[data-host-purge]');
631
711
  if (purgeBtn) { openHostPurgeModal(purgeBtn.dataset.hostPurge); return; }
712
+ var editBtn = e.target.closest('[data-host-edit]');
713
+ if (editBtn) {
714
+ openHostEditModal(editBtn.dataset.hostEdit, editBtn.dataset.hostDir, editBtn.dataset.hostLabel);
715
+ return;
716
+ }
717
+ var clearBtn = e.target.closest('[data-host-clear]');
718
+ if (clearBtn) {
719
+ var clearAgent = clearBtn.dataset.hostClear;
720
+ confirmModal('恢复默认目录?', '<p class="label">删除目录覆盖,恢复内置映射。</p><div class="mono">' + esc(clearAgent) + '</div>', '恢复默认', false, async function () {
721
+ var r = await api('/api/hosts/set', { body: { agentId: clearAgent, clear: true } });
722
+ if (r.error) { toast(r.error); return; }
723
+ closeModal();
724
+ toast('已恢复内置映射');
725
+ loadHosts();
726
+ });
727
+ return;
728
+ }
729
+ var excludeBtn = e.target.closest('[data-host-exclude]');
730
+ if (excludeBtn) {
731
+ var excludeTarget = excludeBtn.dataset.hostExclude;
732
+ confirmModal('加入「不接管」名单?', '<p class="label">该宿主 / 目录将从发现、显示、链接三层全部跳过;已建立的链接不受影响(可用 CLI hub unlink 清理)。</p><div class="mono">' + esc(excludeTarget) + '</div>', '不接管', false, async function () {
733
+ var r = await api('/api/hosts/exclude', { body: { target: excludeTarget } });
734
+ if (r.error) { toast(r.error); return; }
735
+ closeModal();
736
+ toast('已加入不接管名单');
737
+ loadHosts();
738
+ });
739
+ return;
740
+ }
741
+ var markBtn = e.target.closest('[data-host-mark]');
742
+ if (markBtn) {
743
+ var r = await api('/api/hosts/mark', { body: { dir: markBtn.dataset.hostMark, state: markBtn.dataset.markState } });
744
+ if (r.error) { toast(r.error); return; }
745
+ toast('已标记:' + markBtn.dataset.markState);
746
+ loadHosts();
747
+ return;
748
+ }
632
749
  var unregBtn = e.target.closest('[data-host-unregister]');
633
750
  if (unregBtn) {
634
751
  var dir = unregBtn.dataset.hostUnregister;
@@ -641,6 +758,14 @@
641
758
  });
642
759
  }
643
760
  });
761
+ $('#hostExcluded').addEventListener('click', async function (e) {
762
+ var includeBtn = e.target.closest('[data-host-include]');
763
+ if (!includeBtn) return;
764
+ var r = await api('/api/hosts/include', { body: { target: includeBtn.dataset.hostInclude } });
765
+ if (r.error) { toast(r.error); return; }
766
+ toast('已恢复接管');
767
+ loadHosts();
768
+ });
644
769
 
645
770
  async function loadAdopt() {
646
771
  var d = await api('/api/adopt/scan');