agent-decoder 0.3.5__tar.gz → 0.3.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/.gitignore +5 -1
  2. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/ARCHITECTURE.md +52 -0
  3. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/CHANGELOG.md +21 -1
  4. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/PKG-INFO +1 -1
  5. agent_decoder-0.3.6/docs/agent-runs.md +15 -0
  6. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/App.tsx +13 -1
  7. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/api/client.ts +2 -0
  8. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/api/types.ts +22 -0
  9. agent_decoder-0.3.6/frontend/src/components/ArtifactsPanel/ArtifactsPanel.tsx +191 -0
  10. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/ConversationHeader.tsx +21 -1
  11. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/__tests__/NativeTerminalView.test.tsx +2 -0
  12. agent_decoder-0.3.6/frontend/src/components/ChatView/nativeTerminal/__tests__/eraseScrollback.test.ts +64 -0
  13. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/nativeTerminal/xtermTerminal.ts +17 -1
  14. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/TextBlock.tsx +29 -7
  15. agent_decoder-0.3.6/frontend/src/components/Runs.css +26 -0
  16. agent_decoder-0.3.6/frontend/src/components/Runs.tsx +97 -0
  17. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/ShortcutsPopup.tsx +1 -0
  18. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/Sidebar.tsx +5 -0
  19. agent_decoder-0.3.6/frontend/src/components/__tests__/Runs.test.tsx +50 -0
  20. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ui/icons.tsx +11 -0
  21. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/router.ts +2 -1
  22. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/store/index.ts +9 -0
  23. agent_decoder-0.3.6/frontend/src/utils/__tests__/artifacts.test.ts +35 -0
  24. agent_decoder-0.3.6/frontend/src/utils/artifacts.ts +66 -0
  25. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/utils/localStorage.ts +1 -0
  26. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/pyproject.toml +13 -0
  27. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/__init__.py +1 -1
  28. agent_decoder-0.3.6/src/agent_decoder/_tmux_capture.py +23 -0
  29. agent_decoder-0.3.6/src/agent_decoder/_tmux_exec.py +16 -0
  30. agent_decoder-0.3.6/src/agent_decoder/agent_runs.py +266 -0
  31. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/db.py +50 -0
  32. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/driver_manager.py +53 -78
  33. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/drivers/base.py +14 -2
  34. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/drivers/registry.py +4 -5
  35. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/drivers/tmux_driver.py +24 -8
  36. agent_decoder-0.3.6/src/agent_decoder/execution_manager.py +414 -0
  37. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/pty_runner.py +145 -7
  38. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/scanner.py +14 -2
  39. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/server.py +134 -81
  40. agent_decoder-0.3.6/src/agent_decoder/static/assets/ArtifactsPanel-D-CqWycU.js +1 -0
  41. agent_decoder-0.3.5/src/agent_decoder/static/assets/ChatView-keqP4MzD.js → agent_decoder-0.3.6/src/agent_decoder/static/assets/ChatView-CFunVM15.js +39 -39
  42. agent_decoder-0.3.6/src/agent_decoder/static/assets/Dashboard-CBe8ZXJ-.js +1 -0
  43. agent_decoder-0.3.6/src/agent_decoder/static/assets/FileViewer-CVtveC8R.js +1 -0
  44. agent_decoder-0.3.5/src/agent_decoder/static/assets/HelpPopup-D7mbl9pT.js → agent_decoder-0.3.6/src/agent_decoder/static/assets/HelpPopup-BYhBIvA6.js +1 -1
  45. agent_decoder-0.3.6/src/agent_decoder/static/assets/Runs-DvLUpR9O.js +1 -0
  46. agent_decoder-0.3.6/src/agent_decoder/static/assets/Runs-Y8oF-gxW.css +1 -0
  47. agent_decoder-0.3.5/src/agent_decoder/static/assets/ScrollContainer-BT_Dzxah.js → agent_decoder-0.3.6/src/agent_decoder/static/assets/ScrollContainer-CA7-aYqv.js +1 -1
  48. agent_decoder-0.3.6/src/agent_decoder/static/assets/SearchOverlay-CF4dXG6x.js +1 -0
  49. agent_decoder-0.3.6/src/agent_decoder/static/assets/SessionItem-BUejVuL5.js +1 -0
  50. agent_decoder-0.3.6/src/agent_decoder/static/assets/SettingsModal-DWSMRQmn.js +1 -0
  51. agent_decoder-0.3.6/src/agent_decoder/static/assets/ShortcutsPopup-Df9bhfs6.js +1 -0
  52. agent_decoder-0.3.6/src/agent_decoder/static/assets/Sidebar-CJcwJziw.js +1 -0
  53. agent_decoder-0.3.6/src/agent_decoder/static/assets/fileType-BHvAriyJ.js +1 -0
  54. agent_decoder-0.3.6/src/agent_decoder/static/assets/formatRelative-CavTjOcy.js +1 -0
  55. agent_decoder-0.3.6/src/agent_decoder/static/assets/hljs-CvdnYs1C.js +35 -0
  56. agent_decoder-0.3.6/src/agent_decoder/static/assets/index-XPrlVfCr.js +5 -0
  57. agent_decoder-0.3.5/src/agent_decoder/static/assets/react-Bi_azaFt.js → agent_decoder-0.3.6/src/agent_decoder/static/assets/react-CCIEwYL0.js +4 -4
  58. agent_decoder-0.3.6/src/agent_decoder/static/assets/renderSnippet-CcNMfZRd.js +1 -0
  59. agent_decoder-0.3.6/src/agent_decoder/static/assets/sessionCache-Cj3FsbQ4.js +1 -0
  60. agent_decoder-0.3.5/src/agent_decoder/static/assets/shared-DdVGprvF.js → agent_decoder-0.3.6/src/agent_decoder/static/assets/shared-CAVcrZag.js +1 -1
  61. agent_decoder-0.3.6/src/agent_decoder/static/assets/ui-BVMZ0uWi.js +1 -0
  62. agent_decoder-0.3.6/src/agent_decoder/static/assets/useDebounce-CRtjXm0i.js +1 -0
  63. agent_decoder-0.3.6/src/agent_decoder/static/assets/useSessionDetail-D4HneaxZ.js +1 -0
  64. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/index.html +3 -4
  65. agent_decoder-0.3.6/src/agent_decoder/tmux_terminal.py +297 -0
  66. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/drivers/test_driver_manager.py +21 -4
  67. agent_decoder-0.3.6/tests/test_agent_runs.py +272 -0
  68. agent_decoder-0.3.6/tests/test_execution_manager.py +408 -0
  69. agent_decoder-0.3.6/tests/test_scan_unchanged_files.py +178 -0
  70. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_scanner.py +17 -0
  71. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_server.py +72 -0
  72. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_server_capabilities.py +3 -3
  73. agent_decoder-0.3.5/src/agent_decoder/static/assets/Dashboard-CTEAF_VF.js +0 -1
  74. agent_decoder-0.3.5/src/agent_decoder/static/assets/FileViewer-CgpI4NkB.js +0 -1
  75. agent_decoder-0.3.5/src/agent_decoder/static/assets/SearchOverlay-CHa-r4cd.js +0 -1
  76. agent_decoder-0.3.5/src/agent_decoder/static/assets/SessionItem-BimSOVG4.js +0 -1
  77. agent_decoder-0.3.5/src/agent_decoder/static/assets/SettingsModal-CuF_Vlos.js +0 -1
  78. agent_decoder-0.3.5/src/agent_decoder/static/assets/ShortcutsPopup-CMUseb-s.js +0 -1
  79. agent_decoder-0.3.5/src/agent_decoder/static/assets/Sidebar-BwH_O4R9.js +0 -1
  80. agent_decoder-0.3.5/src/agent_decoder/static/assets/client-C24egkBM.js +0 -1
  81. agent_decoder-0.3.5/src/agent_decoder/static/assets/formatRelative-j86webkr.js +0 -1
  82. agent_decoder-0.3.5/src/agent_decoder/static/assets/hljs-B3O74O7Z.js +0 -35
  83. agent_decoder-0.3.5/src/agent_decoder/static/assets/index-CVN26yaN.js +0 -5
  84. agent_decoder-0.3.5/src/agent_decoder/static/assets/renderSnippet-CqpQQugH.js +0 -1
  85. agent_decoder-0.3.5/src/agent_decoder/static/assets/ui-BcGPjpYn.js +0 -1
  86. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/.editorconfig +0 -0
  87. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/.github/CODEOWNERS +0 -0
  88. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  89. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  90. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  91. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  92. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/.github/dependabot.yml +0 -0
  93. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/.github/workflows/ci.yml +0 -0
  94. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/.github/workflows/publish.yml +0 -0
  95. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/.pre-commit-config.yaml +0 -0
  96. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/CODE_OF_CONDUCT.md +0 -0
  97. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/CONTRIBUTING.md +0 -0
  98. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/LICENSE +0 -0
  99. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/Makefile +0 -0
  100. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/README.md +0 -0
  101. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/SECURITY.md +0 -0
  102. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/agent_decoder/__init__.py +0 -0
  103. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/docs/ad-poster.png +0 -0
  104. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/docs/demo-poster.jpg +0 -0
  105. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/index.html +0 -0
  106. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/package-lock.json +0 -0
  107. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/package.json +0 -0
  108. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/playwright.config.ts +0 -0
  109. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/public/android-chrome-192x192.png +0 -0
  110. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/public/android-chrome-512x512.png +0 -0
  111. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/public/app-icon.png +0 -0
  112. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/public/apple-touch-icon.png +0 -0
  113. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/public/favicon-16x16.png +0 -0
  114. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/public/favicon-32x32.png +0 -0
  115. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/public/favicon.ico +0 -0
  116. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/public/site.webmanifest +0 -0
  117. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/api/__tests__/client.test.ts +0 -0
  118. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/api/sessionCache.ts +0 -0
  119. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/assets/fonts/LICENSES.md +0 -0
  120. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/assets/fonts/fira-code.woff2 +0 -0
  121. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/assets/fonts/ioskeley-mono.woff2 +0 -0
  122. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/assets/fonts/jetbrains-mono.woff2 +0 -0
  123. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/assets/fonts/julia-mono.woff2 +0 -0
  124. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/assets/fonts/libertinus-mono.woff2 +0 -0
  125. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/assets/fonts/monaspace-argon.woff2 +0 -0
  126. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/assets/fonts/source-code-pro.woff2 +0 -0
  127. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/assets/fonts/spline-sans-mono.woff2 +0 -0
  128. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/assets/fonts/xanh-mono.woff2 +0 -0
  129. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/ChatInput.tsx +0 -0
  130. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/ChatInputBanners.tsx +0 -0
  131. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/ChatInputBar.tsx +0 -0
  132. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/ChatView.tsx +0 -0
  133. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/EmptyState.tsx +0 -0
  134. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/MessageListContainerContext.ts +0 -0
  135. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/MessageListLoader.tsx +0 -0
  136. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/ModelPicker.tsx +0 -0
  137. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/NativeActionBanner.tsx +0 -0
  138. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/NativeTerminalView.tsx +0 -0
  139. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/OwnershipBanner.tsx +0 -0
  140. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/ProviderThemeContext.tsx +0 -0
  141. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/__tests__/ChatInput.test.tsx +0 -0
  142. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/__tests__/ChatViewLifecycle.test.tsx +0 -0
  143. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/__tests__/ConversationHeader.test.tsx +0 -0
  144. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/__tests__/EmptyState.test.tsx +0 -0
  145. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/__tests__/NativeActionBanner.test.tsx +0 -0
  146. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/chatCard.ts +0 -0
  147. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/hooks/__tests__/useRecaps.test.tsx +0 -0
  148. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/hooks/useExpandPreserveAnchor.ts +0 -0
  149. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/hooks/useMessageHistory.ts +0 -0
  150. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/hooks/useNativePty.ts +0 -0
  151. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/hooks/useRecaps.ts +0 -0
  152. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ChatView/hooks/useSessionOwnership.ts +0 -0
  153. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/ActivityStrip.tsx +0 -0
  154. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/Dashboard.tsx +0 -0
  155. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/DashboardEmpty.tsx +0 -0
  156. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/DashboardSearch.tsx +0 -0
  157. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/FeaturedSession.tsx +0 -0
  158. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/HeadlineInsight.tsx +0 -0
  159. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/Hero.tsx +0 -0
  160. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/ModelRates.tsx +0 -0
  161. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/ModelUsageStrip.tsx +0 -0
  162. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/ProjectStrip.tsx +0 -0
  163. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/SectionHeader.tsx +0 -0
  164. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/Sparkline.tsx +0 -0
  165. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/StatStrip.tsx +0 -0
  166. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/TipCard.tsx +0 -0
  167. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/TouchedFilesList.tsx +0 -0
  168. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Dashboard/fmt.ts +0 -0
  169. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ErrorBoundary.tsx +0 -0
  170. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/FileViewer/FileViewer.tsx +0 -0
  171. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/FileViewer/index.ts +0 -0
  172. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/AssistantMessage.tsx +0 -0
  173. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/ConfirmDialog.tsx +0 -0
  174. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/EphemeralMessage.tsx +0 -0
  175. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/MessageList.tsx +0 -0
  176. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/SidechainBranch.tsx +0 -0
  177. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/StreamingIndicator.tsx +0 -0
  178. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/ThinkingBlock.tsx +0 -0
  179. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/ThoughtChain.tsx +0 -0
  180. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/ToolUseBlock.tsx +0 -0
  181. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/UserMessage.tsx +0 -0
  182. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/__tests__/Ephemeral.test.tsx +0 -0
  183. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/__tests__/TextBlock.test.tsx +0 -0
  184. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/__tests__/ToolUseBlock.test.tsx +0 -0
  185. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/__tests__/UserMessage.test.tsx +0 -0
  186. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/blockRenderers.tsx +0 -0
  187. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/groupMessages.ts +0 -0
  188. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/hooks/__tests__/useScrollController.test.tsx +0 -0
  189. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/hooks/__tests__/useSessionDetail.test.ts +0 -0
  190. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/hooks/useCycle.ts +0 -0
  191. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/hooks/useScrollController.ts +0 -0
  192. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/hooks/useSessionDetail.ts +0 -0
  193. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Messages/pairToolBlocks.ts +0 -0
  194. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ScrollContainer.tsx +0 -0
  195. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Settings/NativePtyFontPicker.tsx +0 -0
  196. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Settings/PathEditor.tsx +0 -0
  197. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Settings/PermissionModeSection.tsx +0 -0
  198. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Settings/ProfileSection.tsx +0 -0
  199. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Settings/RecapSettings.tsx +0 -0
  200. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Settings/RescanButton.tsx +0 -0
  201. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Settings/SettingsModal.tsx +0 -0
  202. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Settings/SortOrderSection.tsx +0 -0
  203. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Settings/__tests__/SettingsModal.test.tsx +0 -0
  204. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Settings/shared.tsx +0 -0
  205. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/FileExplorer.tsx +0 -0
  206. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/HelpPopup.tsx +0 -0
  207. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/NewTaskButton.tsx +0 -0
  208. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/ProjectGroup.tsx +0 -0
  209. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/SearchOverlay.tsx +0 -0
  210. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/SessionItem.tsx +0 -0
  211. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/SidebarHeader.tsx +0 -0
  212. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/__tests__/NewTaskButton.test.tsx +0 -0
  213. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/__tests__/SearchOverlay.test.tsx +0 -0
  214. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/__tests__/SessionItem.test.tsx +0 -0
  215. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/__tests__/SessionItemRename.test.tsx +0 -0
  216. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/hooks/__tests__/useRunnerStatuses.test.tsx +0 -0
  217. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Sidebar/hooks/useRunnerStatuses.ts +0 -0
  218. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/Toast.tsx +0 -0
  219. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ui/ContextMenu.tsx +0 -0
  220. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ui/DelayedSkeleton.tsx +0 -0
  221. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ui/LoadingAnimation.tsx +0 -0
  222. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/components/ui/RenameInput.tsx +0 -0
  223. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/config/ui.ts +0 -0
  224. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/constants/nativePtyFonts.ts +0 -0
  225. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/main.tsx +0 -0
  226. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/store/__tests__/toggleExplorer.test.ts +0 -0
  227. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/store/blocksState.ts +0 -0
  228. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/styles/nativePtyFonts.css +0 -0
  229. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/styles/theme.css +0 -0
  230. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/test-setup.ts +0 -0
  231. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/utils/__tests__/lazyWithRetry.test.tsx +0 -0
  232. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/utils/__tests__/sessionMeta.test.tsx +0 -0
  233. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/utils/events.ts +0 -0
  234. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/utils/fileType.ts +0 -0
  235. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/utils/formatRelative.ts +0 -0
  236. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/utils/hljs.ts +0 -0
  237. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/utils/lazyWithRetry.tsx +0 -0
  238. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/utils/nativePtyFocus.ts +0 -0
  239. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/utils/renderSnippet.tsx +0 -0
  240. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/utils/sessionMeta.ts +0 -0
  241. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/utils/timestamps.ts +0 -0
  242. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/src/utils/useDebounce.ts +0 -0
  243. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/tests/ghostty-web.pw.ts +0 -0
  244. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/tsconfig.json +0 -0
  245. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/tsconfig.node.json +0 -0
  246. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/frontend/vite.config.ts +0 -0
  247. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/scripts/capture-move-bundle.sh +0 -0
  248. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/__main__.py +0 -0
  249. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/_auth_env.py +0 -0
  250. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/_pty_preexec.py +0 -0
  251. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/analytics/__init__.py +0 -0
  252. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/analytics/aggregator.py +0 -0
  253. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/analytics/cost.py +0 -0
  254. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/analytics/extractor.py +0 -0
  255. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/analytics/fast.py +0 -0
  256. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/analytics/models.py +0 -0
  257. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/analytics/pricing.py +0 -0
  258. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/analytics/prompt.py +0 -0
  259. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/analytics/service.py +0 -0
  260. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/analytics/stats.py +0 -0
  261. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/analytics/tips.py +0 -0
  262. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/btw_capture.py +0 -0
  263. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/cli.py +0 -0
  264. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/config.py +0 -0
  265. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/drivers/__init__.py +0 -0
  266. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/editor.py +0 -0
  267. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/events_bus.py +0 -0
  268. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/locks.py +0 -0
  269. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/migrate.py +0 -0
  270. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/models.py +0 -0
  271. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/parser.py +0 -0
  272. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/providers/__init__.py +0 -0
  273. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/providers/base.py +0 -0
  274. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/providers/claude.py +0 -0
  275. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/providers/codex.py +0 -0
  276. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/providers/registry.py +0 -0
  277. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/pty_native.py +0 -0
  278. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/pty_screen_state.py +0 -0
  279. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/query_parser.py +0 -0
  280. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/recap_runner.py +0 -0
  281. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/reporter.py +0 -0
  282. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/android-chrome-192x192.png +0 -0
  283. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/android-chrome-512x512.png +0 -0
  284. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/app-icon.png +0 -0
  285. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/apple-touch-icon.png +0 -0
  286. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/assets/ChatView-BrP-ENHg.css +0 -0
  287. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/assets/fira-code-DGosTW8U.woff2 +0 -0
  288. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/assets/index-Bo2bFwpv.css +0 -0
  289. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/assets/ioskeley-mono-C7VR2-sL.woff2 +0 -0
  290. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/assets/jetbrains-mono-V6pRDFza.woff2 +0 -0
  291. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/assets/julia-mono-Cbvba6sW.woff2 +0 -0
  292. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/assets/libertinus-mono-Coz17E-O.woff2 +0 -0
  293. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/assets/monaspace-argon-RS-2TGNg.woff2 +0 -0
  294. /agent_decoder-0.3.5/src/agent_decoder/static/assets/nativePtyFonts-BaXKtUsn.js → /agent_decoder-0.3.6/src/agent_decoder/static/assets/nativePtyFonts-BxhRYj2G.js +0 -0
  295. /agent_decoder-0.3.5/src/agent_decoder/static/assets/rolldown-runtime-S-ySWqyJ.js → /agent_decoder-0.3.6/src/agent_decoder/static/assets/rolldown-runtime-QTnfLwEv.js +0 -0
  296. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/assets/source-code-pro-DEXWLoHG.woff2 +0 -0
  297. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/assets/spline-sans-mono-mUpA6Mve.woff2 +0 -0
  298. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/assets/xanh-mono-Dsg19vWS.woff2 +0 -0
  299. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/favicon-16x16.png +0 -0
  300. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/favicon-32x32.png +0 -0
  301. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/favicon.ico +0 -0
  302. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/static/site.webmanifest +0 -0
  303. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/update_check.py +0 -0
  304. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/src/agent_decoder/watcher.py +0 -0
  305. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/__init__.py +0 -0
  306. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/analytics/__init__.py +0 -0
  307. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/analytics/test_aggregator.py +0 -0
  308. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/analytics/test_cost.py +0 -0
  309. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/analytics/test_extractor.py +0 -0
  310. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/analytics/test_fast_parity.py +0 -0
  311. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/analytics/test_integration.py +0 -0
  312. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/analytics/test_pricing.py +0 -0
  313. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/analytics/test_prompt.py +0 -0
  314. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/analytics/test_service.py +0 -0
  315. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/analytics/test_stats.py +0 -0
  316. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/analytics/test_tips.py +0 -0
  317. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/conftest.py +0 -0
  318. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/drivers/__init__.py +0 -0
  319. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/drivers/fake_cli.py +0 -0
  320. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/drivers/test_tmux_driver.py +0 -0
  321. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/fixtures/aaaaaaaa-0000-0000-0000-000000000001.jsonl +0 -0
  322. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/fixtures/bad-name.jsonl +0 -0
  323. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/fixtures/bbbbbbbb-0000-0000-0000-000000000002.jsonl +0 -0
  324. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/fixtures/btw_capture/live_zai_no_open_marker.bin +0 -0
  325. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/fixtures/btw_capture/multiline.bin +0 -0
  326. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/fixtures/codex/sample_rollout.jsonl +0 -0
  327. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/fixtures/fake_claude_tui.py +0 -0
  328. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/fixtures/session_with_usage.jsonl +0 -0
  329. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/fixtures/sidechain_session.jsonl +0 -0
  330. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/fixtures/simple_session.jsonl +0 -0
  331. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/providers/__init__.py +0 -0
  332. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/providers/test_claude_adapter.py +0 -0
  333. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/providers/test_codex_adapter.py +0 -0
  334. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/providers/test_registry.py +0 -0
  335. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_active_data_sources.py +0 -0
  336. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_analytics_ignores_ephemerals.py +0 -0
  337. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_auth_env.py +0 -0
  338. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_btw_capture.py +0 -0
  339. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_cli.py +0 -0
  340. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_codex_e2e.py +0 -0
  341. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_config_legacy_migration.py +0 -0
  342. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_db.py +0 -0
  343. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_db_session_meta.py +0 -0
  344. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_delete_sessions.py +0 -0
  345. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_dev_import_path.py +0 -0
  346. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_e2e.py +0 -0
  347. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_editor.py +0 -0
  348. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_ephemeral_messages.py +0 -0
  349. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_events_bus.py +0 -0
  350. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_export_ephemeral.py +0 -0
  351. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_locks.py +0 -0
  352. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_migrate.py +0 -0
  353. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_parser.py +0 -0
  354. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_pty_native.py +0 -0
  355. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_pty_runner.py +0 -0
  356. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_pty_runner_btw.py +0 -0
  357. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_pty_screen_state.py +0 -0
  358. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_query_parser.py +0 -0
  359. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_recaps.py +0 -0
  360. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_reporter.py +0 -0
  361. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_search_ephemeral.py +0 -0
  362. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_server_open_terminal.py +0 -0
  363. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_server_session_title.py +0 -0
  364. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/tests/test_update_check.py +0 -0
  365. {agent_decoder-0.3.5 → agent_decoder-0.3.6}/uv.lock +0 -0
@@ -52,5 +52,9 @@ demo-reel/
52
52
  # Keep the built static assets — they ship with the package
53
53
  !src/agent_decoder/static/
54
54
 
55
- # Local scratch/handoff docs — never published
55
+ # Local scratch/handoff docs and agent config — never published
56
56
  docs/archive/
57
+ /.handoff/
58
+ /.internal-archive/
59
+ /.claude/
60
+ /CLAUDE.md
@@ -164,3 +164,55 @@ See [`SECURITY.md`](SECURITY.md) for the disclosure process and full scope.
164
164
  pre-warmed in the background after first paint.
165
165
  - **GZip middleware.** FastAPI compresses JSON responses over a few hundred
166
166
  bytes, which keeps session-detail payloads small on large conversations.
167
+
168
+ ### Shared interactive execution
169
+
170
+ `ExecutionManager` is the single server owner of Claude and Codex terminals.
171
+ It shares the session lock table, active Native terminal, sizing configuration,
172
+ and shutdown lifecycle. `pty_runner.PtyManager` supplies Claude's established
173
+ input policy (ownership, input acknowledgments/watchdog, `/model`, permissions,
174
+ and ephemeral `/btw` capture); `driver_manager.DriverSessionPolicy` supplies
175
+ Codex's launch/resume, state polling, and rollout adoption. Neither policy is
176
+ instantiated as another manager. The former `DriverManager` import resolves to
177
+ `ExecutionManager` for compatibility. Native input, resize, snapshot, status,
178
+ and termination routes all use the same owner. Provider capability checks still
179
+ run before focus/submit; read-only providers cannot acquire a terminal.
180
+
181
+ Both providers use the isolated `agent-decoder` tmux socket by default.
182
+ `AGENT_DECODER_TMUX_SOCKET` overrides it for isolated installations/tests.
183
+ Claude falls back to direct PTY when tmux is absent; internal consumers can
184
+ explicitly request `terminal_backend="direct"`. Codex requires tmux and retains
185
+ its runtime capability gate. Claude launch environments use a private temporary
186
+ file consumed and unlinked before exec; secrets do not appear in launch argv or
187
+ tmux metadata.
188
+
189
+ Normal server shutdown **detaches** attach clients, stops local watchdogs and
190
+ capture readers, releases server-owned Claude sidecars, and leaves tmux harnesses
191
+ running. Browser disconnect/idle does not reap tmux harnesses. Explicit kill
192
+ **terminates** the harness; kill also resolves a persistent terminal after a
193
+ server restart without spawning or first focusing it. Direct PTY cannot detach
194
+ and therefore terminates on shutdown. Claude reattachment checks session/cwd
195
+ metadata against the pane PID plus the OS process start identity and executable.
196
+ A changed process or a fresh foreign sidecar prevents adoption. Binary,
197
+ permission, or model configuration changes require a verified owned respawn;
198
+ live `/model` updates the persisted launch configuration. These checks coordinate
199
+ same-user processes, not mutually untrusted users sharing a tmux server.
200
+
201
+ Tmux redraws terminal output, which is suitable for Native hydration but changes
202
+ the escape sequences Claude's `/btw` parser needs. Claude therefore also uses
203
+ `pipe-pane` into a private Unix datagram receiver for the original byte stream.
204
+ The relay drains without waiting for the application; datagrams and receiver
205
+ work per loop turn are bounded. Sequence gaps or capture-buffer overflow fail
206
+ the pending ephemeral response instead of persisting partial text. A capture
207
+ owner token prevents an old client from stopping a replacement relay. Detach
208
+ removes the local socket and stops its relay; after a crash an orphaned relay
209
+ keeps draining/dropping until reattachment replaces it.
210
+
211
+ Limitations: pending ephemeral `/btw` output is not recovered across a server
212
+ crash/restart, and output during detach is not retained as a raw byte history;
213
+ reattachment hydrates the tmux screen while JSONL remains the content source.
214
+ A crashed process may leave a private temporary socket directory until OS temp
215
+ cleanup. Deliberately replacing/execing a managed pane into a different executable
216
+ requires explicit recovery rather than automatic ownership adoption. Real
217
+ provider auth/network smoke tests remain opt-in; behavioral tests use synthetic
218
+ CLIs and unique tmux sockets.
@@ -6,6 +6,25 @@ The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.6] - 2026-11-06
10
+
11
+ ### Added
12
+
13
+ - **Durable agent runs and a cross-provider Runs screen** (#52). Each managed execution attempt is recorded in an `agent_runs` table that is independent of the rebuildable session index; no screen or exit code is treated as proof of completion. Read it at `GET /api/runs` and `GET /api/runs/{id}`.
14
+ - **Unified Claude and Codex execution ownership** with persistent tmux terminals (#51), so both providers share one execution manager.
15
+ - **Artifacts panel** listing a session's images and written files.
16
+
17
+ ### Changed
18
+
19
+ - Share one syntax highlighter and cache rendered markdown, and toggle the artifacts panel without re-rendering the chat.
20
+ - Local agent and handoff files (`.handoff/`, `.claude/`, `CLAUDE.md`, ...) are excluded from sdist builds and gitignored.
21
+
22
+ ### Fixed
23
+
24
+ - **Periodic rescan no longer re-parses every Codex transcript each minute.** Codex rollouts are named `rollout-<timestamp>-<uuid>` but indexed under the session id inside the file, so the "has this file changed?" lookup by file name missed most of them and the server re-parsed gigabytes of unchanged transcripts on every pass (a ~14 s CPU spike every 60 s, plus `message_count` reset to 0 on large rollouts). Unchanged files are now skipped by path, including rollouts that share a session id.
25
+ - Dangling symlinks under `~/.claude/projects` are skipped quietly instead of logging a "No such file" warning on every rescan.
26
+ - Native terminal no longer snaps back to the bottom and wipes scrollback when a full-screen clear (`ESC[3J`) arrives while you are scrolled up. A real `/clear` at the bottom still clears. An ignored sequence is logged to the browser console (`agent-decoder: ignored ESC[3J …`) so the cause can be confirmed in real use.
27
+
9
28
  ## [0.3.5] - 2026-10-24
10
29
 
11
30
  ### Changed
@@ -253,7 +272,8 @@ Initial public release.
253
272
 
254
273
  - License changed from MIT to [FSL-1.1-Apache-2.0](LICENSE). All prior unreleased history was developed under MIT; the 0.1.0 release and everything after it ship under FSL-1.1-Apache-2.0, which converts to Apache 2.0 two years after each release.
255
274
 
256
- [Unreleased]: https://github.com/Comradery64/Agent-Decoder/compare/v0.3.5...HEAD
275
+ [Unreleased]: https://github.com/Comradery64/Agent-Decoder/compare/v0.3.6...HEAD
276
+ [0.3.6]: https://github.com/Comradery64/Agent-Decoder/compare/v0.3.5...v0.3.6
257
277
  [0.3.5]: https://github.com/Comradery64/Agent-Decoder/compare/v0.3.2...v0.3.5
258
278
  [0.3.2]: https://github.com/Comradery64/Agent-Decoder/compare/v0.3.1.3...v0.3.2
259
279
  [0.3.1.2]: https://github.com/Comradery64/Agent-Decoder/compare/v0.3.1.1...v0.3.1.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agent-decoder
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: A local web UI for browsing and analyzing AI coding assistant chat history
5
5
  Project-URL: Homepage, https://github.com/Comradery64/Agent-Decoder
6
6
  Project-URL: Repository, https://github.com/Comradery64/Agent-Decoder
@@ -0,0 +1,15 @@
1
+ # Managed runs
2
+
3
+ Open **Runs** in the sidebar (`#/runs`) to inspect managed Claude and Codex execution attempts. Search across session ID, directory, project, model and branch; filter by provider, state or exact project ID. Results use pages of 50. A run detail URL loads independently of the current page. Source links open the existing chat with its Decoded / Native controls; missing indexed chats retain their run history and show an unavailable-source explanation.
4
+
5
+ A run represents **one managed process attempt**, spanning multiple prompts. It is not a task or an individual conversation turn. Historical indexed sessions do not become runs until managed execution is requested. Native rollout adoption changes the session ID while keeping the attempt ID. Reattaching to a verified surviving tmux process also keeps the attempt ID. A replaced or confirmed-absent process closes the old attempt with an unknown outcome (`blocked`, with `ended_at`) and creates a fresh attempt. Terminal records cannot be updated by later observations.
6
+
7
+ States are `queued`, `starting`, `running`, `awaiting_input`, `blocked`, `completed`, `failed`, and `canceled`. Launch begins at `starting`; an attached transport remains `blocked` pending screen evidence. Successful decoded submission or native Enter records `running` with explicit input provenance. Native Enter does not prove what command was entered. Provider screen observations may indicate `awaiting_input`, `running`, or `blocked`, but are advisory: Claude's classifier defaults to idle, and native prompts are not reliable completion signals. Last-observed timestamps reflect real attach, input or screen observations, not a fabricated heartbeat. Old observations can be stale even if a transport remains attached.
8
+
9
+ Explicit termination records `canceled` after the termination operation returns. Confirmed launch failure records `failed`. A capture/attach failure with a verified surviving pane leaves an active, recoverable `blocked` attempt. Disconnect or server restart marks an attempt `blocked` with an unobserved/detached explanation. Shutdown detaches tmux attempts; direct PTY shutdown closes an unknown-outcome attempt because its process is terminated. No prompt, idle screen, process exit (including exit 0), or disconnect records successful completion. There is deliberately no “finish” operation: this slice does not infer or automate task outcomes. `completed` and `queued` are contract foundations for later explicit outcome/queue features.
10
+
11
+ The `agent_runs` SQLite table is independent of the rebuildable session index and has no cascading foreign key. Source indexing fills missing metadata while preserving recorded execution model/context. `task_id` and `parent_run_id` are explicitly nullable foundations; no task management, queue launch, or approval automation is implemented.
12
+
13
+ `GET /api/runs` returns typed `AgentRun[]`; optional `provider`, `state`, `project`, and `q` filters apply before `limit` (1–500, default 200) and `offset`. `GET /api/runs/{run_id}` returns one record or 404. Both endpoints only inspect durable records and indexed metadata; they never start providers or claim execution locks.
14
+
15
+ The execution manager writes lifecycle records before publishing `runs_changed` SSE notifications. Screen observations enter a manager-owned buffer directly from provider policy, rather than through a lossy SSE subscriber; the latest advisory observation is persisted every two seconds. The UI refreshes on confirmed-write notifications/reconnect and every 15 seconds to recover missed notifications. This is current-state history, not a complete event audit log. A partial unique index and immediate SQLite transaction prevent duplicate active attempts for a provider/session across independent store instances. Execution lifecycle operations and observation writes are serialized by the shared manager.
@@ -34,12 +34,14 @@ const settingsModalImport = () => import("./components/Settings/SettingsModal");
34
34
  // render a reload prompt in-place instead of crashing the whole ErrorBoundary.
35
35
  const Sidebar = lazyWithRetry(() => import("./components/Sidebar/Sidebar"));
36
36
  const ChatView = lazyWithRetry(chatViewImport);
37
+ const Runs = lazyWithRetry(() => import("./components/Runs"));
37
38
  const Dashboard = lazyWithRetry(() => import("./components/Dashboard/Dashboard"));
38
39
  const SettingsModal = lazyWithRetry(settingsModalImport);
39
40
  const SearchOverlay = lazyWithRetry(searchOverlayImport);
40
41
  const HelpPopup = lazyWithRetry(() => import("./components/Sidebar/HelpPopup"));
41
42
  const ShortcutsPopup = lazyWithRetry(() => import("./components/Sidebar/ShortcutsPopup"));
42
43
  const FileViewer = lazyWithRetry(() => import("./components/FileViewer/FileViewer"));
44
+ const ArtifactsPanel = lazyWithRetry(() => import("./components/ArtifactsPanel/ArtifactsPanel"));
43
45
 
44
46
  export default function App() {
45
47
  // Five individual selectors instead of `useAppStore()` without a selector —
@@ -275,6 +277,9 @@ export default function App() {
275
277
  } else if (e.key.toLowerCase() === "e" && e.metaKey) {
276
278
  e.preventDefault();
277
279
  useAppStore.getState().toggleResultsExpanded();
280
+ } else if (e.key.toLowerCase() === "a" && e.metaKey && e.shiftKey) {
281
+ e.preventDefault();
282
+ useAppStore.getState().toggleArtifacts();
278
283
  } else if (e.key.toLowerCase() === "b" && e.metaKey) {
279
284
  e.preventDefault();
280
285
  useAppStore.getState().toggleSidebar();
@@ -328,9 +333,16 @@ export default function App() {
328
333
  <Sidebar />
329
334
  <div style={{ flex: 1, display: "flex", minWidth: 200, overflow: "hidden" }}>
330
335
  <ErrorBoundary>
331
- {chatIdFromUrl ? <ChatView /> : <Dashboard />}
336
+ {route.startsWith("/runs") ? <Runs /> : chatIdFromUrl ? <ChatView /> : <Dashboard />}
332
337
  </ErrorBoundary>
333
338
  </div>
339
+ {chatIdFromUrl && (
340
+ <ErrorBoundary>
341
+ <React.Suspense fallback={null}>
342
+ <ArtifactsPanel />
343
+ </React.Suspense>
344
+ </ErrorBoundary>
345
+ )}
334
346
  {viewingFilePath && (
335
347
  <ErrorBoundary>
336
348
  <React.Suspense fallback={<DelayedSkeleton />}>
@@ -137,6 +137,8 @@ function patch<T>(path: string, body: unknown): Promise<T> {
137
137
  }
138
138
 
139
139
  export const api = {
140
+ getRuns: (params = "") => get<import("./types").AgentRun[]>(`/api/runs${params}`),
141
+ getRun: (id: string) => get<import("./types").AgentRun>(`/api/runs/${encodeURIComponent(id)}`),
140
142
  getProjects: () => get<Project[]>("/api/projects"),
141
143
  getProjectSessions: (projectId: string) =>
142
144
  get<Session[]>(`/api/projects/${encodeURIComponent(projectId)}/sessions`),
@@ -591,3 +591,25 @@ export interface DashboardData {
591
591
  total_messages: number;
592
592
  tips: DashboardTip[];
593
593
  }
594
+
595
+ /** A managed execution attempt; independent of indexed session lifetime. */
596
+ export interface AgentRun {
597
+ id: string;
598
+ provider: string;
599
+ native_session_id: string;
600
+ model: string | null;
601
+ project_id: string | null;
602
+ cwd: string | null;
603
+ git_branch: string | null;
604
+ is_worktree: boolean;
605
+ task_id: string | null;
606
+ parent_run_id: string | null;
607
+ state: "queued" | "starting" | "running" | "awaiting_input" | "blocked" | "completed" | "failed" | "canceled";
608
+ reason: string;
609
+ provenance: string;
610
+ created_at: string;
611
+ updated_at: string;
612
+ last_observed_at: string | null;
613
+ ended_at: string | null;
614
+ source_available: boolean;
615
+ }
@@ -0,0 +1,191 @@
1
+ import { useMemo, useState } from "react";
2
+ import { useAppStore } from "../../store";
3
+ import { useSessionDetail } from "../Messages/hooks/useSessionDetail";
4
+ import { ScrollContainer } from "../ScrollContainer";
5
+ import { type Artifact, type ArtifactGroup, extractArtifacts } from "../../utils/artifacts";
6
+ import { blobUrl } from "../../utils/fileType";
7
+
8
+ const WIDTH = 320;
9
+
10
+ const FILTERS: Array<{ id: ArtifactGroup | "all"; label: string }> = [
11
+ { id: "all", label: "All" },
12
+ { id: "images", label: "Images" },
13
+ { id: "docs", label: "Docs" },
14
+ { id: "media", label: "Media" },
15
+ { id: "code", label: "Code" },
16
+ ];
17
+
18
+ function basename(path: string): string {
19
+ return path.slice(path.lastIndexOf("/") + 1) || path;
20
+ }
21
+
22
+ function IconFile() {
23
+ return (
24
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
25
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
26
+ <polyline points="14 2 14 8 20 8" />
27
+ </svg>
28
+ );
29
+ }
30
+
31
+ function ArtifactCard({ artifact }: { artifact: Artifact }) {
32
+ const setViewingFilePath = useAppStore((s) => s.setViewingFilePath);
33
+ const setPendingScrollMessageId = useAppStore((s) => s.setPendingScrollMessageId);
34
+ const jump = () => setPendingScrollMessageId(artifact.messageId);
35
+ // File artifacts open in the file viewer; message images jump to their turn.
36
+ const open = artifact.kind === "file" ? () => setViewingFilePath(artifact.path) : jump;
37
+ const thumb =
38
+ artifact.kind === "image" ? artifact.src
39
+ : artifact.category === "image" ? blobUrl(artifact.path)
40
+ : null;
41
+ const label = artifact.kind === "file" ? basename(artifact.path) : "Image";
42
+
43
+ return (
44
+ <div
45
+ style={{
46
+ border: "1px solid var(--border-subtle)",
47
+ borderRadius: "var(--radius-md)",
48
+ overflow: "hidden",
49
+ background: "var(--bg-tool-block)",
50
+ }}
51
+ >
52
+ <button
53
+ type="button"
54
+ onClick={open}
55
+ title={artifact.kind === "file" ? artifact.path : "Jump to message"}
56
+ style={{
57
+ display: "flex",
58
+ alignItems: "center",
59
+ gap: "8px",
60
+ width: "100%",
61
+ padding: thumb ? 0 : "10px 12px",
62
+ background: "none",
63
+ border: "none",
64
+ cursor: "pointer",
65
+ color: "var(--text-secondary)",
66
+ textAlign: "left",
67
+ }}
68
+ >
69
+ {thumb ? (
70
+ <img
71
+ src={thumb}
72
+ alt={label}
73
+ loading="lazy"
74
+ style={{ display: "block", width: "100%", maxHeight: 360, objectFit: "contain", background: "var(--bg-base)" }}
75
+ />
76
+ ) : (
77
+ <>
78
+ <IconFile />
79
+ <span style={{ fontFamily: "var(--font-mono)", fontSize: "12px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
80
+ {label}
81
+ </span>
82
+ </>
83
+ )}
84
+ </button>
85
+ <div
86
+ style={{
87
+ display: "flex",
88
+ alignItems: "center",
89
+ justifyContent: "space-between",
90
+ gap: "8px",
91
+ padding: "4px 8px 4px 12px",
92
+ borderTop: "1px solid var(--border-subtle)",
93
+ fontSize: "11px",
94
+ color: "var(--text-tertiary)",
95
+ }}
96
+ >
97
+ <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
98
+ {thumb ? label : artifact.group}
99
+ </span>
100
+ <button
101
+ type="button"
102
+ onClick={jump}
103
+ title="Jump to message"
104
+ style={{ background: "none", border: "none", cursor: "pointer", color: "inherit", fontSize: "11px", padding: "2px 4px", flexShrink: 0 }}
105
+ >
106
+ Show in chat ↗
107
+ </button>
108
+ </div>
109
+ </div>
110
+ );
111
+ }
112
+
113
+ export default function ArtifactsPanel() {
114
+ // Open state is read here, not in App, so toggling re-renders only this
115
+ // panel instead of the whole chat.
116
+ const open = useAppStore((s) => s.artifactsOpen);
117
+ const sessionId = useAppStore((s) => s.selectedSessionId);
118
+ // Re-key per session so the filter resets when switching chats.
119
+ return open && sessionId ? <ArtifactsList key={sessionId} sessionId={sessionId} /> : null;
120
+ }
121
+
122
+ function ArtifactsList({ sessionId }: { sessionId: string }) {
123
+ const { detail } = useSessionDetail(sessionId);
124
+ const toggleArtifacts = useAppStore((s) => s.toggleArtifacts);
125
+ const [filter, setFilter] = useState<ArtifactGroup | "all">("all");
126
+ const artifacts = useMemo(() => extractArtifacts(detail?.messages ?? []), [detail?.messages]);
127
+ const present = new Set(artifacts.map((a) => a.group));
128
+ const shown = filter === "all" ? artifacts : artifacts.filter((a) => a.group === filter);
129
+
130
+ return (
131
+ <aside
132
+ aria-label="Artifacts"
133
+ style={{
134
+ width: WIDTH,
135
+ flexShrink: 0,
136
+ display: "flex",
137
+ flexDirection: "column",
138
+ borderLeft: "1px solid var(--border-subtle)",
139
+ background: "var(--bg-base)",
140
+ minHeight: 0,
141
+ }}
142
+ >
143
+ <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "12px 12px 8px 16px" }}>
144
+ <span style={{ fontSize: "13px", fontWeight: 600, color: "var(--text-primary)" }}>
145
+ Artifacts <span style={{ color: "var(--text-tertiary)", fontWeight: 400 }}>{artifacts.length}</span>
146
+ </span>
147
+ <button
148
+ type="button"
149
+ onClick={toggleArtifacts}
150
+ aria-label="Close artifacts"
151
+ title="Close artifacts"
152
+ style={{ background: "none", border: "none", cursor: "pointer", color: "var(--text-tertiary)", fontSize: "16px", padding: "0 4px" }}
153
+ >
154
+ ×
155
+ </button>
156
+ </div>
157
+ <div style={{ display: "flex", flexWrap: "wrap", gap: "4px", padding: "0 12px 8px 16px" }}>
158
+ {FILTERS.filter((f) => f.id === "all" || present.has(f.id)).map((f) => (
159
+ <button
160
+ key={f.id}
161
+ type="button"
162
+ onClick={() => setFilter(f.id)}
163
+ aria-pressed={filter === f.id}
164
+ style={{
165
+ fontSize: "11px",
166
+ padding: "3px 8px",
167
+ borderRadius: "999px",
168
+ border: "1px solid var(--border-subtle)",
169
+ cursor: "pointer",
170
+ background: filter === f.id ? "var(--bg-sidebar-hover)" : "none",
171
+ color: filter === f.id ? "var(--text-primary)" : "var(--text-tertiary)",
172
+ }}
173
+ >
174
+ {f.label}
175
+ </button>
176
+ ))}
177
+ </div>
178
+ <ScrollContainer style={{ flex: 1, minHeight: 0 }}>
179
+ <div style={{ display: "flex", flexDirection: "column", gap: "10px", padding: "4px 12px 16px 16px" }}>
180
+ {shown.length === 0 ? (
181
+ <div style={{ fontSize: "12px", color: "var(--text-tertiary)", padding: "24px 0", textAlign: "center" }}>
182
+ {detail ? "No artifacts in this session yet." : "Loading…"}
183
+ </div>
184
+ ) : (
185
+ shown.map((a) => <ArtifactCard key={a.key} artifact={a} />)
186
+ )}
187
+ </div>
188
+ </ScrollContainer>
189
+ </aside>
190
+ );
191
+ }
@@ -3,7 +3,7 @@ import type { PtyOwnership, Session } from "../../api/types";
3
3
  import { api } from "../../api/client";
4
4
  import { useAppStore } from "../../store";
5
5
  import { emit } from "../../utils/events";
6
- import { IconTerminal } from "../ui/icons";
6
+ import { IconGallery, IconTerminal } from "../ui/icons";
7
7
 
8
8
  // Small status dot for the Phase-0 ownership badge. Inline-rendered
9
9
  // next to the model label so it doesn't disturb existing header layout.
@@ -98,6 +98,8 @@ export function ConversationHeader({
98
98
  const dropdownRef = useRef<HTMLDivElement>(null);
99
99
  const remoteClient = useAppStore((s) => s.hostInfo?.is_remote_client === true);
100
100
  const terminalDisabled = !session || session.is_fork || remoteClient;
101
+ const artifactsOpen = useAppStore((s) => s.artifactsOpen);
102
+ const toggleArtifacts = useAppStore((s) => s.toggleArtifacts);
101
103
  const terminalTitle = session?.is_fork
102
104
  ? "Open in terminal (fork — not resumable)"
103
105
  : remoteClient
@@ -286,6 +288,24 @@ export function ConversationHeader({
286
288
  </span>
287
289
  )}
288
290
 
291
+ <button
292
+ onClick={toggleArtifacts}
293
+ title="Artifacts (⇧⌘A)"
294
+ aria-label="Toggle artifacts panel"
295
+ aria-pressed={artifactsOpen}
296
+ style={{ ...btnBase, color: artifactsOpen ? "var(--text-primary)" : "var(--text-tertiary)" }}
297
+ onMouseEnter={(e) => {
298
+ (e.currentTarget as HTMLButtonElement).style.color = "var(--text-primary)";
299
+ (e.currentTarget as HTMLButtonElement).style.background = "var(--bg-sidebar-hover)";
300
+ }}
301
+ onMouseLeave={(e) => {
302
+ (e.currentTarget as HTMLButtonElement).style.color = artifactsOpen ? "var(--text-primary)" : "var(--text-tertiary)";
303
+ (e.currentTarget as HTMLButtonElement).style.background = "none";
304
+ }}
305
+ >
306
+ <IconGallery />
307
+ </button>
308
+
289
309
  {/* Export dropdown */}
290
310
  {session && (
291
311
  <div ref={dropdownRef} style={{ position: "relative" }}>
@@ -74,6 +74,8 @@ const { fitDimensions, configSource, fitAddonInstances, terminalInstances, MockF
74
74
  // xterm exposes a unicode service; xtermTerminal.ts sets activeVersion and
75
75
  // the Unicode11 addon registers a provider against it.
76
76
  unicode = { activeVersion: "6", register: vi.fn() };
77
+ // createNativeTerminal registers a CSI handler (ESC[3J guard) on the parser.
78
+ parser = { registerCsiHandler: vi.fn() };
77
79
 
78
80
  constructor(options: Record<string, unknown>) {
79
81
  this.options = options;
@@ -0,0 +1,64 @@
1
+ // @vitest-environment node
2
+ import { describe, it, expect } from "vitest";
3
+ import { createNativeTerminal, type NativeTerminalOptions } from "../xtermTerminal";
4
+
5
+ const w = (t: ReturnType<typeof createNativeTerminal>, d: string) =>
6
+ new Promise<void>((r) => t.write(d, () => r()));
7
+
8
+ async function scrolledTerminal() {
9
+ const t = createNativeTerminal({
10
+ cols: 120,
11
+ rows: 30,
12
+ scrollback: 5000,
13
+ allowProposedApi: true,
14
+ } as NativeTerminalOptions);
15
+ let s = "";
16
+ for (let i = 0; i < 400; i++) s += `line ${i}\r\n`;
17
+ await w(t, s);
18
+ return t;
19
+ }
20
+ const pos = (t: ReturnType<typeof createNativeTerminal>) => {
21
+ const b = t.buffer.active;
22
+ return { y: b.viewportY, base: b.baseY, scrollback: b.length - t.rows };
23
+ };
24
+
25
+ // Regression: xterm honors ESC[3J (erase scrollback) by wiping history AND
26
+ // resetting the viewport to the bottom. A TUI that emits a full clear on a tall
27
+ // repaint therefore yanks a reader who scrolled up back down "a few moments"
28
+ // later and destroys what they were reading.
29
+ describe("ESC[3J (erase scrollback) while the reader is scrolled up", () => {
30
+ it("is ignored: position and scrollback are preserved", async () => {
31
+ const t = await scrolledTerminal();
32
+ t.scrollLines(-100);
33
+ const before = pos(t);
34
+ await w(t, "\x1b[2J\x1b[3J\x1b[Hrepaint");
35
+ const after = pos(t);
36
+ expect(after.y).toBe(before.y);
37
+ expect(after.scrollback).toBeGreaterThanOrEqual(before.scrollback);
38
+ expect(after.y).toBeLessThan(after.base); // still not at the bottom
39
+ });
40
+
41
+ it("is ignored even when the sequence is split across two writes", async () => {
42
+ const t = await scrolledTerminal();
43
+ t.scrollLines(-100);
44
+ const before = pos(t);
45
+ await w(t, "\x1b[3");
46
+ await w(t, "Jrepaint");
47
+ expect(pos(t).y).toBe(before.y);
48
+ expect(pos(t).scrollback).toBeGreaterThanOrEqual(before.scrollback);
49
+ });
50
+
51
+ it("still clears scrollback when the reader is at the bottom (faithful /clear)", async () => {
52
+ const t = await scrolledTerminal(); // at the bottom: never scrolled up
53
+ await w(t, "\x1b[2J\x1b[3J\x1b[Hfresh");
54
+ expect(pos(t).scrollback).toBe(0);
55
+ });
56
+
57
+ it("does not affect other erase sequences", async () => {
58
+ const t = await scrolledTerminal();
59
+ t.scrollLines(-100);
60
+ const before = pos(t);
61
+ await w(t, "\x1b[2J\x1b[Hrepaint");
62
+ expect(pos(t).y).toBe(before.y);
63
+ });
64
+ });
@@ -10,7 +10,23 @@ export type NativeTerminalOptions = ITerminalOptions;
10
10
  export type { ITheme };
11
11
 
12
12
  export function createNativeTerminal(options: NativeTerminalOptions): NativeTerminal {
13
- return new Terminal(options);
13
+ const terminal = new Terminal(options);
14
+ // ESC[3J ("erase scrollback") makes xterm wipe the history AND reset the
15
+ // viewport to the bottom. A TUI that does a full clear on a tall repaint
16
+ // would otherwise yank a reader who scrolled up back down a few moments
17
+ // later and destroy what they were reading. Swallow it only while scrolled
18
+ // up; at the bottom it passes through, so a real /clear still clears. Using
19
+ // the parser hook (not a byte filter) means sequences split across PTY
20
+ // chunks are handled by xterm itself.
21
+ terminal.parser.registerCsiHandler({ final: "J" }, (params) => {
22
+ if (params[0] !== 3) return false;
23
+ const b = terminal.buffer.active;
24
+ const scrolledUp = b.viewportY < b.baseY;
25
+ // Diagnostic (no content): confirms in DevTools that the TUI really sends it.
26
+ if (scrolledUp) console.info("agent-decoder: ignored ESC[3J (erase scrollback) while scrolled up");
27
+ return scrolledUp; // true = handled (swallowed)
28
+ });
29
+ return terminal;
14
30
  }
15
31
 
16
32
  export function createNativeFitAddon(): NativeFitAddon {
@@ -226,19 +226,41 @@ interface TextBlockProps {
226
226
  style?: CSSProperties;
227
227
  }
228
228
 
229
+ // One highlighter shared by every block. rehype-highlight builds a lowlight
230
+ // instance (registering ~37 languages) each time its attacher runs, and
231
+ // react-markdown runs attachers on every render — that alone cost ~0.5s per
232
+ // session switch on long chats.
233
+ const highlight = rehypeHighlight({ detect: true });
234
+ const remarkPlugins = [remarkGfm];
235
+ const rehypePlugins = [() => highlight];
236
+
237
+ // Rendered markdown keyed by source text, so re-renders and switching back to
238
+ // a session skip parsing + highlighting. ReactMarkdown (sync) is a pure
239
+ // function, so its output is safe to reuse. Map order gives LRU eviction.
240
+ const MAX_CACHED = 3000;
241
+ const renderedByText = new Map<string, ReactElement>();
242
+
243
+ function renderMarkdown(text: string): ReactElement {
244
+ let el = renderedByText.get(text);
245
+ if (el) {
246
+ renderedByText.delete(text);
247
+ } else {
248
+ el = ReactMarkdown({ remarkPlugins, rehypePlugins, components, children: text });
249
+ if (renderedByText.size >= MAX_CACHED) {
250
+ renderedByText.delete(renderedByText.keys().next().value!);
251
+ }
252
+ }
253
+ renderedByText.set(text, el);
254
+ return el;
255
+ }
256
+
229
257
  export function TextBlock({ text, className, style }: TextBlockProps) {
230
258
  return (
231
259
  <div
232
260
  className={["prose-content", className].filter(Boolean).join(" ")}
233
261
  style={style}
234
262
  >
235
- <ReactMarkdown
236
- remarkPlugins={[remarkGfm]}
237
- rehypePlugins={[[rehypeHighlight, { detect: true, ignoreMissing: true }]]}
238
- components={components}
239
- >
240
- {text}
241
- </ReactMarkdown>
263
+ {renderMarkdown(text)}
242
264
  </div>
243
265
  );
244
266
  }
@@ -0,0 +1,26 @@
1
+ .runs-page { padding: 28px; overflow: auto; flex: 1; min-width: 0; color: var(--text-primary); font-family: var(--font-ui); }
2
+ .runs-page header, .runs-filters, .runs-list, .runs-pagination, .run-detail { max-width: 1100px; margin-inline: auto; }
3
+ .runs-page header p, .run-muted, .runs-page small, .run-detail dt { color: var(--text-secondary); }
4
+ .runs-page h1 { font-size: 26px; margin-top: 0; }
5
+ .runs-page h2 { font-size: 18px; margin: 0; }
6
+ .runs-page p { line-height: 1.5; }
7
+ .runs-page a { color: var(--text-accent); text-underline-offset: 3px; }
8
+ .runs-filters { display: flex; align-items: end; gap: 12px; flex-wrap: wrap; margin-block: 24px; }
9
+ .runs-filters label { display: grid; gap: 6px; flex: 1 1 140px; font-size: 12px; }
10
+ .runs-page input, .runs-page select, .runs-page button { font: inherit; color: var(--text-primary); background: var(--bg-input); border: 1px solid var(--border-default); border-radius: var(--radius-sm); padding: 8px 10px; min-height: 36px; min-width: 0; }
11
+ .runs-page input, .runs-page select { width: 100%; box-sizing: border-box; }
12
+ .runs-page button { cursor: pointer; }
13
+ .runs-page button:hover:not(:disabled) { background: var(--bg-sidebar-hover); }
14
+ .runs-page button:disabled { opacity: .45; cursor: default; }
15
+ .runs-page :focus-visible { outline: 2px solid var(--border-accent); outline-offset: 2px; }
16
+ .runs-list { display: grid; gap: 12px; }
17
+ .run-card { border: 1px solid var(--border-default); border-radius: var(--radius-md); padding: 18px; overflow-wrap: anywhere; background: var(--bg-base); }
18
+ .run-card-title { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; }
19
+ .run-state { font-size: 12px; border: 1px solid var(--border-default); padding: 3px 8px; border-radius: 20px; }
20
+ .run-state-blocked, .run-state-failed { color: var(--tool-error-text); }
21
+ .run-detail { box-sizing: border-box; margin-bottom: 20px; }
22
+ .run-detail dl { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px 24px; }
23
+ .run-detail dt { font-size: 12px; margin-bottom: 4px; }
24
+ .run-detail dd { margin: 0; font-size: 13px; }
25
+ .runs-pagination { display: flex; align-items: center; justify-content: center; gap: 16px; padding-block: 20px; }
26
+ @media (max-width: 600px) { .runs-page { padding: 16px; } .run-card { padding: 14px; } .run-detail dl { grid-template-columns: 1fr; } }