# xiaoqu-agent-service 项目指引 ## ai-proj 关联信息 - **ai-proj 项目 ID:224**(项目名 xiaoqu_agent_service)。本仓所有需求/任务创建后应挂到该项目,关联任务前确保任务也在项目 224。 - 近期需求示例:REQ-20261012-0004(id 5977,空感 toilet.airquality list/detail/metrics/statelog 4 工具,已部署生产+冒烟通过)→ 任务 23488;REQ-20261013-0006(id 5988,代理角色用户查询工具 agent.user.list.query,approved)→ 任务 23566;REQ-20261012-0002(id 5975,客户换纸记录工具 district.paper.trace.list.query,released)→ 任务 23486;REQ-20260924-0009(id 5806,人流量统计工具+统计类限一个月,implementation 完成)→ 任务 22405;REQ-20260924-0003(id 5800,设备生命周期查询工具,implementation 完成)→ 任务 22391;REQ-20260921-0007(id 5778,设备电量消息查询工具,released)→ 任务 22306;REQ-20260919-0003(id 5747,出入库工具复查修复,released)→ 任务 22123。 ## ai-proj MCP 已知限制与真实契约 - MCP `update_requirement` 不支持改 project_id/owner;改归属必须走 REST 两步: 1. `POST /api/v1/requirements/{id}/reassignment/preflight`,body=`{operation_id:"", expected_version:<当前sync_version>, target_project_id:224, target_owner_id?/clear_owner?}` 2. `POST /api/v1/requirements/{id}/reassignment/confirm`,body=`{operation_id, preflight_token}`(两个字段缺一不可;preflight_token 一次性,confirm 失败即作废,必须在同一条命令里 preflight+confirm 原子执行) - REST base 推导:mcp.json 里 ai-proj 的 url 去掉尾部 `/mcp/sse`(即 `https://ai.pipexerp.com/api/v1`),认证头 `X-API-Key` 同 mcp.json。 - ⚠️ 本机 shell(Bash 工具)直读 `~/.kimi-code/mcp.json` 会被托管层拦截("local Shell bypass is disabled for a managed MCP route");用 Read 工具读取不受限。 - 任务跨项目用 `move_task`,再 `link_tasks_to_requirement`(role=implementation)。 - REST 建需求/任务实测(2026-09-24):`POST /requirements` 与 `POST /tasks` 的 body 带 `project_id:224` 会创建失败/被忽略,先裸建再走 reassignment;关联用 `POST /requirements/{id}/tasks` body=`{task_id, role:"implementation"}`;更新用 `PUT /requirements/{id}`(不支持 PATCH),`delivery_stage`/`reviewer_id` 为只读(PUT 静默忽略);`POST /requirements/{id}/submit` 可推进 draft→pending 并自动分配评审人,`POST /requirements/{id}/review` 仅评审人可调用;released 由 CLI sync 或评审/界面流转。 - 本机无 ai-proj CLI,远程同步需在有 CLI 的机器执行 `ai-proj sync requirement --id --direction to_remote`。 ## 生产 ES 集群关键坑 - 生产两个 ES 集群(cloud/local,app.es.cloud/local.*)均为 **ES 7.7.1**:elasticsearch-java 7.17 高阶客户端(ElasticsearchClient)的 vendor Content-Type(`application/vnd.elasticsearch+json; compatible-with=7`)被 406 拒绝,且其 X-Elastic-Product 响应头校验(7.14 才引入)过不了——**业务 ES 查询一律注入低阶 `esCloudRestClient`/`esLocalRestClient`(RestClient)bean**,不要注入 esCloudClient/esLocalClient(EsClientConfig 里仅为兼容保留)。 - 低阶 `org.elasticsearch.client.Response` 构造器包私有且参数是 HttpResponse,测试里用 `mock(Response.class)` + stub `getEntity()` 构造;注意 `thenReturn(esResponse(...))` 里嵌套 when() 会触发 UnfinishedStubbingException,必须先创建好 Response 再 stub。 - electricitymessage 索引(电量消息类):`createTime` 是 date 类型(format strict_date_optional_time||epoch_millis),range 必须发 **epoch 毫秒**(`yyyy-MM-dd HH:mm:ss` 字符串 400);`_source` 里 `value` 是**文本**("28"/"100")、`createTime` 是 ISO 文本(`2026-09-21T10:00:00.000+08:00`),读取时都要归一。老系统能跑是因为 transport 客户端把 Date 序列化成 epoch 毫秒、Jackson 把字符串 value 反序列化进 Integer 字段。 - paperuselog 索引(换纸日志):`rfid` 是 **text** 类型(mapping 有 `ignore_above=256` 的 keyword 子字段),terms 聚合必须用 `rfid.keyword`,直接聚合 text 字段被 7.7.1 以 400 "Text fields are not optimised for aggregations" 拒绝(2026-09-22 生产实测修复);老系统不做 ES 聚合,而是翻页拉全量文档后在 Java 侧按 `P_{positionsId}_{snCode}_{rfid}`/`T_{toiletId}_{snCode}_{rfid}` 复合 key 去重,MCP 用 terms 聚合近似(语义差异见设计文档)。 - curl 直连 MCP 冒烟:JSON body 务必 `-d @file`(行内 `-d` 长 JSON 经网关会被截断报 "Invalid message format");响应是 SSE,`sed -n 's/^data://p'` 取 JSON。 - 托管 MCP 工具(mcp__xiaoqu__*)在服务重启后 session 失效会报 AI_PROJ_MCP_SEND_OUTCOME_UNKNOWN,改走上面 curl 直连即可。 ## 生产部署要点 - `./deploy/deploy.sh remote-deploy`(SSH 别名 xiaoqu-prd,目录 /data/xiaoqu-agent-service)。 - **部署前必须先把 main 推 origin**:出现过本地 main 领先 origin 1 个修复提交、他人从 origin 构建部署导致生产跑到旧代码的情况(2026-09-22 paperuselog terms 结构 400)。 - 新工具上线后必须幂等绑 prod scope(svc 库 MySQL,tool 表唯一标识列是 `name`;scope 表是 `agent_scope`):hardware/district 系列惯例绑 scope 1(test)/2(operation)/3(xiaoqu-ai)/5(ops-query),工单/仓库系列只绑 scope 1。 - prod AK 的 Authorization 头值本身已含 "Bearer " 前缀,不要再拼。 ## 新 MCP 工具上线标准流程(2026-09-24 沉淀,经 REQ-20260924-0003/0009 两轮验证) 按顺序执行,每步完成再进下一步: 1. **设计评审**:先调研老系统接口(xiaoqu-franchisee 控制器行号级依据),写设计文档到 `doc/<工具名>-mcp-tool-design.md`, 含背景/老接口行为与坑/决策表(给出建议与备选)/参数契约/返回契约/与老系统差异/测试要点。 **先给用户评审,拍板后再动工**;拍板结论回写文档(含追加决策)。 2. **实现**:实体/Mapper/(ES Gateway)/Tool 类,遵循现有同类工具模式;枚举配 `*Text` 中文文案; Redis 字典缺失 fail fast;老系统明显 bug(NPE、错位、吞异常)不复制,在文档差异节注明。 注意两个已踩过的坑:**项目未注册 MyBatis-Plus 分页拦截器,分页必须 selectCount+LIMIT 物理分页,禁用 selectPage** (2026-09-24 生产实测 total=0 不分页);Jackson `ObjectNode.putArray` 同名字段会覆盖,filter 数组只建一次。 3. **测试**:每个工具至少覆盖 参数校验/权限/枚举映射/空数据/关键回归(对齐老系统 bug 修复点); `./mvnw clean package` 全量必须全过(构建需 JAVA_HOME 指向 .tools 的 JDK 17 + ComSpec 环境变量)。 4. **提交部署**:Conventional Commits(`feat(mcp): ...`),先 `git push origin main` 再 `./deploy/deploy.sh remote-deploy`;push 偶发 "Failed to authenticate user" 重试即可。 部署后确认远程日志 ToolSyncService「新增 tool」记录。 5. **scope 绑定**:按上面部署要点幂等绑 scope(INSERT IGNORE into scope_tool)。 6. **生产冒烟**:用 `/api/open/token/ensure`(X-Open-Key=$OPEN_API_KEY,旧版密钥可签用户 Token,不能签服务 Token) 给用户(如 id=812 王恒)签 ops-query Token → initialize 取 mcp-session-id → notifications/initialized → tools/call; 真实数据验证关键路径 + 边界(超限报错、不存在返回)。 7. **建 ai-proj 需求**:REST 创建(不带 project_id)→ reassignment preflight/confirm 挂 224 → 建任务(project_id 224、status completed)→ `POST /requirements/{id}/tasks` 关联(role=implementation)→ PUT 回写实施记录(提交号/测试数/部署 pid/冒烟结果/scope 绑定)→ `POST /requirements/{id}/submit` 推进到待评审。 released 只能评审或 CLI sync(本机与生产服务器均无 CLI)。 8. **收尾**:更新 `doc/agent-mcp-tools-reference.md`(追加工具章节,编号顺延)、 更新本文件「近期需求示例」;设计文档与代码同提交入库。