psyclaw 0.29.14 → 0.29.16

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 (36) hide show
  1. package/README.md +14 -14
  2. package/dist/apps/panel/index.html +381 -141
  3. package/dist/src/adapters/pi/extension.js +145 -518
  4. package/dist/src/adapters/pi/extension.js.map +1 -1
  5. package/dist/src/agents/recommended-personas.d.ts +1 -1
  6. package/dist/src/agents/recommended-personas.js +2 -2
  7. package/dist/src/agents/recommended-personas.js.map +1 -1
  8. package/dist/src/analysis/plan.js +2 -2
  9. package/dist/src/analysis/plan.js.map +1 -1
  10. package/dist/src/analysis/stats-router.js +2 -2
  11. package/dist/src/analysis/stats-router.js.map +1 -1
  12. package/dist/src/branding.js +3 -3
  13. package/dist/src/branding.js.map +1 -1
  14. package/dist/src/orchestration/bundled-personas.d.ts +1 -1
  15. package/dist/src/orchestration/bundled-personas.js +1 -1
  16. package/dist/src/orchestration/bundled-personas.js.map +1 -1
  17. package/dist/src/orchestration/personas.js +2 -5
  18. package/dist/src/orchestration/personas.js.map +1 -1
  19. package/dist/src/panel/extension.js +3 -7
  20. package/dist/src/panel/extension.js.map +1 -1
  21. package/dist/src/panel/server.js +4 -4
  22. package/dist/src/panel/server.js.map +1 -1
  23. package/dist/src/session/help.d.ts +1 -1
  24. package/dist/src/session/help.js +26 -27
  25. package/dist/src/session/help.js.map +1 -1
  26. package/dist/src/session/modes.js +3 -3
  27. package/dist/src/session/modes.js.map +1 -1
  28. package/dist/src/skills/recommended.js +1 -1
  29. package/dist/src/skills/recommended.js.map +1 -1
  30. package/dist/src/style/cli-ui.js +14 -13
  31. package/dist/src/style/cli-ui.js.map +1 -1
  32. package/dist/src/verify/checklist.d.ts +5 -4
  33. package/dist/src/verify/checklist.js +7 -8
  34. package/dist/src/verify/checklist.js.map +1 -1
  35. package/package.json +1 -1
  36. package/skills/recommended/catalog.json +36 -3
@@ -1,10 +1,9 @@
1
1
  import { DefaultPackageManager, getAgentDir, SettingsManager } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "typebox";
3
- import { appendApproval, approvalInputDigest, asProject, assertResearchDecision, bootstrapProject, projectPaths, resolveResearchDecision, runInstitutionalFulltext, runLiteratureReview, runExpertReview, runAnalysisDelegation, runWritingReview, runMetaAnalysis, createStageRunner, exportAcademicDocument, recordCitationUse, runParallelPeerReview, writeHandoff } from "../../index.js";
3
+ import { appendApproval, approvalInputDigest, assertResearchDecision, bootstrapProject, resolveResearchDecision, runInstitutionalFulltext, runLiteratureReview, runExpertReview, runAnalysisDelegation, runWritingReview, runMetaAnalysis, createStageRunner, exportAcademicDocument, recordCitationUse, runParallelPeerReview } from "../../index.js";
4
4
  import { runPlanWithPi } from "../../orchestration/pi-executor.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";
5
+ import { formatEffects, hasElevatedEffects, normalizeEffects, toolsForEffects } from "../../orchestration/effects.js";
6
+ import { agentManagerRows } from "../../agents/recommended-personas.js";
8
7
  import { atomicWriteFile } from "../../project/jsonl.js";
9
8
  import { applyCreation, previewCreation, runArsMultiAgentBridge } from "../../index.js";
10
9
  import { loadUserRules, userRulesPrompt } from "../../rules/user-rules.js";
@@ -14,68 +13,38 @@ import { join } from "node:path";
14
13
  import { lstat, mkdir, readFile } from "node:fs/promises";
15
14
  import { PROVIDER_PRESETS, providerCredentialSource, saveProviderConfig } from "../../setup.js";
16
15
  import { dirname } from "node:path";
17
- import { fileURLToPath, pathToFileURL } from "node:url";
16
+ import { fileURLToPath } from "node:url";
18
17
  import { coreSkillNames, enabledRecommendedSkillPaths, normalizeRecommendedSkillId, readRecommendationState, readRecommendedCatalog, recommendedSkillTarget, saveRecommendationState, validateModelInstalledRecommendedSkill, } from "../../skills/recommended.js";
19
18
  import { SkillManagerComponent } from "../../tui/skill-manager.js";
20
19
  import { WakeOptionsComponent } from "../../tui/wake-options.js";
21
20
  import { applyWakeVerifySync, buildWakePrompt, createWakeVerifyChecklist, formatWakeResult, labelsFor, waitForPanelWakeAnswer, } from "../../wake-options/runtime.js";
22
21
  import { panelHub } from "../../panel/hub.js";
23
22
  import { appendChoiceRecord } from "../../research/choice.js";
24
- import { enabledLocalSkillPaths, enabledLocalPromptPaths, readUserSkillState, scanLocalSkills, setLocalSkillEnabled, setLocalSkillsEnabled, skillNamesInPaths, installLocalSkill, userSkillId, } from "../../skills/user-skills.js";
23
+ import { enabledLocalSkillPaths, enabledLocalPromptPaths, readUserSkillState, scanLocalSkills, setLocalSkillEnabled, setLocalSkillsEnabled, skillNamesInPaths, userSkillId, } from "../../skills/user-skills.js";
25
24
  import { RuntimeMcpRegistry, setUserMcpConfigEnabled, } from "../../integrations/mcp-runtime.js";
26
25
  import { SecretInputComponent, ProviderPickerComponent, } from "../../tui/provider-picker.js";
27
- import { ARS_REPOSITORY_URL, ARS_UPSTREAM_COMMIT, ARS_UPSTREAM_REF, PSYCLAW_ARS_PROFILE_VERSION, detectNatureArsFillers, formatNatureArsFillerStatus, isArsPiActive, isArsPiTurn, psyclawArsPatch, setArsPiSessionActive, } from "../../ars/profile.js";
26
+ import { isArsPiActive, isArsPiTurn, psyclawArsPatch, setArsPiSessionActive, } from "../../ars/profile.js";
28
27
  import { buildArsDoctorReport, ensurePdfEngineForExport } from "../../ars/doctor.js";
29
28
  import { formatAcademicSoftRouteInvocation, rankAcademicSoftRoutes, resolveAcademicSoftRoute, } from "../../ars/academic-router.js";
30
- import { advanceAnalysisPlan, createAnalysisPlan, formatAnalysisPlanStatus, listAnalysisPlans, readActiveAnalysisPlan, syncHandoffFromAnalysisPlan, writeAnalysisPlan, } from "../../analysis/plan.js";
29
+ import { advanceAnalysisPlan, createAnalysisPlan, formatAnalysisPlanStatus, readActiveAnalysisPlan, syncHandoffFromAnalysisPlan, writeAnalysisPlan, } from "../../analysis/plan.js";
31
30
  import { formatAnalysisPlanTakeover, resolveStatsIntent, } from "../../analysis/stats-router.js";
32
31
  import { arsRoot } from "../../ars/pi-panel-executor.js";
33
32
  import { ArsModeEditor, isArsModeEditorText } from "../../ars/mode-editor.js";
34
33
  import { MODE_STATUS, nextSessionMode, parseSessionMode, sessionModePrompt, } from "../../session/modes.js";
35
34
  import { continuouslyWorkWarningText, isContinuouslyWorkEnabled, } from "../../session/continuously-work.js";
36
- import { assertHumanVerifyGate, formatVerifyChecklist, isNaturalPlanConfirm, loadVerifyChecklist, markVerifyItem, skipUnverifiedItems } from "../../verify/checklist.js";
35
+ import { assertHumanVerifyGate, formatVerifyChecklist, isNaturalPlanConfirm, ensureDefaultVerifyChecklist, loadVerifyChecklist } from "../../verify/checklist.js";
37
36
  import { formatSessionHelp, formatSessionHelpBrief } from "../../session/help.js";
38
37
  import { openResearchWorkbench } from "../../panel/workbench.js";
39
- const PARADIGMS = new Set([
40
- "survey-observational",
41
- "qualitative-thematic",
42
- "experimental",
43
- "quasi-experimental",
44
- "longitudinal-panel",
45
- "meta-analysis",
46
- "ethnographic",
47
- "historical-documentary",
48
- "policy-legal",
49
- "mixed-methods",
50
- ]);
38
+ /** Codex-style slash surface: bare command, or command + trailing free text. No subcommand trees. */
51
39
  function parseInitArgs(args) {
52
- const trimmed = args.trim();
53
- if (!trimmed)
40
+ const goal = args.trim();
41
+ if (!goal)
54
42
  return {};
55
- const match = trimmed.match(/^--paradigm(?:=|\s+)(\S+)(?:\s+([\s\S]*))?$/i);
56
- if (trimmed.startsWith("--")) {
57
- const paradigm = match?.[1];
58
- const goal = match?.[2]?.trim();
59
- if (!match || !paradigm) {
60
- throw new Error("Usage: /init [--paradigm survey-observational] [optional goal]");
61
- }
62
- if (!PARADIGMS.has(paradigm))
63
- throw new Error(`Unsupported paradigm: ${paradigm}`);
64
- return { paradigm, ...(goal ? { goal } : {}) };
65
- }
66
- return { paradigm: "survey-observational", goal: trimmed };
43
+ return { paradigm: "survey-observational", goal };
67
44
  }
68
45
  async function notifyError(ctx, error) {
69
46
  ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
70
47
  }
71
- async function runPluginCommand(args) {
72
- const entry = fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"));
73
- const modulePath = join(dirname(entry), "package-manager-cli.js");
74
- const { handlePackageCommand } = await import(pathToFileURL(modulePath).href);
75
- const handled = await handlePackageCommand(args);
76
- if (!handled)
77
- throw new Error(`不支持的 Plugin 操作:${args[0] ?? ""}`);
78
- }
79
48
  const activeAgentRuns = new Set();
80
49
  const CORE_SKILLS = new Set(["academic-grill", "research-intake", "evidence-capture", "citation-audit", "analysis-plan"]);
81
50
  function findLastMatching(items, predicate) {
@@ -575,7 +544,7 @@ function agentManagerItems(rows) {
575
544
  `类别:${row.stage}`,
576
545
  `角色:${row.role}`,
577
546
  `来源:${row.source === "bundled" ? "内置 Subagent(类似 Claude Explore/Plan)" : "项目 .psyclaw/agents/custom"}`,
578
- `运行:/agents --agent ${row.id} <只读研究任务>`,
547
+ `运行:/agents <只读研究任务>`,
579
548
  ],
580
549
  }));
581
550
  }
@@ -583,11 +552,11 @@ async function openAgentManager(ctx, rows) {
583
552
  return ctx.ui.custom((tui, theme, keybindings, done) => (new SkillManagerComponent(agentManagerItems(rows), tui, theme, keybindings, done, {
584
553
  title: "Subagent(对齐 Claude Code)",
585
554
  itemLabel: "Subagent",
586
- footer: "↑/↓ 移动 · Enter 查看用法 · Esc 关闭 · 运行: /agents --agent <id> <任务>",
555
+ footer: "↑/↓ 移动 · Enter 查看用法 · Esc 关闭 · 运行: /agents <任务>",
587
556
  enterAction: "details",
588
557
  toggleEnabled: false,
589
558
  enabledText: "已可选用",
590
- lockedMessage: "内置 Subagent 始终可用。运行:/agents --agent <id> <只读研究任务>",
559
+ lockedMessage: "内置 Subagent 始终可用。运行:/agents <只读研究任务>",
591
560
  })));
592
561
  }
593
562
  async function showAgentManager(ctx, statusOnly = false) {
@@ -612,21 +581,9 @@ async function installRecommendedPlugin(ctx, row) {
612
581
  await nativePluginManager(ctx).installAndPersist(row.sourceRef, { local: true });
613
582
  ctx.ui.notify(`${row.name} 已安装到项目目录(仅当前项目)。请执行 /reload 载入 Plugin。`, "info");
614
583
  }
615
- async function showPluginManager(args, ctx) {
616
- const [verb, id] = args.trim().split(/\s+/).filter(Boolean);
617
- if (verb && !["status", "install"].includes(verb))
618
- throw new Error("直接运行 /plugin 打开 Plugin 管理页");
619
- if (verb === "install" && !id)
620
- throw new Error("直接运行 /plugin,在列表中选择要安装的 Plugin");
584
+ async function showPluginManager(ctx) {
621
585
  const rows = await pluginManagerRows(ctx);
622
- if (verb === "install") {
623
- const row = rows.find((candidate) => candidate.id === id);
624
- if (!row)
625
- throw new Error(`未找到推荐 Plugin: ${id}`);
626
- await installRecommendedPlugin(ctx, row);
627
- return;
628
- }
629
- if (!ctx.hasUI || typeof ctx.ui.custom !== "function" || verb === "status") {
586
+ if (!ctx.hasUI || typeof ctx.ui.custom !== "function") {
630
587
  ctx.ui.notify(rows.map((row) => `${row.installed ? "[on]" : "[off]"} ${row.id} — ${row.name}${row.scope ? `(${row.scope === "project" ? "项目" : "系统"})` : ""}`).join("\n"), "info");
631
588
  return;
632
589
  }
@@ -679,50 +636,9 @@ async function queueModelMcpInstall(pi, ctx, row, plan) {
679
636
  pi.sendUserMessage(modelMcpInstallTask(ctx.cwd, row, plan), ctx.isIdle() ? {} : { deliverAs: "followUp" });
680
637
  ctx.ui.notify(`已将 ${row.name} 的下载、安装和配置任务交给当前模型。模型完成并确认可启动后,请执行 /reload。`, "info");
681
638
  }
682
- async function showMcpManager(pi, args, ctx, runtime) {
683
- const [verb, id] = args.trim().split(/\s+/).filter(Boolean);
684
- if (verb && !["status", "enable", "disable", "enable-all", "disable-all", "install"].includes(verb)) {
685
- throw new Error("Usage: /mcp [status|enable <id>|disable <id>|enable-all|disable-all|install <id>]");
686
- }
687
- if ((verb === "enable" || verb === "disable" || verb === "install") && !id) {
688
- throw new Error(`Usage: /mcp ${verb} <id>`);
689
- }
639
+ async function showMcpManager(pi, ctx, runtime) {
690
640
  const catalog = await recommendedItems("mcp");
691
- if (verb === "install") {
692
- const rows = await mcpManagerRows(ctx.cwd, await readRecommendationState(ctx.cwd), runtime);
693
- const row = rows.find((candidate) => candidate.id === id);
694
- if (!row)
695
- throw new Error(`未找到推荐 MCP: ${id}`);
696
- if (row.source !== "recommended")
697
- throw new Error(`MCP ${id} 已是用户配置,无需再次安装`);
698
- await queueModelMcpInstall(pi, ctx, row, catalog.installPrep.find((candidate) => candidate.id === row.id));
699
- return;
700
- }
701
- if (verb === "enable-all" || verb === "disable-all") {
702
- const enabled = verb === "enable-all";
703
- const rows = await mcpManagerRows(ctx.cwd, await readRecommendationState(ctx.cwd), runtime);
704
- for (const row of rows) {
705
- if (row.source === "user" && row.userEntry)
706
- await setUserMcpEnabled(row.userEntry, enabled);
707
- else
708
- await setRecommendedMcpEnabled(ctx.cwd, row.id, enabled);
709
- }
710
- ctx.ui.notify(`已批量${enabled ? "开启" : "关闭"}全部 MCP 配置;重启后重新检查运行时可用性`, "info");
711
- return;
712
- }
713
- if (verb === "enable" || verb === "disable") {
714
- const rows = await mcpManagerRows(ctx.cwd, await readRecommendationState(ctx.cwd), runtime);
715
- const row = rows.find((candidate) => candidate.id === id);
716
- if (!row)
717
- throw new Error(`未找到 MCP: ${id}`);
718
- if (row.source === "user" && row.userEntry)
719
- await setUserMcpEnabled(row.userEntry, verb === "enable");
720
- else
721
- await setRecommendedMcpEnabled(ctx.cwd, row.id, verb === "enable");
722
- ctx.ui.notify(`MCP ${id} 已${verb === "enable" ? "开启" : "关闭"};重启后重新检查安装、信任和工具策略`, "info");
723
- return;
724
- }
725
- if (!ctx.hasUI || typeof ctx.ui.custom !== "function" || verb === "status") {
641
+ if (!ctx.hasUI || typeof ctx.ui.custom !== "function") {
726
642
  const rows = await mcpManagerRows(ctx.cwd, await readRecommendationState(ctx.cwd), runtime);
727
643
  ctx.ui.notify(rows.map((row) => `${row.enabled ? "[on]" : "[off]"} ${row.id} — ${row.name}${row.source === "user" ? "(用户配置)" : ""}`).join("\n"), "info");
728
644
  return;
@@ -803,64 +719,8 @@ async function queueModelSkillInstall(pi, ctx, row) {
803
719
  pi.sendUserMessage(modelSkillInstallTask(ctx.cwd, row, scope), ctx.isIdle() ? {} : { deliverAs: "followUp" });
804
720
  ctx.ui.notify(`已将 ${row.name} 的安装任务交给当前模型,目标为${skillScopeLabel(scope)}。安装完成后请在 /skill 中启用,再执行 /reload。`, "info");
805
721
  }
806
- async function showSkillManager(pi, args, ctx) {
807
- const action = args.trim().split(/\s+/).filter(Boolean);
808
- const verb = action[0];
809
- const id = action[1];
810
- if (verb && !["status", "enable", "disable", "enable-all", "disable-all", "install"].includes(verb)) {
811
- throw new Error("Usage: /skill [status|enable <id>|disable <id>|enable-all|disable-all]");
812
- }
813
- if ((verb === "enable" || verb === "disable" || verb === "install") && !id) {
814
- throw new Error(`Usage: /skill ${verb} <id>`);
815
- }
816
- if (verb === "install") {
817
- const row = (await skillManagerRows(ctx.cwd, await readRecommendationState(ctx.cwd))).find((candidate) => candidate.id === normalizeRecommendedSkillId(id));
818
- if (!row)
819
- throw new Error(`未找到推荐 Skill: ${id}`);
820
- await queueModelSkillInstall(pi, ctx, row);
821
- return;
822
- }
823
- if (verb === "enable-all" || verb === "disable-all") {
824
- const enabled = verb === "enable-all";
825
- const state = await readRecommendationState(ctx.cwd);
826
- const rows = await skillManagerRows(ctx.cwd, state);
827
- const managed = rows.filter((row) => row.source === "recommended" && row.installed && !row.blocked);
828
- const locals = rows.filter((row) => row.source === "local");
829
- const failed = [];
830
- for (const row of managed) {
831
- try {
832
- await setRecommendedSkillEnabled(ctx.cwd, row.id, enabled);
833
- }
834
- catch {
835
- if (enabled)
836
- failed.push(row.name);
837
- }
838
- }
839
- if (locals.length > 0) {
840
- await setLocalSkillsEnabled(ctx.cwd, locals.map((row) => row.name), enabled);
841
- }
842
- ctx.ui.notify(enabled && failed.length > 0
843
- ? `已批量启用(${failed.join("、")} 启用失败)。请运行 /reload 重新加载。`
844
- : `已批量${enabled ? "启用" : "停用"}可管理的 Skill。请运行 /reload 重新加载。`, enabled && failed.length > 0 ? "warning" : "info");
845
- return;
846
- }
847
- if (verb === "enable" || verb === "disable") {
848
- const requested = normalizeRecommendedSkillId(id);
849
- const state = await readRecommendationState(ctx.cwd);
850
- const rows = await skillManagerRows(ctx.cwd, state);
851
- const row = rows.find((candidate) => candidate.id === requested || candidate.name === requested || candidate.id === userSkillId(requested));
852
- if (!row)
853
- throw new Error(`未找到 Skill: ${id}`);
854
- if (row.source === "local") {
855
- await setLocalSkillEnabled(ctx.cwd, row.name, verb === "enable");
856
- }
857
- else {
858
- await setRecommendedSkillEnabled(ctx.cwd, row.id, verb === "enable");
859
- }
860
- ctx.ui.notify(`${verb === "enable" ? "已启用" : "已停用"} ${row.name}。请运行 /reload 重新加载。`, "info");
861
- return;
862
- }
863
- if (!ctx.hasUI || typeof ctx.ui.custom !== "function" || verb === "status") {
722
+ async function showSkillManager(pi, ctx) {
723
+ if (!ctx.hasUI || typeof ctx.ui.custom !== "function") {
864
724
  const state = await readRecommendationState(ctx.cwd);
865
725
  const rows = await skillManagerRows(ctx.cwd, state);
866
726
  const lines = [
@@ -1026,12 +886,6 @@ const WORKFLOW_RUNNERS = {
1026
886
  "writing-review": runWritingReview,
1027
887
  "expert-review": runExpertReview,
1028
888
  };
1029
- function natureFillersFromCommandContext(ctx) {
1030
- return detectNatureArsFillers({
1031
- skills: ctx.getSystemPromptOptions?.()?.skills,
1032
- systemPrompt: ctx.getSystemPrompt?.(),
1033
- });
1034
- }
1035
889
  export default function psyclawExtension(pi) {
1036
890
  const developerCommands = process.env.PSYCLAW_DEVELOPER_COMMANDS === "1";
1037
891
  const legacyTestApi = typeof pi.registerTool !== "function";
@@ -1285,55 +1139,69 @@ export default function psyclawExtension(pi) {
1285
1139
  });
1286
1140
  const handleCrosscheck = async (args, ctx) => {
1287
1141
  try {
1288
- const parts = args.trim().split(/\s+/).filter(Boolean);
1289
- if (parts.length === 0 || parts[0] === "list") {
1290
- ctx.ui.notify(formatVerifyChecklist(await loadVerifyChecklist(ctx.cwd)), "info");
1291
- return;
1292
- }
1293
- if (parts[0] === "skip") {
1294
- ctx.ui.notify(`${formatVerifyChecklist(await skipUnverifiedItems(ctx.cwd))}\n注意:skip 不解除 analysis/academic 人审硬门禁。`, "warning");
1295
- return;
1296
- }
1297
- if (parts[0] === "kind") {
1298
- const kind = (parts[1] ?? "general");
1299
- pi.sendUserMessage([
1300
- "交叉核验任务:请作为助手主动提出核对项并执行字段核查,不要等用户自己去「申请确认」。",
1301
- `核验类别:${kind}(citations=引文,format=格式,requirements=研究要求,stats=统计结果,general=综合)。`,
1302
- "核查完成后用 /crosscheck <id> verified 记为 AI 已核(ai-checked),不得自称已过人审。",
1303
- "analysis / academic 模式下,全文或分析收尾前必须请人在 Panel「核实」或 /crosscheck <id> human;跳过不算通过。",
1304
- "分析前与分析后都要有清单,不能默认跳过。",
1305
- ].join("\n"), ctx.isIdle() ? {} : { deliverAs: "followUp" });
1306
- ctx.ui.notify(`已启动 ${kind} 交叉核验;人审请打开 /panel → 核对清单。`, "info");
1142
+ await ensureDefaultVerifyChecklist(ctx.cwd);
1143
+ const focus = args.trim();
1144
+ if (!focus) {
1145
+ ctx.ui.notify([
1146
+ formatVerifyChecklist(await loadVerifyChecklist(ctx.cwd)),
1147
+ "/crosscheck:过程性核对(数据、引文是否真实存在、格式/报告要求等)。",
1148
+ "人审不经斜杠命令:收尾门禁会要求 Panel「核实」或唤醒选项。",
1149
+ ].join("\n"), "info");
1307
1150
  return;
1308
1151
  }
1309
- const id = parts[0];
1310
- const statusRaw = (parts[1] ?? "verified").toLowerCase();
1311
- const allowed = new Set(["verified", "ai-checked", "unverified", "flagged", "skipped", "human"]);
1312
- if (!allowed.has(statusRaw)) {
1313
- ctx.ui.notify("Usage: /crosscheck list | skip | kind <citations|format|requirements|stats> | <id> verified|ai-checked|human|unverified|flagged|skipped [备注]", "info");
1152
+ pi.sendUserMessage([
1153
+ "/crosscheck — 过程性交叉核对(AI)。",
1154
+ `焦点:${focus}`,
1155
+ "范围重点:",
1156
+ "1) 数据与表:字段、N、表内数值与脚本/输出是否过程一致;",
1157
+ "2) 引文真实性:DOI/题录能否解析,是否指向真实文献(存在性,不在此阶段裁决引用是否“该不该引”);",
1158
+ "3) 格式与报告要求:APA/三线表/必备报告字段、清单项是否齐;",
1159
+ "4) 过程产物:脚本入口、中间文件、可复现痕迹是否齐全。",
1160
+ "执行方式:派出至少两个独立审查视角(例如:数据一致性;引文存在性;格式/报告合同),各自给出发现后再合并为一份报告;标出一致项与冲突项。",
1161
+ "不得自称已过人审。人审由系统在交接/定稿门禁自动要求,经 Panel「核实」或唤醒选项完成。",
1162
+ ].join("\n"), ctx.isIdle() ? {} : { deliverAs: "followUp" });
1163
+ ctx.ui.notify(`已启动过程性 /crosscheck(焦点:${focus})。人审由收尾门禁自动要求。`, "info");
1164
+ }
1165
+ catch (error) {
1166
+ await notifyError(ctx, error);
1167
+ }
1168
+ };
1169
+ const handleVerify = async (args, ctx) => {
1170
+ try {
1171
+ await ensureDefaultVerifyChecklist(ctx.cwd);
1172
+ const focus = args.trim();
1173
+ if (!focus) {
1174
+ ctx.ui.notify([
1175
+ formatVerifyChecklist(await loadVerifyChecklist(ctx.cwd)),
1176
+ "/verify:整体性验证(分析结果是否属实/成立、引文是否合理、方法是否合理)。",
1177
+ "人审不经斜杠命令:Panel「核实」或唤醒选项。",
1178
+ ].join("\n"), "info");
1314
1179
  return;
1315
1180
  }
1316
- const notes = parts.slice(2).join(" ") || undefined;
1317
- const source = statusRaw === "human" ? "human" : "ai";
1318
- const checklist = await markVerifyItem(ctx.cwd, id, statusRaw, notes, undefined, { source });
1319
- const tip = statusRaw === "human" || (statusRaw === "verified" && source === "human")
1320
- ? "已记为人审通过。"
1321
- : statusRaw === "verified" || statusRaw === "ai-checked"
1322
- ? "已记为 AI 已核;analysis/academic 收尾前仍须人在 Panel 点「核实」或 /crosscheck <id> human。"
1323
- : undefined;
1324
- ctx.ui.notify(tip ? `${formatVerifyChecklist(checklist)}\n${tip}` : formatVerifyChecklist(checklist), "info");
1181
+ pi.sendUserMessage([
1182
+ "/verify — 整体性实质验证(AI)。",
1183
+ `焦点:${focus}`,
1184
+ "范围重点:",
1185
+ "1) 分析结果是否属实、是否成立:效应方向/量级、不确定性、与脚本输出是否支持正文主张;",
1186
+ "2) 引文是否合理:是否支撑该主张、有无断章取义或装饰性引用(不仅是 DOI 能否解析);",
1187
+ "3) 方法是否合理:设计、估计目标、检验/模型选择与研究问题是否匹配;",
1188
+ "4) 解释边界:相关≠因果、探索/确证区分、过度声称。",
1189
+ "给出可核对的判断(成立 / 存疑 / 不成立)及依据;不得自称已过人审。",
1190
+ "人审由交接/定稿门禁自动要求,经 Panel「核实」或唤醒选项完成。",
1191
+ ].join("\n"), ctx.isIdle() ? {} : { deliverAs: "followUp" });
1192
+ ctx.ui.notify(`已启动整体性 /verify(焦点:${focus})。人审由收尾门禁自动要求。`, "info");
1325
1193
  }
1326
1194
  catch (error) {
1327
1195
  await notifyError(ctx, error);
1328
1196
  }
1329
1197
  };
1330
1198
  pi.registerCommand("crosscheck", {
1331
- description: "AI 交叉核验 + 人审:/crosscheck list|skip|kind|<id> verified|human|…",
1199
+ description: "过程性 AI 核对:数据/引文真实性/格式要求;可附带焦点",
1332
1200
  handler: async (args, ctx) => handleCrosscheck(args, ctx),
1333
1201
  });
1334
1202
  pi.registerCommand("verify", {
1335
- description: "同 /crosscheck;AI 核查后 analysis/academic 须人审",
1336
- handler: async (args, ctx) => handleCrosscheck(args, ctx),
1203
+ description: "整体性 AI 验证:结果是否成立、引文与方法是否合理;可附带焦点",
1204
+ handler: async (args, ctx) => handleVerify(args, ctx),
1337
1205
  });
1338
1206
  pi.registerCommand("help", {
1339
1207
  description: "打开 Panel 使用速览",
@@ -1353,151 +1221,61 @@ export default function psyclawExtension(pi) {
1353
1221
  },
1354
1222
  });
1355
1223
  pi.registerCommand("plan", {
1356
- description: "分析方案:/plan [new|status|list|confirm|review|run|defer|handoff|reject]",
1224
+ description: "分析方案:单独查看状态,或附带目标文本新建方案",
1357
1225
  handler: async (args, ctx) => {
1358
1226
  try {
1359
- const trimmed = args.trim();
1360
- const [cmdRaw, ...rest] = trimmed.split(/\s+/).filter(Boolean);
1361
- const cmd = (cmdRaw ?? "status").toLowerCase();
1362
- const usage = "Usage: /plan new|status|list|confirm|auto|human|review|run|defer|handoff|reject";
1363
- if (cmd === "new") {
1364
- const goal = rest.join(" ").trim() || "从当前对话澄清的分析目标";
1227
+ const goal = args.trim();
1228
+ if (goal) {
1365
1229
  const plan = await writeAnalysisPlan(ctx.cwd, createAnalysisPlan({ goal }));
1366
- ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n已创建。模型会按分析项逐一请你选择;回复「可以」即可确认执行。`, "info");
1367
- return;
1368
- }
1369
- if (cmd === "auto") {
1370
- let plan = await readActiveAnalysisPlan(ctx.cwd);
1371
- if (!plan) {
1372
- ctx.ui.notify("没有活跃 Plan。先 /plan new 或在 analysis 模式触发 soft takeover。", "warning");
1373
- return;
1374
- }
1375
- plan = advanceAnalysisPlan(plan, { type: "set-approval-mode", mode: "auto" });
1376
- plan = await writeAnalysisPlan(ctx.cwd, plan);
1377
- ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n已启用 auto:后续结果必须标注「未经人审批」。`, "warning");
1378
- return;
1379
- }
1380
- if (cmd === "human") {
1381
- let plan = await readActiveAnalysisPlan(ctx.cwd);
1382
- if (!plan) {
1383
- ctx.ui.notify("没有活跃 Plan。", "warning");
1384
- return;
1385
- }
1386
- plan = advanceAnalysisPlan(plan, { type: "set-approval-mode", mode: "human" });
1387
- plan = await writeAnalysisPlan(ctx.cwd, plan);
1388
- ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n已恢复人工确认。`, "info");
1389
- return;
1390
- }
1391
- if (cmd === "list") {
1392
- const ids = await listAnalysisPlans(ctx.cwd);
1393
- ctx.ui.notify(ids.length > 0 ? `Plans:\n${ids.map((id) => `- ${id}`).join("\n")}` : "尚无 analysis/plans 记录。用 /plan new 开始。", "info");
1394
- return;
1395
- }
1396
- let plan = await readActiveAnalysisPlan(ctx.cwd);
1397
- if (!plan && cmd === "status") {
1398
- ctx.ui.notify("没有活跃分析 Plan。用 /plan new [目标] 创建,或在 analysis 模式用自然语言触发 soft takeover。", "info");
1399
- return;
1400
- }
1401
- if (!plan) {
1402
- ctx.ui.notify(`没有活跃分析 Plan。\n${usage}`, "warning");
1403
- return;
1404
- }
1405
- if (cmd === "status" || cmd === "show") {
1406
- ctx.ui.notify(formatAnalysisPlanStatus(plan), "info");
1407
- return;
1408
- }
1409
- if (cmd === "confirm") {
1410
- const method = rest.join(" ").trim() || plan.primaryAnalysis || plan.proposedMethods[0] || "";
1411
- if (!method) {
1412
- ctx.ui.notify("请指定方法:/plan confirm <主分析方法>", "warning");
1413
- return;
1414
- }
1415
- plan = advanceAnalysisPlan(plan, { type: "confirm", method, backend: "local-script" });
1416
- plan = advanceAnalysisPlan(plan, { type: "review" });
1417
- plan = await writeAnalysisPlan(ctx.cwd, plan);
1418
- ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n软确认完成并已审查。回复「可以」或 /plan run 开始执行。`, "info");
1419
- return;
1420
- }
1421
- if (cmd === "review") {
1422
- plan = advanceAnalysisPlan(plan, { type: "review" });
1423
- plan = await writeAnalysisPlan(ctx.cwd, plan);
1424
- ctx.ui.notify(formatAnalysisPlanStatus(plan), "info");
1425
- return;
1426
- }
1427
- if (cmd === "run") {
1428
- plan = advanceAnalysisPlan(plan, { type: "run-now" });
1429
- plan = await writeAnalysisPlan(ctx.cwd, plan);
1430
- ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n已标记立即执行:编写/运行 analysis/scripts/ 下的可复现脚本(特殊后端再用 MCP)。`, "info");
1230
+ ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n已按目标创建。用自然语言确认方法(如「可以」);完成后用 /handoff 交接。`, "info");
1431
1231
  pi.sendUserMessage([
1432
1232
  "/skill:analysis-plan",
1433
1233
  "",
1434
- `Active plan ${plan.id} is marked running. Execute the confirmed method now with local reproducible scripts under analysis/scripts/.`,
1435
- `Confirmed method: ${plan.confirmedMethod ?? plan.primaryAnalysis ?? "(see plan file)"}`,
1436
- "Do not invent numbers. After results, update analysis/HANDOFF.md via /plan handoff.",
1234
+ `Active plan ${plan.id} created for goal: ${goal}`,
1235
+ "Clarify methods with the researcher; prefer natural-language confirmation. Do not invent numbers.",
1437
1236
  ].join("\n"), ctx.isIdle() ? {} : { deliverAs: "followUp" });
1438
1237
  return;
1439
1238
  }
1440
- if (cmd === "defer") {
1441
- plan = advanceAnalysisPlan(plan, { type: "defer" });
1442
- plan = await writeAnalysisPlan(ctx.cwd, plan);
1443
- ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n已推迟执行;稍后用 /plan run 继续。`, "info");
1239
+ const plan = await readActiveAnalysisPlan(ctx.cwd);
1240
+ if (!plan) {
1241
+ ctx.ui.notify("没有活跃分析 Plan。用 /plan <目标> 创建,或在 analysis 模式用自然语言触发 soft takeover。", "info");
1444
1242
  return;
1445
1243
  }
1446
- if (cmd === "handoff") {
1447
- const gate = await assertHumanVerifyGate(ctx.cwd, "analysis-complete");
1448
- if (!gate.ok) {
1449
- ctx.ui.notify(gate.message, "error");
1450
- return;
1451
- }
1452
- const path = await syncHandoffFromAnalysisPlan(ctx.cwd, plan);
1453
- if (plan.status === "running") {
1454
- plan = advanceAnalysisPlan(plan, plan.scriptEntrypoint
1455
- ? { type: "complete", scriptEntrypoint: plan.scriptEntrypoint }
1456
- : { type: "complete" });
1457
- plan = await writeAnalysisPlan(ctx.cwd, plan);
1458
- }
1459
- ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n已写入 ${path}。可切到 academic 模式。`, "info");
1244
+ ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n确认与执行请用自然语言;交接用 /handoff。`, "info");
1245
+ }
1246
+ catch (error) {
1247
+ await notifyError(ctx, error);
1248
+ }
1249
+ },
1250
+ });
1251
+ pi.registerCommand("handoff", {
1252
+ description: "人审通过后写入 analysis/HANDOFF.md 交接",
1253
+ handler: async (_args, ctx) => {
1254
+ try {
1255
+ const gate = await assertHumanVerifyGate(ctx.cwd, "analysis-complete");
1256
+ if (!gate.ok) {
1257
+ ctx.ui.notify(gate.message, "error");
1460
1258
  return;
1461
1259
  }
1462
- if (cmd === "reject") {
1463
- const reason = rest.join(" ").trim() || "researcher rejected the proposal";
1464
- plan = advanceAnalysisPlan(plan, { type: "block", reason });
1465
- plan = await writeAnalysisPlan(ctx.cwd, plan);
1466
- ctx.ui.notify(formatAnalysisPlanStatus(plan), "warning");
1260
+ let plan = await readActiveAnalysisPlan(ctx.cwd);
1261
+ if (!plan) {
1262
+ ctx.ui.notify("没有活跃分析 Plan,无法交接。先 /plan <目标> 或在 analysis 模式推进。", "warning");
1467
1263
  return;
1468
1264
  }
1469
- ctx.ui.notify(usage, "info");
1265
+ const path = await syncHandoffFromAnalysisPlan(ctx.cwd, plan);
1266
+ if (plan.status === "running") {
1267
+ plan = advanceAnalysisPlan(plan, plan.scriptEntrypoint
1268
+ ? { type: "complete", scriptEntrypoint: plan.scriptEntrypoint }
1269
+ : { type: "complete" });
1270
+ plan = await writeAnalysisPlan(ctx.cwd, plan);
1271
+ }
1272
+ ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n已写入 ${path}。可切到 academic 模式。`, "info");
1470
1273
  }
1471
1274
  catch (error) {
1472
1275
  await notifyError(ctx, error);
1473
1276
  }
1474
1277
  },
1475
1278
  });
1476
- if (developerCommands)
1477
- pi.registerCommand("handoff", {
1478
- description: "(开发)写入机器可读交接检查点",
1479
- handler: async (_args, ctx) => {
1480
- try {
1481
- const paths = projectPaths(ctx.cwd);
1482
- const project = await import("node:fs/promises").then(({ readFile }) => readFile(paths.project, "utf8").then((text) => asProject(JSON.parse(text))));
1483
- await writeHandoff(ctx.cwd, {
1484
- projectId: project.id,
1485
- runId: `run_${Date.now()}`,
1486
- goal: project.goal,
1487
- completed: ["project bootstrap"],
1488
- verified: ["project.json exists"],
1489
- blocked: ["evidence ledger has not been reviewed"],
1490
- nextSteps: ["import a local source and create Claim-Evidence links"],
1491
- verificationCommands: ["pnpm typecheck", "pnpm test"],
1492
- generatedAt: new Date().toISOString(),
1493
- });
1494
- ctx.ui.notify("Wrote .psyclaw/notes/HANDOFF.md and handoff.json", "info");
1495
- }
1496
- catch (error) {
1497
- await notifyError(ctx, error);
1498
- }
1499
- },
1500
- });
1501
1279
  if (!legacyTestApi)
1502
1280
  pi.registerCommand("grill", {
1503
1281
  description: "逐题追问并压力测试学术研究方案",
@@ -1605,89 +1383,55 @@ export default function psyclawExtension(pi) {
1605
1383
  for (const kind of ["skill", "hook", "rule", "subagent"]) {
1606
1384
  if (!legacyTestApi)
1607
1385
  pi.registerCommand(`create-${kind}`, {
1608
- description: `预览并创建项目级 ${kind}`,
1386
+ description: `预览并创建项目级 ${kind}(附带名称与需求文本)`,
1609
1387
  handler: async (args, ctx) => {
1610
- if (!args.trim()) {
1611
- ctx.ui.notify(`Usage: /create-${kind} <name and requirements>`, "info");
1388
+ const requirements = args.trim();
1389
+ if (!requirements) {
1390
+ ctx.ui.notify(`请使用 /create-${kind} <名称与需求描述>`, "info");
1612
1391
  return;
1613
1392
  }
1614
- pi.sendUserMessage(creationCommandPrompt(kind, args.trim()), ctx.isIdle() ? {} : { deliverAs: "followUp" });
1393
+ pi.sendUserMessage(creationCommandPrompt(kind, requirements), ctx.isIdle() ? {} : { deliverAs: "followUp" });
1615
1394
  },
1616
1395
  });
1617
1396
  }
1618
1397
  if (!legacyTestApi)
1619
1398
  pi.registerCommand("skill", {
1620
- description: "管理和安装 Skill",
1621
- handler: async (args, ctx) => {
1622
- const [name, ...rest] = args.trim().split(/\s+/).filter(Boolean);
1623
- if (!name) {
1624
- try {
1625
- await showSkillManager(pi, "", ctx);
1626
- }
1627
- catch (error) {
1628
- await notifyError(ctx, error);
1629
- }
1630
- return;
1631
- }
1632
- if (name === "install") {
1633
- const source = rest.join(" ").trim();
1634
- if (!source) {
1635
- ctx.ui.notify("Usage: /skill install <local-directory>", "info");
1636
- return;
1637
- }
1638
- try {
1639
- const installed = await installLocalSkill(source, ctx.cwd);
1640
- ctx.ui.notify(`已安装本地 Skill ${installed.name}:${installed.target}。请执行 /reload。`, "info");
1641
- }
1642
- catch (error) {
1643
- await notifyError(ctx, error);
1644
- }
1645
- return;
1399
+ description: "打开 Skill 管理页",
1400
+ handler: async (_args, ctx) => {
1401
+ try {
1402
+ await showSkillManager(pi, ctx);
1646
1403
  }
1647
- if (["status", "enable", "disable", "enable-all", "disable-all"].includes(name)) {
1648
- try {
1649
- await showSkillManager(pi, args, ctx);
1650
- }
1651
- catch (error) {
1652
- await notifyError(ctx, error);
1653
- }
1654
- return;
1404
+ catch (error) {
1405
+ await notifyError(ctx, error);
1655
1406
  }
1656
- ctx.ui.notify("Usage: /skill [status|enable <id>|disable <id>|enable-all|disable-all|install <local-directory>]", "info");
1657
1407
  },
1658
1408
  });
1659
1409
  if (!legacyTestApi)
1660
1410
  pi.registerCommand("ars", {
1661
- description: "切换 academic mode,或管理 doctor/start/full/stop",
1411
+ description: "学术模式:单独开启,或附带任务文本启动完整流程",
1662
1412
  handler: async (args, ctx) => {
1663
1413
  try {
1664
- const [action, ...rest] = args.trim().split(/\s+/).filter(Boolean);
1665
- // Bare /ars → sticky academic mode (same as Shift+Tab on). No profile popup.
1666
- if (!action) {
1414
+ const trailing = args.trim();
1415
+ if (!trailing) {
1667
1416
  if (!ctx.hasUI) {
1668
- ctx.ui.notify("当前环境无编辑器;请改用 Shift+Tab 或 /ars start。", "info");
1417
+ ctx.ui.notify("当前环境无编辑器;请改用 Shift+Tab 切换 academic。", "info");
1669
1418
  return;
1670
1419
  }
1671
1420
  arsUiContext = ctx;
1672
1421
  applySessionMode("academic");
1673
1422
  return;
1674
1423
  }
1675
- if (action === "status") {
1676
- ctx.ui.notify([
1677
- `PsyClaw ARS profile v${PSYCLAW_ARS_PROFILE_VERSION}`,
1678
- `当前模式:${arsModeEditor?.getMode() ?? sessionMode}(Shift+Tab:chat → analysis → academic)`,
1679
- "Thinking:Ctrl+Shift+T",
1680
- `来源:${ARS_REPOSITORY_URL} @ ${ARS_UPSTREAM_REF} (${ARS_UPSTREAM_COMMIT.slice(0, 12)})`,
1681
- "入口:Shift+Tab,/ars doctor,/ars start,/ars full <task>,/ars stop,/crosscheck,/help",
1682
- formatNatureArsFillerStatus(natureFillersFromCommandContext(ctx)),
1683
- ].join("\n"), "info");
1684
- return;
1685
- }
1686
- if (action === "install") {
1687
- ctx.ui.notify("ARS 与 Nature/compose 技能已内置:Shift+Tab 切到 academic。", "info");
1424
+ const lower = trailing.toLowerCase();
1425
+ if (lower === "stop") {
1426
+ arsUiContext = ctx;
1427
+ applySessionMode("chat");
1428
+ if (ctx.hasUI && isArsModeEditorText(ctx.ui.getEditorText?.() ?? "")) {
1429
+ ctx.ui.setEditorText("");
1430
+ }
1431
+ ctx.ui.notify("已回到 chat 模式", "info");
1688
1432
  return;
1689
1433
  }
1690
- if (action === "doctor") {
1434
+ if (lower === "doctor") {
1691
1435
  const activeTools = typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [];
1692
1436
  const commands = typeof pi.getCommands === "function"
1693
1437
  ? pi.getCommands().map((command) => command.name)
@@ -1711,33 +1455,10 @@ export default function psyclawExtension(pi) {
1711
1455
  }
1712
1456
  return;
1713
1457
  }
1714
- if (action === "start") {
1715
- arsUiContext = ctx;
1716
- applySessionMode("academic");
1717
- ctx.ui.notify("academic mode 已开启(Shift+Tab 可切换)", "info");
1718
- return;
1719
- }
1720
- if (action === "stop") {
1721
- arsUiContext = ctx;
1722
- applySessionMode("chat");
1723
- if (ctx.hasUI && isArsModeEditorText(ctx.ui.getEditorText?.() ?? "")) {
1724
- ctx.ui.setEditorText("");
1725
- }
1726
- ctx.ui.notify("已回到 chat 模式", "info");
1727
- return;
1728
- }
1729
- const upstreamCommand = action === "full"
1730
- ? `/ars-full${rest.length > 0 ? ` ${rest.join(" ")}` : ""}`
1731
- : undefined;
1732
- if (!upstreamCommand) {
1733
- ctx.ui.notify("Usage: /ars [status|doctor|start|full <task>|stop] 或按 Shift+Tab 切换 academic mode", "info");
1734
- return;
1735
- }
1736
- // A queued prompt does not get a fresh system prompt in Pi. Wait so the
1737
- // upstream wrapper can activate ARS and inject its compatibility note.
1738
- if (action === "full" && !ctx.isIdle())
1458
+ // Any other trailing text is the academic full-pipeline task.
1459
+ if (!ctx.isIdle())
1739
1460
  await ctx.waitForIdle();
1740
- pi.sendUserMessage(upstreamCommand, {
1461
+ pi.sendUserMessage(`/ars-full ${trailing}`, {
1741
1462
  ...(!ctx.isIdle() ? { deliverAs: "followUp" } : {}),
1742
1463
  expandPromptTemplates: true,
1743
1464
  });
@@ -1749,40 +1470,10 @@ export default function psyclawExtension(pi) {
1749
1470
  });
1750
1471
  if (!legacyTestApi)
1751
1472
  pi.registerCommand("plugin", {
1752
- description: "浏览推荐并管理 Plugin / Extension",
1753
- handler: async (args, ctx) => {
1473
+ description: "打开 Plugin 推荐与管理页",
1474
+ handler: async (_args, ctx) => {
1754
1475
  try {
1755
- const [action, ...rest] = args.trim().split(/\s+/).filter(Boolean);
1756
- if (!action) {
1757
- await showPluginManager("", ctx);
1758
- return;
1759
- }
1760
- if (action === "status") {
1761
- await showPluginManager("status", ctx);
1762
- return;
1763
- }
1764
- if (!["list", "install", "remove"].includes(action)) {
1765
- throw new Error("直接运行 /plugin 打开 Plugin 管理页");
1766
- }
1767
- if (action !== "list" && rest.length === 0) {
1768
- await showPluginManager("", ctx);
1769
- return;
1770
- }
1771
- if (action === "install" && rest.length === 1) {
1772
- const recommended = (await pluginManagerRows(ctx)).some((row) => row.id === rest[0]);
1773
- if (recommended) {
1774
- await showPluginManager(`install ${rest[0]}`, ctx);
1775
- return;
1776
- }
1777
- }
1778
- if (action !== "list") {
1779
- const approved = await ctx.ui.confirm(`${action === "install" ? "安装" : "移除"} Plugin?`, `${rest.join(" ")}\nPlugin 与宿主进程同权限,操作完成后需要重启 PsyClaw。`);
1780
- if (!approved)
1781
- return;
1782
- }
1783
- await runPluginCommand([action, ...rest]);
1784
- if (action !== "list")
1785
- ctx.ui.notify("Plugin 配置已更新,请执行 /reload。", "info");
1476
+ await showPluginManager(ctx);
1786
1477
  }
1787
1478
  catch (error) {
1788
1479
  await notifyError(ctx, error);
@@ -1792,9 +1483,9 @@ export default function psyclawExtension(pi) {
1792
1483
  if (!legacyTestApi)
1793
1484
  pi.registerCommand("mcp", {
1794
1485
  description: "打开 MCP 安装与配置管理页",
1795
- handler: async (args, ctx) => {
1486
+ handler: async (_args, ctx) => {
1796
1487
  try {
1797
- await showMcpManager(pi, args, ctx, runtimeMcps);
1488
+ await showMcpManager(pi, ctx, runtimeMcps);
1798
1489
  }
1799
1490
  catch (error) {
1800
1491
  await notifyError(ctx, error);
@@ -1842,7 +1533,7 @@ export default function psyclawExtension(pi) {
1842
1533
  requested = selectedProvider;
1843
1534
  }
1844
1535
  if (!/^[A-Za-z0-9._:-]+$/.test(requested))
1845
- throw new Error("Usage: /provider <provider-id>");
1536
+ throw new Error("请使用 /provider 打开选择,或 /provider <provider-id>");
1846
1537
  const preset = PROVIDER_PRESETS.find((item) => item.id === requested);
1847
1538
  let models = providers.get(requested) ?? [];
1848
1539
  const modelChoices = models.length > 0 ? models : (preset?.models ?? []);
@@ -1901,7 +1592,7 @@ export default function psyclawExtension(pi) {
1901
1592
  });
1902
1593
  if (!legacyTestApi)
1903
1594
  pi.registerCommand("pet", {
1904
- description: "开启或关闭启动横幅宠物",
1595
+ description: "启动横幅宠物:单独查看状态,或附带 on/off",
1905
1596
  handler: async (args, ctx) => {
1906
1597
  const action = args.trim().toLowerCase() || "status";
1907
1598
  if (action === "status") {
@@ -1909,7 +1600,7 @@ export default function psyclawExtension(pi) {
1909
1600
  return;
1910
1601
  }
1911
1602
  if (action !== "on" && action !== "off") {
1912
- ctx.ui.notify("Usage: /pet on|off|status", "error");
1603
+ ctx.ui.notify("请使用 /pet,或 /pet on|off", "error");
1913
1604
  return;
1914
1605
  }
1915
1606
  await setPetPreference(action === "on");
@@ -2605,30 +2296,22 @@ export default function psyclawExtension(pi) {
2605
2296
  // runner behind PSYCLAW_DEVELOPER_COMMANDS in published builds.
2606
2297
  if (!legacyTestApi)
2607
2298
  pi.registerCommand("agents", {
2608
- description: "浏览或运行 Subagent(对齐 Claude Code)",
2299
+ description: "浏览 Subagent,或附带任务文本运行",
2609
2300
  handler: async (args, ctx) => {
2610
- const trimmed = args.trim();
2611
- if (!trimmed || trimmed === "status" || trimmed === "list") {
2301
+ const objective = args.trim();
2302
+ if (!objective) {
2612
2303
  try {
2613
- await showAgentManager(ctx, trimmed === "status" || trimmed === "list");
2304
+ await showAgentManager(ctx, false);
2614
2305
  }
2615
2306
  catch (error) {
2616
2307
  await notifyError(ctx, error);
2617
2308
  }
2618
2309
  return;
2619
2310
  }
2620
- const requested = parseAgentsRequest(args);
2621
- const objective = requested.objective;
2622
- if (!objective) {
2623
- ctx.ui.notify("Usage: /agents [--agent <id>|--agents <id,...>] <task>\n无任务时直接运行 /agents 打开 Subagent 管理页。", "info");
2624
- return;
2625
- }
2626
2311
  if (objective.length > 4_000) {
2627
2312
  ctx.ui.notify("Agent task is too long; split it into smaller bounded tasks", "error");
2628
2313
  return;
2629
2314
  }
2630
- // Do not let the runner create a partial `.psyclaw/runs` tree outside
2631
- // an explicitly initialized `/init` research project.
2632
2315
  try {
2633
2316
  await readProject(ctx.cwd);
2634
2317
  }
@@ -2643,63 +2326,7 @@ export default function psyclawExtension(pi) {
2643
2326
  activeAgentRuns.add(ctx.cwd);
2644
2327
  try {
2645
2328
  const runId = `pi_agent_${Date.now()}`;
2646
- let plan = researchTaskPlan(runId, objective);
2647
- if (requested.ids.length > 0) {
2648
- const available = await loadSelectablePersonas(ctx.cwd);
2649
- const byId = new Map(available.map((persona) => [persona.id, persona]));
2650
- const selected = requested.ids.map((id) => byId.get(id));
2651
- const missing = requested.ids.filter((_id, index) => !selected[index]);
2652
- if (missing.length > 0)
2653
- throw new Error(`Unknown or invalid subagent: ${missing.join(", ")}. Run /agents to list bundled and custom subagents.`);
2654
- const personas = selected.filter((persona) => persona !== undefined);
2655
- const unionEffects = normalizeEffects(personas.flatMap((persona) => persona.allowedEffects));
2656
- const elevated = elevatedEffects(unionEffects);
2657
- if (elevated.length > 0) {
2658
- if (!ctx.hasUI || typeof ctx.ui.confirm !== "function") {
2659
- throw new Error(`Subagent effects [${formatEffects(elevated)}] require interactive confirmation`);
2660
- }
2661
- const approved = await ctx.ui.confirm("批准 Subagent 提升权限?", `将启用:${formatEffects(elevated)}\n对应工具:${toolsForEffects(unionEffects).join(", ")}\n任务:${objective.slice(0, 400)}\n\n凭据读取、绕过门禁与外部发布仍被禁止。`);
2662
- if (!approved) {
2663
- ctx.ui.notify("已取消:未批准提升权限,未启动 Subagent。", "warning");
2664
- return;
2665
- }
2666
- }
2667
- plan = customPersonaPlan(runId, objective, personas);
2668
- await mkdir(join(ctx.cwd, ".psyclaw", "plans"), { recursive: true });
2669
- await atomicWriteFile(join(ctx.cwd, ".psyclaw", "plans", `${runId}.json`), `${JSON.stringify(plan, null, 2)}\n`);
2670
- const eventLog = new RunEventLog(ctx.cwd, runId);
2671
- const result = await runPlanWithPi(plan, {
2672
- cwd: ctx.cwd,
2673
- agentDir: join(ctx.cwd, ".psyclaw", "pi-agent"),
2674
- ...(ctx.model?.provider === undefined ? {} : { provider: ctx.model.provider }),
2675
- ...(ctx.model?.id === undefined ? {} : { model: ctx.model.id }),
2676
- env: providerEnvironment(ctx.model?.provider),
2677
- ...(elevated.length === 0 ? { tools: [] } : {}),
2678
- allowWrites: unionEffects.includes("write"),
2679
- allowNetwork: unionEffects.includes("network"),
2680
- allowDestructive: unionEffects.includes("destructive"),
2681
- root: ctx.cwd,
2682
- pauseRequested: async () => {
2683
- try {
2684
- await import("node:fs/promises").then(({ access }) => access(join(ctx.cwd, ".psyclaw", "runs", `${runId}.pause`)));
2685
- return true;
2686
- }
2687
- catch {
2688
- return false;
2689
- }
2690
- },
2691
- onEvent: async (event) => { await eventLog.append(event); },
2692
- });
2693
- pi.appendEntry("psyclaw:agent-run", {
2694
- runId,
2695
- status: result.status,
2696
- diagnostics: result.diagnostics,
2697
- effects: unionEffects,
2698
- recordedAt: new Date().toISOString(),
2699
- });
2700
- ctx.ui.notify(`Agent run ${result.status}: ${result.diagnostics.join("; ") || "verified"}`, result.status === "completed" ? "info" : "warning");
2701
- return;
2702
- }
2329
+ const plan = researchTaskPlan(runId, objective);
2703
2330
  await mkdir(join(ctx.cwd, ".psyclaw", "plans"), { recursive: true });
2704
2331
  await atomicWriteFile(join(ctx.cwd, ".psyclaw", "plans", `${runId}.json`), `${JSON.stringify(plan, null, 2)}\n`);
2705
2332
  const eventLog = new RunEventLog(ctx.cwd, runId);