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.
- package/README.md +14 -14
- package/dist/apps/panel/index.html +381 -141
- package/dist/src/adapters/pi/extension.js +145 -518
- package/dist/src/adapters/pi/extension.js.map +1 -1
- package/dist/src/agents/recommended-personas.d.ts +1 -1
- package/dist/src/agents/recommended-personas.js +2 -2
- package/dist/src/agents/recommended-personas.js.map +1 -1
- package/dist/src/analysis/plan.js +2 -2
- package/dist/src/analysis/plan.js.map +1 -1
- package/dist/src/analysis/stats-router.js +2 -2
- package/dist/src/analysis/stats-router.js.map +1 -1
- package/dist/src/branding.js +3 -3
- package/dist/src/branding.js.map +1 -1
- package/dist/src/orchestration/bundled-personas.d.ts +1 -1
- package/dist/src/orchestration/bundled-personas.js +1 -1
- package/dist/src/orchestration/bundled-personas.js.map +1 -1
- package/dist/src/orchestration/personas.js +2 -5
- package/dist/src/orchestration/personas.js.map +1 -1
- package/dist/src/panel/extension.js +3 -7
- package/dist/src/panel/extension.js.map +1 -1
- package/dist/src/panel/server.js +4 -4
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/session/help.d.ts +1 -1
- package/dist/src/session/help.js +26 -27
- package/dist/src/session/help.js.map +1 -1
- package/dist/src/session/modes.js +3 -3
- package/dist/src/session/modes.js.map +1 -1
- package/dist/src/skills/recommended.js +1 -1
- package/dist/src/skills/recommended.js.map +1 -1
- package/dist/src/style/cli-ui.js +14 -13
- package/dist/src/style/cli-ui.js.map +1 -1
- package/dist/src/verify/checklist.d.ts +5 -4
- package/dist/src/verify/checklist.js +7 -8
- package/dist/src/verify/checklist.js.map +1 -1
- package/package.json +1 -1
- 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,
|
|
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 {
|
|
6
|
-
import {
|
|
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
|
|
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,
|
|
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 {
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
53
|
-
if (!
|
|
40
|
+
const goal = args.trim();
|
|
41
|
+
if (!goal)
|
|
54
42
|
return {};
|
|
55
|
-
|
|
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
|
|
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
|
|
555
|
+
footer: "↑/↓ 移动 · Enter 查看用法 · Esc 关闭 · 运行: /agents <任务>",
|
|
587
556
|
enterAction: "details",
|
|
588
557
|
toggleEnabled: false,
|
|
589
558
|
enabledText: "已可选用",
|
|
590
|
-
lockedMessage: "内置 Subagent 始终可用。运行:/agents
|
|
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(
|
|
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 (
|
|
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,
|
|
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 (
|
|
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,
|
|
807
|
-
|
|
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
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
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
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
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
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
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
|
|
1199
|
+
description: "过程性 AI 核对:数据/引文真实性/格式要求;可附带焦点",
|
|
1332
1200
|
handler: async (args, ctx) => handleCrosscheck(args, ctx),
|
|
1333
1201
|
});
|
|
1334
1202
|
pi.registerCommand("verify", {
|
|
1335
|
-
description: "
|
|
1336
|
-
handler: async (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: "
|
|
1224
|
+
description: "分析方案:单独查看状态,或附带目标文本新建方案",
|
|
1357
1225
|
handler: async (args, ctx) => {
|
|
1358
1226
|
try {
|
|
1359
|
-
const
|
|
1360
|
-
|
|
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
|
|
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}
|
|
1435
|
-
|
|
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
|
-
|
|
1441
|
-
|
|
1442
|
-
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
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
ctx.ui.notify(
|
|
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
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
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.
|
|
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
|
-
|
|
1611
|
-
|
|
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,
|
|
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: "
|
|
1621
|
-
handler: async (
|
|
1622
|
-
|
|
1623
|
-
|
|
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
|
-
|
|
1648
|
-
|
|
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: "
|
|
1411
|
+
description: "学术模式:单独开启,或附带任务文本启动完整流程",
|
|
1662
1412
|
handler: async (args, ctx) => {
|
|
1663
1413
|
try {
|
|
1664
|
-
const
|
|
1665
|
-
|
|
1666
|
-
if (!action) {
|
|
1414
|
+
const trailing = args.trim();
|
|
1415
|
+
if (!trailing) {
|
|
1667
1416
|
if (!ctx.hasUI) {
|
|
1668
|
-
ctx.ui.notify("当前环境无编辑器;请改用 Shift+Tab
|
|
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
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
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 (
|
|
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
|
-
|
|
1715
|
-
|
|
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(
|
|
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: "
|
|
1753
|
-
handler: async (
|
|
1473
|
+
description: "打开 Plugin 推荐与管理页",
|
|
1474
|
+
handler: async (_args, ctx) => {
|
|
1754
1475
|
try {
|
|
1755
|
-
|
|
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 (
|
|
1486
|
+
handler: async (_args, ctx) => {
|
|
1796
1487
|
try {
|
|
1797
|
-
await showMcpManager(pi,
|
|
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("
|
|
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("
|
|
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: "
|
|
2299
|
+
description: "浏览 Subagent,或附带任务文本运行",
|
|
2609
2300
|
handler: async (args, ctx) => {
|
|
2610
|
-
const
|
|
2611
|
-
if (!
|
|
2301
|
+
const objective = args.trim();
|
|
2302
|
+
if (!objective) {
|
|
2612
2303
|
try {
|
|
2613
|
-
await showAgentManager(ctx,
|
|
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
|
-
|
|
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);
|