fix: preserve task stop intent and guard remote execution boundaries

This commit is contained in:
Xin Wang
2026-10-23 19:53:17 +08:00
parent 8ac75a1873
commit 89c3bad58e
16 changed files with 199 additions and 20 deletions
+12 -2
View File
@@ -9,7 +9,7 @@
| A-004 | 任务作用域 | 平台生成 task_id,记录来源 conversation/assistant;管理接口沿用现有管理员鉴权 | interface | default |
| A-005 | 远端执行关联 | 一次任务一个 OpenCode session;提交前保存session及messageID,初始idle不算完成 | semantic | default |
| A-006 | 取消 | 原生Job取消后调用远端abort并核查;无法确认时保留待确认,不能宣称已停止 | semantic | default |
| A-007 | 通知 | 进度仅网页;完成/失败在线时触发前台总结;离线结果保留供查询 | semantic | default |
| A-007 | 通知 | 普通进度仅网页;完成/失败/取消/状态未知/等待输入在线时触发前台通知;离线结果保留供查询 | semantic | default |
| A-008 | 恢复 | 单进程;重启恢复观察有远端句柄的任务,不自动重新发送未知提交 | semantic | default |
| A-009 | 远端配置 | 地址/工作目录/Basic鉴权由管理员工具配置,任务冻结连接信息;LLM不能改地址和目录 | interface | default |
| A-010 | 补充输入 | 本版仅支持回答远端问题及权限确认;任意实时修改要求需先停止再提交,不自动批准权限 | semantic | default |
@@ -17,6 +17,16 @@
| A-012 | 外部编码变更 | 仅明确调用任务工具时会启动远端执行,不因查看页面自动执行 | semantic | default |
| A-013 | 任意运行中修改任务 | 本版支持远端待回答的问题/权限;任意实时 steer 延后,新增要求通过停止后重新提交 | semantic | default |
| A-014 | 任务API字段 | 与现有API一致使用camelCase;执行器内部保持Python snake_case | interface | default |
| A-015 | 同一工作区并发修改 | 本版原生execute Job采用全局FIFO串行;排队也计入任务时限,避免同时改同一仓库 | semantic | default |
| A-015 | 同一工作区并发修改 | 本版原生execute Job采用全局FIFO串行;排队计入本次等待时限;另有未结束远端句柄时不启动新的编码提交,状态未知/停止未确认需先核查。不能约束平台之外的远端客户端 | semantic | default |
| A-016 | 状态未知后恢复 | 网页提供显式核查/重新观察;只重连已有远端句柄,绝不重复发送原任务 | semantic | default |
| A-017 | 自动测试边界 | task工具不出现在普通工具模拟列表;测试运行器中调用task只返回不支持,真实执行需手动语音调试 | semantic | default |
| A-018 | 远端结果保留与通知长度 | 平台保存消息API关联到本次任务的完整助手文本及错误文本,不包含工具原始日志;独立session.error事件仅保留失败提示;语音上下文最多保留首尾各8000字符并显式标记省略,完整结果可在网页查看 | semantic | sweep |
| A-019 | 超时定义 | 时限针对每次本地观察Job(包含排队);重启/重新观察重新计时,超时与用户停止都进入远端abort核查,确认停止后才显示cancelled,不作为累计任务寿命上限 | semantic | sweep |
| A-020 | 自动测试控制函数 | 测试运行器中提交、查询、取消、回复全部拒绝,不访问真实任务服务;修复首轮审阅发现的仅提交有防护问题 | semantic | sweep |
| A-021 | 任务列表范围 | 管理API最多100项,优先未结束任务;网页展示全部返回项,无分页,更早记录可凭task_id查询/控制 | interface | sweep |
## Sweep corrections
- A-001:修复服务端曾接受 literal/variable 参数的启动 Action task 绑定;不支持即在保存时拒绝。
- A-006:修复重启后丢失 cancel_requested;恢复Job先重试远端停止,不恢复普通执行观察。
- A-007:明确等待输入、未知、取消也会在线通知,与代码一致。
- A-015:原生FIFO不能单独保证外部执行串行;新增提交前的业务检查,已有未结束远端句柄时不再发送新编码指令。该检查不创建第二套队列。
+37 -3
View File
@@ -13,12 +13,46 @@
- F1: 原生 Job + httpx 模拟远端协议共 3 项测试通过;未调用真实模型。
- F2: 累计 7 项原生Job、协议、服务生命周期与密钥保留测试通过。
- F3: 累计9项后端任务测试通过,TypeScript检查通过,前端lint无错误(BatchTestPage已有1条warning)。
- F4: `make test` → exit 0;360 项后端测试、4 项前端契约测试、5 项公开契约检查与生产构建通过。前端已有 BatchTestPage useEffect warning 1 条,无错误。
- F4: `make test` → exit 0;367 项后端测试、4 项前端契约测试、5 项公开契约检查与生产构建通过。前端已有 BatchTestPage useEffect warning 1 条,无错误。
- F4: 初次回归发现公开契约快照需更新;随后构建受沙箱 Google Fonts 网络限制,联网重试通过;未修改字体设计。
- F4: 补充待回答问题的状态核查保留与原生超时清理检查;任务生命周期专项测试通过。假设审阅待执行。
- F4: 补充待回答问题的状态核查保留与原生超时清理检查;任务生命周期专项测试通过。最终全量 `make test` 再次 exit 0;假设审阅进行中。
- 新增迁移区间 SQL:`cd backend && .venv/bin/python -m alembic upgrade 20260907_0025:head --sql` → exit 0。旧历史迁移含数据库读取,未声称全历史离线生成通过。
- F4 数据库集成:使用本机已有 `pgvector/pgvector:pg17` 镜像创建独立临时库,`alembic upgrade head` → exit 0;真实 PostgreSQL TaskRepository + ASGI 管理 API + 原生 Pipecat Job + MockTransport 远端 → exit 0(一次远端 prompt、持久事件连续、服务重启可查询)。临时容器已删除,现有数据库未改动。
- 普通 PostgreSQL 15 镜像不含既有 vector 扩展,首次全历史迁移在旧迁移 0005 失败;切换到仓库 Compose 相同的已缓存 pgvector 镜像后通过,未修改历史迁移。
- F4 审阅修复验收:任务/启动动作专项29项通过;完整 `make test` → exit 0(367项后端、4项前端契约、5项公开契约及生产构建),仅保留既有1条lint warning。
## Deferred
- 真实远程 OpenCode + 模型联调:未提供服务地址/凭证。
- 多进程分布式总线及通用插件发现:超出单进程 MVP。
## Blockers
- 开发数据库连接不可用(提升权限后仍 OSError);本次新增迁移区间 SQL 生成通过,未执行线上/本地数据库迁移。
- 真实远程 OpenCode 联调未执行(未提供地址/凭证);现有开发库未修改,使用前仍需迁移。
## 假设审阅
- 第一轮:4 项未声明决策(3 semantic)与4项台账不一致。全部记录至台账,修复自动测试控制入口、启动 Action 校验、待停止任务重启和远端未结束时的新任务提交;结果保存完整文本、通知显式标注省略、列表展示范围同步改进。
- 第二轮:待复审;审阅类型为 same-family / provisional,不等同于真实远端联调。
## 行为约定(全部 semantic 决策)
- **A-001 — 范围**:只允许提示词 Pipeline 绑定任务工具;不开放 Workflow、Realtime、启动 Action(Source: user)。
- **A-002 — 任务生命周期**:应用级原生 Worker 拥有 Job;网页断线任务继续(Source: user)。
- **A-005 — 远端执行关联**:一次任务一个 OpenCode session;提交前保存session及messageID,初始idle不算完成(Source: default)。
- **A-006 — 取消**:原生Job取消后调用远端abort并核查;无法确认时保留待确认,不能宣称已停止(Source: default)。
- **A-007 — 通知**:普通进度仅网页;完成/失败/取消/状态未知/等待输入在线时触发前台通知;离线结果保留供查询(Source: default)。
- **A-008 — 恢复**:单进程;重启恢复观察有远端句柄的任务,不自动重新发送未知提交(Source: default)。
- **A-010 — 补充输入**:本版仅支持回答远端问题及权限确认;任意实时修改要求需先停止再提交,不自动批准权限(Source: default)。
- **A-011 — 验证边界**:未提供真实远端服务,使用httpx MockTransport验证协议和真实Pipecat验证调度;真实联调待部署配置(Source: default)。
- **A-012 — 外部编码变更**:仅明确调用任务工具时会启动远端执行,不因查看页面自动执行(Source: default)。
- **A-013 — 任意运行中修改任务**:本版支持远端待回答的问题/权限;任意实时 steer 延后,新增要求通过停止后重新提交(Source: default)。
- **A-015 — 同一工作区并发修改**:本版原生execute Job采用全局FIFO串行;排队计入本次等待时限;另有未结束远端句柄时不启动新的编码提交,状态未知/停止未确认需先核查。不能约束平台之外的远端客户端(Source: default)。
- **A-016 — 状态未知后恢复**:网页提供显式核查/重新观察;只重连已有远端句柄,绝不重复发送原任务(Source: default)。
- **A-017 — 自动测试边界**:task工具不出现在普通工具模拟列表;测试运行器中调用task只返回不支持,真实执行需手动语音调试(Source: default)。
- **A-018 — 远端结果保留与通知长度**:平台保存消息API关联到本次任务的完整助手文本及错误文本,不包含工具原始日志;独立session.error事件仅保留失败提示;语音上下文最多保留首尾各8000字符并显式标记省略,完整结果可在网页查看(Source: sweep)。
- **A-019 — 超时定义**:时限针对每次本地观察Job(包含排队);重启/重新观察重新计时,超时与用户停止都进入远端abort核查,确认停止后才显示cancelled,不作为累计任务寿命上限(Source: sweep)。
- **A-020 — 自动测试控制函数**:测试运行器中提交、查询、取消、回复全部拒绝,不访问真实任务服务;修复首轮审阅发现的仅提交有防护问题(Source: sweep)。
## 接口约定与验证范围
- **A-003 — 配置入口**:新增 task 工具类型,复用 Tool CRUD/绑定;每个助手最多一个(Source: default)。
- **A-004 — 任务作用域**:平台生成 task_id,记录来源 conversation/assistant;管理接口沿用现有管理员鉴权(Source: default)。
- **A-009 — 远端配置**:地址/工作目录/Basic鉴权由管理员工具配置,任务冻结连接信息;LLM不能改地址和目录(Source: default)。
- **A-014 — 任务API字段**:与现有API一致使用camelCase;执行器内部保持Python snake_case(Source: default)。
- **A-021 — 任务列表范围**:管理API最多100项,优先未结束任务;网页展示全部返回项,无分页,更早记录可凭task_id查询/控制(Source: sweep)。
- ASK=never;生产链路没有固定结果占位实现,模拟执行器仅在明确标注的测试中注入。
- 未进行浏览器交互验收或真实模型/远端编码验收;前端通过类型检查、lint、契约检查和生产构建。
@@ -0,0 +1,66 @@
{
"review_independence": "same-family",
"acceptance_status": "provisional",
"rounds": [
{
"undeclared": [
{
"site": "backend/services/background_tasks/opencode.py:52-58",
"decision": "The persisted result contains only assistant text, truncated to its last 16,000 characters; an error contains only the first 8,000 characters of the last error. No truncation marker or full result is retained by the platform.",
"why_it_matters": "The webpage and completion summary can receive an incomplete result with earlier qualifications or failure details removed. The ledger does not declare that persistent results are lossy excerpts.",
"class": "semantic"
},
{
"site": "backend/services/background_tasks/service.py:69-72,166-182,198-199",
"decision": "Restart recovery and manual redispatch each receive the full configured timeout again. Deadline expiration follows the same cancellation path and public status as a user-requested stop.",
"why_it_matters": "The configured timeout limits each local observation job rather than total task lifetime, and recorded cancellation does not identify whether the user stopped the task or its deadline expired.",
"class": "semantic"
},
{
"site": "backend/services/brains/prompt_tasks.py:30-59",
"decision": "The automatic-test guard applies only to submission. Query, cancellation and question-answer handlers still call the real task service when tool_executor_factory is present.",
"why_it_matters": "An automatic test containing an existing task ID can cancel that remote task or answer its pending question. A direct invocation with a test runtime confirmed that cancel_background_task calls service.cancel. This live-control exception is absent from A-017.",
"class": "semantic"
},
{
"site": "backend/services/background_tasks/records.py:62-69; frontend/src/components/assistant-editor/background-task-panel.tsx:76-79",
"decision": "The API returns the latest 100 tasks, while the webpage renders only the latest 20, without pagination or prioritizing unresolved tasks.",
"why_it_matters": "An older task awaiting permission or cancellation confirmation can disappear from the available webpage controls while still being counted as pending.",
"class": "interface"
}
],
"stale_rows": [
{
"id": "A-001",
"site": "backend/routes/assistants.py:205-209,245-249,302-314",
"decision": "The ledger excludes startup Action bindings, but backend validation accepts a task tool in a Prompt Pipeline startup Action when instruction uses a literal or variable.",
"why_it_matters": "A concrete startup binding passed _validate_tool_bindings. Its eventual execution reaches ToolExecutor's unsupported-type branch, so the API accepts a configuration the ledger says is unavailable.",
"class": "interface"
},
{
"id": "A-006",
"site": "backend/services/background_tasks/service.py:33-35,100-122",
"decision": "An unconfirmed cancellation is not preserved across restart. Startup redispatches cancel_requested records as ordinary observation jobs, whose progress updates can change them back to running.",
"why_it_matters": "The recorded stop request disappears without confirmation that the remote task stopped. A real native-Job reproduction using the existing simulated executor changed cancel_requested to running after restart.",
"class": "semantic"
},
{
"id": "A-007",
"site": "backend/services/brains/prompt_tasks.py:78-88",
"decision": "The ledger describes webpage-only progress and spoken completion/failure notices, but code also triggers model replies for waiting_input, unknown and cancelled.",
"why_it_matters": "The actual notification policy includes additional spoken status transitions and should be recorded explicitly.",
"class": "semantic"
},
{
"id": "A-015",
"site": "backend/services/background_tasks/native.py:35-46; backend/services/background_tasks/service.py:134-136,166-182",
"decision": "FIFO serialization covers local Jobs, not all outstanding remote executions. An unknown outcome or unconfirmed remote stop releases the local Job and allows the next task to start.",
"why_it_matters": "The ledger's claim that serialization avoids simultaneous repository modification is false on supported failure paths. A reproduction left the first task cancel_requested while the second became running, with two remote runs started.",
"class": "semantic"
}
],
"semantic_undeclared": 3,
"verdict": "gaps"
}
]
}
+1 -1
View File
@@ -4,6 +4,6 @@ Base commit: d45be1e6864d1ea9ac73e9f15490873e2d6f606b
目标:使用 Pipecat 1.13.0 原生 Worker/Jobs,为提示词 Pipeline 增加远程 OpenCode 后台任务。输入为管理员配置的任务工具、工作目录和用户指令;输出为任务回执、持久进度/结果、网页卡片及在线完成通知。
成功检查:backend/.venv/bin/python -m scripts.run_tests;frontend npm run lint、npm run test:contracts、npm run build。
成功检查:仓库根目录运行 `make test`(后端测试、前端 lint/契约/构建、公开契约快照检查)。
范围:单应用进程,原生 AsyncQueueBus;任务所有者常驻应用;仅提示词 Pipeline。真实远端服务未提供,协议验证使用明确标注的本地模拟服务,不声称完成真实模型联调。