feat(app): persist dismissed provider subagents across app restarts - #4441
Closed
SezginKahraman wants to merge 1 commit into
Closed
feat(app): persist dismissed provider subagents across app restarts#4441SezginKahraman wants to merge 1 commit into
SezginKahraman wants to merge 1 commit into
Conversation
Archive finished currently hides completed/failed/canceled provider-owned (Task-tool) subagent rows only in useProviderSubagentStore's in-memory hiddenFromTrack set. This is documented, intentional behavior (docs/agent-lifecycle.md), but it means every dismissal is silently undone by a page reload or app restart: refreshProviderSubagents refills descriptors from the daemon on mount, and nothing in the daemon has a concept of a dismissed provider subagent to filter them back out. Wrap useProviderSubagentStore in zustand's persist middleware, storing only hiddenFromTrack (not the much larger, non-serializable-safe descriptors/timelines maps) through the same createValidatedPersistStorage + AsyncStorage pattern already used by the sidebar collapsed-sections and workspace-service-route-preferences stores. The store construction moves into an exported createProviderSubagentStore(storage) factory, matching workspace-service-routes/store.ts, so tests can inject an in-memory storage and prove a real write -> restart -> read round trip instead of only asserting against the in-memory reducer. The web build of @react-native-async-storage/async-storage reads window.localStorage unconditionally, which crashes when evaluated web-side with no window (this package's Node-environment unit tests, or an SSR context). resolveHiddenTrackStorage guards that one gap with a no-op storage; native and real-browser evaluation are unaffected. This is a scoped, deliberate change to previously-documented behavior: dismissals are still local to the device (no daemon concept of "archived" for provider subagents, so nothing propagates to other clients), and the hidden-id set is not pruned against the live provider list, so it grows for as long as a device keeps dismissing rows. Both are called out in the doc update. A full fix would need a daemon-side dismissed/archived concept for provider subagents; that's a larger, separate change.
Collaborator
|
Closing this feature PR. Please describe the workflow and shared need in Discussions, as described in CONTRIBUTING.md. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linked issue
Discussion: #4440 (this is a feature request, not a bug — no issue to close). Opening as a draft per the discussion until there's agreement this is the right shape; see CONTRIBUTING.md guidance on unsolicited PRs.
Type of change
Reasoning
"Archive finished" hides completed/failed/canceled provider-owned (Task-tool) subagent rows only in an in-memory
hiddenFromTrackSet (packages/app/src/subagents/provider-store.ts), documented as intentional indocs/agent-lifecycle.md. Every reload or app restart wipes it, sincerefreshProviderSubagentsrefetches the full descriptor list from the daemon on mount and the daemon has no concept of a "dismissed" provider subagent to filter back out. On a long-running parent that fans out a lot of subagents, this means re-dismissing the same finished rows over and over across reconnects. See #4440 for the full framing.This PR persists just the
hiddenFromTrackset (not the much larger, non-serializable-safedescriptors/timelinesmaps) to the device's local storage, using the samepersist+createValidatedPersistStorage+AsyncStoragepattern already used bysidebar-collapsed-sections-storeandworkspace-service-route-preferences. The store construction moves into an exportedcreateProviderSubagentStore(storage)factory (matchingworkspace-service-routes/store.ts) so tests can inject an in-memory storage and exercise a real write → restart → read round trip.Goals
descriptors/timelinespersistence — those stay in-memory only, refetched/rebuilt from the daemon as before.Non-goals
hiddenFromTrackagainst the live provider list — an existing test (keeps hidden state when a child temporarily disappears from the provider list) already asserts a dismissal must survive a subagent temporarily vanishing from areplaceListresponse, so the set isn't safe to prune on absence; it only grows. Flagged explicitly in the doc update. A size-bounded eviction policy would need a design decision I didn't want to bundle into this PR.QA
Commands run:
New test
survives an app restart instead of resetting Archive finished dismissals(provider-store.test.ts) creates two separate store instances sharing one in-memory storage backend — the second simulates the app restarting — and asserts the dismissal from the first instance is visible in the second afterpersist.rehydrate(). Confirmed it fails (createProviderSubagentStore is not a function) against the pre-change code and passes after.One thing I had to work around:
@react-native-async-storage/async-storage's web build readswindow.localStorageunconditionally, which crashed as an unhandled rejection when the existingprovider-store.test.ts/select.test.tstests (which mutate the real exported singleton, not an injected instance) ran in this package's Node-environment unit test project.resolveHiddenTrackStorageswaps in a no-op storage specifically for that gap (web-targeted evaluation with nowindow— native and real-browser evaluation are unaffected); covered byhidden-track-persistence.test.ts.I did not test this manually against a running app (web only, per my role in the linked discussion) — the QA here is the automated round-trip test above, which exercises the real persistence mechanism rather than a mock.
Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatpasses