Repository navigation
fix(client): load session ancestors on sync so depth>=2 subagent views resolve their root (#631) #843
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(client): load session ancestors on sync so depth>=2 subagent views resolve their root (#631) #843
Changes from all commits
637a49c
03a0d63
10a6f5f
27b783f
0483469
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1745,15 +1745,46 @@ export function createData(config: CreateDataInput) { | |
| throw error | ||
| }) | ||
| }, | ||
| /** With `children`, also loads every descendant session at any depth, so the family index is complete. */ | ||
| /** | ||
| * With `children`, loads the whole family: every descendant session at any depth, plus the | ||
| * missing ancestors by ID (best effort), so the family index is complete from either end. | ||
| * A plain sync deliberately does not walk ancestors: the TUI's family discovery walks them | ||
| * itself and retries failed reads with backoff, which a walk inside a plain sync would | ||
| * consume and defeat. | ||
| */ | ||
| sync(sessionID: string, options?: { children?: boolean }) { | ||
| return sync.run(options?.children ? `session.family:${sessionID}` : `session:${sessionID}`, async () => { | ||
| const generation = sync.generation() | ||
| const [info, children] = await Promise.all([ | ||
| api().session.get({ sessionID }), | ||
| options?.children ? listChildren({ parentID: sessionID, order: "desc", limit: CHILD_PAGE }) : [], | ||
| ]) | ||
| publishSessions([info, ...children.filter(unread)], generation) | ||
| // Ancestors are never listed by the descendant walk, so a session opened directly | ||
| // (a deep link into a nested subagent, #631) holds no info for them: resolveRoot | ||
| // stops at the first missing parent, the family groups under the wrong root, and | ||
| // the ancestor trail truncates. A family sync reads the missing parents by ID, | ||
| // best effort: a parent that cannot be read leaves the walk at the furthest-known | ||
| // ancestor, exactly what resolveRoot already tolerates. | ||
| const ancestors: SessionInfo[] = [] | ||
| if (options?.children) { | ||
| const seen = new Set([sessionID]) | ||
| let parentID = info.parentID | ||
| while (parentID && !seen.has(parentID)) { | ||
| seen.add(parentID) | ||
| const known = store.session.info[parentID] | ||
| if (known) { | ||
| parentID = known.parentID | ||
| continue | ||
| } | ||
| const parent = await api() | ||
| .session.get({ sessionID: parentID }) | ||
| .catch(() => undefined) | ||
| if (!parent || parent.id !== parentID) break | ||
|
Comment on lines
+1781
to
+1782
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If an ancestor GET fails transiently, this branch exits the walk but lets the family sync complete successfully. Useful? React with 👍 / 👎.
Comment on lines
+1781
to
+1782
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| ancestors.push(parent) | ||
| parentID = parent.parentID | ||
| } | ||
| } | ||
| publishSessions([info, ...ancestors, ...children.filter(unread)], generation) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an ancestor is loaded or renamed while this walk is awaiting another parent request, the saved ancestor snapshot unconditionally overwrites its newer store entry. For example, after fetching a missing parent, the walk waits for the grandparent; a Useful? React with 👍 / 👎. |
||
| // A walk that skipped a failed branch is not kept, so the next sync lists the tree again. | ||
| if (options?.children && !(await syncDescendants(children, new Set([sessionID])))) | ||
| sync.invalidate(`session.family:${sessionID}`) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
sed -n '1700,1830p' packages/client/src/solid/data.tsRepository: developing-today/opencode
Length of output: 6723
Script executed:
Repository: developing-today/opencode
Length of output: 16046
Script executed:
Repository: developing-today/opencode
Length of output: 5832
Publish the requested session before waiting for ancestors.
In the merge base, the requested session and direct children were published after the initial reads. The current family sync inserts a sequential ancestor walk before
publishSessions. A slow or unresolved parent read can delay publication of the requested session and direct children, and it also delays the descendant walk.The initial
infoandlistChildrenreads already block publication, but the ancestor walk adds a new blocking wait for each missing parent. Publish the requested session and direct children before the ancestor walk, then publish ancestors as they resolve.