Skip to content

Commit 9b76c02

Browse files
committed
feat(session-stats): add session statistics plugin (v0.2.8)
1 parent 0cc58d6 commit 9b76c02

10 files changed

Lines changed: 1478 additions & 0 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "session-stats",
3+
"version": "0.2.8",
4+
"description": "Session statistics for ZCode: turns/steps, model vs tool time, TTFT, TPS (average + latest request), token usage and cache hit rate. Zero third-party dependencies.",
5+
"description_i18n": {
6+
"en": "Session statistics for ZCode: turns/steps, model vs tool time, TTFT, TPS (average + latest request), token usage and cache hit rate. Zero third-party dependencies.",
7+
"zh-CN": "会话统计:轮次/步数、模型/工具耗时、TTFT、TPS(会话平均+最近请求)、token 用量与缓存命中率。零第三方依赖。"
8+
},
9+
"author": {
10+
"name": "gary.liu"
11+
},
12+
"keywords": [
13+
"stats",
14+
"usage",
15+
"tokens",
16+
"cache",
17+
"panel",
18+
"mcp-apps"
19+
],
20+
"license": "MIT",
21+
"mcpServers": {
22+
"session-stats": {
23+
"type": "stdio",
24+
"command": "python",
25+
"args": [
26+
"${ZCODE_PLUGIN_ROOT}/server/stats_server.py"
27+
],
28+
"env": {
29+
"PYTHONUTF8": "1",
30+
"PYTHONIOENCODING": "utf-8"
31+
}
32+
}
33+
},
34+
"ui": {
35+
"surfaces": [
36+
{
37+
"id": "stats-panel",
38+
"title": {
39+
"en": "Session Stats",
40+
"zh-CN": "会话统计"
41+
},
42+
"server": "session-stats",
43+
"resourceUri": "ui://session-stats/panel.html",
44+
"availability": "session"
45+
}
46+
]
47+
},
48+
"commands": "./commands"
49+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# session-stats
2+
3+
Session statistics for ZCode: turns/steps, model vs tool time, TTFT, TPS (average +
4+
latest request), token usage and cache hit rate. Zero third-party dependencies.
5+
6+
## Components
7+
8+
- **MCP tool `get_stats`** — statistics for the current session (including subagents,
9+
aggregated via `session.parent_id`) or today's aggregate across sessions.
10+
- **MCP App side panel** (`ui.surfaces`) — stat pills with a details popup
11+
(timings, token buckets, cache hit, per-model breakdown), auto-refresh every 5 s,
12+
follows session switching and the live theme.
13+
- **`/stats` command** — text card rendering of the same numbers.
14+
- **SessionStart hook** — writes the current session id into the plugin data dir so
15+
the server can resolve "current session" without extra round-trips.
16+
17+
## Data source and metric definitions
18+
19+
Everything is read **read-only** from the local session database
20+
`~/.zcode/cli/db/db.sqlite` (SQLite URI `mode=ro`, `busy_timeout=5000`, WAL-safe).
21+
Measured against ZCode 3.17.1:
22+
23+
| Metric | Definition |
24+
| --- | --- |
25+
| Turns | distinct `turn_id` of the main session |
26+
| Steps | distinct `logical_request_id` (retries excluded; failed/cancelled counted) |
27+
| Model time | Σ `duration_ms` |
28+
| Tool time | Σ `tool_usage.duration_ms` |
29+
| TTFT avg | AVG(`time_to_first_token_ms`) |
30+
| TPS | Σ output ÷ Σ(completed_at − first_token_at); "live" TPS = latest completed request |
31+
| Tokens | Σ input + Σ output (`input_tokens` already includes cache reads — measured) |
32+
| Cache hit | cache_read ÷ input |
33+
34+
The database schema is an implementation detail of the host (not a stable API) and
35+
usage rows are retained for 30 days; queries degrade gracefully when columns are
36+
missing.
37+
38+
## Side effects
39+
40+
- Reads the local session database (read-only).
41+
- Starts a loopback-only HTTP service (`127.0.0.1:31710`) that serves the statistics
42+
JSON and the panel's dynamic script to ZCode's renderer. It answers only requests
43+
without a cross-origin `Origin` header (i.e. the app's own `file://` pages); other
44+
origins get 403.
45+
- The SessionStart hook writes one small file (`current_session`) into the
46+
plugin-owned data directory.
47+
- No network access, no credentials, no telemetry.
48+
49+
## Install
50+
51+
ZCode Desktop → **Plugin Marketplace** → add the marketplace that carries this plugin
52+
(the official `zcode-plugins` marketplace after review, or a local directory) →
53+
**Install session-stats** → restart or open a new session → run `/stats` or open the
54+
"Session Stats" side panel.
55+
56+
## Optional composer pills (host patch, experimental)
57+
58+
A stats strip centered under the composer input can be enabled with a patch to the
59+
host `app.asar` (see the upstream project's `patches/` directory). The patch is
60+
idempotent, backs up the host file per version and supports restore; it must be
61+
re-applied after ZCode updates. This is **not** part of the marketplace plugin and
62+
is purely optional.
63+
64+
## License
65+
66+
MIT
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# session-stats — ZCode 会话统计插件
2+
3+
ZCode 会话统计插件:轮次/步数、模型/工具耗时、
4+
TTFT、TPS(会话平均 + 最近请求)、token 用量与缓存命中率。零第三方依赖。
5+
6+
## 组件
7+
8+
- **MCP 工具 `get_stats`**:当前会话(含子代理,经 `session.parent_id` 聚合)
9+
或今日汇总的完整统计。
10+
- **MCP App 侧栏面板**(`ui.surfaces`):统计卡片 + 明细弹窗(耗时、token 分桶、
11+
缓存命中、模型分布),5 秒轮询自动刷新,跟随会话切换与主题。
12+
- **`/stats` 命令**:同样数字的文字卡片输出。
13+
- **SessionStart hook**:把当前会话 id 写入插件数据目录,供服务端解析「当前会话」。
14+
15+
## 数据源与指标口径
16+
17+
全部**只读**读取本地会话数据库 `~/.zcode/cli/db/db.sqlite`(SQLite URI `mode=ro` +
18+
`busy_timeout=5000`,WAL 下并发安全)。口径对 ZCode 3.17.1 实测定案:
19+
20+
| 指标 | 定义 |
21+
|---|---|
22+
| 轮次 | 主会话 `model_usage.turn_id` 去重数 |
23+
| 步数 | `logical_request_id` 去重数(重试不计步;失败/取消计步) |
24+
| 模型用时 | Σ `duration_ms` |
25+
| 工具用时 | Σ `tool_usage.duration_ms` |
26+
| TTFT 均值 | AVG(`time_to_first_token_ms`) |
27+
| TPS | Σ output ÷ Σ(completed_at − first_token_at);「实时」= 最近一次完成请求 |
28+
| token | Σ input + Σ output(实测 `input_tokens` 已含缓存读) |
29+
| 缓存命中率 | cache_read ÷ input |
30+
31+
数据库 schema 属宿主实现细节(非稳定 API),usage 行保留 30 天;查询对缺列做了
32+
优雅降级。
33+
34+
## 副作用说明
35+
36+
- 只读读取本地会话数据库。
37+
- 启动仅绑定回环地址的本地服务(`127.0.0.1:31710`),向 ZCode 渲染层提供统计
38+
JSON 与面板动态脚本;只响应无跨域 `Origin` 头的请求(即应用自身 `file://` 页面),
39+
其余 Origin 一律 403。
40+
- SessionStart hook 在插件数据目录写一个小文件(`current_session`)。
41+
- 无网络访问、无凭证、无遥测。
42+
43+
## 安装
44+
45+
ZCode Desktop → **插件市场** → 添加收录本插件的市场(审核通过后为官方
46+
`zcode-plugins` 市场,审核前可用本地目录)→ **安装 session-stats** → 重启或新开会话
47+
→ 输入 `/stats` 或打开「会话统计」侧栏面板。
48+
49+
## 许可
50+
51+
MIT
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
description: 显示会话统计(轮次/步数/耗时/TTFT/TPS/token/缓存命中)
3+
---
4+
5+
请调用 session-stats MCP 服务器的 get_stats 工具查询会话统计。scope 取值:当参数含「今日」「today」时用 `today`,否则用 `current`。
6+
7+
拿到 structuredContent 后,**严格按数据输出**下面的中文统计卡片(不要编造/估算任何数据里没有的数值,缺失显示「—」):
8+
9+
```
10+
⏱ {turns} 轮 {steps} 步 · {tps} tok/s
11+
Σ {tokens.total} tok · 缓存命中 {cache_hit_pct}%
12+
────────────────────────
13+
模型用时 {model_ms 格式化为 时/分/秒}
14+
工具调用用时 {tool_ms}
15+
首 token 平均 {ttft_ms_avg}
16+
输出速度(TPS) {tps} tok/s
17+
输入(含缓存) {tokens.input}(缓存读 {tokens.cache_read} / 缓存写 {tokens.cache_write})
18+
输出 {tokens.output}(含推理 {tokens.reasoning})
19+
总计 {tokens.total}
20+
```
21+
22+
- `today` 口径:开头注明「今日 {sessions} 个会话汇总」。
23+
- `current` 口径:结尾注明「会话 {session_id} · 含子代理 {subagent_count} 个」。
24+
- 若返回 isError 或 error 字段,如实转述错误并提示检查插件 server 日志。
25+
26+
参数:$ARGUMENTS
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"hooks": {
3+
"SessionStart": [
4+
{
5+
"matcher": "startup|resume|clear|compact",
6+
"hooks": [
7+
{
8+
"type": "process",
9+
"command": "python",
10+
"args": ["${ZCODE_PLUGIN_ROOT}/hooks/write_session.py"],
11+
"timeoutMs": 3000,
12+
"statusMessage": "session-stats: 记录当前会话 id"
13+
}
14+
]
15+
}
16+
]
17+
}
18+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""SessionStart hook:把当前 session_id 写进插件数据目录,供 MCP 服务器解析「当前会话」。
2+
3+
输入:stdin 一行 JSON(含 session_id);输出:无(保持 stdout 干净,退出码恒 0,
4+
绝不阻塞会话)。
5+
"""
6+
7+
import json
8+
import os
9+
import sys
10+
from pathlib import Path
11+
12+
13+
def main() -> int:
14+
try:
15+
req = json.loads(sys.stdin.read() or "{}")
16+
except Exception: # noqa: BLE001
17+
req = {}
18+
sid = req.get("session_id") or os.environ.get("ZCODE_SESSION_ID") or ""
19+
data_dir = os.environ.get("ZCODE_PLUGIN_DATA", "")
20+
if not sid or not data_dir:
21+
return 0 # 静默跳过,由 MCP 服务器走 env/db 兜底
22+
try:
23+
p = Path(data_dir)
24+
p.mkdir(parents=True, exist_ok=True)
25+
(p / "current_session").write_text(sid, encoding="utf-8")
26+
except OSError:
27+
pass
28+
return 0
29+
30+
31+
if __name__ == "__main__":
32+
sys.exit(main())

0 commit comments

Comments
 (0)