feat(version): 版本号单一事实源(core.__version__)+ web 左栏底部展示
- core/__init__.py 新增 __version__ = "0.8.0",作唯一来源 - web/app.py: FastAPI version 与 /healthz 返回都引它(不再写死两份) - dev.html: 左栏「我的资源」技能按钮旁加 #app-version 小灰字(纯展示) - main.js: boot 时无条件 fetch /healthz 填版本号(auth 豁免,embed/未登录皆可) - 放左栏底部而非顶栏:embed 模式桌面端 header 被 CSS 隐藏,顶栏点不到 - CLAUDE.md「文档维护」加规矩:每次 commit/push bump __version__(patch/minor/major 分类) - RUN.md / PROGRESS.md 同步 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
15d69b3372
commit
dd797a91e2
|
|
@ -42,6 +42,12 @@ PowerShell here-string `@'...'@` **只在 PowerShell 工具里有效**;用 Bash
|
|||
- 状态表(§7 B Step 几 / Phase 几)若变化跟着改
|
||||
- 文件清单若新增 / 删除模块跟着改
|
||||
|
||||
**每次 commit / push 必须 bump 版本号** —— 单一事实源是 `core/__init__.py` 的 `__version__`(web/app.py 的 FastAPI version、`/healthz` 返回、前端左栏底部展示都引这里,改版本只动这一行):
|
||||
- patch(`0.8.x`):bug 修复 / 重构 / 调参 / 新加 skill / 样式
|
||||
- minor(`0.x.0`):成批新功能 / 明显的对外行为变化
|
||||
- major(`x.0.0`):1.0 正式发版 / 不兼容大重构
|
||||
- 当前 `0.x` 开发期,未正式发版前不进 1.0
|
||||
|
||||
**只有以下情况才动 `DESIGN.md`**(避免把工程笔记沉淀成设计):
|
||||
- 架构 / 心智模型变化(如 §7.1 task-primary 重写)
|
||||
- 取舍决策推翻或新增(§5 / §7.9 类内容)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
### 2026-06-11
|
||||
|
||||
- **版本号机制(单一事实源 + 前端展示)**:此前只有 `web/app.py` 写死 `version="0.8"`(仅进 OpenAPI 文档,前端拿不到)。改为 `core/__init__.py` 的 `__version__`(当前 `0.8.0`)作唯一来源 → FastAPI `version`、`/healthz` 返回 `{"status":"ok","version":..}`、前端左栏底部展示全引它,**改版本只动这一行**。前端 `main.js` boot 时无条件 fetch `/healthz`(auth 豁免,embed/未登录都拿得到)填进 `#rail-resources` 的 `#app-version`(技能按钮旁,纯展示不可点;rail 折叠/手机时随整栏隐藏)。**放左栏底部而非顶栏**:embed 模式桌面端整层 header 被 CSS 隐藏,顶栏点不到。CLAUDE.md「文档维护」段已加规矩:每次 commit/push bump `__version__`(patch=修复/重构/调参/skill、minor=成批新功能/对外行为变化、major=1.0 发版)。
|
||||
- **并发/线程池轻量监控 + 接管默认 executor(§8.4 落地第 1 步)**:已上生产后线程池排队此前无观测手段。lifespan 显式建 `ThreadPoolExecutor`(尺寸复刻 Python 默认 `min(32, cpu+4)`,env `ZCBOT_RUN_MAX_WORKERS` 可调大)+ `set_default_executor` 接管——run 走 `asyncio.to_thread` 即用它,这样既能读 `max_workers` 判断排队、也成了日后调并发的旋钮(**行为不变**,只从匿名默认池换成显式同尺寸池;run 与 disk scan/pptx/reaper 仍共享此池,同原默认)。加 `_stats_logger` 后台 task 每 60s 采样:`active_runs`(=`len(inflight)`,含排队中)逼近 `max_workers` 即排队、新 run 的 SSE 会卡着不吐 token;**刷新峰值**时打 `[stats] new peak active_runs=N max_workers=M`(≥max_workers 带 `[WARN 已在排队]`),**有负载**时打 `[stats] active_runs=.. max_workers=.. sse_subs=.. rss_peak=..MB`,**空闲静默不刷屏**。RSS 用 stdlib `resource`(Unix 峰值/high-water;Windows dev 降级跳过),零新依赖;新 `broker.total_subscribers()` 给全局 SSE 订阅数。查看:`journalctl -u zcbot | grep '\[stats\]'`。**不做监控界面**(运维健康是少数标量、日志够诊断;业务分析数据已落 DB 走 SQL)——界面阶梯见 DESIGN §8.4。
|
||||
- **dev SPA「技能」查看 modal(左侧 rail 底部入口)**:因 `.skills` 在文件面板隐藏,加左侧 rail 底部「我的资源」分组(`#rail-resources`,留位给后续「记忆」)+「技能」按钮 → 弹 modal 分「平台 skill / 我的 skill」两组列表,点任一项展开**完整 SKILL.md**(`GET /v1/skills/{name}` + 现有 markdown 渲染),「我的」每项带删除(二次确认 → `DELETE /v1/skills/{name}`,只删 user 源 + 防穿越);覆盖标 `已覆盖平台同名`,`load_errors` 提示未加载的。创建/改/fork 仍走对话。新 `web/static/js/skills.js`(零构建 ES module,main.js import + Esc 栈接入);`/v1/skills` 已带 source/overrides/load_errors。**纯查看 + 删除,不在 UI 做创建/编辑**(编辑天然对话式)。
|
||||
- **用户私有 skill(每用户 `.skills/`,可从零写或 fork 内置再改)**:`SkillRegistry` 从单目录改**多来源**(`SkillSource` 列表:内置 `ROOT/skills` + 用户 `user_root/.skills`),后扫同名覆盖先扫 → **user wins**;覆盖关系记进 `user_overrides`,discovery 显式标 `[你的·已覆盖内置]`(不静默)。`Skill` 加 `source` 字段;`from_dir` 区分"无 SKILL.md(静默跳过)"与"有但格式错(抛 `SkillLoadError`)",`_scan` 捕获用户来源的错收进 `load_errors`、注入 system prompt 提示用户修(一个坏 skill 不再崩整次扫描)。容器路径改写从 LoadSkillTool 下沉到 registry(`container_dir` 按 `source` 给 `/sandbox/skills` 或 `/workspace/.skills`),LoadSkillTool 去掉 `container_skills_dir` 参数。**关键判断**:写 skill 用 host-side typed tool(`save_skill`/`fork_skill`,`tools/skill_authoring.py`)而非 fs/shell —— 因 fs 的 base_dir 锚 cwd(host)/ 容器 wd(docker),都够不到 `user_root/.skills`,跨 backend 不可靠;host-side 工具知道 user_root 一个落点两模式通吃(与 seedream/DocumentDownload 一致范式)。`save_skill` 写时校验 frontmatter(名合法 / YAML 合法 / 有 description / name 一致),`fork_skill` copytree 整目录(带脚本)+ 自动把 frontmatter name 对齐新名(否则 fork ppt 仍叫 ppt 会反覆盖内置)。`.skills` 是 dotfile(文件面板隐藏,与 `.memory` 一致;`validate_task_name` 已禁 `.` 起头 working_dir,天然不撞)。`/v1/skills` 带上用户 skill + `source`/`overrides_builtin`/`load_errors`。新增 `skill-creator` 引导 skill。+`test_user_skills.py`(20 例)+ 改写 `test_load_skill.py`。性能:多扫一目录,没 `.skills` 的用户一次 `exists()` 跳过;有 skill 仅每 run +1-3ms,不在热路径。
|
||||
|
|
|
|||
2
RUN.md
2
RUN.md
|
|
@ -142,7 +142,7 @@ curl --noproxy '*' -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8765/v1/ta
|
|||
|
||||
| 方法 + 路径 | 用途 | Auth |
|
||||
|---|---|---|
|
||||
| `GET /healthz` | `{"status":"ok"}` | 豁免 |
|
||||
| `GET /healthz` | `{"status":"ok","version":"<zcbot 版本>"}` | 豁免 |
|
||||
| `GET /` | 302 → `/static/dev.html` dev SPA | 豁免 |
|
||||
| `GET /docs` `/openapi.json` | Swagger UI / OpenAPI schema | 豁免 |
|
||||
| `GET /static/*` | dev.html 等静态文件 | 豁免 |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
|
||||
# 改版本只动这一行。
|
||||
__version__ = "0.8.0"
|
||||
|
|
@ -35,6 +35,7 @@ from pydantic import BaseModel
|
|||
from sqlalchemy import BigInteger, cast, func, select, update
|
||||
from starlette.background import BackgroundTask
|
||||
|
||||
from core import __version__
|
||||
from core.paths import to_db_path
|
||||
from core.storage import (
|
||||
NoSubtaskError,
|
||||
|
|
@ -784,7 +785,7 @@ def create_app() -> FastAPI:
|
|||
|
||||
app = FastAPI(
|
||||
title="zcbot api",
|
||||
version="0.8",
|
||||
version=__version__,
|
||||
description=(
|
||||
"zcbot 后端 — /v1 JSON API + SSE。Auth: PLATFORM_KEY → JWT(§7 D' 过渡)。"
|
||||
"本地 dev SPA: /static/dev.html。"
|
||||
|
|
@ -814,7 +815,7 @@ def create_app() -> FastAPI:
|
|||
|
||||
@app.get("/healthz", tags=["misc"])
|
||||
def healthz():
|
||||
return {"status": "ok"}
|
||||
return {"status": "ok", "version": __version__}
|
||||
|
||||
@app.get("/v1/models", tags=["misc"])
|
||||
def list_models(user_id: UUID = Depends(require_user)):
|
||||
|
|
|
|||
|
|
@ -210,6 +210,11 @@
|
|||
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
||||
}
|
||||
#rail-resources > button svg { flex-shrink: 0; opacity: .85; }
|
||||
/* 版本号:贴在「我的资源」栏右侧,纯展示(低优先信息,rail 折叠/手机时随整栏隐藏) */
|
||||
#app-version {
|
||||
flex-shrink: 0; font-size: 11px; color: var(--muted);
|
||||
font-family: var(--mono); white-space: nowrap; cursor: default;
|
||||
}
|
||||
|
||||
/* ───── 技能查看 modal(两栏 master-detail)───── */
|
||||
#skills-modal { z-index: 112; }
|
||||
|
|
@ -1163,6 +1168,7 @@
|
|||
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1.5"></rect><rect x="14" y="3" width="7" height="7" rx="1.5"></rect><rect x="3" y="14" width="7" height="7" rx="1.5"></rect><rect x="14" y="14" width="7" height="7" rx="1.5"></rect></svg>
|
||||
<span>技能</span>
|
||||
</button>
|
||||
<span id="app-version" title="zcbot 版本"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="split-left" class="splitter" role="separator" aria-orientation="vertical" title="拖拽调整任务栏宽度"></div>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,19 @@ async function loadStorage() {
|
|||
}
|
||||
|
||||
|
||||
// 版本号:/healthz 是 auth 豁免接口,embed / 未登录都拿得到 → boot 时无条件拉一次填进左栏底部。
|
||||
// 失败静默(版本号是装饰性信息,不该因网络抖动报错)。
|
||||
async function loadVersion() {
|
||||
const el = $("app-version");
|
||||
if (!el) return;
|
||||
try {
|
||||
const r = await fetch("/healthz");
|
||||
const d = await r.json();
|
||||
if (d && d.version) el.textContent = "v" + d.version;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
|
||||
// ───── Esc 关弹窗栈(跨模块协调:chpw/选入/文件预览/小预览)─────
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key !== "Escape") return;
|
||||
|
|
@ -67,6 +80,7 @@ document.addEventListener("keydown", (e) => {
|
|||
|
||||
|
||||
// ───── boot ─────
|
||||
loadVersion(); // 与登录态无关,立即拉
|
||||
if (EMBED) {
|
||||
embedInit();
|
||||
} else if (state.token) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue