psyclaw 0.29.16 → 0.29.18

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 (35) hide show
  1. package/README.md +7 -4
  2. package/dist/apps/panel/index.html +42 -0
  3. package/dist/apps/panel/observability.js +87 -0
  4. package/dist/src/adapters/pi/extension.js +28 -1
  5. package/dist/src/adapters/pi/extension.js.map +1 -1
  6. package/dist/src/chat.js +2 -0
  7. package/dist/src/chat.js.map +1 -1
  8. package/dist/src/cli.js +63 -0
  9. package/dist/src/cli.js.map +1 -1
  10. package/dist/src/observability/config.d.ts +47 -0
  11. package/dist/src/observability/config.js +125 -0
  12. package/dist/src/observability/config.js.map +1 -0
  13. package/dist/src/observability/index.d.ts +29 -0
  14. package/dist/src/observability/index.js +75 -0
  15. package/dist/src/observability/index.js.map +1 -0
  16. package/dist/src/observability/node-sdks.d.ts +11 -0
  17. package/dist/src/observability/node-sdks.js +92 -0
  18. package/dist/src/observability/node-sdks.js.map +1 -0
  19. package/dist/src/observability/notice.d.ts +9 -0
  20. package/dist/src/observability/notice.js +43 -0
  21. package/dist/src/observability/notice.js.map +1 -0
  22. package/dist/src/observability/preference.d.ts +23 -0
  23. package/dist/src/observability/preference.js +102 -0
  24. package/dist/src/observability/preference.js.map +1 -0
  25. package/dist/src/observability/public-keys.d.ts +10 -0
  26. package/dist/src/observability/public-keys.js +11 -0
  27. package/dist/src/observability/public-keys.js.map +1 -0
  28. package/dist/src/orchestration/runner.js +21 -2
  29. package/dist/src/orchestration/runner.js.map +1 -1
  30. package/dist/src/panel/server.d.ts +2 -0
  31. package/dist/src/panel/server.js +68 -1
  32. package/dist/src/panel/server.js.map +1 -1
  33. package/dist/src/style/cli-ui.js +2 -0
  34. package/dist/src/style/cli-ui.js.map +1 -1
  35. package/package.json +4 -1
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.29.16`。正式命令、用户配置目录和后续发布统一使用 `psyclaw`。
7
+ 当前版本:`0.29.18`。正式命令、用户配置目录和后续发布统一使用 `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.29.16
24
+ npm install -g psyclaw@0.29.18
25
25
  ```
26
26
 
27
27
  如果本机 npm 配置把 registry 误写成带有 `~/` 的地址,请显式指定官方源:
28
28
 
29
29
  ```bash
30
- npm install -g psyclaw@0.29.16 --registry=https://registry.npmjs.org/
30
+ npm install -g psyclaw@0.29.18 --registry=https://registry.npmjs.org/
31
31
  ```
32
32
 
33
33
  中国大陆网络较慢或无法访问官方源时:
34
34
 
35
35
  ```powershell
36
- npm install -g psyclaw@0.29.16 --registry=https://registry.npmmirror.com
36
+ npm install -g psyclaw@0.29.18 --registry=https://registry.npmmirror.com
37
37
  ```
38
38
 
39
39
  或安装脚本:
@@ -97,6 +97,7 @@ psyclaw evidence add notes\source.md --level user
97
97
  | 查看 PsyClaw 和内置运行时更新 | `psyclaw check-updates` |
98
98
  | 更新 PsyClaw 和内置运行时 | `psyclaw update` |
99
99
  | 导出会话 | 使用 Pi 内置的 `/export` |
100
+ | 匿名产品遥测 | `psyclaw telemetry status`、`psyclaw telemetry off`、`psyclaw telemetry on`(默认开启;对话中 `/telemetry`) |
100
101
  | 打开科研面板 | 在对话中输入 `/panel` |
101
102
  | 查看或切换 Provider | 在对话中输入 `/provider` 或 `/provider <id>` |
102
103
  | 管理启动横幅宠物 | `/pet`、`/pet on`、`/pet off`(默认关闭) |
@@ -178,6 +179,7 @@ node -e 'const fs=require("node:fs"),os=require("node:os"),path=require("node:pa
178
179
  - [评测框架](docs/评测框架.md)
179
180
  - [PsyClaw ARS 模式与保护边界](docs/PsyClaw-ARS模式.md)
180
181
  - [文档与交付物规范](docs/文档规范.md)
182
+ - [遥测(匿名产品遥测,默认开启,可关闭)](docs/telemetry.md)
181
183
 
182
184
  ## 边界
183
185
 
@@ -185,5 +187,6 @@ node -e 'const fs=require("node:fs"),os=require("node:os"),path=require("node:pa
185
187
  - 统计计算委托 Python/R、SPSS/Stata/Mplus 或受信任 MCP;输出必须保存脚本、输入指纹和环境信息。
186
188
  - Skill、Plugin 和 MCP 默认只发现、不执行;启用前需要来源、版本/ref、哈希、许可证和依赖状态。
187
189
  - 没有可定位证据的事实性 Claim 必须是 `uncertain` 或被阻断;没有审批记录的副作用不能被当作完成。
190
+ - 遥测默认开启(匿名产品使用与错误,不含研究正文)。首次启动会说明如何关闭:`psyclaw telemetry off`。详见 [docs/telemetry.md](docs/telemetry.md)。
188
191
 
189
192
  许可证:MIT。
@@ -4,6 +4,8 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
6
  <title>PsyClaw Panel · 科研工作台</title>
7
+ <script id="psyclaw-obs-config">window.__PSYCLAW_OBS__={"surface":"panel","sentryDsn":"","posthogKey":"","posthogHost":"","release":""};</script>
8
+ <script src="/observability.js" defer></script>
7
9
  <style>
8
10
  :root {
9
11
  color-scheme: light;
@@ -1090,6 +1092,26 @@
1090
1092
  text-align: center;
1091
1093
  }
1092
1094
  .hidden { display: none !important; }
1095
+ .telemetry-notice {
1096
+ display: flex;
1097
+ align-items: flex-start;
1098
+ justify-content: space-between;
1099
+ gap: 16px;
1100
+ padding: 10px 20px;
1101
+ background: var(--brand-primary-light);
1102
+ border-bottom: 1px solid var(--border-subtle);
1103
+ font-size: 13px;
1104
+ color: var(--text-main);
1105
+ }
1106
+ .telemetry-notice.hidden { display: none !important; }
1107
+ .telemetry-notice p {
1108
+ margin: 0;
1109
+ color: var(--text-muted);
1110
+ max-width: 72ch;
1111
+ line-height: 1.5;
1112
+ }
1113
+ .telemetry-notice strong { color: var(--text-main); }
1114
+ .telemetry-notice .toolbar { display: flex; gap: 8px; align-items: center; flex-shrink: 0; }
1093
1115
 
1094
1116
  /* Pipeline Stepper */
1095
1117
  .pipeline-stepper {
@@ -1838,6 +1860,10 @@
1838
1860
  </div>
1839
1861
  </div>
1840
1862
  </header>
1863
+ <div id="telemetry-notice" class="telemetry-notice hidden" role="status">
1864
+ <p><strong>匿名产品遥测默认开启。</strong> 用于改进 PsyClaw 的粗粒度使用与错误信息;不含研究正文、论文或个人身份。可随时关闭。</p>
1865
+ <div class="toolbar"><button type="button" class="button" id="telemetry-ack">知道了</button><button type="button" class="button" id="telemetry-off">关闭遥测</button></div>
1866
+ </div>
1841
1867
 
1842
1868
  <!-- 主体工作区 Layout -->
1843
1869
  <div class="layout nav-collapsed">
@@ -3404,6 +3430,22 @@ loadProject().catch(error => {
3404
3430
  });
3405
3431
  loadProjectStatus();
3406
3432
 
3433
+ (function initTelemetryNotice(){
3434
+ const obs = window.__PSYCLAW_OBS__ || {};
3435
+ const banner = $("telemetry-notice");
3436
+ if (!banner) return;
3437
+ if (obs.showTelemetryNotice) banner.classList.remove("hidden");
3438
+ $("telemetry-ack")?.addEventListener("click", async () => {
3439
+ try { await json("/api/telemetry", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "ack" }) }); } catch {}
3440
+ banner.classList.add("hidden");
3441
+ });
3442
+ $("telemetry-off")?.addEventListener("click", async () => {
3443
+ try { await json("/api/telemetry", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "disable" }) }); } catch {}
3444
+ banner.classList.add("hidden");
3445
+ location.reload();
3446
+ });
3447
+ })();
3448
+
3407
3449
  const initialView = decodeURIComponent((location.hash || "").replace(/^#/, "").trim());
3408
3450
  const knownViews = new Set(["project", "ecosystem", "provider", "checklist", "chat", "help"]);
3409
3451
  if (knownViews.has(initialView)) setView(initialView);
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Opt-in browser observability for PsyClaw panel + website.
3
+ *
4
+ * Loads Sentry / PostHog from a CDN only when a DSN or project key is present
5
+ * on window.__PSYCLAW_OBS__, a meta tag, or (local debug) a query param.
6
+ * With those empty, this file is a no-op: no network calls, no SDK init.
7
+ */
8
+ (function psyclawObservability() {
9
+ if (window.__PSYCLAW_OBS_BOOTED__) return;
10
+ window.__PSYCLAW_OBS_BOOTED__ = true;
11
+
12
+ function meta(name) {
13
+ const node = document.querySelector('meta[name="' + name + '"]');
14
+ const value = node && node.getAttribute("content");
15
+ return value ? String(value).trim() : "";
16
+ }
17
+
18
+ function param(name) {
19
+ try {
20
+ return new URLSearchParams(location.search).get(name) || "";
21
+ } catch {
22
+ return "";
23
+ }
24
+ }
25
+
26
+ function readConfig() {
27
+ const fromWindow = window.__PSYCLAW_OBS__ || {};
28
+ const sentryDsn = String(fromWindow.sentryDsn || meta("psyclaw-sentry-dsn") || param("sentryDsn") || "").trim();
29
+ const posthogKey = String(fromWindow.posthogKey || meta("psyclaw-posthog-key") || param("posthogKey") || "").trim();
30
+ const posthogHost = String(
31
+ fromWindow.posthogHost || meta("psyclaw-posthog-host") || param("posthogHost") || "https://us.posthog.com",
32
+ ).trim();
33
+ const surface = fromWindow.surface === "panel" || fromWindow.surface === "website" ? fromWindow.surface : "web";
34
+ const release = String(fromWindow.release || "").trim();
35
+ return { sentryDsn, posthogKey, posthogHost, surface, release };
36
+ }
37
+
38
+ async function loadEsm(url) {
39
+ return import(url);
40
+ }
41
+
42
+ async function bootSentry(config) {
43
+ const Sentry = await loadEsm("https://cdn.jsdelivr.net/npm/@sentry/browser@10/+esm");
44
+ Sentry.init({
45
+ dsn: config.sentryDsn,
46
+ release: config.release || undefined,
47
+ environment: "local",
48
+ tracesSampleRate: 1.0,
49
+ sendDefaultPii: false,
50
+ integrations: typeof Sentry.browserTracingIntegration === "function" ? [Sentry.browserTracingIntegration()] : [],
51
+ });
52
+ window.Sentry = Sentry;
53
+ }
54
+
55
+ async function bootPostHog(config) {
56
+ const mod = await loadEsm("https://cdn.jsdelivr.net/npm/posthog-js@1/+esm");
57
+ const posthog = mod.default || mod.posthog || mod;
58
+ const panel = config.surface === "panel";
59
+ posthog.init(config.posthogKey, {
60
+ api_host: config.posthogHost,
61
+ person_profiles: "identified_only",
62
+ autocapture: true,
63
+ capture_pageview: true,
64
+ capture_pageleave: true,
65
+ persistence: "memory",
66
+ mask_all_text: panel,
67
+ mask_all_element_attributes: panel,
68
+ session_recording: {
69
+ maskAllInputs: true,
70
+ maskTextSelector: panel
71
+ ? ".markdown, .raw, .viewer-body, .event-detail, pre, code, textarea, [data-file]"
72
+ : undefined,
73
+ },
74
+ loaded: function (client) {
75
+ client.register({ surface: config.surface, app: "psyclaw" });
76
+ },
77
+ });
78
+ window.posthog = posthog;
79
+ }
80
+
81
+ const config = readConfig();
82
+ const jobs = [];
83
+ if (config.sentryDsn) jobs.push(bootSentry(config).catch(function () {}));
84
+ if (config.posthogKey) jobs.push(bootPostHog(config).catch(function () {}));
85
+ if (jobs.length === 0) return;
86
+ Promise.all(jobs).catch(function () {});
87
+ })();
@@ -35,6 +35,7 @@ import { continuouslyWorkWarningText, isContinuouslyWorkEnabled, } from "../../s
35
35
  import { assertHumanVerifyGate, formatVerifyChecklist, isNaturalPlanConfirm, 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
39
  /** Codex-style slash surface: bare command, or command + trailing free text. No subcommand trees. */
39
40
  function parseInitArgs(args) {
40
41
  const goal = args.trim();
@@ -43,6 +44,7 @@ function parseInitArgs(args) {
43
44
  return { paradigm: "survey-observational", goal };
44
45
  }
45
46
  async function notifyError(ctx, error) {
47
+ await captureAgentError(error, { phase: "extension" });
46
48
  ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
47
49
  }
48
50
  const activeAgentRuns = new Set();
@@ -887,6 +889,7 @@ const WORKFLOW_RUNNERS = {
887
889
  "expert-review": runExpertReview,
888
890
  };
889
891
  export default function psyclawExtension(pi) {
892
+ void initNodeObservability();
890
893
  const developerCommands = process.env.PSYCLAW_DEVELOPER_COMMANDS === "1";
891
894
  const legacyTestApi = typeof pi.registerTool !== "function";
892
895
  const runtimeMcps = new RuntimeMcpRegistry();
@@ -936,7 +939,10 @@ export default function psyclawExtension(pi) {
936
939
  };
937
940
  });
938
941
  if (!legacyTestApi && typeof pi.on === "function")
939
- pi.on("session_shutdown", () => runtimeMcps.close());
942
+ pi.on("session_shutdown", () => {
943
+ runtimeMcps.close();
944
+ void shutdownObservability();
945
+ });
940
946
  if (!legacyTestApi && typeof pi.on === "function")
941
947
  pi.on("before_agent_start", async (event, ctx) => {
942
948
  if (!(await readActiveProject(ctx?.cwd ?? process.cwd())))
@@ -1094,6 +1100,7 @@ export default function psyclawExtension(pi) {
1094
1100
  if (requiresSeparateOperationConfirmation(event.toolName, event.input)) {
1095
1101
  if (!ctx.hasUI)
1096
1102
  return { block: true, terminate: true, reason: "外部发布需要用户在交互界面中明确确认" };
1103
+ void trackGateWaiting("tool_approval");
1097
1104
  const choice = await ctx.ui.select(`确认外部操作\n${summary}`, ["确认执行", "取消"], { timeout: 120_000 });
1098
1105
  const approved = choice === "确认执行";
1099
1106
  await appendApproval(ctx.cwd, {
@@ -1607,6 +1614,26 @@ export default function psyclawExtension(pi) {
1607
1614
  ctx.ui.notify(`启动横幅宠物已${action === "on" ? "开启" : "关闭"},下次启动生效`, "info");
1608
1615
  },
1609
1616
  });
1617
+ if (!legacyTestApi)
1618
+ pi.registerCommand("telemetry", {
1619
+ description: "查看或关闭匿名产品遥测",
1620
+ handler: async (args, ctx) => {
1621
+ const action = args.trim().toLowerCase() || "status";
1622
+ if (action === "status") {
1623
+ const preference = await readTelemetryPreference();
1624
+ ctx.ui.notify(preference.enabled
1625
+ ? "匿名产品遥测:开启(默认)。采集粗粒度使用与错误,不含研究正文。关闭:/telemetry off"
1626
+ : "匿名产品遥测:已关闭。重新开启:/telemetry on", "info");
1627
+ return;
1628
+ }
1629
+ if (action !== "on" && action !== "off") {
1630
+ ctx.ui.notify("Usage: /telemetry on|off|status", "error");
1631
+ return;
1632
+ }
1633
+ await writeTelemetryPreference({ enabled: action === "on", noticeAcknowledged: true });
1634
+ ctx.ui.notify(action === "on" ? "已开启匿名产品遥测。下次启动生效。" : "已关闭匿名产品遥测。下次启动不再发送。", "info");
1635
+ },
1636
+ });
1610
1637
  if (typeof pi.registerTool === "function") {
1611
1638
  pi.registerTool({
1612
1639
  name: "psyclaw_research_decision",