psyclaw 0.28.2 → 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 +191 -84
- 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/keybindings.d.ts +13 -0
- package/dist/src/ars/keybindings.js +50 -0
- package/dist/src/ars/keybindings.js.map +1 -0
- package/dist/src/ars/mode-editor.d.ts +17 -5
- package/dist/src/ars/mode-editor.js +55 -18
- 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/chat.js +3 -0
- package/dist/src/chat.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/panel/server.js +1 -1
- package/dist/src/panel/server.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";
|
|
24
|
-
import {
|
|
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";
|
|
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) {
|
|
@@ -1118,6 +1122,40 @@ export default function psyclawExtension(pi) {
|
|
|
1118
1122
|
const developerCommands = process.env.PSYCLAW_DEVELOPER_COMMANDS === "1";
|
|
1119
1123
|
const legacyTestApi = typeof pi.registerTool !== "function";
|
|
1120
1124
|
const runtimeMcps = new RuntimeMcpRegistry();
|
|
1125
|
+
let arsModeEditor;
|
|
1126
|
+
let arsUiContext;
|
|
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);
|
|
1137
|
+
if (opts?.syncSession === false || !arsUiContext)
|
|
1138
|
+
return;
|
|
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 });
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
const cycleSessionMode = () => {
|
|
1147
|
+
const next = nextSessionMode(arsModeEditor?.getMode() ?? sessionMode);
|
|
1148
|
+
applySessionMode(next);
|
|
1149
|
+
arsUiContext?.ui.setStatus("mode", MODE_STATUS[next] ?? "chat");
|
|
1150
|
+
};
|
|
1151
|
+
if (!legacyTestApi && typeof pi.registerShortcut === "function") {
|
|
1152
|
+
pi.registerShortcut("shift+tab", {
|
|
1153
|
+
description: "Cycle chat → analysis → academic",
|
|
1154
|
+
handler: () => {
|
|
1155
|
+
cycleSessionMode();
|
|
1156
|
+
},
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1121
1159
|
if (!legacyTestApi && typeof pi.on === "function")
|
|
1122
1160
|
pi.on("resources_discover", async (event) => {
|
|
1123
1161
|
const enabled = await enabledRecommendedSkillPaths(event.cwd);
|
|
@@ -1144,24 +1182,48 @@ export default function psyclawExtension(pi) {
|
|
|
1144
1182
|
});
|
|
1145
1183
|
if (!legacyTestApi && typeof pi.on === "function")
|
|
1146
1184
|
pi.on("before_agent_start", (event, ctx) => {
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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}` };
|
|
1155
1199
|
});
|
|
1156
1200
|
if (!legacyTestApi && typeof pi.on === "function")
|
|
1157
1201
|
pi.on("session_start", (_event, ctx) => {
|
|
1202
|
+
arsUiContext = ctx;
|
|
1158
1203
|
if (!ctx.hasUI || typeof ctx.ui.setEditorComponent !== "function")
|
|
1159
1204
|
return;
|
|
1160
1205
|
ctx.ui.setEditorComponent((tui, theme, keybindings) => {
|
|
1161
1206
|
const editor = new ArsModeEditor(tui, theme, keybindings);
|
|
1162
|
-
|
|
1163
|
-
|
|
1207
|
+
arsModeEditor = editor;
|
|
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);
|
|
1164
1215
|
};
|
|
1216
|
+
editor.onCycleMode = () => {
|
|
1217
|
+
cycleSessionMode();
|
|
1218
|
+
};
|
|
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")
|
|
1226
|
+
ctx.ui.setStatus("ars", ARS_MODE_STATUS);
|
|
1165
1227
|
return editor;
|
|
1166
1228
|
});
|
|
1167
1229
|
});
|
|
@@ -1174,21 +1236,6 @@ export default function psyclawExtension(pi) {
|
|
|
1174
1236
|
ctx.ui.notify("PsyClaw 已统一隐藏底层登录命令。请使用 /provider 配置、切换或更新模型凭据。", "info");
|
|
1175
1237
|
return { action: "handled" };
|
|
1176
1238
|
});
|
|
1177
|
-
if (!legacyTestApi && typeof pi.on === "function")
|
|
1178
|
-
pi.on("input", async (event, ctx) => {
|
|
1179
|
-
if (event.source === "extension" || isArsPiActive(ctx.sessionManager))
|
|
1180
|
-
return;
|
|
1181
|
-
const text = event.text.trim();
|
|
1182
|
-
const entersArs = isArsModeEditorText(text)
|
|
1183
|
-
|| /^\/(?:ars-[a-z0-9-]+|skill:(?:deep-research|academic-paper|academic-paper-reviewer|academic-pipeline))(?:\s|$)/i.test(text);
|
|
1184
|
-
if (!entersArs)
|
|
1185
|
-
return;
|
|
1186
|
-
if (!ctx.hasUI)
|
|
1187
|
-
return { action: "handled" };
|
|
1188
|
-
const approved = await ctx.ui.confirm("启用 ARS?", `来源:${ARS_REPOSITORY_URL} @ ${ARS_UPSTREAM_REF}\n许可:CC BY-NC 4.0,仅限非商业用途。关键研究阶段仍会请求确认。`);
|
|
1189
|
-
if (!approved)
|
|
1190
|
-
return { action: "handled" };
|
|
1191
|
-
});
|
|
1192
1239
|
if (!legacyTestApi && typeof pi.on === "function")
|
|
1193
1240
|
pi.on("tool_call", async (event, ctx) => {
|
|
1194
1241
|
const run = await readControlledRun(ctx.cwd);
|
|
@@ -1223,18 +1270,42 @@ export default function psyclawExtension(pi) {
|
|
|
1223
1270
|
return undefined;
|
|
1224
1271
|
});
|
|
1225
1272
|
pi.registerCommand("init", {
|
|
1226
|
-
description: "
|
|
1273
|
+
description: "搭建干净工作仓库(目录 + psyclaw.md),不自动追问或启动 /run",
|
|
1227
1274
|
handler: async (args, ctx) => {
|
|
1228
1275
|
try {
|
|
1229
1276
|
const parsed = parseInitArgs(args);
|
|
1230
1277
|
const project = await bootstrapProject({ root: ctx.cwd, ...parsed });
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
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;
|
|
1234
1299
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
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;
|
|
1237
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");
|
|
1238
1309
|
}
|
|
1239
1310
|
catch (error) {
|
|
1240
1311
|
await notifyError(ctx, error);
|
|
@@ -1252,7 +1323,7 @@ export default function psyclawExtension(pi) {
|
|
|
1252
1323
|
project = await readProject(ctx.cwd);
|
|
1253
1324
|
}
|
|
1254
1325
|
catch {
|
|
1255
|
-
ctx.ui.notify("当前目录还没有通过 /init
|
|
1326
|
+
ctx.ui.notify("当前目录还没有通过 /init 建立的研究记录。可先 /init 建仓;需要更密受控工具链时再 /run。", "warning");
|
|
1256
1327
|
return;
|
|
1257
1328
|
}
|
|
1258
1329
|
const available = await selectableRunSkills(ctx.cwd);
|
|
@@ -1279,9 +1350,9 @@ export default function psyclawExtension(pi) {
|
|
|
1279
1350
|
}
|
|
1280
1351
|
},
|
|
1281
1352
|
});
|
|
1282
|
-
if (developerCommands
|
|
1283
|
-
pi.registerCommand("
|
|
1284
|
-
description: "
|
|
1353
|
+
if (developerCommands)
|
|
1354
|
+
pi.registerCommand("handoff", {
|
|
1355
|
+
description: "(开发)写入机器可读交接检查点",
|
|
1285
1356
|
handler: async (_args, ctx) => {
|
|
1286
1357
|
try {
|
|
1287
1358
|
const paths = projectPaths(ctx.cwd);
|
|
@@ -1297,7 +1368,7 @@ export default function psyclawExtension(pi) {
|
|
|
1297
1368
|
verificationCommands: ["pnpm typecheck", "pnpm test"],
|
|
1298
1369
|
generatedAt: new Date().toISOString(),
|
|
1299
1370
|
});
|
|
1300
|
-
ctx.ui.notify("Wrote notes/HANDOFF.md and
|
|
1371
|
+
ctx.ui.notify("Wrote .psyclaw/notes/HANDOFF.md and handoff.json", "info");
|
|
1301
1372
|
}
|
|
1302
1373
|
catch (error) {
|
|
1303
1374
|
await notifyError(ctx, error);
|
|
@@ -1467,62 +1538,80 @@ export default function psyclawExtension(pi) {
|
|
|
1467
1538
|
});
|
|
1468
1539
|
if (!legacyTestApi)
|
|
1469
1540
|
pi.registerCommand("ars", {
|
|
1470
|
-
description: "
|
|
1541
|
+
description: "切换 academic mode,或管理 doctor/start/full/stop",
|
|
1471
1542
|
handler: async (args, ctx) => {
|
|
1472
1543
|
try {
|
|
1473
1544
|
const [action, ...rest] = args.trim().split(/\s+/).filter(Boolean);
|
|
1474
|
-
// Bare /ars →
|
|
1545
|
+
// Bare /ars → sticky academic mode (same as Shift+Tab on). No profile popup.
|
|
1475
1546
|
if (!action) {
|
|
1476
1547
|
if (!ctx.hasUI) {
|
|
1477
|
-
ctx.ui.notify("当前环境无编辑器;请改用 /ars start
|
|
1548
|
+
ctx.ui.notify("当前环境无编辑器;请改用 Shift+Tab 或 /ars start。", "info");
|
|
1478
1549
|
return;
|
|
1479
1550
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
if (!approved)
|
|
1483
|
-
return;
|
|
1484
|
-
}
|
|
1485
|
-
ctx.ui.setEditorText(enterArsModeEditorText(ctx.ui.getEditorText?.() ?? ""));
|
|
1486
|
-
ctx.ui.setStatus("ars", "ARS");
|
|
1551
|
+
arsUiContext = ctx;
|
|
1552
|
+
applySessionMode("academic");
|
|
1487
1553
|
return;
|
|
1488
1554
|
}
|
|
1489
1555
|
if (action === "status") {
|
|
1490
1556
|
ctx.ui.notify([
|
|
1491
1557
|
`PsyClaw ARS profile v${PSYCLAW_ARS_PROFILE_VERSION}`,
|
|
1492
|
-
|
|
1558
|
+
`当前模式:${arsModeEditor?.getMode() ?? sessionMode}(Shift+Tab:chat → analysis → academic)`,
|
|
1559
|
+
"Thinking:Ctrl+Shift+Tab",
|
|
1493
1560
|
`来源:${ARS_REPOSITORY_URL} @ ${ARS_UPSTREAM_REF} (${ARS_UPSTREAM_COMMIT.slice(0, 12)})`,
|
|
1494
|
-
"入口:ars
|
|
1561
|
+
"入口:Shift+Tab,/ars doctor,/ars start,/ars full <task>,/ars stop,/verify",
|
|
1495
1562
|
formatNatureArsFillerStatus(natureFillersFromCommandContext(ctx)),
|
|
1496
1563
|
].join("\n"), "info");
|
|
1497
1564
|
return;
|
|
1498
1565
|
}
|
|
1499
1566
|
if (action === "install") {
|
|
1500
|
-
ctx.ui.notify("ARS 与 Nature/compose
|
|
1567
|
+
ctx.ui.notify("ARS 与 Nature/compose 技能已内置:Shift+Tab 切到 academic。", "info");
|
|
1501
1568
|
return;
|
|
1502
1569
|
}
|
|
1503
|
-
if (
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1506
|
-
|
|
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
|
+
}
|
|
1592
|
+
return;
|
|
1507
1593
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
: action === "stop"
|
|
1513
|
-
? "/ars-pi-stop"
|
|
1514
|
-
: action === "full"
|
|
1515
|
-
? `/ars-full${rest.length > 0 ? ` ${rest.join(" ")}` : ""}`
|
|
1516
|
-
: undefined;
|
|
1517
|
-
if (!upstreamCommand) {
|
|
1518
|
-
ctx.ui.notify("Usage: /ars [status|doctor|start|full <task>|stop] 或直接输入 ars 后按 Tab", "info");
|
|
1594
|
+
if (action === "start") {
|
|
1595
|
+
arsUiContext = ctx;
|
|
1596
|
+
applySessionMode("academic");
|
|
1597
|
+
ctx.ui.notify("academic mode 已开启(Shift+Tab 可切换)", "info");
|
|
1519
1598
|
return;
|
|
1520
1599
|
}
|
|
1521
1600
|
if (action === "stop") {
|
|
1522
|
-
ctx
|
|
1601
|
+
arsUiContext = ctx;
|
|
1602
|
+
applySessionMode("chat");
|
|
1523
1603
|
if (ctx.hasUI && isArsModeEditorText(ctx.ui.getEditorText?.() ?? "")) {
|
|
1524
1604
|
ctx.ui.setEditorText("");
|
|
1525
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;
|
|
1526
1615
|
}
|
|
1527
1616
|
// A queued prompt does not get a fresh system prompt in Pi. Wait so the
|
|
1528
1617
|
// upstream wrapper can activate ARS and inject its compatibility note.
|
|
@@ -1882,7 +1971,7 @@ export default function psyclawExtension(pi) {
|
|
|
1882
1971
|
pi.registerTool({
|
|
1883
1972
|
name: "psyclaw_ars_multi_agent",
|
|
1884
1973
|
label: "ARS multi-agent review bridge",
|
|
1885
|
-
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.",
|
|
1886
1975
|
parameters: Type.Object({
|
|
1887
1976
|
mode: Type.Union([Type.Literal("reviewer_full"), Type.Literal("reviewer_re_review")]),
|
|
1888
1977
|
manuscriptPath: Type.Optional(Type.String()),
|
|
@@ -1921,6 +2010,21 @@ export default function psyclawExtension(pi) {
|
|
|
1921
2010
|
}
|
|
1922
2011
|
},
|
|
1923
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
|
+
});
|
|
1924
2028
|
pi.registerTool({
|
|
1925
2029
|
name: "psyclaw_create",
|
|
1926
2030
|
label: "Create project capability",
|
|
@@ -2017,21 +2121,24 @@ export default function psyclawExtension(pi) {
|
|
|
2017
2121
|
executionMode: "sequential",
|
|
2018
2122
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
2019
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。";
|
|
2020
2129
|
const request = params.request.trim();
|
|
2021
|
-
if (!(await readControlledRun(ctx.cwd))) {
|
|
2022
|
-
return {
|
|
2023
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
2024
|
-
schemaVersion: "psyclaw/workbench-routing/v1",
|
|
2025
|
-
status: "general-mode",
|
|
2026
|
-
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.",
|
|
2027
|
-
}, null, 2) }],
|
|
2028
|
-
details: { status: "general-mode", request },
|
|
2029
|
-
};
|
|
2030
|
-
}
|
|
2031
2130
|
const combined = `${request} ${params.identifier ?? ""}`;
|
|
2032
2131
|
const explicitWorkflow = params.workflow?.trim();
|
|
2033
2132
|
if (explicitWorkflow) {
|
|
2034
|
-
|
|
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;
|
|
2035
2142
|
}
|
|
2036
2143
|
const metaAnalysisIntent = /meta[- ]?analysis|meta分析|元分析|系统综述|系统评价|效应量|forest plot|funnel|publication bias/i.test(combined);
|
|
2037
2144
|
const publishIntent = /publish|finalize|发布|导出|定稿|投稿|export (to )?(docx|word|apa)|docx export|apa ?7/i.test(combined);
|