psyclaw 0.30.16 → 0.30.17

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.
@@ -2673,14 +2673,20 @@ async function loadEcosystem() {
2673
2673
  json("/api/recommended-agents"),
2674
2674
  json("/api/enabled-capabilities")
2675
2675
  ]);
2676
- const skillSet = new Set((enabled.skills || []).map(item => item.id));
2677
- const mcpSet = new Set((enabled.mcp || []).map(item => item.id));
2676
+ const skillStatus = new Map((enabled.skills || []).map(item => [item.id, item]));
2677
+ const mcpStatus = new Map((enabled.mcp || []).map(item => [item.id, item]));
2678
2678
  state.eco = [
2679
2679
  ...(enabled.coreSkills || []).map(item => ({ ...item, kind: "core", description: item.description || "PsyClaw 内置科研核心 Skill", sourceRef: "bundled", enabled: item.enabled, installed: true })),
2680
- ...(skills.items || []).map(item => ({ ...item, kind: "skill", enabled: skillSet.has(item.id), installed: skillSet.has(item.id) })),
2680
+ ...(skills.items || []).map(item => {
2681
+ const status = skillStatus.get(item.id) || {};
2682
+ return { ...item, kind: "skill", enabled: Boolean(status.enabled), installed: Boolean(status.installed) };
2683
+ }),
2681
2684
  ...(plugins.items || []).map(item => ({ ...item, kind: "plugin", enabled: Boolean(item.enabled), installed: Boolean(item.installed) })),
2682
2685
  ...(enabled.localSkills || []).map(item => ({ ...item, kind: "local", sourceRef: item.path || "用户配置目录", installed: true })),
2683
- ...(mcps.items || []).map(item => ({ ...item, kind: "mcp", enabled: mcpSet.has(item.id), installed: mcpSet.has(item.id) })),
2686
+ ...(mcps.items || []).map(item => {
2687
+ const status = mcpStatus.get(item.id) || {};
2688
+ return { ...item, kind: "mcp", enabled: Boolean(status.enabled), installed: Boolean(status.installed) };
2689
+ }),
2684
2690
  ...(agents.items || []).map(item => ({ ...item, kind: "agent", enabled: Boolean(item.enabled), installed: Boolean(item.installed), locked: Boolean(item.locked) })),
2685
2691
  ...(skills.externalTools || []).map(item => ({ ...item, kind: "external", enabled: false, installed: false }))
2686
2692
  ];
@@ -2724,7 +2730,13 @@ function renderEcosystem() {
2724
2730
  $("ecosystem-body").innerHTML = items.map(item => {
2725
2731
  const prereqs = itemPrereqs(item);
2726
2732
  const busy = Boolean(state.installing[item.id]);
2727
- const installLabel = busy ? "正在安装…" : item.kind === "plugin" && item.installed ? "重新安装" : item.kind === "core" ? "启用" : "安装";
2733
+ const statusLabel = busy ? "正在安装…"
2734
+ : item.locked ? "内置可用"
2735
+ : item.enabled ? (item.kind === "plugin" ? "已安装" : "已启用")
2736
+ : item.installed ? "已安装未启用"
2737
+ : item.kind === "plugin" && item.configured ? "配置存在,文件缺失"
2738
+ : item.kind === "external" ? "可安装外部工具"
2739
+ : "未安装";
2728
2740
  return `
2729
2741
  <tr>
2730
2742
  <td>
@@ -2747,25 +2759,46 @@ function renderEcosystem() {
2747
2759
  </span>
2748
2760
  </td>
2749
2761
  <td>
2750
- <span class="tag ${item.enabled ? "ok" : item.configured ? "warn" : ""}">
2751
- ${busy ? "正在安装…" : item.locked ? "内置可用" : item.enabled ? (item.kind === "plugin" ? "已安装" : "已启用") : item.kind === "plugin" && item.configured ? "配置存在,文件缺失" : item.kind === "external" ? "可安装外部工具" : "未启用"}
2762
+ <span class="tag ${item.enabled ? "ok" : item.installed || item.configured ? "warn" : ""}">
2763
+ ${esc(statusLabel)}
2752
2764
  </span>
2753
2765
  </td>
2754
2766
  <td style="text-align:right">
2755
- ${item.locked || item.kind === "agent" ? "—" :
2756
- item.kind === "plugin" && item.local ? "—" :
2757
- item.kind === "plugin" ? `<button class="button small ${item.installed ? "" : "primary"}" ${busy ? "disabled" : ""} onclick="installCapability('plugin','${esc(item.id)}')">${installLabel}</button>` :
2758
- item.kind === "external" ? `<button class="button small primary" ${busy ? "disabled" : ""} onclick="installCapability('external','${esc(item.id)}')">${installLabel}</button>` :
2759
- item.enabled ? `<button class="button small" onclick="toggleCapability('${item.kind}','${esc(item.id)}',false)">停用</button>` :
2760
- item.kind === "local" ? `<button class="button small primary" onclick="toggleCapability('local','${esc(item.id)}',true)">启用</button>` :
2761
- `<button class="button small primary" ${busy ? "disabled" : ""} onclick="installCapability('${item.kind}','${esc(item.id)}')">${installLabel}</button>`
2762
- }
2767
+ ${capabilityActionButtons(item, busy)}
2763
2768
  </td>
2764
2769
  </tr>
2765
2770
  `;
2766
2771
  }).join("") || '<tr><td colspan="5" class="empty" style="min-height:100px">未找到匹配的能力项</td></tr>';
2767
2772
  }
2768
2773
 
2774
+ function capabilityActionButtons(item, busy) {
2775
+ if (item.locked || item.kind === "agent") return "—";
2776
+ if (item.kind === "plugin" && item.local) return "—";
2777
+ if (item.kind === "plugin") {
2778
+ const label = busy ? "正在安装…" : item.installed ? "重新安装" : "安装";
2779
+ return `<button class="button small ${item.installed ? "" : "primary"}" ${busy ? "disabled" : ""} onclick="installCapability('plugin','${esc(item.id)}')">${label}</button>`;
2780
+ }
2781
+ if (item.kind === "external") {
2782
+ return `<button class="button small primary" ${busy ? "disabled" : ""} onclick="installCapability('external','${esc(item.id)}')">${busy ? "正在安装…" : "安装"}</button>`;
2783
+ }
2784
+ if (item.kind === "core" || item.kind === "local") {
2785
+ return item.enabled
2786
+ ? `<button class="button small" onclick="toggleCapability('${item.kind}','${esc(item.id)}',false)">停用</button>`
2787
+ : `<button class="button small primary" onclick="toggleCapability('${item.kind}','${esc(item.id)}',true)">启用</button>`;
2788
+ }
2789
+ // Recommended Skill / MCP: keep enable/disable separate from install/reinstall.
2790
+ const buttons = [];
2791
+ if (item.installed) {
2792
+ buttons.push(item.enabled
2793
+ ? `<button class="button small" onclick="toggleCapability('${item.kind}','${esc(item.id)}',false)">停用</button>`
2794
+ : `<button class="button small primary" onclick="toggleCapability('${item.kind}','${esc(item.id)}',true)">启用</button>`);
2795
+ buttons.push(`<button class="button small" ${busy ? "disabled" : ""} onclick="installCapability('${item.kind}','${esc(item.id)}')">${busy ? "正在安装…" : "重新安装"}</button>`);
2796
+ } else {
2797
+ buttons.push(`<button class="button small primary" ${busy ? "disabled" : ""} onclick="installCapability('${item.kind}','${esc(item.id)}')">${busy ? "正在安装…" : "安装"}</button>`);
2798
+ }
2799
+ return buttons.join(" ");
2800
+ }
2801
+
2769
2802
  async function toggleCapability(kind, id, enabled) {
2770
2803
  await json("/api/recommendation-state", {
2771
2804
  method: "POST",
@@ -2790,14 +2823,15 @@ async function installCapability(kind, id) {
2790
2823
  const scope = kind === "skill" || kind === "plugin"
2791
2824
  ? (confirm("安装到系统全局还是当前项目?\n确定:全局所有项目可用;取消:仅当前项目。") ? "user" : "project")
2792
2825
  : undefined;
2793
- let confirmText = `确认安装「${item?.name || id}」?`;
2826
+ const reinstall = Boolean(item?.installed);
2827
+ let confirmText = `确认${reinstall ? "重新安装" : "安装"}「${item?.name || id}」?`;
2794
2828
  if (kind === "external") {
2795
2829
  confirmText += "\n\n系统将按官方可信来源安装;任务会交给当前 PsyClaw 对话中的模型执行。请保持终端会话打开。";
2796
2830
  } else if (kind === "mcp" || kind === "skill") {
2797
2831
  if (prereqs.length) {
2798
2832
  confirmText += `\n\n前置依赖也将一并排队安装:${prereqs.join("、")}。\n安装任务会交给当前 PsyClaw 对话中的模型执行;请保持终端会话打开,完成后执行 /reload。`;
2799
2833
  } else {
2800
- confirmText += "\n\n安装任务会交给当前 PsyClaw 对话中的模型执行;请保持终端会话打开,完成后执行 /reload。";
2834
+ confirmText += `\n\n${reinstall ? "将覆盖当前安装目录并重新交给模型安装" : "安装任务会交给当前 PsyClaw 对话中的模型执行"};请保持终端会话打开,完成后执行 /reload。`;
2801
2835
  }
2802
2836
  } else {
2803
2837
  confirmText += "\n\n安装完成后需要执行 /reload 生效。";
@@ -448,7 +448,7 @@ async function skillManagerRows(root, state) {
448
448
  scope,
449
449
  blocked: typeof item.sourceRef !== "string" || !/^https:\/\//.test(item.sourceRef),
450
450
  reason: onDisk
451
- ? `已检测到目录但未通过来源/许可/哈希校验${state.skills.includes(id) ? "(当前配置为启用,但校验失败不会加载)" : ""};可按 Enter 重新安装修复`
451
+ ? `已检测到目录但未通过来源/许可/哈希校验${state.skills.includes(id) ? "(当前配置为启用,但校验失败不会加载)" : ""};可按 r 重新安装修复`
452
452
  : typeof item.sourceRef === "string" && /^https:\/\//.test(item.sourceRef)
453
453
  ? "尚未安装;按 Enter 选择安装范围并交给当前模型处理"
454
454
  : "没有可交给模型检查的来源网址",
@@ -599,7 +599,7 @@ async function openMcpManager(ctx, rows) {
599
599
  return ctx.ui.custom((tui, theme, keybindings, done) => (new SkillManagerComponent(mcpManagerItems(rows), tui, theme, keybindings, done, {
600
600
  title: "MCP 管理",
601
601
  itemLabel: "MCP",
602
- footer: "↑/↓ 移动 · Space/Enter 安装或启停 · a 全部开启 · d 全部关闭 · Esc 关闭",
602
+ footer: "↑/↓ 移动 · Space/Enter 安装或启停 · r 重新安装 · a 全部开启 · d 全部关闭 · Esc 关闭",
603
603
  enabledText: "已开启;再次点击可关闭",
604
604
  disabledText: "已安装,当前未启用;点击可开启",
605
605
  missingMessage: "尚未安装;点击交给模型安装并默认启用",