psyclaw 0.30.6 → 0.30.8

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 CHANGED
@@ -1,221 +1,116 @@
1
1
  # PsyClaw
2
2
 
3
- PsyClaw 是面向社会科学研究的智能体工作台。它把研究项目、证据来源、Claim-Evidence 账本、完整性门禁、可恢复工作流和本地面板接到内置运行时上;只有无法由证据和通行方法消解、且会实质改变研究设计或解释的分歧才交由研究者取舍。它不替代统计软件,也不会把未经核验的引用、结果或审稿意见写成事实。
3
+ 面向社会科学研究的智能体工作台:把研究项目、证据账本、完整性门禁、可恢复工作流和本地面板接到内置运行时上。它不替代统计软件,也不会把未经核验的引用、结果或审稿意见写成事实。
4
4
 
5
- PsyClaw 自有代码使用 MIT 许可证;随 npm 包内置的 Academic Research Skills 位于 `vendor/ars`,保持上游署名并单独遵循 CC BY-NC 4.0,仅限非商业用途。
6
-
7
- 当前版本:`0.30.6`。正式命令、用户配置目录和后续发布统一使用 `psyclaw`。
8
-
9
- ## 发布流程
10
-
11
- 发布只提交已跟踪文件,并自动递增 patch 版本、创建版本标签,推送当前分支和标签;`.DS_Store`、论文附件、图形和其他未跟踪生成物不会被加入。GitHub Actions 随标签推送执行 Node 22 检查并发布 npm:
12
-
13
- ```bash
14
- RELEASE_MESSAGE="release: describe the change" pnpm release:push
15
- ```
16
-
17
- 运行前请确认当前分支、工作区变更和 GitHub 权限。NPM 发布由 GitHub Actions 的 Trusted Publishing 完成,本机不需要 `npm login`。
5
+ 当前版本:`0.30.8` · 命令与用户目录:`psyclaw` / `~/.psyclaw` · 许可证:MIT
6
+ 内置 Academic Research Skills(`vendor/ars`)遵循上游 CC BY-NC 4.0,仅限非商业用途。
18
7
 
19
8
  ## 安装
20
9
 
21
- 需要 Node.js `>=22.19.0`。官方 npm 源:
22
-
23
- ```powershell
24
- npm install -g psyclaw@0.30.6
25
- ```
26
-
27
- 如果本机 npm 配置把 registry 误写成带有 `~/` 的地址,请显式指定官方源:
10
+ 需要 Node.js `>=22.19.0`。
28
11
 
29
12
  ```bash
30
- npm install -g psyclaw@0.30.6 --registry=https://registry.npmjs.org/
31
- ```
32
-
33
- 中国大陆网络较慢或无法访问官方源时:
34
-
35
- ```powershell
36
- npm install -g psyclaw@0.30.6 --registry=https://registry.npmmirror.com
13
+ npm install -g psyclaw@0.30.8
37
14
  ```
38
15
 
39
- 或安装脚本:
16
+ 中国大陆可用镜像或安装脚本:
40
17
 
41
18
  ```bash
42
- # macOS / Linux
19
+ npm install -g psyclaw@0.30.8 --registry=https://registry.npmmirror.com
20
+ # 或
43
21
  PSYCLAW_CN=1 curl -fsSL https://exekiel179.github.io/psyclaw/install.sh | sh
44
22
  ```
45
23
 
46
24
  ```powershell
47
- # Windows PowerShell
25
+ # Windows
48
26
  irm https://exekiel179.github.io/psyclaw/install.ps1 | iex
49
- # 可选:$env:PSYCLAW_CN = "1";$env:PSYCLAW_VERSION = "0.30.6"
50
27
  ```
51
28
 
52
- 检测到国内 npm registry(如 npmmirror)、`PSYCLAW_CN=1` 或 `PSYCLAW_GITHUB_MIRROR` 时,内置路径统一走国内可用路由:
29
+ 确认入口:
53
30
 
54
- - 首启 `fd` / `ripgrep`(macOS/Linux):GitHub API 与 Release 经 `gh-proxy` 等镜像;Windows 仍用包内二进制,无需下载
55
- - `psyclaw update` / `check-updates`:读版本与安装命令使用 `PSYCLAW_REGISTRY` 或 npmmirror,不再写死官方 npm
56
- - 推荐 Skill 的托管 `git clone`:经 GitHub 镜像前缀
57
- - `fetch` 覆盖 `api.github.com`、`github.com`、`raw.githubusercontent.com`、`codeload.github.com`
58
-
59
- 建议把 `registry=https://registry.npmmirror.com` 写入 `~/.npmrc`,以免下次启动检测不到国内源。已有 HTTP(S) 代理时优先走代理、保持官方 GitHub URL。
60
-
61
- PsyClaw 的 npm 发布包直接携带 Windows x64/arm64 版 `ripgrep` 和 `fd`,Windows 启动时直接使用包内可执行文件,不再联网下载。
62
-
63
- 确认命令入口:
64
-
65
- ```powershell
31
+ ```bash
66
32
  psyclaw --help
67
33
  ```
68
34
 
69
- 直接运行 `psyclaw` 会启动交互研究工作台;首次没有模型配置时会自动进入配置流程。配置界面会检查当前进程、macOS `launchctl` 和已有用户凭据,也允许直接遮罩输入 Key。`launchctl` Key 只注入当前运行进程、不落盘;登录 shell 仅在用户明确按 `Ctrl+I` 后读取。直接输入或明确导入的 Key 只保存到 PsyClaw 用户级 `auth.json`,不会写入项目或 `models.json`。模型、凭据、会话和宿主资源统一保存在 `~/.psyclaw/agent`,PsyClaw 不创建或使用 `~/.pi`。不要把 API key 写进命令参数、项目、README 或 Git 仓库。
35
+ 直接运行 `psyclaw` 会启动交互工作台;首次无模型配置时会进入配置流程。API Key 只写入用户级 `~/.psyclaw/agent/auth.json`,不要写进命令参数、项目或 Git 仓库。
70
36
 
71
37
  ## 五分钟开始
72
38
 
73
- 在新的研究目录中建立项目:
74
-
75
- ```powershell
76
- mkdir my-study
77
- cd my-study
39
+ ```bash
40
+ mkdir my-study && cd my-study
78
41
  psyclaw init "社交支持与研究生心理健康的关系" --paradigm survey-observational
79
42
  psyclaw
80
43
  ```
81
44
 
82
- 初始化会建立 `.psyclaw/data/raw/`、`data/clean/`、`literature/pdfs/`、`notes/` `outputs/`,不再把原始数据目录暴露在项目根目录。`.psyclaw/data/raw/` 是只读保护区;旧项目的 `data/raw/` 仍保持只读兼容,把经确认可用于分析的派生数据放在 `data/clean/`。
83
-
84
- 在对话界面中,`/init <研究目标>` 初始化研究项目与工作区;随后用 Shift+Tab 切换到 `analysis` 或 `academic` 推进。没有单独的 `/run` 或 `/brief` 命令。未 `/init` 时保持普通对话,不强制研究门禁。
45
+ 初始化会创建 `.psyclaw/data/raw/`(只读保护区)、`data/clean/`、`literature/pdfs/`、`notes/`、`outputs/`。对话中也可用 `/init <研究目标>`;用 `Shift+Tab` `chat` `analysis` → `academic` 间切换。未 `/init` 时保持普通对话,不强制研究门禁。
85
46
 
86
- 登记本地材料:
87
-
88
- ```powershell
89
- psyclaw evidence add notes\source.md --level user
47
+ ```bash
48
+ psyclaw evidence add notes/source.md --level user
90
49
  ```
91
50
 
92
- ## 常用入口
51
+ ## 常用命令
93
52
 
94
- | 需求 | 命令或操作 |
53
+ | 需求 | 命令 |
95
54
  | --- | --- |
96
- | 启动研究对话 | `psyclaw` `psyclaw chat` |
97
- | 续接当前项目最近一次会话 | `psyclaw --continue` 或 `psyclaw -c` |
98
- | 创建研究项目 | `psyclaw init <goal> --paradigm <profile>` 或对话 `/init <goal>` |
99
- | 推进分析 / 写作 | Shift+Tab 切换 analysis / academic;可选 `/loop`、`/review` |
100
- | 模拟同行评审 | 论文完成后在对话中输入 `/review` |
101
- | 登记本地证据 | `psyclaw evidence add <path> --level user\|fulltext` |
102
- | 写研究移交记录 | `psyclaw handoff` |
103
- | 扫描本机其他 Agent | `psyclaw agents` |
104
- | 查看 PsyClaw 和内置运行时更新 | `psyclaw check-updates` |
105
- | 更新 PsyClaw 和内置运行时 | `psyclaw update` |
106
- | 导出会话 | 使用 Pi 内置的 `/export` |
107
- | 匿名产品遥测 | `psyclaw telemetry status`、`psyclaw telemetry off`、`psyclaw telemetry on`(默认开启;对话中 `/telemetry`) |
108
- | 打开科研面板 | 在对话中输入 `/panel`(Provider 页可选填 Base URL,指向 OpenAI 兼容网关) |
109
- | 查看或切换 Provider | 在对话中输入 `/provider` 或 `/provider <id>` |
110
- | 管理启动横幅宠物 | `/pet`、`/pet on`、`/pet off`(默认关闭) |
111
- | 管理 Skill | `/skill`(未安装→安装并默认启用;已启用→停用) |
112
- | 创建项目能力 | `/create-skill`、`/create-hook`、`/create-rule`、`/create-subagent`(附带需求文本) |
113
- | 运行只读 Subagent | `/agents` 浏览,或 `/agents <task>` 运行 |
114
- | 调用已加载 Skill | `/skill:<name>` |
115
- | 使用模式 | `Shift+Tab`:`chat` → `analysis` → `academic`;thinking 用 `Ctrl+Shift+T` |
116
- | 搭建仓库 | `/init` 或 `/init <研究目标>` |
117
- | 分析方案 / 交接 | `/plan`、`/plan <目标>`;人审后 `/handoff` |
118
- | AI 过程核对 / 实质验证 | `/crosscheck [焦点]`;`/verify [焦点]`。人审由收尾门禁自动要求(Panel/唤醒选项) |
119
- | 使用 PsyClaw ARS | `/ars` 开学术模式,或 `/ars <任务>`;`/ars doctor`、`/ars stop` |
120
- | 管理 Plugin | `/plugin`;终端使用 `psyclaw plugin install|remove|list` |
121
- | 调用已配置 MCP | `/mcp` 管理;模型通过 `psyclaw_mcp` 调用已启用服务器 |
122
-
123
- `psyclaw update` 会更新 PsyClaw 整包,并同时安装该版本锁定的内置运行时。在源码仓库中运行时,命令会停止并提示通过 Git 更新,不会覆盖本地修改。
124
- 仅查看更新计划而不执行时,使用 `psyclaw update --check`。旧的 `--yes` 参数仍兼容,但不再需要。
125
-
126
- ### 导出使用路径
127
-
128
- ```bash
129
- /export
130
- ```
55
+ | 启动 / 续接会话 | `psyclaw` · `psyclaw -c` |
56
+ | 初始化项目 | `psyclaw init <goal> --paradigm <profile>` `/init` |
57
+ | 登记证据 | `psyclaw evidence add <path> --level user\|fulltext` |
58
+ | 更新 | `psyclaw check-updates` · `psyclaw update` |
59
+ | 科研面板 | 对话中 `/panel` |
60
+ | 模式切换 | `Shift+Tab`(chat / analysis / academic) |
61
+ | 学术 ARS | `/ars` · `/ars <任务>` · `/ars doctor` |
62
+ | Skill / Plugin / MCP | `/skill` · `/plugin` · `/mcp` |
63
+ | 遥测 | `psyclaw telemetry off\|on\|status`(默认开启,可关) |
64
+
65
+ 更多对话命令见 `psyclaw --help` [使用白皮书](docs/使用白皮书.md)。
131
66
 
132
- `/export` 由 Pi 内置命令提供。PsyClaw 不重复实现会话导出或 Trace 运行时。
67
+ ## 文档
133
68
 
134
- ## 开发者本地安装
69
+ **研究者**
135
70
 
136
- ```powershell
137
- pnpm install
138
- pnpm build
139
- npm link
140
- psyclaw --help
141
- ```
71
+ - [使用白皮书](docs/使用白皮书.md)(正文:[v0.30.1](docs/PsyClaw使用白皮书_v0.30.1.md))
72
+ - [PsyClaw ARS 模式](docs/PsyClaw-ARS模式.md)
73
+ - [遥测说明](docs/telemetry.md)
74
+ - [文档与产物目录规范](docs/文档规范.md) · [产物目录规范](docs/产物目录规范.md)
142
75
 
143
- 开发阶段不需要执行 `psyclaw update`。请通过 Git 同步仓库,再运行 `pnpm install` 和 `pnpm build`。
76
+ **贡献者**(实现契约,非日常使用说明)
144
77
 
145
- ## 从旧 Python 版迁移
78
+ - [开工纪要](docs/开工纪要.md) · [架构蓝图](docs/架构蓝图.md) · [评测框架](docs/评测框架.md)
79
+ - [技能与生态准入清单](docs/技能与生态准入清单.md) · [威胁模型](docs/威胁模型.md)
80
+ - [AGENTS.md](AGENTS.md)
146
81
 
147
- 如果启动时出现 `ModuleNotFoundError: No module named 'psyclaw.cli'`,终端仍解析到旧 Python 安装的 `psyclaw.exe`,而不是 npm 版本。先检查命令顺序:
82
+ ## 边界
148
83
 
149
- ```powershell
150
- Get-Command psyclaw -All
151
- where.exe psyclaw
152
- ```
84
+ - 复用官方 Pi runtime,不维护 fork。
85
+ - 统计计算委托 Python/R、SPSS/Stata/Mplus 或受信任 MCP;须保存脚本、输入指纹与环境信息。
86
+ - Skill / Plugin / MCP 默认只发现、不执行;启用前核对来源与信任状态。
87
+ - 无证据的事实性 Claim 须标为 `uncertain` 或阻断;无 receipt / 审批的副作用不算完成。
153
88
 
154
- 保留 npm 的 `psyclaw.cmd`,移除或下调旧 Python 入口所在目录后重新打开终端,再安装当前版本:
89
+ ## 从源码开发
155
90
 
156
- ```powershell
157
- npm uninstall -g psyclaw
158
- npm install -g psyclaw@0.27.5
91
+ ```bash
92
+ pnpm install && pnpm build && npm link
159
93
  psyclaw --help
160
94
  ```
161
95
 
162
- Python 的卸载器提示 `No files were found to uninstall` 时,不要手工删除不明目录;先用上述命令定位旧入口,再在对应 Python 环境中修复或移除它。
96
+ 源码树中请用 Git 同步,不要跑 `psyclaw update`(会拒绝覆盖本地修改)。
163
97
 
164
- ## 卸载
165
-
166
- 仅卸载 PsyClaw 程序,保留用户配置和历史会话:
98
+ 发布(维护者):
167
99
 
168
100
  ```bash
169
- npm uninstall -g psyclaw
101
+ RELEASE_MESSAGE="release: describe the change" pnpm release:push
170
102
  ```
171
103
 
172
- 完全卸载 PsyClaw(同时删除模型配置、用户设置和历史会话):
104
+ 标签推送后由 GitHub Actions(Node 22 + Trusted Publishing)发布 npm。
173
105
 
174
- ```bash
175
- npm uninstall -g psyclaw
176
- ```
106
+ ## 迁移与卸载
177
107
 
178
- ```powershell
179
- # 删除用户配置目录 ~/.psyclaw(不会删除研究项目)
180
- Remove-Item -Recurse -Force "$HOME\.psyclaw" -ErrorAction SilentlyContinue
181
- ```
108
+ 若出现 `ModuleNotFoundError: No module named 'psyclaw.cli'`,说明仍指向旧 Python 入口。用 `Get-Command psyclaw -All` / `which -a psyclaw` 定位后移除旧入口,再安装当前 npm 包:
182
109
 
183
110
  ```bash
184
- # macOS / Linux
185
- rm -rf ~/.psyclaw
111
+ npm uninstall -g psyclaw
112
+ npm install -g psyclaw@0.30.8
186
113
  ```
187
114
 
188
- 上述命令不会删除研究项目目录,因为其中可能包含论文、证据、原始数据和分析产物。如需删除某个项目,应单独确认该项目的准确路径后处理。单独全局安装的 `@earendil-works/pi-coding-agent` 不属于 PsyClaw 用户数据,上述命令不会删除它。
189
-
190
- ## 文档
191
-
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
-
203
- - [项目范围与里程碑](docs/开工纪要.md)
204
- - [架构蓝图](docs/架构蓝图.md)
205
- - [评测框架](docs/评测框架.md)
206
- - [技能与生态准入清单](docs/技能与生态准入清单.md)
207
- - [威胁模型](docs/威胁模型.md)
208
- - [AGENTS.md](AGENTS.md)(贡献行为约束)
209
-
210
- 历史审计、分模块测评报告、会话复盘等仍在 `docs/` 下,按文件名识别;勿与使用白皮书混读。
211
-
212
- ## 边界
213
-
214
- - PsyClaw 复用官方 Pi 的 session、model、skill、package 和 extension runtime,不维护 fork。
215
- - 统计计算委托 Python/R、SPSS/Stata/Mplus 或受信任 MCP;输出必须保存脚本、输入指纹和环境信息。
216
- - Skill、Plugin 和 MCP 默认只发现、不执行;启用前需要来源、版本/ref、哈希、许可证和依赖状态。
217
- - 没有可定位证据的事实性 Claim 必须是 `uncertain` 或被阻断;没有审批记录的副作用不能被当作完成。
218
- - 遥测默认开启(匿名产品使用与错误,不含研究正文)。首次启动会说明如何关闭:`psyclaw telemetry off`。详见 [docs/telemetry.md](docs/telemetry.md)。
219
-
220
- 许可证:MIT。
221
-
115
+ 仅卸载程序:`npm uninstall -g psyclaw`
116
+ 同时清除用户配置与会话:再删除 `~/.psyclaw`(不会删除研究项目目录)。
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "psyclaw/recommended-agents/v1",
3
- "documentVersion": "0.29.2",
4
- "syncedAt": "2026-09-07",
3
+ "documentVersion": "0.30.6",
4
+ "syncedAt": "2026-09-21",
5
5
  "sourceDocument": "bundled literature/review subagents",
6
6
  "sourceRef": "bundled",
7
7
  "repositoryHint": "对齐 Claude Code:Subagent = md 提示合订本。外仓可后续以 GitHub 源加入 installPrep;当前内置 8 个无需安装。",
@@ -38,7 +38,37 @@ import { continuouslyWorkWarningText, isContinuouslyWorkEnabled, } from "../../s
38
38
  import { assertHumanVerifyGate, formatVerifyChecklist, isNaturalPlanConfirm, isPlanRitualConfirm, PLAN_RITUAL_PHRASE, ensureDefaultVerifyChecklist, loadVerifyChecklist } from "../../verify/checklist.js";
39
39
  import { formatSessionHelp, formatSessionHelpBrief } from "../../session/help.js";
40
40
  import { openResearchWorkbench } from "../../panel/workbench.js";
41
- import { captureAgentError, initNodeObservability, readTelemetryPreference, shutdownObservability, trackGateWaiting, trackSkillInstall, withAgentSpan, writeTelemetryPreference, } from "../../observability/index.js";
41
+ import { captureAgentError, extractPiGeneration, finishAgentSpan, initNodeObservability, lastPiGeneration, readTelemetryPreference, shutdownObservability, startAgentSpan, trackGateWaiting, trackLlmGeneration, trackSkillInstall, withAgentSpan, writeTelemetryPreference, } from "../../observability/index.js";
42
+ function ctxProviderModel(ctx) {
43
+ const provider = typeof ctx.model?.provider === "string" ? ctx.model.provider : undefined;
44
+ const model = typeof ctx.model?.id === "string" ? ctx.model.id : undefined;
45
+ return {
46
+ ...(provider === undefined ? {} : { provider }),
47
+ ...(model === undefined ? {} : { model }),
48
+ };
49
+ }
50
+ function piTurnSpanAttributes(extras = {}) {
51
+ return {
52
+ phase: "pi_turn",
53
+ ...(extras.provider ? { provider: extras.provider.slice(0, 80) } : {}),
54
+ ...(extras.model ? { model: extras.model.slice(0, 120) } : {}),
55
+ };
56
+ }
57
+ async function emitPiTurnGeneration(generation, extras) {
58
+ if (!generation)
59
+ return false;
60
+ const provider = generation.provider ?? extras.provider;
61
+ const model = generation.model ?? extras.model;
62
+ await trackLlmGeneration({
63
+ ...generation,
64
+ ...(provider === undefined ? {} : { provider }),
65
+ ...(model === undefined ? {} : { model }),
66
+ ...(extras.latencyMs === undefined ? {} : { latencyMs: extras.latencyMs }),
67
+ surface: "cli",
68
+ spanName: "pi-turn",
69
+ });
70
+ return true;
71
+ }
42
72
  /** Codex-style slash surface: bare command, or command + trailing free text. No subcommand trees. */
43
73
  function parseInitArgs(args) {
44
74
  const goal = args.trim();
@@ -1119,6 +1149,9 @@ export default function psyclawExtension(pi) {
1119
1149
  let arsModeEditor;
1120
1150
  let arsUiContext;
1121
1151
  let sessionMode = "chat";
1152
+ let turnStartedAt;
1153
+ let llmTurnSpan;
1154
+ let capturedTurnGeneration = false;
1122
1155
  const applySessionMode = (mode, opts) => {
1123
1156
  sessionMode = mode;
1124
1157
  arsModeEditor?.setMode(mode);
@@ -1163,9 +1196,55 @@ export default function psyclawExtension(pi) {
1163
1196
  });
1164
1197
  if (!legacyTestApi && typeof pi.on === "function")
1165
1198
  pi.on("session_shutdown", () => {
1199
+ finishAgentSpan(llmTurnSpan);
1200
+ llmTurnSpan = undefined;
1166
1201
  runtimeMcps.close();
1167
1202
  void shutdownObservability();
1168
1203
  });
1204
+ if (!legacyTestApi && typeof pi.on === "function")
1205
+ pi.on("agent_start", () => {
1206
+ capturedTurnGeneration = false;
1207
+ });
1208
+ if (!legacyTestApi && typeof pi.on === "function")
1209
+ pi.on("turn_start", async (event, ctx) => {
1210
+ finishAgentSpan(llmTurnSpan);
1211
+ turnStartedAt = typeof event.timestamp === "number" && Number.isFinite(event.timestamp)
1212
+ ? event.timestamp
1213
+ : Date.now();
1214
+ llmTurnSpan = await startAgentSpan("cli.llm_call", piTurnSpanAttributes(ctxProviderModel(ctx)));
1215
+ });
1216
+ if (!legacyTestApi && typeof pi.on === "function")
1217
+ pi.on("turn_end", async (event, ctx) => {
1218
+ const fallback = ctxProviderModel(ctx);
1219
+ const generation = extractPiGeneration({ type: "message", message: event.message });
1220
+ const latencyMs = turnStartedAt === undefined ? undefined : Math.max(0, Date.now() - turnStartedAt);
1221
+ const provider = generation?.provider ?? fallback.provider;
1222
+ const model = generation?.model ?? fallback.model;
1223
+ finishAgentSpan(llmTurnSpan, piTurnSpanAttributes({
1224
+ ...(provider === undefined ? {} : { provider }),
1225
+ ...(model === undefined ? {} : { model }),
1226
+ }));
1227
+ llmTurnSpan = undefined;
1228
+ if (await emitPiTurnGeneration(generation, { ...fallback, ...(latencyMs === undefined ? {} : { latencyMs }) })) {
1229
+ capturedTurnGeneration = true;
1230
+ turnStartedAt = undefined;
1231
+ }
1232
+ });
1233
+ if (!legacyTestApi && typeof pi.on === "function")
1234
+ pi.on("agent_end", async (event, ctx) => {
1235
+ const fallback = ctxProviderModel(ctx);
1236
+ finishAgentSpan(llmTurnSpan, piTurnSpanAttributes(fallback));
1237
+ llmTurnSpan = undefined;
1238
+ const latencyMs = turnStartedAt === undefined ? undefined : Math.max(0, Date.now() - turnStartedAt);
1239
+ turnStartedAt = undefined;
1240
+ if (capturedTurnGeneration) {
1241
+ capturedTurnGeneration = false;
1242
+ return;
1243
+ }
1244
+ capturedTurnGeneration = false;
1245
+ const generation = lastPiGeneration([event]);
1246
+ await emitPiTurnGeneration(generation, { ...fallback, ...(latencyMs === undefined ? {} : { latencyMs }) });
1247
+ });
1169
1248
  if (!legacyTestApi && typeof pi.on === "function")
1170
1249
  pi.on("before_agent_start", async (event, ctx) => {
1171
1250
  if (!(await readActiveProject(ctx?.cwd ?? process.cwd())))
@@ -1408,6 +1487,12 @@ export default function psyclawExtension(pi) {
1408
1487
  });
1409
1488
  return undefined;
1410
1489
  });
1490
+ pi.registerCommand("exit", {
1491
+ description: "完整退出 PsyClaw(等同 /quit)",
1492
+ handler: async (_args, ctx) => {
1493
+ ctx.shutdown();
1494
+ },
1495
+ });
1411
1496
  pi.registerCommand("init", {
1412
1497
  description: "搭建干净工作仓库(目录 + psyclaw.md),不自动追问",
1413
1498
  handler: async (args, ctx) => {