Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,24 @@ These are captures of real Electron interactions with a temporary Git workspace

## What works today

- Add local workspaces and discover existing Pi sessions.
- Group existing local folders into named workspaces and discover Pi sessions by their original working directory.
- Start, stop, steer, resume, and fork sessions; preview history before launching Pi.
- Browse linked session archives and branch entries; preview and continue the selected path.
- Render streamed Markdown, thinking, and expandable tool output.
- Browse files, review diffs, stage changes, and commit.
- Discover workspace and global skills, switch models, and save model and reasoning defaults per workspace.
- Use quick switch for workspaces, sessions, files, and commands.
- Switch roots while sessions run in the background; retain per-root drafts, views and model defaults.
- Use quick switch for roots, sessions, files, and commands.

Session history shows source/fork relationships and an entry tree with selected and archive-tip markers. Select a branch tip or an earlier entry to preview its parent chain. Resume at the archive tip continues in place; **Resume in new fork** continues a historical selection in a separate archive and preserves the source. **Fork from here** uses the selected entry. Pi 1.5.0 RPC does not expose arbitrary-entry navigation, so forks extract a native branch archive before launching Pi so even runtime startup writes preserve the source. Continuation requires an intact Pi v3 archive within the 16 MiB browsing limit. See [ADR 0013](docs/adr/0013-session-branch-selection.md) for runtime semantics and limits.

Use **Create workspace** or **Edit workspace** in the sidebar to name a group and select its roots. **Add root** chooses an existing folder. Each session runs in one root; the composer shows its path. Switching roots leaves open sessions running and makes them available under **Open sessions**. Files, Changes, History and defaults remain scoped to the selected root. A missing folder retains readable history, but cannot start or resume Pi until its original path is available. Adding another folder does not relocate a session. Group membership does not change Pi's tool permissions or load sibling roots into its context.

Existing `workspaces.json` and `session-defaults.json` records migrate to `workspace-registry.json` in Electron userData. Original files and `.bak` copies remain intact. Workspace groups and selection persist; live processes and drafts do not restart after app quit. A package folder shares its Git worktree's index: pidex refuses its commit when staged changes exist outside that folder. Open the repository top level to review the whole index.

Active sessions and history use a virtualized transcript. A local development benchmark with 4,500 mixed entries opened in about 1.4 seconds, down from 8.3 seconds, and mounted fewer than 100 transcript DOM nodes instead of 39,000. Timings vary by machine and load; see [ADR 0011](docs/adr/0011-virtualized-transcript.md) for the method and limits.

Recovery summaries and work-state signals, folder-to-folder context migration, and multi-root workflows remain on the [issue tracker](https://github.com/echohello-dev/pidex/issues).
Automatic repository/worktree grouping, group-wide history, recovery summaries, and folder-to-folder context transfer remain on the [issue tracker](https://github.com/echohello-dev/pidex/issues).

## Install

Expand Down
6 changes: 6 additions & 0 deletions docs/adr/0014-workspace-root-groups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# ADR 0014 - Group roots without changing session execution scope

- **Status**: Proposed (local implementation, 2026-11-07)
- **Context**: A workspace used to combine a folder path, Pi archive directory, settings key and navigation identity. That makes related repositories hard to keep together and can confuse a browsed folder with a session's actual execution directory. Pi 1.5.0 opens an archive in its stored header cwd, even when its process starts elsewhere. An umbrella parent folder cannot represent unrelated paths, and task-driven worktree orchestration adds lifecycle beyond the first useful slice of #5.
- **Decision**: Store local UUID root and group records in an atomic, versioned user-data registry. A group is an ordered collection of existing roots; a root can belong to multiple groups. Discover history using each archive header cwd rather than its storage directory. Keep model/reasoning defaults on roots. Validate requested root against archive header before resume/fork and recheck live ownership before prompts/model changes. Keep #33's exact-entry extraction and live archive-owner safeguards. Navigation never changes cwd or terminates a child; delayed opens become background tabs and unsent drafts remain at their original root. Persist group/root selection, but keep open children, views and drafts in memory.
- **Consequences**: Legacy workspace/default files are retained with backups; missing roots and their history stay visible. Invalid registry contents require recovery instead of silently resetting data. Root-scoped file previews retain containment; Pi tool execution still follows Pi/OS policy and is not sandboxed by group membership. Package-root commits are refused if the staged index includes changes outside the folder. Repository/worktree hierarchy, group-wide history, durable archive handles and cross-root context transfer remain later work. Relocating archives requires a separate #4 operation; a registry group does not move execution context. Filesystem replacement at an unchanged path cannot be reliably identified from path strings alone; automatic repair is outside this slice.
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ Each ADR follows this shape:
| [0011](0011-virtualized-transcript.md) | Virtualize the session transcript | Accepted |
| [0012](0012-release-please-calver.md) | Release Please with a CalVer adapter | Accepted |
| [0013](0013-session-branch-selection.md) | Explicit archive branch selection over Pi RPC | Proposed |
| [0014](0014-workspace-root-groups.md) | Group roots without changing session execution scope | Proposed |
12 changes: 8 additions & 4 deletions docs/pidex-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The renderer has context isolation enabled and Node integration disabled. Filesy

| File | Responsibility |
|---|---|
| `src/main/workspaces.ts` | Versioned root/group registry, legacy migration, root defaults and selection |
| `src/main/index.ts` | Windows, workspace registration, active session lifecycle, prompt/abort/close IPC |
| `src/main/rpc.ts` | Pi executable discovery, child processes, request IDs, response timeouts, event forwarding |
| `src/main/sessions.ts` | Discover workspaces and read session metadata from Pi JSONL archives |
Expand All @@ -40,7 +41,9 @@ Shared schemas and a separate runtime-adapter package were early proposals; the

## Session lifecycle

Opening or resuming a session starts a Pi subprocess in the workspace directory. Resume supplies a validated session file. Fork asks Pi to clone the selected archive before returning the new session state. New sessions apply the workspace's saved model and reasoning defaults.
New sessions start a Pi subprocess in the selected root and apply its saved model/reasoning defaults. Resume validates both the archive path and its header cwd against that root. Pi uses the archive's stored cwd, so browsing another root cannot relocate a session. Forks extract #33's selected chain before Pi starts, preserve the source and verify the loaded runtime leaf. Main serializes opens and reuses verified archive owners rather than launching competing writers. Before a prompt or model change, main refreshes runtime state and refuses a changed or unavailable execution root.

Groups organize roots for navigation. Switching group/root leaves processes running and restores the target root's view and drafts. A launch finishing after navigation becomes a background tab; its original unsent prompt is not submitted elsewhere. Open sessions are reachable across groups. Late root requests cannot replace another root's files/defaults/history. No simultaneous multi-cwd execution or sibling instruction loading is added.

Main requests `get_state`, `get_messages`, and available commands. The renderer converts the returned messages into user, assistant, and tool entries. Subsequent Pi events arrive through `pidex:session:event`; `timeline.ts` applies text/thinking deltas and tool start/update/end events. Main forwards the RPC events rather than translating them into a second event protocol.

Expand All @@ -54,24 +57,25 @@ The authoritative channel definitions are in `src/preload/index.ts`. Important g

| Bridge methods | Main channels | Purpose |
|---|---|---|
| `workspaceCatalog`, `saveWorkspaceGroup`, `selectWorkspace` | `pidex:workspace:catalog`, `:group`, `:select` | Read groups/roots, edit membership and persist selection |
| `workspaces`, `addWorkspace`, `sessions` | `pidex:workspaces`, `pidex:workspace:add`, `pidex:sessions` | Discover and select local work |
| `openSession`, `sendPrompt`, `abortSession`, `closeSession` | `pidex:session/open`, `/send`, `/abort`, `/close` | Drive active Pi sessions |
| `previewSession` | `pidex:session:preview` | Read archive messages without starting Pi |
| `inspectWorkspace`, `readFile`, `filePatch`, `gitAction` | `pidex:workspace:inspect`, `:read`, `:patch`, `:git` | Inspect local files and manage changes |
| `models`, `setModel`, `settings`, `saveSettings` | `pidex:models:list`, `pidex:session:model`, `pidex:settings:get`, `:save` | Model discovery and per-workspace defaults |
| `onSessionEvent`, `onSessionExit` | `pidex:session:event`, `pidex:session:exit` | Subscribe to runtime events; each subscription returns cleanup |

Workspace helpers validate known workspaces and file containment. Realpath checks reject previews outside the workspace, including symlinks that escape it. Text previews are limited to 1 MB and reject binary files. Git commands use argument arrays, literal pathspecs where needed, and disable external diff/textconv helpers. Session previews validate the Pi archive root and JSONL extension and reject archives larger than 16 MB; resuming uses Pi directly.
Workspace helpers validate known workspaces and file containment. Realpath checks reject previews outside the workspace, including symlinks that escape it. Text previews are limited to 1 MB and reject binary files. Git commands use argument arrays, literal pathspecs where needed, and disable external diff/textconv helpers. Session previews validate the Pi archive root and JSONL extension and reject archives larger than 16 MB; continuation preserves the validated selected archive chain. A nested folder can stage only its own files; a commit is blocked when the shared index includes staged sibling files.

## Local state

Pi session archives live under `~/.pi/agent/sessions`. pidex discovers workspace paths from session headers and merges manually added folders. Custom workspace paths and per-workspace session defaults live in Electron's user-data directory as `workspaces.json` and `session-defaults.json`.
Pi session archives live under `~/.pi/agent/sessions`. pidex discovers workspace paths from session headers and merges manually added folders. Stable UUID roots, named groups, per-root defaults and selected group/root live in Electron userData as `workspace-registry.json`. The first load migrates `workspaces.json` and `session-defaults.json`, retaining originals and `.bak` copies. Writes use a temporary file and atomic rename; invalid/unknown schema data fails closed. Synchronous main-process transactions serialize mutations, and group edits compare prior membership before saving. Available aliases deduplicate by realpath; missing paths remain as roots and preserve history. Independent nested folders remain separate roots. Initially missing paths are canonicalized when they become available, retaining their root IDs and original path aliases. Resolved paths stay pinned; retargeting one does not relocate its sessions. Each archive is indexed by its own cwd across all supported store subdirectories, with stat-based metadata caching. Native session ID and canonical archive locator remain separate; durable pidex archive handles are deferred.

History preview follows the archive's current parent chain so abandoned branches are not mixed into the displayed conversation. Archive metadata scans read a bounded prefix, so displayed message counts and model metadata can be incomplete for large sessions. The full active branch is read separately for preview.

Workspace inspection reads the file tree and Git branch/status. It refreshes on focus, view/runtime-state changes, and every 15 seconds. There is no filesystem watcher. File discovery skips common dependency/build directories and symlinks and is capped at 3,000 files and seven directory levels.

Drafts, open tabs, transcript selection, and expanded rows are in-memory UI state. Scroll position and expansion are not persisted when a transcript is reopened.
Per-root views, new-session/history drafts, unsaved settings drafts, open tabs, transcript selection, and expanded rows are in-memory UI state. Restoring an interrupted history load retries the saved archive/entry, while completed error states require explicit retry. Settings cache only dirty drafts; clean views follow saved defaults. Changes views remount per root and late mutation results are checked against their original navigation generation. Scroll position and expansion are not persisted when a transcript is reopened.

## Transcript rendering

Expand Down
Loading
Loading