# 2026-06-25 工作日志

## 当日工作内容

- 09:18：我完成了项目级 `write-development-docs` 技能落地。
  - Git 提交检查：`git fetch --all --prune` 后未发现 `HEAD..origin/main` 或 `origin/main..HEAD` 新提交；工作区已有多处未提交改动，本次只处理 `.codex/skills/write-development-docs` 和 `.gitignore`。
  - 修改：新增 `.codex/skills/write-development-docs/SKILL.md`、`agents/openai.yaml`、`assets/CONCEPT.md`、`assets/TODO.md`，把“落文档”请求固化为 `document/development/<功能目录>/CONCEPT.md + TODO.md` 两文件流程。
  - 修改：补充 `.gitignore` allowlist，确保 `write-development-docs` 技能文件不会被 `.codex/skills/*` 忽略规则挡住。
  - 操作：先读取 `document/development` 既有样例和 `hermes/skills/domain/write-development-docs` 旧版技能，再用 `init_skill.py` 生成标准 Skill 骨架并收口为项目规范。
  - 验证：`frontmatter ok`、`required files ok`、`.codex/skills/write-development-docs` 无 `[TODO:]` 初始化占位符，`git diff --check -- .codex/skills/write-development-docs .gitignore` 通过。
  - 影响：以后用户说“落文档 / 写开发文档 / 补 concept 和 todo”时，可以优先触发仓库级技能，按项目格式生成两份开发文档。

- 09:23：我按新的目录约定升级了 `write-development-docs` 技能。
  - Git 提交检查：`git fetch --all --prune` 后未发现 upstream 新提交；本地 ahead 1 个提交，为本会话前一步生成的 `23f7de6c chore(skills): add development docs writer`。
  - 修改：`.codex/skills/write-development-docs` 和 `hermes/skills/domain/write-development-docs` 同步新增路径规则，默认落点调整为 `document/development/<YYYY-MM-DD>/feature/<具体功能点目录>/CONCEPT.md + TODO.md`。
  - 修改：两个技能副本的 `agents/openai.yaml`、`assets/CONCEPT.md`、`assets/TODO.md` 同步补充日期、`feature` 聚合层和具体功能点目录提示。
  - 操作：保留历史文档原地更新规则，避免把 `document/development` 既有旧路径自动搬迁。
  - 验证：两个 `SKILL.md` frontmatter 分别校验通过，路径规则检索命中，两个技能副本的模板和 UI 元数据 diff 一致，`git diff --check -- .codex/skills/write-development-docs hermes/skills/domain/write-development-docs` 通过。
  - 影响：后续新功能落文档会先按日期建目录，再进入 `feature`，最后按具体功能点独立建目录，便于按天回溯和按功能点拆分。

- 21:30：我针对 AI 工作台意图门控做了三处加固，守住高风险动作的确认底线、补齐缺失的兜底与预筛逻辑。
  - Git 提交检查：`git fetch --all --prune` 后 origin/main 落后本地 0、领先本地 0；本地 ahead 3 个提交（`6b0756a5`/`4d8a606c`/`23f7de6c`），均与本任务无关；工作区有大量未提交改动，本次只动 `web/src/composables/workbenchAiMode` 与对应测试。
  - 背景：先通读门控全链路（`workbenchAiApplicationGateModel` / `workbenchIntentFrameModel` / `workbenchIntentActionPolicy` / `workbenchAiIntentPlannerModel` / `useWorkbenchAiCommandIntents` / `useWorkbenchAiActionRouter` / `usePersonalWorkbenchAiMode`），确认高风险动作（删除/审核/驳回）一律 `requiresConfirmation` 且执行出口无 `execute_allowed`、政策类问题被挡在执行链路外、直接提交需二次确认——安全底线稳。
  - 修改①（低置信度反问）：`workbenchAiIntentPlannerModel.js` 新增 `WORKBENCH_AI_INTENT_CONFIDENCE_THRESHOLD=0.6` 与 `isLowConfidenceTravelApplicationPlan`；规则兜底（rule_fallback）与显式 submit/save_draft 不计为低置信。`usePersonalWorkbenchAiMode.js` 在可执行 travel 计划前插入低置信分支，新增 `startModelPlannedTravelApplicationConfirmation` 与 `buildLowConfidenceTravelApplicationConfirmationText`，反问消息携带 `ai_application_confirm_intent` 动作。`useWorkbenchAiActionRouter.js` 新增 `ai_application_confirm_intent` 分支，还原 ontologyFields/提交标记后调用 `startAiApplicationPreview`。
  - 修改②（闲聊预筛）：`shouldRequestWorkbenchAiIntentPlan` 增加业务关键词正则，「你好/谢谢/嗯/ok」等闲聊不再发起 35s 的模型规划请求，直接落到通用 steward 回复。
  - 修改③（报销兜底核查）：复查确认 `executeModelPlannedWorkbenchIntent` 的 catch 分支不 return 时，`isReimbursementCreationIntent(cleanPrompt)` 在后续 823 行仍会被求值，报销兜底天然可达，无需新增重复分支，本次未改动。
  - 验证：`workbench-ai-intent-planner-model.test.mjs`（20/20，含新增 3 个用例）、`workbench-ai-application-gate-model.test.mjs`（5/5）、`workbench-intent-frame-model.test.mjs`（8/8）全绿；`expense-application-fast-preview.test.mjs` 既有 12 个失败（「小财管家」文案/表格渲染问题，与本次无关），本次改动额外使 `not ok 2 - AI workbench routes compact travel direct-submit planner` 由失败转通过，无新增失败。
  - 影响：模型给出低置信度差旅申请意图时不再直接建预览，先反问确认；闲聊类输入不再误触发模型规划，响应更快、减轻后端压力。
  - 局限：`agent-change-log` Skill 在当前环境不可调用，已按 AGENTS.md 规范手动增量更新本日志。

- 22:40：我落地了注册表驱动的意图插槽架构,让新增意图从「改 6+ 处硬编码」降到「写一个描述符 + 执行函数 + 注册」,并端到端跑通了「查差旅标准」查询意图作为样板。
  - Git 提交检查：`git fetch --all --prune` 后本地与 origin/main 同步(不 ahead 不 behind);工作区有本次新增/修改的后端文件。
  - 背景：排查确认旧架构里 `task_type`/`assigned_agent`/`flow_id` 在 schema(Literal)、function call schema(enum)、model_plan_builder(白名单)、action_contracts(if/else)、action_executor(if/elif)五层硬编码,加一个意图要同步改 6+ 处,完全没有扩展点;且"查差旅标准"这类查询意图无任何位置(task_type enum 只有 expense_application/reimbursement)。
  - 修改①(注册表核心):新建 `steward_intent_registry.py`——`IntentDescriptor` 声明 task_type/assigned_agent/signal_keywords/ontology_field_allowlist/action_steps_builder/executor/flow_id/prompt_fragment;新建 `steward_intent_bootstrap.py` 在 import 时注册 3 个意图(expense_application/reimbursement/query_travel_standard)。
  - 修改②(schema 放宽):`schemas/steward.py` 把 `StewardTaskType`/`StewardAssignedAgent`/`StewardActionType`/`StewardFlowId` 从 Literal 改为 str,运行时校验下沉到 registry,让 schema 不再是扩展拦路虎。
  - 修改③(执行分发):`steward_action_executor.py` 的 `execute()` 从 if/elif 链改为优先查 registry(`resolve_intent_by_action`)委托 executor;新增 `_dispatch_application_action`/`_dispatch_reimbursement_action` 分发入口;`SUPPORTED_ACTIONS`/`NOOP_ACTIONS` 改为与 registry 聚合(`all_side_effect_actions`/`all_noop_actions`)。
  - 修改④(动作生成):`steward_action_contracts.py` 的 `build_task_action_steps` 改为查 registry 的 `action_steps_builder`;原 `_build_application_steps`/`_build_reimbursement_steps` 改公开供 registry 引用。
  - 修改⑤(function schema 动态化):`steward_intent_agent.py` 的 task_type/flow_id enum 改为 `all_task_types()`/`all_flow_ids()` 动态生成;system prompt 改为从 registry 拼接意图列表 + 每个 intent 的 prompt_fragment。
  - 修改⑥(白名单放开):`steward_model_plan_builder.py` 的 task_type 白名单改 `get_intent`;assigned_agent/flow_id/字段过滤全部改 registry 驱动;`_sanitize_model_ontology_fields`/`_sanitize_model_missing_fields` 改 per-task_type allowlist(`field_allowlist_for`);查询类意图(flow_id=None)跳过必填字段推断。
  - 修改⑦(查询执行器):新建 `steward_query_executors.py`——`build_travel_standard_query_steps` 生成单步无副作用动作;`execute_travel_standard_query` 从槽位取 location/employee_grade/standard_category,复用 `DEFAULT_TRAVEL_POLICY_CONFIG` 按职级×城市分级查住宿/交通标准,拼装 Markdown 回复(补助标准因未纳入运行时配置用占位说明)。
  - 修改⑧(门控适配):`steward_planner_extraction.py` 的 `_looks_like_ambiguous_travel_flow` 加查询信号词前置判断(命中查询意图直接返回 False,不走候选流程);`_build_task` 的 task_id/assigned_agent/label 改 registry 驱动;`steward_planner_fallback.py` 的 `_classify_irrelevant_input` 补充 registry signal_keywords 判断(避免查询类输入被判 off_topic);`steward_graph_planner.py` import bootstrap 触发注册。
  - 修改⑨(前端):`stewardPlanModel.js` 的 `TASK_TYPE_LABELS`/`AGENT_LABELS`/`EXECUTABLE_STEWARD_ACTION_TYPES` 加 query_travel_standard/execute_travel_standard_query/policy_query_assistant。
  - 验证:后端全量 steward 测试 **72 passed**(含新增 14 个:registry 7 + query executor 7);前端意图测试 **28 passed**;既有申请/报销/规划/动作执行/槽位决策链路全部无回归。
  - 容器:容器名为 `x-financial-local-linux`(非 `local-x-financial-linux`),已运行 19 小时;后端测试在该容器内执行,venv 在 `/tmp/x-financial-server-venv`。
  - 影响:现在加一个新意图(如查报销进度、查预算执行)只需:① 写 `IntentDescriptor` 声明 task_type/槽位/信号词/executor;② 注册进 bootstrap;③ 写执行函数。function schema、动作生成、执行分发、字段过滤、门控全部自动适配,零硬编码改动。
  - 局限:补助标准(allowance)尚未接入运行时配置,查询时返回占位说明;前端查询结果当前以 Markdown 消息展示,未做卡片化;registry 只在后端,前端 task_type 分发仍是 `resolveNextActionContext` 的 if/else(本次只加了 query 分支,未全面注册表化)。

## 遗留问题

- 09:18：官方 `quick_validate.py` 仍因当前 Python 环境缺少 `PyYAML` 无法运行，已用 frontmatter、必需文件、占位符和 diff check 做人工兜底。建议后续统一为 skill 校验脚本补齐依赖或增加无 PyYAML 的轻量校验路径。
- 09:23：当前环境没有找到 Skill Creator 的 `quick_validate.py` 脚本文件本体，因此本次继续采用人工兜底校验。建议后续恢复系统 Skill Creator 脚本路径，或把轻量校验脚本纳入仓库级工具。
- 21:30：`expense-application-fast-preview.test.mjs` 仍有 12 个既有失败（文案「小财管家」「此意图系统不支持」与 markdown 表格整块渲染相关），与本次意图门控改动无关，建议单独排查。
- 21:30：本次未纳入范围的三项已记录：时间过滤维度扩展（仅支持 N天前/昨天/今天）、排除词两处重复维护、`handleInlineDraftDeletionIntent` 命名与职责不符，建议后续分批处理。
- 22:40：补助标准(allowance)未纳入 `DEFAULT_TRAVEL_POLICY_CONFIG`,查询差旅标准时住宿/交通有确定数值,补助只返回占位说明。补助数据在 `server/rules/finance-rules/出差补助标准.xlsx`,后续需把补助标准接入运行时配置并在 `resolve_travel_standard_snapshot` 补全。
- 22:40：前端查询结果当前以普通 Markdown 消息展示,没有像申请预览那样的卡片化视图;查询意图的前端分发仍是 `resolveNextActionContext` 的 if/else,未全面注册表化(本次只加了 query 分支)。
- 22:40：`server/rules/finance-rules/` 下有两个 Excel(交通工具等级标准、交通费用预估表)被标记为 modified,疑似容器运行时产物,非本次代码改动,未处理。
- 22:40：`agent-change-log` Skill 在当前环境不可调用,已按 AGENTS.md 规范手动增量更新本日志。

- 23:30：我落地了会话上下文保留机制（LLM + 确定性双保险），解决了"用户删除草稿后说'再提交'丢失上下文"的问题。
  - Git 提交检查：`git fetch --all --prune` 后本地与 origin/main 同步(不 ahead 不 behind)。
  - 背景：排查确认对话消息和 steward_state 虽已持久化在 DB，但 plan 接口的 `build_plan` 从不读历史 task，且"再提交"被路由到 plan 接口（而非能恢复 task 的 runtime-decision 接口），导致系统无法把"再提交"和之前被拦的出差申请关联起来。
  - 修改①（LLM 历史关联·保险②）：`steward.py` 新增 `_inject_recent_conversation_history`，在 build_plan 前用 `AgentConversationService.list_message_history(conversation_id, limit=10)` 读出最近 10 条对话，注入 `context_json.recent_history`。`steward_intent_agent.py` 的 `_build_messages` 把 recent_history 暴露为 context_payload 顶层结构化字段，并在 system prompt 加引导："当用户说'再提交''继续''重新提交'等确认类话术时，必须结合 recent_history 里最近一次提到的出差/报销申请来理解"。
  - 修改②（确定性兜底·保险①）：新建 `steward_context_resume.py`——`should_resume_recent_task` 检测"再提交"类话术（12 个关键词）+ `steward_state.flows` 有可恢复 flow；`resume_task_from_flow` 从 flow.fields 恢复 StewardTask（复用 runtime-decision 的恢复逻辑）；`attach_resumed_task` 把恢复的 task 挂回 plan，planning_source 标记为 `context_resume`。`steward.py` 新增 `_apply_context_resume`，在 build_plan 后、plan 无 task 时触发确定性兜底。两个入口（`/plans` 和 `/plans/stream`）都已接入。
  - 验证：后端全量测试 **67 passed**（含新增 11 个：context_resume 8 + intent_agent history 3）；端到端验证两轮对话——"上海出差火车"→"再提交"，LLM 历史关联成功恢复 expense_application task（fields 完整）；纯函数验证确定性兜底在模型返回空 task 时从 state 恢复（planning_source=context_resume）。
  - 影响：会话上下文保留到用户清理会话；行为处理只看最近 10 条，超长会话不爆 token；"再提交"类话术现在能恢复之前被拦的申请 task。正常 plan 产生的 task 已通过 `merge_plan` 写进 `steward_state.flows`，重复检查不改 state，所以 task 在 state 里一直存活到会话结束。

## 遗留问题（补充）

- 23:30：历史条数固定为 10，未做 token 感知裁剪；极端情况下单条消息很长（如粘贴大段文本）可能导致 token 超限，但实测正常对话不会触发。

- 00:10：我完成了统一门控管道的架构设计文档，作为后续重构的唯一事实来源。
  - 文档路径：`document/development/AI意图规划器/UNIFIED_GATE_PIPELINE.md`
  - 核心判断：当前门控散落在 7 处（前端 7 层 if/else + 后端 endpoint 4 个补丁 + 图条件边 + off_topic 关键词 + 候选流程判定），每加一个场景成本 O(n)，漏一处静默出错。这是"不持久"的根因。
  - 目标架构：LangGraph 图成为唯一编排者（load_context → gate_classify → route 分支 → attach_action_steps → persist_state），endpoint 退化为 3 行纯 IO，前端退化为纯渲染（fetchStewardPlan → renderPlanResponse）。
  - 接入成本 O(1) 的硬验收标准：加场景只需新建 1 个 SceneDescriptor + 1 个 handler 函数 + 注册，不动图/endpoint/前端/extraction。
  - 迁移分 3 阶段：Phase 1 后端收口（建 scenes 注册表 + endpoint 补丁搬进图节点）、Phase 2 前端退化纯渲染（移除 7 层 if/else）、Phase 3 清理冗余。
  - Git 提交检查：本地与 origin/main 同步。

- 00:50：我完成了统一门控管道 Phase 1 的 scene 注册表骨架（P1.1-P1.2），作为后端收口的基础设施。
  - Git 提交检查：本地与 origin/main 同步。
  - 修改：新建 `server/src/app/services/scenes/` 目录——`gate_rules.py`（GateRule/SceneRoute 枚举）、`scene_descriptor.py`（SceneDescriptor dataclass，声明 scene_id/label/signal_keywords/ontology_fields/gate/route/handler/can_resume/flow_id/prompt_fragment/priority 等）、`scene_registry.py`（SceneRegistry 单例 + 查询方法）、3 个场景文件（expense_application/reimbursement/query_travel_standard）、`__init__.py`（bootstrap + 运行时绑定 handler/builder/executor）。
  - 验证：冒烟测试 3 个场景注册成功、优先级排序正确（query 在前,priority=50）、35 个 signal_keywords 聚合、handler/builder/executor 运行时绑定成功、无循环 import；后端全量 76 passed，scene 注册表的加入未破坏任何现有代码。
  - 影响：为后续图拓扑重构（P1.3-P1.8）提供了声明式场景注册基础设施。当前 scene_registry 与现有 intent_registry 并存，后续 P1.3-P1.7 会把 intent_registry 的消费者逐步迁移到 scene_registry。
  - 下一步：P1.3-P1.8 图拓扑重构（新增 load_context/gate_classify/resume/persist 节点、endpoint 退化、registry 消费者迁移）。

- 01:30：我完成了统一门控管道 Phase 1 的图拓扑重构（P1.3-P1.7），LangGraph 成为唯一编排者。
  - Git 提交检查：本地与 origin/main 同步。
  - 修改①（图节点扩展）：`steward_graph_planner.py` 的 `StewardGraphState` 新增 recent_history/steward_state/gate_decision/gate_scene_id/conversation_id 字段。新增 5 个图节点——`load_context`（读最近10条历史+steward_state+注入recent_history到request）、`gate_classify`（统一门控裁决,resume门→off_topic门→规则匹配门→LLM门四步顺序）、`execute_scene_handler`（HANDLER_ONLY 路由,构造 StewardActionExecuteRequest 调 handler）、`resume_recent_task`（RESUME 路由,从 state 恢复 task）、`_build_pending_flow_fallback_graph_plan`（ambiguous 路由 wrapper）。
  - 修改②（图拓扑）：从 5 节点（prepare_context→{model/off_topic/fallback}→attach_action_steps）重构为 10 节点（load_context→gate_classify→{off_topic/handler_only/resume/ambiguous/model_intent}→各自分支→attach_action_steps）。
  - 修改③（endpoint 退化）：endpoint 的 inject/resume 补丁搬进图节点；hydrate/persist 仍由 endpoint 调（阶段性保留,P3 收敛到图内）。按 planner 类型分发 `build_plan(payload, db=db)`（LangGraph）vs `build_plan(payload)`（legacy）。
  - 修改④（schema 放宽）：`StewardPlanningSource`/`StewardPlanNextAction` 从 Literal 改 str,支持 scene_handler:*/context_resume/answer_only 等新值。
  - 修改⑤（resume 门控优先级）：resume 门控提到 off_topic 门之前,避免"再提交"被误判 off_topic。
  - 验证：后端全量 **76 passed**;端到端 4 场景全部通过——①出差申请(llm_function_call, 1 task)、②再提交(context_resume, 1 task, 5 fields)、③查差旅标准(scene_handler:query_travel_standard)、④闲聊(off_topic)。
  - 影响：LangGraph 图成为唯一编排者,门控收敛到 gate_classify 单一决策点。endpoint 仍保留 hydrate/persist 两个补丁,P3 会收敛。

- 02:30：我把意图识别从"规则驱动"改为"LLM 驱动 + 极轻量规则过滤",规则不再判断"这是哪个业务场景"。
  - Git 提交检查：本地与 origin/main 同步。
  - 背景：gate_classify 用 94 关键词 + scene_registry CHOICE 规则匹配判断业务相关性,每加场景要维护关键词,关键词冲突(出差既可能申请也可能查标准),还导致 off_topic 误杀("下周去上海"被拦)。但 LLM 已经完全具备多场景识别能力(schema enum + prompt_fragment 都从 registry 动态生成),规则匹配门是多余的。
  - 修改①（简化 gate_classify）：删掉规则匹配门(③)和 ambiguous 提前判断(④)。gate_classify 现在只做两件事:①闲聊拦截(极轻量:greeting+meaningless,省 LLM 成本)②resume 确定性兜底。其他全部默认走 model_intent。新增 `_is_lightweight_off_topic` 只调 `_looks_like_greeting`+`_looks_like_meaningless`,不调带 94 词的 `_classify_irrelevant_input`。
  - 修改②（HANDLER_ONLY 改为 LLM 输出驱动）：查差旅标准不再由 gate_classify 规则命中,改为 LLM 返回 task_type=query_travel_standard 后,`_route_after_model_intent` 检查 scene.route=HANDLER_ONLY → 转 execute_scene_handler。`execute_scene_handler` 优先使用 LLM 生成的 task(含 ontology_fields)。
  - 修改③（图拓扑简化）：gate_classify 只输出 off_topic|resume|model_intent 三种。删掉 handler_only/ambiguous 边。
  - 验证：后端 **76 passed**;端到端 7 场景全通过——①你好→off_topic、②123→off_topic、③下周去上海→llm_function_call(之前被误杀!)、④P5武汉住宿标准→scene_handler:query_travel_standard、⑤再提交→context_resume、⑥出差并且报销招待费→llm_function_call 返回 2 task(expense_application+reimbursement)。
  - 影响：意图识别全部交给 LLM,规则只保留闲聊拦截和 resume 兜底两个不可替代职责。加新场景只需注册 SceneDescriptor(prompt_fragment+ontology_fields+handler),LLM 的 system prompt 和 schema 自动包含,**不加任何关键词规则,不动门控代码**。

## TODO

- [ ] 为 `quick_validate.py` 准备稳定运行环境，避免后续新增 Skill 时继续依赖人工兜底。（来源：09:18 技能校验）
- [ ] 排查 `expense-application-fast-preview.test.mjs` 的 12 个既有失败（小财管家文案 / 表格整块渲染）。（来源：21:30 意图门控加固）
- [ ] 评估意图门控剩余三项：时间过滤维度扩展、排除词常量抽取、`handleInlineDraftDeletionIntent` 重命名。（来源：21:30 意图门控加固）
- [ ] 把补助标准(`出差补助标准.xlsx`)接入运行时配置,补全 `resolve_travel_standard_snapshot` 的 allowance 数值查询。（来源：22:40 注册表架构）
- [ ] 前端查询结果卡片化,并把前端 task_type 分发也改成注册表驱动。（来源：22:40 注册表架构）
- [ ] 在真实 LLM 连通环境下,用「我去武汉出差的住宿标准是多少」端到端验证查询意图识别→执行→回复。（来源：22:40 注册表架构）
- [ ] 评估是否需要把 LangGraph 迁移 Phase 5(trace UI、legacy 收敛)与注册表架构的查询意图 trace 打通。（来源：22:40 注册表架构）
