Files
jiang be6317eb48 feat(server): integrate MCP queries and investigation messaging
Add project-bound OAuth MCP queries and controlled analysis with scoped evidence,
limits, timeouts and a nearby-model spatial query using EPSG:4326 inputs.
Default nearby searches to 200 meters and five results.

Integrate investigation messaging/presentation, health/readiness and typed history;
remove duplicate web search and fix online simulation and compressed baseline writes.
Include project migrations 023-025; keep local credentials and demo seeding excluded.

Validation: 319 focused pytest checks; real PostGIS temporary-table regression;
read-only Jinqian bridge radius comparison; cross-language MCP discovery/calls.
2026-10-19 18:46:58 +08:00

129 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 工况任务后端契约
`resources/sql/014_operational_tasks.sql` 提供项目隔离的统一工况任务存储。所有接口均位于 `/api/v1`,使用产品 Bearer 身份和 `X-Project-Id`;任务正文不会接受客户端提供的项目 ID。
## 三类任务
- 定时任务:当前产品继续使用前端 mock。`scheduled_flow` 及后端计划接口为已有保留能力,不接当前前端、不由当前 worker 调度或执行。
- `external_work_order`:外围预约、维修等工单的只读任务投影。`source.kind + source.external_id` 在项目内唯一,进度以外部版本反馈为准。
预约任务采用单层结构:一张外围工单对应一条任务,工单快照直接提供状态、位置、负责人、预约时间、回执要求和按发生时间记录的 `activity`。活动仅由工单创建、修改或显式现场反馈产生;不能按当前状态补造派发、执行、复令三个阶段。旧快照中的 `subtasks` 仅在读取时兼容并丢弃,不回写外围工单或历史版本;后续外围修订同步为新结构。
- `event_investigation`:投诉、爆管上报或人工发起的调查。Agent 查询受控数据,发布执行图、证据引用和报告;现场反馈会创建新的执行 generation,让 Agent 重新分析。
调查计划由 Agent 按本次工单或人工请求动态生成。新计划使用 `execution-plan@2`,节点声明执行步骤、条件判断、并行分叉/汇合和审核点;判断节点记录所选路线,审核点引用持久化申请或动作。重新分析追加计划修订和新节点,不使用环形连线。旧 `execution-plan@1` 快照保留原样读取。字段、约束与示例见 [`dynamic-investigation-plan.md`](dynamic-investigation-plan.md)。
调查数据读取沿用账号已有的项目权限,已有来源读取权限不需要逐任务审批或委托凭据。显式数据审批路径保留兼容:通过 `POST /operational-tasks/{task_id}/access-requests` 创建申请,`POST .../access-requests/{access_request_id}/decision` 由具备项目审核权限及对应数据源读取权限的用户决定。申请、批准范围和审核身份保存在任务库中,批准或拒绝后重新排队分析。兼容委托路径使用 `OPERATIONAL_DATA_DELEGATION_BEARER`,不能用普通 Agent 会话的工具许可代替任务授权。账号权限、委托和工作身份边界见 [工作进程文档](operational-task-workers.md)。
工单草稿可通过 `PATCH .../actions/{action_id}/draft` 修改并增加审计版本;`POST .../actions/{action_id}/confirm-dispatch` 使用一次人工操作批准当前版本并幂等提交,结果不明时继续使用回执核对。其他动作类型维持既有审批接口,不在调查页伪装为已支持的工单编辑动作。
创建请求使用 `request_id` 幂等。任务执行使用独立的 `run_id + execution_request_id`:Agent 必须领取当前 run,并通过 run 级心跳维持 60 秒租约。正常结束必须显式 complete;缺少同代最终计划或报告会以 `ARTIFACT_INCOMPLETE` 失败。`POST /operational-tasks/leases/recover` 会处理过期执行,连续三代超时后停止自动重试。
## 主要接口
| 方法和路径 | 作用 |
| --- | --- |
| `POST /operational-tasks` | 幂等创建任务 |
| `GET /operational-tasks` | 按类型、状态分页读取任务 |
| `GET /operational-tasks/{task_id}` | 读取当前计划、报告、证据摘要、审批动作和当前用户权限 |
| `GET /operational-tasks/{task_id}/agent-context` | 读取 Agent 任务上下文及当前身份允许的数据源 |
| `POST /operational-tasks/{task_id}/retry` | 无活动运行时,为原任务创建新一轮执行 |
| `POST /operational-tasks/{task_id}/runs/{run_id}/claim` | 使用当前执行请求号领取 queued run |
| `POST /operational-tasks/{task_id}/runs/{run_id}/heartbeat` | 续期当前 Agent run,不递增任务业务 revision |
| `POST /operational-tasks/{task_id}/runs/{run_id}/complete` | 校验同代最终产物并显式结束 run |
| `POST /operational-tasks/{task_id}/runs/{run_id}/fail` | 使用稳定失败码显式终止 run |
| `POST /operational-tasks/{task_id}/data-query` | 查询固定白名单数据源,同时保存有界原始快照并返回证据 ID |
| `POST /operational-tasks/{task_id}/access-requests` | Agent 创建任务级数据访问申请 |
| `POST .../access-requests/{access_request_id}/decision` | 人工批准部分或全部申请范围,或拒绝申请 |
| `POST /operational-tasks/{task_id}/agent-artifacts` | 追加证据、计划快照和报告修订 |
| `POST /operational-tasks/{task_id}/human-evidence` | 保存用户补充信息并重新排队分析 |
| `POST /operational-tasks/{task_id}/external-feedback` | 幂等同步稳定外部事件流的单调版本 |
| `POST /operational-tasks/{task_id}/actions` | 提议需要人工确认的动作 |
| `POST .../actions/{action_id}/decision` | 有对应审批权限的用户确认或拒绝 |
| `PATCH .../actions/{action_id}/draft` | 人工修改工单草稿并产生新审核版本 |
| `POST .../actions/{action_id}/confirm-dispatch` | 一次确认当前草稿并幂等下发工单 |
| `POST .../actions/{action_id}/execute` | 重新校验执行权限后通过类型化适配器提交 |
| `POST .../actions/{action_id}/reconcile` | 仅使用固定执行请求号查询不确定或已提交动作的回执 |
| `GET /operational-task-notifications` | 返回当前用户可见、按用户区分已读状态的通知 |
报告采用不可变修订,正文只保存当前结论、结论边界、建议、证据引用和待确认事项。详细的 Agent 工具轨迹和模型内部推理不进入报告。系统数据只能通过受控 `data-query` 形成证据,外围反馈和人工补充也有各自入口;Agent 不能自行声明这三类证据。合成演练证据始终标记为 `synthetic`。
## 调查轮次与协作边界
本节按 2026-10-13 工作区代码核对,区分已实现行为和待接入功能,不代表运行环境已完成部署。
### 第二轮调查的触发条件
下列业务入口会要求重新分析。只有服务端接受并保存请求后才改变任务,聊天文字本身不构成触发条件。
| 入口 | 触发条件 | 当前处理 |
| --- | --- | --- |
| 人工补充证据 | 协作 Agent 调用 `add_task_human_evidence`,通过 `human-evidence` 保存用户明确提供或确认的事实 | 保存 `human_input` 证据并重新排队;假设、查询要求和 Agent 推断不能当作现场事实 |
| 外围工单反馈 | `external-feedback` 接受原事件的新版本,或符合关联校验的现场工单新反馈 | 重新排队;重复版本不重复触发,子工单创建回执不作为已结束调查的新现场反馈 |
| 动作审核 | `decide_action` 接受人工驳回 | 重新排队,让调查重新判断处置方案;仅批准不直接触发 |
| 动作执行与回执核对 | `submit_action` 或 `reconcile_action` 得到 `succeeded`、`failed`、`unsupported` | 重新排队;`submitted`、`unknown` 不走这一终态触发分支,后续由回执或外部反馈推进 |
| 数据授权审批 | 兼容的显式审批路径接受批准或拒绝 | 按可用范围重新排队;已有项目数据读取权限不需要逐任务审批,见工作进程文档 |
| 显式重新调查 | 调用 `POST /operational-tasks/{task_id}/retry`,且不存在活动运行 | 为原任务创建下一轮;后端接口已实现,主 Agent 的受控工具入口尚未接入 |
| 租约超时恢复 | 工作进程发现执行租约过期 | 将旧运行记为 `timed_out`,创建下一轮;连续三轮超时后任务失败,停止自动重试 |
业务重新排队统一经过 `OperationalTaskService._requeue_task`,显式重试和租约恢复分别由 `retry_task`、`recover_expired_leases` 处理。具体实现见 [任务服务](../app/services/operational_tasks.py)。
### 轮次合并、替代与执行
- 原任务的 `task_id` 保持不变,历史报告修订保留。创建下一轮时,`execution_generation` 加一,并分配新的 `run_id` 和对应的 `execution_request_id`;报告版本号不是调查轮次。
- 业务重新排队时,如果已有 `queued` 运行,补充信息合并到该待执行轮次,不增加轮次或另建运行。
- 如果已有领取中或执行中的运行,业务重新排队会将其标记为 `cancelled`,失败码为 `SUPERSEDED_BY_REQUEUE`,再创建下一轮。旧轮次未投递消息会失败,不自动转投新轮次;旧执行也不能覆盖新轮次结果。
- 显式 `retry` 与上述合并逻辑不同:存在活动运行时返回 `ACTIVE_RUN_EXISTS`,不会替代或再建一轮。
- 同一幂等请求的重放不重复创建轮次。外部反馈还按来源版本去重,不能把重复提交或重复通知当作新反馈。
- 保存为 `queued` 只表示已排队。后台工作进程领取并交给 Agent 执行后才算开始调查,浏览器不负责启动或维持执行。新一轮结束时追加报告,不改写旧报告。
### 协作对话和运行消息
任务页的“Agent 协作”打开或复用独立的 `task_collaboration` 会话。它不是后台 `task_execution` 会话,也不等于主对话向调查运行发送消息。已结束的任务仍可打开协作,讨论报告和证据,不因执行结束而禁用入口。
点击按钮只打开对话、显示引用并进入编辑状态,保留已有草稿,不自动发送。普通问题读取授权任务上下文及所选报告后回答,不改变轮次。查看报告、刷新页面、更新 Mermaid 或报告修订本身也不会创建下一轮。
主 Agent 的 `send_task_message` 必须指定核实过的 `task_id` 和 `run_id`,只接受 `queued` 或具有有效租约的 `claimed`、`running` 运行。新消息发往已结束轮次时返回 `409 RUN_TERMINAL`,不会唤醒旧执行、创建新轮次或改投最新轮次。消息属于讨论输入,不自动保存为人工证据。投递与回执规则见 [工作进程的私有消息说明](operational-task-workers.md#普通对话与调查运行的私有消息)。
### 前端引用与说明
任务页保留单一“Agent 协作”入口,不直接展示 `task_id`、`run_id`,但底层仍保留任务和会话绑定。对话引用区只显示任务名称、所选报告的轮次和版本,以及证据或缺口引用说明。轮次取自所选报告快照,旧报告缺少轮次时省略,不拿任务当前轮次补齐。
引用区不显示实时执行状态,不以缓存的“调查中”或“已结束”决定消息能否投递。发送时由后端重新校验目标和状态,历史报告引用不随任务进入新轮次而改变。
- 按钮提示:打开调查协作对话,引用此任务,不会直接发送。
- 输入框提示:可询问调查进展、讨论报告或补充信息。
### 待接入功能
目前保存人工证据会直接重新排队,没有单独的重跑确认步骤。后续拟在保存前明确提示“保存此信息将启动新一轮调查”;若已有待执行轮次,则应说明会合并到该轮次,不能把确认步骤描述为已经实现。
主 Agent 尚不能通过普通消息重启已结束任务。后续受控的重新调查工具应在用户明确要求后读取最新任务,校验项目权限和活动运行,再为原任务创建新轮次并关联补充要求。目标是保留原 `task_id`、返回新 `run_id` 并等待后台领取,不复活旧运行,不把普通提问隐式转换成重跑。
## 人工确认与执行真实性
动作分为 `create_work_order` 和 `issue_command`,审批状态与执行状态分别保存。每次状态变化都会写入不可变动作版本;首次提交生成固定 `execution_request_id`,后续回执核对不改变它。
工单审批和执行可授予项目成员。设备命令审批、执行只授予具有系统管理身份且同时能进入项目的人员。普通查看者只能看任务及其 `permissions`,不能从前端角色名称推断或绕过服务端权限。
`approved` 只表示允许提交。`submitted` 需要外部接收回执,`succeeded` 还需要完成时间和完成回执。未配置适配器返回 `unsupported`,响应丢失返回 `unknown`;这两种情况都不会被记录为执行成功。外部适配器及 worker 的部署方式见 `docs/operational-task-workers.md`。
## 数据库部署
数据库变更遵循 [`workbench-database-rollout.md`](workbench-database-rollout.md)。后续默认只在 `tjwater_v2` 应用,验证通过并由维护者明确确认后,再逐库扩展。不要从 Metadata DB 枚举所有活跃项目并批量写入。
应用前确认当前 DSN 指向 `tjwater_v2`:
```bash
test "$(psql "$PROJECT_BIZ_DSN" -XAtc 'select current_database()')" = "tjwater_v2"
```
`008`、`011`、`014`、`015` 和 `017` 属于历史基线;不要在旧工单表退役后重新运行 `011`,否则会重建它。`019` 和 `022` 都带有 `tjwater_v2` 物理数据库名硬门禁,其他 BizDB 尚未应用。完成备份和回滚演练后,仅在已核对的 `tjwater_v2` 执行迁移:
```bash
psql "$PROJECT_BIZ_DSN" -v ON_ERROR_STOP=1 -f resources/sql/019_operational_task_access_requests.sql
psql "$PROJECT_BIZ_DSN" -v ON_ERROR_STOP=1 -f resources/sql/021_retire_workbench_work_orders.sql
psql "$PROJECT_BIZ_DSN" -v ON_ERROR_STOP=1 -f resources/sql/022_operational_notification_lifecycle.sql
```
`017_workbench_schema_comments.sql` 只增加历史基线的数据库可见注释。`018_remove_synthetic_check_task_projections.sql` 只清理错误进入预约任务的旧种子检查投影。`020_prune_non_actionable_notifications.sql` 删除历史任务创建、报告更新和正常完成通知,保留审批与异常通知。`021` 只有在三条演示工单均已迁入外围样板并形成有效工况任务时才删除旧表,不修改外围源及历史修订。`022_operational_notification_lifecycle.sql` 仅限 `tjwater_v2`,为保留通知回填 `active|resolved` 生命周期和业务对象引用,不新增表。通知路由和状态流见 [`operational-notification-routing.md`](operational-notification-routing.md)。SQL 文件包含自己的事务边界,不能把外层 `ROLLBACK` 当作演练;具体要求见数据库变更门禁。