psyclaw 0.27.4 → 0.27.6
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 +11 -5
- package/dist/apps/panel/index.html +78 -3449
- package/dist/src/adapters/pi/extension.js +388 -77
- package/dist/src/adapters/pi/extension.js.map +1 -1
- package/dist/src/adapters/pi/rpc.js +7 -4
- package/dist/src/adapters/pi/rpc.js.map +1 -1
- package/dist/src/branding.js +6 -4
- package/dist/src/branding.js.map +1 -1
- package/dist/src/chat.js +2 -2
- package/dist/src/chat.js.map +1 -1
- package/dist/src/core/citations.d.ts +2 -0
- package/dist/src/core/citations.js +4 -1
- package/dist/src/core/citations.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/integrations/mcp-runtime.d.ts +13 -0
- package/dist/src/integrations/mcp-runtime.js +106 -0
- package/dist/src/integrations/mcp-runtime.js.map +1 -0
- package/dist/src/literature/archive.d.ts +30 -0
- package/dist/src/literature/archive.js +133 -0
- package/dist/src/literature/archive.js.map +1 -0
- package/dist/src/orchestration/research-agents.d.ts +32 -0
- package/dist/src/orchestration/research-agents.js +225 -0
- package/dist/src/orchestration/research-agents.js.map +1 -0
- package/dist/src/panel/extension.js +0 -52
- package/dist/src/panel/extension.js.map +1 -1
- package/dist/src/panel/server.js +47 -65
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/project/hitl.js +2 -0
- package/dist/src/project/hitl.js.map +1 -1
- package/dist/src/project/paths.d.ts +2 -6
- package/dist/src/project/paths.js +4 -12
- package/dist/src/project/paths.js.map +1 -1
- package/dist/src/skills/recommended.d.ts +1 -0
- package/dist/src/skills/recommended.js +40 -10
- package/dist/src/skills/recommended.js.map +1 -1
- package/dist/src/telemetry/export.d.ts +32 -0
- package/dist/src/telemetry/export.js +102 -1
- package/dist/src/telemetry/export.js.map +1 -1
- package/dist/src/tui/provider-picker.d.ts +48 -0
- package/dist/src/tui/provider-picker.js +115 -0
- package/dist/src/tui/provider-picker.js.map +1 -0
- package/dist/src/workflows/academic-export.d.ts +14 -0
- package/dist/src/workflows/academic-export.js +42 -0
- package/dist/src/workflows/academic-export.js.map +1 -0
- package/dist/src/workflows/meta-analysis.d.ts +1 -0
- package/dist/src/workflows/meta-analysis.js +17 -9
- package/dist/src/workflows/meta-analysis.js.map +1 -1
- package/dist/src/workflows/publish.js +14 -0
- package/dist/src/workflows/publish.js.map +1 -1
- package/package.json +1 -1
- package/skills/core/academic-grill/SKILL.md +77 -0
- package/skills/core/manifest.json +3 -2
- package/skills/recommended/catalog.json +56 -9
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { McpClient, StdioMcpTransport } from "./mcp.js";
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function runtimeEnvironment(value) {
|
|
9
|
+
const configured = isRecord(value)
|
|
10
|
+
? Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"))
|
|
11
|
+
: {};
|
|
12
|
+
return {
|
|
13
|
+
...(process.env.PATH ? { PATH: process.env.PATH } : {}),
|
|
14
|
+
...(process.env.HOME ? { HOME: process.env.HOME } : {}),
|
|
15
|
+
...(process.env.TMPDIR ? { TMPDIR: process.env.TMPDIR } : {}),
|
|
16
|
+
...(process.env.LANG ? { LANG: process.env.LANG } : {}),
|
|
17
|
+
...configured,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function asRuntimeConfig(value) {
|
|
21
|
+
if (!isRecord(value) || typeof value.id !== "string" || typeof value.command !== "string")
|
|
22
|
+
return undefined;
|
|
23
|
+
if (value.enabled === false || value.trusted === false || (value.transport !== undefined && value.transport !== "stdio"))
|
|
24
|
+
return undefined;
|
|
25
|
+
const args = Array.isArray(value.args) && value.args.every((item) => typeof item === "string") ? value.args : [];
|
|
26
|
+
return {
|
|
27
|
+
id: value.id,
|
|
28
|
+
command: value.command,
|
|
29
|
+
args,
|
|
30
|
+
env: runtimeEnvironment(value.env),
|
|
31
|
+
trusted: true,
|
|
32
|
+
enabled: true,
|
|
33
|
+
...(typeof value.name === "string" ? { name: value.name } : {}),
|
|
34
|
+
transport: "stdio",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async function configsIn(directory) {
|
|
38
|
+
let names;
|
|
39
|
+
try {
|
|
40
|
+
names = (await readdir(directory)).filter((name) => name.endsWith(".json")).sort();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
const configs = [];
|
|
46
|
+
for (const name of names) {
|
|
47
|
+
try {
|
|
48
|
+
const config = asRuntimeConfig(JSON.parse(await readFile(join(directory, name), "utf8")));
|
|
49
|
+
if (config)
|
|
50
|
+
configs.push(config);
|
|
51
|
+
}
|
|
52
|
+
catch { /* Ignore malformed entries; they remain visible in MCP management. */ }
|
|
53
|
+
}
|
|
54
|
+
return configs;
|
|
55
|
+
}
|
|
56
|
+
export class RuntimeMcpRegistry {
|
|
57
|
+
connections = new Map();
|
|
58
|
+
async list(root, serverId) {
|
|
59
|
+
const configs = await this.configs(root);
|
|
60
|
+
const selected = serverId ? configs.filter((config) => config.id === serverId) : configs;
|
|
61
|
+
if (serverId && selected.length === 0)
|
|
62
|
+
throw new Error(`MCP server is not configured or enabled: ${serverId}`);
|
|
63
|
+
return Promise.all(selected.map(async (config) => {
|
|
64
|
+
const connection = await this.connection(root, config);
|
|
65
|
+
connection.tools ??= await connection.client.listTools();
|
|
66
|
+
return { serverId: config.id, serverName: config.name ?? config.id, tools: connection.tools };
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
async call(root, serverId, tool, input) {
|
|
70
|
+
const config = (await this.configs(root)).find((candidate) => candidate.id === serverId);
|
|
71
|
+
if (!config)
|
|
72
|
+
throw new Error(`MCP server is not configured or enabled: ${serverId}`);
|
|
73
|
+
const connection = await this.connection(root, config);
|
|
74
|
+
connection.tools ??= await connection.client.listTools();
|
|
75
|
+
if (!connection.tools.some((candidate) => candidate.name === tool))
|
|
76
|
+
throw new Error(`MCP tool is not available: ${serverId}.${tool}`);
|
|
77
|
+
return connection.client.callTool(tool, input);
|
|
78
|
+
}
|
|
79
|
+
close() {
|
|
80
|
+
for (const connection of this.connections.values())
|
|
81
|
+
connection.client.close();
|
|
82
|
+
this.connections.clear();
|
|
83
|
+
}
|
|
84
|
+
async configs(root) {
|
|
85
|
+
const user = await configsIn(join(homedir(), ".psyclaw", "mcp"));
|
|
86
|
+
const project = await configsIn(join(root, ".psyclaw", "mcp"));
|
|
87
|
+
const merged = new Map(user.map((config) => [config.id, config]));
|
|
88
|
+
for (const config of project)
|
|
89
|
+
merged.set(config.id, config);
|
|
90
|
+
return [...merged.values()];
|
|
91
|
+
}
|
|
92
|
+
async connection(root, config) {
|
|
93
|
+
const key = `${root}\u0000${config.id}\u0000${config.command}\u0000${JSON.stringify(config.args)}`;
|
|
94
|
+
const existing = this.connections.get(key);
|
|
95
|
+
if (existing)
|
|
96
|
+
return existing;
|
|
97
|
+
const transport = new StdioMcpTransport(config);
|
|
98
|
+
const client = new McpClient(transport);
|
|
99
|
+
await client.initialize({ name: "psyclaw", version: process.env.PSYCLAW_VERSION ?? "development" });
|
|
100
|
+
client.initialized();
|
|
101
|
+
const connection = { config, transport, client };
|
|
102
|
+
this.connections.set(key, connection);
|
|
103
|
+
return connection;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=mcp-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-runtime.js","sourceRoot":"","sources":["../../../src/integrations/mcp-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type ReferenceFulltextStatus = "downloaded" | "manual-download-required" | "download-failed";
|
|
2
|
+
export interface ReferenceFulltextRecord {
|
|
3
|
+
schemaVersion: "psyclaw/reference-fulltext/v1";
|
|
4
|
+
doi: string;
|
|
5
|
+
doiUrl: string;
|
|
6
|
+
status: ReferenceFulltextStatus;
|
|
7
|
+
localPath: string;
|
|
8
|
+
sourceUrl?: string;
|
|
9
|
+
reason?: string;
|
|
10
|
+
checkedAt: string;
|
|
11
|
+
}
|
|
12
|
+
export interface CitationFulltextAudit {
|
|
13
|
+
schemaVersion: "psyclaw/citation-fulltext-audit/v1";
|
|
14
|
+
ok: boolean;
|
|
15
|
+
citedDois: string[];
|
|
16
|
+
verifiedDois: string[];
|
|
17
|
+
downloadedDois: string[];
|
|
18
|
+
missingVerification: string[];
|
|
19
|
+
missingPdfs: Array<{
|
|
20
|
+
doi: string;
|
|
21
|
+
doiUrl: string;
|
|
22
|
+
expectedPath: string;
|
|
23
|
+
reason?: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export declare function referencePdfPath(doi: string): string;
|
|
27
|
+
/** Download only a verified open-access location. Paywalled items stay as a DOI link and an exact local target. */
|
|
28
|
+
export declare function archiveOpenAccessPdf(root: string, doi: string, fetchFn?: typeof fetch): Promise<ReferenceFulltextRecord>;
|
|
29
|
+
/** Final manuscript gate: every cited DOI must be metadata-verified and backed by a recognizable local PDF. */
|
|
30
|
+
export declare function auditCitationFulltexts(root: string): Promise<CitationFulltextAudit>;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { sha256Text } from "../core/hash.js";
|
|
3
|
+
import { sniffKindFromBuffer } from "../core/filekind.js";
|
|
4
|
+
import { lookupOaPdfUrl } from "../core/doi.js";
|
|
5
|
+
import { atomicWriteFile, readJsonl } from "../project/jsonl.js";
|
|
6
|
+
import { assertSafeProjectPath, projectPaths } from "../project/paths.js";
|
|
7
|
+
function normalizedDoi(value) {
|
|
8
|
+
return value.trim().replace(/^https?:\/\/(?:dx\.)?doi\.org\//i, "").toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
export function referencePdfPath(doi) {
|
|
11
|
+
return `literature/pdfs/doi-${sha256Text(normalizedDoi(doi)).slice(0, 20)}.pdf`;
|
|
12
|
+
}
|
|
13
|
+
function recordPath(doi) {
|
|
14
|
+
return `.psyclaw/fulltexts/${sha256Text(normalizedDoi(doi)).slice(0, 20)}.json`;
|
|
15
|
+
}
|
|
16
|
+
async function saveRecord(root, record) {
|
|
17
|
+
await atomicWriteFile(await assertSafeProjectPath(root, recordPath(record.doi)), `${JSON.stringify(record, null, 2)}\n`);
|
|
18
|
+
}
|
|
19
|
+
async function readRecord(root, doi) {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(await readFile(await assertSafeProjectPath(root, recordPath(doi)), "utf8"));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function isPdf(root, relativePath) {
|
|
28
|
+
try {
|
|
29
|
+
const bytes = await readFile(await assertSafeProjectPath(root, relativePath));
|
|
30
|
+
return sniffKindFromBuffer(bytes.subarray(0, 512)) === "pdf";
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Download only a verified open-access location. Paywalled items stay as a DOI link and an exact local target. */
|
|
37
|
+
export async function archiveOpenAccessPdf(root, doi, fetchFn = fetch) {
|
|
38
|
+
const normalized = normalizedDoi(doi);
|
|
39
|
+
if (!/^10\.\d{4,9}\/\S+$/i.test(normalized))
|
|
40
|
+
throw new Error(`Invalid DOI: ${doi}`);
|
|
41
|
+
const checkedAt = new Date().toISOString();
|
|
42
|
+
const doiUrl = `https://doi.org/${normalized}`;
|
|
43
|
+
const localPath = referencePdfPath(normalized);
|
|
44
|
+
await mkdir(projectPaths(root).literaturePdfs, { recursive: true });
|
|
45
|
+
if (await isPdf(root, localPath)) {
|
|
46
|
+
const record = { schemaVersion: "psyclaw/reference-fulltext/v1", doi: normalized, doiUrl, status: "downloaded", localPath, checkedAt };
|
|
47
|
+
await saveRecord(root, record);
|
|
48
|
+
return record;
|
|
49
|
+
}
|
|
50
|
+
const lookup = await lookupOaPdfUrl(normalized, fetchFn);
|
|
51
|
+
if (!lookup.oaPdfUrl || !/^https?:\/\//i.test(lookup.oaPdfUrl)) {
|
|
52
|
+
const record = {
|
|
53
|
+
schemaVersion: "psyclaw/reference-fulltext/v1",
|
|
54
|
+
doi: normalized,
|
|
55
|
+
doiUrl,
|
|
56
|
+
status: "manual-download-required",
|
|
57
|
+
localPath,
|
|
58
|
+
reason: "No verified open-access PDF was found. Use lawful personal or institutional access; PsyClaw will not bypass a paywall.",
|
|
59
|
+
checkedAt,
|
|
60
|
+
};
|
|
61
|
+
await saveRecord(root, record);
|
|
62
|
+
return record;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetchFn(lookup.oaPdfUrl, { redirect: "follow", signal: AbortSignal.timeout(30_000) });
|
|
66
|
+
if (!response.ok)
|
|
67
|
+
throw new Error(`HTTP ${response.status}`);
|
|
68
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
69
|
+
if (bytes.byteLength > 100 * 1024 * 1024)
|
|
70
|
+
throw new Error("PDF exceeds the 100 MiB project limit");
|
|
71
|
+
if (sniffKindFromBuffer(bytes.subarray(0, 512)) !== "pdf")
|
|
72
|
+
throw new Error("downloaded content is not a recognizable PDF");
|
|
73
|
+
const target = await assertSafeProjectPath(root, localPath);
|
|
74
|
+
try {
|
|
75
|
+
await writeFile(target, bytes, { flag: "wx" });
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error.code !== "EEXIST")
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
if (!(await isPdf(root, localPath)))
|
|
82
|
+
throw new Error("local PDF verification failed");
|
|
83
|
+
const record = { schemaVersion: "psyclaw/reference-fulltext/v1", doi: normalized, doiUrl, status: "downloaded", localPath, sourceUrl: lookup.oaPdfUrl, checkedAt };
|
|
84
|
+
await saveRecord(root, record);
|
|
85
|
+
return record;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const record = {
|
|
89
|
+
schemaVersion: "psyclaw/reference-fulltext/v1",
|
|
90
|
+
doi: normalized,
|
|
91
|
+
doiUrl,
|
|
92
|
+
status: "download-failed",
|
|
93
|
+
localPath,
|
|
94
|
+
sourceUrl: lookup.oaPdfUrl,
|
|
95
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
96
|
+
checkedAt,
|
|
97
|
+
};
|
|
98
|
+
await saveRecord(root, record);
|
|
99
|
+
return record;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Final manuscript gate: every cited DOI must be metadata-verified and backed by a recognizable local PDF. */
|
|
103
|
+
export async function auditCitationFulltexts(root) {
|
|
104
|
+
const [uses, references] = await Promise.all([
|
|
105
|
+
readJsonl(await assertSafeProjectPath(root, ".psyclaw/citations.jsonl")),
|
|
106
|
+
readJsonl(await assertSafeProjectPath(root, ".psyclaw/references.jsonl")),
|
|
107
|
+
]);
|
|
108
|
+
const citedDois = [...new Set(uses.map((item) => normalizedDoi(item.doi)))].sort();
|
|
109
|
+
const verified = new Set(references.filter((item) => item.verified).map((item) => normalizedDoi(item.doi)));
|
|
110
|
+
const verifiedDois = citedDois.filter((doi) => verified.has(doi));
|
|
111
|
+
const missingVerification = citedDois.filter((doi) => !verified.has(doi));
|
|
112
|
+
const downloadedDois = [];
|
|
113
|
+
const missingPdfs = [];
|
|
114
|
+
for (const doi of citedDois) {
|
|
115
|
+
const localPath = referencePdfPath(doi);
|
|
116
|
+
if (await isPdf(root, localPath)) {
|
|
117
|
+
downloadedDois.push(doi);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const record = await readRecord(root, doi);
|
|
121
|
+
missingPdfs.push({ doi, doiUrl: `https://doi.org/${doi}`, expectedPath: localPath, ...(record?.reason ? { reason: record.reason } : {}) });
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
schemaVersion: "psyclaw/citation-fulltext-audit/v1",
|
|
125
|
+
ok: citedDois.length > 0 && missingVerification.length === 0 && missingPdfs.length === 0,
|
|
126
|
+
citedDois,
|
|
127
|
+
verifiedDois,
|
|
128
|
+
downloadedDois,
|
|
129
|
+
missingVerification,
|
|
130
|
+
missingPdfs,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../../../src/literature/archive.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ResearchProject } from "../core/contracts.js";
|
|
2
|
+
import type { TaskNode, WorkerReport } from "./contracts.js";
|
|
3
|
+
import { type PiExecutorOptions } from "./pi-executor.js";
|
|
4
|
+
export type ResearchAgentMode = "literature" | "review";
|
|
5
|
+
interface PersonaDefinition {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
role: TaskNode["role"];
|
|
9
|
+
instructions: string;
|
|
10
|
+
}
|
|
11
|
+
export interface MultiAgentRunOptions extends Pick<PiExecutorOptions, "provider" | "model" | "env" | "timeoutMs"> {
|
|
12
|
+
root: string;
|
|
13
|
+
project: ResearchProject;
|
|
14
|
+
onEvent?: PiExecutorOptions["onEvent"];
|
|
15
|
+
skillGuidance?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface MultiAgentResearchResult {
|
|
18
|
+
runId: string;
|
|
19
|
+
status: "completed" | "paused" | "blocked";
|
|
20
|
+
outputPath: string;
|
|
21
|
+
reports: WorkerReport[];
|
|
22
|
+
diagnostics: string[];
|
|
23
|
+
}
|
|
24
|
+
/** Create only the personas needed for the requested workflow. Existing files are user-owned and remain unchanged. */
|
|
25
|
+
export declare function ensureResearchAgentPersonas(root: string, mode: ResearchAgentMode): Promise<Array<PersonaDefinition & {
|
|
26
|
+
prompt: string;
|
|
27
|
+
}>>;
|
|
28
|
+
/** Search independently, then let four isolated Pi workers analyse separate evidence slices in parallel. */
|
|
29
|
+
export declare function runParallelLiteratureResearch(options: MultiAgentRunOptions): Promise<MultiAgentResearchResult>;
|
|
30
|
+
/** Run four independent reviewers against the current manuscript; no worker may edit it. */
|
|
31
|
+
export declare function runParallelPeerReview(options: MultiAgentRunOptions): Promise<MultiAgentResearchResult>;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { atomicWriteFile } from "../project/jsonl.js";
|
|
4
|
+
import { assertSafeProjectPath, projectPaths } from "../project/paths.js";
|
|
5
|
+
import { readManuscript } from "../project/manuscript.js";
|
|
6
|
+
import { searchOpenAlex } from "../workflows/meta-analysis.js";
|
|
7
|
+
import { runPlanWithPi } from "./pi-executor.js";
|
|
8
|
+
const LITERATURE_PERSONAS = [
|
|
9
|
+
{
|
|
10
|
+
id: "landscape-mapper",
|
|
11
|
+
label: "研究版图分析者",
|
|
12
|
+
role: "researcher",
|
|
13
|
+
instructions: "梳理研究主题、年代分布、代表性论文和研究方向。只依据提供的可核验元数据与摘要,不得补造论文内容。",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "methods-scout",
|
|
17
|
+
label: "方法学分析者",
|
|
18
|
+
role: "analyst",
|
|
19
|
+
instructions: "识别文献中的研究设计、测量与分析线索,指出仅凭元数据无法判断之处,不得把推测写成研究事实。",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "evidence-critic",
|
|
23
|
+
label: "证据批判者",
|
|
24
|
+
role: "critic",
|
|
25
|
+
instructions: "检查证据强弱、潜在偏倚、相互冲突与因果边界,明确区分检索结果、摘要信息和进一步需要全文核验的主张。",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "gap-finder",
|
|
29
|
+
label: "研究缺口分析者",
|
|
30
|
+
role: "verifier",
|
|
31
|
+
instructions: "寻找尚未充分覆盖的问题、对象、情境和方法,并把真正的证据缺口与单纯的检索缺口分开。",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
const REVIEW_PERSONAS = [
|
|
35
|
+
{
|
|
36
|
+
id: "theory-reviewer",
|
|
37
|
+
label: "理论与贡献审稿人",
|
|
38
|
+
role: "critic",
|
|
39
|
+
instructions: "以匿名同行评审标准检查问题重要性、理论定位、创新性、主张与证据的一致性。给出带稿件定位的主要与次要意见。",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "methods-reviewer",
|
|
43
|
+
label: "研究设计审稿人",
|
|
44
|
+
role: "analyst",
|
|
45
|
+
instructions: "检查抽样、设计、测量、效度、探索与确证边界、因果推断边界以及方法与问题的适配性。",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "statistics-reviewer",
|
|
49
|
+
label: "统计与可复现性审稿人",
|
|
50
|
+
role: "verifier",
|
|
51
|
+
instructions: "检查分析策略、效应量、置信区间、缺失值、稳健性、图表和可复现信息。不得重新计算或虚构任何统计量。",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "ethics-editorial-reviewer",
|
|
55
|
+
label: "伦理与编辑审稿人",
|
|
56
|
+
role: "writer",
|
|
57
|
+
instructions: "检查伦理、隐私、透明报告、引用表达、结构与可读性,并给出可执行但不直接改稿的建议。",
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
function personaMarkdown(persona) {
|
|
61
|
+
return [
|
|
62
|
+
"---",
|
|
63
|
+
"schemaVersion: psyclaw/agent-persona/v1",
|
|
64
|
+
`id: ${persona.id}`,
|
|
65
|
+
`label: ${persona.label}`,
|
|
66
|
+
"---",
|
|
67
|
+
"",
|
|
68
|
+
`# ${persona.label}`,
|
|
69
|
+
"",
|
|
70
|
+
persona.instructions,
|
|
71
|
+
"",
|
|
72
|
+
"所有判断必须标明依据和不确定性;不得虚构引用、数据、统计结果或稿件中不存在的内容。",
|
|
73
|
+
"",
|
|
74
|
+
].join("\n");
|
|
75
|
+
}
|
|
76
|
+
/** Create only the personas needed for the requested workflow. Existing files are user-owned and remain unchanged. */
|
|
77
|
+
export async function ensureResearchAgentPersonas(root, mode) {
|
|
78
|
+
const definitions = mode === "literature" ? LITERATURE_PERSONAS : REVIEW_PERSONAS;
|
|
79
|
+
const directory = await assertSafeProjectPath(root, `.psyclaw/agents/${mode}`);
|
|
80
|
+
await mkdir(directory, { recursive: true });
|
|
81
|
+
const loaded = [];
|
|
82
|
+
for (const persona of definitions) {
|
|
83
|
+
const relative = `.psyclaw/agents/${mode}/${persona.id}.md`;
|
|
84
|
+
const path = await assertSafeProjectPath(root, relative);
|
|
85
|
+
try {
|
|
86
|
+
loaded.push({ ...persona, prompt: await readFile(path, "utf8") });
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error.code !== "ENOENT")
|
|
90
|
+
throw error;
|
|
91
|
+
const prompt = personaMarkdown(persona);
|
|
92
|
+
await atomicWriteFile(path, prompt);
|
|
93
|
+
loaded.push({ ...persona, prompt });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return loaded;
|
|
97
|
+
}
|
|
98
|
+
function task(persona, objective, inputs) {
|
|
99
|
+
return {
|
|
100
|
+
id: persona.id,
|
|
101
|
+
role: persona.role,
|
|
102
|
+
objective: [persona.prompt, objective, "请用中文完成分析。summary 字段可以使用简洁 Markdown,但必须保留论文 DOI/OpenAlex 标识或稿件章节定位。"].join("\n\n"),
|
|
103
|
+
deps: [],
|
|
104
|
+
ownedPaths: [],
|
|
105
|
+
parallelSafe: true,
|
|
106
|
+
inputs,
|
|
107
|
+
outputs: [],
|
|
108
|
+
allowedEffects: ["read"],
|
|
109
|
+
completionContract: { requiredArtifacts: [], requiredReceiptEffects: [], mustPassGates: [] },
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function plan(runId, tasks) {
|
|
113
|
+
return {
|
|
114
|
+
schemaVersion: "psyclaw/plan/v1",
|
|
115
|
+
runId,
|
|
116
|
+
tasks,
|
|
117
|
+
budget: { maxTurns: tasks.length, maxWorkers: Math.min(4, tasks.length) },
|
|
118
|
+
horizon: { strategy: "hierarchical-plan-act-reflect", maxIterations: tasks.length, reflectionEvery: 1 },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function savePlan(root, value) {
|
|
122
|
+
await mkdir(projectPaths(root).plans, { recursive: true });
|
|
123
|
+
await atomicWriteFile(join(projectPaths(root).plans, `${value.runId}.json`), `${JSON.stringify(value, null, 2)}\n`);
|
|
124
|
+
}
|
|
125
|
+
function reportsInPlanOrder(value, reports) {
|
|
126
|
+
const byTask = new Map(reports.map((report) => [report.taskId, report]));
|
|
127
|
+
return value.tasks.map((item) => byTask.get(item.id)).filter((item) => item !== undefined);
|
|
128
|
+
}
|
|
129
|
+
async function writeSynthesis(root, relative, title, project, reports, diagnostics) {
|
|
130
|
+
const contents = [
|
|
131
|
+
`# ${title}`,
|
|
132
|
+
"",
|
|
133
|
+
`研究目标:${project.goal}`,
|
|
134
|
+
"",
|
|
135
|
+
"> 本报告由相互独立的子智能体并发生成后汇总。它是研究或审稿辅助材料,不替代全文核验与研究者判断。",
|
|
136
|
+
"",
|
|
137
|
+
...reports.flatMap((report) => [
|
|
138
|
+
`## ${report.taskId}`,
|
|
139
|
+
"",
|
|
140
|
+
report.summary,
|
|
141
|
+
"",
|
|
142
|
+
...(report.blockers.length ? ["### 阻断与待核验事项", "", ...report.blockers.map((item) => `- ${item}`), ""] : []),
|
|
143
|
+
]),
|
|
144
|
+
...(diagnostics.length ? ["## 运行诊断", "", ...diagnostics.map((item) => `- ${item}`), ""] : []),
|
|
145
|
+
].join("\n");
|
|
146
|
+
await atomicWriteFile(await assertSafeProjectPath(root, relative), contents);
|
|
147
|
+
}
|
|
148
|
+
function studyInput(studies) {
|
|
149
|
+
return JSON.stringify(studies.map((study) => ({
|
|
150
|
+
openAlexId: study.id,
|
|
151
|
+
title: study.title,
|
|
152
|
+
authors: study.authors,
|
|
153
|
+
abstract: study.abstract,
|
|
154
|
+
year: study.year,
|
|
155
|
+
venue: study.venue,
|
|
156
|
+
doi: study.doi,
|
|
157
|
+
citations: study.citations,
|
|
158
|
+
})));
|
|
159
|
+
}
|
|
160
|
+
/** Search independently, then let four isolated Pi workers analyse separate evidence slices in parallel. */
|
|
161
|
+
export async function runParallelLiteratureResearch(options) {
|
|
162
|
+
const personas = await ensureResearchAgentPersonas(options.root, "literature");
|
|
163
|
+
const queries = [
|
|
164
|
+
options.project.goal,
|
|
165
|
+
`${options.project.goal} systematic review`,
|
|
166
|
+
`${options.project.goal} empirical study`,
|
|
167
|
+
`${options.project.goal} measurement methodology`,
|
|
168
|
+
];
|
|
169
|
+
const batches = await Promise.all(queries.map((query) => searchOpenAlex(query, 12)));
|
|
170
|
+
const seen = new Set();
|
|
171
|
+
const distinct = batches.map((batch) => batch.filter((study) => {
|
|
172
|
+
const key = study.doi ?? study.id ?? study.title.toLowerCase();
|
|
173
|
+
if (seen.has(key))
|
|
174
|
+
return false;
|
|
175
|
+
seen.add(key);
|
|
176
|
+
return true;
|
|
177
|
+
}));
|
|
178
|
+
const runId = `literature_agents_${Date.now()}`;
|
|
179
|
+
const researchPlan = plan(runId, personas.map((persona, index) => task(persona, `围绕研究目标“${options.project.goal}”分析分配给你的 OpenAlex 检索结果。检索式:${queries[index]}. 这些记录仅表示检索命中;只有字段中明确出现的信息才可作为依据。${options.skillGuidance ? `\n用户指定的 Skill 指引:${options.skillGuidance}` : ""}`, [studyInput(distinct[index] ?? [])])));
|
|
180
|
+
await savePlan(options.root, researchPlan);
|
|
181
|
+
const result = await runPlanWithPi(researchPlan, {
|
|
182
|
+
cwd: options.root,
|
|
183
|
+
root: options.root,
|
|
184
|
+
maxWorkers: 4,
|
|
185
|
+
...(options.provider === undefined ? {} : { provider: options.provider }),
|
|
186
|
+
...(options.model === undefined ? {} : { model: options.model }),
|
|
187
|
+
...(options.env === undefined ? {} : { env: options.env }),
|
|
188
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
189
|
+
...(options.onEvent === undefined ? {} : { onEvent: options.onEvent }),
|
|
190
|
+
});
|
|
191
|
+
const reports = reportsInPlanOrder(researchPlan, result.reports);
|
|
192
|
+
const outputPath = "outputs/multi-agent-literature-report.md";
|
|
193
|
+
await writeSynthesis(options.root, outputPath, "多智能体文献调研报告", options.project, reports, result.diagnostics);
|
|
194
|
+
return { runId, status: result.status, outputPath, reports, diagnostics: result.diagnostics };
|
|
195
|
+
}
|
|
196
|
+
/** Run four independent reviewers against the current manuscript; no worker may edit it. */
|
|
197
|
+
export async function runParallelPeerReview(options) {
|
|
198
|
+
const manuscript = await readManuscript(options.root);
|
|
199
|
+
const isActualManuscript = manuscript.path === "notes/manuscript.md"
|
|
200
|
+
|| manuscript.path?.startsWith("paper/")
|
|
201
|
+
|| manuscript.path?.startsWith("docs/");
|
|
202
|
+
if (!manuscript.exists || !manuscript.path || !isActualManuscript || !manuscript.markdown.trim()) {
|
|
203
|
+
throw new Error("未找到可审稿的论文。请先完成并保存手稿,再运行 /review");
|
|
204
|
+
}
|
|
205
|
+
const manuscriptPath = manuscript.path;
|
|
206
|
+
const personas = await ensureResearchAgentPersonas(options.root, "review");
|
|
207
|
+
const runId = `peer_review_agents_${Date.now()}`;
|
|
208
|
+
const reviewPlan = plan(runId, personas.map((persona) => task(persona, `对当前手稿 ${manuscriptPath} 进行独立模拟同行评审。输出总体建议(接收/小修/大修/拒稿仅作模拟)、主要问题、次要问题、具体定位和修改理由。不得直接修改手稿。${options.skillGuidance ? `\n用户指定的 Skill 指引:${options.skillGuidance}` : ""}`, [manuscriptPath])));
|
|
209
|
+
await savePlan(options.root, reviewPlan);
|
|
210
|
+
const result = await runPlanWithPi(reviewPlan, {
|
|
211
|
+
cwd: options.root,
|
|
212
|
+
root: options.root,
|
|
213
|
+
maxWorkers: 4,
|
|
214
|
+
...(options.provider === undefined ? {} : { provider: options.provider }),
|
|
215
|
+
...(options.model === undefined ? {} : { model: options.model }),
|
|
216
|
+
...(options.env === undefined ? {} : { env: options.env }),
|
|
217
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
218
|
+
...(options.onEvent === undefined ? {} : { onEvent: options.onEvent }),
|
|
219
|
+
});
|
|
220
|
+
const reports = reportsInPlanOrder(reviewPlan, result.reports);
|
|
221
|
+
const outputPath = "outputs/simulated-peer-review.md";
|
|
222
|
+
await writeSynthesis(options.root, outputPath, "多智能体模拟同行评审", options.project, reports, result.diagnostics);
|
|
223
|
+
return { runId, status: result.status, outputPath, reports, diagnostics: result.diagnostics };
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=research-agents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"research-agents.js","sourceRoot":"","sources":["../../../src/orchestration/research-agents.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,31 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { createPanelServer } from "./server.js";
|
|
3
|
-
import { PiRpcClient } from "../adapters/pi/rpc.js";
|
|
4
|
-
const BROWSER_ASSISTANT_PROMPT = [
|
|
5
|
-
"You are the PsyClaw browser research assistant.",
|
|
6
|
-
"Work in a read-only mode. You may inspect project files with read, grep, find, and ls only.",
|
|
7
|
-
"Do not claim a statistical result, citation, or completed action without evidence.",
|
|
8
|
-
"When the user asks for a change or side effect, explain the proposed plan and state that approval is required.",
|
|
9
|
-
].join("\n");
|
|
10
|
-
function assistantEnv(provider) {
|
|
11
|
-
const envName = provider === "deepseek" ? "DEEPSEEK_API_KEY" : provider === "openai" ? "OPENAI_API_KEY" : provider === "anthropic" ? "ANTHROPIC_API_KEY" : provider === "google" ? "GEMINI_API_KEY" : undefined;
|
|
12
|
-
const value = envName === undefined ? undefined : process.env[envName];
|
|
13
|
-
return envName !== undefined && value !== undefined ? { [envName]: value } : {};
|
|
14
|
-
}
|
|
15
|
-
function assistantText(events) {
|
|
16
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
17
|
-
const event = events[index];
|
|
18
|
-
if (event?.type !== "message_end" || !event.message || typeof event.message !== "object")
|
|
19
|
-
continue;
|
|
20
|
-
const content = event.message.content;
|
|
21
|
-
if (!Array.isArray(content))
|
|
22
|
-
continue;
|
|
23
|
-
const text = content.filter((part) => Boolean(part && typeof part === "object" && part.type === "text" && typeof part.text === "string")).map((part) => part.text).join("");
|
|
24
|
-
if (text.trim())
|
|
25
|
-
return text.trim();
|
|
26
|
-
}
|
|
27
|
-
return "没有收到可显示的研究答复。";
|
|
28
|
-
}
|
|
29
3
|
async function listen(server, port) {
|
|
30
4
|
await new Promise((resolve, reject) => {
|
|
31
5
|
server.once("error", reject);
|
|
@@ -50,14 +24,10 @@ function openWorkbench(url) {
|
|
|
50
24
|
export default function psyclawPanelExtension(pi) {
|
|
51
25
|
let server;
|
|
52
26
|
let workbenchUrl;
|
|
53
|
-
let assistant;
|
|
54
27
|
const close = async () => {
|
|
55
28
|
const current = server;
|
|
56
29
|
server = undefined;
|
|
57
30
|
workbenchUrl = undefined;
|
|
58
|
-
const currentAssistant = assistant;
|
|
59
|
-
assistant = undefined;
|
|
60
|
-
await currentAssistant?.stop();
|
|
61
31
|
if (!current)
|
|
62
32
|
return;
|
|
63
33
|
await new Promise((resolve) => current.close(() => resolve()));
|
|
@@ -73,28 +43,6 @@ export default function psyclawPanelExtension(pi) {
|
|
|
73
43
|
if (server === undefined || workbenchUrl === undefined) {
|
|
74
44
|
const next = createPanelServer(ctx.cwd, { installSkill: async (task) => {
|
|
75
45
|
pi.sendUserMessage(task, ctx.isIdle() ? {} : { deliverAs: "followUp" });
|
|
76
|
-
}, assistant: async (message) => {
|
|
77
|
-
if (!assistant) {
|
|
78
|
-
assistant = new PiRpcClient({
|
|
79
|
-
cwd: ctx.cwd,
|
|
80
|
-
...(ctx.model?.provider === undefined ? {} : { provider: ctx.model.provider }),
|
|
81
|
-
...(ctx.model?.id === undefined ? {} : { model: ctx.model.id }),
|
|
82
|
-
env: assistantEnv(ctx.model?.provider),
|
|
83
|
-
tools: ["read", "grep", "find", "ls"],
|
|
84
|
-
systemPrompt: BROWSER_ASSISTANT_PROMPT,
|
|
85
|
-
});
|
|
86
|
-
try {
|
|
87
|
-
await assistant.start();
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
await assistant.stop().catch(() => undefined);
|
|
91
|
-
assistant = undefined;
|
|
92
|
-
throw error;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
const current = assistant;
|
|
96
|
-
const events = await current.promptAndWait(message);
|
|
97
|
-
return { text: assistantText(events) };
|
|
98
46
|
} });
|
|
99
47
|
const actualPort = await listen(next, 0);
|
|
100
48
|
server = next;
|