* feat(backend): add support for fetching file content at git ref
Add GetFileContentAtRef method to WorkspaceTracker for retrieving file content
at specific git refs (branches, commits, HEAD). This enables diff expansion by
fetching old file versions without checking out.
Changes:
- Extract maxFileSize constant to package level (10MB)
- Add GetFileContentAtRef to workspace_files.go using git show
- Add workspace.file.get_at_ref WebSocket action and handler
- Add agentctl client method for the new endpoint
- Register handler in workspace file handlers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(web): add diff expansion support for code review
Add lazy-loaded diff expansion to show more context in code reviews. When expansion
is enabled, users can expand hunks to see more surrounding code by clicking expand
buttons. Full file content is fetched on-demand via WebSocket.
Changes:
- Add useExpandableDiff hook for lazy-loading old/new file content
- Add requestFileContentAtRef to workspace-files.ts
- Integrate expansion into diff viewer components
- Add enableExpansion and baseRef props to DiffViewer
- Auto-load expansion content when enabled
- Pass expansion state through diff viewer hierarchy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(web): gracefully handle expansion props for Monaco diff viewer
Monaco diff viewer has built-in expansion that doesn't require fetching
old file content. Strip pierre-diffs-only expansion props (enableExpansion,
baseRef) when Monaco is selected to avoid passing unsupported props.
This ensures the feature works correctly regardless of which diff viewer
the user selects in settings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(backend): add diff expansion e2e scenario and fix unstaged diff base ref
- Add scenarioDiffExpansionSetup mock-agent scenario that creates a
200-line file, commits it, then modifies lines 50 and 150 to produce
a two-hunk uncommitted diff for testing diff expansion in the UI
- Add workspace_file_handlers for workspace.file.get_at_ref WS action
- Fix enrichWithDiffData to always diff against HEAD for unstaged/staged
content so locally committed files show correct diffs
- Add unit tests for GetFileContentAtRef (6 sub-tests)
* feat(web): add diff expansion UI and e2e test
- Wire useExpandableDiff hook into FileDiffViewer and ReviewDiffList
with enableExpansion and baseRef props
- Gracefully handle expansion props for Monaco diff viewer fallback
- Add e2e Playwright test for diff expansion between hunks
* fix(web): fix diff expansion by correcting hunk separators and line splitting
Three bugs prevented @pierre/diffs expand buttons from rendering:
- hunkSeparators was "simple" (no expand UI); changed to "line-info" when expansion enabled
- expandUnchanged:true showed all lines expanded (no separators); removed the option
- .split("\n") stripped trailing newlines; now uses SPLIT_WITH_NEWLINES regex to match library
* feat(web): add editor provider settings UI
Add an "Editor Providers" section to the Editors settings page with
Select dropdowns for each configurable context (Code Editor, Diff Viewer,
Chat Code Blocks, Chat Diff Blocks). Reads from and writes to the
existing localStorage-backed editor resolver store. Changes apply
immediately without requiring a save action.
* test(web): add e2e tests for editor provider settings
Verify that the editor provider dropdowns render with correct defaults,
update on selection, and persist changes across page reloads via
localStorage.
* feat(web): add expand/collapse unchanged regions toggle to diff toolbar
Refactor diff header toolbar into composable ToolbarButtons component and
add a toggle button to expand or collapse unchanged regions in both
Pierre Diffs and Monaco diff viewers. Pass enableExpansion through to
Monaco to control hideUnchangedRegions.
* fix(web): handle deleted files in diff expansion
Treat "file not found" / "no such file" errors as empty content instead
of throwing, so diff expansion works correctly for deleted files.
* chore(web): add diff expansion debug e2e helpers
* test(web): split diff expansion tests by provider (Pierre Diffs + Monaco)
Restructure diff-expansion e2e tests into two describe blocks, one per
diff viewer provider. Each block sets the provider via localStorage
before navigation and verifies the correct viewer renders:
- Pierre Diffs: checks for diffs-container element and shadow DOM
expand buttons between hunks
- Monaco: checks for .monaco-diff-viewer wrapper and the built-in
hideUnchangedRegions widget with "Show Unchanged Region" link
Both suites verify that clicking expand reveals collapsed middle lines.
* fix(web): fix Pierre Diffs expand button selectors in e2e tests
Use shadow DOM data attributes ([data-expand-button], [data-expand-up])
instead of XPath parent traversal which doesn't work inside shadow DOM.
Wait for expand buttons to load asynchronously before clicking.
* fix(e2e): make diff-expansion-setup scenario idempotent
The mock-agent scenario now removes and re-commits the test file before
each run, so subsequent tests in the same repo don't fail with "git
commit failed" when the file already exists. Also increase test timeouts
to match other heavy e2e tests.
* refactor(web): remove editor provider settings UI, enable expand-all in review
Remove the editor provider switching UI and its tests — Monaco's diff
expansion doesn't work and exposing provider switching adds confusion.
Show Pierre Diffs header toolbar in the review panel (remove hideHeader)
so the expand-all button is accessible. Add e2e test for expand-all.
* style(web): fix prettier formatting and lint warnings
Run prettier on files with formatting drift. Extract ToolbarBtn helper
in diff-header-toolbar to stay within max-lines-per-function limit.
Consolidate useMonacoDiffViewerState to reduce line count.
* chore(web): remove debug scaffolding, revert Monaco changes, drop stray console.log
Revert Monaco diff-viewer-toolbar and monaco-diff-viewer to main
(expansion not functional with Monaco). Remove debug-expansion
screenshot scripts and spec. Strip console.log from commit-detail-panel.
Strip enableExpansion from Monaco path in diff-viewer-resolver.
* fix(web): use commit-relative base ref for diff expansion
Use `${commitSha}^` instead of hardcoded `HEAD~1` so expansion fetches
the correct old content when viewing non-HEAD commits.
* fix(web,backend): address CodeRabbit review feedback
- Add aria-label to icon-only toolbar buttons for screen reader access
- Guard auto-load expansion effect against error state to prevent retry
loops when WebSocket fetch fails
- Invalidate expansion cache on file/ref change with request versioning
to prevent stale content
- Preflight blob size with git cat-file -s before git show to avoid
memory spikes on large files
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: session resumption with ACP session ID support
- Add useSessionResumption hook for automatic session recovery on page reload
- Fix ACPSessionID not being set on AgentInstance in Launch() and createInstance()
- Add task.session.status and task.session.resume WebSocket actions
- Fix shell terminal retry logic for resumed sessions
- Fix taskSessionState reading from Zustand store instead of local state
- Remove dead code: unused stream handling functions in lifecycle/manager.go
- Fix test mock interfaces (CleanupStaleInstanceByTaskID, NewManager signature)
- Add enhanced logging for session resumption debugging
- Simplify page-client.tsx state merging logic
* refactor: remove unused AgentController (dead code)
- Delete agent_controller.go - entirely unused duplicate of Manager methods
- Remove agentController field from Manager struct
- Remove AgentController initialization in NewManager
The AgentController duplicated StartAgentProcess, initializeACPSession, and
PromptAgent from Manager but was never called. All agent control operations
go through Manager's own methods.
* refactor: remove unused MemoryRepository (dead code)
- Delete memory.go - entire MemoryRepository implementation unused
(replaced by SQLite repository)
- Delete memory_test.go - tests for removed MemoryRepository
Found via 'deadcode' tool analysis.
* refactor: consolidate agentctl packages into server/client structure
Reorganize the confusing dual agentctl packages:
- internal/agentctl/* → internal/agentctl/server/
- internal/agent/agentctl/* → internal/agentctl/client/
Structure is now:
internal/agentctl/
├── client/ - HTTP client for talking to agentctl servers
│ ├── client.go
│ ├── standalone.go
│ ├── acp.go
│ └── launcher/
├── server/ - agentctl server implementation (runs in containers)
│ ├── api/
│ ├── adapter/
│ ├── acp/
│ ├── config/
│ ├── instance/
│ ├── process/
│ └── shell/
└── types/ - Shared types used by both client and server
Also consolidated shared types (GitStatusUpdate, FileInfo, etc.) from
process/types.go into the shared types/ package.
* refactor: rename AgentInstance to AgentExecution for clarity
Rename AgentInstance -> AgentExecution across the entire codebase to better
reflect what it represents: an agent actively working on a task, not just
a generic 'instance'.
Changes:
- AgentInstance struct -> AgentExecution
- InstanceStore -> ExecutionStore (new file: execution_store.go)
- RecoveredInstance -> RecoveredExecution
- All method names: GetInstance* -> GetExecution*, ListInstances -> ListExecutions, etc.
- All parameters: instanceID -> executionID
- All local variables: instance -> execution
- DTOs and JSON tags: agent_instance_id -> agent_execution_id
- Database columns: agent_instance_id -> agent_execution_id
Also consolidates duplicate prompt logic:
- Merge sendInitialPrompt and SendPrompt into single SendPrompt method
- Add validateStatus parameter to differentiate initial vs follow-up prompts
Also extracts ACP session management to session.go:
- Move initializeACPSession logic to SessionManager.InitializeAndPrompt
- Move PromptAgent logic to SessionManager.SendPrompt
- Manager now delegates to SessionManager for all prompt operations
Also removes duplicate event publishing:
- Remove 9 duplicate m.publish*() methods from manager.go
- Use existing EventPublisher methods via m.eventPublisher.Publish*()
Also extracts types to dedicated file:
- Move AgentExecution, LaunchRequest, PromptResult, etc. to types.go
Net reduction: ~600 lines removed from manager.go through deduplication
and better separation of concerns.
* fix(backend): Docker runtime now creates instance via ControlServer
The Docker runtime was incorrectly creating an agentctl.Client pointing
directly to port 9999 (the ControlServer port), then trying to call
instance-specific APIs like /api/v1/agent/configure which only exist
on per-instance servers.
Fixed by making Docker runtime follow the same flow as standalone mode:
- LaunchContainer now uses StandaloneCtl to create an instance via the
control API, then creates Client pointing to the instance port
- RecoverInstances queries the ControlServer to get the instance port
- Added waitForHealth helper to wait for agentctl to be ready
Also improved HTTP error handling in agentctl client:
- All client methods now check HTTP status codes before JSON decoding
- Better error messages include status code and response body
* fix: restore session resumption imports and variables
* fix: add sessionId prop back to TaskLayout
* feat(web): add file browser with syntax-highlighted viewer
- Add file browser component in right panel to browse workspace files
- Open files in left panel tabs alongside Notes/Changes/Chat
- Color-coded dots for file types (TS, JS, Go, Python, etc.)
- Visual separator between main tabs and file tabs
- Folders sorted to top in file tree view
- LRU eviction for max 4 open file tabs
- Backend handlers for workspace file tree and content via WebSocket
* fix(web): refresh file tree on filesystem changes via fsnotify
- Subscribe to file change notifications from agentctl's fsnotify watcher
- Backend lifecycle manager now connects to file changes stream
- Publishes FileChangeNotified events to event bus
- WebSocket broadcasts workspace.file.changes to task subscribers
- Frontend file browser refreshes tree when changes detected
* perf(agentctl): use fsnotify instead of polling for git status
- Replace 2-second polling with fsnotify-triggered updates
- Add debouncing (300ms) to batch rapid file changes
- Git status updates now triggered by actual filesystem changes
- More efficient and responsive than periodic polling
* refactor(agentctl): consolidate fs events into single debounced trigger
- Single fsChangeTrigger channel for all filesystem events
- After 300ms debounce: update git status, files, and notify subscribers
- Removed per-file notifications in favor of single 'refresh' event
- Simpler and more efficient than separate triggers
* refactor(agentctl): simplify file API - use HTTP for tree/content
- Remove unused filesConn, fileTreeConn, fileContentConn from Client struct
- Convert RequestFileTree and RequestFileContent from WebSocket to HTTP GET
- Remove StreamFiles and CloseFilesStream (redundant with file changes stream)
- Keep only fileChangesConn for the debounced refresh notification
HTTP is simpler than WebSocket for request/response patterns.
* feat(web): switch to CodeMirror for file viewer
- Replace react-syntax-highlighter with @uiw/react-codemirror
- Add language extensions for JS/TS, Python, Go, Rust, Java, C++, CSS, HTML, JSON, Markdown, YAML
- Use VS Code dark theme for consistent look
- Enable code folding and read-only mode
- Better performance and features than syntax highlighter
* refactor: consolidate duplicated types and extract shared constants
- Re-export file types from process package in agentctl/client.go instead of duplicating
- Move OpenFileTab type to lib/types/backend.ts (shared by file-browser and page-client)
- Extract FILE_EXTENSION_COLORS constant for file type color indicators
- Remove console.log statement and unused imports in file-browser.tsx
- Add 'refresh' operation to FileChangeNotificationPayload type
* refactor(agentctl): move Close() method from acp.go to client.go
Close() is a general client cleanup method that closes all streams
(ACP, permissions, git status, file changes). It belongs in client.go
since acp.go should only contain ACP-specific communication logic.
* fix(web): remove unused cn import in file-browser
* refactor(agentctl): consolidate duplicated types into process/types.go
- Create process/types.go with all workspace-related types
- Remove duplicate GitStatusUpdate, FileInfo from workspace_tracker.go
- Remove duplicate GitStatusUpdate, FileInfo from api/workspace.go
- Re-export all types from agent/agentctl/client.go for convenience
Types are now defined once and imported everywhere.
* chore: remove package-lock.json, add to .gitignore
Project uses pnpm (pnpm-lock.yaml), not npm.
* fix(orchestrator): probe agent before returning stale execution state
When checking task.execution, the backend now probes the actual agent:
- For tracked instances: health check via agentctl
- For Docker: verify container is running
If the agent is not running but DB says 'running', the session is
marked as stopped and false is returned. This fixes the UI showing
agents as running after backend restart when they're actually dead.
Changes:
- Add IsAgentRunningForTask to AgentManagerClient interface
- Add IsContainerRunning to docker client
- Update GetExecution to verify agent is actually running
- Clean up stale sessions in database when agent not found
* fix(orchestrator): use agentctl status to verify agent is running
Simplified approach - probe agentctl's /api/v1/status endpoint to check
if agent process is running. Same check for both Docker and standalone.
- Add IsAgentRunning() helper to StatusResponse
- Simplify IsAgentRunningForTask to just use agentctl.GetStatus()
* fix(orchestrator): also verify agent running for in-memory executions
The previous fix only checked agent status when loading from DB. But if
an execution is cached in memory and the agent process stops (while
agentctl stays running), the stale state was still returned.
Now both paths verify the agent is actually running by probing agentctl.
If agent stopped, clean up in-memory state and update DB session.
* chore(web): remove unused react-syntax-highlighter
Switched to CodeMirror for code viewing, no longer needed.
* fix(tests): add IsAgentRunningForTask to mock agent managers
Add the new interface method to test mocks:
- mockAgentManager in scheduler_test.go
- SimulatedAgentManagerClient in orchestrator_test.go
---------
Co-authored-by: Kandev Agent <kandev@example.com>
- Add Docker reconciliation on lifecycle manager startup to find running containers
- Add container_id field to agent_sessions table for cleanup tracking
- Add GetRecoveredInstances to AgentManagerClient interface
- Sync executor cache with recovered Docker instances on startup
- Add task.execution WebSocket endpoint to check agent running state
- Fix Stop Agent button showing Start Agent after page refresh
- Ensure containers are not leaked when backend restarts