Documentation
¶
Index ¶
- Constants
- Variables
- type ConflictBroadcaster
- type ConflictPayload
- type FileChange
- type FileStatus
- type Manager
- func (m *Manager) AdvanceTurn(ctx context.Context, messageCount int) error
- func (m *Manager) CheckoutFile(ctx context.Context, tag Tag, relPath string) error
- func (m *Manager) Cleanup(ctx context.Context)
- func (m *Manager) Conflicts(ctx context.Context) ([]ConflictPayload, error)
- func (m *Manager) Diff(ctx context.Context, from, to Tag) ([]FileChange, error)
- func (m *Manager) OldestTag(ctx context.Context) string
- func (m *Manager) ReadBlob(ctx context.Context, sha string) ([]byte, error)
- func (m *Manager) ReferencedBlobs(ctx context.Context) []string
- func (m *Manager) Restore(ctx context.Context, tag Tag) (int, error)
- func (m *Manager) RestorePreview(ctx context.Context, tag Tag) ([]string, error)
- func (m *Manager) Snapshot(ctx context.Context, relPath string, newContent []byte, messageCount int) (Tag, error)
- type Ring
- type Store
- func (s *Store) AdvanceTurn(ctx context.Context, chatID api.ChatID, messageCount int)
- func (s *Store) CheckoutFile(ctx context.Context, chatID api.ChatID, tag Tag, relPath string) error
- func (s *Store) Cleanup(ctx context.Context, chatID api.ChatID)
- func (s *Store) Conflicts(ctx context.Context, chatID api.ChatID) ([]ConflictPayload, error)
- func (s *Store) Diff(ctx context.Context, chatID api.ChatID, from, to Tag) ([]FileChange, error)
- func (s *Store) OldestTag(ctx context.Context, chatID api.ChatID) Tag
- func (s *Store) OwnerOf(ctx context.Context, relPath string) (api.ChatID, bool)
- func (s *Store) ReadBlob(ctx context.Context, chatID api.ChatID, sha string) ([]byte, error)
- func (s *Store) Restore(ctx context.Context, chatID api.ChatID, tag Tag) (int, error)
- func (s *Store) RestorePreview(ctx context.Context, chatID api.ChatID, tag Tag) ([]string, error)
- func (s *Store) Snapshot(ctx context.Context, chatID api.ChatID, relPath string, newContent []byte, ...) (Tag, error)
- func (s *Store) StartBackgroundTasks(ctx context.Context)
- func (s *Store) Stop()
- type Tag
Constants ¶
const ( FileAdded = chktypes.FileAdded FileModified = chktypes.FileModified FileDeleted = chktypes.FileDeleted )
FileAdded and the following constants re-export the FileStatus values from the types sub-package for backward compatibility.
const (
// FileEvents is the name of the per-chat append-only event log file.
FileEvents = "events.jsonl"
)
const RestoreStageSuffix = ".vibekit-restore-*"
RestoreStageSuffix is the temp-file pattern used by both CheckoutFile and stageRestoreLocked for two-phase atomic writes. Single constant so cleanup tooling or tests that glob for orphaned staging files can reference it instead of hardcoding the pattern.
Variables ¶
var ErrBlobCorrupt = errors.New("blob integrity check failed")
ErrBlobCorrupt signals that a blob's content hash does not match its filename (bitrot / truncated write / bad sector). Get fails closed on this so a corrupt blob can't be written over a good working-tree file during a restore.
var ErrBlobNotFound = errors.New("blob not found")
ErrBlobNotFound signals that a blob was requested but isn't in the store. Distinct from os.ErrNotExist so callers can tell "blob GC'd" apart from "filesystem error reading a blob that should exist".
var ErrBlobTooLarge = errors.New("blob too large")
ErrBlobTooLarge signals that a blob exceeds the read cap. Distinct from a generic error so the HTTP layer can map it to 413 without string matching.
var ErrLogCorrupt = errors.New("event log corrupt")
ErrLogCorrupt signals that the event log is in a corrupt or tampered state (e.g. symlink replacement). Distinct from a generic error so callers can classify the failure category.
var ErrPathEscape = errors.New("path escapes workdir")
ErrPathEscape signals that a workspace-relative path resolves outside the workdir boundary. Distinct from a generic error so callers can classify the failure without string matching.
var ErrTagNotFound = chktypes.ErrTagNotFound
ErrTagNotFound signals that a requested tag isn't in the event log. Separate from a generic error so the HTTP layer can map it to 404.
var ErrTransient = errors.New("transient checkpoint error")
ErrTransient signals a retryable I/O failure (disk full, permission denied temporarily, NFS stale handle). Callers can use errors.Is(err, ErrTransient) to distinguish "retry later" from permanent failures (ErrPathEscape, ErrTagNotFound, ErrLogCorrupt).
var ParseTag = chktypes.ParseTag
ParseTag validates and returns a Tag.
Functions ¶
This section is empty.
Types ¶
type ConflictBroadcaster ¶
type ConflictBroadcaster func(chatID string, payload *ConflictPayload)
ConflictBroadcaster is the hook the Manager calls to fan conflict events out to SSE subscribers. Takes the payload by pointer so the hot path doesn't copy ~90 bytes per call.
type ConflictPayload ¶
type ConflictPayload = chktypes.ConflictPayload
ConflictPayload is re-exported from the types sub-package for backward compatibility within this package.
type FileChange ¶
type FileChange = chktypes.FileChange
FileChange is one entry returned by Diff. Re-exported from the types sub-package for backward compatibility.
type FileStatus ¶
type FileStatus = chktypes.FileStatus
FileStatus is a typed string for diff result statuses. Re-exported from the types sub-package for backward compatibility.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager owns one chat's checkpoint log and drives all snapshot / restore / diff operations on behalf of that chat. Safe for concurrent use — every entry point serializes on m.mu.
func (*Manager) AdvanceTurn ¶
AdvanceTurn records a fresh user turn boundary in the event log. Called from cmdPrompt right before the agent starts a new turn so the tag allocation gets a clean "N" slot. messageCount is the persisted message count at the moment of the turn boundary.
func (*Manager) CheckoutFile ¶
CheckoutFile reverts a single file to its content at `tag`. Used by the per-file Undo button on tool-call cards. Does not touch other files, does not truncate the transcript. Two-phase (stage + rename) so a crash mid-write can't leave a corrupt file.
func (*Manager) Cleanup ¶
Cleanup removes this chat's event log + parent directory and forgets every cross-chat index entry owned by this chat. Blobs are NOT touched here — the blob GC pass reaps any orphans asynchronously.
Stale-reference semantics: after Cleanup, any caller holding a *Manager reference obtained from a prior Store.get on this chatID will see an empty state on the next method call (the log is gone, so ensureLoaded replays as a fresh chat — no error). Callers that need "was this chat deleted?" semantics should re-fetch through Store.get, not reuse stale Manager references. In practice the hub always goes through Store.get and never holds Manager pointers across Cleanup.
func (*Manager) Conflicts ¶
func (m *Manager) Conflicts(ctx context.Context) ([]ConflictPayload, error)
Conflicts returns every conflict_detected event recorded for this chat in chronological order. O(1) copy from the ring buffer maintained by state.apply — no log re-read.
func (*Manager) Diff ¶
Diff returns per-file changes between two tags. Walks the event log for every file touched in (from, to], compares the stored blobs at the two endpoints, and returns a line-delta summary.
func (*Manager) OldestTag ¶
OldestTag is the earliest available tag, or "" if no snapshots exist yet. O(1) via the sorted index in state.
func (*Manager) ReadBlob ¶
ReadBlob returns the content of a blob for this chat. Exposed so the client can fetch "what another chat left on disk" without going through the filesystem. SHA is validated as hex to prevent path traversal; only blobs referenced by this chat's event log are returned — we refuse SHAs we don't recognize so chat A can't probe chat B's private blobs.
func (*Manager) ReferencedBlobs ¶
ReferencedBlobs returns a copy of all blob SHAs referenced by this chat's event log. Used by the GC to collect the referenced set from in-memory state instead of re-reading disk. Thread-safe.
func (*Manager) Restore ¶
Restore rolls the workspace back to the state captured at `tag`. Only files snapshotted at tags after `tag` are touched — every other file in the workspace is preserved. Returns the message count watermark recorded at `tag` so the caller can truncate the chat transcript in sync.
Journaled two-phase commit:
- Stage every target file to a `.vibekit-restore` sibling (phase 1). Nothing destructive yet; any stage failure aborts with a clean workspace.
- Append `restore_started` so the log records "we are about to mutate". fsync guarantees this survives a crash.
- Phase 2: execute every rename / delete. A crash between renames leaves the workspace half-restored, but the journal tells us what to finish on next load.
- Append `restore_committed` on success.
Recovery (in ensureLoaded): if we see a `restore_started` without a matching `restore_committed` on replay, re-run the restore to that tag before accepting any new writes. Crashes during recovery are themselves idempotent — running the commit twice is harmless (renames either succeed or error with "already at target state").
func (*Manager) RestorePreview ¶
RestorePreview returns the list of files that would be touched by a Restore(tag) call. Used by the client to warn the user before the destructive action so they know whether in-flight editor buffers will be clobbered. Read-only; no log mutations.
NOTE: the preview is a point-in-time snapshot of state at call time. If the agent snapshots another file between preview and Restore, that file will be touched by Restore but will not appear in the preview. Today the client accepts that window; tighter race closure (e.g. passing the expected file set back to Restore and rejecting the call if it changed) is deferred until a concrete user problem motivates it.
func (*Manager) Snapshot ¶
func (m *Manager) Snapshot(ctx context.Context, relPath string, newContent []byte, messageCount int) (Tag, error)
Snapshot captures the state of `relPath` just BEFORE an agent write lands on it AND the content it's about to write. The caller passes `newContent` so we can record the post-write SHA without a second disk read — that SHA feeds the cross-chat index's drift detector.
The write itself is NOT performed here. The caller writes `newContent` to disk after Snapshot returns.
type Ring ¶
type Ring[T any] struct { // contains filtered or unexported fields }
Ring is a generic fixed-size ring buffer. Append is O(1) unconditionally; oldest entries are silently overwritten when the buffer is full. Len() == cap means full.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the hub's entry point. One Store per vibekit process; internally it hands out one Manager per chatID and keeps the shared blob store + cross-chat index alive.
The 5-minute blob age gate (blobGCMinAge) provides the primary safety guarantee against removing in-flight blobs: a blob written now cannot be reaped for at least 5 minutes, which is far longer than the event append takes. No per-sweep lock is needed.
func NewStore ¶
func NewStore(configDir, workDir string, onConf ConflictBroadcaster) *Store
NewStore builds a Store rooted at configDir with the given work tree. All chats share the same work tree, the same blob store, and the same cross-chat index. `onConf` is called every time a Snapshot detects drift; pass nil to disable broadcasting (tests). The cross-chat index starts empty and is populated lazily as Managers are accessed — each Manager's ensureLoaded applies its events to the shared index after replay, halving startup I/O compared to the prior eager-rebuild approach.
func (*Store) AdvanceTurn ¶
AdvanceTurn delegates to Manager.AdvanceTurn (pass-through — logic lives in Manager). Errors are logged but not returned because the prompt path shouldn't stall on a checkpoint failure — the checkpoint exists to serve the user, not gate them.
func (*Store) CheckoutFile ¶
CheckoutFile reverts a single file to its content at `tag` (pass-through — logic lives in Manager).
func (*Store) Cleanup ¶
Cleanup removes chatID's event log + parent dir and evicts the manager from cache. Index entries owned by the chat are forgotten inside Manager.Cleanup. Blobs survive — the GC pass reaps orphans.
Serializes with runGCOnce via gcLock: a chat delete walks the same event logs runBlobGC collects from, so wiping a chat mid- sweep could drop its blob references from the referenced-set and let the sweep reap blobs that were live until microseconds ago. Both Cleanup and Snapshot take gcLock.RLock so neither can interleave with the GC's exclusive Lock() sweep — Cleanup mutates the referenced-blob set from the GC's perspective just as Snapshot does, only in the opposite direction.
func (*Store) Conflicts ¶
Conflicts returns all conflict_detected events for a chat (pass-through — logic lives in Manager).
func (*Store) Diff ¶
Diff returns per-file changes between two tags (pass-through — logic lives in Manager).
func (*Store) OldestTag ¶
OldestTag returns the earliest available tag for chatID, or "" (pass-through — logic lives in Manager).
func (*Store) OwnerOf ¶ added in v0.2.8
OwnerOf returns the chat whose agent most recently wrote relPath — the owner of the path's checkpoint lineage — or ok=false when no chat tracks it. The editor-save capture uses this to route a manual save into the one timeline whose restores and undos consult the path. The first call warms the cross-chat index from disk: managers load lazily, so a cold process would otherwise report "no owner" for everything until chats happened to be accessed.
func (*Store) ReadBlob ¶
ReadBlob returns blob content for a chat-scoped SHA (pass-through — logic lives in Manager).
func (*Store) Restore ¶
Restore rolls the workspace back to `tag` (pass-through — logic lives in Manager).
func (*Store) RestorePreview ¶
RestorePreview returns paths a Restore would mutate (pass-through — logic lives in Manager).
func (*Store) Snapshot ¶
func (s *Store) Snapshot(ctx context.Context, chatID api.ChatID, relPath string, newContent []byte, messageCount int) (Tag, error)
Snapshot captures the pre-write content of relPath (pass-through — logic lives in Manager). The gcLock.RLock coordination lives inside Manager.Snapshot itself so this wrapper is a thin pass-through.
func (*Store) StartBackgroundTasks ¶
StartBackgroundTasks kicks off the periodic blob GC. Runs one immediate sweep, then every blobGCInterval. Idempotent — a second call while a gcLoop is already running is a no-op, so defensive double-calls from lifecycle refactors don't fork a second ticker and double the scheduled work. Split out from NewStore so unit tests can construct a Store without accidentally starting a goroutine. The context is used for cancellation of in-flight GC I/O on shutdown.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package gc implements the blob garbage collector for the checkpoint subsystem.
|
Package gc implements the blob garbage collector for the checkpoint subsystem. |
|
Package types defines checkpoint domain types shared between the checkpoint implementation and its consumers (api, hub).
|
Package types defines checkpoint domain types shared between the checkpoint implementation and its consumers (api, hub). |