psyclaw 0.30.0 → 0.30.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 +22 -10
- package/dist/src/adapters/pi/extension.js +120 -42
- package/dist/src/adapters/pi/extension.js.map +1 -1
- package/dist/src/analysis/plan.d.ts +11 -0
- package/dist/src/analysis/plan.js +38 -1
- package/dist/src/analysis/plan.js.map +1 -1
- package/dist/src/analysis/stats-router.js +7 -4
- package/dist/src/analysis/stats-router.js.map +1 -1
- package/dist/src/ars/academic-router.js +6 -0
- package/dist/src/ars/academic-router.js.map +1 -1
- package/dist/src/branding.js +7 -7
- package/dist/src/branding.js.map +1 -1
- package/dist/src/cli.js +2 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/core/citations.d.ts +3 -2
- package/dist/src/core/citations.js +5 -4
- package/dist/src/core/citations.js.map +1 -1
- package/dist/src/literature/archive.d.ts +3 -1
- package/dist/src/literature/archive.js +41 -0
- package/dist/src/literature/archive.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/bootstrap.d.ts +12 -0
- package/dist/src/project/bootstrap.js +90 -2
- package/dist/src/project/bootstrap.js.map +1 -1
- package/dist/src/session/help.js +4 -4
- package/dist/src/session/help.js.map +1 -1
- package/dist/src/session/modes.js +12 -8
- package/dist/src/session/modes.js.map +1 -1
- package/dist/src/style/cli-ui.js +1 -1
- package/dist/src/style/cli-ui.js.map +1 -1
- package/dist/src/tui/skill-manager.js +35 -23
- package/dist/src/tui/skill-manager.js.map +1 -1
- package/dist/src/verify/checklist.d.ts +5 -0
- package/dist/src/verify/checklist.js +9 -1
- package/dist/src/verify/checklist.js.map +1 -1
- package/docs/PsyClaw/344/275/277/347/224/250/347/231/275/347/232/256/344/271/{246_v0.29.15.md → 246_v0.30.1.md} +80 -57
- package/docs//344/275/277/347/224/250/347/231/275/347/232/256/344/271/246.md +1 -1
- package/package.json +2 -2
- package/skills/core/academic-grill/SKILL.md +1 -1
- package/skills/core/analysis-plan/SKILL.md +12 -8
- package/skills/recommended/catalog.json +11 -2
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.30.
|
|
7
|
+
当前版本:`0.30.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.30.
|
|
24
|
+
npm install -g psyclaw@0.30.2
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
如果本机 npm 配置把 registry 误写成带有 `~/` 的地址,请显式指定官方源:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
npm install -g psyclaw@0.30.
|
|
30
|
+
npm install -g psyclaw@0.30.2 --registry=https://registry.npmjs.org/
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
中国大陆网络较慢或无法访问官方源时:
|
|
34
34
|
|
|
35
35
|
```powershell
|
|
36
|
-
npm install -g psyclaw@0.30.
|
|
36
|
+
npm install -g psyclaw@0.30.2 --registry=https://registry.npmmirror.com
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
或安装脚本:
|
|
@@ -46,7 +46,7 @@ PSYCLAW_CN=1 curl -fsSL https://exekiel179.github.io/psyclaw/install.sh | sh
|
|
|
46
46
|
```powershell
|
|
47
47
|
# Windows PowerShell
|
|
48
48
|
irm https://exekiel179.github.io/psyclaw/install.ps1 | iex
|
|
49
|
-
# 可选:$env:PSYCLAW_CN = "1";$env:PSYCLAW_VERSION = "0.30.
|
|
49
|
+
# 可选:$env:PSYCLAW_CN = "1";$env:PSYCLAW_VERSION = "0.30.2"
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
检测到国内 npm registry(如 npmmirror)、`PSYCLAW_CN=1` 或 `PSYCLAW_GITHUB_MIRROR` 时,内置路径统一走国内可用路由:
|
|
@@ -108,7 +108,7 @@ psyclaw evidence add notes\source.md --level user
|
|
|
108
108
|
| 打开科研面板 | 在对话中输入 `/panel` |
|
|
109
109
|
| 查看或切换 Provider | 在对话中输入 `/provider` 或 `/provider <id>` |
|
|
110
110
|
| 管理启动横幅宠物 | `/pet`、`/pet on`、`/pet off`(默认关闭) |
|
|
111
|
-
| 管理 Skill | `/skill
|
|
111
|
+
| 管理 Skill | `/skill`(未安装→安装并默认启用;已启用→停用) |
|
|
112
112
|
| 创建项目能力 | `/create-skill`、`/create-hook`、`/create-rule`、`/create-subagent`(附带需求文本) |
|
|
113
113
|
| 运行只读 Subagent | `/agents` 浏览,或 `/agents <task>` 运行 |
|
|
114
114
|
| 调用已加载 Skill | `/skill:<name>` |
|
|
@@ -189,13 +189,25 @@ rm -rf ~/.psyclaw
|
|
|
189
189
|
|
|
190
190
|
## 文档
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
### 使用(面向研究者)
|
|
193
|
+
|
|
194
|
+
- [使用白皮书](docs/使用白皮书.md)(当前版同步说明;带版本号的副本见 `docs/PsyClaw使用白皮书_v*.md`)
|
|
195
|
+
- [PsyClaw ARS 模式与保护边界](docs/PsyClaw-ARS模式.md)
|
|
196
|
+
- [遥测(匿名产品遥测,默认开启,可关闭)](docs/telemetry.md)
|
|
197
|
+
- [文档与交付物规范](docs/文档规范.md)(研究项目内目录约定)
|
|
198
|
+
|
|
199
|
+
### 开发(面向贡献者 / 维护者)
|
|
200
|
+
|
|
201
|
+
下列文件是仓库与实现契约,**不是**日常使用说明;贡献或改核心前再读:
|
|
202
|
+
|
|
193
203
|
- [项目范围与里程碑](docs/开工纪要.md)
|
|
194
204
|
- [架构蓝图](docs/架构蓝图.md)
|
|
195
205
|
- [评测框架](docs/评测框架.md)
|
|
196
|
-
- [
|
|
197
|
-
- [
|
|
198
|
-
- [
|
|
206
|
+
- [技能与生态准入清单](docs/技能与生态准入清单.md)
|
|
207
|
+
- [威胁模型](docs/威胁模型.md)
|
|
208
|
+
- [AGENTS.md](AGENTS.md)(贡献行为约束)
|
|
209
|
+
|
|
210
|
+
历史审计、分模块测评报告、会话复盘等仍在 `docs/` 下,按文件名识别;勿与使用白皮书混读。
|
|
199
211
|
|
|
200
212
|
## 边界
|
|
201
213
|
|
|
@@ -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, assertResearchDecision, bootstrapProject, resolveResearchDecision, runInstitutionalFulltext, runLiteratureReview, runExpertReview, runAnalysisDelegation, runWritingReview, runMetaAnalysis, createStageRunner, exportAcademicDocument, recordCitationUse, runParallelPeerReview } from "../../index.js";
|
|
3
|
+
import { appendApproval, approvalInputDigest, assertResearchDecision, bootstrapProject, ensureProjectBinding, hasCanonicalWorkspace, resolveResearchDecision, runInstitutionalFulltext, runLiteratureReview, runExpertReview, runAnalysisDelegation, runWritingReview, runMetaAnalysis, createStageRunner, exportAcademicDocument, recordCitationUse, runParallelPeerReview } from "../../index.js";
|
|
4
4
|
import { runPlanWithPi } from "../../orchestration/pi-executor.js";
|
|
5
5
|
import { formatEffects, hasElevatedEffects, normalizeEffects, toolsForEffects } from "../../orchestration/effects.js";
|
|
6
6
|
import { agentManagerRows } from "../../agents/recommended-personas.js";
|
|
@@ -32,7 +32,7 @@ import { arsRoot } from "../../ars/pi-panel-executor.js";
|
|
|
32
32
|
import { ArsModeEditor, isArsModeEditorText } from "../../ars/mode-editor.js";
|
|
33
33
|
import { MODE_STATUS, detectChatModeMismatch, formatChatModeMismatchNotice, nextSessionMode, parseSessionMode, sessionModePrompt, } from "../../session/modes.js";
|
|
34
34
|
import { continuouslyWorkWarningText, isContinuouslyWorkEnabled, } from "../../session/continuously-work.js";
|
|
35
|
-
import { assertHumanVerifyGate, formatVerifyChecklist, isNaturalPlanConfirm, ensureDefaultVerifyChecklist, loadVerifyChecklist } from "../../verify/checklist.js";
|
|
35
|
+
import { assertHumanVerifyGate, formatVerifyChecklist, isNaturalPlanConfirm, isPlanRitualConfirm, PLAN_RITUAL_PHRASE, ensureDefaultVerifyChecklist, loadVerifyChecklist } from "../../verify/checklist.js";
|
|
36
36
|
import { formatSessionHelp, formatSessionHelpBrief } from "../../session/help.js";
|
|
37
37
|
import { openResearchWorkbench } from "../../panel/workbench.js";
|
|
38
38
|
import { captureAgentError, initNodeObservability, readTelemetryPreference, shutdownObservability, trackGateWaiting, writeTelemetryPreference, } from "../../observability/index.js";
|
|
@@ -181,10 +181,10 @@ function academicGrillRequest(subject, mode) {
|
|
|
181
181
|
: "持久化模式:review。追问完成后先展示拟更新摘要和受影响文件,并询问我是否写回;得到明确确认前不得修改项目文档。",
|
|
182
182
|
].join("\n");
|
|
183
183
|
}
|
|
184
|
-
/** Lightweight ideation path
|
|
184
|
+
/** Lightweight ideation path for research directions. */
|
|
185
185
|
function academicBrainstormRequest(subject) {
|
|
186
186
|
return [
|
|
187
|
-
"这是 /brainstorm
|
|
187
|
+
"这是 /brainstorm:做选题向头脑风暴。不要加载 academic-grill,不要进入逐题压力测试或研究规格访谈。",
|
|
188
188
|
subject
|
|
189
189
|
? `头脑风暴主题:${subject}`
|
|
190
190
|
: "根据当前对话与项目材料确定头脑风暴主题;已有信息不要重复询问。",
|
|
@@ -193,12 +193,12 @@ function academicBrainstormRequest(subject) {
|
|
|
193
193
|
"2. 简要比较各自的价值、可行性、数据需求与推断边界。",
|
|
194
194
|
"3. 明确推荐其中一个,并说明推荐理由与主要取舍。",
|
|
195
195
|
"4. 最多再问 1-2 个真正影响选题的澄清问题(可一次给出);不要展开完整的设计/测量/估计量审讯。",
|
|
196
|
-
"
|
|
196
|
+
"不要执行分析、不要写论文、不要伪造文献或结果。",
|
|
197
197
|
].join("\n");
|
|
198
198
|
}
|
|
199
199
|
async function readActiveProject(root) {
|
|
200
200
|
try {
|
|
201
|
-
const project = await
|
|
201
|
+
const { project } = await ensureProjectBinding({ root });
|
|
202
202
|
return {
|
|
203
203
|
projectId: project.id,
|
|
204
204
|
runId: `project_${project.id}`,
|
|
@@ -410,17 +410,21 @@ async function skillManagerRows(root, state) {
|
|
|
410
410
|
}
|
|
411
411
|
async function mcpManagerRows(root, state, runtime) {
|
|
412
412
|
const { items, installPrep } = await recommendedItems("mcp");
|
|
413
|
+
const userEntries = await runtime.listUserConfigs(root);
|
|
414
|
+
const configuredIds = new Set(userEntries.map((entry) => entry.id));
|
|
413
415
|
const recommendedRows = items.map((item) => {
|
|
414
416
|
const id = String(item.id ?? "");
|
|
415
417
|
const plan = installPrep.find((candidate) => candidate.id === id);
|
|
416
418
|
const dependencies = Array.isArray(plan?.dependencies)
|
|
417
419
|
? plan.dependencies.filter((value) => typeof value === "string")
|
|
418
420
|
: [];
|
|
421
|
+
const installed = configuredIds.has(id);
|
|
419
422
|
return {
|
|
420
423
|
id,
|
|
421
424
|
name: String(item.name ?? id),
|
|
422
425
|
description: String(item.description ?? ""),
|
|
423
426
|
...(typeof item.sourceRef === "string" ? { sourceRef: item.sourceRef } : {}),
|
|
427
|
+
installed,
|
|
424
428
|
enabled: state.mcp.includes(id),
|
|
425
429
|
source: "recommended",
|
|
426
430
|
details: [
|
|
@@ -433,10 +437,11 @@ async function mcpManagerRows(root, state, runtime) {
|
|
|
433
437
|
// Merge user-configured MCP servers (`.psyclaw/mcp/*.json` and
|
|
434
438
|
// `~/.psyclaw/mcp/*.json`) so the management page shows them too, even when
|
|
435
439
|
// disabled. User rows win over a same-id recommended row.
|
|
436
|
-
const userRows =
|
|
440
|
+
const userRows = userEntries.map((entry) => ({
|
|
437
441
|
id: entry.id,
|
|
438
442
|
name: entry.name,
|
|
439
443
|
description: `用户配置:${entry.command}`,
|
|
444
|
+
installed: true,
|
|
440
445
|
enabled: entry.enabled,
|
|
441
446
|
source: "user",
|
|
442
447
|
userEntry: entry,
|
|
@@ -508,7 +513,7 @@ function mcpManagerItems(rows) {
|
|
|
508
513
|
id: row.id,
|
|
509
514
|
name: row.name,
|
|
510
515
|
description: row.description,
|
|
511
|
-
status: row.enabled ? "enabled" : "disabled",
|
|
516
|
+
status: row.enabled ? "enabled" : row.installed ? "disabled" : "missing",
|
|
512
517
|
configuredEnabled: row.enabled,
|
|
513
518
|
...(row.sourceRef === undefined ? {} : { sourceRef: row.sourceRef }),
|
|
514
519
|
details: row.details,
|
|
@@ -518,10 +523,10 @@ async function openMcpManager(ctx, rows) {
|
|
|
518
523
|
return ctx.ui.custom((tui, theme, keybindings, done) => (new SkillManagerComponent(mcpManagerItems(rows), tui, theme, keybindings, done, {
|
|
519
524
|
title: "MCP 管理",
|
|
520
525
|
itemLabel: "MCP",
|
|
521
|
-
footer: "↑/↓ 移动 · Space
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
526
|
+
footer: "↑/↓ 移动 · Space/Enter 安装或启停 · a 全部开启 · d 全部关闭 · Esc 关闭",
|
|
527
|
+
enabledText: "已开启;再次点击可关闭",
|
|
528
|
+
disabledText: "已安装,当前未启用;点击可开启",
|
|
529
|
+
missingMessage: "尚未安装;点击交给模型安装并默认启用",
|
|
525
530
|
})));
|
|
526
531
|
}
|
|
527
532
|
function pluginManagerItems(rows) {
|
|
@@ -643,7 +648,7 @@ function modelMcpInstallTask(root, row, plan) {
|
|
|
643
648
|
"用户已经通过 MCP 管理页明确授权本次安装。你可以直接联网、克隆或下载来源、使用所需包管理器安装依赖、执行安装命令,并写入与该 MCP 有关的项目或用户级运行配置;不要再次要求用户批准这些安装步骤。",
|
|
644
649
|
"请先阅读来源仓库的实际安装与启动说明,再根据当前操作系统和宿主环境完成可运行的 stdio/HTTP MCP 配置。目录中的版本、命令和依赖只作参考,不得因为哈希、许可证、依赖或本地数据策略检查而中止安装。",
|
|
645
650
|
"安装完成后实际启动一次服务器或执行等价的最小连通性检查,确认启动命令和工作目录有效。不要输出、复制或提交 API Key、令牌及其他凭据。",
|
|
646
|
-
|
|
651
|
+
"成功后确保 .psyclaw/recommendations.json 的 mcp 列表包含该 id(安装时已默认写入启用),说明写入了哪些配置,并提醒用户执行 /reload。若安装命令本身失败,直接诊断并修复;只有遇到必须由用户提供的凭据或外部软件许可证时才向用户说明。",
|
|
647
652
|
].join("\n");
|
|
648
653
|
}
|
|
649
654
|
async function queueModelMcpInstall(pi, ctx, row, plan) {
|
|
@@ -651,7 +656,7 @@ async function queueModelMcpInstall(pi, ctx, row, plan) {
|
|
|
651
656
|
throw new Error(`推荐 MCP 没有来源网址: ${row.id}`);
|
|
652
657
|
await setRecommendedMcpEnabled(ctx.cwd, row.id, true);
|
|
653
658
|
pi.sendUserMessage(modelMcpInstallTask(ctx.cwd, row, plan), ctx.isIdle() ? {} : { deliverAs: "followUp" });
|
|
654
|
-
ctx.ui.notify(`已将 ${row.name}
|
|
659
|
+
ctx.ui.notify(`已将 ${row.name} 的下载、安装和配置任务交给当前模型(默认启用)。完成后请执行 /reload。`, "info");
|
|
655
660
|
}
|
|
656
661
|
async function showMcpManager(pi, ctx, runtime) {
|
|
657
662
|
const catalog = await recommendedItems("mcp");
|
|
@@ -708,7 +713,7 @@ function modelSkillInstallTask(root, row, scope) {
|
|
|
708
713
|
? "这是多 Skill 套件:目标目录自身无需 SKILL.md,但其子目录必须包含一个或多个有效 SKILL.md。保留套件内共享目录和相对路径,不得包含 .git、符号链接或凭据。"
|
|
709
714
|
: "目标目录最终必须直接包含有效 SKILL.md(YAML frontmatter 至少包含 name 和 description),不得包含 .git、符号链接、凭据或二进制大文件。",
|
|
710
715
|
"如果仓库包含多个 Skill,只安装与此推荐项相符的部分;如果它不是 Skill 或无法合理适配,停止并说明原因,不要伪造 SKILL.md。",
|
|
711
|
-
"
|
|
716
|
+
"安装完成后检查目标目录结构。该项已在推荐状态中默认启用;完成后执行 /reload 即可加载,无需再手动启用。",
|
|
712
717
|
].join("\n");
|
|
713
718
|
}
|
|
714
719
|
async function chooseSkillScope(ctx) {
|
|
@@ -721,20 +726,27 @@ async function chooseSkillScope(ctx) {
|
|
|
721
726
|
return "user";
|
|
722
727
|
return undefined;
|
|
723
728
|
}
|
|
729
|
+
async function markRecommendedSkillDesired(root, requestedId, scope) {
|
|
730
|
+
const id = normalizeRecommendedSkillId(requestedId);
|
|
731
|
+
const state = await readRecommendationState(root);
|
|
732
|
+
const current = new Set(state.skills);
|
|
733
|
+
current.add(id);
|
|
734
|
+
state.skills = [...current];
|
|
735
|
+
state.skillScopes = { ...(state.skillScopes ?? {}), [id]: scope };
|
|
736
|
+
await saveRecommendationState(root, state);
|
|
737
|
+
}
|
|
724
738
|
async function queueModelSkillInstall(pi, ctx, row) {
|
|
725
739
|
if (!row.sourceRef)
|
|
726
740
|
throw new Error(`推荐 Skill 没有来源网址: ${row.id}`);
|
|
727
741
|
const scope = await chooseSkillScope(ctx);
|
|
728
742
|
if (!scope)
|
|
729
743
|
return;
|
|
730
|
-
const approved = await ctx.ui.confirm("交给当前模型安装 Skill?", `${row.name}\n来源:${row.sourceRef}\n安装位置:${skillScopeLabel(scope)}\n目标目录:${recommendedSkillTarget(ctx.cwd, row.id, scope)}\n
|
|
744
|
+
const approved = await ctx.ui.confirm("交给当前模型安装 Skill?", `${row.name}\n来源:${row.sourceRef}\n安装位置:${skillScopeLabel(scope)}\n目标目录:${recommendedSkillTarget(ctx.cwd, row.id, scope)}\n模型将检查仓库并使用文件与命令工具完成安装;安装后默认启用。`);
|
|
731
745
|
if (!approved)
|
|
732
746
|
return;
|
|
733
|
-
|
|
734
|
-
state.skillScopes = { ...(state.skillScopes ?? {}), [row.id]: scope };
|
|
735
|
-
await saveRecommendationState(ctx.cwd, state);
|
|
747
|
+
await markRecommendedSkillDesired(ctx.cwd, row.id, scope);
|
|
736
748
|
pi.sendUserMessage(modelSkillInstallTask(ctx.cwd, row, scope), ctx.isIdle() ? {} : { deliverAs: "followUp" });
|
|
737
|
-
ctx.ui.notify(`已将 ${row.name}
|
|
749
|
+
ctx.ui.notify(`已将 ${row.name} 的安装任务交给当前模型(默认启用),目标为${skillScopeLabel(scope)}。完成后请执行 /reload。`, "info");
|
|
738
750
|
}
|
|
739
751
|
async function showSkillManager(pi, ctx) {
|
|
740
752
|
if (!ctx.hasUI || typeof ctx.ui.custom !== "function") {
|
|
@@ -1049,29 +1061,72 @@ export default function psyclawExtension(pi) {
|
|
|
1049
1061
|
};
|
|
1050
1062
|
}
|
|
1051
1063
|
}
|
|
1052
|
-
//
|
|
1053
|
-
if (mode === "analysis" && isNaturalPlanConfirm(trimmed)) {
|
|
1064
|
+
// Plan confirmations: soft「可以」= per-node / method acknowledge; ritual phrase = authorize execute.
|
|
1065
|
+
if (mode === "analysis" && (isPlanRitualConfirm(trimmed) || isNaturalPlanConfirm(trimmed))) {
|
|
1054
1066
|
const plan = await readActiveAnalysisPlan(ctx.cwd);
|
|
1055
1067
|
if (plan && (plan.status === "awaiting-confirm" || plan.status === "ready" || plan.status === "reviewing")) {
|
|
1056
1068
|
const method = plan.confirmedMethod || plan.primaryAnalysis || plan.proposedMethods[0] || "按当前方案执行";
|
|
1069
|
+
const pendingChoices = (plan.analysisChoices ?? []).filter((row) => !row.selected);
|
|
1070
|
+
const ritual = isPlanRitualConfirm(trimmed);
|
|
1071
|
+
if (!ritual) {
|
|
1072
|
+
// Soft acknowledge only — never jump to run-now.
|
|
1073
|
+
let next = plan.status === "awaiting-confirm" && !plan.confirmedMethod
|
|
1074
|
+
? advanceAnalysisPlan(plan, { type: "confirm", method, backend: "local-script" })
|
|
1075
|
+
: plan;
|
|
1076
|
+
next = await writeAnalysisPlan(ctx.cwd, next);
|
|
1077
|
+
const needChoices = pendingChoices.length > 0
|
|
1078
|
+
? `仍有 ${pendingChoices.length} 项子分析待选;请先逐项确认。`
|
|
1079
|
+
: `方法已记下。要开始执行,请完整输入:「${PLAN_RITUAL_PHRASE}」`;
|
|
1080
|
+
ctx.ui.notify(`${formatAnalysisPlanStatus(next)}\n${needChoices}`, "info");
|
|
1081
|
+
return {
|
|
1082
|
+
action: "transform",
|
|
1083
|
+
text: [
|
|
1084
|
+
"/skill:analysis-plan",
|
|
1085
|
+
"",
|
|
1086
|
+
`User soft-acknowledged ("${trimmed}"). Do NOT run scripts yet.`,
|
|
1087
|
+
`Confirmed method so far: ${next.confirmedMethod ?? method}`,
|
|
1088
|
+
pendingChoices.length > 0
|
|
1089
|
+
? `Pending per-analysis choices: ${pendingChoices.map((row) => row.id).join(", ")}. Present the next undecided choice card and wait.`
|
|
1090
|
+
: `All listed choices are decided (or none yet). Ask the user to type exactly: ${PLAN_RITUAL_PHRASE}`,
|
|
1091
|
+
"Do not start /crosscheck or /verify until the user explicitly agrees to open that block.",
|
|
1092
|
+
].join("\n"),
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
if (pendingChoices.length > 0) {
|
|
1096
|
+
ctx.ui.notify(`${formatAnalysisPlanStatus(plan)}\n仪式句已收到,但还有 ${pendingChoices.length} 项子分析未确认,请先逐项选完。`, "warning");
|
|
1097
|
+
return {
|
|
1098
|
+
action: "transform",
|
|
1099
|
+
text: [
|
|
1100
|
+
"/skill:analysis-plan",
|
|
1101
|
+
"",
|
|
1102
|
+
`User typed the ritual phrase but ${pendingChoices.length} analysis choice(s) remain undecided.`,
|
|
1103
|
+
"Present the next undecided choice card and wait. Do not execute.",
|
|
1104
|
+
].join("\n"),
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1057
1107
|
let next = plan.status === "awaiting-confirm"
|
|
1058
1108
|
? advanceAnalysisPlan(plan, { type: "confirm", method, backend: "local-script" })
|
|
1059
1109
|
: plan;
|
|
1110
|
+
next = advanceAnalysisPlan(next, { type: "ritual-approve", text: PLAN_RITUAL_PHRASE });
|
|
1060
1111
|
if (next.status === "reviewing" || next.status === "awaiting-confirm") {
|
|
1061
1112
|
next = advanceAnalysisPlan(next, { type: "review" });
|
|
1062
1113
|
}
|
|
1063
1114
|
next = advanceAnalysisPlan(next, { type: "run-now" });
|
|
1064
1115
|
next = await writeAnalysisPlan(ctx.cwd, next);
|
|
1065
|
-
|
|
1116
|
+
if (next.status !== "running") {
|
|
1117
|
+
ctx.ui.notify(`${formatAnalysisPlanStatus(next)}\n未能进入执行(检查子项确认与仪式批准)。`, "warning");
|
|
1118
|
+
return { action: "handled" };
|
|
1119
|
+
}
|
|
1120
|
+
ctx.ui.notify(`${formatAnalysisPlanStatus(next)}\n已记录仪式批准并开始执行。`, "info");
|
|
1066
1121
|
return {
|
|
1067
1122
|
action: "transform",
|
|
1068
1123
|
text: [
|
|
1069
1124
|
"/skill:analysis-plan",
|
|
1070
1125
|
"",
|
|
1071
|
-
`User
|
|
1126
|
+
`User completed plan ritual ("${PLAN_RITUAL_PHRASE}"). Execute plan ${next.id} now.`,
|
|
1072
1127
|
`Confirmed method: ${next.confirmedMethod ?? method}`,
|
|
1073
|
-
next.approvalMode === "auto" ? "approvalMode=auto: label outputs as 未经人审批." : "Human
|
|
1074
|
-
"
|
|
1128
|
+
next.approvalMode === "auto" ? "approvalMode=auto: label outputs as 未经人审批." : "Human ritual approval recorded.",
|
|
1129
|
+
"Do NOT auto-start /crosscheck or /verify. After scripts finish, ask whether to open those blocks; only proceed if the user agrees.",
|
|
1075
1130
|
].join("\n"),
|
|
1076
1131
|
};
|
|
1077
1132
|
}
|
|
@@ -1079,7 +1134,11 @@ export default function psyclawExtension(pi) {
|
|
|
1079
1134
|
const wantsStats = mode === "analysis" && Boolean(resolveStatsIntent(trimmed));
|
|
1080
1135
|
const wantsAcademic = mode === "academic" && Boolean(resolveAcademicSoftRoute(trimmed));
|
|
1081
1136
|
if ((wantsStats || wantsAcademic) && !(await readActiveProject(ctx.cwd))) {
|
|
1082
|
-
if (
|
|
1137
|
+
if (await hasCanonicalWorkspace(ctx.cwd)) {
|
|
1138
|
+
// Heal binding silently — prior /init layout already present.
|
|
1139
|
+
await ensureProjectBinding({ root: ctx.cwd }).catch(() => undefined);
|
|
1140
|
+
}
|
|
1141
|
+
else if (!ctx.hasUI) {
|
|
1083
1142
|
return {
|
|
1084
1143
|
action: "transform",
|
|
1085
1144
|
text: [
|
|
@@ -1089,13 +1148,15 @@ export default function psyclawExtension(pi) {
|
|
|
1089
1148
|
].join("\n"),
|
|
1090
1149
|
};
|
|
1091
1150
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1151
|
+
else {
|
|
1152
|
+
const choice = await ctx.ui.select("尚未 /init。统计/写作建议先建仓,否则交接与核对清单不完整。", ["先执行 /init", "本次继续(稍后 init)", "取消"], { timeout: 120_000 });
|
|
1153
|
+
if (choice === "取消" || choice === undefined)
|
|
1154
|
+
return { action: "handled" };
|
|
1155
|
+
if (choice === "先执行 /init") {
|
|
1156
|
+
return { action: "transform", text: `/init\n\n(用户选择先初始化,完成后再继续:${trimmed})` };
|
|
1157
|
+
}
|
|
1158
|
+
ctx.ui.notify("继续本次请求;建议随后 /init。关键结果请做交叉核验。", "warning");
|
|
1097
1159
|
}
|
|
1098
|
-
ctx.ui.notify("继续本次请求;建议随后 /init。关键结果请做交叉核验。", "warning");
|
|
1099
1160
|
}
|
|
1100
1161
|
if (mode === "academic") {
|
|
1101
1162
|
const match = resolveAcademicSoftRoute(event.text);
|
|
@@ -1156,7 +1217,17 @@ export default function psyclawExtension(pi) {
|
|
|
1156
1217
|
handler: async (args, ctx) => {
|
|
1157
1218
|
try {
|
|
1158
1219
|
const parsed = parseInitArgs(args);
|
|
1220
|
+
const already = await hasCanonicalWorkspace(ctx.cwd);
|
|
1221
|
+
const hadProject = Boolean(await readActiveProject(ctx.cwd).catch(() => null));
|
|
1159
1222
|
const project = await bootstrapProject({ root: ctx.cwd, ...parsed });
|
|
1223
|
+
if (already || hadProject) {
|
|
1224
|
+
ctx.ui.notify([
|
|
1225
|
+
`已检测到既有工作区,无需重新建仓:${project.id}`,
|
|
1226
|
+
"规范目录与 psyclaw.md 已存在;跨会话可直接继续 analysis / academic。",
|
|
1227
|
+
"Shift+Tab:chat → analysis → academic · 输入 /help 查看速览。",
|
|
1228
|
+
].join("\n"), "info");
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1160
1231
|
ctx.ui.notify([
|
|
1161
1232
|
`工作仓库已初始化:${project.id}`,
|
|
1162
1233
|
"已创建 data/raw|clean、analysis/、literature/、paper/、psyclaw.md、.psyclaw/",
|
|
@@ -1191,9 +1262,9 @@ export default function psyclawExtension(pi) {
|
|
|
1191
1262
|
"3) 格式与报告要求:APA/三线表/必备报告字段、清单项是否齐;",
|
|
1192
1263
|
"4) 过程产物:脚本入口、中间文件、可复现痕迹是否齐全。",
|
|
1193
1264
|
"执行方式:派出至少两个独立审查视角(例如:数据一致性;引文存在性;格式/报告合同),各自给出发现后再合并为一份报告;标出一致项与冲突项。",
|
|
1194
|
-
"
|
|
1265
|
+
"不得自称已过人审。不要在用户未明确同意前自动启动本核对。人审由系统在交接/定稿门禁自动要求,经 Panel「核实」或唤醒选项完成。",
|
|
1195
1266
|
].join("\n"), ctx.isIdle() ? {} : { deliverAs: "followUp" });
|
|
1196
|
-
ctx.ui.notify(`已启动过程性 /crosscheck(焦点:${focus}
|
|
1267
|
+
ctx.ui.notify(`已启动过程性 /crosscheck(焦点:${focus})。大段核对应先征得用户同意。`, "info");
|
|
1197
1268
|
}
|
|
1198
1269
|
catch (error) {
|
|
1199
1270
|
await notifyError(ctx, error);
|
|
@@ -1320,11 +1391,11 @@ export default function psyclawExtension(pi) {
|
|
|
1320
1391
|
});
|
|
1321
1392
|
if (!legacyTestApi)
|
|
1322
1393
|
pi.registerCommand("brainstorm", {
|
|
1323
|
-
description: "
|
|
1394
|
+
description: "研究方向头脑风暴:提出并比较可选问题",
|
|
1324
1395
|
handler: async (args, ctx) => {
|
|
1325
1396
|
const subject = args.trim();
|
|
1326
1397
|
pi.sendUserMessage(academicBrainstormRequest(subject), ctx.isIdle() ? {} : { deliverAs: "followUp" });
|
|
1327
|
-
ctx.ui.notify("
|
|
1398
|
+
ctx.ui.notify("已启动研究方向头脑风暴。", "info");
|
|
1328
1399
|
},
|
|
1329
1400
|
});
|
|
1330
1401
|
if (!legacyTestApi)
|
|
@@ -2272,6 +2343,7 @@ export default function psyclawExtension(pi) {
|
|
|
2272
2343
|
reason: Type.String({ description: "One-line reason this source is cited at this position (e.g. 支持:社会支持缓冲压力假说)" }),
|
|
2273
2344
|
context: Type.String({ description: "The sentence/context in the manuscript where the citation appears" }),
|
|
2274
2345
|
section: Type.Optional(Type.String({ description: "Manuscript section, e.g. 1 引言" })),
|
|
2346
|
+
downloadOa: Type.Optional(Type.Boolean({ description: "Set true only after the researcher explicitly approved downloading the open-access PDF" })),
|
|
2275
2347
|
}),
|
|
2276
2348
|
executionMode: "sequential",
|
|
2277
2349
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -2287,8 +2359,18 @@ export default function psyclawExtension(pi) {
|
|
|
2287
2359
|
reason: params.reason,
|
|
2288
2360
|
context: params.context,
|
|
2289
2361
|
...(params.section === undefined || !params.section.trim() ? {} : { section: params.section }),
|
|
2362
|
+
...(params.downloadOa === true ? { downloadOa: true } : {}),
|
|
2290
2363
|
});
|
|
2291
2364
|
const ref = result.reference;
|
|
2365
|
+
const fulltextNext = result.record.verified && result.fulltext?.status === "downloaded"
|
|
2366
|
+
? `引用已双源核验,开放全文已保存到 ${result.fulltext.localPath}。`
|
|
2367
|
+
: result.fulltext?.status === "approval-required"
|
|
2368
|
+
? `引用已双源核验,发现开放全文候选。请先征得用户同意,再以 downloadOa=true 重新调用本工具下载到 ${result.fulltext.localPath}(候选:${result.fulltext.sourceUrl ?? "OA URL"})。`
|
|
2369
|
+
: result.fulltext?.status === "manual-download-required"
|
|
2370
|
+
? `该引用的题录已登记,但未找到开放 PDF。请通过 ${result.fulltext.doiUrl} 使用你的合法访问权限下载,并保存到 ${result.fulltext.localPath};完成前不得将依赖全文的主张写入正文。`
|
|
2371
|
+
: result.fulltext?.status === "download-failed"
|
|
2372
|
+
? `开放全文下载失败:${result.fulltext.reason ?? "未知原因"}。请通过 ${result.fulltext.doiUrl} 自行合法获取并保存到 ${result.fulltext.localPath}。`
|
|
2373
|
+
: "该 DOI 或全文未能完成核验;请在最终交付前补核验或替换来源。";
|
|
2292
2374
|
return {
|
|
2293
2375
|
content: [{ type: "text", text: JSON.stringify({
|
|
2294
2376
|
schemaVersion: "psyclaw/citation-use/v1",
|
|
@@ -2299,11 +2381,7 @@ export default function psyclawExtension(pi) {
|
|
|
2299
2381
|
referenceTitle: ref?.title ?? null,
|
|
2300
2382
|
archived: ref !== null,
|
|
2301
2383
|
fulltext: result.fulltext,
|
|
2302
|
-
next:
|
|
2303
|
-
? `引用已双源核验,开放全文已保存到 ${result.fulltext.localPath}。`
|
|
2304
|
-
: result.fulltext?.status === "manual-download-required"
|
|
2305
|
-
? `该引用的题录已登记,但未找到开放 PDF。请通过 ${result.fulltext.doiUrl} 使用你的合法访问权限下载,并保存到 ${result.fulltext.localPath};完成前不得将依赖全文的主张写入正文。`
|
|
2306
|
-
: "该 DOI 或全文未能完成核验;请在最终交付前补核验或替换来源。",
|
|
2384
|
+
next: fulltextNext,
|
|
2307
2385
|
}, null, 2) }],
|
|
2308
2386
|
details: { citationId: result.record.citationId, doi: result.record.doi, verified: result.record.verified },
|
|
2309
2387
|
};
|