Skip to content

feat(sdk): Expose snapshot.track() and session.addPart() for plugin undo integration #7553

@kdcokenny

Description

@kdcokenny

Problem

The native task tool blocks synchronously, limiting multi-agent orchestration patterns. Community plugins like opencode-background-agents enable async delegation, but changes made by background agents cannot be reverted with /undo because:

  1. PatchParts are only created during LLM stream processing at finish-step
  2. No SDK API exists to create patches programmatically
  3. No plugin hooks for snapshot/patch integration

This forces an impossible choice: either block the parent session (defeating async), or lose undo capability.

Proposed Solution

Expose two primitives that already exist internally, allowing plugins to integrate async operations with the native undo system.

API

// 1. Expose existing Snapshot.track()
// Captures current file state, returns git tree hash
client.snapshot.track(): Promise<string | undefined>

// 2. Expose Session.updatePart() pattern
// Adds a PatchPart to an existing message so /undo can find it
client.session.addPart(
  sessionId: string,
  messageId: string,
  part: { type: 'patch', hash: string, files: string[] }
): Promise<void>

Usage

// Before async delegation starts
const beforeHash = await client.snapshot.track()
if (!beforeHash) throw new Error("Failed to capture snapshot")

// Fire async delegation (non-blocking)
const delegation = await startDelegation(prompt, agent)

// ... parent continues working ...

// When delegation completes (via session.idle listener)
const changedFiles = getFilesChangedByDelegation(delegation)
if (changedFiles.length === 0) return  // Nothing to register

// Register changes with parent's undo history
await client.session.addPart(parentSessionId, parentMessageId, {
  type: 'patch',
  hash: beforeHash,
  files: changedFiles
})

// Now /undo will find this patch and revert the async agent's changes!

Why This Design

  1. Minimal surface area - Just two methods, both exposing existing internals
  2. Matches existing patterns - addPart mirrors Session.updatePart(), validation follows "trust caller, validate structure" philosophy
  3. No architectural changes - Uses existing PatchPart structure and undo scanning
  4. Enables ecosystem innovation - Plugins can build async orchestration with native undo

Prior Art

Tool/Framework Approach
VS Code Extensions push custom undo handlers via workspace.applyEdit()
Cline Shadow Git repo for checkpoints, message-timestamp association
Aider Session-scoped commit tracking, per-file git checkout for undo
Crystal Git worktrees for isolation, commit-based undo

Internal Consistency

This proposal exposes existing internals without modification:

  • Snapshot.track() returns string | undefined → expose as client.snapshot.track()
  • Session.updatePart() pattern → expose as client.session.addPart()
  • PatchPart { type, hash, files } → same structure
  • Undo scans for type === 'patch' → works automatically

References

Implementation Notes

The implementation would:

  1. Add track() method to SDK's snapshot namespace (wraps existing Snapshot.track())
  2. Add addPart() method to SDK's session namespace (wraps existing Session.updatePart() pattern)
  3. Use Zod for structure validation (matching existing patterns)
  4. Trust caller for sessionId/messageId (matching existing patterns)

Happy to contribute a PR if this direction is acceptable.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions