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:
@@ -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()` 仅落 ledger。context 派发(PURE `services/toolbox_context.build_toolbox_context`):brief_only/with_project→`build_brief_context`、with_world→ + world_entities 硬规则卡、with_outline_chapter→ + 指定章 outline beats(**缺章/缺大纲→空节拍不报错**)。未知 tool_key→404;legacy 工具(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/glossary→world_entities,fine-outline→outline);其余→**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` 审计);schema→DB JSONB 形变写库(rules→`{"rules":[...]}`;细纲场景按 idx 升序拼成该章 beats,`outline_write_repo.upsert_chapter`)。outline(细纲)不做 continuity 预检(场景是粗节拍展开,非设定/角色比对),无 chapter_no→422。未知/legacy→404。提交边界: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` 注 fake(schema-routing fake 网关按 `req.output_schema` 返 IdeaListResult/ContinuityReview/…)。
|
||||
- **守不变量**:#2(spec 只声明 tier)/#3(预览不写库;入库经 continuity gate + 白名单)/#9(system_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-19;M5 R3 迁入 ww_skills 包, 2026-06-19)
|
||||
- 来源:`ARCHITECTURE.md §5.6` / `PRODUCT_SPEC §5.5`(声明式 Skill = AgentSpec;「声明式权限 + apply 层白名单」,**非进程沙箱**)。消费方:M5 编排/生成入库层、技能库 UI(T5.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),现已迁回。
|
||||
|
||||
@@ -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.md:auth/多租户化已 deferred,`users`/`owner_id` 是 stub),无公网暴露、无多调用方滥用面;KISS/YAGNI——为零真实风险的场景引入限流中间件 + 新依赖属过早工程。代价:若误暴露到公网,LLM 端点可被滥用刷成本(已记触发条件兜底)。
|
||||
- 触发(revisit when):**引入多租户/鉴权** 或 **应用对外公开暴露** 时,须补轻量限流——按 IP/token 对 LLM 端点 + `/oauth/start` + `/settings/providers/test` 加每窗口配额(届时再评估 `slowapi` 或反代层限流)。
|
||||
- 影响:无契约/代码改动;仅本条记录决策与重启条件。
|
||||
|
||||
## [2026-06-19] Kimi Code OAuth:token 存储包 + 刷新启发式 + 省略 scope + 后台轮询走 jobs — @backend (K1.3)
|
||||
- 背景:Kimi 订阅 plan 走 device-flow OAuth;需定 token 持久化形、何时刷新、device authorization 是否带 scope、后台轮询如何挂。
|
||||
- 选择:
|
||||
|
||||
@@ -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_authorization;token 交换(`poll_token`)/刷新(`refresh`)**不带** scope(device 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`。
|
||||
|
||||
Reference in New Issue
Block a user