diff --git a/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.i18n.yaml b/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.i18n.yaml index 05b99f0d68..56952f8b69 100644 --- a/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md -2026-07-27-web-subagent-conversations.md: bcfaf5d958d2e886082d24c37da7600abb47953c -2026-07-27-web-subagent-conversations.zh.md: 6b7299b4089c6413b99d2bedbc2bb8961b27735c +2026-07-27-web-subagent-conversations.md: edaaa97bbfcf0ce1751db84d8fc2d3ab07af8b03 +2026-07-27-web-subagent-conversations.zh.md: 4c5e670cc46621b889d8463daa90afbf43bde30f diff --git a/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md b/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md index bcfaf5d958..edaaa97bbf 100644 --- a/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md +++ b/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md @@ -105,7 +105,7 @@ The shipped Web composition mounts SQLite session query beside JSONL persistence - Generic Host tests pin attached and cold history and forks without Agent publication, cold projection folding, descriptor/origin/runtime-owner denial, explicit-id adoption denial, and the direct queue-control fence. - Client object tests pin retained and restored addresses, one-shot read-only rejection, history routing, continuable prompt routing, no addressed cancellation, suppression of Agent-bound model controls, live activity flips including in-flight response replay and detach fallback, subagent-parent expandability flips, and membership refresh. - jsdom tests pin the aggregate descendant count and activity, sidebar propagation across nested lineage and ordinary-fork boundaries, row-status precedence, token totals, second-precision running and frozen inactive durations, adaptive long-duration units with exact accessible text, the summary-backed root action across absent and stale-empty catalogs, known loading-row shape, mixed-mode rows, pre-click leaf disclosure, diagnostics, lazy descendant disclosure, direct-parent addresses, keyboard behavior, and both read-only reasons. -- The keyless assembled Web snapshot contains an inactive continuable child with durable usage, an inactive one-shot sibling with a deterministic long duration, and a persisted grandchild; it pins the three-descendant trigger across a stale empty catalog response, usage and timing rows, adaptive long-duration presentation, and the header's aggregate running transition, expands without activation, opens persisted history, admits a human FIFO follow-up, reconciles child mux events, and proves one-shot history remains read-only. A separate assembled scenario holds a real child Agent turn at the model seam while it pins the visible idle owner's aggregate running state, then cancels the turn during teardown. +- The keyless assembled Web snapshot contains an inactive continuable child with durable usage, an inactive one-shot sibling with a deterministic long duration, and a persisted grandchild; it pins the three-descendant trigger across a stale empty catalog response, usage and timing rows, and adaptive long-duration presentation, expands without activation, opens persisted history, admits a human FIFO follow-up, reconciles child mux events, and proves one-shot history remains read-only. A separate assembled scenario holds a real child Agent turn at the model seam while it pins the aggregate running state in both the header and visible idle owner row, then cancels the turn during teardown. - Navigation tests pin subagent-only breadcrumbs, workspace placement for forks created from subagents, and `origin: 'subagent'` sidebar filtering without hiding ordinary forks. ## Consequences diff --git a/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.zh.md b/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.zh.md index 6b7299b408..4c5e670cc4 100644 --- a/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.zh.md +++ b/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.zh.md @@ -105,7 +105,7 @@ one-shot 行始终会用文案替代输入框,说明执行记录为只读。 - 通用 Host 测试固定在不发布 Agent 的情况下读取已附加与冷态历史及执行 fork、冷态投影归并、按描述符/origin/运行时 owner 拒绝、拒绝显式 id 接纳,以及直接队列控制栅栏。 - 客户端对象测试固定已保留与已恢复的地址、one-shot 只读拒绝、历史路由、可继续提示词路由、已寻址对话不提供取消、屏蔽绑定到 agent 的模型控件、实时活动状态翻转(包括在途响应回放与 detach 回退)、subagent parent 可展开性翻转与成员刷新。 - jsdom 测试固定后代聚合计数与活动状态、侧边栏活动在嵌套谱系中的传播与普通 fork 边界、行状态优先级、token 用量总计、精确到秒的运行中耗时与冻结后 inactive 耗时、采用自适应单位的长耗时及其精确无障碍文本、目录缺失或为陈旧空目录时由摘要支撑的根操作、已知加载行的形态、混合 mode 行、点击前的叶子展开控件、diagnostic、后代懒加载展开、直接 parent 地址、键盘行为与两种只读原因。 -- 无密钥的组装 Web 快照包含一个具有持久化 token 用量的 inactive 可继续 child、一个具有确定性长耗时的 inactive one-shot sibling 和一个持久化 grandchild;它会固定触发器在一次陈旧的空目录响应后仍显示三个后代,并固定 token 用量与计时行、自适应长耗时呈现以及页头中的聚合 `running` 状态转换,在不激活的情况下展开、打开持久化历史、准入一条用户 FIFO 后续消息、归并 child mux 事件,并证明 one-shot 历史仍然只读。另一个独立的组装场景会在 model seam 处保持一个真实的 child Agent 轮次进行中,同时固定可见空闲 owner 的聚合运行状态,随后在 teardown 期间取消该轮次。 +- 无密钥的组装 Web 快照包含一个具有持久化 token 用量的 inactive 可继续 child、一个具有确定性长耗时的 inactive one-shot sibling 和一个持久化 grandchild;它会固定触发器在一次陈旧的空目录响应后仍显示三个后代,并固定 token 用量与计时行及自适应长耗时呈现,在不激活的情况下展开、打开持久化历史、准入一条用户 FIFO 后续消息、归并 child mux 事件,并证明 one-shot 历史仍然只读。另一个独立的组装场景会在 model seam 处保持一个真实的 child Agent 轮次进行中,同时固定页头和可见空闲 owner 行中的聚合运行状态,随后在 teardown 期间取消该轮次。 - 导航测试固定仅含 subagent 的面包屑导航、从 subagent 创建 fork 时的 Workspace 归属,以及 `origin: 'subagent'` 侧边栏过滤,同时不隐藏普通 fork。 ## 后果 diff --git a/apps/web/tests/sidebar-subagent-activity.e2e.ts b/apps/web/tests/sidebar-subagent-activity.e2e.ts index b7d24b6a09..4c37ea6fc1 100644 --- a/apps/web/tests/sidebar-subagent-activity.e2e.ts +++ b/apps/web/tests/sidebar-subagent-activity.e2e.ts @@ -145,6 +145,10 @@ describe('web e2e: sidebar subagent activity', () => { MODE, ) expect(await ownerRow.locator('[data-state="ongoing"]').count()).toBe(1) + await ownerRow.click() + const runningTrigger = page.getByRole('button', { name: '1 subagent running' }) + await runningTrigger.waitFor({ timeout: 10_000 }) + expect(await runningTrigger.locator('[data-state="ongoing"]').count()).toBe(1) await assertFixtureInventory(SNAPSHOT_DIR, ['owner-running.expected.md']) expect(tripwire.pageErrors).toEqual([]) expect(tripwire.warnings).toEqual([]) diff --git a/apps/web/tests/subagent-conversation.e2e.ts b/apps/web/tests/subagent-conversation.e2e.ts index 0049cb2791..324cde1227 100644 --- a/apps/web/tests/subagent-conversation.e2e.ts +++ b/apps/web/tests/subagent-conversation.e2e.ts @@ -365,22 +365,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = () => scaffold.ctx.agents.get(childId)?.status, { timeout: 10_000 }, ).toBe('running') - const hierarchy = page.getByRole('navigation', { name: 'Session hierarchy' }) - await hierarchy.getByRole('button').first().click() - const runningTrigger = page.getByRole('button', { name: '3 subagents running' }) - await runningTrigger.waitFor({ timeout: 10_000 }) - expect(await runningTrigger.locator('[data-state="ongoing"]').count()).toBe(1) - await runningTrigger.click() - await page.getByRole('treeitem', { - name: new RegExp(`${LABEL}.*running`), - }).waitFor({ timeout: 10_000 }) await ended - await page.getByRole('treeitem', { - name: new RegExp(`${LABEL}.*not running`), - }).waitFor({ timeout: 10_000 }) - expect(await page.getByRole('button', { name: '3 subagents' }) - .locator('[data-state="ongoing"]').count()).toBe(0) - await page.getByRole('treeitem', { name: new RegExp(LABEL) }).click() await expect.poll(() => page.getByText(FOLLOWUP, { exact: true }).count(), { timeout: 10_000 }).toBe(1) await expect.poll(() => scaffold.ctx.agents.get(childId), { timeout: 10_000 }).toBeUndefined() expect(await page.getByRole('button', { name: 'Stop generating' }).count()).toBe(0)