diff --git a/docs/releases.md b/docs/releases.md index d053eb9477..81e0e7f113 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -17,60 +17,60 @@ Maestro can update itself automatically! This feature was introduced in **v0.8.7 **Latest: v0.15.2** | Released March 12, 2026 -# Major 0.15.x Additions - -๐ŸŽถ **Maestro Symphony** โ€” Contribute to open source with AI assistance! Browse curated issues from projects with the `runmaestro.ai` label, clone repos with one click, and automatically process the relevant Auto Run playbooks. Track your contributions, streaks, and stats. You're contributing CPU and tokens towards your favorite open source projects and features. - -๐ŸŽฌ **Director's Notes** โ€” Aggregates history across all agents into a unified timeline with search, filters, and an activity graph. Includes an AI Overview tab that generates a structured synopsis of recent work. Off by default, gated behind a new "Encore Features" panel under settings. This is a precursor to an eventual plugin system, allowing for extensions and customizations without bloating the core app. - -๐Ÿท๏ธ **Conductor Profile** โ€” Available under Settings > General. Provide a short description on how Maestro agents should interface with you. - -๐Ÿง  **Three-State Thinking Toggle** โ€” The thinking toggle now cycles through three modes: off, on, and sticky. Sticky mode keeps thinking content visible after the response completes. Cycle with CMD/CTRL+SHIFT+K. - -๐Ÿค– **Factory.ai Droid Support** โ€” Added support for the [Factory.ai](https://factory.ai/product/cli) droid agent. Full session management and output parsing integration. - -## Change in v0.15.2 - -Patch release with bug fixes, UX improvements, and cherry-picks from the 0.16.0 RC. - -### New Features - -- **Cmd+0 โ†’ Last Tab:** Remapped Cmd+0 to jump to last tab; Cmd+Shift+0 now resets font size -- **Unsent draft protection:** Confirm dialog before closing tabs with unsent draft input -- **Read-only CLI flag:** Added `--read-only` flag to `maestro-cli send` command -- **Gemini read-only enforcement:** Gemini `-y` flag now works in read-only mode -- **Capability-based providers:** Replaced hardcoded agent ID checks with capability flags and shared metadata - -### Bug Fixes - -- **Sticky overlay scroll:** Fixed sticky overlays breaking tab scroll-into-view -- **Director's Notes stats:** Count only agents with entries in lookback window -- **SSH remote config:** Check `sessionSshRemoteConfig` as primary SSH remote ID source -- **.maestro file tree:** Always show .maestro directory even when dotfiles are hidden -- **Provider hardening:** Prototype safety, capability gates, stale map cleanup -- **Session search:** Per-session error resilience and metadata-based title matching -- **File tree stale loads:** Load sequence counter prevents stale file tree updates -- **File tree Unicode:** NFC normalization prevents duplicate entries -- **File tree duplicates:** Tree-structured data resolves duplicate entries -- **File tree auto-refresh:** Timer no longer destroyed on right panel tab switch -- **Menu z-index:** Branding header menu renders above sidebar content -- **Dropdown clipping:** Fixed hamburger menu and live overlay dropdown clipping -- **Font size shortcuts:** Restored Cmd+/- font size shortcuts lost with custom menu -- **Draft input preservation:** Replaying a previous message no longer discards current draft -- **SSH directory collision:** Skip warning when agents are on different SSH hosts -- **IPC error handling:** Handle expected IPC errors gracefully -- **Auto-focus on mode switch:** Input field auto-focuses when toggling AI/Shell mode -- **OpenCode parser:** Preserve JSON error events; reset resultEmitted on step_start -- **NDJSON performance:** Eliminated triple JSON parsing on hot path -- **Agent config overrides:** Apply config overrides in context groomer before spawning -- **Stale closure fix:** Resolved model not saving in wizard agent config - -### Visual Polish - -- **Light theme contrast:** Improved syntax highlighting contrast across all light themes -- **Context warning sash:** Dark text colors in light mode for readability -- **Session name dimming:** Use `textMain` color to prevent visual dimming -- **Session name pill:** Allow shrinking so date doesn't collide with type pill +# Major 0.15.x Additions + +๐ŸŽถ **Maestro Symphony** โ€” Contribute to open source with AI assistance! Browse curated issues from projects with the `runmaestro.ai` label, clone repos with one click, and automatically process the relevant Auto Run playbooks. Track your contributions, streaks, and stats. You're contributing CPU and tokens towards your favorite open source projects and features. + +๐ŸŽฌ **Director's Notes** โ€” Aggregates history across all agents into a unified timeline with search, filters, and an activity graph. Includes an AI Overview tab that generates a structured synopsis of recent work. Off by default, gated behind a new "Encore Features" panel under settings. This is a precursor to an eventual plugin system, allowing for extensions and customizations without bloating the core app. + +๐Ÿท๏ธ **Conductor Profile** โ€” Available under Settings > General. Provide a short description on how Maestro agents should interface with you. + +๐Ÿง  **Three-State Thinking Toggle** โ€” The thinking toggle now cycles through three modes: off, on, and sticky. Sticky mode keeps thinking content visible after the response completes. Cycle with CMD/CTRL+SHIFT+K. + +๐Ÿค– **Factory.ai Droid Support** โ€” Added support for the [Factory.ai](https://factory.ai/product/cli) droid agent. Full session management and output parsing integration. + +## Change in v0.15.2 + +Patch release with bug fixes, UX improvements, and cherry-picks from the 0.16.0 RC. + +### New Features + +- **Cmd+0 โ†’ Last Tab:** Remapped Cmd+0 to jump to last tab; Cmd+Shift+0 now resets font size +- **Unsent draft protection:** Confirm dialog before closing tabs with unsent draft input +- **Read-only CLI flag:** Added `--read-only` flag to `maestro-cli send` command +- **Gemini read-only enforcement:** Gemini `-y` flag now works in read-only mode +- **Capability-based providers:** Replaced hardcoded agent ID checks with capability flags and shared metadata + +### Bug Fixes + +- **Sticky overlay scroll:** Fixed sticky overlays breaking tab scroll-into-view +- **Director's Notes stats:** Count only agents with entries in lookback window +- **SSH remote config:** Check `sessionSshRemoteConfig` as primary SSH remote ID source +- **.maestro file tree:** Always show .maestro directory even when dotfiles are hidden +- **Provider hardening:** Prototype safety, capability gates, stale map cleanup +- **Session search:** Per-session error resilience and metadata-based title matching +- **File tree stale loads:** Load sequence counter prevents stale file tree updates +- **File tree Unicode:** NFC normalization prevents duplicate entries +- **File tree duplicates:** Tree-structured data resolves duplicate entries +- **File tree auto-refresh:** Timer no longer destroyed on right panel tab switch +- **Menu z-index:** Branding header menu renders above sidebar content +- **Dropdown clipping:** Fixed hamburger menu and live overlay dropdown clipping +- **Font size shortcuts:** Restored Cmd+/- font size shortcuts lost with custom menu +- **Draft input preservation:** Replaying a previous message no longer discards current draft +- **SSH directory collision:** Skip warning when agents are on different SSH hosts +- **IPC error handling:** Handle expected IPC errors gracefully +- **Auto-focus on mode switch:** Input field auto-focuses when toggling AI/Shell mode +- **OpenCode parser:** Preserve JSON error events; reset resultEmitted on step_start +- **NDJSON performance:** Eliminated triple JSON parsing on hot path +- **Agent config overrides:** Apply config overrides in context groomer before spawning +- **Stale closure fix:** Resolved model not saving in wizard agent config + +### Visual Polish + +- **Light theme contrast:** Improved syntax highlighting contrast across all light themes +- **Context warning sash:** Dark text colors in light mode for readability +- **Session name dimming:** Use `textMain` color to prevent visual dimming +- **Session name pill:** Allow shrinking so date doesn't collide with type pill - **Scroll-to-bottom arrow:** Removed noisy indicator from terminal output view ### Previous Releases in this Series @@ -83,41 +83,41 @@ Patch release with bug fixes, UX improvements, and cherry-picks from the 0.16.0 **Latest: v0.14.5** | Released January 24, 2026 -Changes in this point release include: - -- Desktop app performance improvements (more to come on this, we want Maestro blazing fast) ๐ŸŒ -- Added local manifest feature for custom playbooks ๐Ÿ“– -- Agents are now inherently aware of your activity history as seen in the history panel ๐Ÿ“œ (this is built-in cross context memory!) -- Added markdown rendering support for AI responses in mobile view ๐Ÿ“ฑ -- Bugfix in tracking costs from JSONL files that were aged out ๐Ÿฆ -- Added BlueSky social media handle for leaderboard ๐Ÿฆ‹ -- Added options to disable GPU rendering and confetti ๐ŸŽŠ -- Better handling of large files in preview ๐Ÿ—„๏ธ -- Bug fix in Claude context calculation ๐Ÿงฎ -- Addressed bug in OpenSpec version reporting ๐Ÿ› - -The major contributions to 0.14.x remain: - -๐Ÿ—„๏ธ Document Graphs. Launch from file preview or from the FIle tree panel. Explore relationships between Markdown documents that contain links between documents and to URLs. - -๐Ÿ“ถ SSH support for agents. Manage a remote agent with feature parity over SSH. Includes support for Git and File tree panels. Manage agents on remote systems or in containers. This even works for Group Chat, which is rad as hell. - -๐Ÿง™โ€โ™‚๏ธ Added an in-tab wizard for generating Auto Run Playbooks via `/wizard` or a new button in the Auto Run panel. - -# Smaller Changes in 014.x - -- Improved User Dashboard, available from hamburger menu, command palette or hotkey ๐ŸŽ›๏ธ -- Leaderboard tracking now works across multiple systems and syncs level from cloud ๐Ÿ† -- Agent duplication. Pro tip: Consider a group of unused "Template" agents โœŒ๏ธ -- New setting to prevent system from going to sleep while agents are active ๐Ÿ›๏ธ -- The tab menu has a new "Publish as GitHub Gist" option ๐Ÿ“ -- The tab menu has options to move the tab to the first or last position ๐Ÿ”€ -- [Maestro-Playbooks](https://github.com/pedramamini/Maestro-Playbooks) can now contain non-markdown assets ๐Ÿ“™ -- Improved default shell detection ๐Ÿš -- Added logic to prevent overlapping TTS notifications ๐Ÿ’ฌ -- Added "Toggle Bookmark" shortcut (CTRL/CMD+SHIFT+B) โŒจ๏ธ -- Gist publishing now shows previous URLs with copy button ๐Ÿ“‹ - +Changes in this point release include: + +- Desktop app performance improvements (more to come on this, we want Maestro blazing fast) ๐ŸŒ +- Added local manifest feature for custom playbooks ๐Ÿ“– +- Agents are now inherently aware of your activity history as seen in the history panel ๐Ÿ“œ (this is built-in cross context memory!) +- Added markdown rendering support for AI responses in mobile view ๐Ÿ“ฑ +- Bugfix in tracking costs from JSONL files that were aged out ๐Ÿฆ +- Added BlueSky social media handle for leaderboard ๐Ÿฆ‹ +- Added options to disable GPU rendering and confetti ๐ŸŽŠ +- Better handling of large files in preview ๐Ÿ—„๏ธ +- Bug fix in Claude context calculation ๐Ÿงฎ +- Addressed bug in OpenSpec version reporting ๐Ÿ› + +The major contributions to 0.14.x remain: + +๐Ÿ—„๏ธ Document Graphs. Launch from file preview or from the FIle tree panel. Explore relationships between Markdown documents that contain links between documents and to URLs. + +๐Ÿ“ถ SSH support for agents. Manage a remote agent with feature parity over SSH. Includes support for Git and File tree panels. Manage agents on remote systems or in containers. This even works for Group Chat, which is rad as hell. + +๐Ÿง™โ€โ™‚๏ธ Added an in-tab wizard for generating Auto Run Playbooks via `/wizard` or a new button in the Auto Run panel. + +# Smaller Changes in 014.x + +- Improved User Dashboard, available from hamburger menu, command palette or hotkey ๐ŸŽ›๏ธ +- Leaderboard tracking now works across multiple systems and syncs level from cloud ๐Ÿ† +- Agent duplication. Pro tip: Consider a group of unused "Template" agents โœŒ๏ธ +- New setting to prevent system from going to sleep while agents are active ๐Ÿ›๏ธ +- The tab menu has a new "Publish as GitHub Gist" option ๐Ÿ“ +- The tab menu has options to move the tab to the first or last position ๐Ÿ”€ +- [Maestro-Playbooks](https://github.com/pedramamini/Maestro-Playbooks) can now contain non-markdown assets ๐Ÿ“™ +- Improved default shell detection ๐Ÿš +- Added logic to prevent overlapping TTS notifications ๐Ÿ’ฌ +- Added "Toggle Bookmark" shortcut (CTRL/CMD+SHIFT+B) โŒจ๏ธ +- Gist publishing now shows previous URLs with copy button ๐Ÿ“‹ + Thanks for the contributions: @t1mmen @aejfager @Crumbgrabber @whglaser @b3nw @deandebeer @shadown @breki @charles-dyfis-net @ronaldeddings @jlengrand @ksylvan ### Previous Releases in this Series @@ -136,20 +136,22 @@ Thanks for the contributions: @t1mmen @aejfager @Crumbgrabber @whglaser @b3nw @d ### Changes -- TAKE TWO! Fixed Linux ARM64 build architecture contamination issues ๐Ÿ—๏ธ - -### v0.13.1 Changes -- Fixed Linux ARM64 build architecture contamination issues ๐Ÿ—๏ธ -- Enhanced error handling for Auto Run batch processing ๐Ÿšจ - -### v0.13.0 Changes -- Added a global usage dashboard, data collection begins with this install ๐ŸŽ›๏ธ -- Added a Playbook Exchange for downloading pre-defined Auto Run playbooks from [Maestro-Playbooks](https://github.com/pedramamini/Maestro-Playbooks) ๐Ÿ“• -- Bundled OpenSpec commands for structured change proposals ๐Ÿ“ -- Added pre-release channel support for beta/RC updates ๐Ÿงช -- Implemented global hands-on time tracking across sessions โฑ๏ธ -- Added new keyboard shortcut for agent settings (Opt+Cmd+, | Ctrl+Alt+,) โŒจ๏ธ -- Added directory size calculation with file/folder counts in file explorer ๐Ÿ“Š +- TAKE TWO! Fixed Linux ARM64 build architecture contamination issues ๐Ÿ—๏ธ + +### v0.13.1 Changes + +- Fixed Linux ARM64 build architecture contamination issues ๐Ÿ—๏ธ +- Enhanced error handling for Auto Run batch processing ๐Ÿšจ + +### v0.13.0 Changes + +- Added a global usage dashboard, data collection begins with this install ๐ŸŽ›๏ธ +- Added a Playbook Exchange for downloading pre-defined Auto Run playbooks from [Maestro-Playbooks](https://github.com/pedramamini/Maestro-Playbooks) ๐Ÿ“• +- Bundled OpenSpec commands for structured change proposals ๐Ÿ“ +- Added pre-release channel support for beta/RC updates ๐Ÿงช +- Implemented global hands-on time tracking across sessions โฑ๏ธ +- Added new keyboard shortcut for agent settings (Opt+Cmd+, | Ctrl+Alt+,) โŒจ๏ธ +- Added directory size calculation with file/folder counts in file explorer ๐Ÿ“Š - Added sleep detection to exclude laptop sleep from time tracking โฐ ### Previous Releases in this Series @@ -163,22 +165,26 @@ Thanks for the contributions: @t1mmen @aejfager @Crumbgrabber @whglaser @b3nw @d **Latest: v0.12.3** | Released December 28, 2025 -The big changes in the v0.12.x line are the following three: - -## Show Thinking -๐Ÿค” There is now a toggle to show thinking for the agent, the default for new tabs is off, though this can be changed under Settings > General. The toggle shows next to History and Read-Only. Very similar pattern. This has been the #1 most requested feature, though personally, I don't think I'll use it as I prefer to not see the details of the work, but the results of the work. Just as we work with our colleagues. - -## GitHub Spec-Kit Integration -๐ŸŽฏ Added [GitHub Spec-Kit](https://github.com/github/spec-kit) commands into Maestro with a built in updater to grab the latest prompts from the repository. We do override `/speckit-implement` (the final step) to create Auto Run docs and guide the user through their execution, which thanks to Wortrees from v0.11.x allows us to run in parallel! - -## Context Management Tools -๐Ÿ“– Added context management options from tab right-click menu. You can now compress, merge, and transfer contexts between agents. You will received (configurable) warnings at 60% and 80% context consumption with a hint to compact. - -## Changes Specific to v0.12.3: -- We now have hosted documentation through Mintlify ๐Ÿ“š -- Export any tab conversation as self-contained themed HTML file ๐Ÿ“„ -- Publish files as private/public Gists ๐ŸŒ -- Added tab hover overlay menu with close operations and export ๐Ÿ“‹ +The big changes in the v0.12.x line are the following three: + +## Show Thinking + +๐Ÿค” There is now a toggle to show thinking for the agent, the default for new tabs is off, though this can be changed under Settings > General. The toggle shows next to History and Read-Only. Very similar pattern. This has been the #1 most requested feature, though personally, I don't think I'll use it as I prefer to not see the details of the work, but the results of the work. Just as we work with our colleagues. + +## GitHub Spec-Kit Integration + +๐ŸŽฏ Added [GitHub Spec-Kit](https://github.com/github/spec-kit) commands into Maestro with a built in updater to grab the latest prompts from the repository. We do override `/speckit-implement` (the final step) to create Auto Run docs and guide the user through their execution, which thanks to Wortrees from v0.11.x allows us to run in parallel! + +## Context Management Tools + +๐Ÿ“– Added context management options from tab right-click menu. You can now compress, merge, and transfer contexts between agents. You will received (configurable) warnings at 60% and 80% context consumption with a hint to compact. + +## Changes Specific to v0.12.3: + +- We now have hosted documentation through Mintlify ๐Ÿ“š +- Export any tab conversation as self-contained themed HTML file ๐Ÿ“„ +- Publish files as private/public Gists ๐ŸŒ +- Added tab hover overlay menu with close operations and export ๐Ÿ“‹ - Added social handles to achievement share images ๐Ÿ† ### Previous Releases in this Series @@ -192,12 +198,12 @@ The big changes in the v0.12.x line are the following three: **Latest: v0.11.0** | Released December 22, 2025 -๐ŸŒณ Github Worktree support was added. Any agent bound to a Git repository has the option to enable worktrees, each of which show up as a sub-agent with their own write-lock and Auto Run capability. Now you can truly develop in parallel on the same project and issue PRs when you're ready, all from within Maestro. Huge improvement, major thanks to @petersilberman. - -# Other Changes - -- @ file mentions now include documents from your Auto Run folder (which may not live in your agent working directory) ๐Ÿ—„๏ธ -- The wizard is now capable of detecting and continuing on past started projects ๐Ÿง™ +๐ŸŒณ Github Worktree support was added. Any agent bound to a Git repository has the option to enable worktrees, each of which show up as a sub-agent with their own write-lock and Auto Run capability. Now you can truly develop in parallel on the same project and issue PRs when you're ready, all from within Maestro. Huge improvement, major thanks to @petersilberman. + +# Other Changes + +- @ file mentions now include documents from your Auto Run folder (which may not live in your agent working directory) ๐Ÿ—„๏ธ +- The wizard is now capable of detecting and continuing on past started projects ๐Ÿง™ - Bug fixes ๐Ÿ›๐Ÿœ๐Ÿž --- @@ -208,14 +214,14 @@ The big changes in the v0.12.x line are the following three: ### Changes -- Export group chats as self-contained HTML โฌ‡๏ธ -- Enhanced system process viewer now has details view with full process args ๐Ÿ’ป -- Update button hides until platform binaries are available in releases. โณ -- Added Auto Run stall detection at the loop level, if no documents are updated after a loop ๐Ÿ” -- Improved Codex session discovery ๐Ÿ” -- Windows compatibility fixes ๐Ÿ› -- 64-bit Linux ARM build issue fixed (thanks @LilYoopug) ๐Ÿœ -- Addressed session enumeration issues with Codex and OpenCode ๐Ÿž +- Export group chats as self-contained HTML โฌ‡๏ธ +- Enhanced system process viewer now has details view with full process args ๐Ÿ’ป +- Update button hides until platform binaries are available in releases. โณ +- Added Auto Run stall detection at the loop level, if no documents are updated after a loop ๐Ÿ” +- Improved Codex session discovery ๐Ÿ” +- Windows compatibility fixes ๐Ÿ› +- 64-bit Linux ARM build issue fixed (thanks @LilYoopug) ๐Ÿœ +- Addressed session enumeration issues with Codex and OpenCode ๐Ÿž - Addressed pathing issues around gh command (thanks @oliveiraantoniocc) ๐Ÿ ### Previous Releases in this Series @@ -231,13 +237,13 @@ The big changes in the v0.12.x line are the following three: ### Changes -- Add Sentry crashing reporting monitoring with opt-out ๐Ÿ› -- Stability fixes on v0.9.0 along with all the changes it brought along, including... - - Major refactor to enable supporting of multiple providers ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ - - Added OpenAI Codex support ๐Ÿ‘จโ€๐Ÿ’ป - - Added OpenCode support ๐Ÿ‘ฉโ€๐Ÿ’ป - - Error handling system detects and recovers from agent failures ๐Ÿšจ - - Added option to specify CLI arguments to AI providers โœจ +- Add Sentry crashing reporting monitoring with opt-out ๐Ÿ› +- Stability fixes on v0.9.0 along with all the changes it brought along, including... + - Major refactor to enable supporting of multiple providers ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ + - Added OpenAI Codex support ๐Ÿ‘จโ€๐Ÿ’ป + - Added OpenCode support ๐Ÿ‘ฉโ€๐Ÿ’ป + - Error handling system detects and recovers from agent failures ๐Ÿšจ + - Added option to specify CLI arguments to AI providers โœจ - Bunch of other little tweaks and additions ๐Ÿ’Ž ### Previous Releases in this Series @@ -252,19 +258,19 @@ The big changes in the v0.12.x line are the following three: ### Changes -- Added "Nudge" messages. Short static copy to include with every interactive message sent, perhaps to remind the agent on how to work ๐Ÿ“Œ -- Addressed various resource consumption issues to reduce battery cost ๐Ÿ“‰ -- Implemented fuzzy file search in quick actions for instant navigation ๐Ÿ” -- Added "clear" command support to clean terminal shell logs ๐Ÿงน -- Simplified search highlighting by integrating into markdown pipeline โœจ -- Enhanced update checker to filter prerelease tags like -rc, -beta ๐Ÿš€ -- Fixed RPM package compatibility for OpenSUSE Tumbleweed ๐Ÿง (H/T @JOduMonT) -- Added libuuid1 support alongside standard libuuid dependency ๐Ÿ“ฆ -- Introduced Cmd+Shift+U shortcut for tab unread toggle โŒจ๏ธ -- Enhanced keyboard navigation for marking tabs unread ๐ŸŽฏ -- Expanded Linux distribution support with smart dependencies ๐ŸŒ -- Major underlying code re-structuring for maintainability ๐Ÿงน -- Improved stall detection to allow for individual docs to stall out while not affecting the entire playbook ๐Ÿ“– (H/T @mattjay) +- Added "Nudge" messages. Short static copy to include with every interactive message sent, perhaps to remind the agent on how to work ๐Ÿ“Œ +- Addressed various resource consumption issues to reduce battery cost ๐Ÿ“‰ +- Implemented fuzzy file search in quick actions for instant navigation ๐Ÿ” +- Added "clear" command support to clean terminal shell logs ๐Ÿงน +- Simplified search highlighting by integrating into markdown pipeline โœจ +- Enhanced update checker to filter prerelease tags like -rc, -beta ๐Ÿš€ +- Fixed RPM package compatibility for OpenSUSE Tumbleweed ๐Ÿง (H/T @JOduMonT) +- Added libuuid1 support alongside standard libuuid dependency ๐Ÿ“ฆ +- Introduced Cmd+Shift+U shortcut for tab unread toggle โŒจ๏ธ +- Enhanced keyboard navigation for marking tabs unread ๐ŸŽฏ +- Expanded Linux distribution support with smart dependencies ๐ŸŒ +- Major underlying code re-structuring for maintainability ๐Ÿงน +- Improved stall detection to allow for individual docs to stall out while not affecting the entire playbook ๐Ÿ“– (H/T @mattjay) - Added option to select a static listening port for remote control ๐ŸŽฎ (H/T @b3nw) ### Previous Releases in this Series @@ -284,35 +290,40 @@ The big changes in the v0.12.x line are the following three: **Latest: v0.7.4** | Released December 12, 2025 -Minor bugfixes on top of v0.7.3: - -# Onboarding, Wizard, and Tours -- Implemented comprehensive onboarding wizard with integrated tour system ๐Ÿš€ -- Added project-understanding confidence display to wizard UI ๐ŸŽจ -- Enhanced keyboard navigation across all wizard screens โŒจ๏ธ -- Added analytics tracking for wizard and tour completion ๐Ÿ“ˆ -- Added First Run Celebration modal with confetti animation ๐ŸŽ‰ - -# UI / UX Enhancements -- Added expand-to-fullscreen button for Auto Run interface ๐Ÿ–ฅ๏ธ -- Created dedicated modal component and improved modal priority constants for expanded Auto Run view ๐Ÿ“ -- Enhanced user experience with fullscreen editing capabilities โœจ -- Fixed tab name display to correctly show full name for active tabs ๐Ÿท๏ธ -- Added performance optimizations with throttling and caching for scrolling โšก -- Implemented drag-and-drop reordering for execution queue items ๐ŸŽฏ -- Enhanced toast context with agent name for OS notifications ๐Ÿ“ข - -# Auto Run Workflow Improvements -- Created phase document generation for Auto Run workflow ๐Ÿ“„ -- Added real-time log streaming to the LogViewer component ๐Ÿ“Š - -# Application Behavior / Core Fixes -- Added validation to prevent nested worktrees inside the main repository ๐Ÿšซ -- Fixed process manager to properly emit exit events on errors ๐Ÿ”ง -- Fixed process exit handling to ensure proper cleanup ๐Ÿงน - -# Update System -- Implemented automatic update checking on application startup ๐Ÿš€ +Minor bugfixes on top of v0.7.3: + +# Onboarding, Wizard, and Tours + +- Implemented comprehensive onboarding wizard with integrated tour system ๐Ÿš€ +- Added project-understanding confidence display to wizard UI ๐ŸŽจ +- Enhanced keyboard navigation across all wizard screens โŒจ๏ธ +- Added analytics tracking for wizard and tour completion ๐Ÿ“ˆ +- Added First Run Celebration modal with confetti animation ๐ŸŽ‰ + +# UI / UX Enhancements + +- Added expand-to-fullscreen button for Auto Run interface ๐Ÿ–ฅ๏ธ +- Created dedicated modal component and improved modal priority constants for expanded Auto Run view ๐Ÿ“ +- Enhanced user experience with fullscreen editing capabilities โœจ +- Fixed tab name display to correctly show full name for active tabs ๐Ÿท๏ธ +- Added performance optimizations with throttling and caching for scrolling โšก +- Implemented drag-and-drop reordering for execution queue items ๐ŸŽฏ +- Enhanced toast context with agent name for OS notifications ๐Ÿ“ข + +# Auto Run Workflow Improvements + +- Created phase document generation for Auto Run workflow ๐Ÿ“„ +- Added real-time log streaming to the LogViewer component ๐Ÿ“Š + +# Application Behavior / Core Fixes + +- Added validation to prevent nested worktrees inside the main repository ๐Ÿšซ +- Fixed process manager to properly emit exit events on errors ๐Ÿ”ง +- Fixed process exit handling to ensure proper cleanup ๐Ÿงน + +# Update System + +- Implemented automatic update checking on application startup ๐Ÿš€ - Added settings toggle for enabling/disabling startup update checks โš™๏ธ ### Previous Releases in this Series @@ -328,38 +339,40 @@ Minor bugfixes on top of v0.7.3: **Latest: v0.6.1** | Released December 4, 2025 -In this release... -- Added recursive subfolder support for Auto Run markdown files ๐Ÿ—‚๏ธ -- Enhanced document tree display with expandable folder navigation ๐ŸŒณ -- Enabled creating documents in subfolders with path selection ๐Ÿ“ -- Improved batch runner UI with inline progress bars and loop indicators ๐Ÿ“Š -- Fixed execution queue display bug for immediate command processing ๐Ÿ› -- Added folder icons and better visual hierarchy for document browser ๐ŸŽจ -- Implemented dynamic task re-counting for batch run loop iterations ๐Ÿ”„ -- Enhanced create document modal with location selector dropdown ๐Ÿ“ -- Improved progress tracking with per-document completion visualization ๐Ÿ“ˆ -- Added support for nested folder structures in document management ๐Ÿ—๏ธ - -Plus the pre-release ALPHA... -- Template vars now set context in default autorun prompt ๐Ÿš€ -- Added Enter key support for queued message confirmation dialog โŒจ๏ธ -- Kill process capability added to System Process Monitor ๐Ÿ’€ -- Toggle markdown rendering added to Cmd+K Quick Actions ๐Ÿ“ -- Fixed cloudflared detection in packaged app environments ๐Ÿ”ง -- Added debugging logs for process exit diagnostics ๐Ÿ› -- Tab switcher shows last activity timestamps and filters by project ๐Ÿ• -- Slash commands now fill text on Tab/Enter instead of executing โšก -- Added GitHub Actions workflow for auto-assigning issues/PRs ๐Ÿค– -- Graceful handling for playbooks with missing documents implemented โœจ -- Added multi-document batch processing for Auto Run ๐Ÿš€ -- Introduced Git worktree support for parallel execution ๐ŸŒณ -- Created playbook system for saving run configurations ๐Ÿ“š -- Implemented document reset-on-completion with loop mode ๐Ÿ”„ -- Added drag-and-drop document reordering interface ๐ŸŽฏ -- Built Auto Run folder selector with file management ๐Ÿ“ -- Enhanced progress tracking with per-document metrics ๐Ÿ“Š -- Integrated PR creation after worktree completion ๐Ÿ”€ -- Added undo/redo support in document editor โ†ฉ๏ธ +In this release... + +- Added recursive subfolder support for Auto Run markdown files ๐Ÿ—‚๏ธ +- Enhanced document tree display with expandable folder navigation ๐ŸŒณ +- Enabled creating documents in subfolders with path selection ๐Ÿ“ +- Improved batch runner UI with inline progress bars and loop indicators ๐Ÿ“Š +- Fixed execution queue display bug for immediate command processing ๐Ÿ› +- Added folder icons and better visual hierarchy for document browser ๐ŸŽจ +- Implemented dynamic task re-counting for batch run loop iterations ๐Ÿ”„ +- Enhanced create document modal with location selector dropdown ๐Ÿ“ +- Improved progress tracking with per-document completion visualization ๐Ÿ“ˆ +- Added support for nested folder structures in document management ๐Ÿ—๏ธ + +Plus the pre-release ALPHA... + +- Template vars now set context in default autorun prompt ๐Ÿš€ +- Added Enter key support for queued message confirmation dialog โŒจ๏ธ +- Kill process capability added to System Process Monitor ๐Ÿ’€ +- Toggle markdown rendering added to Cmd+K Quick Actions ๐Ÿ“ +- Fixed cloudflared detection in packaged app environments ๐Ÿ”ง +- Added debugging logs for process exit diagnostics ๐Ÿ› +- Tab switcher shows last activity timestamps and filters by project ๐Ÿ• +- Slash commands now fill text on Tab/Enter instead of executing โšก +- Added GitHub Actions workflow for auto-assigning issues/PRs ๐Ÿค– +- Graceful handling for playbooks with missing documents implemented โœจ +- Added multi-document batch processing for Auto Run ๐Ÿš€ +- Introduced Git worktree support for parallel execution ๐ŸŒณ +- Created playbook system for saving run configurations ๐Ÿ“š +- Implemented document reset-on-completion with loop mode ๐Ÿ”„ +- Added drag-and-drop document reordering interface ๐ŸŽฏ +- Built Auto Run folder selector with file management ๐Ÿ“ +- Enhanced progress tracking with per-document metrics ๐Ÿ“Š +- Integrated PR creation after worktree completion ๐Ÿ”€ +- Added undo/redo support in document editor โ†ฉ๏ธ - Implemented auto-save with 5-second debounce ๐Ÿ’พ ### Previous Releases in this Series @@ -374,15 +387,15 @@ Plus the pre-release ALPHA... ### Changes -- Added "Made with Maestro" badge to README header ๐ŸŽฏ -- Redesigned app icon with darker purple color scheme ๐ŸŽจ -- Created new SVG badge for project attribution ๐Ÿท๏ธ -- Added side-by-side image diff viewer for git changes ๐Ÿ–ผ๏ธ -- Enhanced confetti animation with realistic cannon-style bursts ๐ŸŽŠ -- Fixed z-index layering for standing ovation overlay ๐Ÿ“Š -- Improved tab switcher to show all named sessions ๐Ÿ” -- Enhanced batch synopsis prompts for cleaner summaries ๐Ÿ“ -- Added binary file detection in git diff parser ๐Ÿ”ง +- Added "Made with Maestro" badge to README header ๐ŸŽฏ +- Redesigned app icon with darker purple color scheme ๐ŸŽจ +- Created new SVG badge for project attribution ๐Ÿท๏ธ +- Added side-by-side image diff viewer for git changes ๐Ÿ–ผ๏ธ +- Enhanced confetti animation with realistic cannon-style bursts ๐ŸŽŠ +- Fixed z-index layering for standing ovation overlay ๐Ÿ“Š +- Improved tab switcher to show all named sessions ๐Ÿ” +- Enhanced batch synopsis prompts for cleaner summaries ๐Ÿ“ +- Added binary file detection in git diff parser ๐Ÿ”ง - Implemented git file reading at specific refs ๐Ÿ“ ### Previous Releases in this Series @@ -397,24 +410,24 @@ Plus the pre-release ALPHA... ### Changes -- Added Tab Switcher modal for quick navigation between AI tabs ๐Ÿš€ -- Implemented @ mention file completion for AI mode references ๐Ÿ“ -- Added navigation history with back/forward through sessions and tabs โฎ๏ธ -- Introduced tab completion filters for branches, tags, and files ๐ŸŒณ -- Added unread tab indicators and filtering for better organization ๐Ÿ“ฌ -- Implemented token counting display with human-readable formatting ๐Ÿ”ข -- Added markdown rendering toggle for AI responses in terminal ๐Ÿ“ -- Removed built-in slash commands in favor of custom AI commands ๐ŸŽฏ -- Added context menu for sessions with rename, bookmark, move options ๐Ÿ–ฑ๏ธ -- Enhanced file preview with stats showing size, tokens, timestamps ๐Ÿ“Š -- Added token counting with js-tiktoken for file preview stats bar ๐Ÿ”ข -- Implemented Tab Switcher modal for fuzzy-search navigation (Opt+Cmd+T) ๐Ÿ” -- Added Save to History toggle (Cmd+S) for automatic work synopsis tracking ๐Ÿ’พ -- Enhanced tab completion with @ mentions for file references in AI prompts ๐Ÿ“Ž -- Implemented navigation history with back/forward shortcuts (Cmd+Shift+,/.) ๐Ÿ”™ -- Added git branches and tags to intelligent tab completion system ๐ŸŒฟ -- Enhanced markdown rendering with syntax highlighting and toggle view ๐Ÿ“ -- Added right-click context menus for session management and organization ๐Ÿ–ฑ๏ธ +- Added Tab Switcher modal for quick navigation between AI tabs ๐Ÿš€ +- Implemented @ mention file completion for AI mode references ๐Ÿ“ +- Added navigation history with back/forward through sessions and tabs โฎ๏ธ +- Introduced tab completion filters for branches, tags, and files ๐ŸŒณ +- Added unread tab indicators and filtering for better organization ๐Ÿ“ฌ +- Implemented token counting display with human-readable formatting ๐Ÿ”ข +- Added markdown rendering toggle for AI responses in terminal ๐Ÿ“ +- Removed built-in slash commands in favor of custom AI commands ๐ŸŽฏ +- Added context menu for sessions with rename, bookmark, move options ๐Ÿ–ฑ๏ธ +- Enhanced file preview with stats showing size, tokens, timestamps ๐Ÿ“Š +- Added token counting with js-tiktoken for file preview stats bar ๐Ÿ”ข +- Implemented Tab Switcher modal for fuzzy-search navigation (Opt+Cmd+T) ๐Ÿ” +- Added Save to History toggle (Cmd+S) for automatic work synopsis tracking ๐Ÿ’พ +- Enhanced tab completion with @ mentions for file references in AI prompts ๐Ÿ“Ž +- Implemented navigation history with back/forward shortcuts (Cmd+Shift+,/.) ๐Ÿ”™ +- Added git branches and tags to intelligent tab completion system ๐ŸŒฟ +- Enhanced markdown rendering with syntax highlighting and toggle view ๐Ÿ“ +- Added right-click context menus for session management and organization ๐Ÿ–ฑ๏ธ - Improved mobile app with better WebSocket reconnection and status badges ๐Ÿ“ฑ ### Previous Releases in this Series @@ -429,15 +442,15 @@ Plus the pre-release ALPHA... ### Changes -- Fixed tab handling requiring explicitly selected Claude session ๐Ÿ”ง -- Added auto-scroll navigation for slash command list selection โšก -- Implemented TTS audio feedback for toast notifications speak ๐Ÿ”Š -- Fixed shortcut case sensitivity using lowercase key matching ๐Ÿ”ค -- Added Cmd+Shift+J shortcut to jump to bottom instantly โฌ‡๏ธ -- Sorted shortcuts alphabetically in help modal for discovery ๐Ÿ“‘ -- Display full commit message body in git log view ๐Ÿ“ -- Added expand/collapse all buttons to process tree header ๐ŸŒณ -- Support synopsis process type in process tree parsing ๐Ÿ” +- Fixed tab handling requiring explicitly selected Claude session ๐Ÿ”ง +- Added auto-scroll navigation for slash command list selection โšก +- Implemented TTS audio feedback for toast notifications speak ๐Ÿ”Š +- Fixed shortcut case sensitivity using lowercase key matching ๐Ÿ”ค +- Added Cmd+Shift+J shortcut to jump to bottom instantly โฌ‡๏ธ +- Sorted shortcuts alphabetically in help modal for discovery ๐Ÿ“‘ +- Display full commit message body in git log view ๐Ÿ“ +- Added expand/collapse all buttons to process tree header ๐ŸŒณ +- Support synopsis process type in process tree parsing ๐Ÿ” - Renamed "No Group" to "UNGROUPED" for better clarity โœจ ### Previous Releases in this Series @@ -450,15 +463,15 @@ Plus the pre-release ALPHA... **Latest: v0.2.3** | Released November 29, 2025 -โ€ข Enhanced mobile web interface with session sync and history panel ๐Ÿ“ฑ -โ€ข Added ThinkingStatusPill showing real-time token counts and elapsed time โฑ๏ธ -โ€ข Implemented task count badges and session deduplication for batch runner ๐Ÿ“Š -โ€ข Added TTS stop control and improved voice synthesis compatibility ๐Ÿ”Š -โ€ข Created image lightbox with navigation, clipboard, and delete features ๐Ÿ–ผ๏ธ -โ€ข Fixed UI bugs in search, auto-scroll, and sidebar interactions ๐Ÿ› -โ€ข Added global Claude stats with streaming updates across projects ๐Ÿ“ˆ -โ€ข Improved markdown checkbox styling and collapsed palette hover UX โœจ -โ€ข Enhanced scratchpad with search, image paste, and attachment support ๐Ÿ” +โ€ข Enhanced mobile web interface with session sync and history panel ๐Ÿ“ฑ +โ€ข Added ThinkingStatusPill showing real-time token counts and elapsed time โฑ๏ธ +โ€ข Implemented task count badges and session deduplication for batch runner ๐Ÿ“Š +โ€ข Added TTS stop control and improved voice synthesis compatibility ๐Ÿ”Š +โ€ข Created image lightbox with navigation, clipboard, and delete features ๐Ÿ–ผ๏ธ +โ€ข Fixed UI bugs in search, auto-scroll, and sidebar interactions ๐Ÿ› +โ€ข Added global Claude stats with streaming updates across projects ๐Ÿ“ˆ +โ€ข Improved markdown checkbox styling and collapsed palette hover UX โœจ +โ€ข Enhanced scratchpad with search, image paste, and attachment support ๐Ÿ” โ€ข Added splash screen with logo and progress bar during startup ๐ŸŽจ ### Previous Releases in this Series @@ -473,15 +486,15 @@ Plus the pre-release ALPHA... **Latest: v0.1.6** | Released November 27, 2025 -โ€ข Added template variables for dynamic AI command customization ๐ŸŽฏ -โ€ข Implemented session bookmarking with star icons and dedicated section โญ -โ€ข Enhanced Git Log Viewer with smarter date formatting ๐Ÿ“… -โ€ข Improved GitHub release workflow to handle partial failures gracefully ๐Ÿ”ง -โ€ข Added collapsible template documentation in AI Commands panel ๐Ÿ“š -โ€ข Updated default commit command with session ID traceability ๐Ÿ” -โ€ข Added tag indicators for custom-named sessions visually ๐Ÿท๏ธ -โ€ข Improved Git Log search UX with better focus handling ๐ŸŽจ -โ€ข Fixed input placeholder spacing for better readability ๐Ÿ“ +โ€ข Added template variables for dynamic AI command customization ๐ŸŽฏ +โ€ข Implemented session bookmarking with star icons and dedicated section โญ +โ€ข Enhanced Git Log Viewer with smarter date formatting ๐Ÿ“… +โ€ข Improved GitHub release workflow to handle partial failures gracefully ๐Ÿ”ง +โ€ข Added collapsible template documentation in AI Commands panel ๐Ÿ“š +โ€ข Updated default commit command with session ID traceability ๐Ÿ” +โ€ข Added tag indicators for custom-named sessions visually ๐Ÿท๏ธ +โ€ข Improved Git Log search UX with better focus handling ๐ŸŽจ +โ€ข Fixed input placeholder spacing for better readability ๐Ÿ“ โ€ข Updated documentation with new features and template references ๐Ÿ“– ### Previous Releases in this Series @@ -500,6 +513,7 @@ Plus the pre-release ALPHA... All releases are available on the [GitHub Releases page](https://github.com/RunMaestro/Maestro/releases). Maestro is available for: + - **macOS** - Apple Silicon (arm64) and Intel (x64) - **Windows** - x64 - **Linux** - x64 and arm64, AppImage, deb, and rpm packages diff --git a/src/main/storage/opencode-session-storage.ts b/src/main/storage/opencode-session-storage.ts index fb5c4fe67b..06c48ec525 100644 --- a/src/main/storage/opencode-session-storage.ts +++ b/src/main/storage/opencode-session-storage.ts @@ -2,13 +2,12 @@ * OpenCode Session Storage Implementation * * This module implements the AgentSessionStorage interface for OpenCode. - * OpenCode stores sessions as JSON files in ~/.local/share/opencode/storage/ * - * Directory structure: - * - project/ - Project metadata (SHA1 hash of path as ID) - * - session/{projectID}/ - Session metadata per project - * - message/{sessionID}/ - Messages per session - * - part/{messageID}/ - Message parts (text, tool, reasoning) + * OpenCode v1.2+ stores sessions in SQLite at ~/.local/share/opencode/opencode.db + * Older versions used JSON files at ~/.local/share/opencode/storage/ + * + * This implementation reads from SQLite first, falls back to JSON for pre-v1.2 + * installs, and deduplicates sessions when both sources exist (migration period). * * Session IDs: Format is `ses_{base62}` (e.g., ses_4d585107dffeO9bO3HvMdvLYyC) * Project IDs: SHA1 hash of the project path @@ -21,7 +20,9 @@ import path from 'path'; import os from 'os'; import fs from 'fs/promises'; +import fsSync from 'fs'; import { createHash } from 'crypto'; +import Database from 'better-sqlite3'; import { logger } from '../utils/logger'; import { captureException } from '../utils/sentry'; import { readFileRemote, readDirRemote, statRemote } from '../utils/remote-fs'; @@ -37,20 +38,38 @@ import { isWindows } from '../../shared/platformDetection'; const LOG_CONTEXT = '[OpenCodeSessionStorage]'; +/** Regex matching one or more trailing path separators (platform-aware) */ +const TRAILING_SEP_RE = new RegExp(`${path.sep.replace('\\', '\\\\')}+$`); + /** - * Get OpenCode storage base directory (platform-specific) - * - Linux/macOS: ~/.local/share/opencode/storage - * - Windows: %APPDATA%\opencode\storage + * Get OpenCode data base directory (platform-specific) + * - Linux/macOS: ~/.local/share/opencode + * - Windows: %APPDATA%\opencode */ -function getOpenCodeStorageDir(): string { +function getOpenCodeDataDir(): string { if (isWindows()) { const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'); - return path.join(appData, 'opencode', 'storage'); + return path.join(appData, 'opencode'); } - return path.join(os.homedir(), '.local', 'share', 'opencode', 'storage'); + return path.join(os.homedir(), '.local', 'share', 'opencode'); +} + +/** + * Get OpenCode JSON storage directory (pre-v1.2) + */ +function getOpenCodeStorageDir(): string { + return path.join(getOpenCodeDataDir(), 'storage'); +} + +/** + * Get OpenCode SQLite database path (v1.2+) + */ +function getOpenCodeDbPath(): string { + return path.join(getOpenCodeDataDir(), 'opencode.db'); } const OPENCODE_STORAGE_DIR = getOpenCodeStorageDir(); +const OPENCODE_DB_PATH = getOpenCodeDbPath(); /** * OpenCode project metadata structure @@ -133,6 +152,115 @@ interface OpenCodePart { }; } +// โ”€โ”€โ”€ SQLite row types (v1.2+) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +/** + * Raw row from the SQLite `session` table + */ +interface SqliteSessionRow { + id: string; + project_id: string; + directory: string; + title: string; + version: string; + time_created: number; // Unix ms + time_updated: number; // Unix ms + summary_additions: number | null; + summary_deletions: number | null; + summary_files: number | null; +} + +/** + * Raw row from the SQLite `message` table + * The `data` column is a JSON blob containing role, model, tokens, cost, etc. + */ +interface SqliteMessageRow { + id: string; + session_id: string; + time_created: number; + time_updated: number; + data: string; // JSON blob +} + +/** + * Parsed message data from the SQLite `data` JSON blob + */ +interface SqliteMessageData { + role?: 'user' | 'assistant'; + modelID?: string; + providerID?: string; + agent?: string; + tokens?: { + input?: number; + output?: number; + reasoning?: number; + cache?: { + read?: number; + write?: number; + }; + }; + cost?: number; +} + +/** + * Parsed part data from the SQLite `data` JSON blob + */ +interface SqlitePartData { + type?: 'text' | 'reasoning' | 'tool' | 'step-start' | 'step-finish'; + text?: string; + tool?: string; + state?: { + status?: string; + input?: unknown; + output?: unknown; + }; +} + +// โ”€โ”€โ”€ SQLite helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +/** + * Open the OpenCode SQLite database in read-only mode. + * Returns null if the database file doesn't exist. + */ +function openOpenCodeDb(dbPath: string = OPENCODE_DB_PATH): Database.Database | null { + if (!fsSync.existsSync(dbPath)) { + return null; + } + try { + const db = new Database(dbPath, { readonly: true, fileMustExist: true }); + return db; + } catch (error) { + logger.warn(`${LOG_CONTEXT} Failed to open OpenCode SQLite database at ${dbPath}: ${error}`); + captureException(error instanceof Error ? error : new Error(String(error)), { + extra: { dbPath }, + }); + throw error; + } +} + +/** + * Safely parse a JSON string, returning null on failure + */ +function safeJsonParse(json: string): T | null { + try { + return JSON.parse(json) as T; + } catch { + return null; + } +} + +/** + * Check if a table exists in a SQLite database + */ +function tableExists(db: Database.Database, tableName: string): boolean { + const row = db + .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?") + .get(tableName) as { name: string } | undefined; + return !!row; +} + +// โ”€โ”€โ”€ Shared helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + /** * Generate the project ID hash from a path (SHA1) */ @@ -202,7 +330,8 @@ async function listJsonFilesRemote(dirPath: string, sshConfig: SshRemoteConfig): /** * OpenCode Session Storage Implementation * - * Provides access to OpenCode's local session storage at ~/.local/share/opencode/storage/ + * Reads from SQLite (v1.2+) with JSON file fallback (pre-v1.2). + * During migration periods, both sources are merged with dedup by session ID. */ export class OpenCodeSessionStorage extends BaseSessionStorage { readonly agentId: ToolType = 'opencode'; @@ -272,8 +401,8 @@ export class OpenCodeSessionStorage extends BaseSessionStorage { const projectFiles = await listJsonFiles(projectDir); - // Normalize project path for comparison (resolve and remove trailing slashes) - const normalizedPath = path.resolve(projectPath).replace(/\/+$/, ''); + // Normalize project path for comparison (resolve and remove trailing separators) + const normalizedPath = path.resolve(projectPath).replace(TRAILING_SEP_RE, ''); logger.info(`Looking for OpenCode project for path: ${normalizedPath}`, LOG_CONTEXT); for (const file of projectFiles) { @@ -284,7 +413,7 @@ export class OpenCodeSessionStorage extends BaseSessionStorage { if (!projectData?.worktree) continue; // Normalize stored path the same way - const storedPath = path.resolve(projectData.worktree).replace(/\/+$/, ''); + const storedPath = path.resolve(projectData.worktree).replace(TRAILING_SEP_RE, ''); // Exact match if (storedPath === normalizedPath) { @@ -297,8 +426,8 @@ export class OpenCodeSessionStorage extends BaseSessionStorage { // Check if one is a subdirectory of the other (handles worktree subdirs) if ( - normalizedPath.startsWith(storedPath + '/') || - storedPath.startsWith(normalizedPath + '/') + normalizedPath.startsWith(storedPath + path.sep) || + storedPath.startsWith(normalizedPath + path.sep) ) { logger.info( `Found OpenCode project (subdirectory match): ${projectData.id} for path: ${normalizedPath}`, @@ -610,19 +739,377 @@ export class OpenCodeSessionStorage extends BaseSessionStorage { return textParts.join(' ').trim(); } + // โ”€โ”€โ”€ SQLite-based methods (OpenCode v1.2+) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + /** + * List sessions from SQLite database for a given project path. + * Returns null if the database doesn't exist or lacks the expected schema. + */ + private listSessionsSqlite(projectPath: string): AgentSessionInfo[] | null { + const db = openOpenCodeDb(); + if (!db) return null; + + try { + if (!tableExists(db, 'session') || !tableExists(db, 'project')) { + return null; + } + + const normalizedPath = path.resolve(projectPath).replace(TRAILING_SEP_RE, ''); + + // Find matching project(s) โ€” exact match or subdirectory match + const projects = db.prepare('SELECT id, worktree FROM project').all() as Array<{ + id: string; + worktree: string; + }>; + + const matchingProjectIds: string[] = []; + let hasGlobalProject = false; + for (const proj of projects) { + // Skip the 'global' project (worktree '/') from project-level matching โ€” + // it matches everything. Its sessions are filtered by directory below. + if (proj.id === 'global') { + hasGlobalProject = true; + continue; + } + const storedPath = path.resolve(proj.worktree).replace(TRAILING_SEP_RE, ''); + if ( + storedPath === normalizedPath || + normalizedPath.startsWith(storedPath + path.sep) || + storedPath.startsWith(normalizedPath + path.sep) + ) { + matchingProjectIds.push(proj.id); + } + } + + // Collect sessions from matching dedicated projects + let sessions: SqliteSessionRow[] = []; + if (matchingProjectIds.length > 0) { + const placeholders = matchingProjectIds.map(() => '?').join(','); + sessions = db + .prepare( + `SELECT id, project_id, directory, title, version, time_created, time_updated, summary_additions, summary_deletions, summary_files FROM session WHERE project_id IN (${placeholders}) ORDER BY time_updated DESC` + ) + .all(...matchingProjectIds) as SqliteSessionRow[]; + } + + // Also include global project sessions that match by directory field + if (hasGlobalProject) { + const escapedPath = normalizedPath.replace(/[%_\\]/g, '\\$&'); + const globalSessions = db + .prepare( + "SELECT id, project_id, directory, title, version, time_created, time_updated, summary_additions, summary_deletions, summary_files FROM session WHERE project_id = 'global' AND (directory = ? OR directory LIKE ? ESCAPE '\\') ORDER BY time_updated DESC" + ) + .all(normalizedPath, escapedPath + path.sep + '%') as SqliteSessionRow[]; + if (globalSessions.length > 0) { + const existingIds = new Set(sessions.map((s) => s.id)); + for (const gs of globalSessions) { + if (!existingIds.has(gs.id)) { + sessions.push(gs); + } + } + } + } + + // Re-sort after merging global sessions so newest come first + sessions.sort((a, b) => b.time_updated - a.time_updated); + + if (sessions.length === 0) { + logger.info(`No OpenCode sessions found in SQLite for: ${normalizedPath}`, LOG_CONTEXT); + return []; + } + + logger.info( + `Found ${sessions.length} OpenCode sessions in SQLite for: ${normalizedPath}`, + LOG_CONTEXT + ); + + return this.convertSqliteSessionRows(sessions, projectPath, db); + } catch (error) { + logger.warn(`Error reading OpenCode SQLite database: ${error}`, LOG_CONTEXT); + return null; + } finally { + db.close(); + } + } + + /** + * Convert SQLite session rows to AgentSessionInfo array, loading message stats + */ + private convertSqliteSessionRows( + rows: SqliteSessionRow[], + projectPath: string, + db: Database.Database + ): AgentSessionInfo[] { + const hasMessageTable = tableExists(db, 'message'); + const hasPartTable = tableExists(db, 'part'); + const sessions: AgentSessionInfo[] = []; + + for (const row of rows) { + let messageCount = 0; + let totalInputTokens = 0; + let totalOutputTokens = 0; + let totalCacheReadTokens = 0; + let totalCacheWriteTokens = 0; + let totalCost = 0; + let firstMessage = row.title || ''; + let durationSeconds = 0; + + if (hasMessageTable) { + const messages = db + .prepare( + 'SELECT id, data, time_created FROM message WHERE session_id = ? ORDER BY time_created ASC' + ) + .all(row.id) as Array<{ id: string; data: string; time_created: number }>; + + messageCount = messages.length; + + if (messages.length >= 2) { + const first = messages[0].time_created; + const last = messages[messages.length - 1].time_created; + if (first && last) { + durationSeconds = Math.max(0, Math.floor((last - first) / 1000)); + } + } + + // Aggregate stats and find preview message + let foundPreview = false; + for (const msg of messages) { + const data = safeJsonParse(msg.data); + if (!data) continue; + + if (data.tokens) { + totalInputTokens += data.tokens.input || 0; + totalOutputTokens += data.tokens.output || 0; + totalCacheReadTokens += data.tokens.cache?.read || 0; + totalCacheWriteTokens += data.tokens.cache?.write || 0; + } + if (data.cost) { + totalCost += data.cost; + } + + // Get preview from first assistant message with text + if (!foundPreview && data.role === 'assistant' && hasPartTable) { + const parts = db + .prepare('SELECT data FROM part WHERE message_id = ? ORDER BY time_created ASC') + .all(msg.id) as Array<{ data: string }>; + for (const part of parts) { + const partData = safeJsonParse(part.data); + if (partData?.type === 'text' && partData.text?.trim()) { + firstMessage = partData.text; + foundPreview = true; + break; + } + } + } + + // Fall back to first user message if no assistant text found + if (!foundPreview && data.role === 'user' && hasPartTable) { + const parts = db + .prepare('SELECT data FROM part WHERE message_id = ? ORDER BY time_created ASC') + .all(msg.id) as Array<{ data: string }>; + for (const part of parts) { + const partData = safeJsonParse(part.data); + if (partData?.type === 'text' && partData.text?.trim()) { + firstMessage = partData.text; + foundPreview = true; + break; + } + } + } + } + } + + const createdAt = row.time_created + ? new Date(row.time_created).toISOString() + : new Date().toISOString(); + const updatedAt = row.time_updated ? new Date(row.time_updated).toISOString() : createdAt; + + sessions.push({ + sessionId: row.id, + projectPath, + timestamp: createdAt, + modifiedAt: updatedAt, + firstMessage: firstMessage.slice(0, 200), + messageCount, + sizeBytes: 0, + costUsd: totalCost, + inputTokens: totalInputTokens, + outputTokens: totalOutputTokens, + cacheReadTokens: totalCacheReadTokens, + cacheCreationTokens: totalCacheWriteTokens, + durationSeconds, + }); + } + + return sessions; + } + + /** + * Load messages for a session from SQLite. + * Returns null if the database doesn't exist or lacks the expected schema. + */ + private loadSessionMessagesSqlite(sessionId: string): { + messages: OpenCodeMessage[]; + parts: Map; + totalInputTokens: number; + totalOutputTokens: number; + totalCacheReadTokens: number; + totalCacheWriteTokens: number; + totalCost: number; + } | null { + const db = openOpenCodeDb(); + if (!db) return null; + + try { + if (!tableExists(db, 'message')) return null; + + const messageRows = db + .prepare( + 'SELECT id, session_id, time_created, time_updated, data FROM message WHERE session_id = ? ORDER BY time_created ASC' + ) + .all(sessionId) as SqliteMessageRow[]; + + if (messageRows.length === 0) { + // Verify session actually exists in SQLite before blocking JSON fallback + if (tableExists(db, 'session')) { + const sessionExists = db + .prepare('SELECT 1 FROM session WHERE id = ? LIMIT 1') + .get(sessionId); + if (sessionExists) { + return { + messages: [], + parts: new Map(), + totalInputTokens: 0, + totalOutputTokens: 0, + totalCacheReadTokens: 0, + totalCacheWriteTokens: 0, + totalCost: 0, + }; + } + } + return null; + } + + const hasPartTable = tableExists(db, 'part'); + const messages: OpenCodeMessage[] = []; + const parts = new Map(); + let totalInputTokens = 0; + let totalOutputTokens = 0; + let totalCacheReadTokens = 0; + let totalCacheWriteTokens = 0; + let totalCost = 0; + + for (const row of messageRows) { + const data = safeJsonParse(row.data); + if (!data) continue; + + const msg: OpenCodeMessage = { + id: row.id, + sessionID: sessionId, + role: data.role || 'user', + time: { created: row.time_created }, + tokens: data.tokens, + cost: data.cost, + }; + messages.push(msg); + + if (data.tokens) { + totalInputTokens += data.tokens.input || 0; + totalOutputTokens += data.tokens.output || 0; + totalCacheReadTokens += data.tokens.cache?.read || 0; + totalCacheWriteTokens += data.tokens.cache?.write || 0; + } + if (data.cost) { + totalCost += data.cost; + } + + // Load parts from SQLite + if (hasPartTable) { + const partRows = db + .prepare('SELECT id, data FROM part WHERE message_id = ? ORDER BY time_created ASC') + .all(row.id) as Array<{ id: string; data: string }>; + + const messageParts: OpenCodePart[] = []; + for (const partRow of partRows) { + const partData = safeJsonParse(partRow.data); + if (partData) { + messageParts.push({ + id: partRow.id, + messageID: row.id, + type: partData.type || 'text', + text: partData.text, + tool: partData.tool, + state: partData.state, + }); + } + } + parts.set(row.id, messageParts); + } + } + + return { + messages, + parts, + totalInputTokens, + totalOutputTokens, + totalCacheReadTokens, + totalCacheWriteTokens, + totalCost, + }; + } catch (error) { + logger.warn(`Error loading messages from OpenCode SQLite: ${error}`, LOG_CONTEXT); + return null; + } finally { + db.close(); + } + } + + // โ”€โ”€โ”€ Merged listing (SQLite + JSON) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + async listSessions( projectPath: string, sshConfig?: SshRemoteConfig ): Promise { - // Use SSH remote access if config provided + // Use SSH remote access if config provided (JSON only for SSH โ€” no remote SQLite) if (sshConfig) { return this.listSessionsRemote(projectPath, sshConfig); } + // Try SQLite first (v1.2+), then fall back to JSON, merge and dedup + const sqliteSessions = this.listSessionsSqlite(projectPath); + const jsonSessions = await this.listSessionsJson(projectPath); + + if (sqliteSessions && sqliteSessions.length > 0) { + if (jsonSessions.length > 0) { + // Merge: SQLite is authoritative, add JSON-only sessions + const sqliteIds = new Set(sqliteSessions.map((s) => s.sessionId)); + const merged = [...sqliteSessions]; + for (const jsonSession of jsonSessions) { + if (!sqliteIds.has(jsonSession.sessionId)) { + merged.push(jsonSession); + } + } + merged.sort((a, b) => new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime()); + logger.info( + `Merged ${sqliteSessions.length} SQLite + ${merged.length - sqliteSessions.length} JSON-only sessions for: ${projectPath}`, + LOG_CONTEXT + ); + return merged; + } + return sqliteSessions; + } + + // SQLite unavailable or empty โ€” use JSON results + return jsonSessions; + } + + /** + * List sessions from JSON files (pre-v1.2 format) + */ + private async listSessionsJson(projectPath: string): Promise { const projectId = await this.findProjectId(projectPath); if (!projectId) { - logger.info(`No OpenCode project found for path: ${projectPath}`, LOG_CONTEXT); return []; } @@ -634,7 +1121,6 @@ export class OpenCodeSessionStorage extends BaseSessionStorage { try { await fs.access(sessionDir); } catch { - logger.info(`No OpenCode sessions directory for project: ${projectPath}`, LOG_CONTEXT); return []; } @@ -731,10 +1217,12 @@ export class OpenCodeSessionStorage extends BaseSessionStorage { // Sort by modified date (newest first) sessions.sort((a, b) => new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime()); - logger.info( - `Found ${sessions.length} OpenCode sessions for project: ${projectPath}`, - LOG_CONTEXT - ); + if (sessions.length > 0) { + logger.info( + `Found ${sessions.length} OpenCode sessions (JSON) for: ${projectPath}`, + LOG_CONTEXT + ); + } return sessions; } @@ -872,9 +1360,22 @@ export class OpenCodeSessionStorage extends BaseSessionStorage { options?: SessionReadOptions, sshConfig?: SshRemoteConfig ): Promise { - const { messages, parts } = sshConfig - ? await this.loadSessionMessagesRemote(sessionId, sshConfig) - : await this.loadSessionMessages(sessionId); + // Try SQLite first for local sessions, fall back to JSON + let loaded: { + messages: OpenCodeMessage[]; + parts: Map; + } | null = null; + + if (sshConfig) { + loaded = await this.loadSessionMessagesRemote(sessionId, sshConfig); + } else { + loaded = this.loadSessionMessagesSqlite(sessionId); + if (!loaded) { + loaded = await this.loadSessionMessages(sessionId); + } + } + + const { messages, parts } = loaded; const sessionMessages: SessionMessage[] = []; @@ -911,9 +1412,22 @@ export class OpenCodeSessionStorage extends BaseSessionStorage { _projectPath: string, sshConfig?: SshRemoteConfig ): Promise { - const { messages, parts } = sshConfig - ? await this.loadSessionMessagesRemote(sessionId, sshConfig) - : await this.loadSessionMessages(sessionId); + // Try SQLite first for local sessions, fall back to JSON + let loaded: { + messages: OpenCodeMessage[]; + parts: Map; + } | null = null; + + if (sshConfig) { + loaded = await this.loadSessionMessagesRemote(sessionId, sshConfig); + } else { + loaded = this.loadSessionMessagesSqlite(sessionId); + if (!loaded) { + loaded = await this.loadSessionMessages(sessionId); + } + } + + const { messages, parts } = loaded; return messages .filter((msg) => msg.role === 'user' || msg.role === 'assistant') @@ -929,11 +1443,24 @@ export class OpenCodeSessionStorage extends BaseSessionStorage { sessionId: string, sshConfig?: SshRemoteConfig ): string | null { - // OpenCode uses a more complex structure with multiple directories - // Return the message directory as the "session path" if (sshConfig) { return this.getRemoteMessageDir(sessionId); } + // Check if session exists in SQLite before returning DB path + if (fsSync.existsSync(OPENCODE_DB_PATH)) { + const db = openOpenCodeDb(); + if (db) { + try { + const exists = tableExists(db, 'session') + ? db.prepare('SELECT 1 FROM session WHERE id = ? LIMIT 1').get(sessionId) + : null; + if (exists) return OPENCODE_DB_PATH; + } finally { + db.close(); + } + } + } + // Fallback to JSON message directory return this.getMessageDir(sessionId); } @@ -951,7 +1478,21 @@ export class OpenCodeSessionStorage extends BaseSessionStorage { } try { - // Load all messages for the session + // Check if this session exists in SQLite โ€” deletion not supported for SQLite sessions + // (we open the DB read-only and shouldn't modify OpenCode's database) + const sqliteResult = this.loadSessionMessagesSqlite(sessionId); + if (sqliteResult) { + logger.warn( + 'Delete message pair not supported for SQLite-backed OpenCode sessions', + LOG_CONTEXT + ); + return { + success: false, + error: 'Delete not supported for OpenCode v1.2+ SQLite sessions', + }; + } + + // Load all messages for the session (JSON files) const { messages, parts } = await this.loadSessionMessages(sessionId); if (messages.length === 0) {