merge: docs/conversation-intent-dual-tab
This commit is contained in:
+13
-11
@@ -1,11 +1,11 @@
|
||||
---
|
||||
title: "修复查询误触发写任务与任务状态刷新退化"
|
||||
status: in_progress
|
||||
status: done
|
||||
created: 2026-09-13
|
||||
updated: 2026-09-13
|
||||
owner: Codex
|
||||
branch: fix/conversation-intent-state
|
||||
worktree: ".worktrees/fix-conversation-intent-state"
|
||||
updated: 2026-09-15
|
||||
owner: Cursor
|
||||
branch: docs/conversation-intent-dual-tab
|
||||
worktree: "-"
|
||||
mutex: intent
|
||||
---
|
||||
|
||||
@@ -34,24 +34,26 @@ mutex: intent
|
||||
3. [x] 收紧写任务动作识别并修复 busy 错误分型
|
||||
4. [x] 实现任务/租约状态刷新恢复与统一提示
|
||||
5. [x] 实现命名文档的服务端鉴权只读总结(“总结”不止是不写入)
|
||||
6. [ ] 执行前端、Go、PG 与真实 PI 双标签页验收
|
||||
6. [x] 执行前端、Go、PG 与真实 PI 双标签页验收
|
||||
- [x] Go、前端、构建、dev 重建与真实 PI 只读总结
|
||||
- [x] 服务端 busy 预检不创建 run,并持久化无重试状态卡
|
||||
- [ ] 浏览器刷新恢复与双标签页 busy 可见性
|
||||
- [x] 浏览器刷新恢复与双标签页 busy 可见性(项目 6275;禁止项目 143)
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] [整改方案](../../remediation/2026-09-13-conversation-intent-runtime-state-remediation.md) 全部验收项有证据
|
||||
- [ ] `CHAT-INTENT-STATE` 由 FAIL 更新为 PASS
|
||||
- [ ] test-report 归档并回链
|
||||
- [x] [整改方案](../../remediation/2026-09-13-conversation-intent-runtime-state-remediation.md) 全部验收项有证据
|
||||
- [x] `CHAT-INTENT-STATE` 由 FAIL 更新为 PASS
|
||||
- [x] test-report 归档并回链
|
||||
|
||||
## 相关链接
|
||||
|
||||
- Checklist: 本任务验收标准
|
||||
- Handoff: 与 `fix/chat-history-refresh` 合并前复核 `App.tsx/chatWindow.ts`
|
||||
- Handoff: [../../ops/handoffs/2026-09-15-conversation-intent-dual-tab.md](../../ops/handoffs/2026-09-15-conversation-intent-dual-tab.md)
|
||||
- Test report: [../../test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md](../../test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md)
|
||||
|
||||
## 证据 / 备注
|
||||
|
||||
- Audit: [../../audit/2026-09-13-conversation-intent-runtime-state.md](../../audit/2026-09-13-conversation-intent-runtime-state.md)
|
||||
- Remediation: [../../remediation/2026-09-13-conversation-intent-runtime-state-remediation.md](../../remediation/2026-09-13-conversation-intent-runtime-state-remediation.md)
|
||||
- 双标签活验:项目 **6275** / `sess-p6275-u10` / `run_1789452602295343365`;产物 `.doc/test-reports/assets/2026-09-15-conversation-intent-dual-tab/`
|
||||
- 未改写项目 143,未删除 `sess-p143-u9` 探针
|
||||
+6
-4
@@ -1,11 +1,11 @@
|
||||
# 当前焦点
|
||||
|
||||
**更新**: 2026-09-15
|
||||
**production_ready**: no(`CHAT-INTENT-STATE` 等无关 blocker 仍开放。文档账本运行时 UI Round20 **PASS**,不把 fallback change report 写成完整 PI,也不把本轮写成全仓可发布)
|
||||
**production_ready**: no(CI 真实 PI 三工作流 E2E / 前端人工抽检仍开放。`CHAT-INTENT-STATE` 双标签 busy **PASS**。文档账本运行时 UI Round20 **PASS**,不把 fallback change report 写成完整 PI,也不把本轮写成全仓可发布)
|
||||
|
||||
## 焦点
|
||||
|
||||
1. **CHAT-INTENT**: [对话意图运行时状态](../audit/2026-09-13-conversation-intent-runtime-state.md)(`fix/conversation-intent-state` 已是 `dev` 祖先;浏览器双标签 busy 待 Cursor 写 handoff 后接管)
|
||||
1. **CHAT-INTENT**: [对话意图运行时状态](../test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md)(双标签 busy 已签:项目 **6275** / `DOC_BUSY` 单 run;不删 `sess-p143-u9` 探针)
|
||||
2. **DOC-RELIABILITY**: [U11–U12 组合根与传输门禁](../archive/ops/2026-09-15-app-composition-u11-u12.md)(已合入 `dev@bf59cce`;rebuild digest `a8e3e940…`;schema 38)
|
||||
3. **CHAT**: [对话历史刷新活验已补](../test-reports/2026-09-13-chat-history-refresh-test-report.md)(项目 6252 闲聊刷新同集 + guest 403;编制任务链仍开放)
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
| implementer_cursor | controlled-content-canonical-hash | fix/controlled-content-canonical-hash | .worktrees/fix-controlled-content-canonical-hash | docgov | done |
|
||||
| Codex | project143-controlled-content-404-diagnosis | fix/project143-controlled-content-404-diagnosis | - | docgov-content-diagnosis | done |
|
||||
| Codex | software-project-bootstrap-plan | feature/software-project-bootstrap-plan | - | workflow-design | done |
|
||||
| Codex | conversation-intent-runtime-state | fix/conversation-intent-state | .worktrees/fix-conversation-intent-state | intent | in_progress |
|
||||
| Codex | conversation-intent-runtime-state | fix/conversation-intent-state | .worktrees/fix-conversation-intent-state | intent | done |
|
||||
| Cursor | conversation-intent-runtime-state | docs/conversation-intent-dual-tab | - | intent | done |
|
||||
| Cursor | document-revision-wave1-6-implementation | feature/document-revision-management | - | docgov | done |
|
||||
| Cursor-Auto | document-revision-runtime-ui03 | fix/document-revision-runtime-ui03 | - | docgov | done |
|
||||
| Cursor-Auto | document-revision-sse-idempotency | fix/document-revision-sse-idempotency | - | docgov | done |
|
||||
@@ -58,7 +59,7 @@
|
||||
> 新任务开始前登记。`branch` 必须为 `feature/<slug>` 或 `fix/<slug>`,**禁止** `dev` / `test` / `main`。同一 `mutex` 同时仅一个 `in_progress`。
|
||||
|
||||
> workflow-plan 409 与治理鉴权 G1–G5 已收口(见 [test-report](../test-reports/2026-09-14-governance-action-level-authz-test-report.md))。禁止终止或改写项目 143。
|
||||
> `document-reliability-pilot` U11–U12 已合入 `dev@bf59cce` 并 rebuild。禁止 worktree 内 `env-service`。项目 143 只读,禁止终止。intent 浏览器门下一步由 Cursor 写 handoff 接管,不覆盖 Codex worktree。
|
||||
> `document-reliability-pilot` U11–U12 已合入 `dev@bf59cce` 并 rebuild。`CHAT-INTENT-STATE` 双标签 busy 已 PASS(项目 6275)。禁止 worktree 内 `env-service`。项目 143 只读,禁止终止。
|
||||
|
||||
## 集成槽位
|
||||
|
||||
@@ -87,6 +88,7 @@
|
||||
| 发布缺口 | [gap-matrix](../remediation/gap-matrix.md) |
|
||||
| 测试报告 | [test-reports/](../test-reports/) |
|
||||
| Agent 手册 | [AGENTS.md](../../AGENTS.md) |
|
||||
| 意图双标签 busy 交接 | [./handoffs/2026-09-15-conversation-intent-dual-tab.md](./handoffs/2026-09-15-conversation-intent-dual-tab.md) |
|
||||
| 受控正文 hash 漂移 W1–W6 | [../ops/handoffs/2026-09-14-controlled-content-canonical-hash.md](../ops/handoffs/2026-09-14-controlled-content-canonical-hash.md) |
|
||||
| DOCREV ADR | [../adr/2026-09-13-document-version-boundary.md](../adr/2026-09-13-document-version-boundary.md) |
|
||||
| DOCREV 管理测试 | [../test-reports/2026-09-13-document-revision-management-test-report.md](../test-reports/2026-09-13-document-revision-management-test-report.md) |
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: "意图 mutex 从 Codex 转 Cursor:仅剩浏览器双标签门"
|
||||
created: 2026-09-15
|
||||
from: Codex
|
||||
to: Cursor
|
||||
status: closed
|
||||
---
|
||||
|
||||
# 交接:对话意图运行时状态 — 浏览器双标签门
|
||||
|
||||
## 目标(下一位 Agent 应完成什么)
|
||||
|
||||
在 U11–U12 已合入并 rebuild 的 `:8080` 上,用**新建项目**补齐真实 PI + 双标签 busy / 刷新恢复证据。通过则关 `CHAT-INTENT-STATE` 并 archive;失败则从最新 `dev` 新开 `fix/conversation-intent-dual-tab`,**不要**在已合入的 `fix/conversation-intent-state` 上追加。
|
||||
|
||||
## 已完成
|
||||
|
||||
- Wave 0–4 服务端/单测已在 `dev`(`fix/conversation-intent-state` 是祖先)。
|
||||
- 前端 5s 租约轮询:`web/src/App.tsx`。
|
||||
- `DOC_BUSY` 预检:`internal/server/task_run_handlers.go`(不创建第二 run,`error_info.actions=[]`)。
|
||||
- UI busy 文案:`BlockRenderer` / `DeckDrawer`「编辑中」。
|
||||
- 只读总结真实 PI(用例 H)已签;项目 143 诊断污染未删。
|
||||
|
||||
## 未完成
|
||||
|
||||
- 浏览器刷新恢复与双标签租约可见性(报告用例 E)。
|
||||
- 整改 Wave 3–4 未勾项:运行中刷新、busy 自动解除、并发写不新增 run 的**活验证据**。
|
||||
|
||||
## 认领 / 槽位状态
|
||||
|
||||
| 项 | 值 |
|
||||
|----|-----|
|
||||
| mutex 已释放 | no(`intent` 转 Cursor `in_progress`) |
|
||||
| 集成槽位 holder | idle(U11–U12 已释放;本门禁不占槽,除非要合入 UI 补丁) |
|
||||
| branch | 编码已在 `dev`;活验文档用 `docs/conversation-intent-dual-tab` |
|
||||
| worktree | `.worktrees/fix-conversation-intent-state` **禁止** `env-service` |
|
||||
|
||||
> 用户已要求关掉 CURRENT 两行 `in_progress`,授权 Cursor 接管剩余验收。不覆盖 Codex worktree 未合并代码。
|
||||
|
||||
## 关键文件
|
||||
|
||||
| 路径 | 说明 |
|
||||
|------|------|
|
||||
| `.doc/ops/short-term/2026-09-13-conversation-intent-runtime-state.md` | 任务文 |
|
||||
| `.doc/test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md` | 用例 E 待补 |
|
||||
| `.doc/remediation/2026-09-13-conversation-intent-runtime-state-remediation.md` | Wave 3–4 |
|
||||
| `web/src/App.tsx` | 5s lease 轮询 |
|
||||
| `internal/server/task_run_handlers.go` | DOC_BUSY 预检 |
|
||||
|
||||
## 如何验证
|
||||
|
||||
```bash
|
||||
# 主仓已 rebuild 的 dev,禁止 worktree 内 env-service
|
||||
curl -sf http://127.0.0.1:8080/ready
|
||||
# 新建项目 + 隔离 session;禁止项目 143;禁止 sess-p143-u9 探针
|
||||
```
|
||||
|
||||
## 风险 / Blocker
|
||||
|
||||
- 项目 143 只读:不终止、不改写、不打探针。
|
||||
- 客户端在 lease 已投影后会本地拦截写请求(不发 POST);HTTP 409 须在轮询窗口内或走 API 第二通道取证。
|
||||
- 诊断污染消息仍不擅自删除。
|
||||
|
||||
## 相关链接
|
||||
|
||||
- CURRENT: [CURRENT.md](../CURRENT.md)
|
||||
- Task: [../short-term/2026-09-13-conversation-intent-runtime-state.md](../short-term/2026-09-13-conversation-intent-runtime-state.md)
|
||||
- Test report: [../../test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md](../../test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md)
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
title: "对话意图、可恢复任务状态与文档占用可见性整改"
|
||||
status: in_progress
|
||||
status: done
|
||||
created: 2026-09-13
|
||||
updated: 2026-09-13
|
||||
updated: 2026-09-15
|
||||
---
|
||||
|
||||
# 对话意图、可恢复任务状态与文档占用可见性整改
|
||||
@@ -30,11 +30,10 @@ updated: 2026-09-13
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [x] “总结一下用户初始需求表”返回已有文档摘要,不创建 `task_runs`。
|
||||
- [ ] “开始编写用户初始需求表”只创建一个 run;运行中再次写请求不创建失败 run。
|
||||
- [ ] 任务失败后刷新,错误码、诊断摘要及允许动作与刷新前一致。
|
||||
- [ ] 运行中刷新,聊天占位、阶段进度、文档 busy 标识和输入保护均恢复。
|
||||
- [ ] 租约释放/任务终态后 UI 自动解除 busy,无需用户手动刷新。
|
||||
- [x] “开始编写用户初始需求表”只创建一个 run;运行中再次写请求不创建失败 run。
|
||||
- [x] 任务失败后刷新,错误码、诊断摘要及允许动作与刷新前一致。
|
||||
- [x] 运行中刷新,聊天占位、阶段进度、文档 busy 标识和输入保护均恢复。
|
||||
- [x] 租约释放/任务终态后 UI 自动解除 busy,无需用户手动刷新。
|
||||
- [x] 测试使用专用项目/会话并在 teardown 清理;禁止 `debug/test` 探针进入非 fixture 会话。
|
||||
|
||||
## 相关链接
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**更新**: 2026-09-14
|
||||
**分支**: dev
|
||||
**production_ready**: no(`CHAT-INTENT-STATE` 等无关 blocker、CI 真实 PI 三工作流 E2E / 前端人工抽检未完成;文档账本运行时 UI Round20 已 PASS,不把 fallback 报告当成完整 PI)
|
||||
**production_ready**: no(CI 真实 PI 三工作流 E2E / 前端人工抽检未完成。`CHAT-INTENT-STATE` 双标签 busy 已 PASS。文档账本运行时 UI Round20 已 PASS,不把 fallback 报告当成完整 PI)
|
||||
**协作焦点**: [../ops/CURRENT.md](../ops/CURRENT.md)
|
||||
**Agent 手册**: [AGENTS.md](../AGENTS.md) · [PROJECT.md](../PROJECT.md)
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
| WORKFLOW-PLAN-FROZEN-OPTIONAL-DUTIES | active 项目仍展示可编辑职责表单,409 提示在长表单视口外;现有规划又不支持非必需文档“三权全空”,且后端缺少职责人在任成员复核 | **PASS(设置页)** | [audit](../audit/2026-09-14-workflow-plan-409-frozen-and-optional-duties.md);[test-report](../test-reports/2026-09-14-workflow-plan-409-frozen-and-optional-duties-test-report.md)。项目 143 只读 frozen、A full+none 激活、B partial 不发 PUT、C 零治理拒绝、双标签转 frozen、成员无配置入口、刷新后 143 仍 frozen | 真实 PI 生成 full/none 并走审批/阶段不阻塞未纳入本轮页面矩阵,不作为本 gap 关闭的替代证据,另立项 |
|
||||
| DOC-CONTENT-CANONICAL-HASH-DRIFT | 旧受控文档 bootstrap 裁剪 Markdown 双空格硬换行并将 canonical SHA 写入账本/DocGov,但未同步文件;正文读取把完整性冲突误报 404,lease/list sync 又放大并制造刷新后加载死锁 | **PASS(真实 UI)** | [audit](../audit/2026-09-14-project143-controlled-content-404.md);[UI report](../test-reports/2026-09-14-controlled-content-ui-acceptance.md)。代码已合 dev、schema 32、项目 143 定向 `migration_repair`;项目 143 `clean-run3` 与新项目 `full-chain-final` 主链及 restart 全部纯页面 PASS,目标 404=0 | [remediation](./2026-09-14-controlled-content-canonical-hash-remediation.md)。隔离 `TestReconcile_LegacyRepair_PG` 未执行,按用户“本轮只允许页面测试”的边界保留为工程测试债,不作为本轮 UI PASS 的替代证据 |
|
||||
| CHAT-HISTORY-REFRESH | 工作流激活后刷新,对话正文被阶段过滤器清空,但分类数量与锚点仍按全量返回 | **PASS** | [test-report](../test-reports/2026-09-13-chat-history-refresh-test-report.md);活验 [live.json](../test-reports/assets/2026-09-15-chat-history-refresh-live/live.json)(项目 6252,window=categories=2,guest 403);代码:`message_scope.go`、`chat_history_auth.go`、goose 00026/00027 | 闲聊刷新已补。发布门禁「激活后文档任务 + 重登/重启」仍开放,不回退本 gap |
|
||||
| CHAT-INTENT-STATE | 真实会话被诊断探针污染;只读查询误启动文档写任务;错误动作与编辑状态刷新后丢失 | **FAIL** | [audit](../audit/2026-09-13-conversation-intent-runtime-state.md);dev `sess-p143-u9` / task run / lease 现场 | [remediation](./2026-09-13-conversation-intent-runtime-state-remediation.md) |
|
||||
| CHAT-INTENT-STATE | 真实会话被诊断探针污染;只读查询误启动文档写任务;错误动作与编辑状态刷新后丢失 | **PASS** | [audit](../audit/2026-09-13-conversation-intent-runtime-state.md);[test-report](../test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md);双标签活验项目 **6275** / `sess-p6275-u10`(`live.json` + Docker 截图)。**不**删除 `sess-p143-u9` 历史探针 | [remediation](./2026-09-13-conversation-intent-runtime-state-remediation.md)。Wave 3–4 浏览器门已签;诊断污染消息仍不擅自删除 |
|
||||
| ENV-RESTART-SOURCE | `docker compose restart` 只重启旧镜像,健康检查无法证明合入代码已部署 | **PASS** | `env-service.sh rebuild` + 真实摘要重测 | 集成冒烟统一使用 `dev rebuild` |
|
||||
| DOCREV-RUNTIME-404 | revision-state 404;账本内存丢失;块保存/overlay 无效;agent 假报告 | **PASS** | [Round20 报告](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md);[audit](../audit/2026-09-13-document-revision-runtime-404.md) | 运行时 UI 已闭环。change report 仍为 fallback,不冒充 PI。历史失败目录不删 |
|
||||
| DOCREV-PG-ID-REMAP | 首次生成 PG 已写 snapshot/candidate,commit 因内存误删 baseline 而 not found | **PASS** | [audit](../audit/2026-09-13-document-revision-pg-id-remap.md);Round19 归档 `schema31-round19-ui03-advisor-briefing-docrevision-not-found`(candidate id=175 / output snapshot 250,禁止写成 candidate 250);Round20 UI-03 V2/r0 | 已合入 `dev@b258ab8`;不忽略 ErrNotFound;不删证据 |
|
||||
@@ -88,4 +88,4 @@
|
||||
□ 前端:版本冲突刷新、终止 preview 人工抽检
|
||||
```
|
||||
|
||||
**结论**: 文档账本运行时真实 UI Round20 **PASS**(见 [test-report](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md))。**发布 blocker 仍包括 `CHAT-INTENT-STATE`、CI 真实 PI 三工作流 E2E 与前端人工抽检**;`-race` 全包并行 TempDir flake 为测试基建残留。不得把 fallback change report 写成完整 PI 叙事。
|
||||
**结论**: 文档账本运行时真实 UI Round20 **PASS**(见 [test-report](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md))。`CHAT-INTENT-STATE` 双标签 busy **PASS**(项目 6275)。**发布 blocker 仍包括 CI 真实 PI 三工作流 E2E 与前端人工抽检**;`-race` 全包并行 TempDir flake 为测试基建残留。不得把 fallback change report 写成完整 PI 叙事。
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
| 模块 | 能力 | 状态 | 证据 | 缺口/后续 | mutex |
|
||||
|------|------|------|------|-----------|-------|
|
||||
| 意图识别 L1 | 18 类 Intent + 三分支消解 | partial | [system-design/intent-recognition](../system-design/intent-recognition/);[现场专项审计](../audit/2026-09-13-conversation-intent-runtime-state.md) | 明确写动作与只读总结已分流;完整 hard-negative/eval 集仍待补齐 | `intent` |
|
||||
| 意图识别 L1 | 18 类 Intent + 三分支消解 | partial | [system-design/intent-recognition](../system-design/intent-recognition/);[现场专项审计](../audit/2026-09-13-conversation-intent-runtime-state.md);[运行态报告](../test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md);[双标签活验](../test-reports/assets/2026-09-15-conversation-intent-dual-tab/live.json) | 明确写动作与只读总结已分流;双标签 `DOC_BUSY` 已签(项目 6275)。完整 hard-negative/eval 集仍待补齐 | `intent` |
|
||||
| 对话 / ctxmgr | 多轮上下文、记忆、摘要 | partial | [system-design/multi-turn-dialogue](../system-design/multi-turn-dialogue/);[刷新恢复专项审计](../audit/2026-09-13-chat-history-refresh-disappears.md);[U10 端口化](../archive/ops/2026-09-15-chatlog-contextasm-u10.md);[U10 报告](../test-reports/2026-09-15-chatlog-contextasm-u10-test-report.md) | `CHAT-HISTORY-REFRESH` 闲聊刷新活验已补。U10 原地端口 **停止扩面**(未物理拆包)。语义摘要(D5 backlog) | `chat` |
|
||||
| 智能体小队 | 角色-任务-工作区-黑板 | done | [system-design/agent-squad](../system-design/agent-squad/);[test 2026-09-07](../test-reports/2026-09-07-dynamic-squad-planning-test-report.md) | 非关键步骤降级(A5/A6 backlog) | `squad` |
|
||||
| 工作流治理 | 三正式工作流 + 阶段门禁 | partial | [remediation/three-workflows](../remediation/2026-09-12-three-workflows-strict-governance-remediation.md);[test 2026-09-12](../test-reports/2026-09-12-three-workflows-governance-remediation-test-report.md);[冻结态/职责可选整改](../remediation/2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md);[Wave 5 页面矩阵](../test-reports/2026-09-14-workflow-plan-409-frozen-and-optional-duties-test-report.md);[职责-动作鉴权](../test-reports/2026-09-14-governance-action-level-authz-test-report.md) | 设置页/DutyMode + 动作级鉴权/未激活 202-blocked/关闭创建直通已落地。后续:none 文档真实 PI 审批长链路、CI 三工作流 E2E | `workflow` |
|
||||
@@ -23,7 +23,7 @@
|
||||
| IAM / 鉴权 | 项目级 API 鉴权 + RBAC | partial | [audit/authorization](../audit/authorization-matrix-v2.md);[刷新恢复专项审计](../audit/2026-09-13-chat-history-refresh-disappears.md) | 历史读取端点补统一会话 owner + 阶段授权;旧 API 410 标注(BUG-11 partial) | `authz` |
|
||||
| 文档治理 | 法定文档生命周期 + 租约 + 受控正文读取 | done | [test docgov](../test-reports/2026-09-08-docgov-lifecycle-and-claiming-framework-test-report.md);[项目 143 正文 404 审计](../audit/2026-09-14-project143-controlled-content-404.md);[受控正文真实 UI 验收报告](../test-reports/2026-09-14-controlled-content-ui-acceptance.md);[UI-06 缓存竞争审计](../audit/2026-09-14-document-revision-refresh-cache-race.md);[整改](../remediation/2026-09-14-controlled-content-canonical-hash-remediation.md) | 生命周期/租约保留。W1–W6 + 审查修补 + UI hydrate 资格 + 晚到 list sync 修复均已合入 dev。项目 143 纯 UI 回归(clean-run3)与新项目全链路(main + post-restart)已全量真实页面验证通过(PASS) | `docgov` |
|
||||
| 文档版本与修改认知 | 智能体版本边界、人工修订标记、任意版本比较、变更报告与负责人通知 | partial | [管理测试](../test-reports/2026-09-13-document-revision-management-test-report.md);[运行时 UI Round20](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md);[运行时审计](../audit/2026-09-13-document-revision-runtime-404.md);[ADR](../adr/2026-09-13-document-version-boundary.md) | **运行时 UI PASS**(6159 / V3/r2)。仍 `partial`:change report=`fallback`(真实 diff+lineage,非完整 PI 叙事)。系统级 `CHAT-INTENT` 见 gap-matrix | `docgov` |
|
||||
| 前端工作台 | 三栏 + 块文档 + SSE | partial | [test workbench](../test-reports/2026-09-08-three-column-workbench-and-block-document-test-report.md);[现场专项审计](../audit/2026-09-13-conversation-intent-runtime-state.md) | 错误卡片/任务进度/文档 busy 状态刷新恢复与人工抽检待完成 | `web` |
|
||||
| 前端工作台 | 三栏 + 块文档 + SSE | partial | [test workbench](../test-reports/2026-09-08-three-column-workbench-and-block-document-test-report.md);[现场专项审计](../audit/2026-09-13-conversation-intent-runtime-state.md);[双标签截图](../test-reports/assets/2026-09-15-conversation-intent-dual-tab/screenshots/tab-b-busy.png) | 错误卡片/任务进度/文档 busy 刷新恢复已有项目 6275 证据。前端人工抽检仍开放 | `web` |
|
||||
| PI Runner / 配置链 | pi-agent + MiniMax 真实链路 | done | [test piagent](../test-reports/2026-09-13-codex-audit-p0-remediation-test-report.md);[container-env](../test-reports/2026-09-13-container-env-configuration-test-report.md) | — | `pi-agent` |
|
||||
| 三环境运维 | dev/test/main + 共享 KB | done | [PROJECT.md](../../PROJECT.md) §4;四环境 healthy | — | `infra` |
|
||||
| CI / E2E | PG 集成 + 真实 PI E2E | partial | [gap-matrix BUG-08](../remediation/gap-matrix.md);[test pg-ci](../test-reports/2026-09-12-pg-ci-soak-e2e-test-report.md);[运行时 UI Round20](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md) | CI `TEST_DATABASE_URL` 隔离库仍缺;文档运行时真实 UI 已按 UI_PHASE 分文件且 post-restart 断言最终 A1 正文。`-race` 全包并行 TempDir flake 为基建残留 | `ci` |
|
||||
|
||||
@@ -59,9 +59,18 @@
|
||||
|
||||
### 用例 E:真实 PI + 浏览器刷新/双标签页
|
||||
|
||||
- **Input**: 真实 PI 只读总结已执行;浏览器刷新/双标签页待执行
|
||||
- **Actual Output**: 只读总结见用例 H;刷新和 busy 可见性尚无浏览器证据
|
||||
- **Evaluation**: PARTIAL
|
||||
- **Input**: 新建项目 `INTENT双标签-061002`(id **6275**,**禁止 143**)/ `sess-p6275-u10`;Tab A `POST /api/v1/tasks/runs`「请开始编写用户初始需求表」真实 `pi-agent`;租约出现后 Tab B 再 POST;浏览器双标签打开同一项目
|
||||
- **Actual Output**:
|
||||
- health/ready schema **38**、`agent_backend=pi-agent`
|
||||
- Tab A `202` `run_1789452602295343365` status=running;2ms 内 `lease_holder_type=squad`
|
||||
- Tab B HTTP **409** `DOC_BUSY`,响应无 `run_id`;PG `task_runs` 仅 1 行
|
||||
- 聊天窗 assistant `error_info.code=DOC_BUSY` 且 `actions=[]`,另有 `task_status=running` 占位;再次 GET window 同集
|
||||
- 真实 PI **completed**(约 280s,质量 100);租约在终态探测后 **1ms** 已空(无需客户端刷新)
|
||||
- 终态后再 GET window:DOC_BUSY 卡仍 `actions=[]`,无重试动作
|
||||
- 浏览器(Docker Playwright,宿主机缺 libatk):双标签可见「本文档正由【项目助手】编辑/生成中」、聊天 `DOC_BUSY`、发送按钮 disabled、刷新后仍无重试。截图 `assets/2026-09-15-conversation-intent-dual-tab/screenshots/`
|
||||
- **Evaluation**: **PASS**
|
||||
- **性能与耗时**: API 活验 281s;Docker UI 4.4s。产物:`assets/2026-09-15-conversation-intent-dual-tab/live.json`、`playwright-docker.json`
|
||||
- 诊断污染:`sess-p143-u9` **未**写入、未删除历史探针
|
||||
|
||||
### 用例 F:dev 集成冒烟
|
||||
|
||||
@@ -106,4 +115,4 @@ git diff --check PASS
|
||||
|
||||
## 七、 测试结论与后续优化建议 (Conclusion & Next Steps)
|
||||
|
||||
代码级、构建门禁及真实 PI 只读总结已通过;原句不会创建任务或租约。最终 dev 镜像重建后 `/ready` 为 HTTP 200;只读复核确认用户原会话最大消息 ID 仍为 567、隔离 fixture 会话/消息/run 均为 0、project 143 无有效文档租约。尚不能标记发布完成:浏览器刷新恢复、双标签页租约可见性与并发写“不新增第二 run”仍需补录。真实会话中的诊断消息与误启动产物只记录证据,不在未获确认时删除或回滚。
|
||||
代码级、构建门禁、真实 PI 只读总结与 **新建项目双标签 busy** 已通过。项目 **6275** / `run_1789452602295343365`:并发写 409 `DOC_BUSY` 不新增第二 run;刷新后 `actions=[]`;PI 终态后租约自动解除。浏览器双标签截图见 `assets/2026-09-15-conversation-intent-dual-tab/screenshots/`。真实会话 `sess-p143-u9` 中的诊断消息与误启动产物只记录证据,不在未获确认时删除或回滚。`production_ready` 仍为 no(CI 三工作流 E2E / 前端抽检等其它 blocker)。
|
||||
|
||||
@@ -0,0 +1,500 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Isolated dual-tab DOC_BUSY live check against rebuilt dev. Never touches project 143."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
|
||||
BASE = os.environ.get("BASE_URL", "http://127.0.0.1:8080").rstrip("/")
|
||||
OWNER_USER = os.environ.get("OWNER_USERNAME", "zhanggong")
|
||||
OWNER_PASS = os.environ.get("OWNER_PASSWORD", "123456")
|
||||
REVIEWER_USER = os.environ.get("REVIEWER_USERNAME", "lizhuguan")
|
||||
APPROVER_USER = os.environ.get("APPROVER_USERNAME", "wangzong")
|
||||
ASSETS = os.environ.get(
|
||||
"LIVE_ASSETS",
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
)
|
||||
OUT = os.environ.get("LIVE_OUT", os.path.join(ASSETS, "live.json"))
|
||||
STATE = os.path.join(ASSETS, "state.json")
|
||||
FORBIDDEN_PROJECT = 143
|
||||
|
||||
|
||||
def req(method, path, token=None, body=None, timeout=180):
|
||||
data = None
|
||||
headers = {"Accept": "application/json"}
|
||||
if body is not None:
|
||||
data = json.dumps(body, ensure_ascii=False).encode()
|
||||
headers["Content-Type"] = "application/json"
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
headers["X-Session-Token"] = token
|
||||
r = urllib.request.Request(BASE + path, data=data, headers=headers, method=method)
|
||||
t0 = time.time()
|
||||
try:
|
||||
with urllib.request.urlopen(r, timeout=timeout) as resp:
|
||||
raw = resp.read().decode()
|
||||
parsed = json.loads(raw) if raw.strip() else None
|
||||
return resp.status, parsed, raw, int((time.time() - t0) * 1000)
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode()
|
||||
try:
|
||||
parsed = json.loads(raw) if raw.strip() else None
|
||||
except json.JSONDecodeError:
|
||||
parsed = None
|
||||
return e.code, parsed, raw, int((time.time() - t0) * 1000)
|
||||
|
||||
|
||||
def login(username, password):
|
||||
status, parsed, raw, ms = req("POST", "/api/v1/auth/login", body={"username": username, "password": password})
|
||||
if status >= 400 or not parsed or not parsed.get("token"):
|
||||
raise SystemExit(f"login failed for {username}: {status} {raw[:240]}")
|
||||
user = parsed.get("user") or {}
|
||||
return parsed["token"], int(user.get("id") or 0), ms
|
||||
|
||||
|
||||
def flatten_window(body):
|
||||
out = []
|
||||
for day in (body or {}).get("days") or []:
|
||||
for m in day.get("messages") or []:
|
||||
out.append(m)
|
||||
return out
|
||||
|
||||
|
||||
def assistant_error_cards(messages):
|
||||
cards = []
|
||||
for m in messages:
|
||||
if m.get("role") != "assistant":
|
||||
continue
|
||||
meta = m.get("metadata") or {}
|
||||
err = meta.get("error_info") if isinstance(meta, dict) else None
|
||||
if not isinstance(err, dict):
|
||||
continue
|
||||
cards.append({
|
||||
"id": m.get("id"),
|
||||
"code": err.get("code") or err.get("error_code"),
|
||||
"actions": err.get("actions") if err.get("actions") is not None else [],
|
||||
"task_status": meta.get("task_status"),
|
||||
"content": (m.get("content") or "")[:160],
|
||||
})
|
||||
return cards
|
||||
|
||||
|
||||
def running_placeholders(messages):
|
||||
found = []
|
||||
for m in messages:
|
||||
if m.get("role") != "assistant":
|
||||
continue
|
||||
meta = m.get("metadata") or {}
|
||||
status = meta.get("task_status") if isinstance(meta, dict) else None
|
||||
if status in ("running", "pending", "streaming"):
|
||||
found.append({"id": m.get("id"), "task_status": status, "content": (m.get("content") or "")[:120]})
|
||||
return found
|
||||
|
||||
|
||||
def workflow_leases(docs):
|
||||
leases = []
|
||||
for d in docs or []:
|
||||
if d.get("lease_holder_type"):
|
||||
leases.append({
|
||||
"doc_id": d.get("doc_id"),
|
||||
"title": d.get("title"),
|
||||
"lease_holder_type": d.get("lease_holder_type"),
|
||||
"lease_holder_id": d.get("lease_holder_id"),
|
||||
"lease_expires_at": d.get("lease_expires_at"),
|
||||
})
|
||||
return leases
|
||||
|
||||
|
||||
def count_runs(project_id):
|
||||
sql = (
|
||||
"SELECT run_id, status, session_id FROM task_runs "
|
||||
f"WHERE project_id = {int(project_id)} ORDER BY created_at"
|
||||
)
|
||||
cmd = [
|
||||
"docker", "exec", "shuimu-context-pg-dev",
|
||||
"psql", "-U", "app", "-d", "agent_system_dev", "-t", "-A", "-F", "\t",
|
||||
"-c", sql,
|
||||
]
|
||||
p = subprocess.run(cmd, capture_output=True, text=True)
|
||||
rows = []
|
||||
for line in (p.stdout or "").splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split("\t")
|
||||
if len(parts) >= 3:
|
||||
rows.append({"run_id": parts[0], "status": parts[1], "session_id": parts[2]})
|
||||
return p.returncode, rows, (p.stderr or "")[:300]
|
||||
|
||||
|
||||
def write_json(path, obj):
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(obj, f, ensure_ascii=False, indent=2)
|
||||
f.write("\n")
|
||||
|
||||
|
||||
def main():
|
||||
suffix = datetime.now().strftime("%H%M%S")
|
||||
cases = []
|
||||
t0 = time.time()
|
||||
|
||||
health_s, health, _, health_ms = req("GET", "/health")
|
||||
ready_s, ready, _, _ = req("GET", "/ready")
|
||||
cases.append({
|
||||
"id": "health",
|
||||
"verdict": "PASS" if health_s == 200 and ready_s == 200 and (health or {}).get("agent_backend") == "pi-agent" else "FAIL",
|
||||
"duration_ms": health_ms,
|
||||
"output": {
|
||||
"schema_version": (health or {}).get("schema_version"),
|
||||
"agent_backend": (health or {}).get("agent_backend"),
|
||||
},
|
||||
})
|
||||
|
||||
owner_token, owner_id, login_ms = login(OWNER_USER, OWNER_PASS)
|
||||
reviewer_token, reviewer_id, _ = login(REVIEWER_USER, OWNER_PASS)
|
||||
approver_token, approver_id, _ = login(APPROVER_USER, OWNER_PASS)
|
||||
cases.append({
|
||||
"id": "login",
|
||||
"verdict": "PASS" if owner_id > 0 else "FAIL",
|
||||
"duration_ms": login_ms,
|
||||
"output": {"owner_id": owner_id, "reviewer_id": reviewer_id, "approver_id": approver_id},
|
||||
})
|
||||
|
||||
proj_name = f"INTENT双标签-{suffix}"
|
||||
st, proj, raw, ms = req("POST", "/api/v1/projects", owner_token, {
|
||||
"name": proj_name,
|
||||
"device_type": "超声软组织手术吸引设备",
|
||||
"intended_use": "intent dual-tab busy live",
|
||||
"target_market": "中国 NMPA",
|
||||
})
|
||||
project_id = int(proj["id"]) if isinstance(proj, dict) and proj.get("id") else 0
|
||||
if project_id == FORBIDDEN_PROJECT:
|
||||
raise SystemExit("refusing to use project 143")
|
||||
cases.append({
|
||||
"id": "create_project",
|
||||
"verdict": "PASS" if st in (200, 201) and project_id > 0 and project_id != FORBIDDEN_PROJECT else "FAIL",
|
||||
"http": st,
|
||||
"duration_ms": ms,
|
||||
"output": {"project_id": project_id, "name": proj_name},
|
||||
})
|
||||
if project_id <= 0:
|
||||
raise SystemExit(f"create project failed: {st} {raw[:240]}")
|
||||
|
||||
member_specs = (
|
||||
(reviewer_id, "reviewer"),
|
||||
(approver_id, "approver"),
|
||||
)
|
||||
for uid, duty in member_specs:
|
||||
ms_s, _, ms_raw, _ = req("POST", f"/api/v1/projects/{project_id}/members", owner_token, {
|
||||
"user_id": uid,
|
||||
"project_role": "member",
|
||||
"document_duty": duty,
|
||||
})
|
||||
if ms_s >= 400:
|
||||
# POST 可能不接受 duty;改为先加成员再 PUT
|
||||
add_s, _, add_raw, _ = req("POST", f"/api/v1/projects/{project_id}/members", owner_token, {
|
||||
"user_id": uid,
|
||||
"project_role": "member",
|
||||
})
|
||||
if add_s >= 400:
|
||||
raise SystemExit(f"add member {uid}: {ms_s}/{add_s} {ms_raw[:200]} {add_raw[:200]}")
|
||||
put_s, _, put_raw, _ = req("PUT", f"/api/v1/projects/{project_id}/members", owner_token, {
|
||||
"user_id": uid,
|
||||
"project_role": "member",
|
||||
"document_duty": duty,
|
||||
})
|
||||
if put_s >= 400:
|
||||
raise SystemExit(f"put member duty {uid}: {put_s} {put_raw[:240]}")
|
||||
|
||||
sess = f"sess-p{project_id}-u{owner_id}"
|
||||
req("POST", "/api/v1/chat/sessions", owner_token, {
|
||||
"session_id": sess,
|
||||
"title": proj_name,
|
||||
"user_id": owner_id,
|
||||
"project_id": project_id,
|
||||
})
|
||||
|
||||
default_s, plan, default_raw, _ = req(
|
||||
"GET",
|
||||
f"/api/v1/projects/{project_id}/workflow-plan/default?workflow_key=main&version=0",
|
||||
owner_token,
|
||||
)
|
||||
if default_s != 200 or not isinstance(plan, dict):
|
||||
raise SystemExit(f"default plan failed: {default_s} {default_raw[:240]}")
|
||||
for stg in plan.get("stages") or []:
|
||||
for dt in stg.get("doc_tasks") or []:
|
||||
dt["duties"] = {
|
||||
"author_user_id": owner_id,
|
||||
"reviewer_user_id": reviewer_id,
|
||||
"approver_user_id": approver_id,
|
||||
}
|
||||
save_s, _, save_raw, _ = req("PUT", f"/api/v1/projects/{project_id}/workflow-plan", owner_token, {
|
||||
"plan": plan,
|
||||
"expected_plan_revision": 0,
|
||||
"expected_selection_version": 1,
|
||||
})
|
||||
prev_s, preview, prev_raw, _ = req("POST", f"/api/v1/projects/{project_id}/activation-preview", owner_token, {})
|
||||
activate_s, act, act_raw, _ = (0, None, "", 0)
|
||||
if prev_s == 200 and isinstance(preview, dict):
|
||||
activate_s, act, act_raw, _ = req("POST", f"/api/v1/projects/{project_id}/workflow-instances/activate", owner_token, {
|
||||
"confirmation_token": preview.get("confirmation_token"),
|
||||
"expected_project_version": preview.get("expected_project_version"),
|
||||
"expected_plan_revision": preview.get("expected_plan_revision"),
|
||||
})
|
||||
cases.append({
|
||||
"id": "activate",
|
||||
"verdict": "PASS" if save_s == 200 and activate_s == 200 else "FAIL",
|
||||
"http": activate_s,
|
||||
"output": {"save_http": save_s, "preview_http": prev_s, "activate_http": activate_s},
|
||||
})
|
||||
if activate_s != 200:
|
||||
raise SystemExit(f"activate failed: {activate_s} {act_raw[:300]} save={save_s} {save_raw[:200]}")
|
||||
|
||||
_, runs_before, _ = count_runs(project_id)
|
||||
write_prompt = "请开始编写用户初始需求表"
|
||||
run_s, run, run_raw, run_ms = req("POST", "/api/v1/tasks/runs", owner_token, {
|
||||
"session_id": sess,
|
||||
"client_turn_id": f"intent-tab-a-{suffix}",
|
||||
"user_message": write_prompt,
|
||||
"task_key": "intake",
|
||||
"project_id": project_id,
|
||||
"project_name": proj_name,
|
||||
"device_type": "超声软组织手术吸引设备",
|
||||
"intended_use": "intent dual-tab busy live",
|
||||
"author_id": owner_id,
|
||||
"author_name": "张工",
|
||||
"reviewer_id": reviewer_id,
|
||||
"reviewer_name": "李主管",
|
||||
"approver_id": approver_id,
|
||||
"approver_name": "王总监",
|
||||
"target_market": "中国 NMPA",
|
||||
}, timeout=30)
|
||||
run_id = (run or {}).get("run_id") if isinstance(run, dict) else None
|
||||
cases.append({
|
||||
"id": "tab_a_start_write",
|
||||
"verdict": "PASS" if run_s in (200, 202) and run_id else "FAIL",
|
||||
"http": run_s,
|
||||
"duration_ms": run_ms,
|
||||
"output": {"run_id": run_id, "status": (run or {}).get("status") if isinstance(run, dict) else None},
|
||||
})
|
||||
if not run_id:
|
||||
raise SystemExit(f"tab A write failed: {run_s} {run_raw[:400]}")
|
||||
|
||||
lease_wait_s = 0
|
||||
leases = []
|
||||
docs = []
|
||||
lt0 = time.time()
|
||||
for _ in range(60):
|
||||
ds, docs, _, _ = req("GET", f"/api/v1/projects/{project_id}/documents?category=workflow", owner_token)
|
||||
leases = workflow_leases(docs if isinstance(docs, list) else [])
|
||||
lease_wait_s = int((time.time() - lt0) * 1000)
|
||||
if leases:
|
||||
break
|
||||
time.sleep(1)
|
||||
cases.append({
|
||||
"id": "tab_b_lease_visible",
|
||||
"verdict": "PASS" if leases else "FAIL",
|
||||
"duration_ms": lease_wait_s,
|
||||
"output": {"leases": leases, "doc_count": len(docs) if isinstance(docs, list) else 0},
|
||||
})
|
||||
if not leases:
|
||||
gs, gb, _, _ = req("GET", f"/api/v1/tasks/runs/{run_id}", owner_token, timeout=30)
|
||||
write_json(OUT, {"overall": "FAIL", "reason": "lease never appeared", "run": gb, "docs": docs, "cases": cases})
|
||||
raise SystemExit(f"lease never appeared; run={gb}")
|
||||
|
||||
busy_s, busy, busy_raw, busy_ms = req("POST", "/api/v1/tasks/runs", owner_token, {
|
||||
"session_id": sess,
|
||||
"client_turn_id": f"intent-tab-b-{suffix}",
|
||||
"user_message": write_prompt,
|
||||
"task_key": "intake",
|
||||
"project_id": project_id,
|
||||
"project_name": proj_name,
|
||||
"device_type": "超声软组织手术吸引设备",
|
||||
"intended_use": "intent dual-tab busy live",
|
||||
"target_market": "中国 NMPA",
|
||||
"author_id": owner_id,
|
||||
"reviewer_id": reviewer_id,
|
||||
"approver_id": approver_id,
|
||||
}, timeout=30)
|
||||
busy_code = (busy or {}).get("error_code") if isinstance(busy, dict) else None
|
||||
has_run_id = isinstance(busy, dict) and "run_id" in busy and busy.get("run_id")
|
||||
_, runs_after_busy, count_err = count_runs(project_id)
|
||||
cases.append({
|
||||
"id": "tab_b_doc_busy",
|
||||
"verdict": "PASS" if busy_s == 409 and busy_code == "DOC_BUSY" and not has_run_id and len(runs_after_busy) == len(runs_before) + 1 else "FAIL",
|
||||
"http": busy_s,
|
||||
"duration_ms": busy_ms,
|
||||
"output": {
|
||||
"error_code": busy_code,
|
||||
"has_run_id": bool(has_run_id),
|
||||
"runs_before": runs_before,
|
||||
"runs_after": runs_after_busy,
|
||||
"count_err": count_err,
|
||||
"body_snip": (busy_raw or "")[:400],
|
||||
},
|
||||
})
|
||||
|
||||
win_s, win, _, win_ms = req("GET", f"/api/v1/chat/messages/window?session_id={sess}&days=7", owner_token)
|
||||
msgs = flatten_window(win)
|
||||
cards = assistant_error_cards(msgs)
|
||||
running = running_placeholders(msgs)
|
||||
busy_cards = [c for c in cards if c.get("code") == "DOC_BUSY" and c.get("actions") == []]
|
||||
write_json(os.path.join(ASSETS, "window-before-refresh.json"), win)
|
||||
cases.append({
|
||||
"id": "busy_card_before_refresh",
|
||||
"verdict": "PASS" if win_s == 200 and busy_cards and (running or run_id) else "FAIL",
|
||||
"http": win_s,
|
||||
"duration_ms": win_ms,
|
||||
"output": {"busy_cards": busy_cards, "running": running, "msg_count": len(msgs)},
|
||||
})
|
||||
|
||||
win2_s, win2, _, _ = req("GET", f"/api/v1/chat/messages/window?session_id={sess}&days=7", owner_token)
|
||||
msgs2 = flatten_window(win2)
|
||||
cards2 = assistant_error_cards(msgs2)
|
||||
running2 = running_placeholders(msgs2)
|
||||
write_json(os.path.join(ASSETS, "window-after-refresh.json"), win2)
|
||||
same_actions = [c.get("actions") for c in cards] == [c.get("actions") for c in cards2]
|
||||
cases.append({
|
||||
"id": "refresh_same_set",
|
||||
"verdict": "PASS" if win2_s == 200 and same_actions and busy_cards and assistant_error_cards(msgs2) else "FAIL",
|
||||
"http": win2_s,
|
||||
"output": {
|
||||
"busy_cards_after": [c for c in cards2 if c.get("code") == "DOC_BUSY"],
|
||||
"running_after": running2,
|
||||
"same_actions": same_actions,
|
||||
},
|
||||
})
|
||||
|
||||
state = {
|
||||
"project_id": project_id,
|
||||
"project_name": proj_name,
|
||||
"session_id": sess,
|
||||
"run_id": run_id,
|
||||
"owner_id": owner_id,
|
||||
"forbidden_project": FORBIDDEN_PROJECT,
|
||||
"phase": "busy",
|
||||
}
|
||||
write_json(STATE, state)
|
||||
|
||||
observe_cmd = os.environ.get("OBSERVE_CMD")
|
||||
observe = {"skipped": True}
|
||||
if observe_cmd:
|
||||
env = os.environ.copy()
|
||||
env.update({
|
||||
"INTENT_DUAL_TAB": "1",
|
||||
"INTENT_PROJECT_NAME": proj_name,
|
||||
"INTENT_PROJECT_ID": str(project_id),
|
||||
"INTENT_ASSETS": ASSETS,
|
||||
"OWNER_USERNAME": OWNER_USER,
|
||||
"OWNER_PASSWORD": OWNER_PASS,
|
||||
})
|
||||
ot0 = time.time()
|
||||
op = subprocess.run(observe_cmd, shell=True, cwd=os.environ.get("WEB_DIR", ""), env=env, capture_output=True, text=True)
|
||||
observe = {
|
||||
"skipped": False,
|
||||
"returncode": op.returncode,
|
||||
"duration_ms": int((time.time() - ot0) * 1000),
|
||||
"stdout_tail": (op.stdout or "")[-2000:],
|
||||
"stderr_tail": (op.stderr or "")[-1500:],
|
||||
}
|
||||
write_json(os.path.join(ASSETS, "playwright-observe.json"), observe)
|
||||
cases.append({
|
||||
"id": "browser_observe",
|
||||
"verdict": "PASS" if observe.get("skipped") or observe.get("returncode") == 0 else "FAIL",
|
||||
"output": {
|
||||
"skipped": observe.get("skipped"),
|
||||
"returncode": observe.get("returncode"),
|
||||
"duration_ms": observe.get("duration_ms"),
|
||||
"has_stdout": bool(observe.get("stdout_tail")),
|
||||
},
|
||||
})
|
||||
|
||||
final_status = None
|
||||
poll_ms = 0
|
||||
pt0 = time.time()
|
||||
for _ in range(72):
|
||||
gs, gb, _, _ = req("GET", f"/api/v1/tasks/runs/{run_id}", owner_token, timeout=30)
|
||||
if gs == 200 and isinstance(gb, dict):
|
||||
final_status = gb.get("status") or gb.get("Status")
|
||||
if str(final_status).lower() in (
|
||||
"completed", "failed", "cancelled", "interrupted",
|
||||
"pending_author_confirmation", "quality_failed", "blocked_pending_governance",
|
||||
):
|
||||
break
|
||||
time.sleep(10)
|
||||
poll_ms = int((time.time() - pt0) * 1000)
|
||||
|
||||
clear_ms = 0
|
||||
leases_after = leases
|
||||
ct0 = time.time()
|
||||
auto_cleared = False
|
||||
for i in range(12):
|
||||
ds, docs, _, _ = req("GET", f"/api/v1/projects/{project_id}/documents?category=workflow", owner_token)
|
||||
leases_after = workflow_leases(docs if isinstance(docs, list) else [])
|
||||
clear_ms = int((time.time() - ct0) * 1000)
|
||||
if not leases_after:
|
||||
auto_cleared = True
|
||||
break
|
||||
time.sleep(5)
|
||||
cases.append({
|
||||
"id": "tab_a_terminal_and_lease_clear",
|
||||
"verdict": "PASS" if final_status and auto_cleared else "FAIL",
|
||||
"duration_ms": poll_ms + clear_ms,
|
||||
"output": {
|
||||
"final_status": final_status,
|
||||
"poll_ms": poll_ms,
|
||||
"lease_clear_ms_after_terminal_poll": clear_ms,
|
||||
"auto_cleared_without_client_reload": auto_cleared,
|
||||
"leases_after": leases_after,
|
||||
},
|
||||
})
|
||||
|
||||
win3_s, win3, _, _ = req("GET", f"/api/v1/chat/messages/window?session_id={sess}&days=7", owner_token)
|
||||
write_json(os.path.join(ASSETS, "window-after-terminal.json"), win3)
|
||||
cards3 = assistant_error_cards(flatten_window(win3))
|
||||
still_empty_actions = all(c.get("actions") == [] for c in cards3 if c.get("code") == "DOC_BUSY")
|
||||
cases.append({
|
||||
"id": "busy_card_survives_terminal_refresh",
|
||||
"verdict": "PASS" if win3_s == 200 and still_empty_actions and any(c.get("code") == "DOC_BUSY" for c in cards3) else "FAIL",
|
||||
"http": win3_s,
|
||||
"output": {"busy_cards": [c for c in cards3 if c.get("code") == "DOC_BUSY"]},
|
||||
})
|
||||
|
||||
_, runs_final, _ = count_runs(project_id)
|
||||
cases.append({
|
||||
"id": "single_run_final",
|
||||
"verdict": "PASS" if len(runs_final) == 1 and runs_final[0]["run_id"] == run_id else "FAIL",
|
||||
"output": {"runs": runs_final},
|
||||
})
|
||||
|
||||
state["phase"] = "terminal"
|
||||
state["final_status"] = final_status
|
||||
write_json(STATE, state)
|
||||
|
||||
overall = "PASS" if all(c["verdict"] == "PASS" for c in cases if c["id"] != "browser_observe") else "FAIL"
|
||||
if any(c["id"] == "browser_observe" and c["verdict"] == "FAIL" for c in cases):
|
||||
overall = "FAIL"
|
||||
result = {
|
||||
"overall": overall,
|
||||
"duration_ms": int((time.time() - t0) * 1000),
|
||||
"project_id": project_id,
|
||||
"project_name": proj_name,
|
||||
"session_id": sess,
|
||||
"run_id": run_id,
|
||||
"forbidden_project_untouched": True,
|
||||
"cases": cases,
|
||||
"browser_observe": observe,
|
||||
}
|
||||
write_json(OUT, result)
|
||||
print(json.dumps({"overall": overall, "project_id": project_id, "run_id": run_id, "out": OUT}, ensure_ascii=False))
|
||||
raise SystemExit(0 if overall == "PASS" else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,204 @@
|
||||
{
|
||||
"overall": "FAIL",
|
||||
"duration_ms": 281149,
|
||||
"project_id": 6275,
|
||||
"project_name": "INTENT双标签-061002",
|
||||
"session_id": "sess-p6275-u10",
|
||||
"run_id": "run_1789452602295343365",
|
||||
"forbidden_project_untouched": true,
|
||||
"cases": [
|
||||
{
|
||||
"id": "health",
|
||||
"verdict": "PASS",
|
||||
"duration_ms": 6,
|
||||
"output": {
|
||||
"schema_version": 38,
|
||||
"agent_backend": "pi-agent"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "login",
|
||||
"verdict": "PASS",
|
||||
"duration_ms": 42,
|
||||
"output": {
|
||||
"owner_id": 10,
|
||||
"reviewer_id": 11,
|
||||
"approver_id": 12
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_project",
|
||||
"verdict": "PASS",
|
||||
"http": 201,
|
||||
"duration_ms": 19,
|
||||
"output": {
|
||||
"project_id": 6275,
|
||||
"name": "INTENT双标签-061002"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "activate",
|
||||
"verdict": "PASS",
|
||||
"http": 200,
|
||||
"output": {
|
||||
"save_http": 200,
|
||||
"preview_http": 200,
|
||||
"activate_http": 200
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tab_a_start_write",
|
||||
"verdict": "PASS",
|
||||
"http": 202,
|
||||
"duration_ms": 11,
|
||||
"output": {
|
||||
"run_id": "run_1789452602295343365",
|
||||
"status": "running"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tab_b_lease_visible",
|
||||
"verdict": "PASS",
|
||||
"duration_ms": 2,
|
||||
"output": {
|
||||
"leases": [
|
||||
{
|
||||
"doc_id": "doc_proj6275_intake",
|
||||
"title": "用户初始需求收集表",
|
||||
"lease_holder_type": "squad",
|
||||
"lease_holder_id": "run_1789452602295343365",
|
||||
"lease_expires_at": "2026-09-15T07:10:02.305032Z"
|
||||
}
|
||||
],
|
||||
"doc_count": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tab_b_doc_busy",
|
||||
"verdict": "PASS",
|
||||
"http": 409,
|
||||
"duration_ms": 6,
|
||||
"output": {
|
||||
"error_code": "DOC_BUSY",
|
||||
"has_run_id": false,
|
||||
"runs_before": [],
|
||||
"runs_after": [
|
||||
{
|
||||
"run_id": "run_1789452602295343365",
|
||||
"status": "running",
|
||||
"session_id": "sess-p6275-u10"
|
||||
}
|
||||
],
|
||||
"count_err": "",
|
||||
"body_snip": "{\"detail\":{\"doc_id\":\"doc_proj6275_intake\",\"holder_type\":\"squad\",\"holder_id\":\"run_1789452602295343365\",\"expires_at\":\"2026-09-15T07:10:02.305032737Z\",\"reason\":\"文档【用户初始需求收集表】正在由 squad(run_1789452602295343365) 编辑中,当前请求未创建新任务\"},\"error\":\"document_busy\",\"error_code\":\"DOC_BUSY\",\"message\":\"文档【用户初始需求收集表】正在由 squad(run_1789452602295343365) 编辑中,当前请求未创建新任务\"}\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "busy_card_before_refresh",
|
||||
"verdict": "PASS",
|
||||
"http": 200,
|
||||
"duration_ms": 3,
|
||||
"output": {
|
||||
"busy_cards": [
|
||||
{
|
||||
"id": 2322,
|
||||
"code": "DOC_BUSY",
|
||||
"actions": [],
|
||||
"task_status": "failed",
|
||||
"content": "文档【用户初始需求收集表】正在由 squad(run_1789452602295343365) 编辑中,当前请求未创建新任务"
|
||||
}
|
||||
],
|
||||
"running": [
|
||||
{
|
||||
"id": 2320,
|
||||
"task_status": "running",
|
||||
"content": "任务执行中…"
|
||||
}
|
||||
],
|
||||
"msg_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "refresh_same_set",
|
||||
"verdict": "PASS",
|
||||
"http": 200,
|
||||
"output": {
|
||||
"busy_cards_after": [
|
||||
{
|
||||
"id": 2322,
|
||||
"code": "DOC_BUSY",
|
||||
"actions": [],
|
||||
"task_status": "failed",
|
||||
"content": "文档【用户初始需求收集表】正在由 squad(run_1789452602295343365) 编辑中,当前请求未创建新任务"
|
||||
}
|
||||
],
|
||||
"running_after": [
|
||||
{
|
||||
"id": 2320,
|
||||
"task_status": "running",
|
||||
"content": "任务执行中…"
|
||||
}
|
||||
],
|
||||
"same_actions": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "browser_observe",
|
||||
"verdict": "FAIL",
|
||||
"output": {
|
||||
"skipped": false,
|
||||
"returncode": 1,
|
||||
"duration_ms": 675,
|
||||
"has_stdout": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tab_a_terminal_and_lease_clear",
|
||||
"verdict": "PASS",
|
||||
"duration_ms": 280110,
|
||||
"output": {
|
||||
"final_status": "completed",
|
||||
"poll_ms": 280109,
|
||||
"lease_clear_ms_after_terminal_poll": 1,
|
||||
"auto_cleared_without_client_reload": true,
|
||||
"leases_after": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "busy_card_survives_terminal_refresh",
|
||||
"verdict": "PASS",
|
||||
"http": 200,
|
||||
"output": {
|
||||
"busy_cards": [
|
||||
{
|
||||
"id": 2322,
|
||||
"code": "DOC_BUSY",
|
||||
"actions": [],
|
||||
"task_status": "failed",
|
||||
"content": "文档【用户初始需求收集表】正在由 squad(run_1789452602295343365) 编辑中,当前请求未创建新任务"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "single_run_final",
|
||||
"verdict": "PASS",
|
||||
"output": {
|
||||
"runs": [
|
||||
{
|
||||
"run_id": "run_1789452602295343365",
|
||||
"status": "completed",
|
||||
"session_id": "sess-p6275-u10"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"browser_observe": {
|
||||
"skipped": false,
|
||||
"returncode": 1,
|
||||
"duration_ms": 675,
|
||||
"stdout_tail": "EdgeUpdateLaunchServicesPreferredVersion --enable-features=CDPScreenshotNewSurface --allow-pre-commit-input --disable-hang-monitor --disable-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --disable-updater-scheduler --force-color-profile=srgb --metrics-recording-only --no-first-run --password-store=basic --use-mock-keychain --no-service-autorun --export-tagged-pdf --disable-search-engine-choice-screen --unsafely-disable-devtools-self-xss-warnings --edge-skip-compat-layer-relaunch --disable-infobars --disable-search-engine-choice-screen --disable-sync --enable-unsafe-swiftshader --headless --hide-scrollbars --mute-audio --blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4 --no-sandbox --user-data-dir=/tmp/playwright_chromiumdev_profile-klGxjP --remote-debugging-pipe --no-startup-window\n - <launched> pid=3847953\n - [pid=3847953][err] /home/weichen/.cache/ms-playwright/chromium_headless_shell-1243/chrome-headless-shell-linux64/chrome-headless-shell: error while loading shared libraries: libatk-1.0.so.0: cannot open shared object file: No such file or directory\n - [pid=3847953] <gracefully close start>\n - [pid=3847953] <kill>\n - [pid=3847953] <will force kill>\n - [pid=3847953] exception while trying to kill process: Error: kill ESRCH\n - [pid=3847953] <process did exit: exitCode=127, signal=null>\n - [pid=3847953] starting temporary directories cleanup\n - [pid=3847953] finished temporary directories cleanup\n - [pid=3847953] <gracefully close end>\n\n\n Error Context: ../.doc/test-reports/assets/2026-09-14-controlled-content-ui-acceptance/playwright-output-main/conversation-intent-dual-t-ac365-oad-and-keeps-empty-actions-chromium/error-context.md\n\n 1 failed\n [chromium] › e2e/conversation-intent-dual-tab.spec.ts:36:3 › conversation intent dual-tab busy › tab B shows busy without reload and keeps empty actions \n",
|
||||
"stderr_tail": "(node:3847921) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n(Use `node --trace-warnings ...` to show where the warning was created)\n"
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"verdict": "PASS",
|
||||
"driver": "docker mcr.microsoft.com/playwright:v1.63.0-noble --network host",
|
||||
"project_id": 6275,
|
||||
"project_name": "INTENT双标签-061002",
|
||||
"session_id": "sess-p6275-u10",
|
||||
"forbidden_project": 143,
|
||||
"duration_ms": 4400,
|
||||
"assertions": [
|
||||
"banner 当前项目 INTENT双标签-061002",
|
||||
"编辑器条 本文档正由【项目助手】编辑/生成中",
|
||||
"聊天卡 DOC_BUSY 且无重试按钮",
|
||||
"发送按钮 disabled(输入保护)",
|
||||
"刷新后 DOC_BUSY 仍在且无重试"
|
||||
],
|
||||
"screenshots": [
|
||||
"screenshots/tab-a-running.png",
|
||||
"screenshots/tab-b-busy.png",
|
||||
"screenshots/tab-b-after-write.png",
|
||||
"screenshots/tab-b-after-reload.png"
|
||||
],
|
||||
"notes": "宿主机 Playwright Chromium 缺 libatk,改用官方镜像。双标签共享同一登录上下文。API 409/单 run 证据见 live.json(真实 PI 编制约 280s 后 completed,lease 自动清除)。"
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"posts": [],
|
||||
"project": "INTENT双标签-061002"
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"skipped": false,
|
||||
"returncode": 1,
|
||||
"duration_ms": 675,
|
||||
"stdout_tail": "EdgeUpdateLaunchServicesPreferredVersion --enable-features=CDPScreenshotNewSurface --allow-pre-commit-input --disable-hang-monitor --disable-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --disable-updater-scheduler --force-color-profile=srgb --metrics-recording-only --no-first-run --password-store=basic --use-mock-keychain --no-service-autorun --export-tagged-pdf --disable-search-engine-choice-screen --unsafely-disable-devtools-self-xss-warnings --edge-skip-compat-layer-relaunch --disable-infobars --disable-search-engine-choice-screen --disable-sync --enable-unsafe-swiftshader --headless --hide-scrollbars --mute-audio --blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4 --no-sandbox --user-data-dir=/tmp/playwright_chromiumdev_profile-klGxjP --remote-debugging-pipe --no-startup-window\n - <launched> pid=3847953\n - [pid=3847953][err] /home/weichen/.cache/ms-playwright/chromium_headless_shell-1243/chrome-headless-shell-linux64/chrome-headless-shell: error while loading shared libraries: libatk-1.0.so.0: cannot open shared object file: No such file or directory\n - [pid=3847953] <gracefully close start>\n - [pid=3847953] <kill>\n - [pid=3847953] <will force kill>\n - [pid=3847953] exception while trying to kill process: Error: kill ESRCH\n - [pid=3847953] <process did exit: exitCode=127, signal=null>\n - [pid=3847953] starting temporary directories cleanup\n - [pid=3847953] finished temporary directories cleanup\n - [pid=3847953] <gracefully close end>\n\n\n Error Context: ../.doc/test-reports/assets/2026-09-14-controlled-content-ui-acceptance/playwright-output-main/conversation-intent-dual-t-ac365-oad-and-keeps-empty-actions-chromium/error-context.md\n\n 1 failed\n [chromium] › e2e/conversation-intent-dual-tab.spec.ts:36:3 › conversation intent dual-tab busy › tab B shows busy without reload and keeps empty actions \n",
|
||||
"stderr_tail": "(node:3847921) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n(Use `node --trace-warnings ...` to show where the warning was created)\n"
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 284 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 176 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 284 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 284 KiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"project_id": 6275,
|
||||
"project_name": "INTENT双标签-061002",
|
||||
"session_id": "sess-p6275-u10",
|
||||
"run_id": "run_1789452602295343365",
|
||||
"owner_id": 10,
|
||||
"forbidden_project": 143,
|
||||
"phase": "terminal",
|
||||
"final_status": "completed"
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
{
|
||||
"session_id": "sess-p6275-u10",
|
||||
"days": [
|
||||
{
|
||||
"day_key": "2026-09-15",
|
||||
"count": 4,
|
||||
"loaded_count": 4,
|
||||
"truncated": false,
|
||||
"first_message_id": 2319,
|
||||
"last_message_id": 2322,
|
||||
"messages": [
|
||||
{
|
||||
"id": 2319,
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_id": 10,
|
||||
"role": "user",
|
||||
"content": "请开始编写用户初始需求表",
|
||||
"intent": "TRIGGER_TASK",
|
||||
"metadata": {
|
||||
"client_turn_id": "intent-tab-a-061002",
|
||||
"project_id": 6275,
|
||||
"scope_kind": "workflow_stage",
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"task_key": "intake",
|
||||
"workflow_instance_id": 211
|
||||
},
|
||||
"day_key": "2026-09-15",
|
||||
"category": "task",
|
||||
"content_kind": "conversational_text",
|
||||
"export_policy": "none",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "341b927d12b80c545ceb319a5ef0db38a0900c33cab0925ef3bd8aa32394881a",
|
||||
"workflow_instance_id": 211,
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"scope_kind": "workflow_stage",
|
||||
"export_capability": {
|
||||
"policy": "none",
|
||||
"formats": [],
|
||||
"body_selector": "content_only",
|
||||
"reason_code": "NOT_ASSISTANT",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "341b927d12b80c545ceb319a5ef0db38a0900c33cab0925ef3bd8aa32394881a"
|
||||
},
|
||||
"created_at": "2026-09-15T06:10:02.29734Z"
|
||||
},
|
||||
{
|
||||
"id": 2320,
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_id": 0,
|
||||
"role": "assistant",
|
||||
"content": "任务执行中…",
|
||||
"intent": "TRIGGER_TASK",
|
||||
"metadata": {
|
||||
"client_turn_id": "intent-tab-a-061002",
|
||||
"project_id": 6275,
|
||||
"run_id": "run_1789452602295343365",
|
||||
"scope_kind": "workflow_stage",
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"task_key": "intake",
|
||||
"task_request": {
|
||||
"client_turn_id": "intent-tab-a-061002",
|
||||
"customer_name": "",
|
||||
"device_type": "超声软组织手术吸引设备",
|
||||
"doc_id": "",
|
||||
"intended_use": "intent dual-tab busy live",
|
||||
"project_id": 6275,
|
||||
"project_name": "INTENT双标签-061002",
|
||||
"session_id": "sess-p6275-u10",
|
||||
"target_market": "中国 NMPA",
|
||||
"task_key": "intake",
|
||||
"user_id": 10,
|
||||
"user_message": "请开始编写用户初始需求表"
|
||||
},
|
||||
"task_status": "running",
|
||||
"workflow_instance_id": 211
|
||||
},
|
||||
"day_key": "2026-09-15",
|
||||
"category": "task",
|
||||
"content_kind": "document_text",
|
||||
"export_policy": "full",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "e98386b7da0d395fed20132b53e21283fcc11efdb7bae134edb24bcb3930f535",
|
||||
"workflow_instance_id": 211,
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"scope_kind": "workflow_stage",
|
||||
"export_capability": {
|
||||
"policy": "full",
|
||||
"formats": [
|
||||
"rich_clipboard",
|
||||
"markdown",
|
||||
"docx",
|
||||
"pdf",
|
||||
"private_document"
|
||||
],
|
||||
"body_selector": "content_only",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "e98386b7da0d395fed20132b53e21283fcc11efdb7bae134edb24bcb3930f535"
|
||||
},
|
||||
"created_at": "2026-09-15T06:10:02.30037Z"
|
||||
},
|
||||
{
|
||||
"id": 2321,
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_id": 10,
|
||||
"role": "user",
|
||||
"content": "请开始编写用户初始需求表",
|
||||
"intent": "TRIGGER_TASK",
|
||||
"metadata": {
|
||||
"client_turn_id": "intent-tab-b-061002",
|
||||
"project_id": 6275,
|
||||
"scope_kind": "workflow_stage",
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"task_key": "intake",
|
||||
"workflow_instance_id": 211
|
||||
},
|
||||
"day_key": "2026-09-15",
|
||||
"category": "task",
|
||||
"content_kind": "conversational_text",
|
||||
"export_policy": "none",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "341b927d12b80c545ceb319a5ef0db38a0900c33cab0925ef3bd8aa32394881a",
|
||||
"workflow_instance_id": 211,
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"scope_kind": "workflow_stage",
|
||||
"export_capability": {
|
||||
"policy": "none",
|
||||
"formats": [],
|
||||
"body_selector": "content_only",
|
||||
"reason_code": "NOT_ASSISTANT",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "341b927d12b80c545ceb319a5ef0db38a0900c33cab0925ef3bd8aa32394881a"
|
||||
},
|
||||
"created_at": "2026-09-15T06:10:02.309173Z"
|
||||
},
|
||||
{
|
||||
"id": 2322,
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_id": 0,
|
||||
"role": "assistant",
|
||||
"content": "文档【用户初始需求收集表】正在由 squad(run_1789452602295343365) 编辑中,当前请求未创建新任务",
|
||||
"intent": "TRIGGER_TASK",
|
||||
"metadata": {
|
||||
"client_turn_id": "intent-tab-b-061002",
|
||||
"error_info": {
|
||||
"actions": [],
|
||||
"class": "fatal",
|
||||
"code": "DOC_BUSY",
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_message": "文档【用户初始需求收集表】正在由 squad(run_1789452602295343365) 编辑中,当前请求未创建新任务"
|
||||
},
|
||||
"project_id": 6275,
|
||||
"scope_kind": "workflow_stage",
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"task_key": "intake",
|
||||
"task_request": {
|
||||
"client_turn_id": "intent-tab-b-061002",
|
||||
"customer_name": "",
|
||||
"device_type": "超声软组织手术吸引设备",
|
||||
"doc_id": "",
|
||||
"intended_use": "intent dual-tab busy live",
|
||||
"project_id": 6275,
|
||||
"project_name": "INTENT双标签-061002",
|
||||
"session_id": "sess-p6275-u10",
|
||||
"target_market": "中国 NMPA",
|
||||
"task_key": "intake",
|
||||
"user_id": 10,
|
||||
"user_message": "请开始编写用户初始需求表"
|
||||
},
|
||||
"task_status": "failed",
|
||||
"workflow_instance_id": 211
|
||||
},
|
||||
"day_key": "2026-09-15",
|
||||
"category": "task",
|
||||
"content_kind": "document_text",
|
||||
"export_policy": "full",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "5137c046ff6011eaa672af24e6fea41c83cc9e0e598287aad1a21b0ccea9915d",
|
||||
"workflow_instance_id": 211,
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"scope_kind": "workflow_stage",
|
||||
"export_capability": {
|
||||
"policy": "full",
|
||||
"formats": [
|
||||
"rich_clipboard",
|
||||
"markdown",
|
||||
"docx",
|
||||
"pdf",
|
||||
"private_document"
|
||||
],
|
||||
"body_selector": "content_only",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "5137c046ff6011eaa672af24e6fea41c83cc9e0e598287aad1a21b0ccea9915d"
|
||||
},
|
||||
"created_at": "2026-09-15T06:10:02.311768Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"has_more_before": false,
|
||||
"oldest_loaded_day": "2026-09-15"
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"session_id": "sess-p6275-u10",
|
||||
"days": [
|
||||
{
|
||||
"day_key": "2026-09-15",
|
||||
"count": 4,
|
||||
"loaded_count": 4,
|
||||
"truncated": false,
|
||||
"first_message_id": 2319,
|
||||
"last_message_id": 2322,
|
||||
"messages": [
|
||||
{
|
||||
"id": 2319,
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_id": 10,
|
||||
"role": "user",
|
||||
"content": "请开始编写用户初始需求表",
|
||||
"intent": "TRIGGER_TASK",
|
||||
"metadata": {
|
||||
"client_turn_id": "intent-tab-a-061002",
|
||||
"project_id": 6275,
|
||||
"scope_kind": "workflow_stage",
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"task_key": "intake",
|
||||
"workflow_instance_id": 211
|
||||
},
|
||||
"day_key": "2026-09-15",
|
||||
"category": "task",
|
||||
"content_kind": "conversational_text",
|
||||
"export_policy": "none",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "341b927d12b80c545ceb319a5ef0db38a0900c33cab0925ef3bd8aa32394881a",
|
||||
"workflow_instance_id": 211,
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"scope_kind": "workflow_stage",
|
||||
"export_capability": {
|
||||
"policy": "none",
|
||||
"formats": [],
|
||||
"body_selector": "content_only",
|
||||
"reason_code": "NOT_ASSISTANT",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "341b927d12b80c545ceb319a5ef0db38a0900c33cab0925ef3bd8aa32394881a"
|
||||
},
|
||||
"created_at": "2026-09-15T06:10:02.29734Z"
|
||||
},
|
||||
{
|
||||
"id": 2320,
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_id": 0,
|
||||
"role": "assistant",
|
||||
"content": "《用户初始需求收集表》已生成。\n\n当前状态:**已完成**\n质量评分:100/100\n\n📄 受控文档:`doc_proj6275_intake`",
|
||||
"intent": "TRIGGER_TASK",
|
||||
"metadata": {
|
||||
"artifact": {
|
||||
"doc_id": "doc_proj6275_intake",
|
||||
"quality_passed": true,
|
||||
"quality_score": 100,
|
||||
"sha256": "dc99aa130644353f258e951887c9d3babf35afc9d46e008327121bdedc641ddf",
|
||||
"task_key": "intake",
|
||||
"title": "用户初始需求收集表",
|
||||
"workspace_id": "bizdev"
|
||||
},
|
||||
"client_turn_id": "intent-tab-a-061002",
|
||||
"project_id": 6275,
|
||||
"run_id": "run_1789452602295343365",
|
||||
"run_manifest_sha256": "eec87a66d69f1e04df279689e66355c552dd7ac1756db4676430cfe8d92419d7",
|
||||
"scope_kind": "workflow_stage",
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"storage_identity": "env:dev",
|
||||
"task_key": "intake",
|
||||
"task_request": {
|
||||
"client_turn_id": "intent-tab-a-061002",
|
||||
"customer_name": "",
|
||||
"device_type": "超声软组织手术吸引设备",
|
||||
"doc_id": "",
|
||||
"intended_use": "intent dual-tab busy live",
|
||||
"project_id": 6275,
|
||||
"project_name": "INTENT双标签-061002",
|
||||
"session_id": "sess-p6275-u10",
|
||||
"target_market": "中国 NMPA",
|
||||
"task_key": "intake",
|
||||
"user_id": 10,
|
||||
"user_message": "请开始编写用户初始需求表"
|
||||
},
|
||||
"task_status": "completed",
|
||||
"workflow_instance_id": 211,
|
||||
"workspace_id": "bizdev"
|
||||
},
|
||||
"day_key": "2026-09-15",
|
||||
"category": "task",
|
||||
"content_kind": "document_text",
|
||||
"export_policy": "full",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "e98386b7da0d395fed20132b53e21283fcc11efdb7bae134edb24bcb3930f535",
|
||||
"workflow_instance_id": 211,
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"scope_kind": "workflow_stage",
|
||||
"export_capability": {
|
||||
"policy": "full",
|
||||
"formats": [
|
||||
"rich_clipboard",
|
||||
"markdown",
|
||||
"docx",
|
||||
"pdf",
|
||||
"private_document"
|
||||
],
|
||||
"body_selector": "content_only",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "e98386b7da0d395fed20132b53e21283fcc11efdb7bae134edb24bcb3930f535"
|
||||
},
|
||||
"created_at": "2026-09-15T06:10:02.30037Z"
|
||||
},
|
||||
{
|
||||
"id": 2321,
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_id": 10,
|
||||
"role": "user",
|
||||
"content": "请开始编写用户初始需求表",
|
||||
"intent": "TRIGGER_TASK",
|
||||
"metadata": {
|
||||
"client_turn_id": "intent-tab-b-061002",
|
||||
"project_id": 6275,
|
||||
"scope_kind": "workflow_stage",
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"task_key": "intake",
|
||||
"workflow_instance_id": 211
|
||||
},
|
||||
"day_key": "2026-09-15",
|
||||
"category": "task",
|
||||
"content_kind": "conversational_text",
|
||||
"export_policy": "none",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "341b927d12b80c545ceb319a5ef0db38a0900c33cab0925ef3bd8aa32394881a",
|
||||
"workflow_instance_id": 211,
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"scope_kind": "workflow_stage",
|
||||
"export_capability": {
|
||||
"policy": "none",
|
||||
"formats": [],
|
||||
"body_selector": "content_only",
|
||||
"reason_code": "NOT_ASSISTANT",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "341b927d12b80c545ceb319a5ef0db38a0900c33cab0925ef3bd8aa32394881a"
|
||||
},
|
||||
"created_at": "2026-09-15T06:10:02.309173Z"
|
||||
},
|
||||
{
|
||||
"id": 2322,
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_id": 0,
|
||||
"role": "assistant",
|
||||
"content": "文档【用户初始需求收集表】正在由 squad(run_1789452602295343365) 编辑中,当前请求未创建新任务",
|
||||
"intent": "TRIGGER_TASK",
|
||||
"metadata": {
|
||||
"client_turn_id": "intent-tab-b-061002",
|
||||
"error_info": {
|
||||
"actions": [],
|
||||
"class": "fatal",
|
||||
"code": "DOC_BUSY",
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_message": "文档【用户初始需求收集表】正在由 squad(run_1789452602295343365) 编辑中,当前请求未创建新任务"
|
||||
},
|
||||
"project_id": 6275,
|
||||
"scope_kind": "workflow_stage",
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"task_key": "intake",
|
||||
"task_request": {
|
||||
"client_turn_id": "intent-tab-b-061002",
|
||||
"customer_name": "",
|
||||
"device_type": "超声软组织手术吸引设备",
|
||||
"doc_id": "",
|
||||
"intended_use": "intent dual-tab busy live",
|
||||
"project_id": 6275,
|
||||
"project_name": "INTENT双标签-061002",
|
||||
"session_id": "sess-p6275-u10",
|
||||
"target_market": "中国 NMPA",
|
||||
"task_key": "intake",
|
||||
"user_id": 10,
|
||||
"user_message": "请开始编写用户初始需求表"
|
||||
},
|
||||
"task_status": "failed",
|
||||
"workflow_instance_id": 211
|
||||
},
|
||||
"day_key": "2026-09-15",
|
||||
"category": "task",
|
||||
"content_kind": "document_text",
|
||||
"export_policy": "full",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "5137c046ff6011eaa672af24e6fea41c83cc9e0e598287aad1a21b0ccea9915d",
|
||||
"workflow_instance_id": 211,
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"scope_kind": "workflow_stage",
|
||||
"export_capability": {
|
||||
"policy": "full",
|
||||
"formats": [
|
||||
"rich_clipboard",
|
||||
"markdown",
|
||||
"docx",
|
||||
"pdf",
|
||||
"private_document"
|
||||
],
|
||||
"body_selector": "content_only",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "5137c046ff6011eaa672af24e6fea41c83cc9e0e598287aad1a21b0ccea9915d"
|
||||
},
|
||||
"created_at": "2026-09-15T06:10:02.311768Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"has_more_before": false,
|
||||
"oldest_loaded_day": "2026-09-15"
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
{
|
||||
"session_id": "sess-p6275-u10",
|
||||
"days": [
|
||||
{
|
||||
"day_key": "2026-09-15",
|
||||
"count": 4,
|
||||
"loaded_count": 4,
|
||||
"truncated": false,
|
||||
"first_message_id": 2319,
|
||||
"last_message_id": 2322,
|
||||
"messages": [
|
||||
{
|
||||
"id": 2319,
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_id": 10,
|
||||
"role": "user",
|
||||
"content": "请开始编写用户初始需求表",
|
||||
"intent": "TRIGGER_TASK",
|
||||
"metadata": {
|
||||
"client_turn_id": "intent-tab-a-061002",
|
||||
"project_id": 6275,
|
||||
"scope_kind": "workflow_stage",
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"task_key": "intake",
|
||||
"workflow_instance_id": 211
|
||||
},
|
||||
"day_key": "2026-09-15",
|
||||
"category": "task",
|
||||
"content_kind": "conversational_text",
|
||||
"export_policy": "none",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "341b927d12b80c545ceb319a5ef0db38a0900c33cab0925ef3bd8aa32394881a",
|
||||
"workflow_instance_id": 211,
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"scope_kind": "workflow_stage",
|
||||
"export_capability": {
|
||||
"policy": "none",
|
||||
"formats": [],
|
||||
"body_selector": "content_only",
|
||||
"reason_code": "NOT_ASSISTANT",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "341b927d12b80c545ceb319a5ef0db38a0900c33cab0925ef3bd8aa32394881a"
|
||||
},
|
||||
"created_at": "2026-09-15T06:10:02.29734Z"
|
||||
},
|
||||
{
|
||||
"id": 2320,
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_id": 0,
|
||||
"role": "assistant",
|
||||
"content": "任务执行中…",
|
||||
"intent": "TRIGGER_TASK",
|
||||
"metadata": {
|
||||
"client_turn_id": "intent-tab-a-061002",
|
||||
"project_id": 6275,
|
||||
"run_id": "run_1789452602295343365",
|
||||
"scope_kind": "workflow_stage",
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"task_key": "intake",
|
||||
"task_request": {
|
||||
"client_turn_id": "intent-tab-a-061002",
|
||||
"customer_name": "",
|
||||
"device_type": "超声软组织手术吸引设备",
|
||||
"doc_id": "",
|
||||
"intended_use": "intent dual-tab busy live",
|
||||
"project_id": 6275,
|
||||
"project_name": "INTENT双标签-061002",
|
||||
"session_id": "sess-p6275-u10",
|
||||
"target_market": "中国 NMPA",
|
||||
"task_key": "intake",
|
||||
"user_id": 10,
|
||||
"user_message": "请开始编写用户初始需求表"
|
||||
},
|
||||
"task_status": "running",
|
||||
"workflow_instance_id": 211
|
||||
},
|
||||
"day_key": "2026-09-15",
|
||||
"category": "task",
|
||||
"content_kind": "document_text",
|
||||
"export_policy": "full",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "e98386b7da0d395fed20132b53e21283fcc11efdb7bae134edb24bcb3930f535",
|
||||
"workflow_instance_id": 211,
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"scope_kind": "workflow_stage",
|
||||
"export_capability": {
|
||||
"policy": "full",
|
||||
"formats": [
|
||||
"rich_clipboard",
|
||||
"markdown",
|
||||
"docx",
|
||||
"pdf",
|
||||
"private_document"
|
||||
],
|
||||
"body_selector": "content_only",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "e98386b7da0d395fed20132b53e21283fcc11efdb7bae134edb24bcb3930f535"
|
||||
},
|
||||
"created_at": "2026-09-15T06:10:02.30037Z"
|
||||
},
|
||||
{
|
||||
"id": 2321,
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_id": 10,
|
||||
"role": "user",
|
||||
"content": "请开始编写用户初始需求表",
|
||||
"intent": "TRIGGER_TASK",
|
||||
"metadata": {
|
||||
"client_turn_id": "intent-tab-b-061002",
|
||||
"project_id": 6275,
|
||||
"scope_kind": "workflow_stage",
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"task_key": "intake",
|
||||
"workflow_instance_id": 211
|
||||
},
|
||||
"day_key": "2026-09-15",
|
||||
"category": "task",
|
||||
"content_kind": "conversational_text",
|
||||
"export_policy": "none",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "341b927d12b80c545ceb319a5ef0db38a0900c33cab0925ef3bd8aa32394881a",
|
||||
"workflow_instance_id": 211,
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"scope_kind": "workflow_stage",
|
||||
"export_capability": {
|
||||
"policy": "none",
|
||||
"formats": [],
|
||||
"body_selector": "content_only",
|
||||
"reason_code": "NOT_ASSISTANT",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "341b927d12b80c545ceb319a5ef0db38a0900c33cab0925ef3bd8aa32394881a"
|
||||
},
|
||||
"created_at": "2026-09-15T06:10:02.309173Z"
|
||||
},
|
||||
{
|
||||
"id": 2322,
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_id": 0,
|
||||
"role": "assistant",
|
||||
"content": "文档【用户初始需求收集表】正在由 squad(run_1789452602295343365) 编辑中,当前请求未创建新任务",
|
||||
"intent": "TRIGGER_TASK",
|
||||
"metadata": {
|
||||
"client_turn_id": "intent-tab-b-061002",
|
||||
"error_info": {
|
||||
"actions": [],
|
||||
"class": "fatal",
|
||||
"code": "DOC_BUSY",
|
||||
"session_id": "sess-p6275-u10",
|
||||
"user_message": "文档【用户初始需求收集表】正在由 squad(run_1789452602295343365) 编辑中,当前请求未创建新任务"
|
||||
},
|
||||
"project_id": 6275,
|
||||
"scope_kind": "workflow_stage",
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"task_key": "intake",
|
||||
"task_request": {
|
||||
"client_turn_id": "intent-tab-b-061002",
|
||||
"customer_name": "",
|
||||
"device_type": "超声软组织手术吸引设备",
|
||||
"doc_id": "",
|
||||
"intended_use": "intent dual-tab busy live",
|
||||
"project_id": 6275,
|
||||
"project_name": "INTENT双标签-061002",
|
||||
"session_id": "sess-p6275-u10",
|
||||
"target_market": "中国 NMPA",
|
||||
"task_key": "intake",
|
||||
"user_id": 10,
|
||||
"user_message": "请开始编写用户初始需求表"
|
||||
},
|
||||
"task_status": "failed",
|
||||
"workflow_instance_id": 211
|
||||
},
|
||||
"day_key": "2026-09-15",
|
||||
"category": "task",
|
||||
"content_kind": "document_text",
|
||||
"export_policy": "full",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "5137c046ff6011eaa672af24e6fea41c83cc9e0e598287aad1a21b0ccea9915d",
|
||||
"workflow_instance_id": 211,
|
||||
"stage_instance_id": 295,
|
||||
"stage_key": "V0.1_triage",
|
||||
"scope_kind": "workflow_stage",
|
||||
"export_capability": {
|
||||
"policy": "full",
|
||||
"formats": [
|
||||
"rich_clipboard",
|
||||
"markdown",
|
||||
"docx",
|
||||
"pdf",
|
||||
"private_document"
|
||||
],
|
||||
"body_selector": "content_only",
|
||||
"content_revision": 1,
|
||||
"content_sha256": "5137c046ff6011eaa672af24e6fea41c83cc9e0e598287aad1a21b0ccea9915d"
|
||||
},
|
||||
"created_at": "2026-09-15T06:10:02.311768Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"has_more_before": false,
|
||||
"oldest_loaded_day": "2026-09-15"
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { test, expect, type Page } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const runDualTab = process.env.INTENT_DUAL_TAB === '1';
|
||||
const ASSETS = process.env.INTENT_ASSETS
|
||||
? path.resolve(process.env.INTENT_ASSETS)
|
||||
: path.resolve(__dirname, '../../.doc/test-reports/assets/2026-09-15-conversation-intent-dual-tab');
|
||||
const SCREENSHOTS = path.join(ASSETS, 'screenshots');
|
||||
fs.mkdirSync(SCREENSHOTS, { recursive: true });
|
||||
|
||||
const OWNER_USER = process.env.OWNER_USERNAME || process.env.ADMIN_USERNAME || '';
|
||||
const OWNER_PASS = process.env.OWNER_PASSWORD || process.env.ADMIN_PASSWORD || '';
|
||||
const PROJECT_NAME = (process.env.INTENT_PROJECT_NAME || '').trim();
|
||||
|
||||
test.skip(!runDualTab, 'set INTENT_DUAL_TAB=1 against rebuilt dev; do not use project 143');
|
||||
|
||||
async function login(page: Page, username: string, password: string) {
|
||||
await page.goto('/');
|
||||
await page.fill('input[placeholder*="系统用户名"]', username);
|
||||
await page.fill('input[placeholder*="登录密码"]', password);
|
||||
await page.click('button:has-text("进入医疗协同工作台")');
|
||||
await expect(page.getByText('新建立项会话')).toBeVisible({ timeout: 30_000 });
|
||||
}
|
||||
|
||||
async function openProject(page: Page, name: string) {
|
||||
const entry = page.getByTitle(name, { exact: true });
|
||||
await expect(entry.first()).toBeVisible({ timeout: 30_000 });
|
||||
await entry.first().click();
|
||||
await expect(page.getByRole('banner').getByText(name, { exact: true })).toBeVisible({ timeout: 30_000 });
|
||||
}
|
||||
|
||||
test.describe.serial('conversation intent dual-tab busy', () => {
|
||||
test('tab B shows busy without reload and keeps empty actions', async ({ browser }) => {
|
||||
if (!OWNER_USER || !OWNER_PASS) {
|
||||
throw new Error('OWNER_USERNAME and OWNER_PASSWORD are required');
|
||||
}
|
||||
if (!PROJECT_NAME) {
|
||||
throw new Error('INTENT_PROJECT_NAME is required');
|
||||
}
|
||||
if (/143/.test(PROJECT_NAME) || process.env.INTENT_PROJECT_ID === '143') {
|
||||
throw new Error('project 143 is read-only');
|
||||
}
|
||||
|
||||
const context = await browser.newContext();
|
||||
const tabA = await context.newPage();
|
||||
const posts: { status: number; body: string }[] = [];
|
||||
const attachRunProbe = (page: Page) => {
|
||||
page.on('response', async (res) => {
|
||||
if (res.url().includes('/api/v1/tasks/runs') && res.request().method() === 'POST') {
|
||||
posts.push({ status: res.status(), body: await res.text().catch(() => '') });
|
||||
}
|
||||
});
|
||||
};
|
||||
attachRunProbe(tabA);
|
||||
await login(tabA, OWNER_USER, OWNER_PASS);
|
||||
await openProject(tabA, PROJECT_NAME);
|
||||
|
||||
const tabB = await context.newPage();
|
||||
attachRunProbe(tabB);
|
||||
await tabB.goto('/');
|
||||
await openProject(tabB, PROJECT_NAME);
|
||||
|
||||
const busyBanner = tabB.getByText(/本文档正由【(项目助手|其他用户)】编辑/)
|
||||
const busyBadge = tabB.getByText('编辑中', { exact: true })
|
||||
await expect(busyBanner.or(busyBadge).first()).toBeVisible({ timeout: 20_000 })
|
||||
await tabA.screenshot({ path: path.join(SCREENSHOTS, 'tab-a-running.png'), fullPage: true })
|
||||
await tabB.screenshot({ path: path.join(SCREENSHOTS, 'tab-b-busy.png'), fullPage: true })
|
||||
|
||||
await expect(tabB.getByRole('button', { name: /发送/ })).toBeDisabled()
|
||||
await expect(tabB.getByText('DOC_BUSY')).toBeVisible()
|
||||
|
||||
const box = tabB.getByPlaceholder(/向项目助手提问/)
|
||||
await expect(box).toBeVisible({ timeout: 15_000 })
|
||||
await box.fill('请开始编写用户初始需求表')
|
||||
const send = tabB.getByRole('button', { name: /发送/ })
|
||||
if (await send.isEnabled()) {
|
||||
await send.click()
|
||||
}
|
||||
await tabB.waitForTimeout(2000)
|
||||
await tabB.screenshot({ path: path.join(SCREENSHOTS, 'tab-b-after-write.png'), fullPage: true })
|
||||
|
||||
await expect(tabB.getByRole('button', { name: /重试|重新开始/ })).toHaveCount(0)
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(ASSETS, 'playwright-network.json'),
|
||||
JSON.stringify({ posts, project: PROJECT_NAME }, null, 2),
|
||||
)
|
||||
|
||||
await tabB.reload()
|
||||
await expect(tabB.getByText(/本文档正由【(项目助手|其他用户)】编辑/).or(tabB.getByText('DOC_BUSY')).first()).toBeVisible({ timeout: 20_000 })
|
||||
await expect(tabB.getByRole('button', { name: /重试|重新开始/ })).toHaveCount(0)
|
||||
await tabB.screenshot({ path: path.join(SCREENSHOTS, 'tab-b-after-reload.png'), fullPage: true })
|
||||
|
||||
await context.close();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user