feat(toolbox): T6 创作工具箱通用生成器框架 — 8 新生成器 + 声明驱动落地页 + P2 收尾

通用执行路径驱动全部生成器("加生成器=加一份声明"):
- @llm: ww_agents +7 输出 schema + 7 spec(book-title/blurb/name/golden-finger/
  glossary/opening/fine-outline,只声明 tier)+ build_outline_chapter_context
- @backend: ww_skills GeneratorTool 描述符 + TOOLBOX(11) + get_tool;3 通用端点
  GET /skills/toolbox · POST .../skills/{tool_key}/generate(预览不写库,仅记账) ·
  POST .../ingest(复用 continuity 409 + partition_writes 白名单);纯 context 派发
- @frontend: 工具箱落地页 RSC + 声明驱动 GeneratorRunner + lib/toolbox 纯函数
  + LeftNav「工具箱」+ ⌘K nav-toolbox/action-gen-*;legacy 3 跳现页
- @qa: tests/test_t6_toolbox_e2e.py 5 用例真 pg + mock 网关零 token,无端点 bug
- P2 收尾: 限流→decisions.md 记延后(单用户原型);noopener/Committable 早已修

守不变量 #2(只声明 tier)/#3(预览不写库,入库经验收 gate)/#9(缓存前缀)。无 DB 迁移。
门禁绿: 后端 ruff/format/mypy 195/alembic 无漂移/pytest 583;前端 lint/tsc/vitest 279/build。
spec 回写 PRODUCT_SPEC §7 + ARCHITECTURE §7.2 端点表。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yaojia Wang
2026-06-22 20:37:55 +02:00
parent 1f1afa37b6
commit f43ccd293f
46 changed files with 4848 additions and 12 deletions

View File

@@ -177,6 +177,16 @@
- **服务缝**`services/kimi_oauth.py`注入 `AsyncHttpClient` Protocol = `httpx.AsyncClient.post` 子集,测试零联网):`start_device_authorization(http)->DeviceAuth``poll_token(http, device_code)->TokenSet`(一次尝试,`AuthorizationPending`/`SlowDown` 异常供调用方循环)、`refresh(http, refresh_token)->TokenSet``encrypt_oauth_bundle`/`decrypt_oauth_bundle`Fernet 复用 `security/credentials` `encrypt_api_key`/`decrypt_api_key`工作在 JSON 串上`needs_refresh(token)`client_id env `KIMI_CLIENT_ID` 可覆盖默认 `17e5f671-...`device authorization **带 `scope=kimi-code`**coding entitlement实测省略 scope`api.kimi.com/coding/v1` 401故重新带上token 交换/刷新不带 scope)。
- **@frontend 行动K1.4**`pnpm gen:api` 纳入 OAuth start/disconnect/status + 3 schema连接流 = start user_code + verification_uri 轮询 `GET /jobs/{job_id}` done已连接/failed过期/拒绝status 端点查连接态档位路由到 `kimi-code:kimi-for-coding`
### C3 扩展T6.2/T6.3, 2026-06-22· 创作工具箱通用端点(独立 `routers/toolbox.py`已注册 owner @backend 状态: 稳定
- 通用生成器框架声明驱动一条执行路径——「加一个生成器」= `ww_skills.TOOLBOX` 加一份 `GeneratorTool` 声明descriptor 在代码、** DB 迁移**无需新端点snake_case**新增 3 端点 + schema @frontend 必须 `cd apps/web && pnpm gen:api`**T6 前端前置)。
- `GET /skills/toolbox` **200** `ToolboxListResponse{tools:[ToolDescriptorView{key,title,subtitle,genre?,is_legacy:bool,ingestable:bool,input_fields:[ToolInputFieldView{name,label,type,required,default?,help?}],legacy_route?:str}]}`legacy 3 + 8 key 升序)。legacy 工具worldbuilding/character/outline`is_legacy=true` `legacy_route``/projects/{id}/codex?gen=world` · `?gen=character` · `/projects/{id}/outline`供前端跳现有页面不回归既测
- `POST /projects/{project_id}/skills/{tool_key}/generate` `ToolGenerateRequest{brief:str="", chapter_no:int|None(ge1), count:int|None(1..12), kind:str|None}` **200** `ToolGeneratePreviewResponse{tool_key, output_kind:str(产物 schema 名), preview:dict(各工具 output_schema 的 model_dump)}`。**预览不入库** `spec.tier` `get_tier_gateway_builder` 建网关 `run_generator`末尾 `commit()` 仅落 ledgercontext 派发PURE `services/toolbox_context.build_toolbox_context`brief_only/with_project`build_brief_context`with_world + world_entities 硬规则卡with_outline_chapter + 指定章 outline beats**缺章/缺大纲空节拍不报错**)。未知 tool_key404legacy 工具spec=None→**404**(前端走 legacy_route项目不存在404无凭据→**503** `LLM_UNAVAILABLE`
- `POST /projects/{project_id}/skills/{tool_key}/ingest` `ToolIngestRequest{world_entities:[WorldEntityCardView], chapter_no:int|None, scenes:[OutlineSceneIngestView{idx,beat,purpose,conflict,hook}], acknowledge_conflicts:bool=false}` **201** `ToolIngestResponse{table:str, created:list[str], rejected_tables:list[str]}`。** `ingest!=None` 的工具**golden-finger/glossaryworld_entitiesfine-outlineoutline其余→**422 VALIDATION**。入库 gate仿 `POST /characters`world_entities **continuity 预检analyst 网关)** 比对待入库实体 vs 既有世界观真相源——有冲突且 `acknowledge_conflicts=false`→**409 `CONFLICT_UNRESOLVED`** + `details{conflicts:[{type,where,refs,suggestion}],conflict_count}``partition_writes(spec,{table:items})` 白名单过滤越权`rejected_tables` 审计schemaDB JSONB 形变写库rules`{"rules":[...]}`细纲场景按 idx 升序拼成该章 beats`outline_write_repo.upsert_chapter`)。outline细纲不做 continuity 预检场景是粗节拍展开非设定/角色比对 chapter_no422未知/legacy404提交边界ledger + 写侧只 flush 端点末尾一次 `commit()` 409 路径也 commit precheck usage)。
- **注册表**`packages/skills/ww_skills/toolbox_registry.py` `ww_skills` 导出`TOOLBOX:dict[str,GeneratorTool]`11 + `get_tool(key)->GeneratorTool|None`新工具的 `key==spec.name``output_schema is spec.output_schema`
- **注入缝**`services/project_deps.py``get_tier_gateway_builder``TierGatewayBuilder=Callable[[Tier],Awaitable[Gateway]]`按工具 tier 动态建网关测试经 `app.dependency_overrides[get_tier_gateway_builder]` 注返 mock 网关的 builder + `get_world_entity_write_repo`/`get_outline_write_repo`/`get_memory_repos`/`get_project_repo` fakeschema-routing fake 网关按 `req.output_schema` IdeaListResult/ContinuityReview/…)。
- **守不变量**#2spec 只声明 tier/#3预览不写库入库经 continuity gate + 白名单/#9system_prompt 进缓存前块注入材料进 input)。**无新迁移**descriptor 在代码复用 world_entities/outline )。
- **@frontend 行动**`pnpm gen:api` 纳入 `GET /skills/toolbox` + `POST .../skills/{tool_key}/generate` + `POST .../skills/{tool_key}/ingest` schema `ToolboxListResponse`/`ToolDescriptorView`/`ToolInputFieldView`/`ToolGenerateRequest`/`ToolGeneratePreviewResponse`/`ToolIngestRequest`/`ToolIngestResponse`/`OutlineSceneIngestView`工具箱落地页据 `GET /skills/toolbox` 渲染卡片栅格legacy `legacy_route` 跳页新工具据 `input_fields` 声明驱动表单 generate 预览 可入库者 ingest + 复用 409 冲突裁决)。
## C8 · Skill registry + 表权限沙箱 owner @backend 状态: 稳定T5.5, 2026-06-19M5 R3 迁入 ww_skills 包, 2026-06-19
- 来源`ARCHITECTURE.md §5.6` / `PRODUCT_SPEC §5.5`声明式 Skill = AgentSpec「声明式权限 + apply 层白名单」,**非进程沙箱**)。消费方M5 编排/生成入库层技能库 UIT5.6 C3)。
- **实现位置M5 R3 已迁移**实现现落 `packages/skills/ww_skills/{skill_registry,skill_permissions}.py``packages/skills` 已是 uv workspace member)。`ww_skills/__init__.py` **直接导出**不再是再导出 ww_core 的门面)。消费方 `from ww_skills import SkillRegistry/SqlSkillRepo/SkillRecord/SkillRepo/partition_writes/filter_reads/validate_declaration/KNOWN_TABLES`。**`ww_core.domain` 已删除这两个模块 + `__init__` 导出**不再经 ww_core.domain 暴露 skill 符号)。历史T5.5 曾因 skills workspace member 暂落 ww_core.domain decisions现已迁回

View File

@@ -14,6 +14,13 @@
---
## [2026-06-22] 限流rate limiting原型期**延后**,记触发条件 — @backend (P2 code-review)
- 背景:代码审查指出触 LLM 的端点draft/review/accept/outline/style/refine 等)+ `/oauth/start` + `/settings/providers/test` 缺速率限制;审查约定「若接受延后,记入 memory/decisions.md」。
- 选择:本期**不加**任何限流代码/依赖(不引 `slowapi` 等)。
- 理由 / 取舍:原型显式为**单用户**CLAUDE.mdauth/多租户化已 deferred`users`/`owner_id` 是 stub无公网暴露、无多调用方滥用面KISS/YAGNI——为零真实风险的场景引入限流中间件 + 新依赖属过早工程。代价若误暴露到公网LLM 端点可被滥用刷成本(已记触发条件兜底)。
- 触发revisit when**引入多租户/鉴权** 或 **应用对外公开暴露** 时,须补轻量限流——按 IP/token 对 LLM 端点 + `/oauth/start` + `/settings/providers/test` 加每窗口配额(届时再评估 `slowapi` 或反代层限流)。
- 影响:无契约/代码改动;仅本条记录决策与重启条件。
## [2026-06-19] Kimi Code OAuthtoken 存储包 + 刷新启发式 + 省略 scope + 后台轮询走 jobs — @backend (K1.3)
- 背景Kimi 订阅 plan 走 device-flow OAuth需定 token 持久化形、何时刷新、device authorization 是否带 scope、后台轮询如何挂。
- 选择:

View File

@@ -83,3 +83,4 @@
- [2026-06-19] @backend Kimi Code device authorization **必须带 `scope=kimi-code`**`start_device_authorization` 请求体 = `{"client_id": <id>, "scope": "kimi-code"}`。K1.5 联网实测省略 scope 登录能拿到真 JWT但调 `api.kimi.com/coding/v1` `401 Invalid Authentication`——token coding entitlement`scope=kimi-code` coding-agent 文档化 scope`ooojustin/opencode-kimi` `constants.ts` 发送kimi-cli v1.41.0 已不发但服务端仍接受)。scope **只**放 device_authorizationtoken 交换(`poll_token`/刷新(`refresh`**不带** scopedevice flow 惯例)。⚠ 此前 K1.3 单测/decision/contract 省略 scope」(误信 kimi-cli v1.41.0已全部修正
- [2026-06-19] @llm **Kimi Code coding API 伪造头真源 = `ooojustin/opencode-kimi``src/headers.ts`+`src/constants.ts`1:1 镜像 kimi-cli v1.37.0),发完整 7 头、UA `KimiCLI/1.37.0`**K1.2 校正推翻上方 pi-kimi-coder UA-only 那条`picassio/pi-kimi-coder` 是分歧/错误参考只发 UA照它裁成 UA-only 是误修已回退`kimi_code_headers()` `User-Agent=KimiCLI/1.37.0` + `X-Msh-Platform=kimi_cli`字面常量 OS + `X-Msh-Version=1.37.0`==UA 版本+ `X-Msh-Device-Name``socket.gethostname()` ASCII + `X-Msh-Device-Model`macOS=`f"macOS {platform.mac_ver()[0]} {platform.machine()}"``machine()` 原样 `arm64`/`x86_64` 不归一化+ `X-Msh-Os-Version``platform.version()`+ `X-Msh-Device-Id`稳定 32 位无连字符 `uuid4().hex`env `KIMI_DEVICE_ID` 优先否则读/ `~/.kimi/device_id` 复用**绝不每次随机**否则 Kimi 侧每次当新设备)。源码若与此有出入以源码为准本次核对 master 一致)。⚠ HTTP 头值含非 ASCII 会被底层 fetch/httpx host 派生值Device-Name/Model/Os-Version ASCII `asciiHeaderValue` `\x20-\x7e` 之外 + trim空回退 `unknown`)。单测用 env `KIMI_DEVICE_ID` 注入固定 id CI 确定不触真 ~/.kimi)。ruff E501 按显示宽度算中文占 2 含中文 docstring/注释的行别贴边 100
- [2026-06-19] @backend `assemble`C5此前只从 world_entities/characters/style/rules + cards/foreshadow/digests/outline.beats 组装**从不含项目 premise/logline/theme/title也无写章指令** 全新项目只有 premise无世界观/角色/大纲产出 `stable_core=""`+`volatile=""`writer user message 为空LLM `400 "the message at position 0 with role 'user' must not be empty"`修复`MemoryRepos` 加第 8 repo `project:ProjectSpecRepo``spec(project_id)->ProjectSpecView{title,logline?,premise?,theme?}` project_id `projects` owner 过滤——owner 隔离归路由层`assemble` 作品蓝本放进 `stable_core` 首段书级 spec=定型→缓存前缀,不违 #9并让 `volatile` 始终含 `请创作第 {chapter_no} 章的正文。`易变指令)。⚠ 任何手搓 `MemoryRepos(...)` 的地方含测试 fake现在**必须** `project=` 字段否则 dataclass 缺参报错 DDL 变更只读既有 projects 无迁移
- [2026-06-22] @frontend/@orchestrator T6 创作工具箱通用端点的两处契约边界加新生成器时注意):① `ToolGenerateRequest` **固定字段并集** `{brief, chapter_no?, count?, kind?}`——`GeneratorRunner` 只映射落在此集合内的 `input_field` 新生成器若声明了集合外的 `input_field`前端会**静默忽略**该字段新工具的 `input_fields` 须落在 `brief/chapter_no/count/kind` 否则需先扩 `ToolGenerateRequest` 契约+`pnpm gen:api`)。② `ToolInputFieldView.type` 是自由字符串后端可声明 `"select"` descriptor **无 `options` 字段** 前端把 select text 输入渲染若某工具需要真下拉选项须先给 `InputField`/`ToolInputFieldView` `options` 再改前端 `GeneratorRunner`