@aion0/forge 0.5.26 → 0.5.27

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 (253) hide show
  1. package/.forge/worktrees/pipeline-4dd8dc2d/CLAUDE.md +86 -0
  2. package/.forge/worktrees/pipeline-4dd8dc2d/README.md +136 -0
  3. package/.forge/worktrees/pipeline-4dd8dc2d/RELEASE_NOTES.md +36 -0
  4. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/agents/route.ts +17 -0
  5. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/[...nextauth]/route.ts +3 -0
  6. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/verify/route.ts +46 -0
  7. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/route.ts +31 -0
  8. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/stream/route.ts +63 -0
  9. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/route.ts +28 -0
  10. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/entries/route.ts +23 -0
  11. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/live/route.ts +72 -0
  12. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/route.ts +37 -0
  13. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/sync/route.ts +17 -0
  14. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-templates/route.ts +145 -0
  15. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/code/route.ts +299 -0
  16. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/[id]/route.ts +62 -0
  17. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/route.ts +40 -0
  18. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/detect-cli/route.ts +46 -0
  19. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/route.ts +176 -0
  20. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/sessions/route.ts +54 -0
  21. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/favorites/route.ts +26 -0
  22. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/route.ts +6 -0
  23. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/run/route.ts +19 -0
  24. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/git/route.ts +149 -0
  25. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/help/route.ts +84 -0
  26. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/issue-scanner/route.ts +116 -0
  27. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/logs/route.ts +100 -0
  28. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/mobile-chat/route.ts +115 -0
  29. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/monitor/route.ts +74 -0
  30. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notifications/route.ts +42 -0
  31. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notify/test/route.ts +33 -0
  32. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/online/route.ts +40 -0
  33. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/[id]/route.ts +41 -0
  34. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/route.ts +90 -0
  35. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/plugins/route.ts +75 -0
  36. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/[...path]/route.ts +64 -0
  37. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/route.ts +156 -0
  38. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-pipelines/route.ts +91 -0
  39. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-sessions/route.ts +61 -0
  40. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/projects/route.ts +26 -0
  41. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/chat/route.ts +64 -0
  42. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/messages/route.ts +9 -0
  43. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/route.ts +17 -0
  44. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/route.ts +20 -0
  45. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/settings/route.ts +64 -0
  46. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/local/route.ts +228 -0
  47. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/route.ts +182 -0
  48. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/smith-templates/route.ts +81 -0
  49. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/status/route.ts +12 -0
  50. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tabs/route.ts +25 -0
  51. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/route.ts +51 -0
  52. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/stream/route.ts +77 -0
  53. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/link/route.ts +37 -0
  54. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/route.ts +44 -0
  55. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/session/route.ts +14 -0
  56. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/telegram/route.ts +23 -0
  57. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/templates/route.ts +6 -0
  58. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-bell/route.ts +39 -0
  59. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-cwd/route.ts +19 -0
  60. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-state/route.ts +15 -0
  61. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tunnel/route.ts +26 -0
  62. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/upgrade/route.ts +43 -0
  63. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/usage/route.ts +20 -0
  64. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/version/route.ts +78 -0
  65. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/watchers/route.ts +33 -0
  66. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/agents/route.ts +35 -0
  67. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/memory/route.ts +23 -0
  68. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/smith/route.ts +22 -0
  69. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/stream/route.ts +31 -0
  70. package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/route.ts +79 -0
  71. package/.forge/worktrees/pipeline-4dd8dc2d/app/global-error.tsx +21 -0
  72. package/.forge/worktrees/pipeline-4dd8dc2d/app/globals.css +52 -0
  73. package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.ico +0 -0
  74. package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.png +0 -0
  75. package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.svg +106 -0
  76. package/.forge/worktrees/pipeline-4dd8dc2d/app/layout.tsx +17 -0
  77. package/.forge/worktrees/pipeline-4dd8dc2d/app/login/LoginForm.tsx +96 -0
  78. package/.forge/worktrees/pipeline-4dd8dc2d/app/login/page.tsx +10 -0
  79. package/.forge/worktrees/pipeline-4dd8dc2d/app/mobile/page.tsx +10 -0
  80. package/.forge/worktrees/pipeline-4dd8dc2d/app/page.tsx +22 -0
  81. package/.forge/worktrees/pipeline-4dd8dc2d/bin/forge-server.mjs +484 -0
  82. package/.forge/worktrees/pipeline-4dd8dc2d/check-forge-status.sh +71 -0
  83. package/.forge/worktrees/pipeline-4dd8dc2d/cli/mw.ts +579 -0
  84. package/.forge/worktrees/pipeline-4dd8dc2d/components/BrowserPanel.tsx +175 -0
  85. package/.forge/worktrees/pipeline-4dd8dc2d/components/ChatPanel.tsx +191 -0
  86. package/.forge/worktrees/pipeline-4dd8dc2d/components/ClaudeTerminal.tsx +267 -0
  87. package/.forge/worktrees/pipeline-4dd8dc2d/components/CodeViewer.tsx +787 -0
  88. package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationEditor.tsx +411 -0
  89. package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationGraphView.tsx +347 -0
  90. package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationTerminalView.tsx +303 -0
  91. package/.forge/worktrees/pipeline-4dd8dc2d/components/Dashboard.tsx +807 -0
  92. package/.forge/worktrees/pipeline-4dd8dc2d/components/DashboardWrapper.tsx +9 -0
  93. package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryFlowEditor.tsx +491 -0
  94. package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryList.tsx +230 -0
  95. package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryWorkspace.tsx +589 -0
  96. package/.forge/worktrees/pipeline-4dd8dc2d/components/DocTerminal.tsx +187 -0
  97. package/.forge/worktrees/pipeline-4dd8dc2d/components/DocsViewer.tsx +574 -0
  98. package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpDialog.tsx +169 -0
  99. package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpTerminal.tsx +141 -0
  100. package/.forge/worktrees/pipeline-4dd8dc2d/components/InlinePipelineView.tsx +111 -0
  101. package/.forge/worktrees/pipeline-4dd8dc2d/components/LogViewer.tsx +194 -0
  102. package/.forge/worktrees/pipeline-4dd8dc2d/components/MarkdownContent.tsx +73 -0
  103. package/.forge/worktrees/pipeline-4dd8dc2d/components/MobileView.tsx +385 -0
  104. package/.forge/worktrees/pipeline-4dd8dc2d/components/MonitorPanel.tsx +122 -0
  105. package/.forge/worktrees/pipeline-4dd8dc2d/components/NewSessionModal.tsx +93 -0
  106. package/.forge/worktrees/pipeline-4dd8dc2d/components/NewTaskModal.tsx +492 -0
  107. package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineEditor.tsx +570 -0
  108. package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineView.tsx +1018 -0
  109. package/.forge/worktrees/pipeline-4dd8dc2d/components/PluginsPanel.tsx +472 -0
  110. package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectDetail.tsx +1618 -0
  111. package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectList.tsx +108 -0
  112. package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectManager.tsx +401 -0
  113. package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionList.tsx +74 -0
  114. package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionView.tsx +726 -0
  115. package/.forge/worktrees/pipeline-4dd8dc2d/components/SettingsModal.tsx +1647 -0
  116. package/.forge/worktrees/pipeline-4dd8dc2d/components/SkillsPanel.tsx +969 -0
  117. package/.forge/worktrees/pipeline-4dd8dc2d/components/StatusBar.tsx +99 -0
  118. package/.forge/worktrees/pipeline-4dd8dc2d/components/TabBar.tsx +46 -0
  119. package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskBoard.tsx +113 -0
  120. package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskDetail.tsx +372 -0
  121. package/.forge/worktrees/pipeline-4dd8dc2d/components/TerminalLauncher.tsx +398 -0
  122. package/.forge/worktrees/pipeline-4dd8dc2d/components/TunnelToggle.tsx +206 -0
  123. package/.forge/worktrees/pipeline-4dd8dc2d/components/UsagePanel.tsx +207 -0
  124. package/.forge/worktrees/pipeline-4dd8dc2d/components/WebTerminal.tsx +1743 -0
  125. package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceTree.tsx +221 -0
  126. package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceView.tsx +4048 -0
  127. package/.forge/worktrees/pipeline-4dd8dc2d/dev-test.sh +5 -0
  128. package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Memory_Layer_Design.docx +0 -0
  129. package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Strategy_Research_2026.docx +0 -0
  130. package/.forge/worktrees/pipeline-4dd8dc2d/docs/LOCAL-DEPLOY.md +144 -0
  131. package/.forge/worktrees/pipeline-4dd8dc2d/docs/roadmap-multi-agent-workflow.md +330 -0
  132. package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.png +0 -0
  133. package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.svg +106 -0
  134. package/.forge/worktrees/pipeline-4dd8dc2d/hooks/useSidebarResize.ts +52 -0
  135. package/.forge/worktrees/pipeline-4dd8dc2d/install.sh +29 -0
  136. package/.forge/worktrees/pipeline-4dd8dc2d/instrumentation.ts +35 -0
  137. package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/claude-adapter.ts +104 -0
  138. package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/generic-adapter.ts +64 -0
  139. package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/index.ts +245 -0
  140. package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/types.ts +70 -0
  141. package/.forge/worktrees/pipeline-4dd8dc2d/lib/artifacts.ts +106 -0
  142. package/.forge/worktrees/pipeline-4dd8dc2d/lib/auth.ts +62 -0
  143. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/docker.yaml +70 -0
  144. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/http.yaml +66 -0
  145. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/jenkins.yaml +92 -0
  146. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/llm-vision.yaml +85 -0
  147. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/playwright.yaml +111 -0
  148. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/shell-command.yaml +60 -0
  149. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/slack.yaml +48 -0
  150. package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/webhook.yaml +56 -0
  151. package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-process.ts +361 -0
  152. package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-sessions.ts +266 -0
  153. package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-templates.ts +227 -0
  154. package/.forge/worktrees/pipeline-4dd8dc2d/lib/cloudflared.ts +424 -0
  155. package/.forge/worktrees/pipeline-4dd8dc2d/lib/crypto.ts +67 -0
  156. package/.forge/worktrees/pipeline-4dd8dc2d/lib/delivery.ts +787 -0
  157. package/.forge/worktrees/pipeline-4dd8dc2d/lib/dirs.ts +99 -0
  158. package/.forge/worktrees/pipeline-4dd8dc2d/lib/flows.ts +86 -0
  159. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-mcp-server.ts +732 -0
  160. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-inbox.md +38 -0
  161. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-send.md +47 -0
  162. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-status.md +32 -0
  163. package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-workspace-sync.md +37 -0
  164. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/00-overview.md +40 -0
  165. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/01-settings.md +194 -0
  166. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/02-telegram.md +41 -0
  167. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/03-tunnel.md +31 -0
  168. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/04-tasks.md +52 -0
  169. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/05-pipelines.md +460 -0
  170. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/06-skills.md +43 -0
  171. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/07-projects.md +73 -0
  172. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/08-rules.md +53 -0
  173. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/09-issue-autofix.md +55 -0
  174. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/10-troubleshooting.md +89 -0
  175. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/11-workspace.md +810 -0
  176. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/CLAUDE.md +62 -0
  177. package/.forge/worktrees/pipeline-4dd8dc2d/lib/init.ts +266 -0
  178. package/.forge/worktrees/pipeline-4dd8dc2d/lib/issue-scanner.ts +298 -0
  179. package/.forge/worktrees/pipeline-4dd8dc2d/lib/logger.ts +79 -0
  180. package/.forge/worktrees/pipeline-4dd8dc2d/lib/notifications.ts +75 -0
  181. package/.forge/worktrees/pipeline-4dd8dc2d/lib/notify.ts +108 -0
  182. package/.forge/worktrees/pipeline-4dd8dc2d/lib/password.ts +97 -0
  183. package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline-scheduler.ts +373 -0
  184. package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline.ts +1565 -0
  185. package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/executor.ts +347 -0
  186. package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/registry.ts +228 -0
  187. package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/types.ts +103 -0
  188. package/.forge/worktrees/pipeline-4dd8dc2d/lib/project-sessions.ts +53 -0
  189. package/.forge/worktrees/pipeline-4dd8dc2d/lib/projects.ts +86 -0
  190. package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-manager.ts +156 -0
  191. package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-utils.ts +53 -0
  192. package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-watcher.ts +345 -0
  193. package/.forge/worktrees/pipeline-4dd8dc2d/lib/settings.ts +195 -0
  194. package/.forge/worktrees/pipeline-4dd8dc2d/lib/skills.ts +458 -0
  195. package/.forge/worktrees/pipeline-4dd8dc2d/lib/task-manager.ts +951 -0
  196. package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-bot.ts +1477 -0
  197. package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-standalone.ts +83 -0
  198. package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-server.ts +70 -0
  199. package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-standalone.ts +438 -0
  200. package/.forge/worktrees/pipeline-4dd8dc2d/lib/usage-scanner.ts +249 -0
  201. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/state-machine.test.ts +388 -0
  202. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/workspace.test.ts +311 -0
  203. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-bus.ts +416 -0
  204. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-worker.ts +655 -0
  205. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/api-backend.ts +262 -0
  206. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/cli-backend.ts +491 -0
  207. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/index.ts +84 -0
  208. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/manager.ts +136 -0
  209. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/orchestrator.ts +3415 -0
  210. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/persistence.ts +309 -0
  211. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/presets.ts +649 -0
  212. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/requests.ts +287 -0
  213. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/session-monitor.ts +240 -0
  214. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/skill-installer.ts +275 -0
  215. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/smith-memory.ts +498 -0
  216. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/types.ts +241 -0
  217. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/watch-manager.ts +560 -0
  218. package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace-standalone.ts +978 -0
  219. package/.forge/worktrees/pipeline-4dd8dc2d/middleware.ts +51 -0
  220. package/.forge/worktrees/pipeline-4dd8dc2d/next.config.ts +26 -0
  221. package/.forge/worktrees/pipeline-4dd8dc2d/package.json +74 -0
  222. package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-lock.yaml +3719 -0
  223. package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-workspace.yaml +1 -0
  224. package/.forge/worktrees/pipeline-4dd8dc2d/postcss.config.mjs +7 -0
  225. package/.forge/worktrees/pipeline-4dd8dc2d/publish.sh +133 -0
  226. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/README.md +66 -0
  227. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/results/.gitignore +2 -0
  228. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/run.ts +635 -0
  229. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/task.md +26 -0
  230. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/validator.sh +46 -0
  231. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/setup.sh +19 -0
  232. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/task.md +48 -0
  233. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/validator.sh +69 -0
  234. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/setup.sh +82 -0
  235. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/task.md +30 -0
  236. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/validator.sh +29 -0
  237. package/.forge/worktrees/pipeline-4dd8dc2d/scripts/verify-usage.ts +178 -0
  238. package/.forge/worktrees/pipeline-4dd8dc2d/src/config/index.ts +129 -0
  239. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/db/database.ts +259 -0
  240. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/memory/strategy.ts +32 -0
  241. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/chat.ts +65 -0
  242. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/registry.ts +60 -0
  243. package/.forge/worktrees/pipeline-4dd8dc2d/src/core/session/manager.ts +190 -0
  244. package/.forge/worktrees/pipeline-4dd8dc2d/src/types/index.ts +129 -0
  245. package/.forge/worktrees/pipeline-4dd8dc2d/start.sh +32 -0
  246. package/.forge/worktrees/pipeline-4dd8dc2d/templates/smith-lead.json +45 -0
  247. package/.forge/worktrees/pipeline-4dd8dc2d/tsconfig.json +42 -0
  248. package/RELEASE_NOTES.md +11 -28
  249. package/app/api/terminal-bell/route.ts +6 -2
  250. package/components/WebTerminal.tsx +36 -2
  251. package/lib/terminal-standalone.ts +19 -2
  252. package/next-env.d.ts +1 -1
  253. package/package.json +1 -1
@@ -0,0 +1,726 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useRef, useCallback } from 'react';
4
+ import { useSidebarResize } from '@/hooks/useSidebarResize';
5
+ import MarkdownContent from './MarkdownContent';
6
+
7
+ interface SessionEntry {
8
+ type: 'user' | 'assistant_text' | 'tool_use' | 'tool_result' | 'thinking' | 'system';
9
+ content: string;
10
+ toolName?: string;
11
+ model?: string;
12
+ timestamp?: string;
13
+ }
14
+
15
+ interface ClaudeSessionInfo {
16
+ sessionId: string;
17
+ summary?: string;
18
+ firstPrompt?: string;
19
+ messageCount?: number;
20
+ created?: string;
21
+ modified?: string;
22
+ gitBranch?: string;
23
+ fileSize: number;
24
+ }
25
+
26
+ interface Watcher {
27
+ id: string;
28
+ projectName: string;
29
+ sessionId: string | null;
30
+ label: string | null;
31
+ checkInterval: number;
32
+ active: boolean;
33
+ createdAt: string;
34
+ }
35
+
36
+
37
+ export default function SessionView({
38
+ projectName,
39
+ projects,
40
+ onOpenInTerminal,
41
+ singleProject,
42
+ }: {
43
+ projectName?: string;
44
+ projects: { name: string; path: string; language: string | null }[];
45
+ onOpenInTerminal?: (sessionId: string, projectPath: string) => void;
46
+ singleProject?: boolean; // hide project tree sidebar, show only the given project
47
+ }) {
48
+ const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 288, minWidth: 160, maxWidth: 480 });
49
+ // Tree data: project → sessions
50
+ const [sessionTree, setSessionTree] = useState<Record<string, ClaudeSessionInfo[]>>({});
51
+ const [expandedProjects, setExpandedProjects] = useState<Set<string>>(new Set());
52
+ const [selectedProject, setSelectedProject] = useState(projectName || '');
53
+ const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
54
+ const [entries, setEntries] = useState<SessionEntry[]>([]);
55
+ const [expandedTools, setExpandedTools] = useState<Set<number>>(new Set());
56
+ const [syncing, setSyncing] = useState(false);
57
+ const [watchers, setWatchers] = useState<Watcher[]>([]);
58
+ const [batchMode, setBatchMode] = useState(false);
59
+ const [selectedIds, setSelectedIds] = useState<Map<string, Set<string>>>(new Map());
60
+ const bottomRef = useRef<HTMLDivElement>(null);
61
+ // Project-level fixed session binding
62
+ const [boundSessions, setBoundSessions] = useState<Record<string, { sessionId: string }>>({});
63
+
64
+ // Load cached sessions tree
65
+ const loadTree = useCallback(async (force = false) => {
66
+ setSyncing(true);
67
+ try {
68
+ if (force) {
69
+ const res = await fetch('/api/claude-sessions/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' });
70
+ const data = await res.json();
71
+ setSessionTree(data.sessions);
72
+ } else {
73
+ const res = await fetch('/api/claude-sessions/sync');
74
+ const data = await res.json();
75
+ setSessionTree(data);
76
+ }
77
+ } catch {}
78
+ setSyncing(false);
79
+ }, []);
80
+
81
+ // Load watchers
82
+ const loadWatchers = useCallback(async () => {
83
+ try {
84
+ const res = await fetch('/api/watchers');
85
+ setWatchers(await res.json());
86
+ } catch {}
87
+ }, []);
88
+
89
+ // Load project-level fixed session bindings
90
+ const loadBoundSessions = useCallback(async () => {
91
+ try {
92
+ const res = await fetch('/api/project-sessions');
93
+ const all = await res.json(); // { "/path/to/project": "session-uuid", ... }
94
+ const bound: Record<string, { sessionId: string }> = {};
95
+ for (const p of projects) {
96
+ if (all[p.path]) {
97
+ bound[p.name] = { sessionId: all[p.path] };
98
+ }
99
+ }
100
+ setBoundSessions(bound);
101
+ } catch {}
102
+ }, [projects]);
103
+
104
+ useEffect(() => {
105
+ // In single-project mode: load cached first (fast), then sync in background
106
+ if (singleProject) {
107
+ loadTree(false).then(() => loadTree(true));
108
+ } else {
109
+ loadTree(true);
110
+ }
111
+ loadWatchers();
112
+ // Defer bound session loading to avoid blocking initial render
113
+ const t = setTimeout(loadBoundSessions, 500);
114
+ return () => clearTimeout(t);
115
+ }, [loadTree, loadWatchers, loadBoundSessions, singleProject]);
116
+
117
+ // Auto-expand project if only one or if pre-selected
118
+ useEffect(() => {
119
+ const projectNames = Object.keys(sessionTree);
120
+ if (projectName && sessionTree[projectName]) {
121
+ setExpandedProjects(new Set([projectName]));
122
+ } else if (projectNames.length === 1) {
123
+ setExpandedProjects(new Set([projectNames[0]]));
124
+ }
125
+ }, [sessionTree, projectName]);
126
+
127
+ // SSE live stream
128
+ useEffect(() => {
129
+ if (!selectedProject || !activeSessionId) return;
130
+
131
+ setEntries([]);
132
+ const es = new EventSource(
133
+ `/api/claude-sessions/${encodeURIComponent(selectedProject)}/live?sessionId=${activeSessionId}`
134
+ );
135
+
136
+ es.onmessage = (event) => {
137
+ try {
138
+ const data = JSON.parse(event.data);
139
+ if (data.type === 'init') setEntries(data.entries);
140
+ else if (data.type === 'update') setEntries(prev => [...prev, ...data.entries]);
141
+ } catch {}
142
+ };
143
+
144
+ es.onerror = () => es.close();
145
+ return () => es.close();
146
+ }, [selectedProject, activeSessionId]);
147
+
148
+ // Auto-scroll
149
+ useEffect(() => {
150
+ bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
151
+ }, [entries]);
152
+
153
+ const toggleProject = (name: string) => {
154
+ setExpandedProjects(prev => {
155
+ const next = new Set(prev);
156
+ next.has(name) ? next.delete(name) : next.add(name);
157
+ return next;
158
+ });
159
+ };
160
+
161
+ const selectSession = (project: string, sessionId: string) => {
162
+ setSelectedProject(project);
163
+ setActiveSessionId(sessionId);
164
+ setEntries([]);
165
+ setExpandedTools(new Set());
166
+ };
167
+
168
+ const toggleTool = (i: number) => {
169
+ setExpandedTools(prev => {
170
+ const next = new Set(prev);
171
+ next.has(i) ? next.delete(i) : next.add(i);
172
+ return next;
173
+ });
174
+ };
175
+
176
+ const addWatcher = async (project: string, sessionId?: string) => {
177
+ await fetch('/api/watchers', {
178
+ method: 'POST',
179
+ headers: { 'Content-Type': 'application/json' },
180
+ body: JSON.stringify({ projectName: project, sessionId, label: sessionId ? `${project}/${sessionId.slice(0, 8)}` : project }),
181
+ });
182
+ loadWatchers();
183
+ };
184
+
185
+ const removeWatcher = async (id: string) => {
186
+ await fetch('/api/watchers', {
187
+ method: 'POST',
188
+ headers: { 'Content-Type': 'application/json' },
189
+ body: JSON.stringify({ action: 'delete', id }),
190
+ });
191
+ loadWatchers();
192
+ };
193
+
194
+ const deleteSessionById = async (project: string, sessionId: string) => {
195
+ if (!confirm(`Delete session ${sessionId.slice(0, 8)}? This cannot be undone.`)) return;
196
+ await fetch(`/api/claude-sessions/${encodeURIComponent(project)}`, {
197
+ method: 'DELETE',
198
+ headers: { 'Content-Type': 'application/json' },
199
+ body: JSON.stringify({ sessionId }),
200
+ });
201
+ // Clear selection if deleted session was active
202
+ if (activeSessionId === sessionId) {
203
+ setActiveSessionId(null);
204
+ setEntries([]);
205
+ }
206
+ loadTree(false);
207
+ };
208
+
209
+ const createMonitorTask = async (project: string, sessionId: string) => {
210
+ const sessionLabel = sessionTree[project]?.find(s => s.sessionId === sessionId);
211
+ const label = sessionLabel?.summary || sessionLabel?.firstPrompt?.slice(0, 40) || sessionId.slice(0, 8);
212
+ await fetch('/api/tasks', {
213
+ method: 'POST',
214
+ headers: { 'Content-Type': 'application/json' },
215
+ body: JSON.stringify({
216
+ projectName: project,
217
+ prompt: `Monitor session ${sessionId}`,
218
+ mode: 'monitor',
219
+ conversationId: sessionId,
220
+ watchConfig: {
221
+ condition: 'change',
222
+ action: 'notify',
223
+ repeat: true,
224
+ },
225
+ }),
226
+ });
227
+ alert(`Monitor task created for "${label}"`);
228
+ };
229
+
230
+ // ─── Batch helpers ────────────────────────────────────────
231
+ const totalSelected = Array.from(selectedIds.values()).reduce((n, s) => n + s.size, 0);
232
+
233
+ const toggleSelect = (project: string, sessionId: string) => {
234
+ setSelectedIds(prev => {
235
+ const next = new Map(prev);
236
+ const set = new Set(next.get(project) || []);
237
+ set.has(sessionId) ? set.delete(sessionId) : set.add(sessionId);
238
+ if (set.size === 0) next.delete(project); else next.set(project, set);
239
+ return next;
240
+ });
241
+ };
242
+
243
+ const toggleSelectAll = (project: string) => {
244
+ const sessions = sessionTree[project] || [];
245
+ setSelectedIds(prev => {
246
+ const next = new Map(prev);
247
+ const existing = next.get(project);
248
+ if (existing && existing.size === sessions.length) {
249
+ next.delete(project);
250
+ } else {
251
+ next.set(project, new Set(sessions.map(s => s.sessionId)));
252
+ }
253
+ return next;
254
+ });
255
+ };
256
+
257
+ const isSelected = (project: string, sessionId: string) =>
258
+ selectedIds.get(project)?.has(sessionId) ?? false;
259
+
260
+ const isAllSelected = (project: string) => {
261
+ const sessions = sessionTree[project] || [];
262
+ return sessions.length > 0 && (selectedIds.get(project)?.size ?? 0) === sessions.length;
263
+ };
264
+
265
+ const exitBatchMode = () => {
266
+ setBatchMode(false);
267
+ setSelectedIds(new Map());
268
+ };
269
+
270
+ const batchDelete = async () => {
271
+ if (totalSelected === 0) return;
272
+ if (!confirm(`Delete ${totalSelected} sessions? This cannot be undone.`)) return;
273
+ for (const [project, ids] of selectedIds) {
274
+ await fetch(`/api/claude-sessions/${encodeURIComponent(project)}`, {
275
+ method: 'DELETE',
276
+ headers: { 'Content-Type': 'application/json' },
277
+ body: JSON.stringify({ sessionIds: Array.from(ids) }),
278
+ });
279
+ }
280
+ // Clear active if it was deleted
281
+ if (activeSessionId && selectedIds.get(selectedProject)?.has(activeSessionId)) {
282
+ setActiveSessionId(null);
283
+ setEntries([]);
284
+ }
285
+ exitBatchMode();
286
+ loadTree(false);
287
+ };
288
+
289
+ const batchMonitor = async () => {
290
+ if (totalSelected === 0) return;
291
+ for (const [project, ids] of selectedIds) {
292
+ for (const sessionId of ids) {
293
+ await fetch('/api/tasks', {
294
+ method: 'POST',
295
+ headers: { 'Content-Type': 'application/json' },
296
+ body: JSON.stringify({
297
+ projectName: project,
298
+ prompt: `Monitor session ${sessionId}`,
299
+ mode: 'monitor',
300
+ conversationId: sessionId,
301
+ watchConfig: { condition: 'change', action: 'notify', repeat: true },
302
+ }),
303
+ });
304
+ }
305
+ }
306
+ alert(`Created ${totalSelected} monitor tasks`);
307
+ exitBatchMode();
308
+ };
309
+
310
+ const activeSession = sessionTree[selectedProject]?.find(s => s.sessionId === activeSessionId);
311
+ const watchedSessionIds = new Set(watchers.filter(w => w.active).map(w => w.sessionId));
312
+ const watchedProjects = new Set(watchers.filter(w => w.active && !w.sessionId).map(w => w.projectName));
313
+
314
+ return (
315
+ <div className="flex h-full">
316
+ {/* Left: tree view */}
317
+ <div style={{ width: singleProject ? 200 : sidebarWidth }} className="flex flex-col shrink-0 overflow-hidden">
318
+ {/* Header */}
319
+ <div className="flex items-center justify-between p-2 border-b border-[var(--border)]">
320
+ <span className="text-[10px] font-semibold text-[var(--text-secondary)] uppercase">Sessions</span>
321
+ <div className="flex items-center gap-2">
322
+ <button
323
+ onClick={() => batchMode ? exitBatchMode() : setBatchMode(true)}
324
+ className={`text-[9px] transition-colors ${batchMode ? 'text-[var(--accent)]' : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'}`}
325
+ >
326
+ {batchMode ? 'Cancel' : 'Batch'}
327
+ </button>
328
+ <button
329
+ onClick={() => loadTree(true)}
330
+ disabled={syncing}
331
+ className="text-[9px] text-[var(--text-secondary)] hover:text-[var(--text-primary)] disabled:opacity-50"
332
+ >
333
+ {syncing ? 'Syncing...' : 'Sync'}
334
+ </button>
335
+ </div>
336
+ </div>
337
+
338
+ {/* Batch action bar */}
339
+ {batchMode && (
340
+ <div className="flex items-center gap-1.5 px-2 py-1.5 border-b border-[var(--border)] bg-[var(--bg-tertiary)]">
341
+ <span className="text-[9px] text-[var(--text-secondary)] flex-1">
342
+ {totalSelected} selected
343
+ </span>
344
+ <button
345
+ onClick={batchMonitor}
346
+ disabled={totalSelected === 0}
347
+ className="text-[8px] px-1.5 py-0.5 rounded bg-[var(--accent)]/10 text-[var(--accent)] hover:bg-[var(--accent)]/20 disabled:opacity-30"
348
+ >
349
+ Monitor All
350
+ </button>
351
+ <button
352
+ onClick={batchDelete}
353
+ disabled={totalSelected === 0}
354
+ className="text-[8px] px-1.5 py-0.5 rounded bg-red-500/10 text-red-400 hover:bg-red-500/20 disabled:opacity-30"
355
+ >
356
+ Delete All
357
+ </button>
358
+ </div>
359
+ )}
360
+
361
+ {/* Tree */}
362
+ <div className="flex-1 overflow-y-auto">
363
+ {Object.keys(sessionTree).length === 0 && (
364
+ <p className="text-[10px] text-[var(--text-secondary)] p-3">
365
+ {syncing ? 'Loading sessions...' : 'No sessions found. Click Sync.'}
366
+ </p>
367
+ )}
368
+
369
+ {Object.entries(sessionTree)
370
+ .filter(([project]) => !singleProject || project === projectName)
371
+ .sort(([a], [b]) => a.localeCompare(b))
372
+ .map(([project, sessions]) => (
373
+ <div key={project}>
374
+ {/* Project node (hidden in single-project mode) */}
375
+ {!singleProject && (
376
+ <div
377
+ className="w-full flex items-center gap-1.5 px-2 py-1.5 text-left hover:bg-[var(--bg-tertiary)] transition-colors border-b border-[var(--border)]/50 cursor-pointer"
378
+ onClick={() => toggleProject(project)}
379
+ >
380
+ {batchMode && (
381
+ <input
382
+ type="checkbox"
383
+ checked={isAllSelected(project)}
384
+ onChange={(e) => { e.stopPropagation(); toggleSelectAll(project); }}
385
+ onClick={(e) => e.stopPropagation()}
386
+ className="shrink-0 accent-[var(--accent)]"
387
+ />
388
+ )}
389
+ <span className="text-[10px] text-[var(--text-secondary)]">
390
+ {expandedProjects.has(project) ? '▼' : '▶'}
391
+ </span>
392
+ <span className="text-[11px] font-medium text-[var(--text-primary)] truncate flex-1">{project}</span>
393
+ <span className="text-[9px] text-[var(--text-secondary)]">{sessions.length}</span>
394
+ {watchedProjects.has(project) && (
395
+ <span className="text-[9px] text-[var(--accent)]" title="Watching">👁</span>
396
+ )}
397
+ </div>
398
+ )}
399
+
400
+ {/* Session children (always visible in single-project mode) */}
401
+ {(singleProject || expandedProjects.has(project)) && sessions.map(s => {
402
+ const isActive = selectedProject === project && activeSessionId === s.sessionId;
403
+ const isWatched = watchedSessionIds.has(s.sessionId);
404
+ return (
405
+ <div
406
+ key={s.sessionId}
407
+ className={`group relative w-full text-left ${singleProject ? 'pl-2' : 'pl-6'} pr-2 py-1.5 hover:bg-[var(--bg-tertiary)] transition-colors cursor-pointer ${
408
+ isActive ? 'bg-[var(--bg-tertiary)] border-l-2 border-l-[var(--accent)]' : 'border-l-2 border-l-transparent'
409
+ }`}
410
+ onClick={() => batchMode ? toggleSelect(project, s.sessionId) : selectSession(project, s.sessionId)}
411
+ >
412
+ <div className="flex items-center gap-1">
413
+ {batchMode && (
414
+ <input
415
+ type="checkbox"
416
+ checked={isSelected(project, s.sessionId)}
417
+ onChange={() => toggleSelect(project, s.sessionId)}
418
+ onClick={(e) => e.stopPropagation()}
419
+ className="shrink-0 accent-[var(--accent)]"
420
+ />
421
+ )}
422
+ <span className="text-[10px] text-[var(--text-primary)] truncate flex-1">
423
+ {s.summary || s.firstPrompt?.slice(0, 40) || s.sessionId.slice(0, 8)}
424
+ </span>
425
+ {isWatched && <span className="text-[8px] text-[var(--accent)]">👁</span>}
426
+ </div>
427
+ <div className="flex items-center gap-2 mt-0.5">
428
+ <span className="text-[8px] text-[var(--text-secondary)] font-mono">{s.sessionId.slice(0, 8)}</span>
429
+ {boundSessions[project]?.sessionId === s.sessionId && (
430
+ <span className="text-[7px] px-1 py-0 rounded bg-[#f0883e]/20 text-[#f0883e] font-medium">bound</span>
431
+ )}
432
+ {s.gitBranch && <span className="text-[8px] text-[var(--accent)]">{s.gitBranch}</span>}
433
+ {s.modified && (
434
+ <span className="text-[8px] text-[var(--text-secondary)]">
435
+ {timeAgo(s.modified)}
436
+ </span>
437
+ )}
438
+ {/* Actions — right side */}
439
+ {!batchMode && (
440
+ <span className="hidden group-hover:flex items-center gap-0.5 ml-auto shrink-0">
441
+ <button
442
+ onClick={(e) => {
443
+ e.stopPropagation();
444
+ const pp = projects.find(p => p.name === project)?.path || '';
445
+ if (pp) window.dispatchEvent(new CustomEvent('forge:open-terminal', { detail: { projectPath: pp, projectName: project, agentId: 'claude', resumeMode: true, sessionId: s.sessionId } }));
446
+ }}
447
+ className="text-[8px] px-1 py-0.5 rounded bg-green-500/10 text-green-400 hover:bg-green-500/20"
448
+ title="Open this session in terminal"
449
+ >open</button>
450
+ {boundSessions[project]?.sessionId !== s.sessionId && (
451
+ <button
452
+ onClick={async (e) => {
453
+ e.stopPropagation();
454
+ const pp = projects.find(p => p.name === project)?.path || '';
455
+ if (!pp) return;
456
+ const res = await fetch('/api/project-sessions', {
457
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
458
+ body: JSON.stringify({ projectPath: pp, fixedSessionId: s.sessionId }),
459
+ });
460
+ if (res.ok) {
461
+ setBoundSessions(prev => ({ ...prev, [project]: { sessionId: s.sessionId } }));
462
+ window.dispatchEvent(new Event('forge:session-bound'));
463
+ }
464
+ }}
465
+ className="text-[8px] px-1 py-0.5 rounded bg-[#f0883e]/10 text-[#f0883e] hover:bg-[#f0883e]/20"
466
+ title="Set as fixed session"
467
+ >bind</button>
468
+ )}
469
+ <button
470
+ onClick={(e) => { e.stopPropagation(); createMonitorTask(project, s.sessionId); }}
471
+ className="text-[8px] px-1 py-0.5 rounded bg-[var(--accent)]/10 text-[var(--accent)] hover:bg-[var(--accent)]/20"
472
+ title="Monitor via Telegram"
473
+ >monitor</button>
474
+ <button
475
+ onClick={(e) => { e.stopPropagation(); deleteSessionById(project, s.sessionId); }}
476
+ className="text-[8px] px-1 py-0.5 rounded bg-red-500/10 text-red-400 hover:bg-red-500/20"
477
+ title="Delete session"
478
+ >del</button>
479
+ </span>
480
+ )}
481
+ </div>
482
+ </div>
483
+ );
484
+ })}
485
+ </div>
486
+ ))}
487
+
488
+ {/* Active watchers section */}
489
+ {watchers.length > 0 && (
490
+ <div className="border-t border-[var(--border)] mt-2 pt-2">
491
+ <div className="px-2 mb-1">
492
+ <span className="text-[9px] font-semibold text-[var(--text-secondary)] uppercase">Watchers</span>
493
+ </div>
494
+ {watchers.map(w => (
495
+ <div key={w.id} className="flex items-center gap-1 px-2 py-1 text-[10px]">
496
+ <span className={`${w.active ? 'text-green-400' : 'text-gray-500'}`}>
497
+ {w.active ? '●' : '○'}
498
+ </span>
499
+ <span className="text-[var(--text-secondary)] truncate flex-1">
500
+ {w.label || w.projectName}
501
+ </span>
502
+ <button
503
+ onClick={() => removeWatcher(w.id)}
504
+ className="text-[8px] text-gray-500 hover:text-red-400"
505
+ >
506
+ x
507
+ </button>
508
+ </div>
509
+ ))}
510
+ </div>
511
+ )}
512
+ </div>
513
+ </div>
514
+
515
+ {/* Sidebar resize handle (not in single-project mode) */}
516
+ {!singleProject && (
517
+ <div
518
+ onMouseDown={onSidebarDragStart}
519
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
520
+ />
521
+ )}
522
+
523
+ {/* Right: session content */}
524
+ <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
525
+ {activeSession && (
526
+ <div className="border-b border-[var(--border)] px-4 py-2 shrink-0">
527
+ <div className="flex items-center gap-2">
528
+ <span className="text-sm font-semibold">{selectedProject}</span>
529
+ <span className="text-[10px] text-[var(--text-secondary)] font-mono">{activeSessionId?.slice(0, 12)}</span>
530
+ {activeSession.gitBranch && (
531
+ <span className="text-[10px] px-1.5 py-0.5 rounded bg-[var(--accent)]/10 text-[var(--accent)]">
532
+ {activeSession.gitBranch}
533
+ </span>
534
+ )}
535
+ <div className="ml-auto flex items-center gap-2">
536
+ {activeSessionId && !watchedSessionIds.has(activeSessionId) && (
537
+ <button
538
+ onClick={() => addWatcher(selectedProject, activeSessionId!)}
539
+ className="text-[10px] px-2 py-0.5 border border-[var(--border)] text-[var(--text-secondary)] rounded hover:text-[var(--text-primary)] hover:border-[var(--accent)]"
540
+ >
541
+ Watch
542
+ </button>
543
+ )}
544
+ {activeSessionId && (
545
+ <button
546
+ onClick={() => createMonitorTask(selectedProject, activeSessionId!)}
547
+ className="text-[10px] px-2 py-0.5 border border-[var(--border)] text-[var(--text-secondary)] rounded hover:text-[var(--accent)] hover:border-[var(--accent)]"
548
+ title="Create a monitor task that sends Telegram notifications on changes"
549
+ >
550
+ Monitor
551
+ </button>
552
+ )}
553
+ {activeSessionId && (
554
+ <button
555
+ onClick={() => {
556
+ const proj = projects.find(p => p.name === selectedProject);
557
+ if (proj) window.dispatchEvent(new CustomEvent('forge:open-terminal', { detail: { projectPath: proj.path, projectName: selectedProject, agentId: 'claude', resumeMode: true, sessionId: activeSessionId } }));
558
+ }}
559
+ className="text-[10px] px-2 py-0.5 bg-[var(--accent)] text-white rounded hover:opacity-90"
560
+ >
561
+ Open in Terminal
562
+ </button>
563
+ )}
564
+ {activeSessionId && (
565
+ <button
566
+ onClick={() => deleteSessionById(selectedProject, activeSessionId!)}
567
+ className="text-[10px] px-2 py-0.5 border border-red-500/30 text-red-400 rounded hover:bg-red-500/10"
568
+ >
569
+ Delete
570
+ </button>
571
+ )}
572
+ </div>
573
+ </div>
574
+ {activeSession.summary && (
575
+ <p className="text-xs text-[var(--text-secondary)] mt-0.5">{activeSession.summary}</p>
576
+ )}
577
+ </div>
578
+ )}
579
+
580
+ <div className="flex-1 overflow-y-auto overflow-x-hidden p-4 space-y-2">
581
+ {!activeSessionId && (
582
+ <div className="flex-1 flex items-center justify-center text-[var(--text-secondary)] h-full">
583
+ <p>Select a session from the tree to view</p>
584
+ </div>
585
+ )}
586
+
587
+ {entries.map((entry, i) => (
588
+ <SessionEntryView
589
+ key={i}
590
+ entry={entry}
591
+ expanded={expandedTools.has(i)}
592
+ onToggle={() => toggleTool(i)}
593
+ />
594
+ ))}
595
+
596
+ {entries.length > 0 && (
597
+ <div className="text-[10px] text-[var(--text-secondary)] pt-2">
598
+ {entries.length} entries — live updating
599
+ </div>
600
+ )}
601
+ <div ref={bottomRef} />
602
+ </div>
603
+ </div>
604
+
605
+ </div>
606
+ );
607
+ }
608
+
609
+ // ─── Time ago helper ─────────────────────────────────────────
610
+
611
+ function timeAgo(dateStr: string): string {
612
+ const diff = Date.now() - new Date(dateStr).getTime();
613
+ const mins = Math.floor(diff / 60000);
614
+ if (mins < 1) return 'just now';
615
+ if (mins < 60) return `${mins}m ago`;
616
+ const hours = Math.floor(mins / 60);
617
+ if (hours < 24) return `${hours}h ago`;
618
+ const days = Math.floor(hours / 24);
619
+ return `${days}d ago`;
620
+ }
621
+
622
+ // ─── Session entry renderer ─────────────────────────────────
623
+
624
+ function SessionEntryView({
625
+ entry,
626
+ expanded,
627
+ onToggle,
628
+ }: {
629
+ entry: SessionEntry;
630
+ expanded: boolean;
631
+ onToggle: () => void;
632
+ }) {
633
+ if (entry.type === 'user') {
634
+ return (
635
+ <div className="flex justify-end">
636
+ <div className="max-w-[80%] px-3 py-2 bg-[var(--accent)]/10 border border-[var(--accent)]/20 rounded-lg">
637
+ <p className="text-xs text-[var(--text-primary)] whitespace-pre-wrap break-all">{entry.content}</p>
638
+ {entry.timestamp && (
639
+ <span className="text-[9px] text-[var(--text-secondary)] mt-1 block">
640
+ {new Date(entry.timestamp).toLocaleTimeString()}
641
+ </span>
642
+ )}
643
+ </div>
644
+ </div>
645
+ );
646
+ }
647
+
648
+ if (entry.type === 'assistant_text') {
649
+ return (
650
+ <div className="py-1 overflow-hidden" style={{ maxWidth: 0, minWidth: '100%' }}>
651
+ <MarkdownContent content={entry.content} />
652
+ </div>
653
+ );
654
+ }
655
+
656
+ if (entry.type === 'thinking') {
657
+ const isLong = entry.content.length > 100;
658
+ return (
659
+ <div className="border border-[var(--border)] rounded overflow-hidden opacity-60">
660
+ <button
661
+ onClick={onToggle}
662
+ className="w-full flex items-center gap-2 px-2 py-1 bg-[var(--bg-tertiary)] hover:bg-[var(--border)]/30 text-left"
663
+ >
664
+ <span className="text-[10px] text-[var(--text-secondary)] italic">thinking...</span>
665
+ {isLong && <span className="text-[9px] text-[var(--text-secondary)] ml-auto">{expanded ? '▲' : '▼'}</span>}
666
+ </button>
667
+ {expanded && (
668
+ <pre className="px-3 py-2 text-[10px] text-[var(--text-secondary)] font-mono whitespace-pre-wrap break-all max-h-40 overflow-y-auto border-t border-[var(--border)]">
669
+ {entry.content}
670
+ </pre>
671
+ )}
672
+ </div>
673
+ );
674
+ }
675
+
676
+ if (entry.type === 'tool_use') {
677
+ const isLong = entry.content.length > 80;
678
+ return (
679
+ <div className="border border-[var(--border)] rounded overflow-hidden max-w-full">
680
+ <button
681
+ onClick={onToggle}
682
+ className="w-full flex items-center gap-2 px-2 py-1.5 bg-[var(--bg-tertiary)] hover:bg-[var(--border)]/30 transition-colors text-left"
683
+ >
684
+ <span className="text-[10px] px-1.5 py-0.5 bg-[var(--accent)]/15 text-[var(--accent)] rounded font-medium font-mono">
685
+ {entry.toolName || 'tool'}
686
+ </span>
687
+ <span className="text-[11px] text-[var(--text-secondary)] truncate flex-1 font-mono">
688
+ {isLong && !expanded ? entry.content.slice(0, 80) + '...' : (!isLong ? entry.content : '')}
689
+ </span>
690
+ {isLong && <span className="text-[9px] text-[var(--text-secondary)] shrink-0">{expanded ? '▲' : '▼'}</span>}
691
+ </button>
692
+ {(expanded || !isLong) && isLong && (
693
+ <pre className="px-3 py-2 text-[11px] text-[var(--text-secondary)] font-mono whitespace-pre-wrap break-all max-h-60 overflow-y-auto border-t border-[var(--border)]">
694
+ {entry.content}
695
+ </pre>
696
+ )}
697
+ </div>
698
+ );
699
+ }
700
+
701
+ if (entry.type === 'tool_result') {
702
+ const isLong = entry.content.length > 150;
703
+ return (
704
+ <div className="ml-4 border-l-2 border-[var(--accent)]/30 pl-3 overflow-hidden">
705
+ <pre className={`text-[11px] text-[var(--text-secondary)] font-mono whitespace-pre-wrap break-all ${isLong && !expanded ? 'max-h-16 overflow-hidden' : 'max-h-80 overflow-y-auto'}`}>
706
+ {entry.content}
707
+ </pre>
708
+ {isLong && !expanded && (
709
+ <button onClick={onToggle} className="text-[9px] text-[var(--accent)] hover:underline mt-0.5">
710
+ show more
711
+ </button>
712
+ )}
713
+ </div>
714
+ );
715
+ }
716
+
717
+ if (entry.type === 'system') {
718
+ return (
719
+ <div className="text-[10px] text-[var(--text-secondary)] py-0.5 flex items-center gap-1 opacity-50">
720
+ <span>--</span> {entry.content}
721
+ </div>
722
+ );
723
+ }
724
+
725
+ return null;
726
+ }