psyclaw 0.27.5 → 0.27.7
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 +9 -7
- package/dist/apps/panel/index.html +11 -11
- package/dist/src/adapters/pi/extension.js +383 -55
- package/dist/src/adapters/pi/extension.js.map +1 -1
- package/dist/src/chat.js +2 -2
- package/dist/src/chat.js.map +1 -1
- package/dist/src/cli.js +1 -4
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/integrations/mcp-runtime.d.ts +26 -0
- package/dist/src/integrations/mcp-runtime.js +154 -0
- package/dist/src/integrations/mcp-runtime.js.map +1 -0
- package/dist/src/panel/events.d.ts +2 -1
- package/dist/src/panel/events.js +2 -5
- package/dist/src/panel/events.js.map +1 -1
- package/dist/src/panel/server.js +28 -8
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/project/bootstrap.d.ts +17 -0
- package/dist/src/project/bootstrap.js +87 -1
- package/dist/src/project/bootstrap.js.map +1 -1
- package/dist/src/skills/user-skills.d.ts +57 -0
- package/dist/src/skills/user-skills.js +294 -0
- package/dist/src/skills/user-skills.js.map +1 -0
- package/dist/src/style/cli-ui.js +1 -1
- package/dist/src/style/cli-ui.js.map +1 -1
- package/dist/src/telemetry/export.d.ts +8 -6
- package/dist/src/telemetry/export.js +81 -18
- package/dist/src/telemetry/export.js.map +1 -1
- package/dist/src/tui/skill-manager.d.ts +3 -0
- package/dist/src/tui/skill-manager.js +11 -1
- package/dist/src/tui/skill-manager.js.map +1 -1
- package/package.json +1 -1
- package/scripts/rebrand-pi.mjs +66 -4
- package/skills/core/citation-audit/SKILL.md +9 -9
- package/skills/core/evidence-capture/SKILL.md +9 -9
- package/skills/core/research-brief/SKILL.md +8 -8
- package/skills/core/research-intake/SKILL.md +9 -9
package/README.md
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
PsyClaw 是面向社会科学研究的 Pi 工作台。它把研究项目、证据来源、Claim-Evidence 账本、完整性门禁、人工裁决、可恢复工作流和本地面板接到官方 Pi runtime 上;它不替代统计软件,也不会把未经核验的引用、结果或审稿意见写成事实。
|
|
4
4
|
|
|
5
|
-
当前版本:`0.27.
|
|
5
|
+
当前版本:`0.27.7`。本仓库以 `psypi v0.4.1` 的完整工作台为功能基线完成改名;`psypi` 仓库保留为历史仓库,正式命令和后续发布均使用 `psyclaw`。
|
|
6
6
|
|
|
7
7
|
## 安装
|
|
8
8
|
|
|
9
9
|
需要 Node.js `>=22.19.0`。官方 npm 源:
|
|
10
10
|
|
|
11
11
|
```powershell
|
|
12
|
-
npm install -g psyclaw@0.27.
|
|
12
|
+
npm install -g psyclaw@0.27.7
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
中国大陆网络较慢或无法访问官方源时:
|
|
16
16
|
|
|
17
17
|
```powershell
|
|
18
|
-
npm install -g psyclaw@0.27.
|
|
18
|
+
npm install -g psyclaw@0.27.7 --registry=https://registry.npmmirror.com
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
确认命令入口:
|
|
@@ -54,7 +54,7 @@ psyclaw brief
|
|
|
54
54
|
| --- | --- |
|
|
55
55
|
| 启动研究对话 | `psyclaw` 或 `psyclaw chat` |
|
|
56
56
|
| 创建研究项目 | `psyclaw init <goal> --paradigm <profile>` |
|
|
57
|
-
| 启动受控研究流程 | 对话中依次输入 `/init <goal>`、`/run [objective]` |
|
|
57
|
+
| 启动受控研究流程 | 对话中依次输入 `/init <goal>`、`/run [--skills a,b] [objective]` |
|
|
58
58
|
| 模拟同行评审 | 论文完成后在对话中输入 `/review` |
|
|
59
59
|
| 登记本地证据 | `psyclaw evidence add <path> --level user\|fulltext` |
|
|
60
60
|
| 生成离线简报 | `psyclaw brief` |
|
|
@@ -63,10 +63,12 @@ psyclaw brief
|
|
|
63
63
|
| 扫描本机其他 Agent | `psyclaw agents` |
|
|
64
64
|
| 查看 PsyClaw 和内置 Pi 更新 | `psyclaw check-updates` |
|
|
65
65
|
| 更新 PsyClaw 和内置 Pi | `psyclaw update` |
|
|
66
|
-
|
|
|
66
|
+
| 导出完整使用路径 | 在对话中输入 `/export` |
|
|
67
67
|
| 打开科研面板 | 在对话中输入 `/panel` |
|
|
68
68
|
| 查看或切换 Provider | 在对话中输入 `/provider` 或 `/provider <id>` |
|
|
69
69
|
| 管理启动横幅宠物 | `/pet status`、`/pet on`、`/pet off`(默认关闭) |
|
|
70
|
+
| 调用本机 Skill | `/skill <name> [任务]` 或 Pi 原生 `/skill:<name>` |
|
|
71
|
+
| 调用已配置 MCP | 模型通过 `psyclaw_mcp` 自动发现并调用 `.psyclaw/mcp/*.json` 中启用的服务器 |
|
|
70
72
|
|
|
71
73
|
`psyclaw update` 会更新 PsyClaw 整包,并同时安装该版本锁定、验证过的内置 Pi;它不会更新或删除系统中单独安装的 `pi` 命令。在源码仓库中运行时,命令会停止并提示通过 Git 更新,不会覆盖本地修改。
|
|
72
74
|
仅查看更新计划而不执行时,使用 `psyclaw update --check`。旧的 `--yes` 参数仍兼容,但不再需要。
|
|
@@ -74,10 +76,10 @@ psyclaw brief
|
|
|
74
76
|
### 导出使用路径
|
|
75
77
|
|
|
76
78
|
```bash
|
|
77
|
-
/
|
|
79
|
+
/export
|
|
78
80
|
```
|
|
79
81
|
|
|
80
|
-
该斜杠命令直接在 PsyClaw 对话界面中使用,从当前项目的工作流日志和对应 Pi 会话中生成
|
|
82
|
+
该斜杠命令直接在 PsyClaw 对话界面中使用,从当前项目的工作流日志和对应 Pi 会话中生成 `outputs/psyclaw-export.otlp.json`,用于通过 OpenTelemetry Collector 导入 Langfuse 或 LangSmith。导出文件包含对话正文、工具参数、研究内容、原始 ID 与绝对路径,只写入本地,不会自动上传;不要将其提交到公开仓库。终端入口为 `psyclaw export`。Panel 不再读取实时 Trace,只展示 `outputs/` 下已经显式生成的 Export 文件。
|
|
81
83
|
|
|
82
84
|
## 开发者本地安装
|
|
83
85
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
:root{color-scheme:light;--bg:#f4f6f8;--surface:#fff;--line:#dfe3e8;--text:#17202a;--muted:#66717d;--accent:#087f73;--accent-soft:#e8f5f3;--ok:#15803d;--warn:#a16207;--bad:#b42318;--radius:6px;font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
|
9
9
|
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font-size:14px}button,input,select{font:inherit}button{cursor:pointer}.shell{min-height:100vh;display:grid;grid-template-rows:56px 1fr}.topbar{display:flex;align-items:center;justify-content:space-between;padding:0 24px;background:var(--surface);border-bottom:1px solid var(--line)}.brand{display:flex;align-items:center;gap:10px;font-weight:750;font-size:17px}.brand-mark{display:grid;place-items:center;width:30px;height:30px;background:var(--text);color:#fff;border-radius:5px}.local{font-size:12px;color:var(--muted)}.layout{display:grid;grid-template-columns:190px minmax(0,1fr);min-height:0}.nav{padding:18px 12px;background:#eef1f3;border-right:1px solid var(--line)}.nav button{width:100%;display:flex;align-items:center;gap:10px;padding:10px 12px;margin-bottom:4px;border:0;background:transparent;color:var(--muted);text-align:left;border-radius:5px}.nav button.active{background:var(--surface);color:var(--text);box-shadow:0 0 0 1px var(--line);font-weight:650}.main{padding:22px;min-width:0}.view{display:none}.view.active{display:block}.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin-bottom:16px}.page-head h1{font-size:20px;margin:0 0 4px}.page-head p{margin:0;color:var(--muted);font-size:13px}.button{border:1px solid var(--line);background:var(--surface);padding:7px 11px;border-radius:5px;color:var(--text)}.button.primary{background:var(--accent);border-color:var(--accent);color:#fff}.button.small{padding:4px 8px;font-size:12px}.button:disabled{opacity:.5;cursor:default}.panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius)}.workspace{display:grid;grid-template-columns:280px minmax(0,1fr);height:calc(100vh - 124px);overflow:hidden}.tree{border-right:1px solid var(--line);overflow:auto;padding:10px}.tree-section{margin:12px 8px 5px;color:var(--muted);font-size:11px;font-weight:700;text-transform:uppercase}.tree-item{display:flex;align-items:center;gap:8px;width:100%;border:0;background:transparent;padding:7px 8px;border-radius:4px;text-align:left;color:var(--text);overflow:hidden}.tree-item:hover,.tree-item.active{background:var(--accent-soft);color:#075e56}.tree-item span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.viewer{overflow:auto}.viewer-head{position:sticky;top:0;z-index:2;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 18px;background:rgba(255,255,255,.96);border-bottom:1px solid var(--line)}.viewer-path{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.viewer-body{max-width:900px;margin:0 auto;padding:28px 34px 50px}.empty{display:grid;place-items:center;min-height:320px;color:var(--muted)}.markdown{line-height:1.75}.markdown h1,.markdown h2,.markdown h3{line-height:1.3;margin:1.5em 0 .65em}.markdown h1{font-size:26px;border-bottom:1px solid var(--line);padding-bottom:10px}.markdown h2{font-size:20px}.markdown h3{font-size:16px}.markdown p{margin:.8em 0}.markdown blockquote{margin:1em 0;padding:4px 14px;border-left:3px solid var(--accent);color:var(--muted);background:#f8fafb}.markdown code,.code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.markdown code{background:#f0f2f4;padding:2px 4px;border-radius:3px}.markdown pre,.raw{white-space:pre-wrap;word-break:break-word;background:#111827;color:#e5e7eb;padding:16px;border-radius:5px;overflow:auto}.markdown table{width:100%;border-collapse:collapse;margin:1em 0}.markdown th,.markdown td{padding:7px 9px;border-bottom:1px solid var(--line);text-align:left}.markdown th{font-weight:700;background:#f7f8f9}.toolbar{display:flex;gap:8px;align-items:center}.segmented{display:inline-flex;border:1px solid var(--line);background:var(--surface);border-radius:5px;padding:2px}.segmented button{border:0;background:transparent;padding:5px 10px;border-radius:3px;color:var(--muted)}.segmented button.active{background:var(--text);color:#fff}.trace-summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:18px}.metric{padding:12px;border:1px solid var(--line);border-radius:5px}.metric small{display:block;color:var(--muted);margin-bottom:4px}.metric strong{font-size:20px}.timeline{border-left:2px solid var(--line);margin-left:8px}.event{position:relative;padding:0 0 18px 22px}.event:before{content:"";position:absolute;left:-6px;top:4px;width:9px;height:9px;border-radius:50%;background:var(--accent);box-shadow:0 0 0 3px #fff}.event.error:before{background:var(--bad)}.event-head{display:flex;justify-content:space-between;gap:12px;font-weight:650}.event-meta{margin-top:3px;color:var(--muted);font-size:12px}.controls{display:flex;gap:8px;flex-wrap:wrap}.controls input,.controls select,.form input,.form select{border:1px solid var(--line);border-radius:5px;background:var(--surface);padding:7px 9px;color:var(--text)}.table-wrap{overflow:auto;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius)}table.data{width:100%;border-collapse:collapse;min-width:760px}table.data th,table.data td{padding:9px 11px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}table.data th{position:sticky;top:0;background:#f7f8f9;color:var(--muted);font-size:12px;font-weight:700}table.data tr:last-child td{border-bottom:0}.name{font-weight:650}.subtle{color:var(--muted);font-size:12px;margin-top:2px}.tag{display:inline-block;padding:2px 6px;border-radius:999px;font-size:11px;background:#eef1f3;color:var(--muted)}.tag.ok{background:#eaf7ee;color:var(--ok)}.tag.warn{background:#fff7df;color:var(--warn)}.provider-grid{display:grid;grid-template-columns:minmax(300px,420px) minmax(0,1fr);gap:16px}.section{padding:16px}.section h2{font-size:15px;margin:0 0 14px}.form{display:grid;gap:12px}.field{display:grid;gap:5px}.field label{font-size:12px;color:var(--muted)}.feedback{font-size:12px;color:var(--muted);min-height:18px}.cost{margin-top:16px}.cost-metrics{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:12px}.cost-note{font-size:12px;color:var(--muted);margin:0 0 12px}.hidden{display:none!important}
|
|
10
10
|
@media(max-width:850px){.layout{grid-template-columns:1fr}.nav{display:flex;border-right:0;border-bottom:1px solid var(--line);padding:8px;overflow:auto}.nav button{width:auto;white-space:nowrap;margin:0 4px 0 0}.main{padding:12px}.workspace{grid-template-columns:1fr;height:auto}.tree{max-height:280px;border-right:0;border-bottom:1px solid var(--line)}.viewer{min-height:520px}.provider-grid{grid-template-columns:1fr}.trace-summary,.cost-metrics{grid-template-columns:1fr}.topbar{padding:0 14px}}
|
|
11
|
+
.event-detail{white-space:pre-wrap;word-break:break-word;background:#f8fafc;border:1px solid var(--line);border-radius:4px;padding:8px;margin:6px 0 2px;font-size:12px;color:var(--text)}
|
|
11
12
|
</style>
|
|
12
13
|
</head>
|
|
13
14
|
<body>
|
|
@@ -21,19 +22,19 @@
|
|
|
21
22
|
</nav>
|
|
22
23
|
<main class="main">
|
|
23
24
|
<section id="view-project" class="view active">
|
|
24
|
-
<div class="page-head"><div><h1>项目文件</h1><p
|
|
25
|
+
<div class="page-head"><div><h1>项目文件</h1><p>查看研究文档与 Export 导出结果;内容只读。</p></div><button id="refresh-project" class="button">刷新</button></div>
|
|
25
26
|
<div class="panel workspace">
|
|
26
27
|
<aside id="file-tree" class="tree"><div class="empty">读取中...</div></aside>
|
|
27
28
|
<section class="viewer">
|
|
28
29
|
<div class="viewer-head"><div id="viewer-path" class="viewer-path">选择左侧文件</div><span id="viewer-kind" class="tag">只读</span></div>
|
|
29
|
-
<div id="viewer-body" class="viewer-body empty"
|
|
30
|
+
<div id="viewer-body" class="viewer-body empty">选择研究文档或 Export 结果查看</div>
|
|
30
31
|
</section>
|
|
31
32
|
</div>
|
|
32
33
|
</section>
|
|
33
34
|
|
|
34
35
|
<section id="view-ecosystem" class="view">
|
|
35
36
|
<div class="page-head"><div><h1>Skill / MCP 生态</h1><p>安装、启用和停用研究能力。配置变更后使用 <code>/reload</code>。</p></div><button id="refresh-ecosystem" class="button">刷新</button></div>
|
|
36
|
-
<div class="controls" style="margin-bottom:12px"><input id="eco-search" placeholder="搜索名称或描述"><select id="eco-kind"><option value="all">全部类型</option><option value="skill">Skill</option><option value="mcp">MCP</option><option value="external">外部工具</option></select><select id="eco-status"><option value="all">全部状态</option><option value="enabled">已启用</option><option value="available">未启用</option></select></div>
|
|
37
|
+
<div class="controls" style="margin-bottom:12px"><input id="eco-search" placeholder="搜索名称或描述"><select id="eco-kind"><option value="all">全部类型</option><option value="skill">Skill</option><option value="local">用户 Skill</option><option value="mcp">MCP</option><option value="external">外部工具</option></select><select id="eco-status"><option value="all">全部状态</option><option value="enabled">已启用</option><option value="available">未启用</option></select></div>
|
|
37
38
|
<div class="table-wrap"><table class="data"><thead><tr><th>名称</th><th>类型</th><th>来源</th><th>状态</th><th>操作</th></tr></thead><tbody id="ecosystem-body"><tr><td colspan="5">读取中...</td></tr></tbody></table></div>
|
|
38
39
|
</section>
|
|
39
40
|
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
</div>
|
|
50
51
|
</div>
|
|
51
52
|
<script>
|
|
52
|
-
const state={view:"project",files:[],
|
|
53
|
+
const state={view:"project",files:[],eco:[],models:[],active:{},usage:null,currency:"cny"};
|
|
53
54
|
const $=id=>document.getElementById(id);
|
|
54
55
|
const esc=value=>String(value??"").replace(/[&<>"']/g,ch=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[ch]));
|
|
55
56
|
async function json(url,options){const response=await fetch(url,options);const body=await response.json().catch(()=>({error:"请求失败"}));if(!response.ok)throw new Error(body.error||"请求失败");return body}
|
|
@@ -58,16 +59,15 @@ document.querySelectorAll(".nav button").forEach(button=>button.addEventListener
|
|
|
58
59
|
|
|
59
60
|
function inlineMarkdown(text){let value=esc(text);value=value.replace(/`([^`]+)`/g,"<code>$1</code>").replace(/\*\*([^*]+)\*\*/g,"<strong>$1</strong>").replace(/\*([^*]+)\*/g,"<em>$1</em>");value=value.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(_,label,target)=>/^https?:\/\//i.test(target)?`<a href="${esc(target)}" target="_blank" rel="noreferrer">${label}</a>`:label);return value}
|
|
60
61
|
function renderMarkdown(markdown){const lines=String(markdown).replace(/\r\n/g,"\n").split("\n"),out=[];let i=0,inCode=false,code=[];while(i<lines.length){const line=lines[i],trim=line.trim();if(trim.startsWith("```")){if(inCode){out.push(`<pre><code>${esc(code.join("\n"))}</code></pre>`);code=[];inCode=false}else inCode=true;i++;continue}if(inCode){code.push(line);i++;continue}const heading=trim.match(/^(#{1,3})\s+(.+)$/);if(heading){const level=heading[1].length;out.push(`<h${level}>${inlineMarkdown(heading[2])}</h${level}>`);i++;continue}if(trim.startsWith(">")){out.push(`<blockquote>${inlineMarkdown(trim.replace(/^>\s?/,""))}</blockquote>`);i++;continue}if(/^[-*+]\s+/.test(trim)){const items=[];while(i<lines.length&&/^[-*+]\s+/.test(lines[i].trim()))items.push(lines[i++].trim().replace(/^[-*+]\s+/,""));out.push(`<ul>${items.map(item=>`<li>${inlineMarkdown(item)}</li>`).join("")}</ul>`);continue}if(/^\d+\.\s+/.test(trim)){const items=[];while(i<lines.length&&/^\d+\.\s+/.test(lines[i].trim()))items.push(lines[i++].trim().replace(/^\d+\.\s+/,""));out.push(`<ol>${items.map(item=>`<li>${inlineMarkdown(item)}</li>`).join("")}</ol>`);continue}if(trim.startsWith("|")&&i+1<lines.length&&/^\|?[\s:|-]+\|/.test(lines[i+1].trim())){const cells=value=>value.replace(/^\||\|$/g,"").split("|").map(cell=>cell.trim());const head=cells(trim);i+=2;const rows=[];while(i<lines.length&&lines[i].trim().startsWith("|"))rows.push(cells(lines[i++]));out.push(`<table><thead><tr>${head.map(cell=>`<th>${inlineMarkdown(cell)}</th>`).join("")}</tr></thead><tbody>${rows.map(row=>`<tr>${row.map(cell=>`<td>${inlineMarkdown(cell)}</td>`).join("")}</tr>`).join("")}</tbody></table>`);continue}if(!trim){i++;continue}const para=[trim];i++;while(i<lines.length&&lines[i].trim()&&!/^(#{1,3})\s+/.test(lines[i].trim())&&!/^[-*+]\s+/.test(lines[i].trim())&&!/^\d+\.\s+/.test(lines[i].trim())&&!lines[i].trim().startsWith(">")&&!lines[i].trim().startsWith("```"))para.push(lines[i++].trim());out.push(`<p>${inlineMarkdown(para.join(" "))}</p>`)}if(inCode)out.push(`<pre><code>${esc(code.join("\n"))}</code></pre>`);return out.join("")}
|
|
61
|
-
function groupFiles(files){const priority=["notes/goal.md","notes/research-spec.md","notes/plan.md","notes/decisions.md","notes/HANDOFF.md"];const ordered=[...files].sort((a,b)=>{const ai=priority.indexOf(a),bi=priority.indexOf(b);if(ai>=0||bi>=0)return(ai<0?99:ai)-(bi<0?99:bi);return a.localeCompare(b,"zh-CN")});const groups={"核心文档":ordered.filter(path=>priority.includes(path)),"论文与报告":ordered.filter(path=>/^(paper|docs
|
|
62
|
-
function renderTree(){const tree=$("file-tree"),groups=groupFiles(state.files);let html="";for(const[name,files]of Object.entries(groups)){if(!files.length)continue;html+=`<div class="tree-section">${esc(name)}</div>${files.map(path=>`<button class="tree-item" data-file="${esc(path)}"><span>◇</span><span>${esc(path.split("/").at(-1))}</span></button>`).join("")}`}
|
|
62
|
+
function groupFiles(files){const priority=["notes/goal.md","notes/research-spec.md","notes/plan.md","notes/decisions.md","notes/HANDOFF.md"];const ordered=[...files].sort((a,b)=>{const ai=priority.indexOf(a),bi=priority.indexOf(b);if(ai>=0||bi>=0)return(ai<0?99:ai)-(bi<0?99:bi);return a.localeCompare(b,"zh-CN")});const groups={"核心文档":ordered.filter(path=>priority.includes(path)),"Export 导出结果":ordered.filter(path=>path.startsWith("outputs/")),"论文与报告":ordered.filter(path=>/^(paper|docs)\//.test(path)&&!priority.includes(path)),"研究记录":ordered.filter(path=>/^(notes|logs)\//.test(path)&&!priority.includes(path))};return groups}
|
|
63
|
+
function renderTree(){const tree=$("file-tree"),groups=groupFiles(state.files);let html="";for(const[name,files]of Object.entries(groups)){if(!files.length)continue;html+=`<div class="tree-section">${esc(name)}</div>${files.map(path=>`<button class="tree-item" data-file="${esc(path)}"><span>◇</span><span>${esc(path.split("/").at(-1))}</span></button>`).join("")}`}tree.innerHTML=html||'<div class="empty">暂无项目文件或 Export 结果</div>';tree.querySelectorAll("[data-file]").forEach(button=>button.addEventListener("click",()=>openFile(button.dataset.file,button)))}
|
|
63
64
|
function selectTree(button){document.querySelectorAll(".tree-item").forEach(item=>item.classList.toggle("active",item===button))}
|
|
64
65
|
async function openFile(path,button){selectTree(button);$("viewer-path").textContent=path;$("viewer-kind").textContent="读取中";$("viewer-body").className="viewer-body empty";$("viewer-body").textContent="读取中...";try{const file=await json(`/api/project-file?path=${encodeURIComponent(path)}`);$("viewer-kind").textContent=file.format;$("viewer-body").className="viewer-body";$("viewer-body").innerHTML=file.format==="markdown"?`<article class="markdown">${renderMarkdown(file.content)}</article>`:`<pre class="raw">${esc(file.format==="json"?JSON.stringify(JSON.parse(file.content),null,2):file.content)}</pre>`}catch(error){$("viewer-body").className="viewer-body empty";$("viewer-body").textContent=error.message}}
|
|
65
|
-
function
|
|
66
|
-
async function loadProject(){const[files,traces]=await Promise.all([json("/api/project-files"),json("/api/traces")]);state.files=files.files||[];state.traces=traces.traces||[];renderTree();const goal=document.querySelector('[data-file="notes/goal.md"]')||document.querySelector("[data-file]");if(goal)goal.click()}
|
|
66
|
+
async function loadProject(){const files=await json("/api/project-files");state.files=files.files||[];renderTree();const goal=document.querySelector('[data-file="notes/goal.md"]')||document.querySelector("[data-file]");if(goal)goal.click()}
|
|
67
67
|
|
|
68
|
-
async function loadEcosystem(){try{const[skills,mcps,enabled]=await Promise.all([json("/api/recommended-skills"),json("/api/recommended-mcps"),json("/api/enabled-capabilities")]);const skillSet=new Set((enabled.skills||[]).map(item=>item.id)),mcpSet=new Set((enabled.mcp||[]).map(item=>item.id));state.eco=[...(enabled.coreSkills||[]).map(item=>({...item,kind:"core",description:"PsyClaw 内置 Skill",sourceRef:"bundled",enabled:item.enabled,installed:true})),...(skills.items||[]).map(item=>({...item,kind:"skill",enabled:skillSet.has(item.id),installed:skillSet.has(item.id)})),...(mcps.items||[]).map(item=>({...item,kind:"mcp",enabled:mcpSet.has(item.id),installed:mcpSet.has(item.id)})),...(skills.externalTools||[]).map(item=>({...item,kind:"external",enabled:false,installed:false}))];renderEcosystem()}catch(error){$("ecosystem-body").innerHTML=`<tr><td colspan="5">${esc(error.message)}</td></tr>`}}
|
|
69
|
-
function renderEcosystem(){const query=$("eco-search").value.trim().toLowerCase(),kind=$("eco-kind").value,status=$("eco-status").value;const items=state.eco.filter(item=>(kind==="all"||item.kind===kind)&&(status==="all"||(status==="enabled"?item.enabled:!item.enabled))&&`${item.name} ${item.description||""}`.toLowerCase().includes(query));$("ecosystem-body").innerHTML=items.map(item=>`<tr><td><div class="name">${esc(item.name)}</div><div class="subtle">${esc(item.description||"")}</div></td><td>${esc(item.kind==="core"?"Core Skill":item.kind.toUpperCase())}</td><td>${/^https?:/.test(item.sourceRef||"")?`<a href="${esc(item.sourceRef)}" target="_blank" rel="noreferrer">来源</a>`:esc(item.sourceRef||"bundled")}</td><td><span class="tag ${item.enabled?"ok":""}">${item.enabled?"已启用":item.kind==="external"?"外部工具":"未启用"}</span></td><td>${item.kind==="external"?"—":item.enabled?`<button class="button small" onclick="toggleCapability('${item.kind}','${esc(item.id)}',false)">停用</button>`:`<button class="button small primary" onclick="installCapability('${item.kind}','${esc(item.id)}')">${item.kind==="core"?"启用":"安装"}</button>`}</td></tr>`).join("")||'<tr><td colspan="5">没有匹配项</td></tr>'}
|
|
70
|
-
async function toggleCapability(kind,id,enabled){await json("/api/recommendation-state",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({kind:kind==="core"?"core-skill":kind==="skill"?"skills":kind,id,enabled})});await loadEcosystem()}
|
|
68
|
+
async function loadEcosystem(){try{const[skills,mcps,enabled]=await Promise.all([json("/api/recommended-skills"),json("/api/recommended-mcps"),json("/api/enabled-capabilities")]);const skillSet=new Set((enabled.skills||[]).map(item=>item.id)),mcpSet=new Set((enabled.mcp||[]).map(item=>item.id));state.eco=[...(enabled.coreSkills||[]).map(item=>({...item,kind:"core",description:"PsyClaw 内置 Skill",sourceRef:"bundled",enabled:item.enabled,installed:true})),...(skills.items||[]).map(item=>({...item,kind:"skill",enabled:skillSet.has(item.id),installed:skillSet.has(item.id)})),...(enabled.localSkills||[]).map(item=>({...item,kind:"local",sourceRef:item.path||"用户目录",installed:true})),...(mcps.items||[]).map(item=>({...item,kind:"mcp",enabled:mcpSet.has(item.id),installed:mcpSet.has(item.id)})),...(skills.externalTools||[]).map(item=>({...item,kind:"external",enabled:false,installed:false}))];renderEcosystem()}catch(error){$("ecosystem-body").innerHTML=`<tr><td colspan="5">${esc(error.message)}</td></tr>`}}
|
|
69
|
+
function renderEcosystem(){const query=$("eco-search").value.trim().toLowerCase(),kind=$("eco-kind").value,status=$("eco-status").value;const items=state.eco.filter(item=>(kind==="all"||item.kind===kind)&&(status==="all"||(status==="enabled"?item.enabled:!item.enabled))&&`${item.name} ${item.description||""}`.toLowerCase().includes(query));$("ecosystem-body").innerHTML=items.map(item=>`<tr><td><div class="name">${esc(item.name)}</div><div class="subtle">${esc(item.description||"")}</div></td><td>${esc(item.kind==="core"?"Core Skill":item.kind==="local"?"用户 Skill":item.kind.toUpperCase())}</td><td>${/^https?:/.test(item.sourceRef||"")?`<a href="${esc(item.sourceRef)}" target="_blank" rel="noreferrer">来源</a>`:esc(item.sourceRef||"bundled")}</td><td><span class="tag ${item.enabled?"ok":""}">${item.enabled?"已启用":item.kind==="external"?"外部工具":"未启用"}</span></td><td>${item.kind==="external"?"—":item.enabled?`<button class="button small" onclick="toggleCapability('${item.kind}','${esc(item.id)}',false)">停用</button>`:item.kind==="local"?`<button class="button small primary" onclick="toggleCapability('local','${esc(item.id)}',true)">启用</button>`:`<button class="button small primary" onclick="installCapability('${item.kind}','${esc(item.id)}')">${item.kind==="core"?"启用":"安装"}</button>`}</td></tr>`).join("")||'<tr><td colspan="5">没有匹配项</td></tr>'}
|
|
70
|
+
async function toggleCapability(kind,id,enabled){await json("/api/recommendation-state",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({kind:kind==="core"?"core-skill":kind==="skill"?"skills":kind==="local"?"local-skill":kind,id,enabled})});await loadEcosystem()}
|
|
71
71
|
async function installCapability(kind,id){if(kind==="core"){await toggleCapability(kind,id,true);return}const scope=kind==="skill"?(confirm("安装到系统目录?\n确定:所有项目使用;取消:仅当前项目使用。")?"user":"project"):undefined;if(!confirm(`确认安装 ${id}?安装完成后需要 /reload。`))return;const result=await json("/api/install/execute",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({kind,id,scope,approved:true,actor:"researcher"})});alert(result.message||(result.ok?"安装任务已处理,请执行 /reload。":"安装未完成"));await loadEcosystem()}
|
|
72
72
|
[$("eco-search"),$("eco-kind"),$("eco-status")].forEach(control=>control.addEventListener("input",renderEcosystem));
|
|
73
73
|
|