psyclaw 0.28.3 → 0.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/agents/recommended/catalog.json +1 -1
- package/dist/src/adapters/pi/extension.js +167 -80
- package/dist/src/adapters/pi/extension.js.map +1 -1
- package/dist/src/analysis/hooks.js +1 -1
- package/dist/src/analysis/hooks.js.map +1 -1
- package/dist/src/ars/doctor.d.ts +27 -0
- package/dist/src/ars/doctor.js +121 -0
- package/dist/src/ars/doctor.js.map +1 -0
- package/dist/src/ars/mode-editor.d.ts +14 -7
- package/dist/src/ars/mode-editor.js +39 -23
- package/dist/src/ars/mode-editor.js.map +1 -1
- package/dist/src/ars/profile.d.ts +10 -0
- package/dist/src/ars/profile.js +15 -2
- package/dist/src/ars/profile.js.map +1 -1
- package/dist/src/branding.js +12 -13
- package/dist/src/branding.js.map +1 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/project/bootstrap.d.ts +7 -2
- package/dist/src/project/bootstrap.js +14 -13
- package/dist/src/project/bootstrap.js.map +1 -1
- package/dist/src/project/paths.d.ts +9 -0
- package/dist/src/project/paths.js +18 -3
- package/dist/src/project/paths.js.map +1 -1
- package/dist/src/project/workspace.d.ts +14 -0
- package/dist/src/project/workspace.js +82 -0
- package/dist/src/project/workspace.js.map +1 -0
- package/dist/src/session/modes.d.ts +10 -0
- package/dist/src/session/modes.js +52 -0
- package/dist/src/session/modes.js.map +1 -0
- package/dist/src/verify/checklist.d.ts +20 -0
- package/dist/src/verify/checklist.js +71 -0
- package/dist/src/verify/checklist.js.map +1 -0
- package/package.json +1 -1
- package/vendor/ars/pi/wrapper.js +14 -5
- package/vendor/ars/pi/wrapper.test.mjs +10 -0
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.
|
|
7
|
+
当前版本:`0.29.1`。正式命令、用户配置目录和后续发布统一使用 `psyclaw`。
|
|
8
8
|
|
|
9
9
|
## 发布流程
|
|
10
10
|
|
|
@@ -21,19 +21,19 @@ RELEASE_MESSAGE="release: describe the change" pnpm release:push
|
|
|
21
21
|
需要 Node.js `>=22.19.0`。官方 npm 源:
|
|
22
22
|
|
|
23
23
|
```powershell
|
|
24
|
-
npm install -g psyclaw@0.
|
|
24
|
+
npm install -g psyclaw@0.29.1
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
如果本机 npm 配置把 registry 误写成带有 `~/` 的地址,请显式指定官方源:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
npm install -g psyclaw@0.
|
|
30
|
+
npm install -g psyclaw@0.29.1 --registry=https://registry.npmjs.org/
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
中国大陆网络较慢或无法访问官方源时:
|
|
34
34
|
|
|
35
35
|
```powershell
|
|
36
|
-
npm install -g psyclaw@0.
|
|
36
|
+
npm install -g psyclaw@0.29.1 --registry=https://registry.npmmirror.com
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
PsyClaw 的 npm 发布包直接携带 Windows x64/arm64 版 `ripgrep` 和 `fd`,Windows 启动时直接使用包内可执行文件,不再联网下载。其他平台缺少这两个工具时仍由锁定的 Pi 原生工具管理器处理:代理环境走 GitHub 官方 API 与 Release,国内 npm registry 环境统一走国内镜像。
|
|
@@ -92,7 +92,10 @@ psyclaw brief
|
|
|
92
92
|
| 创建项目能力 | `/create-skill`、`/create-hook`、`/create-rule`、`/create-subagent`(均先预览并确认) |
|
|
93
93
|
| 运行自定义只读角色 | `/agents --agent <id> <task>` 或 `/agents --agents <id,...> <task>`(最多四个隔离 worker) |
|
|
94
94
|
| 调用已加载 Skill | `/skill:<name>` |
|
|
95
|
-
|
|
|
95
|
+
| 使用模式 | `Shift+Tab`:`chat` → `analysis` → `academic`;thinking 用 `Ctrl+Shift+Tab` |
|
|
96
|
+
| 搭建仓库 | `/init`(只建目录 + `psyclaw.md`) |
|
|
97
|
+
| 人确认关键字段 | `/verify list` 或 `/verify <id> verified` |
|
|
98
|
+
| 使用 PsyClaw ARS | 切到 `academic`;也可用 `/ars doctor` / `/ars full <task>` |
|
|
96
99
|
| 管理 Plugin | `/plugin`;终端使用 `psyclaw plugin install|remove|list` |
|
|
97
100
|
| 调用已配置 MCP | 模型通过 `psyclaw_mcp` 自动发现并调用 `.psyclaw/mcp/*.json` 中启用的服务器 |
|
|
98
101
|
|
|
@@ -20,8 +20,12 @@ import { SkillManagerComponent } from "../../tui/skill-manager.js";
|
|
|
20
20
|
import { enabledLocalSkillPaths, enabledLocalPromptPaths, readUserSkillState, scanLocalSkills, setLocalSkillEnabled, setLocalSkillsEnabled, skillNamesInPaths, installLocalSkill, userSkillId, } from "../../skills/user-skills.js";
|
|
21
21
|
import { RuntimeMcpRegistry, setUserMcpConfigEnabled, } from "../../integrations/mcp-runtime.js";
|
|
22
22
|
import { SecretInputComponent, ProviderPickerComponent, } from "../../tui/provider-picker.js";
|
|
23
|
-
import { ARS_REPOSITORY_URL, ARS_UPSTREAM_COMMIT, ARS_UPSTREAM_REF, PSYCLAW_ARS_PROFILE_VERSION, detectNatureArsFillers, formatNatureArsFillerStatus, isArsPiActive, isArsPiTurn, psyclawArsPatch, } from "../../ars/profile.js";
|
|
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
|
+
import { buildArsDoctorReport, ensurePdfEngineForExport } from "../../ars/doctor.js";
|
|
25
|
+
import { arsRoot } from "../../ars/pi-panel-executor.js";
|
|
24
26
|
import { ArsModeEditor, ARS_MODE_STATUS, isArsModeEditorText } from "../../ars/mode-editor.js";
|
|
27
|
+
import { MODE_STATUS, nextSessionMode, parseSessionMode, sessionModePrompt, } from "../../session/modes.js";
|
|
28
|
+
import { formatVerifyChecklist, loadVerifyChecklist, markVerifyItem } from "../../verify/checklist.js";
|
|
25
29
|
const PARADIGMS = new Set([
|
|
26
30
|
"survey-observational",
|
|
27
31
|
"qualitative-thematic",
|
|
@@ -36,19 +40,19 @@ const PARADIGMS = new Set([
|
|
|
36
40
|
]);
|
|
37
41
|
function parseInitArgs(args) {
|
|
38
42
|
const trimmed = args.trim();
|
|
43
|
+
if (!trimmed)
|
|
44
|
+
return {};
|
|
39
45
|
const match = trimmed.match(/^--paradigm(?:=|\s+)(\S+)(?:\s+([\s\S]*))?$/i);
|
|
40
46
|
if (trimmed.startsWith("--")) {
|
|
41
47
|
const paradigm = match?.[1];
|
|
42
48
|
const goal = match?.[2]?.trim();
|
|
43
|
-
if (!match || !paradigm
|
|
44
|
-
throw new Error("Usage: /init [--paradigm survey-observational]
|
|
49
|
+
if (!match || !paradigm) {
|
|
50
|
+
throw new Error("Usage: /init [--paradigm survey-observational] [optional goal]");
|
|
45
51
|
}
|
|
46
52
|
if (!PARADIGMS.has(paradigm))
|
|
47
53
|
throw new Error(`Unsupported paradigm: ${paradigm}`);
|
|
48
|
-
return { paradigm, goal };
|
|
54
|
+
return { paradigm, ...(goal ? { goal } : {}) };
|
|
49
55
|
}
|
|
50
|
-
if (!trimmed)
|
|
51
|
-
throw new Error("Usage: /init [--paradigm survey-observational] <research goal>");
|
|
52
56
|
return { paradigm: "survey-observational", goal: trimmed };
|
|
53
57
|
}
|
|
54
58
|
async function notifyError(ctx, error) {
|
|
@@ -1120,31 +1124,35 @@ export default function psyclawExtension(pi) {
|
|
|
1120
1124
|
const runtimeMcps = new RuntimeMcpRegistry();
|
|
1121
1125
|
let arsModeEditor;
|
|
1122
1126
|
let arsUiContext;
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1127
|
+
let sessionMode = "chat";
|
|
1128
|
+
const applySessionMode = (mode, opts) => {
|
|
1129
|
+
sessionMode = mode;
|
|
1130
|
+
arsModeEditor?.setMode(mode);
|
|
1131
|
+
arsUiContext?.ui.setStatus("mode", MODE_STATUS[mode]);
|
|
1132
|
+
// Clear legacy ars status key when leaving academic.
|
|
1133
|
+
if (mode !== "academic")
|
|
1134
|
+
arsUiContext?.ui.setStatus("ars", undefined);
|
|
1135
|
+
else
|
|
1136
|
+
arsUiContext?.ui.setStatus("ars", ARS_MODE_STATUS);
|
|
1126
1137
|
if (opts?.syncSession === false || !arsUiContext)
|
|
1127
1138
|
return;
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
});
|
|
1134
|
-
}
|
|
1135
|
-
else if (!active && sessionActive) {
|
|
1136
|
-
pi.sendUserMessage("/ars-pi-stop", {
|
|
1137
|
-
...(!arsUiContext.isIdle() ? { deliverAs: "followUp" } : {}),
|
|
1138
|
-
expandPromptTemplates: true,
|
|
1139
|
-
});
|
|
1139
|
+
setArsPiSessionActive(typeof pi.appendEntry === "function"
|
|
1140
|
+
? pi.appendEntry.bind(pi)
|
|
1141
|
+
: undefined, mode === "academic");
|
|
1142
|
+
if (typeof pi.appendEntry === "function") {
|
|
1143
|
+
pi.appendEntry("psyclaw-session-mode", { mode });
|
|
1140
1144
|
}
|
|
1141
1145
|
};
|
|
1146
|
+
const cycleSessionMode = () => {
|
|
1147
|
+
const next = nextSessionMode(arsModeEditor?.getMode() ?? sessionMode);
|
|
1148
|
+
applySessionMode(next);
|
|
1149
|
+
arsUiContext?.ui.setStatus("mode", MODE_STATUS[next] ?? "chat");
|
|
1150
|
+
};
|
|
1142
1151
|
if (!legacyTestApi && typeof pi.registerShortcut === "function") {
|
|
1143
1152
|
pi.registerShortcut("shift+tab", {
|
|
1144
|
-
description: "
|
|
1153
|
+
description: "Cycle chat → analysis → academic",
|
|
1145
1154
|
handler: () => {
|
|
1146
|
-
|
|
1147
|
-
applyArsUiMode(next);
|
|
1155
|
+
cycleSessionMode();
|
|
1148
1156
|
},
|
|
1149
1157
|
});
|
|
1150
1158
|
}
|
|
@@ -1174,14 +1182,20 @@ export default function psyclawExtension(pi) {
|
|
|
1174
1182
|
});
|
|
1175
1183
|
if (!legacyTestApi && typeof pi.on === "function")
|
|
1176
1184
|
pi.on("before_agent_start", (event, ctx) => {
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
+
const modeEntry = [...(ctx?.sessionManager?.getBranch?.() ?? [])].reverse().find((entry) => entry.type === "custom" && entry.customType === "psyclaw-session-mode");
|
|
1186
|
+
const restored = parseSessionMode(modeEntry?.data?.mode)
|
|
1187
|
+
?? (isArsPiActive(ctx?.sessionManager) ? "academic" : sessionMode);
|
|
1188
|
+
sessionMode = restored;
|
|
1189
|
+
const modeBlock = sessionModePrompt(restored);
|
|
1190
|
+
if (restored === "academic" || isArsPiTurn(event.systemPrompt) || isArsPiActive(ctx?.sessionManager)) {
|
|
1191
|
+
return {
|
|
1192
|
+
systemPrompt: `${event.systemPrompt}\n\n${modeBlock}\n\n${psyclawArsPatch({
|
|
1193
|
+
skills: event.systemPromptOptions?.skills,
|
|
1194
|
+
systemPrompt: event.systemPrompt,
|
|
1195
|
+
})}`,
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
return { systemPrompt: `${event.systemPrompt}\n\n${modeBlock}` };
|
|
1185
1199
|
});
|
|
1186
1200
|
if (!legacyTestApi && typeof pi.on === "function")
|
|
1187
1201
|
pi.on("session_start", (_event, ctx) => {
|
|
@@ -1191,15 +1205,24 @@ export default function psyclawExtension(pi) {
|
|
|
1191
1205
|
ctx.ui.setEditorComponent((tui, theme, keybindings) => {
|
|
1192
1206
|
const editor = new ArsModeEditor(tui, theme, keybindings);
|
|
1193
1207
|
arsModeEditor = editor;
|
|
1194
|
-
editor.
|
|
1195
|
-
|
|
1208
|
+
editor.onModeChange = (mode) => {
|
|
1209
|
+
sessionMode = mode;
|
|
1210
|
+
ctx.ui.setStatus("mode", MODE_STATUS[mode]);
|
|
1211
|
+
if (mode === "academic")
|
|
1212
|
+
ctx.ui.setStatus("ars", ARS_MODE_STATUS);
|
|
1213
|
+
else
|
|
1214
|
+
ctx.ui.setStatus("ars", undefined);
|
|
1196
1215
|
};
|
|
1197
|
-
editor.
|
|
1198
|
-
|
|
1216
|
+
editor.onCycleMode = () => {
|
|
1217
|
+
cycleSessionMode();
|
|
1199
1218
|
};
|
|
1200
|
-
const
|
|
1201
|
-
|
|
1202
|
-
|
|
1219
|
+
const modeEntry = [...ctx.sessionManager.getBranch()].reverse().find((entry) => entry.type === "custom" && entry.customType === "psyclaw-session-mode");
|
|
1220
|
+
const restore = parseSessionMode(modeEntry?.data?.mode)
|
|
1221
|
+
?? (isArsPiActive(ctx.sessionManager) ? "academic" : "chat");
|
|
1222
|
+
editor.setMode(restore);
|
|
1223
|
+
sessionMode = restore;
|
|
1224
|
+
ctx.ui.setStatus("mode", MODE_STATUS[restore]);
|
|
1225
|
+
if (restore === "academic")
|
|
1203
1226
|
ctx.ui.setStatus("ars", ARS_MODE_STATUS);
|
|
1204
1227
|
return editor;
|
|
1205
1228
|
});
|
|
@@ -1247,18 +1270,42 @@ export default function psyclawExtension(pi) {
|
|
|
1247
1270
|
return undefined;
|
|
1248
1271
|
});
|
|
1249
1272
|
pi.registerCommand("init", {
|
|
1250
|
-
description: "
|
|
1273
|
+
description: "搭建干净工作仓库(目录 + psyclaw.md),不自动追问或启动 /run",
|
|
1251
1274
|
handler: async (args, ctx) => {
|
|
1252
1275
|
try {
|
|
1253
1276
|
const parsed = parseInitArgs(args);
|
|
1254
1277
|
const project = await bootstrapProject({ root: ctx.cwd, ...parsed });
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1278
|
+
ctx.ui.notify([
|
|
1279
|
+
`工作仓库已初始化:${project.id}`,
|
|
1280
|
+
"已创建 data/raw|clean、analysis/、literature/、paper/、psyclaw.md、.psyclaw/",
|
|
1281
|
+
"Shift+Tab:chat → analysis → academic",
|
|
1282
|
+
"澄清与分析请切到 analysis;写作审稿请切到 academic。可选 /run 加强受控工具。",
|
|
1283
|
+
].join("\n"), "info");
|
|
1284
|
+
}
|
|
1285
|
+
catch (error) {
|
|
1286
|
+
await notifyError(ctx, error);
|
|
1287
|
+
}
|
|
1288
|
+
},
|
|
1289
|
+
});
|
|
1290
|
+
pi.registerCommand("verify", {
|
|
1291
|
+
description: "人确认关键字段:/verify list | /verify <id> verified|unverified|flagged",
|
|
1292
|
+
handler: async (args, ctx) => {
|
|
1293
|
+
try {
|
|
1294
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
1295
|
+
if (parts.length === 0 || parts[0] === "list") {
|
|
1296
|
+
const checklist = await loadVerifyChecklist(ctx.cwd);
|
|
1297
|
+
ctx.ui.notify(formatVerifyChecklist(checklist), "info");
|
|
1298
|
+
return;
|
|
1258
1299
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1300
|
+
const id = parts[0];
|
|
1301
|
+
const status = (parts[1] ?? "verified");
|
|
1302
|
+
if (status !== "verified" && status !== "unverified" && status !== "flagged") {
|
|
1303
|
+
ctx.ui.notify("Usage: /verify list | /verify <id> verified|unverified|flagged [备注]", "info");
|
|
1304
|
+
return;
|
|
1261
1305
|
}
|
|
1306
|
+
const notes = parts.slice(2).join(" ") || undefined;
|
|
1307
|
+
const checklist = await markVerifyItem(ctx.cwd, id, status, notes);
|
|
1308
|
+
ctx.ui.notify(formatVerifyChecklist(checklist), "info");
|
|
1262
1309
|
}
|
|
1263
1310
|
catch (error) {
|
|
1264
1311
|
await notifyError(ctx, error);
|
|
@@ -1276,7 +1323,7 @@ export default function psyclawExtension(pi) {
|
|
|
1276
1323
|
project = await readProject(ctx.cwd);
|
|
1277
1324
|
}
|
|
1278
1325
|
catch {
|
|
1279
|
-
ctx.ui.notify("当前目录还没有通过 /init
|
|
1326
|
+
ctx.ui.notify("当前目录还没有通过 /init 建立的研究记录。可先 /init 建仓;需要更密受控工具链时再 /run。", "warning");
|
|
1280
1327
|
return;
|
|
1281
1328
|
}
|
|
1282
1329
|
const available = await selectableRunSkills(ctx.cwd);
|
|
@@ -1303,9 +1350,9 @@ export default function psyclawExtension(pi) {
|
|
|
1303
1350
|
}
|
|
1304
1351
|
},
|
|
1305
1352
|
});
|
|
1306
|
-
if (developerCommands
|
|
1307
|
-
pi.registerCommand("
|
|
1308
|
-
description: "
|
|
1353
|
+
if (developerCommands)
|
|
1354
|
+
pi.registerCommand("handoff", {
|
|
1355
|
+
description: "(开发)写入机器可读交接检查点",
|
|
1309
1356
|
handler: async (_args, ctx) => {
|
|
1310
1357
|
try {
|
|
1311
1358
|
const paths = projectPaths(ctx.cwd);
|
|
@@ -1321,7 +1368,7 @@ export default function psyclawExtension(pi) {
|
|
|
1321
1368
|
verificationCommands: ["pnpm typecheck", "pnpm test"],
|
|
1322
1369
|
generatedAt: new Date().toISOString(),
|
|
1323
1370
|
});
|
|
1324
|
-
ctx.ui.notify("Wrote notes/HANDOFF.md and
|
|
1371
|
+
ctx.ui.notify("Wrote .psyclaw/notes/HANDOFF.md and handoff.json", "info");
|
|
1325
1372
|
}
|
|
1326
1373
|
catch (error) {
|
|
1327
1374
|
await notifyError(ctx, error);
|
|
@@ -1502,47 +1549,69 @@ export default function psyclawExtension(pi) {
|
|
|
1502
1549
|
return;
|
|
1503
1550
|
}
|
|
1504
1551
|
arsUiContext = ctx;
|
|
1505
|
-
|
|
1552
|
+
applySessionMode("academic");
|
|
1506
1553
|
return;
|
|
1507
1554
|
}
|
|
1508
1555
|
if (action === "status") {
|
|
1509
1556
|
ctx.ui.notify([
|
|
1510
1557
|
`PsyClaw ARS profile v${PSYCLAW_ARS_PROFILE_VERSION}`,
|
|
1511
|
-
|
|
1512
|
-
"Thinking:Ctrl+Shift+Tab
|
|
1558
|
+
`当前模式:${arsModeEditor?.getMode() ?? sessionMode}(Shift+Tab:chat → analysis → academic)`,
|
|
1559
|
+
"Thinking:Ctrl+Shift+Tab",
|
|
1513
1560
|
`来源:${ARS_REPOSITORY_URL} @ ${ARS_UPSTREAM_REF} (${ARS_UPSTREAM_COMMIT.slice(0, 12)})`,
|
|
1514
|
-
"入口:Shift+Tab,/ars doctor,/ars start,/ars full <task>,/ars stop",
|
|
1561
|
+
"入口:Shift+Tab,/ars doctor,/ars start,/ars full <task>,/ars stop,/verify",
|
|
1515
1562
|
formatNatureArsFillerStatus(natureFillersFromCommandContext(ctx)),
|
|
1516
1563
|
].join("\n"), "info");
|
|
1517
1564
|
return;
|
|
1518
1565
|
}
|
|
1519
1566
|
if (action === "install") {
|
|
1520
|
-
ctx.ui.notify("ARS 与 Nature/compose
|
|
1567
|
+
ctx.ui.notify("ARS 与 Nature/compose 技能已内置:Shift+Tab 切到 academic。", "info");
|
|
1521
1568
|
return;
|
|
1522
1569
|
}
|
|
1523
|
-
|
|
1524
|
-
?
|
|
1525
|
-
|
|
1526
|
-
?
|
|
1527
|
-
:
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1570
|
+
if (action === "doctor") {
|
|
1571
|
+
const activeTools = typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [];
|
|
1572
|
+
const commands = typeof pi.getCommands === "function"
|
|
1573
|
+
? pi.getCommands().map((command) => command.name)
|
|
1574
|
+
: [];
|
|
1575
|
+
const toolNames = [...new Set([
|
|
1576
|
+
...activeTools.map((name) => String(name)),
|
|
1577
|
+
"psyclaw_ars_multi_agent",
|
|
1578
|
+
])];
|
|
1579
|
+
const commandNames = [...new Set([...commands.map(String), "agents", "create-subagent"])];
|
|
1580
|
+
const report = await buildArsDoctorReport({
|
|
1581
|
+
repositoryRoot: arsRoot(),
|
|
1582
|
+
activeTools: toolNames,
|
|
1583
|
+
commands: commandNames,
|
|
1584
|
+
controlledRunActive: Boolean(await readControlledRun(ctx.cwd)),
|
|
1585
|
+
});
|
|
1586
|
+
if (typeof pi.sendMessage === "function") {
|
|
1587
|
+
pi.sendMessage({ customType: "psyclaw-ars-doctor", content: report, display: true });
|
|
1588
|
+
}
|
|
1589
|
+
else {
|
|
1590
|
+
ctx.ui.notify(report, "info");
|
|
1591
|
+
}
|
|
1534
1592
|
return;
|
|
1535
1593
|
}
|
|
1536
1594
|
if (action === "start") {
|
|
1537
1595
|
arsUiContext = ctx;
|
|
1538
|
-
|
|
1596
|
+
applySessionMode("academic");
|
|
1597
|
+
ctx.ui.notify("academic mode 已开启(Shift+Tab 可切换)", "info");
|
|
1598
|
+
return;
|
|
1539
1599
|
}
|
|
1540
1600
|
if (action === "stop") {
|
|
1541
1601
|
arsUiContext = ctx;
|
|
1542
|
-
|
|
1602
|
+
applySessionMode("chat");
|
|
1543
1603
|
if (ctx.hasUI && isArsModeEditorText(ctx.ui.getEditorText?.() ?? "")) {
|
|
1544
1604
|
ctx.ui.setEditorText("");
|
|
1545
1605
|
}
|
|
1606
|
+
ctx.ui.notify("已回到 chat 模式", "info");
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
const upstreamCommand = action === "full"
|
|
1610
|
+
? `/ars-full${rest.length > 0 ? ` ${rest.join(" ")}` : ""}`
|
|
1611
|
+
: undefined;
|
|
1612
|
+
if (!upstreamCommand) {
|
|
1613
|
+
ctx.ui.notify("Usage: /ars [status|doctor|start|full <task>|stop] 或按 Shift+Tab 切换 academic mode", "info");
|
|
1614
|
+
return;
|
|
1546
1615
|
}
|
|
1547
1616
|
// A queued prompt does not get a fresh system prompt in Pi. Wait so the
|
|
1548
1617
|
// upstream wrapper can activate ARS and inject its compatibility note.
|
|
@@ -1902,7 +1971,7 @@ export default function psyclawExtension(pi) {
|
|
|
1902
1971
|
pi.registerTool({
|
|
1903
1972
|
name: "psyclaw_ars_multi_agent",
|
|
1904
1973
|
label: "ARS multi-agent review bridge",
|
|
1905
|
-
description: "Run ARS reviewer_full Stage 3 as five process-separated read-only reviewer seats, or Stage 3 re-review as three ordered fenced calls. This is a parallel-agent capability; it preserves ARS checkpoints and never claims independent error processes.",
|
|
1974
|
+
description: "Run ARS reviewer_full Stage 3 as five process-separated read-only reviewer seats, or Stage 3 re-review as three ordered fenced calls. This is a parallel-agent / multi-agent / subagent workflow capability; it preserves ARS checkpoints and never claims independent error processes.",
|
|
1906
1975
|
parameters: Type.Object({
|
|
1907
1976
|
mode: Type.Union([Type.Literal("reviewer_full"), Type.Literal("reviewer_re_review")]),
|
|
1908
1977
|
manuscriptPath: Type.Optional(Type.String()),
|
|
@@ -1941,6 +2010,21 @@ export default function psyclawExtension(pi) {
|
|
|
1941
2010
|
}
|
|
1942
2011
|
},
|
|
1943
2012
|
});
|
|
2013
|
+
pi.registerTool({
|
|
2014
|
+
name: "psyclaw_ensure_pdf_engine",
|
|
2015
|
+
label: "Ensure PDF engine",
|
|
2016
|
+
description: "Install or locate a PDF engine (tectonic/xelatex) only when the user explicitly asked to export PDF. Do not call during /ars doctor or ordinary startup checks.",
|
|
2017
|
+
parameters: Type.Object({}),
|
|
2018
|
+
executionMode: "sequential",
|
|
2019
|
+
async execute() {
|
|
2020
|
+
const result = await ensurePdfEngineForExport();
|
|
2021
|
+
return {
|
|
2022
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2023
|
+
details: result,
|
|
2024
|
+
...(result.status === "missing" || result.status === "unsupported-platform" ? { isError: true } : {}),
|
|
2025
|
+
};
|
|
2026
|
+
},
|
|
2027
|
+
});
|
|
1944
2028
|
pi.registerTool({
|
|
1945
2029
|
name: "psyclaw_create",
|
|
1946
2030
|
label: "Create project capability",
|
|
@@ -2037,21 +2121,24 @@ export default function psyclawExtension(pi) {
|
|
|
2037
2121
|
executionMode: "sequential",
|
|
2038
2122
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
2039
2123
|
try {
|
|
2124
|
+
// Soft gate: /run is optional denser tooling, not a hard block on getting results.
|
|
2125
|
+
const controlled = await readControlledRun(ctx.cwd);
|
|
2126
|
+
const softNote = controlled
|
|
2127
|
+
? undefined
|
|
2128
|
+
: "受控 /run 未启用(软门禁):继续执行;关键字段请 AI 核查并由人 /verify。";
|
|
2040
2129
|
const request = params.request.trim();
|
|
2041
|
-
if (!(await readControlledRun(ctx.cwd))) {
|
|
2042
|
-
return {
|
|
2043
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
2044
|
-
schemaVersion: "psyclaw/workbench-routing/v1",
|
|
2045
|
-
status: "general-mode",
|
|
2046
|
-
message: "PsyClaw controlled research mode is inactive. Continue as a normal conversational agent; do not impose research workflow gates. The user can run /init and then /run when they want the controlled workflow.",
|
|
2047
|
-
}, null, 2) }],
|
|
2048
|
-
details: { status: "general-mode", request },
|
|
2049
|
-
};
|
|
2050
|
-
}
|
|
2051
2130
|
const combined = `${request} ${params.identifier ?? ""}`;
|
|
2052
2131
|
const explicitWorkflow = params.workflow?.trim();
|
|
2053
2132
|
if (explicitWorkflow) {
|
|
2054
|
-
|
|
2133
|
+
const result = await runWorkflowTool(ctx.cwd, explicitWorkflow, params.target, params.nStudies);
|
|
2134
|
+
if (softNote && result && typeof result === "object" && "content" in result) {
|
|
2135
|
+
const text = Array.isArray(result.content) ? result.content.map((part) => part.text ?? "").join("\n") : "";
|
|
2136
|
+
return {
|
|
2137
|
+
...result,
|
|
2138
|
+
content: [{ type: "text", text: `${softNote}\n${text}` }],
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
return result;
|
|
2055
2142
|
}
|
|
2056
2143
|
const metaAnalysisIntent = /meta[- ]?analysis|meta分析|元分析|系统综述|系统评价|效应量|forest plot|funnel|publication bias/i.test(combined);
|
|
2057
2144
|
const publishIntent = /publish|finalize|发布|导出|定稿|投稿|export (to )?(docx|word|apa)|docx export|apa ?7/i.test(combined);
|