psyclaw 0.30.1 → 0.30.3

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.
Files changed (47) hide show
  1. package/README.md +22 -10
  2. package/dist/apps/panel/observability.js +6 -3
  3. package/dist/src/adapters/pi/extension.js +40 -18
  4. package/dist/src/adapters/pi/extension.js.map +1 -1
  5. package/dist/src/adapters/pi/model.js +28 -2
  6. package/dist/src/adapters/pi/model.js.map +1 -1
  7. package/dist/src/cli.js +6 -5
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/observability/config.d.ts +6 -0
  10. package/dist/src/observability/config.js +36 -5
  11. package/dist/src/observability/config.js.map +1 -1
  12. package/dist/src/observability/error-context.d.ts +20 -0
  13. package/dist/src/observability/error-context.js +104 -0
  14. package/dist/src/observability/error-context.js.map +1 -0
  15. package/dist/src/observability/expected.d.ts +10 -0
  16. package/dist/src/observability/expected.js +37 -0
  17. package/dist/src/observability/expected.js.map +1 -0
  18. package/dist/src/observability/identity.d.ts +10 -0
  19. package/dist/src/observability/identity.js +24 -0
  20. package/dist/src/observability/identity.js.map +1 -0
  21. package/dist/src/observability/index.d.ts +12 -2
  22. package/dist/src/observability/index.js +67 -9
  23. package/dist/src/observability/index.js.map +1 -1
  24. package/dist/src/observability/langfuse.d.ts +27 -0
  25. package/dist/src/observability/langfuse.js +65 -0
  26. package/dist/src/observability/langfuse.js.map +1 -0
  27. package/dist/src/observability/llm.d.ts +105 -0
  28. package/dist/src/observability/llm.js +185 -0
  29. package/dist/src/observability/llm.js.map +1 -0
  30. package/dist/src/observability/node-sdks.d.ts +9 -0
  31. package/dist/src/observability/node-sdks.js +80 -10
  32. package/dist/src/observability/node-sdks.js.map +1 -1
  33. package/dist/src/observability/notice.js +1 -1
  34. package/dist/src/observability/notice.js.map +1 -1
  35. package/dist/src/observability/preference.d.ts +3 -0
  36. package/dist/src/observability/preference.js +13 -0
  37. package/dist/src/observability/preference.js.map +1 -1
  38. package/dist/src/orchestration/pi-executor.js +10 -1
  39. package/dist/src/orchestration/pi-executor.js.map +1 -1
  40. package/dist/src/orchestration/runner.js +3 -3
  41. package/dist/src/orchestration/runner.js.map +1 -1
  42. package/dist/src/panel/server.js +1 -1
  43. package/dist/src/panel/server.js.map +1 -1
  44. package/dist/src/tui/skill-manager.js +35 -23
  45. package/dist/src/tui/skill-manager.js.map +1 -1
  46. package/package.json +1 -1
  47. 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.1`。正式命令、用户配置目录和后续发布统一使用 `psyclaw`。
7
+ 当前版本:`0.30.3`。正式命令、用户配置目录和后续发布统一使用 `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.1
24
+ npm install -g psyclaw@0.30.3
25
25
  ```
26
26
 
27
27
  如果本机 npm 配置把 registry 误写成带有 `~/` 的地址,请显式指定官方源:
28
28
 
29
29
  ```bash
30
- npm install -g psyclaw@0.30.1 --registry=https://registry.npmjs.org/
30
+ npm install -g psyclaw@0.30.3 --registry=https://registry.npmjs.org/
31
31
  ```
32
32
 
33
33
  中国大陆网络较慢或无法访问官方源时:
34
34
 
35
35
  ```powershell
36
- npm install -g psyclaw@0.30.1 --registry=https://registry.npmmirror.com
36
+ npm install -g psyclaw@0.30.3 --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.1"
49
+ # 可选:$env:PSYCLAW_CN = "1";$env:PSYCLAW_VERSION = "0.30.3"
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
- - [PsyClaw v0.30.1 使用白皮书](docs/PsyClaw使用白皮书_v0.30.1.md)
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
- - [PsyClaw ARS 模式与保护边界](docs/PsyClaw-ARS模式.md)
197
- - [文档与交付物规范](docs/文档规范.md)
198
- - [遥测(匿名产品遥测,默认开启,可关闭)](docs/telemetry.md)
206
+ - [技能与生态准入清单](docs/技能与生态准入清单.md)
207
+ - [威胁模型](docs/威胁模型.md)
208
+ - [AGENTS.md](AGENTS.md)(贡献行为约束)
209
+
210
+ 历史审计、分模块测评报告、会话复盘等仍在 `docs/` 下,按文件名识别;勿与使用白皮书混读。
199
211
 
200
212
  ## 边界
201
213
 
@@ -32,7 +32,8 @@
32
32
  ).trim();
33
33
  const surface = fromWindow.surface === "panel" || fromWindow.surface === "website" ? fromWindow.surface : "web";
34
34
  const release = String(fromWindow.release || "").trim();
35
- return { sentryDsn, posthogKey, posthogHost, surface, release };
35
+ const distinctId = String(fromWindow.distinctId || "").trim();
36
+ return { sentryDsn, posthogKey, posthogHost, surface, release, distinctId };
36
37
  }
37
38
 
38
39
  async function loadEsm(url) {
@@ -56,7 +57,7 @@
56
57
  const mod = await loadEsm("https://cdn.jsdelivr.net/npm/posthog-js@1/+esm");
57
58
  const posthog = mod.default || mod.posthog || mod;
58
59
  const panel = config.surface === "panel";
59
- posthog.init(config.posthogKey, {
60
+ const init = {
60
61
  api_host: config.posthogHost,
61
62
  person_profiles: "identified_only",
62
63
  autocapture: true,
@@ -74,7 +75,9 @@
74
75
  loaded: function (client) {
75
76
  client.register({ surface: config.surface, app: "psyclaw" });
76
77
  },
77
- });
78
+ };
79
+ if (config.distinctId) init.bootstrap = { distinctID: config.distinctId };
80
+ posthog.init(config.posthogKey, init);
78
81
  window.posthog = posthog;
79
82
  }
80
83
 
@@ -35,7 +35,7 @@ import { continuouslyWorkWarningText, isContinuouslyWorkEnabled, } from "../../s
35
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
- import { captureAgentError, initNodeObservability, readTelemetryPreference, shutdownObservability, trackGateWaiting, writeTelemetryPreference, } from "../../observability/index.js";
38
+ import { captureAgentError, initNodeObservability, readTelemetryPreference, shutdownObservability, trackGateWaiting, trackSkillInstall, withAgentSpan, writeTelemetryPreference, } from "../../observability/index.js";
39
39
  /** Codex-style slash surface: bare command, or command + trailing free text. No subcommand trees. */
40
40
  function parseInitArgs(args) {
41
41
  const goal = args.trim();
@@ -43,8 +43,8 @@ function parseInitArgs(args) {
43
43
  return {};
44
44
  return { paradigm: "survey-observational", goal };
45
45
  }
46
- async function notifyError(ctx, error) {
47
- await captureAgentError(error, { phase: "extension" });
46
+ async function notifyError(ctx, error, extra = {}) {
47
+ await captureAgentError(error, { phase: "extension", cwd: ctx.cwd, ...extra });
48
48
  ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
49
49
  }
50
50
  const activeAgentRuns = new Set();
@@ -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 = (await runtime.listUserConfigs(root)).map((entry) => ({
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 开启/关闭 · a 全部开启 · d 全部关闭 · Enter 交给模型安装并配置 · Esc 关闭",
522
- enterAction: "install",
523
- enabledText: "已开启;Enter 可让当前模型重新安装或修复推荐配置",
524
- disabledText: "已关闭",
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
- `成功后确保 .psyclaw/recommendations.json 的 mcp 列表包含 ${JSON.stringify(row.id)},说明写入了哪些配置,并提醒用户执行 /reload。若安装命令本身失败,直接诊断并修复;只有遇到必须由用户提供的凭据或外部软件许可证时才向用户说明。`,
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} 的下载、安装和配置任务交给当前模型。模型完成并确认可启动后,请执行 /reload。`, "info");
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
- "安装完成后检查目标目录结构,并提醒用户执行 /skill 启用该项,再执行 /reload。",
716
+ "安装完成后检查目标目录结构。该项已在推荐状态中默认启用;完成后执行 /reload 即可加载,无需再手动启用。",
712
717
  ].join("\n");
713
718
  }
714
719
  async function chooseSkillScope(ctx) {
@@ -721,20 +726,37 @@ 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
- const state = await readRecommendationState(ctx.cwd);
734
- state.skillScopes = { ...(state.skillScopes ?? {}), [row.id]: scope };
735
- await saveRecommendationState(ctx.cwd, state);
736
- pi.sendUserMessage(modelSkillInstallTask(ctx.cwd, row, scope), ctx.isIdle() ? {} : { deliverAs: "followUp" });
737
- ctx.ui.notify(`已将 ${row.name} 的安装任务交给当前模型,目标为${skillScopeLabel(scope)}。安装完成后请在 /skill 中启用,再执行 /reload。`, "info");
747
+ await withAgentSpan("cli.skill_install", { phase: "skill_install", scope, skill_id: row.id }, async () => {
748
+ try {
749
+ await markRecommendedSkillDesired(ctx.cwd, row.id, scope);
750
+ }
751
+ catch (error) {
752
+ void trackSkillInstall("error", { skill_id: row.id, scope, status: "error" });
753
+ await notifyError(ctx, error, { phase: "skill_install", skill_id: row.id, scope });
754
+ return;
755
+ }
756
+ void trackSkillInstall("queued", { skill_id: row.id, scope, status: "queued" });
757
+ pi.sendUserMessage(modelSkillInstallTask(ctx.cwd, row, scope), ctx.isIdle() ? {} : { deliverAs: "followUp" });
758
+ ctx.ui.notify(`已将 ${row.name} 的安装任务交给当前模型(默认启用),目标为${skillScopeLabel(scope)}。完成后请执行 /reload。`, "info");
759
+ });
738
760
  }
739
761
  async function showSkillManager(pi, ctx) {
740
762
  if (!ctx.hasUI || typeof ctx.ui.custom !== "function") {