psyclaw 0.29.1 → 0.29.2
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 +8 -10
- package/agents/recommended/catalog.json +2 -2
- package/dist/src/adapters/pi/extension.js +151 -184
- package/dist/src/adapters/pi/extension.js.map +1 -1
- package/dist/src/analysis/plan.d.ts +82 -0
- package/dist/src/analysis/plan.js +246 -0
- package/dist/src/analysis/plan.js.map +1 -0
- package/dist/src/analysis/stats-router.d.ts +13 -0
- package/dist/src/analysis/stats-router.js +44 -0
- package/dist/src/analysis/stats-router.js.map +1 -0
- package/dist/src/ars/academic-router.d.ts +33 -0
- package/dist/src/ars/academic-router.js +204 -0
- package/dist/src/ars/academic-router.js.map +1 -0
- package/dist/src/ars/doctor.d.ts +2 -2
- package/dist/src/ars/doctor.js +4 -4
- package/dist/src/ars/doctor.js.map +1 -1
- package/dist/src/ars/profile.js +1 -1
- package/dist/src/ars/profile.js.map +1 -1
- package/dist/src/branding.js +7 -7
- package/dist/src/branding.js.map +1 -1
- package/dist/src/cli.js +1 -8
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/panel/server.js +1 -1
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/project/approvals.d.ts +4 -3
- package/dist/src/project/approvals.js +4 -3
- package/dist/src/project/approvals.js.map +1 -1
- package/dist/src/project/bootstrap.d.ts +1 -1
- package/dist/src/project/bootstrap.js +1 -1
- package/dist/src/project/paths.d.ts +1 -0
- package/dist/src/project/paths.js +2 -0
- package/dist/src/project/paths.js.map +1 -1
- package/dist/src/project/workspace.d.ts +1 -1
- package/dist/src/project/workspace.js +5 -5
- package/dist/src/project/workspace.js.map +1 -1
- package/dist/src/session/modes.js +7 -2
- 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 +3 -3
- package/dist/src/style/cli-ui.js.map +1 -1
- package/package.json +2 -1
- package/scripts/rebrand-pi.mjs +2 -2
- package/skills/core/academic-grill/SKILL.md +1 -1
- package/skills/core/analysis-plan/SKILL.md +29 -0
- package/skills/core/manifest.json +3 -3
- package/vendor/ars/pi/wrapper.js +1 -1
- package/dist/src/research/brief.d.ts +0 -11
- package/dist/src/research/brief.js +0 -81
- package/dist/src/research/brief.js.map +0 -1
- package/skills/core/research-brief/SKILL.md +0 -17
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.29.
|
|
7
|
+
当前版本:`0.29.2`。正式命令、用户配置目录和后续发布统一使用 `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.29.
|
|
24
|
+
npm install -g psyclaw@0.29.2
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
如果本机 npm 配置把 registry 误写成带有 `~/` 的地址,请显式指定官方源:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
npm install -g psyclaw@0.29.
|
|
30
|
+
npm install -g psyclaw@0.29.2 --registry=https://registry.npmjs.org/
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
中国大陆网络较慢或无法访问官方源时:
|
|
34
34
|
|
|
35
35
|
```powershell
|
|
36
|
-
npm install -g psyclaw@0.29.
|
|
36
|
+
npm install -g psyclaw@0.29.2 --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 环境统一走国内镜像。
|
|
@@ -59,13 +59,12 @@ psyclaw
|
|
|
59
59
|
|
|
60
60
|
初始化会建立 `.psyclaw/data/raw/`、`data/clean/`、`literature/pdfs/`、`notes/` 和 `outputs/`,不再把原始数据目录暴露在项目根目录。`.psyclaw/data/raw/` 是只读保护区;旧项目的 `data/raw/` 仍保持只读兼容,把经确认可用于分析的派生数据放在 `data/clean/`。
|
|
61
61
|
|
|
62
|
-
在对话界面中,`/init <研究目标>`
|
|
62
|
+
在对话界面中,`/init <研究目标>` 初始化研究项目与工作区;随后用 Shift+Tab 切换到 `analysis` 或 `academic` 推进。没有单独的 `/run` 或 `/brief` 命令。未 `/init` 时保持普通对话,不强制研究门禁。
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
登记本地材料:
|
|
65
65
|
|
|
66
66
|
```powershell
|
|
67
67
|
psyclaw evidence add notes\source.md --level user
|
|
68
|
-
psyclaw brief
|
|
69
68
|
```
|
|
70
69
|
|
|
71
70
|
## 常用入口
|
|
@@ -74,11 +73,10 @@ psyclaw brief
|
|
|
74
73
|
| --- | --- |
|
|
75
74
|
| 启动研究对话 | `psyclaw` 或 `psyclaw chat` |
|
|
76
75
|
| 续接当前项目最近一次会话 | `psyclaw --continue` 或 `psyclaw -c` |
|
|
77
|
-
| 创建研究项目 | `psyclaw init <goal> --paradigm <profile>` |
|
|
78
|
-
|
|
|
76
|
+
| 创建研究项目 | `psyclaw init <goal> --paradigm <profile>` 或对话 `/init <goal>` |
|
|
77
|
+
| 推进分析 / 写作 | Shift+Tab 切换 analysis / academic;可选 `/loop`、`/review` |
|
|
79
78
|
| 模拟同行评审 | 论文完成后在对话中输入 `/review` |
|
|
80
79
|
| 登记本地证据 | `psyclaw evidence add <path> --level user\|fulltext` |
|
|
81
|
-
| 生成离线简报 | `psyclaw brief` |
|
|
82
80
|
| 写研究移交记录 | `psyclaw handoff` |
|
|
83
81
|
| 扫描本机其他 Agent | `psyclaw agents` |
|
|
84
82
|
| 查看 PsyClaw 和内置运行时更新 | `psyclaw check-updates` |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "psyclaw/recommended-agents/v1",
|
|
3
|
-
"documentVersion": "0.29.
|
|
4
|
-
"syncedAt": "2026-09-
|
|
3
|
+
"documentVersion": "0.29.2",
|
|
4
|
+
"syncedAt": "2026-09-07",
|
|
5
5
|
"sourceDocument": "bundled literature/review subagents",
|
|
6
6
|
"sourceRef": "bundled",
|
|
7
7
|
"repositoryHint": "对齐 Claude Code:Subagent = md 提示合订本。外仓可后续以 GitHub 源加入 installPrep;当前内置 8 个无需安装。",
|
|
@@ -1,6 +1,6 @@
|
|
|
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,
|
|
3
|
+
import { appendApproval, approvalInputDigest, asProject, assertResearchDecision, bootstrapProject, projectPaths, resolveResearchDecision, runInstitutionalFulltext, runLiteratureReview, runExpertReview, runAnalysisDelegation, runWritingReview, runMetaAnalysis, createStageRunner, exportAcademicDocument, recordCitationUse, runParallelPeerReview, writeHandoff } from "../../index.js";
|
|
4
4
|
import { runPlanWithPi } from "../../orchestration/pi-executor.js";
|
|
5
5
|
import { customPersonaPlan, parseAgentsRequest } from "../../orchestration/personas.js";
|
|
6
6
|
import { elevatedEffects, formatEffects, hasElevatedEffects, normalizeEffects, toolsForEffects } from "../../orchestration/effects.js";
|
|
@@ -22,6 +22,9 @@ import { RuntimeMcpRegistry, setUserMcpConfigEnabled, } from "../../integrations
|
|
|
22
22
|
import { SecretInputComponent, ProviderPickerComponent, } from "../../tui/provider-picker.js";
|
|
23
23
|
import { ARS_REPOSITORY_URL, ARS_UPSTREAM_COMMIT, ARS_UPSTREAM_REF, PSYCLAW_ARS_PROFILE_VERSION, detectNatureArsFillers, formatNatureArsFillerStatus, isArsPiActive, isArsPiTurn, psyclawArsPatch, setArsPiSessionActive, } from "../../ars/profile.js";
|
|
24
24
|
import { buildArsDoctorReport, ensurePdfEngineForExport } from "../../ars/doctor.js";
|
|
25
|
+
import { formatAcademicSoftRouteInvocation, rankAcademicSoftRoutes, resolveAcademicSoftRoute, } from "../../ars/academic-router.js";
|
|
26
|
+
import { advanceAnalysisPlan, createAnalysisPlan, formatAnalysisPlanStatus, listAnalysisPlans, readActiveAnalysisPlan, syncHandoffFromAnalysisPlan, writeAnalysisPlan, } from "../../analysis/plan.js";
|
|
27
|
+
import { formatAnalysisPlanTakeover, resolveStatsIntent, } from "../../analysis/stats-router.js";
|
|
25
28
|
import { arsRoot } from "../../ars/pi-panel-executor.js";
|
|
26
29
|
import { ArsModeEditor, ARS_MODE_STATUS, isArsModeEditorText } from "../../ars/mode-editor.js";
|
|
27
30
|
import { MODE_STATUS, nextSessionMode, parseSessionMode, sessionModePrompt, } from "../../session/modes.js";
|
|
@@ -67,7 +70,7 @@ async function runPluginCommand(args) {
|
|
|
67
70
|
throw new Error(`不支持的 Plugin 操作:${args[0] ?? ""}`);
|
|
68
71
|
}
|
|
69
72
|
const activeAgentRuns = new Set();
|
|
70
|
-
const CORE_SKILLS = new Set(["academic-grill", "research-intake", "evidence-capture", "citation-audit", "
|
|
73
|
+
const CORE_SKILLS = new Set(["academic-grill", "research-intake", "evidence-capture", "citation-audit", "analysis-plan"]);
|
|
71
74
|
function findLastMatching(items, predicate) {
|
|
72
75
|
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
73
76
|
const item = items[index];
|
|
@@ -200,80 +203,19 @@ function academicGrillRequest(subject, mode) {
|
|
|
200
203
|
: "持久化模式:review。追问完成后先展示拟更新摘要和受影响文件,并询问我是否写回;得到明确确认前不得修改项目文档。",
|
|
201
204
|
].join("\n");
|
|
202
205
|
}
|
|
203
|
-
async function
|
|
204
|
-
return join(projectPaths(root).root, ".psyclaw", "controlled-run.json");
|
|
205
|
-
}
|
|
206
|
-
async function readControlledRun(root) {
|
|
206
|
+
async function readActiveProject(root) {
|
|
207
207
|
try {
|
|
208
|
-
const value = JSON.parse(await readFile(await controlledRunPath(root), "utf8"));
|
|
209
|
-
if (!(value.schemaVersion === "psyclaw/controlled-run/v1" && value.status === "active" && typeof value.runId === "string" && typeof value.projectId === "string" && typeof value.objective === "string" && typeof value.activatedAt === "string"))
|
|
210
|
-
return null;
|
|
211
208
|
const project = await readProject(root);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return { ...value, mode: value.mode === "auto" ? "auto" : "human", selectedSkills: Array.isArray(value.selectedSkills) ? value.selectedSkills.filter((item) => typeof item === "string") : [] };
|
|
209
|
+
return {
|
|
210
|
+
projectId: project.id,
|
|
211
|
+
runId: `project_${project.id}`,
|
|
212
|
+
objective: project.goal,
|
|
213
|
+
};
|
|
218
214
|
}
|
|
219
215
|
catch {
|
|
220
216
|
return null;
|
|
221
217
|
}
|
|
222
218
|
}
|
|
223
|
-
async function activateControlledRun(root, runId, projectId, objective, selectedSkills, mode) {
|
|
224
|
-
const state = {
|
|
225
|
-
schemaVersion: "psyclaw/controlled-run/v1",
|
|
226
|
-
runId,
|
|
227
|
-
projectId,
|
|
228
|
-
objective,
|
|
229
|
-
selectedSkills: [...new Set(selectedSkills)],
|
|
230
|
-
mode,
|
|
231
|
-
activatedAt: new Date().toISOString(),
|
|
232
|
-
status: "active",
|
|
233
|
-
};
|
|
234
|
-
await atomicWriteFile(await controlledRunPath(root), `${JSON.stringify(state, null, 2)}\n`);
|
|
235
|
-
return state;
|
|
236
|
-
}
|
|
237
|
-
function controlledRunRequest(objective, selectedSkills, mode) {
|
|
238
|
-
return [
|
|
239
|
-
`PsyClaw 受控研究流程已由用户通过 /run 明确启动${mode === "auto" ? ",本次采用连续执行方式" : ""}。先读取 .psyclaw/project.json、.psyclaw/controlled-run.json、.psyclaw/approvals.jsonl、notes/research-spec.md 和 notes/plan.md。`,
|
|
240
|
-
`本次目标:${objective}`,
|
|
241
|
-
selectedSkills.length > 0
|
|
242
|
-
? `本次运行由用户选择的优化 Skill:${selectedSkills.join(", ")}。在相关任务中优先加载并遵循这些 Skill;同名 Skill 只加载一次。`
|
|
243
|
-
: "本次运行未指定额外 Skill,使用 PsyClaw 默认研究流程。不要删除、停用或改写已安装的其他 Skill。",
|
|
244
|
-
"计划内的文件创建、脚本执行、分析委托、可恢复下载和普通工具调用连续推进,不要逐项向用户索要许可;/run 已代表用户同意这些为完成本次目标所必需且符合项目边界的操作,系统仍须记录结构化回执。凭据处理、原始数据覆盖、破坏性命令、绕过访问控制和外部发布不在该授权范围内。",
|
|
245
|
-
"人工决策只用于真正的研究取舍:存在两个以上有实质依据的合理方案,现有研究问题、数据、证据和方法规范不能确定其中之一,且不同选择会改变研究问题、样本纳入或排除、变量操作化、估计目标、分析方法或结果解释。提出决策前必须先核对可用证据与通行方法;提出时说明仍无法消解的原因、备选方案、各自依据与影响,并给出系统推荐;一次只请求一个决定。",
|
|
246
|
-
"满足上述全部条件时,先调用 psyclaw_research_decision 写入结构化研究取舍事件,再向研究者提问。该工具拒绝的事项不得改用自然语言伪装成 awaiting-human;应继续自动修复、明确报告技术限制,或按独立安全授权边界处理。",
|
|
247
|
-
"不要因软件缺失、安装配置、格式问题、脚本错误、字段可推断、统计报告遗漏、效应量或置信区间缺失、缺失值说明不足、引用格式错误或可补充的证据不足而进入人工决策。先自行安装或修复、重跑、补算、补写、检索核验或缩小表述;确实无法完成时报告具体未完成项和下一步,不把技术失败包装成研究者取舍。",
|
|
248
|
-
"实际推进当前尚未完成的研究任务;先形成或更新符合学术规范的 Markdown 分析报告,不能只描述计划。分析报告必须清楚区分数据来源、统计结果、文献证据、限制与尚未核验内容。",
|
|
249
|
-
"写作时把内部状态和校验术语留在 .psyclaw 记录中,不要把 Claim、Evidence、ledger、gate、audit、blocked、receipt 等标签直接写进面向研究者的对话、摘要或论文。中文正文避免生硬使用“审计”“结局”“依赖”“受阻”等词:分别按语境改为质量检查、结果变量或指标、所需软件或前提、无法分析。",
|
|
250
|
-
"摘要首先服从用户提供的会议或期刊格式;没有明确要求时写成一段式摘要,完整交代目的、方法、主要结果和结论,但不机械添加四个小标题。摘要只保留最关键结果,不罗列损坏列、逐列缺失数、文件哈希或实现细节。数据质量细节放在方法、附录或单独质量报告中。",
|
|
251
|
-
"若用户只提供数据集,先结合变量、研究背景和可识别的理论机制主动提出 2-4 个有研究价值且可检验的问题或假设,再推荐分析主线。不要把“是否有预注册”作为第一个或默认问题;探索性分析明确标注即可,只有用户声明为确证性研究时才核对事前分析方案,且不索要公开链接。",
|
|
252
|
-
"数据与隐私默认最小披露:对话、报告和论文先给汇总缺失率、受影响的变量组及其分析影响,不输出原始行、个体文本、标识符、完整列清单或冗长的逐列缺失信息;确需列名时只列与当前方法决策直接相关者,完整诊断另存补充材料。",
|
|
253
|
-
"参考文献按目标格式生成并逐项核对。APA 7 稿件必须做到正文引文与文末条目一一对应,区分期刊论文、会议论文、数据集、预印本和软件,核对作者顺序、年份、标题、刊名或会议名、卷期页码或文章号、DOI、标点、排序及悬挂缩进;缺少字段时不得猜测。",
|
|
254
|
-
"分析报告完成后,下一步不是直接询问是否导出 DOCX,而是询问用户是否据此撰写论文。",
|
|
255
|
-
"若用户选择撰写论文,再单独询问是否先进行文献调研,并说明该阶段可能花费较长时间;提示用户可以指定已启用的 Skill,或者选择 PsyClaw 默认的文献调研方式。不要在获得答复前自动开始长时调研。",
|
|
256
|
-
"文献调研完成并核验后,再询问是否进行全文撰写,并询问使用哪个写作 Skill;未指定时可以建议默认方案,但仍需用户确认。只有充分证据经过核验后,才能把相关主张写入论文正文。",
|
|
257
|
-
"同行评审由用户需要时另行运行 /review;不要在本轮自动审稿。/review 会再次询问使用哪个评审 Skill。最后才询问是否导出 DOCX,以及格式要求。",
|
|
258
|
-
"开放获取 PDF 自动保存到 literature/pdfs/。不得绕过付费墙;没有合法开放全文时给出 https://doi.org/<DOI> 可点击链接,提示用户通过自己的权限下载到系统给出的目标路径。论文中的每篇引用都必须完成 DOI 核验并有本地 PDF,否则用自然语言列出待补材料,并说明补齐前不宜定稿;不要向用户输出 blocked、gate 等内部状态标签。",
|
|
259
|
-
"如果用户在未完成文献调研、全文写作或评审前要求导出 DOCX,允许导出当前分析报告,但必须明确标为遵循学术规范的分析报告,不得称为论文。",
|
|
260
|
-
].join("\n");
|
|
261
|
-
}
|
|
262
|
-
function parseRunArgs(args) {
|
|
263
|
-
const value = args.trim();
|
|
264
|
-
const auto = value === "auto" || value.startsWith("auto ");
|
|
265
|
-
const rest = auto ? value.slice(4).trim() : value;
|
|
266
|
-
if (!rest.startsWith("--skills"))
|
|
267
|
-
return { objective: rest, mode: auto ? "auto" : "human" };
|
|
268
|
-
const match = rest.match(/^--skills(?:=|\s+)([^\s]+)(?:\s+([\s\S]*))?$/);
|
|
269
|
-
if (!match)
|
|
270
|
-
throw new Error("Usage: /run [auto] [--skills skill-a,skill-b] [objective]");
|
|
271
|
-
return {
|
|
272
|
-
objective: (match[2] ?? "").trim(),
|
|
273
|
-
requestedSkills: [...new Set(match[1].split(",").map((item) => item.trim()).filter(Boolean))],
|
|
274
|
-
mode: auto ? "auto" : "human",
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
219
|
function contextProjectTrusted(ctx) {
|
|
278
220
|
const candidate = ctx;
|
|
279
221
|
return typeof candidate.isProjectTrusted === "function" && candidate.isProjectTrusted();
|
|
@@ -294,41 +236,6 @@ function requiresSeparateOperationConfirmation(toolName, input) {
|
|
|
294
236
|
const text = `${toolName} ${JSON.stringify(input ?? {})}`;
|
|
295
237
|
return /submit\s+(?:manuscript|paper|form)|投稿(?:到|至)|发布到(?:外部|期刊|会议|网站)|send\s+(?:email|message)|邮件发送/i.test(text);
|
|
296
238
|
}
|
|
297
|
-
async function selectableRunSkills(root) {
|
|
298
|
-
const state = await readRecommendationState(root);
|
|
299
|
-
const rows = await skillManagerRows(root, state);
|
|
300
|
-
const available = [
|
|
301
|
-
...coreSkillNames().map((name) => ({ id: name, name })),
|
|
302
|
-
...rows.filter((row) => row.installed && row.enabled && !row.blocked).map((row) => ({ id: row.id, name: row.name })),
|
|
303
|
-
];
|
|
304
|
-
const seen = new Set();
|
|
305
|
-
return available.filter((skill) => {
|
|
306
|
-
const key = skill.name.trim().toLocaleLowerCase();
|
|
307
|
-
if (!key || seen.has(key))
|
|
308
|
-
return false;
|
|
309
|
-
seen.add(key);
|
|
310
|
-
return true;
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
function resolveRunSkills(requested, available) {
|
|
314
|
-
const lookup = new Map();
|
|
315
|
-
for (const skill of available) {
|
|
316
|
-
lookup.set(skill.id.toLocaleLowerCase(), skill.name);
|
|
317
|
-
lookup.set(skill.name.toLocaleLowerCase(), skill.name);
|
|
318
|
-
}
|
|
319
|
-
const selected = [];
|
|
320
|
-
const unknown = [];
|
|
321
|
-
for (const value of requested) {
|
|
322
|
-
const name = lookup.get(value.toLocaleLowerCase());
|
|
323
|
-
if (!name)
|
|
324
|
-
unknown.push(value);
|
|
325
|
-
else if (!selected.some((item) => item.toLocaleLowerCase() === name.toLocaleLowerCase()))
|
|
326
|
-
selected.push(name);
|
|
327
|
-
}
|
|
328
|
-
if (unknown.length > 0)
|
|
329
|
-
throw new Error(`本次运行不可用的 Skill:${unknown.join(", ")}。请先通过 /skill 安装或启用。`);
|
|
330
|
-
return selected;
|
|
331
|
-
}
|
|
332
239
|
async function recommendedItems(kind) {
|
|
333
240
|
const file = kind === "skills" ? "catalog.json" : "mcp-catalog.json";
|
|
334
241
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -1174,7 +1081,7 @@ export default function psyclawExtension(pi) {
|
|
|
1174
1081
|
pi.on("session_shutdown", () => runtimeMcps.close());
|
|
1175
1082
|
if (!legacyTestApi && typeof pi.on === "function")
|
|
1176
1083
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
1177
|
-
if (!(await
|
|
1084
|
+
if (!(await readActiveProject(ctx?.cwd ?? process.cwd())))
|
|
1178
1085
|
return;
|
|
1179
1086
|
const prompt = userRulesPrompt(await loadUserRules(ctx?.cwd ?? process.cwd()));
|
|
1180
1087
|
if (prompt)
|
|
@@ -1231,14 +1138,34 @@ export default function psyclawExtension(pi) {
|
|
|
1231
1138
|
pi.on("input", (event, ctx) => {
|
|
1232
1139
|
if (event.source === "extension")
|
|
1233
1140
|
return;
|
|
1234
|
-
if (
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1141
|
+
if (/^\/(?:login|logout)(?:\s|$)/i.test(event.text.trim())) {
|
|
1142
|
+
ctx.ui.notify("PsyClaw 已统一隐藏底层登录命令。请使用 /provider 配置、切换或更新模型凭据。", "info");
|
|
1143
|
+
return { action: "handled" };
|
|
1144
|
+
}
|
|
1145
|
+
const mode = arsModeEditor?.getMode() ?? sessionMode;
|
|
1146
|
+
if (mode === "academic") {
|
|
1147
|
+
const match = resolveAcademicSoftRoute(event.text);
|
|
1148
|
+
if (!match)
|
|
1149
|
+
return;
|
|
1150
|
+
const ranked = rankAcademicSoftRoutes(event.text, 3);
|
|
1151
|
+
return {
|
|
1152
|
+
action: "transform",
|
|
1153
|
+
text: formatAcademicSoftRouteInvocation(match, event.text, ranked.slice(1)),
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
if (mode === "analysis") {
|
|
1157
|
+
const match = resolveStatsIntent(event.text);
|
|
1158
|
+
if (!match)
|
|
1159
|
+
return;
|
|
1160
|
+
return {
|
|
1161
|
+
action: "transform",
|
|
1162
|
+
text: formatAnalysisPlanTakeover(event.text),
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1238
1165
|
});
|
|
1239
1166
|
if (!legacyTestApi && typeof pi.on === "function")
|
|
1240
1167
|
pi.on("tool_call", async (event, ctx) => {
|
|
1241
|
-
const run = await
|
|
1168
|
+
const run = await readActiveProject(ctx.cwd);
|
|
1242
1169
|
if (!run || !toolNeedsApproval(event.toolName, event.input))
|
|
1243
1170
|
return;
|
|
1244
1171
|
const summary = toolApprovalSummary(event.toolName, event.input);
|
|
@@ -1270,7 +1197,7 @@ export default function psyclawExtension(pi) {
|
|
|
1270
1197
|
return undefined;
|
|
1271
1198
|
});
|
|
1272
1199
|
pi.registerCommand("init", {
|
|
1273
|
-
description: "搭建干净工作仓库(目录 + psyclaw.md
|
|
1200
|
+
description: "搭建干净工作仓库(目录 + psyclaw.md),不自动追问",
|
|
1274
1201
|
handler: async (args, ctx) => {
|
|
1275
1202
|
try {
|
|
1276
1203
|
const parsed = parseInitArgs(args);
|
|
@@ -1279,7 +1206,7 @@ export default function psyclawExtension(pi) {
|
|
|
1279
1206
|
`工作仓库已初始化:${project.id}`,
|
|
1280
1207
|
"已创建 data/raw|clean、analysis/、literature/、paper/、psyclaw.md、.psyclaw/",
|
|
1281
1208
|
"Shift+Tab:chat → analysis → academic",
|
|
1282
|
-
"澄清与分析请切到 analysis;写作审稿请切到 academic
|
|
1209
|
+
"澄清与分析请切到 analysis;写作审稿请切到 academic。",
|
|
1283
1210
|
].join("\n"), "info");
|
|
1284
1211
|
}
|
|
1285
1212
|
catch (error) {
|
|
@@ -1312,44 +1239,100 @@ export default function psyclawExtension(pi) {
|
|
|
1312
1239
|
}
|
|
1313
1240
|
},
|
|
1314
1241
|
});
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1242
|
+
pi.registerCommand("plan", {
|
|
1243
|
+
description: "分析方案:/plan [new|status|list|confirm|review|run|defer|handoff|reject]",
|
|
1244
|
+
handler: async (args, ctx) => {
|
|
1245
|
+
try {
|
|
1246
|
+
const trimmed = args.trim();
|
|
1247
|
+
const [cmdRaw, ...rest] = trimmed.split(/\s+/).filter(Boolean);
|
|
1248
|
+
const cmd = (cmdRaw ?? "status").toLowerCase();
|
|
1249
|
+
const usage = "Usage: /plan new [目标] | status | list | confirm [方法] | review | run | defer | handoff | reject [原因]";
|
|
1250
|
+
if (cmd === "new") {
|
|
1251
|
+
const goal = rest.join(" ").trim() || "从当前对话澄清的分析目标";
|
|
1252
|
+
const plan = await writeAnalysisPlan(ctx.cwd, createAnalysisPlan({ goal }));
|
|
1253
|
+
ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n已创建。继续澄清/EDA,完成后用 /plan confirm。`, "info");
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
if (cmd === "list") {
|
|
1257
|
+
const ids = await listAnalysisPlans(ctx.cwd);
|
|
1258
|
+
ctx.ui.notify(ids.length > 0 ? `Plans:\n${ids.map((id) => `- ${id}`).join("\n")}` : "尚无 analysis/plans 记录。用 /plan new 开始。", "info");
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
let plan = await readActiveAnalysisPlan(ctx.cwd);
|
|
1262
|
+
if (!plan && cmd === "status") {
|
|
1263
|
+
ctx.ui.notify("没有活跃分析 Plan。用 /plan new [目标] 创建,或在 analysis 模式用自然语言触发 soft takeover。", "info");
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
if (!plan) {
|
|
1267
|
+
ctx.ui.notify(`没有活跃分析 Plan。\n${usage}`, "warning");
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
if (cmd === "status" || cmd === "show") {
|
|
1271
|
+
ctx.ui.notify(formatAnalysisPlanStatus(plan), "info");
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
if (cmd === "confirm") {
|
|
1275
|
+
const method = rest.join(" ").trim() || plan.primaryAnalysis || plan.proposedMethods[0] || "";
|
|
1276
|
+
if (!method) {
|
|
1277
|
+
ctx.ui.notify("请指定方法:/plan confirm <主分析方法>", "warning");
|
|
1327
1278
|
return;
|
|
1328
1279
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
kind: "run-mode",
|
|
1335
|
-
nodeId: "run-mode",
|
|
1336
|
-
decision: "approved",
|
|
1337
|
-
actor: "human",
|
|
1338
|
-
runId,
|
|
1339
|
-
sha256: approvalInputDigest({ projectId: project.id, objective, selectedSkills, mode: parsed.mode }),
|
|
1340
|
-
summary: "用户通过 /run 启动受控研究流程;计划内常规操作连续执行,仅研究取舍分歧请求决策",
|
|
1341
|
-
});
|
|
1342
|
-
await activateControlledRun(ctx.cwd, runId, project.id, objective, selectedSkills, parsed.mode);
|
|
1343
|
-
await new RunEventLog(ctx.cwd, runId).append({ type: "planned", at: new Date().toISOString(), message: objective });
|
|
1344
|
-
pi.appendEntry("psyclaw:controlled-run", { runId, projectId: project.id, objective, selectedSkills, mode: parsed.mode, activatedAt: new Date().toISOString() });
|
|
1345
|
-
pi.sendUserMessage(controlledRunRequest(objective, selectedSkills, parsed.mode), ctx.isIdle() ? {} : { deliverAs: "followUp" });
|
|
1346
|
-
ctx.ui.notify(`受控研究流程已启动${selectedSkills.length > 0 ? `;本次使用 Skill:${selectedSkills.join(", ")}` : ";使用默认 Skill"}。仅在研究分析存在需要你取舍的实质分歧时暂停询问。`, "info");
|
|
1280
|
+
plan = advanceAnalysisPlan(plan, { type: "confirm", method, backend: "local-script" });
|
|
1281
|
+
plan = advanceAnalysisPlan(plan, { type: "review" });
|
|
1282
|
+
plan = await writeAnalysisPlan(ctx.cwd, plan);
|
|
1283
|
+
ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n软确认完成并已审查。接着 /plan run 或 /plan defer。`, "info");
|
|
1284
|
+
return;
|
|
1347
1285
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1286
|
+
if (cmd === "review") {
|
|
1287
|
+
plan = advanceAnalysisPlan(plan, { type: "review" });
|
|
1288
|
+
plan = await writeAnalysisPlan(ctx.cwd, plan);
|
|
1289
|
+
ctx.ui.notify(formatAnalysisPlanStatus(plan), "info");
|
|
1290
|
+
return;
|
|
1350
1291
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1292
|
+
if (cmd === "run") {
|
|
1293
|
+
plan = advanceAnalysisPlan(plan, { type: "run-now" });
|
|
1294
|
+
plan = await writeAnalysisPlan(ctx.cwd, plan);
|
|
1295
|
+
ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n已标记立即执行:编写/运行 analysis/scripts/ 下的可复现脚本(特殊后端再用 MCP)。`, "info");
|
|
1296
|
+
pi.sendUserMessage([
|
|
1297
|
+
"/skill:analysis-plan",
|
|
1298
|
+
"",
|
|
1299
|
+
`Active plan ${plan.id} is marked running. Execute the confirmed method now with local reproducible scripts under analysis/scripts/.`,
|
|
1300
|
+
`Confirmed method: ${plan.confirmedMethod ?? plan.primaryAnalysis ?? "(see plan file)"}`,
|
|
1301
|
+
"Do not invent numbers. After results, update analysis/HANDOFF.md via /plan handoff.",
|
|
1302
|
+
].join("\n"), ctx.isIdle() ? {} : { deliverAs: "followUp" });
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
if (cmd === "defer") {
|
|
1306
|
+
plan = advanceAnalysisPlan(plan, { type: "defer" });
|
|
1307
|
+
plan = await writeAnalysisPlan(ctx.cwd, plan);
|
|
1308
|
+
ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n已推迟执行;稍后用 /plan run 继续。`, "info");
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
if (cmd === "handoff") {
|
|
1312
|
+
const path = await syncHandoffFromAnalysisPlan(ctx.cwd, plan);
|
|
1313
|
+
if (plan.status === "running") {
|
|
1314
|
+
plan = advanceAnalysisPlan(plan, plan.scriptEntrypoint
|
|
1315
|
+
? { type: "complete", scriptEntrypoint: plan.scriptEntrypoint }
|
|
1316
|
+
: { type: "complete" });
|
|
1317
|
+
plan = await writeAnalysisPlan(ctx.cwd, plan);
|
|
1318
|
+
}
|
|
1319
|
+
ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n已写入 ${path}。可切到 academic 模式。`, "info");
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
if (cmd === "reject") {
|
|
1323
|
+
const reason = rest.join(" ").trim() || "researcher rejected the proposal";
|
|
1324
|
+
plan = advanceAnalysisPlan(plan, { type: "block", reason });
|
|
1325
|
+
plan = await writeAnalysisPlan(ctx.cwd, plan);
|
|
1326
|
+
ctx.ui.notify(formatAnalysisPlanStatus(plan), "warning");
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
ctx.ui.notify(usage, "info");
|
|
1330
|
+
}
|
|
1331
|
+
catch (error) {
|
|
1332
|
+
await notifyError(ctx, error);
|
|
1333
|
+
}
|
|
1334
|
+
},
|
|
1335
|
+
});
|
|
1353
1336
|
if (developerCommands)
|
|
1354
1337
|
pi.registerCommand("handoff", {
|
|
1355
1338
|
description: "(开发)写入机器可读交接检查点",
|
|
@@ -1375,22 +1358,6 @@ export default function psyclawExtension(pi) {
|
|
|
1375
1358
|
}
|
|
1376
1359
|
},
|
|
1377
1360
|
});
|
|
1378
|
-
pi.registerCommand("brief", {
|
|
1379
|
-
description: "运行离线证据门控研究简报",
|
|
1380
|
-
handler: async (_args, ctx) => {
|
|
1381
|
-
try {
|
|
1382
|
-
if (!(await readControlledRun(ctx.cwd))) {
|
|
1383
|
-
ctx.ui.notify("当前是普通对话模式。需要生成可追溯研究简报时,请先运行 /init,再运行 /run。", "warning");
|
|
1384
|
-
return;
|
|
1385
|
-
}
|
|
1386
|
-
const result = await runOfflineBrief(ctx.cwd);
|
|
1387
|
-
ctx.ui.notify(result.verdict === "pass" ? `Brief ready: ${result.briefPath}` : "Brief blocked by evidence gates", result.verdict === "pass" ? "info" : "warning");
|
|
1388
|
-
}
|
|
1389
|
-
catch (error) {
|
|
1390
|
-
await notifyError(ctx, error);
|
|
1391
|
-
}
|
|
1392
|
-
},
|
|
1393
|
-
});
|
|
1394
1361
|
if (!legacyTestApi)
|
|
1395
1362
|
pi.registerCommand("grill", {
|
|
1396
1363
|
description: "逐题追问并压力测试学术研究方案",
|
|
@@ -1410,8 +1377,8 @@ export default function psyclawExtension(pi) {
|
|
|
1410
1377
|
}
|
|
1411
1378
|
try {
|
|
1412
1379
|
const project = await readProject(ctx.cwd);
|
|
1413
|
-
if (!(await
|
|
1414
|
-
ctx.ui.notify("请先使用 /
|
|
1380
|
+
if (!(await readActiveProject(ctx.cwd))) {
|
|
1381
|
+
ctx.ui.notify("请先使用 /init 初始化研究项目;未建仓时不会自动执行同行评审。", "warning");
|
|
1415
1382
|
return;
|
|
1416
1383
|
}
|
|
1417
1384
|
const reviewSkill = _args.trim();
|
|
@@ -1465,8 +1432,8 @@ export default function psyclawExtension(pi) {
|
|
|
1465
1432
|
ctx.ui.notify("请先使用 /init 初始化研究项目,再运行 /loop", "warning");
|
|
1466
1433
|
return;
|
|
1467
1434
|
}
|
|
1468
|
-
if (!(await
|
|
1469
|
-
ctx.ui.notify("
|
|
1435
|
+
if (!(await readActiveProject(ctx.cwd))) {
|
|
1436
|
+
ctx.ui.notify("请先使用 /init 初始化研究项目,再运行 /loop。", "warning");
|
|
1470
1437
|
return;
|
|
1471
1438
|
}
|
|
1472
1439
|
const objective = requested || project.goal;
|
|
@@ -1474,7 +1441,7 @@ export default function psyclawExtension(pi) {
|
|
|
1474
1441
|
"运行一个有边界的 PsyClaw 研究循环,采用计划、执行、检查、修正的顺序推进当前研究阶段。",
|
|
1475
1442
|
`本轮目标:${objective}`,
|
|
1476
1443
|
"开始前读取 .psyclaw/project.json、notes/goal.md 和 notes/plan.md;如果当前对话或项目中存在经研究者确认的 /grill 研究规格,以该规格细化初始计划,但不得违反系统规则、AGENTS.md 或研究门禁。",
|
|
1477
|
-
"
|
|
1444
|
+
"先识别当前最高优先级且前提已满足的一个任务,再使用可用工具实际推进。不要只描述准备做什么。普通项目文件写入、脚本和可恢复工具调用在已 /init 的项目中连续推进;原始数据覆盖、凭据、外部发布、破坏性操作仍遵守单独的安全边界。",
|
|
1478
1445
|
"每完成一个实质步骤就检查产物、证据和计划是否一致。缺失信息若能检索、计算、重跑、改写或明确标注,应先自行修复。只有存在会改变研究问题、样本处理、操作化、估计目标、方法或解释的实质分歧时才请求研究者取舍;权限边界、不可恢复错误或当前阶段完成时停止。不得为了继续循环而虚构输入或结论。",
|
|
1479
1446
|
"本次最多推进一个可验收的研究阶段。结束时简要报告已完成、已核验、阻塞、计划变化和下一步;规划内容不得表述为已有研究证据。",
|
|
1480
1447
|
].join("\n");
|
|
@@ -1581,7 +1548,7 @@ export default function psyclawExtension(pi) {
|
|
|
1581
1548
|
repositoryRoot: arsRoot(),
|
|
1582
1549
|
activeTools: toolNames,
|
|
1583
1550
|
commands: commandNames,
|
|
1584
|
-
|
|
1551
|
+
projectActive: Boolean(await readActiveProject(ctx.cwd)),
|
|
1585
1552
|
});
|
|
1586
1553
|
if (typeof pi.sendMessage === "function") {
|
|
1587
1554
|
pi.sendMessage({ customType: "psyclaw-ars-doctor", content: report, display: true });
|
|
@@ -1827,10 +1794,10 @@ export default function psyclawExtension(pi) {
|
|
|
1827
1794
|
}),
|
|
1828
1795
|
executionMode: "sequential",
|
|
1829
1796
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
1830
|
-
const run = await
|
|
1797
|
+
const run = await readActiveProject(ctx.cwd);
|
|
1831
1798
|
if (!run) {
|
|
1832
1799
|
return {
|
|
1833
|
-
content: [{ type: "text", text: "
|
|
1800
|
+
content: [{ type: "text", text: "当前目录尚未 /init,不能进入研究决策状态。请先执行 /init。" }],
|
|
1834
1801
|
details: { status: "general-mode" },
|
|
1835
1802
|
isError: true,
|
|
1836
1803
|
};
|
|
@@ -1900,9 +1867,9 @@ export default function psyclawExtension(pi) {
|
|
|
1900
1867
|
}),
|
|
1901
1868
|
executionMode: "sequential",
|
|
1902
1869
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
1903
|
-
const run = await
|
|
1870
|
+
const run = await readActiveProject(ctx.cwd);
|
|
1904
1871
|
if (!run) {
|
|
1905
|
-
return { content: [{ type: "text", text: "
|
|
1872
|
+
return { content: [{ type: "text", text: "当前目录尚未 /init,没有有效的研究项目。" }], details: { status: "general-mode" }, isError: true };
|
|
1906
1873
|
}
|
|
1907
1874
|
const eventLog = new RunEventLog(ctx.cwd, run.runId);
|
|
1908
1875
|
const events = await eventLog.snapshot();
|
|
@@ -1947,9 +1914,9 @@ export default function psyclawExtension(pi) {
|
|
|
1947
1914
|
const servers = await runtimeMcps.list(ctx.cwd, params.server);
|
|
1948
1915
|
return { content: [{ type: "text", text: JSON.stringify({ servers }, null, 2) }], details: { action: "list", servers: servers.length } };
|
|
1949
1916
|
}
|
|
1950
|
-
if (!(await
|
|
1917
|
+
if (!(await readActiveProject(ctx.cwd))) {
|
|
1951
1918
|
return {
|
|
1952
|
-
content: [{ type: "text", text: "
|
|
1919
|
+
content: [{ type: "text", text: "当前目录尚未 /init。可以查看 MCP 工具,但实际调用需先运行 /init。" }],
|
|
1953
1920
|
details: { action: "call", status: "general-mode" },
|
|
1954
1921
|
isError: true,
|
|
1955
1922
|
};
|
|
@@ -2080,7 +2047,7 @@ export default function psyclawExtension(pi) {
|
|
|
2080
2047
|
label: "Research skill",
|
|
2081
2048
|
description: "Load one trusted bundled psyclaw core skill and make its use visible to the user. Use this instead of directly reading a core SKILL.md file.",
|
|
2082
2049
|
parameters: Type.Object({
|
|
2083
|
-
name: Type.String({ description: "Core skill name: academic-grill, research-intake, evidence-capture, citation-audit, or
|
|
2050
|
+
name: Type.String({ description: "Core skill name: academic-grill, research-intake, evidence-capture, citation-audit, or analysis-plan" }),
|
|
2084
2051
|
purpose: Type.Optional(Type.String({ description: "Short user-facing reason for using this skill now" })),
|
|
2085
2052
|
}),
|
|
2086
2053
|
executionMode: "sequential",
|
|
@@ -2110,7 +2077,7 @@ export default function psyclawExtension(pi) {
|
|
|
2110
2077
|
pi.registerTool({
|
|
2111
2078
|
name: "psyclaw_workbench",
|
|
2112
2079
|
label: "Research workbench",
|
|
2113
|
-
description: "Primary psyclaw workbench for durable research state, evidence tracking, academic analysis/report contracts, separate operational authorization, and recoverable workflows. Infer the workflow from natural language, or pass an explicit `workflow` id to run it directly. Workflows: meta-analysis (real OpenAlex literature search, effect-size dataset contract at data/clean/effects.csv, R metafor delegation for REML / I² / Egger / forest — psyclaw never fabricates statistics), literature-review, analysis-delegation, writing-review, expert-review, institutional-fulltext. For paper/full-text requests call this first; for data analysis or academic reports route through research-intake -> evidence-capture -> citation-audit
|
|
2080
|
+
description: "Primary psyclaw workbench for durable research state, evidence tracking, academic analysis/report contracts, separate operational authorization, and recoverable workflows. Infer the workflow from natural language, or pass an explicit `workflow` id to run it directly. Workflows: meta-analysis (real OpenAlex literature search, effect-size dataset contract at data/clean/effects.csv, R metafor delegation for REML / I² / Egger / forest — psyclaw never fabricates statistics), literature-review, analysis-delegation, writing-review, expert-review, institutional-fulltext. For paper/full-text requests call this first; for data analysis or academic reports route through research-intake -> evidence-capture -> citation-audit and record figure/document deliverables. Use psyclaw_research_decision only for qualifying substantive research trade-offs. Do not handle credentials.",
|
|
2114
2081
|
parameters: Type.Object({
|
|
2115
2082
|
request: Type.String({ description: "The user's research task in natural language" }),
|
|
2116
2083
|
identifier: Type.Optional(Type.String({ description: "DOI, publisher URL, or exact paper title when relevant" })),
|
|
@@ -2121,11 +2088,11 @@ export default function psyclawExtension(pi) {
|
|
|
2121
2088
|
executionMode: "sequential",
|
|
2122
2089
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
2123
2090
|
try {
|
|
2124
|
-
// Soft gate: /
|
|
2125
|
-
const
|
|
2126
|
-
const softNote =
|
|
2091
|
+
// Soft gate: missing /init or verify does not hard-block results.
|
|
2092
|
+
const project = await readActiveProject(ctx.cwd);
|
|
2093
|
+
const softNote = project
|
|
2127
2094
|
? undefined
|
|
2128
|
-
: "
|
|
2095
|
+
: "尚未 /init(软门禁):继续执行;关键字段请 AI 核查并由人 /verify。建议先 /init 建仓。";
|
|
2129
2096
|
const request = params.request.trim();
|
|
2130
2097
|
const combined = `${request} ${params.identifier ?? ""}`;
|
|
2131
2098
|
const explicitWorkflow = params.workflow?.trim();
|
|
@@ -2183,7 +2150,7 @@ export default function psyclawExtension(pi) {
|
|
|
2183
2150
|
schemaVersion: "psyclaw/academic-report-plan/v1",
|
|
2184
2151
|
workflow: "academic-analysis-report",
|
|
2185
2152
|
status: "plan-required",
|
|
2186
|
-
requiredSkills: ["research-intake", "evidence-capture", "citation-audit"
|
|
2153
|
+
requiredSkills: ["research-intake", "evidence-capture", "citation-audit"],
|
|
2187
2154
|
requiredDeliverables: ["analysis-plan", "source-backed-reference-list", "publication-ready-figures-or-reproducible-plot-script", "markdown-report", "docx-report"],
|
|
2188
2155
|
documentExport: {
|
|
2189
2156
|
primary: "pandoc",
|
|
@@ -2249,9 +2216,9 @@ export default function psyclawExtension(pi) {
|
|
|
2249
2216
|
executionMode: "sequential",
|
|
2250
2217
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
2251
2218
|
try {
|
|
2252
|
-
if (!(await
|
|
2219
|
+
if (!(await readActiveProject(ctx.cwd))) {
|
|
2253
2220
|
return {
|
|
2254
|
-
content: [{ type: "text", text: "
|
|
2221
|
+
content: [{ type: "text", text: "当前目录尚未 /init;请先运行 /init,之后才会启用引用归档与论文证据门禁。" }],
|
|
2255
2222
|
details: { status: "general-mode" },
|
|
2256
2223
|
};
|
|
2257
2224
|
}
|
|
@@ -2341,7 +2308,7 @@ export default function psyclawExtension(pi) {
|
|
|
2341
2308
|
return;
|
|
2342
2309
|
}
|
|
2343
2310
|
// Do not let the runner create a partial `.psyclaw/runs` tree outside
|
|
2344
|
-
// an explicitly
|
|
2311
|
+
// an explicitly initialized `/init` research project.
|
|
2345
2312
|
try {
|
|
2346
2313
|
await readProject(ctx.cwd);
|
|
2347
2314
|
}
|