psyclaw 0.28.0 → 0.28.1

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 (44) hide show
  1. package/README.md +4 -4
  2. package/agents/recommended/catalog.json +99 -0
  3. package/dist/apps/panel/index.html +5 -5
  4. package/dist/src/adapters/pi/extension.js +121 -9
  5. package/dist/src/adapters/pi/extension.js.map +1 -1
  6. package/dist/src/adapters/pi/rpc.d.ts +2 -0
  7. package/dist/src/adapters/pi/rpc.js +9 -2
  8. package/dist/src/adapters/pi/rpc.js.map +1 -1
  9. package/dist/src/agents/recommended-personas.d.ts +42 -0
  10. package/dist/src/agents/recommended-personas.js +135 -0
  11. package/dist/src/agents/recommended-personas.js.map +1 -0
  12. package/dist/src/cli.js +6 -3
  13. package/dist/src/cli.js.map +1 -1
  14. package/dist/src/creation/contracts.d.ts +3 -0
  15. package/dist/src/creation/service.js +17 -5
  16. package/dist/src/creation/service.js.map +1 -1
  17. package/dist/src/index.d.ts +1 -0
  18. package/dist/src/index.js +1 -0
  19. package/dist/src/index.js.map +1 -1
  20. package/dist/src/orchestration/bundled-personas.d.ts +16 -0
  21. package/dist/src/orchestration/bundled-personas.js +101 -0
  22. package/dist/src/orchestration/bundled-personas.js.map +1 -0
  23. package/dist/src/orchestration/effects.d.ts +8 -0
  24. package/dist/src/orchestration/effects.js +39 -0
  25. package/dist/src/orchestration/effects.js.map +1 -0
  26. package/dist/src/orchestration/personas.d.ts +2 -1
  27. package/dist/src/orchestration/personas.js +20 -7
  28. package/dist/src/orchestration/personas.js.map +1 -1
  29. package/dist/src/orchestration/pi-executor.d.ts +9 -5
  30. package/dist/src/orchestration/pi-executor.js +55 -22
  31. package/dist/src/orchestration/pi-executor.js.map +1 -1
  32. package/dist/src/orchestration/research-agents.d.ts +3 -7
  33. package/dist/src/orchestration/research-agents.js +5 -69
  34. package/dist/src/orchestration/research-agents.js.map +1 -1
  35. package/dist/src/panel/server.js +6 -0
  36. package/dist/src/panel/server.js.map +1 -1
  37. package/dist/src/style/cli-ui.d.ts +14 -0
  38. package/dist/src/style/cli-ui.js +45 -0
  39. package/dist/src/style/cli-ui.js.map +1 -1
  40. package/dist/src/tui/skill-manager.js +1 -1
  41. package/dist/src/tui/skill-manager.js.map +1 -1
  42. package/dist/src/updates/update.js +9 -3
  43. package/dist/src/updates/update.js.map +1 -1
  44. package/package.json +2 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@ PsyClaw 是面向社会科学研究的智能体工作台。它把研究项目、
4
4
 
5
5
  PsyClaw 自有代码使用 MIT 许可证;随 npm 包内置的 Academic Research Skills 位于 `vendor/ars`,保持上游署名并单独遵循 CC BY-NC 4.0,仅限非商业用途。
6
6
 
7
- 当前版本:`0.28.0`。正式命令、用户配置目录和后续发布统一使用 `psyclaw`。
7
+ 当前版本:`0.28.1`。正式命令、用户配置目录和后续发布统一使用 `psyclaw`。
8
8
 
9
9
  ## 发布流程
10
10
 
@@ -21,19 +21,19 @@ RELEASE_MESSAGE="release: describe the change" pnpm release:push
21
21
  需要 Node.js `>=22.19.0`。官方 npm 源:
22
22
 
23
23
  ```powershell
24
- npm install -g psyclaw@0.28.0
24
+ npm install -g psyclaw@0.28.1
25
25
  ```
26
26
 
27
27
  如果本机 npm 配置把 registry 误写成带有 `~/` 的地址,请显式指定官方源:
28
28
 
29
29
  ```bash
30
- npm install -g psyclaw@0.28.0 --registry=https://registry.npmjs.org/
30
+ npm install -g psyclaw@0.28.1 --registry=https://registry.npmjs.org/
31
31
  ```
32
32
 
33
33
  中国大陆网络较慢或无法访问官方源时:
34
34
 
35
35
  ```powershell
36
- npm install -g psyclaw@0.28.0 --registry=https://registry.npmmirror.com
36
+ npm install -g psyclaw@0.28.1 --registry=https://registry.npmmirror.com
37
37
  ```
38
38
 
39
39
  PsyClaw 的 npm 发布包直接携带 Windows x64/arm64 版 `ripgrep` 和 `fd`,Windows 启动时直接使用包内可执行文件,不再联网下载。其他平台缺少这两个工具时仍由锁定的 Pi 原生工具管理器处理:代理环境走 GitHub 官方 API 与 Release,国内 npm registry 环境统一走国内镜像。
@@ -0,0 +1,99 @@
1
+ {
2
+ "schemaVersion": "psyclaw/recommended-agents/v1",
3
+ "documentVersion": "0.28.1",
4
+ "syncedAt": "2026-09-06",
5
+ "sourceDocument": "bundled literature/review subagents",
6
+ "sourceRef": "bundled",
7
+ "repositoryHint": "对齐 Claude Code:Subagent = md 提示合订本。外仓可后续以 GitHub 源加入 installPrep;当前内置 8 个无需安装。",
8
+ "items": [
9
+ {
10
+ "id": "landscape-mapper",
11
+ "name": "研究版图分析者",
12
+ "kind": "agent",
13
+ "pack": "literature",
14
+ "role": "researcher",
15
+ "stage": "文献多智能体",
16
+ "description": "梳理研究主题、年代分布、代表性论文和研究方向。",
17
+ "sourceRef": "bundled",
18
+ "bundled": true
19
+ },
20
+ {
21
+ "id": "methods-scout",
22
+ "name": "方法学分析者",
23
+ "kind": "agent",
24
+ "pack": "literature",
25
+ "role": "analyst",
26
+ "stage": "文献多智能体",
27
+ "description": "识别研究设计、测量与分析线索,区分元数据可判断与不可判断之处。",
28
+ "sourceRef": "bundled",
29
+ "bundled": true
30
+ },
31
+ {
32
+ "id": "evidence-critic",
33
+ "name": "证据批判者",
34
+ "kind": "agent",
35
+ "pack": "literature",
36
+ "role": "critic",
37
+ "stage": "文献多智能体",
38
+ "description": "检查证据强弱、偏倚、冲突与因果边界。",
39
+ "sourceRef": "bundled",
40
+ "bundled": true
41
+ },
42
+ {
43
+ "id": "gap-finder",
44
+ "name": "研究缺口分析者",
45
+ "kind": "agent",
46
+ "pack": "literature",
47
+ "role": "verifier",
48
+ "stage": "文献多智能体",
49
+ "description": "寻找尚未充分覆盖的问题、对象、情境和方法。",
50
+ "sourceRef": "bundled",
51
+ "bundled": true
52
+ },
53
+ {
54
+ "id": "theory-reviewer",
55
+ "name": "理论与贡献审稿人",
56
+ "kind": "agent",
57
+ "pack": "review",
58
+ "role": "critic",
59
+ "stage": "同行评审多智能体",
60
+ "description": "检查问题重要性、理论定位、创新性与主张—证据一致性。",
61
+ "sourceRef": "bundled",
62
+ "bundled": true
63
+ },
64
+ {
65
+ "id": "methods-reviewer",
66
+ "name": "研究设计审稿人",
67
+ "kind": "agent",
68
+ "pack": "review",
69
+ "role": "analyst",
70
+ "stage": "同行评审多智能体",
71
+ "description": "检查抽样、设计、测量、效度与因果推断边界。",
72
+ "sourceRef": "bundled",
73
+ "bundled": true
74
+ },
75
+ {
76
+ "id": "statistics-reviewer",
77
+ "name": "统计与可复现性审稿人",
78
+ "kind": "agent",
79
+ "pack": "review",
80
+ "role": "verifier",
81
+ "stage": "同行评审多智能体",
82
+ "description": "检查分析策略、效应量、置信区间与可复现信息(不重算统计)。",
83
+ "sourceRef": "bundled",
84
+ "bundled": true
85
+ },
86
+ {
87
+ "id": "ethics-editorial-reviewer",
88
+ "name": "伦理与编辑审稿人",
89
+ "kind": "agent",
90
+ "pack": "review",
91
+ "role": "writer",
92
+ "stage": "同行评审多智能体",
93
+ "description": "检查伦理、透明报告、引用表达与可读性。",
94
+ "sourceRef": "bundled",
95
+ "bundled": true
96
+ }
97
+ ],
98
+ "installPrep": []
99
+ }
@@ -17,7 +17,7 @@
17
17
  <div class="layout">
18
18
  <nav class="nav" aria-label="Panel 导航">
19
19
  <button class="active" data-view="project">项目文件</button>
20
- <button data-view="ecosystem">Skill / Plugin / MCP</button>
20
+ <button data-view="ecosystem">Skill / Plugin / MCP / Agent</button>
21
21
  <button data-view="provider">Provider 与成本</button>
22
22
  </nav>
23
23
  <main class="main">
@@ -33,8 +33,8 @@
33
33
  </section>
34
34
 
35
35
  <section id="view-ecosystem" class="view">
36
- <div class="page-head"><div><h1>Skill / Plugin / MCP 生态</h1><p>在同一页面管理研究 Skill、Pi Plugin 与 MCP。配置变更后使用 <code>/reload</code>。</p></div><button id="refresh-ecosystem" class="button">刷新</button></div>
37
- <div class="controls" style="margin-bottom:12px"><input id="eco-search" placeholder="搜索名称或描述"><div class="segmented" aria-label="能力类型"><button class="active" data-eco-kind="all">全部</button><button data-eco-kind="skill">Skill</button><button data-eco-kind="plugin">Plugin</button><button data-eco-kind="mcp">MCP</button></div><select id="eco-kind" aria-label="更多类型"><option value="all">全部来源</option><option value="local">用户 Skill</option><option value="external">外部工具</option></select><select id="eco-status"><option value="all">全部状态</option><option value="enabled">已启用</option><option value="available">未启用</option></select></div>
36
+ <div class="page-head"><div><h1>Skill / Plugin / MCP / Agent 生态</h1><p>在同一页面管理研究 Skill、Pi Plugin、MCPSubagent(对齐 Claude Code)。配置变更后使用 <code>/reload</code>。</p></div><button id="refresh-ecosystem" class="button">刷新</button></div>
37
+ <div class="controls" style="margin-bottom:12px"><input id="eco-search" placeholder="搜索名称或描述"><div class="segmented" aria-label="能力类型"><button class="active" data-eco-kind="all">全部</button><button data-eco-kind="skill">Skill</button><button data-eco-kind="plugin">Plugin</button><button data-eco-kind="mcp">MCP</button><button data-eco-kind="agent">Agent</button></div><select id="eco-kind" aria-label="更多类型"><option value="all">全部来源</option><option value="local">用户 Skill</option><option value="external">外部工具</option></select><select id="eco-status"><option value="all">全部状态</option><option value="enabled">已启用</option><option value="available">未启用</option></select></div>
38
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>
39
39
  </section>
40
40
 
@@ -65,8 +65,8 @@ function selectTree(button){document.querySelectorAll(".tree-item").forEach(item
65
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}}
66
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,plugins,mcps,enabled]=await Promise.all([json("/api/recommended-skills"),json("/api/recommended-plugins"),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:item.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)})),...(plugins.items||[]).map(item=>({...item,kind:"plugin",enabled:Boolean(item.enabled),installed:Boolean(item.installed)})),...(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(),sourceKind=$("eco-kind").value,status=$("eco-status").value,primaryKind=state.ecoKind;const primaryMatches=item=>primaryKind==="all"||(primaryKind==="skill"?(item.kind==="skill"||item.kind==="core"||item.kind==="local"):item.kind===primaryKind);const items=state.eco.filter(item=>primaryMatches(item)&&(sourceKind==="all"||item.kind===sourceKind)&&(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||"")}${item.kind==="plugin"&&item.scope?` · ${item.scope==="project"?"当前项目":"所有项目"}${item.filtered?" · 已限定资源范围":""}`:""}</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.configured?"warn":""}">${item.locked?"可用":item.enabled?(item.kind==="plugin"?"已安装":"已启用"):item.kind==="plugin"&&item.configured?"配置存在,文件缺失":item.kind==="external"?"可安装外部工具":"未启用"}</span></td><td>${item.locked?"—":item.kind==="plugin"&&item.local?"—":item.kind==="plugin"?`<button class="button small ${item.installed?"":"primary"}" onclick="installCapability('plugin','${esc(item.id)}')">${item.installed?"重新安装":"安装"}</button>`:item.kind==="external"?`<button class="button small primary" onclick="installCapability('external','${esc(item.id)}')">安装</button>`: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>'}
68
+ async function loadEcosystem(){try{const[skills,plugins,mcps,agents,enabled]=await Promise.all([json("/api/recommended-skills"),json("/api/recommended-plugins"),json("/api/recommended-mcps"),json("/api/recommended-agents"),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:item.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)})),...(plugins.items||[]).map(item=>({...item,kind:"plugin",enabled:Boolean(item.enabled),installed:Boolean(item.installed)})),...(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)})),...(agents.items||[]).map(item=>({...item,kind:"agent",enabled:Boolean(item.enabled),installed:Boolean(item.installed),locked:Boolean(item.locked)})),...(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(),sourceKind=$("eco-kind").value,status=$("eco-status").value,primaryKind=state.ecoKind;const primaryMatches=item=>primaryKind==="all"||(primaryKind==="skill"?(item.kind==="skill"||item.kind==="core"||item.kind==="local"):item.kind===primaryKind);const items=state.eco.filter(item=>primaryMatches(item)&&(sourceKind==="all"||item.kind===sourceKind)&&(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||"")}${item.kind==="plugin"&&item.scope?` · ${item.scope==="project"?"当前项目":"所有项目"}${item.filtered?" · 已限定资源范围":""}`:""}${item.kind==="agent"&&item.slashCommand?` · ${esc(item.slashCommand)}`:""}</div></td><td>${esc(item.kind==="core"?"Core Skill":item.kind==="local"?"用户 Skill":item.kind==="agent"?"Agent":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.configured?"warn":""}">${item.locked?"内置可用":item.enabled?(item.kind==="plugin"?"已安装":"已启用"):item.kind==="plugin"&&item.configured?"配置存在,文件缺失":item.kind==="external"?"可安装外部工具":"未启用"}</span></td><td>${item.locked||item.kind==="agent"?"—":item.kind==="plugin"&&item.local?"—":item.kind==="plugin"?`<button class="button small ${item.installed?"":"primary"}" onclick="installCapability('plugin','${esc(item.id)}')">${item.installed?"重新安装":"安装"}</button>`:item.kind==="external"?`<button class="button small primary" onclick="installCapability('external','${esc(item.id)}')">安装</button>`: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
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"||kind==="plugin"?(confirm("安装到系统目录?\n确定:所有项目使用;取消:仅当前项目使用。")?"user":"project"):undefined;if(!confirm(`确认安装 ${id}?${kind==="external"?"模型将按官方来源安装并验证。":"安装完成后需要 /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?"安装任务已处理。":"安装未完成"));await loadEcosystem()}
72
72
  [$("eco-search"),$("eco-kind"),$("eco-status")].forEach(control=>control.addEventListener("input",renderEcosystem));
@@ -2,7 +2,9 @@ import { DefaultPackageManager, getAgentDir, SettingsManager } from "@earendil-w
2
2
  import { Type } from "typebox";
3
3
  import { appendApproval, approvalInputDigest, asProject, assertResearchDecision, bootstrapProject, projectPaths, readApprovals, resolveResearchDecision, runOfflineBrief, runInstitutionalFulltext, runLiteratureReview, runExpertReview, runAnalysisDelegation, runWritingReview, runMetaAnalysis, createStageRunner, exportAcademicDocument, recordCitationUse, runParallelPeerReview, writeHandoff } from "../../index.js";
4
4
  import { runPlanWithPi } from "../../orchestration/pi-executor.js";
5
- import { customPersonaPlan, loadCustomPersonas, parseAgentsRequest } from "../../orchestration/personas.js";
5
+ import { customPersonaPlan, parseAgentsRequest } from "../../orchestration/personas.js";
6
+ import { elevatedEffects, formatEffects, hasElevatedEffects, normalizeEffects, toolsForEffects } from "../../orchestration/effects.js";
7
+ import { agentManagerRows, loadSelectablePersonas } from "../../agents/recommended-personas.js";
6
8
  import { atomicWriteFile } from "../../project/jsonl.js";
7
9
  import { applyCreation, previewCreation, runArsMultiAgentBridge } from "../../index.js";
8
10
  import { loadUserRules, userRulesPrompt } from "../../rules/user-rules.js";
@@ -643,6 +645,46 @@ async function openPluginManager(ctx, rows) {
643
645
  missingMessage: "按 Enter 安装到当前项目。",
644
646
  })));
645
647
  }
648
+ function agentManagerItems(rows) {
649
+ return rows.map((row) => ({
650
+ id: row.id,
651
+ name: row.name,
652
+ description: row.description,
653
+ status: row.status,
654
+ sourceRef: row.sourceRef,
655
+ details: [
656
+ `类别:${row.stage}`,
657
+ `角色:${row.role}`,
658
+ `来源:${row.source === "bundled" ? "内置 Subagent(类似 Claude Explore/Plan)" : "项目 .psyclaw/agents/custom"}`,
659
+ `运行:/agents --agent ${row.id} <只读研究任务>`,
660
+ ],
661
+ }));
662
+ }
663
+ async function openAgentManager(ctx, rows) {
664
+ return ctx.ui.custom((tui, theme, keybindings, done) => (new SkillManagerComponent(agentManagerItems(rows), tui, theme, keybindings, done, {
665
+ title: "Subagent(对齐 Claude Code)",
666
+ itemLabel: "Subagent",
667
+ footer: "↑/↓ 移动 · Enter 查看用法 · Esc 关闭 · 运行: /agents --agent <id> <任务>",
668
+ enterAction: "details",
669
+ toggleEnabled: false,
670
+ enabledText: "已可选用",
671
+ lockedMessage: "内置 Subagent 始终可用。运行:/agents --agent <id> <只读研究任务>",
672
+ })));
673
+ }
674
+ async function showAgentManager(ctx, statusOnly = false) {
675
+ const rows = await agentManagerRows(ctx.cwd);
676
+ if (!ctx.hasUI || typeof ctx.ui.custom !== "function" || statusOnly) {
677
+ ctx.ui.notify(rows.map((row) => `${row.status === "core" ? "◆" : "●"} ${row.id} — ${row.name}(${row.stage})`).join("\n")
678
+ || "暂无 Subagent。可用 /create-subagent 创建(类似 Claude Code)。", "info");
679
+ return;
680
+ }
681
+ while (true) {
682
+ const action = await openAgentManager(ctx, await agentManagerRows(ctx.cwd));
683
+ if (action.type === "close")
684
+ return;
685
+ // Enter/Space only show inline notices; keep the manager open.
686
+ }
687
+ }
646
688
  async function installRecommendedPlugin(ctx, row) {
647
689
  if (!row.installable)
648
690
  throw new Error(row.reason ?? `${row.name} 尚未提供可安装的 Plugin package`);
@@ -1848,7 +1890,7 @@ export default function psyclawExtension(pi) {
1848
1890
  pi.registerTool({
1849
1891
  name: "psyclaw_create",
1850
1892
  label: "Create project capability",
1851
- description: "Preview and, after an explicit UI confirmation, create one PsyClaw-native project Skill, declarative analysis Hook, additive Rule, or read-only Subagent persona.",
1893
+ description: "Preview and, after an explicit UI confirmation, create one PsyClaw-native project Skill, declarative analysis Hook, additive Rule, or Subagent (Claude-style). Elevated subagent effects require confirmation at create and again at each /agents run.",
1852
1894
  parameters: Type.Object({
1853
1895
  kind: Type.Union([Type.Literal("skill"), Type.Literal("hook"), Type.Literal("rule"), Type.Literal("subagent")]),
1854
1896
  id: Type.String({ minLength: 1, maxLength: 64 }),
@@ -1859,6 +1901,12 @@ export default function psyclawExtension(pi) {
1859
1901
  pattern: Type.Optional(Type.String({ maxLength: 500 })),
1860
1902
  pathPrefix: Type.Optional(Type.String({ maxLength: 200 })),
1861
1903
  role: Type.Optional(Type.Union([Type.Literal("planner"), Type.Literal("researcher"), Type.Literal("analyst"), Type.Literal("critic"), Type.Literal("writer"), Type.Literal("verifier")])),
1904
+ allowedEffects: Type.Optional(Type.Array(Type.Union([
1905
+ Type.Literal("read"),
1906
+ Type.Literal("write"),
1907
+ Type.Literal("network"),
1908
+ Type.Literal("destructive"),
1909
+ ]), { maxItems: 4 })),
1862
1910
  }),
1863
1911
  executionMode: "sequential",
1864
1912
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -1870,7 +1918,15 @@ export default function psyclawExtension(pi) {
1870
1918
  if (!ctx.hasUI || typeof ctx.ui?.confirm !== "function") {
1871
1919
  return { content: [{ type: "text", text: JSON.stringify({ ...preview, status: "preview-only", reason: "interactive confirmation unavailable" }, null, 2) }], details: preview };
1872
1920
  }
1873
- const approved = await ctx.ui.confirm(`创建 ${params.kind} “${preview.id}”?`, `目标:${preview.path}\nSHA-256:${preview.sha256}\n\n${preview.contents}`);
1921
+ const effects = request.kind === "subagent" ? normalizeEffects(request.allowedEffects) : ["read"];
1922
+ const elevated = request.kind === "subagent" && hasElevatedEffects(effects);
1923
+ const title = elevated
1924
+ ? `创建 Subagent “${preview.id}”(含提升权限)?`
1925
+ : `创建 ${params.kind} “${preview.id}”?`;
1926
+ const body = elevated
1927
+ ? `目标:${preview.path}\n声明 effects:${formatEffects(effects)}\n将开放工具:${toolsForEffects(effects).join(", ")}\nSHA-256:${preview.sha256}\n\n每次 /agents 运行仍会再次确认。凭据读取与绕过门禁仍被禁止。\n\n${preview.contents}`
1928
+ : `目标:${preview.path}\nSHA-256:${preview.sha256}\n\n${preview.contents}`;
1929
+ const approved = await ctx.ui.confirm(title, body);
1874
1930
  if (!approved)
1875
1931
  return { content: [{ type: "text", text: "Creation cancelled; no file was written." }], details: { status: "cancelled", preview } };
1876
1932
  const result = await applyCreation(ctx.cwd, request, preview.sha256, true);
@@ -2121,12 +2177,22 @@ export default function psyclawExtension(pi) {
2121
2177
  // runner behind PSYCLAW_DEVELOPER_COMMANDS in published builds.
2122
2178
  if (!legacyTestApi)
2123
2179
  pi.registerCommand("agents", {
2124
- description: "运行经批准的只读研究 Agent",
2180
+ description: "浏览或运行 Subagent(对齐 Claude Code)",
2125
2181
  handler: async (args, ctx) => {
2182
+ const trimmed = args.trim();
2183
+ if (!trimmed || trimmed === "status" || trimmed === "list") {
2184
+ try {
2185
+ await showAgentManager(ctx, trimmed === "status" || trimmed === "list");
2186
+ }
2187
+ catch (error) {
2188
+ await notifyError(ctx, error);
2189
+ }
2190
+ return;
2191
+ }
2126
2192
  const requested = parseAgentsRequest(args);
2127
2193
  const objective = requested.objective;
2128
2194
  if (!objective) {
2129
- ctx.ui.notify("Usage: /agents [--agent <id>|--agents <id,...>] <bounded read-only research task>", "info");
2195
+ ctx.ui.notify("Usage: /agents [--agent <id>|--agents <id,...>] <task>\n无任务时直接运行 /agents 打开 Subagent 管理页。", "info");
2130
2196
  return;
2131
2197
  }
2132
2198
  if (objective.length > 4_000) {
@@ -2151,13 +2217,60 @@ export default function psyclawExtension(pi) {
2151
2217
  const runId = `pi_agent_${Date.now()}`;
2152
2218
  let plan = researchTaskPlan(runId, objective);
2153
2219
  if (requested.ids.length > 0) {
2154
- const available = await loadCustomPersonas(ctx.cwd);
2220
+ const available = await loadSelectablePersonas(ctx.cwd);
2155
2221
  const byId = new Map(available.map((persona) => [persona.id, persona]));
2156
2222
  const selected = requested.ids.map((id) => byId.get(id));
2157
2223
  const missing = requested.ids.filter((_id, index) => !selected[index]);
2158
2224
  if (missing.length > 0)
2159
- throw new Error(`Unknown or invalid custom subagent: ${missing.join(", ")}`);
2160
- plan = customPersonaPlan(runId, objective, selected.filter((persona) => persona !== undefined));
2225
+ throw new Error(`Unknown or invalid subagent: ${missing.join(", ")}. Run /agents to list bundled and custom subagents.`);
2226
+ const personas = selected.filter((persona) => persona !== undefined);
2227
+ const unionEffects = normalizeEffects(personas.flatMap((persona) => persona.allowedEffects));
2228
+ const elevated = elevatedEffects(unionEffects);
2229
+ if (elevated.length > 0) {
2230
+ if (!ctx.hasUI || typeof ctx.ui.confirm !== "function") {
2231
+ throw new Error(`Subagent effects [${formatEffects(elevated)}] require interactive confirmation`);
2232
+ }
2233
+ const approved = await ctx.ui.confirm("批准 Subagent 提升权限?", `将启用:${formatEffects(elevated)}\n对应工具:${toolsForEffects(unionEffects).join(", ")}\n任务:${objective.slice(0, 400)}\n\n凭据读取、绕过门禁与外部发布仍被禁止。`);
2234
+ if (!approved) {
2235
+ ctx.ui.notify("已取消:未批准提升权限,未启动 Subagent。", "warning");
2236
+ return;
2237
+ }
2238
+ }
2239
+ plan = customPersonaPlan(runId, objective, personas);
2240
+ await mkdir(join(ctx.cwd, ".psyclaw", "plans"), { recursive: true });
2241
+ await atomicWriteFile(join(ctx.cwd, ".psyclaw", "plans", `${runId}.json`), `${JSON.stringify(plan, null, 2)}\n`);
2242
+ const eventLog = new RunEventLog(ctx.cwd, runId);
2243
+ const result = await runPlanWithPi(plan, {
2244
+ cwd: ctx.cwd,
2245
+ agentDir: join(ctx.cwd, ".psyclaw", "pi-agent"),
2246
+ ...(ctx.model?.provider === undefined ? {} : { provider: ctx.model.provider }),
2247
+ ...(ctx.model?.id === undefined ? {} : { model: ctx.model.id }),
2248
+ env: providerEnvironment(ctx.model?.provider),
2249
+ ...(elevated.length === 0 ? { tools: [] } : {}),
2250
+ allowWrites: unionEffects.includes("write"),
2251
+ allowNetwork: unionEffects.includes("network"),
2252
+ allowDestructive: unionEffects.includes("destructive"),
2253
+ root: ctx.cwd,
2254
+ pauseRequested: async () => {
2255
+ try {
2256
+ await import("node:fs/promises").then(({ access }) => access(join(ctx.cwd, ".psyclaw", "runs", `${runId}.pause`)));
2257
+ return true;
2258
+ }
2259
+ catch {
2260
+ return false;
2261
+ }
2262
+ },
2263
+ onEvent: async (event) => { await eventLog.append(event); },
2264
+ });
2265
+ pi.appendEntry("psyclaw:agent-run", {
2266
+ runId,
2267
+ status: result.status,
2268
+ diagnostics: result.diagnostics,
2269
+ effects: unionEffects,
2270
+ recordedAt: new Date().toISOString(),
2271
+ });
2272
+ ctx.ui.notify(`Agent run ${result.status}: ${result.diagnostics.join("; ") || "verified"}`, result.status === "completed" ? "info" : "warning");
2273
+ return;
2161
2274
  }
2162
2275
  await mkdir(join(ctx.cwd, ".psyclaw", "plans"), { recursive: true });
2163
2276
  await atomicWriteFile(join(ctx.cwd, ".psyclaw", "plans", `${runId}.json`), `${JSON.stringify(plan, null, 2)}\n`);
@@ -2168,7 +2281,6 @@ export default function psyclawExtension(pi) {
2168
2281
  ...(ctx.model?.provider === undefined ? {} : { provider: ctx.model.provider }),
2169
2282
  ...(ctx.model?.id === undefined ? {} : { model: ctx.model.id }),
2170
2283
  env: providerEnvironment(ctx.model?.provider),
2171
- ...(requested.ids.length > 0 ? { tools: [] } : {}),
2172
2284
  root: ctx.cwd,
2173
2285
  pauseRequested: async () => {
2174
2286
  try {