fix(acp): implement session/delete so Zed Trash removes whale sessions - #355
Conversation
| core.ApprovalEventsSuffix, | ||
| core.ToolInputEventsSuffix, | ||
| ".approvals.json", | ||
| ".jsonl.tmp", |
There was a problem hiding this comment.
Blocking: This hard-delete path leaves several session-scoped sidecars on disk: .state.json, .todo.json, .user_input.json, and .goal.json (see the corresponding state helpers under internal/session). Those files can retain the session mode, todos, pending user-input questions, and goal/objective after Zed reports the thread deleted.
Please include them in this cleanup list and extend the deletion test to create and assert removal of each.
There was a problem hiding this comment.
Thanks for the careful review — confirmed. session/delete (removeSessionFiles) removed the primary .jsonl plus the meta/telemetry/approvals sidecars, but left the session-state files persisted by the helpers under internal/session. I cross-checked every session-scoped path in the repo; the full missing set is exactly the four you listed:
.state.json(mode) —internal/session/mode_state.go.todo.json(todos) —internal/session/todo_state.go.user_input.json(pending user-input questions) —internal/session/user_input_state.go.goal.json(goal/objective) —internal/session/goal_state.go
(internal/app/commands_doctor.go independently lists meta/state/todo/user_input; .goal.json is confirmed by goal_state.go.)
The fix adds all four to removeSessionFiles using the same SanitizeSessionID(id)+suffix path construction as the helpers. TestSessionDeleteRemovesPersistedSession now creates each of the four sidecars and asserts they are gone after delete, and assertSessionArtifactsGone covers them as well. The full internal/acp suite passes.
For completeness, the sweep also verified the remaining per-session artifacts are out of scope for this path: promoted_tools.json is written only by the main app into a per-session subdirectory (sessionsDir/<id>/promoted_tools.json), and tool-result payloads live under ~/.whale/tool-results/<id>/ — the ACP handler creates neither; it keeps flat files in its sessions dir.
453240f to
e9c83b5
Compare
Zed's agent panel only deletes archived threads when the agent advertises agentCapabilities.sessionCapabilities.delete and implements session/delete; without it the Trash button is a cosmetic no-op (zed-industries/zed#51912). - initialize advertises delete: {} (SessionDeleteCapabilities). - Dispatch session/delete -> handleSessionDelete: refuse in-flight prompts (every .jsonl writer is turn-scoped, so deleting mid-turn would let the prompt resurrect the file); otherwise drop the live session and close its runtime off-path; idempotent success for unknown sessions. - removeSessionFiles: primary <id>.jsonl removal is fatal on failure; sidecars (meta.json, approval/tool-input events, approvals.json, stale .jsonl.tmp) are best-effort. - Tests: advertise; happy path; unknown id; invalid ids/params; live idle session (Close fires); in-flight refusal; post-delete load replays nothing; fatal .jsonl removal failure; -race delete-vs-list concurrency. Co-authored-by: GPT-5.6 Sol <codex@openai.com>
handlePrompt looked up the session, released the lock, then registered the prompt under a second lock acquire. session/delete or LRU eviction could remove the session in that window, leaving the prompt to run on a closed runtime and — for delete — recreate the .jsonl the delete just removed. Register under the lock with a fresh map lookup; if the session is gone, respond session-not-found instead of running on a closed runtime. Co-authored-by: GPT-5.6 Sol <codex@openai.com>
e9c83b5 to
03cab99
Compare
|
Commits are only separated for reviewability, you can Squash and merge. |
Summary
Zed gates thread deletion on
agentCapabilities.sessionCapabilities.delete+session/delete; whale-acp advertised neither → agent-panel Trash was a cosmetic no-op (zed-industries/zed#51912).Changes
initializeadvertisessessionCapabilities {list:{}, delete:{}}.session/delete: params required;sessionIdvalidated (isSafeSessionID) before any file access. Live session: refuses in-flight prompts (every.jsonlwriter is turn-scoped → file resurrection unreachable), else drops from map + closes runtime off-path. Unknown id → idempotent success.<id>.jsonlfatal on failure; sidecars best-effort (meta.json,.approval_events.jsonl,.tool_input_events.jsonl,.approvals.json, stale.jsonl.tmp)..jsonl.Validation
go build ./...,go vet, gofmt clean; full suite green exceptinternal/tools(pre-existingTestRunShellBackgroundDoesNotPanic10m timeout).-raceoninternal/acp+internal/session+internal/store..jsonlfailure; dir-as-file;_metaignored;-racedelete-vs-list concurrency.Review guidance
handleSessionDelete+removeSessionFiles(handler.go): in-flight guard, off-path close, fatal-vs-best-effort split.adapter.goregistration re-check: closes the delete/eviction gap.User-visible impact
SessionListUpdate::Refreshafterdelete_sessionsucceeds), so the thread disappears from the archive.session/list.Breaking changes
None. New method
session/delete; capability addition only affects clients that check it.Reproduction
initialize→sessionCapabilities.deleteadvertised.session/new+session/prompt; archive;session/delete→{};session/list→ session gone, artifacts removed on disk.Developed with carefully directed, manually reviewed AI assistance.