feat: optimize subagent list children query
This commit is contained in:
@@ -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 packages/client/runtime/README.md
|
||||
README.md: 0f5b35b6fa4a92f083d5e20c354c19eaae1d5b70
|
||||
README.zh.md: 67cdf6479ef2ba7156b7babb7249b51f8640b134
|
||||
README.md: e4b8e1777ad3a01144dfe9da0a81f83e0f4e2b6a
|
||||
README.zh.md: 5aa53ce1ec680579b44f0b2859104a1eda7471d3
|
||||
|
||||
@@ -56,7 +56,7 @@ Each resident `Session` owns a `modelSelection` snapshot containing the current
|
||||
|
||||
## Addressed subagent conversations
|
||||
|
||||
`SessionListState.subagentsByParent` carries direct durable catalogs and `currentAddress` records the catalog-derived `{parentSessionId, childSessionId}` for the selected child. Only that recorded address selects subagent transport: lineage alone remains insufficient because ordinary forks also have `parentId`. An addressed Session loads and reconnects through `subagent.history`, sends through `subagent.prompt`, never calls ordinary cancel, and persists its address with the selected session across refresh and repeated ordinary selection of that same child. The list also projects the header's coarse `origin: 'subagent'` classification for navigation filtering; the recorded address, not `origin`, remains transport authority. Catalog reads are single-flight; `host/session-status` flips a listed child's coarse activity in place, while `host/session-added` causes one debounced refetch only while that parent catalog is open. Parent availability propagates into `ConversationSnapshot.subagent` so presentation can replace the composer with a read-only explanation without activating the parent.
|
||||
`SessionListState.subagentsByParent` carries direct durable catalogs and `currentAddress` records the catalog-derived `{parentSessionId, childSessionId}` for the selected child. Only that recorded address selects subagent transport: lineage alone remains insufficient because ordinary forks also have `parentId`. An addressed Session loads and reconnects through `subagent.history`, sends through `subagent.prompt`, never calls ordinary cancel, and persists its address with the selected session across refresh and repeated ordinary selection of that same child. The list also projects the header's coarse `origin: 'subagent'` classification for navigation filtering; the recorded address, not `origin`, remains transport authority. Catalog reads are single-flight; `host/session-status` flips a listed child's coarse activity in place, while an origin-classified `host/session-added` immediately marks any loaded direct parent row `hasChildren: true` and causes one debounced refetch when that parent is selected or its catalog is open. Parent availability propagates into `ConversationSnapshot.subagent` so presentation can replace the composer with a read-only explanation without activating the parent.
|
||||
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ Session 对象会在事件 wire 边界依据生产方的完整字段契约,验
|
||||
|
||||
## 已寻址的 subagent 对话
|
||||
|
||||
`SessionListState.subagentsByParent` 携带直接持久化目录,`currentAddress` 则记录所选 child 从目录得到的 `{parentSessionId, childSessionId}`。只有这份已记录地址能选择 subagent 传输;单凭谱系仍然不足,因为普通 fork 同样具有 `parentId`。已寻址的 Session 通过 `subagent.history` 加载和重连,通过 `subagent.prompt` 发送,绝不调用普通取消,并在刷新期间及通过普通选择路径重复选择同一 child 时,把地址与所选会话一同持久化。列表还会投影 header 的粗粒度 `origin: 'subagent'` 分类供导航过滤;传输的权威依据仍是已记录地址,而不是 `origin`。目录读取为 single-flight;`host/session-status` 就地翻转已列 child 的粗粒度活动状态,`host/session-added` 则只在对应 parent 目录打开时触发一次去抖动的重拉。parent 可用性会传播到 `ConversationSnapshot.subagent`,使呈现层可以把编辑器替换为只读说明,而不激活 parent。
|
||||
`SessionListState.subagentsByParent` 携带直接持久化目录,`currentAddress` 则记录所选 child 从目录得到的 `{parentSessionId, childSessionId}`。只有这份已记录地址能选择 subagent 传输;单凭谱系仍然不足,因为普通 fork 同样具有 `parentId`。已寻址的 Session 通过 `subagent.history` 加载和重连,通过 `subagent.prompt` 发送,绝不调用普通取消,并在刷新期间及通过普通选择路径重复选择同一 child 时,把地址与所选会话一同持久化。列表还会投影 header 的粗粒度 `origin: 'subagent'` 分类供导航过滤;传输的权威依据仍是已记录地址,而不是 `origin`。目录读取为 single-flight;`host/session-status` 就地翻转已列 child 的粗粒度活动状态,按 origin 分类的 `host/session-added` 则会立即把任何已加载的直接 parent 行标记为 `hasChildren: true`,并在该 parent 被选中或其目录打开时触发一次去抖动的重拉。parent 可用性会传播到 `ConversationSnapshot.subagent`,使呈现层可以把编辑器替换为只读说明,而不激活 parent。
|
||||
|
||||
## 模型体验
|
||||
|
||||
|
||||
@@ -616,6 +616,9 @@ export class SessionManager {
|
||||
...(frame.cwd !== undefined ? { cwd: frame.cwd } : {}),
|
||||
})
|
||||
this.sessions.get(frame.sessionId)?.handleBlank(frame.blank)
|
||||
if (frame.origin === 'subagent' && frame.parentSessionId !== undefined) {
|
||||
this.markCatalogParentExpandable(frame.parentSessionId)
|
||||
}
|
||||
if (frame.parentSessionId !== undefined
|
||||
&& (this.selected === frame.parentSessionId || this.openCatalogs.has(frame.parentSessionId))) {
|
||||
this.scheduleCatalogRefresh(frame.parentSessionId)
|
||||
@@ -714,6 +717,22 @@ export class SessionManager {
|
||||
if (changed) this.notifier.markDirty()
|
||||
}
|
||||
|
||||
/** Mark a loaded parent row expandable after one direct subagent publishes. */
|
||||
private markCatalogParentExpandable(parentSessionId: SessionId): void {
|
||||
let changed = false
|
||||
for (const [catalogParentId, catalog] of this.catalogs) {
|
||||
if (!catalog.entries.some(entry =>
|
||||
entry.kind === 'child' && entry.id === parentSessionId && !entry.hasChildren)) continue
|
||||
const entries = catalog.entries.map((entry) => {
|
||||
if (entry.kind !== 'child' || entry.id !== parentSessionId || entry.hasChildren) return entry
|
||||
return { ...entry, hasChildren: true }
|
||||
})
|
||||
changed = true
|
||||
this.catalogs.set(catalogParentId, { ...catalog, entries })
|
||||
}
|
||||
if (changed) this.notifier.markDirty()
|
||||
}
|
||||
|
||||
private buildListSnapshot(): SessionListSnapshot {
|
||||
const merged: TitledSessionSummary[] = this.summaries.map((summary) => {
|
||||
// List rows read the generic 'title' projection key (host-computed unit
|
||||
|
||||
@@ -287,7 +287,8 @@ describe('subagent catalogs', () => {
|
||||
] as never[] }))
|
||||
api.onSubagentList = () => Promise.resolve(ok({
|
||||
entries: [{
|
||||
kind: 'child', id: S2, mode: 'continuable', label: 'worker', activity: 'running',
|
||||
kind: 'child', id: S2, mode: 'continuable', label: 'worker',
|
||||
activity: 'running', hasChildren: false,
|
||||
}] as never[],
|
||||
parentAvailable: true,
|
||||
}))
|
||||
@@ -385,6 +386,46 @@ describe('subagent catalogs', () => {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('marks a loaded parent row expandable only for a direct subagent publication', async () => {
|
||||
const api = new FakeApiClient()
|
||||
const root = 'fk-root' as SessionId
|
||||
api.onSubagentList = () => Promise.resolve(ok({
|
||||
entries: [
|
||||
{
|
||||
kind: 'child', id: S1, mode: 'continuable', label: 'parent',
|
||||
activity: 'inactive', hasChildren: false,
|
||||
},
|
||||
{
|
||||
kind: 'child', id: S2, mode: 'continuable', label: 'ordinary parent',
|
||||
activity: 'inactive', hasChildren: false,
|
||||
},
|
||||
] as never[],
|
||||
parentAvailable: true,
|
||||
}))
|
||||
const manager = new SessionManager(api)
|
||||
await manager.refreshSubagents(root)
|
||||
|
||||
manager.handleHostEnvelope({
|
||||
rpcId: 'nested-subagent' as never,
|
||||
payload: {
|
||||
type: 'host/session-added', sessionId: 'fk-grandchild' as SessionId,
|
||||
parentSessionId: S1, origin: 'subagent', blank: false,
|
||||
},
|
||||
})
|
||||
manager.handleHostEnvelope({
|
||||
rpcId: 'ordinary-fork' as never,
|
||||
payload: {
|
||||
type: 'host/session-added', sessionId: 'fk-fork' as SessionId,
|
||||
parentSessionId: S2, blank: false,
|
||||
},
|
||||
})
|
||||
|
||||
expect(manager.getListSnapshot().subagentsByParent[root]?.entries).toMatchObject([
|
||||
{ kind: 'child', id: S1, hasChildren: true },
|
||||
{ kind: 'child', id: S2, hasChildren: false },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('remaining branches', () => {
|
||||
|
||||
Reference in New Issue
Block a user