@aion0/forge 0.5.26 → 0.5.28

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 (255) 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 +10 -29
  249. package/app/api/terminal-bell/route.ts +6 -2
  250. package/app/api/terminal-cwd/route.ts +7 -4
  251. package/components/CodeViewer.tsx +3 -31
  252. package/components/Dashboard.tsx +34 -20
  253. package/components/WebTerminal.tsx +36 -2
  254. package/lib/terminal-standalone.ts +19 -2
  255. package/package.json +1 -1
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Standalone Telegram bot process.
4
+ * Runs as a single process — no duplication from Next.js workers.
5
+ */
6
+
7
+ import { loadSettings } from './settings';
8
+
9
+ const settings = loadSettings();
10
+ if (!settings.telegramBotToken || !settings.telegramChatId) {
11
+ console.log('[telegram] No token or chatId configured, exiting');
12
+ process.exit(0);
13
+ }
14
+
15
+ const TOKEN = settings.telegramBotToken;
16
+ const ALLOWED_IDS = settings.telegramChatId.split(',').map(s => s.trim()).filter(Boolean);
17
+ let lastUpdateId = 0;
18
+ let polling = true;
19
+ const processedMsgIds = new Set<number>();
20
+
21
+ // Skip stale messages on startup
22
+ async function init() {
23
+ try {
24
+ const res = await fetch(`https://api.telegram.org/bot${TOKEN}/getUpdates?offset=-1`);
25
+ const data = await res.json();
26
+ if (data.ok && data.result?.length > 0) {
27
+ lastUpdateId = data.result[data.result.length - 1].update_id;
28
+ }
29
+ } catch {}
30
+ console.log('[telegram] Bot started (standalone)');
31
+ poll();
32
+ }
33
+
34
+ async function poll() {
35
+ if (!polling) return;
36
+
37
+ try {
38
+ const controller = new AbortController();
39
+ const timeout = setTimeout(() => controller.abort(), 35000);
40
+
41
+ const res = await fetch(
42
+ `https://api.telegram.org/bot${TOKEN}/getUpdates?offset=${lastUpdateId + 1}&timeout=30`,
43
+ { signal: controller.signal }
44
+ );
45
+ clearTimeout(timeout);
46
+
47
+ const data = await res.json();
48
+ if (data.ok && data.result) {
49
+ for (const update of data.result) {
50
+ if (update.update_id <= lastUpdateId) continue;
51
+ lastUpdateId = update.update_id;
52
+
53
+ if (update.message?.text) {
54
+ const msgId = update.message.message_id;
55
+ if (processedMsgIds.has(msgId)) continue;
56
+ processedMsgIds.add(msgId);
57
+ if (processedMsgIds.size > 200) {
58
+ const oldest = [...processedMsgIds].slice(0, 100);
59
+ oldest.forEach(id => processedMsgIds.delete(id));
60
+ }
61
+
62
+ // Forward to Next.js API for processing
63
+ try {
64
+ await fetch(`http://localhost:${process.env.PORT || 8403}/api/telegram`, {
65
+ method: 'POST',
66
+ headers: { 'Content-Type': 'application/json', 'x-telegram-secret': TOKEN },
67
+ body: JSON.stringify(update.message),
68
+ });
69
+ } catch {}
70
+ }
71
+ }
72
+ }
73
+ } catch {
74
+ // Network error — silent retry
75
+ }
76
+
77
+ setTimeout(poll, 1000);
78
+ }
79
+
80
+ process.on('SIGTERM', () => { polling = false; process.exit(0); });
81
+ process.on('SIGINT', () => { polling = false; process.exit(0); });
82
+
83
+ init();
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Terminal Server — standalone WebSocket PTY server.
3
+ * Runs on port 8404 alongside the Next.js server on 8403.
4
+ */
5
+
6
+ import { WebSocketServer, WebSocket } from 'ws';
7
+ import * as pty from 'node-pty';
8
+ import { homedir } from 'node:os';
9
+
10
+ let wss: WebSocketServer | null = null;
11
+
12
+ export function startTerminalServer(port = 8404) {
13
+ if (wss) return;
14
+
15
+ wss = new WebSocketServer({ port });
16
+ console.log(`[terminal] WebSocket server on ws://localhost:${port}`);
17
+
18
+ wss.on('connection', (ws: WebSocket) => {
19
+ const shell = process.env.SHELL || '/bin/zsh';
20
+ const term = pty.spawn(shell, [], {
21
+ name: 'xterm-256color',
22
+ cols: 120,
23
+ rows: 30,
24
+ cwd: homedir(),
25
+ env: {
26
+ ...process.env,
27
+ TERM: 'xterm-256color',
28
+ COLORTERM: 'truecolor',
29
+ } as Record<string, string>,
30
+ });
31
+
32
+ console.log(`[terminal] New session (pid: ${term.pid})`);
33
+
34
+ term.onData((data: string) => {
35
+ if (ws.readyState === WebSocket.OPEN) {
36
+ ws.send(JSON.stringify({ type: 'output', data }));
37
+ }
38
+ });
39
+
40
+ term.onExit(({ exitCode }) => {
41
+ if (ws.readyState === WebSocket.OPEN) {
42
+ ws.send(JSON.stringify({ type: 'exit', code: exitCode }));
43
+ ws.close();
44
+ }
45
+ });
46
+
47
+ ws.on('message', (msg: Buffer) => {
48
+ try {
49
+ const parsed = JSON.parse(msg.toString());
50
+ if (parsed.type === 'input') {
51
+ term.write(parsed.data);
52
+ } else if (parsed.type === 'resize') {
53
+ term.resize(parsed.cols, parsed.rows);
54
+ }
55
+ } catch {}
56
+ });
57
+
58
+ ws.on('close', () => {
59
+ term.kill();
60
+ console.log(`[terminal] Session closed (pid: ${term.pid})`);
61
+ });
62
+ });
63
+ }
64
+
65
+ export function stopTerminalServer() {
66
+ if (wss) {
67
+ wss.close();
68
+ wss = null;
69
+ }
70
+ }
@@ -0,0 +1,438 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Standalone terminal WebSocket server with tmux-backed persistent sessions.
4
+ * Sessions survive browser close and app restart.
5
+ *
6
+ * Protocol:
7
+ * Client → Server:
8
+ * { type: 'create', cols, rows } — create new tmux session
9
+ * { type: 'attach', sessionName, cols, rows } — attach to existing
10
+ * { type: 'list' } — list all mw-* sessions
11
+ * { type: 'input', data } — stdin
12
+ * { type: 'resize', cols, rows } — resize
13
+ * { type: 'kill', sessionName } — kill a session
14
+ * { type: 'load-state' } — load shared terminal state
15
+ * { type: 'save-state', data } — save shared terminal state
16
+ *
17
+ * Server → Client:
18
+ * { type: 'sessions', sessions: [{name, created, attached, windows}] }
19
+ * { type: 'connected', sessionName }
20
+ * { type: 'output', data }
21
+ * { type: 'exit', code }
22
+ * { type: 'terminal-state', data } — loaded state (or null)
23
+ *
24
+ * Usage: npx tsx lib/terminal-standalone.ts
25
+ */
26
+
27
+ import { WebSocketServer, WebSocket } from 'ws';
28
+ import * as pty from 'node-pty';
29
+ import { execSync } from 'node:child_process';
30
+ import { homedir } from 'node:os';
31
+ import { createHash } from 'node:crypto';
32
+ import { getDataDir } from './dirs';
33
+ import { readFileSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
34
+ import { join } from 'node:path';
35
+
36
+ const PORT = Number(process.env.TERMINAL_PORT) || 8404;
37
+ // Session prefix based on DATA_DIR hash — default instance keeps 'mw-' for backward compat
38
+ const _dataDir = process.env.FORGE_DATA_DIR || '';
39
+ const _isDefault = !_dataDir || _dataDir.endsWith('/data') || _dataDir.endsWith('/.forge');
40
+ const SESSION_PREFIX = _isDefault ? 'mw-' : `mw${createHash('md5').update(_dataDir).digest('hex').slice(0, 6)}-`;
41
+
42
+ // Remove CLAUDECODE env so Claude Code can run inside terminal sessions
43
+ delete process.env.CLAUDECODE;
44
+
45
+ // ─── Shared state persistence ─────────────────────────────────
46
+
47
+ const STATE_DIR = getDataDir();
48
+ const STATE_FILE = join(STATE_DIR, 'terminal-state.json');
49
+
50
+ function loadTerminalState(): unknown {
51
+ try {
52
+ return JSON.parse(readFileSync(STATE_FILE, 'utf-8'));
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ function saveTerminalState(data: unknown): void {
59
+ try {
60
+ mkdirSync(STATE_DIR, { recursive: true });
61
+ writeFileSync(STATE_FILE, JSON.stringify(data, null, 2));
62
+ } catch (e) {
63
+ console.error('[terminal] Failed to save state:', e);
64
+ }
65
+ }
66
+
67
+
68
+ // ─── tmux helpers ──────────────────────────────────────────────
69
+
70
+ function tmuxBin(): string {
71
+ try {
72
+ return execSync('which tmux', { encoding: 'utf-8' }).trim();
73
+ } catch {
74
+ return 'tmux';
75
+ }
76
+ }
77
+
78
+ const TMUX = tmuxBin();
79
+
80
+ function listTmuxSessions(): { name: string; created: string; attached: boolean; windows: number }[] {
81
+ try {
82
+ const out = execSync(
83
+ `${TMUX} list-sessions -F "#{session_name}||#{session_created}||#{session_attached}||#{session_windows}" 2>/dev/null`,
84
+ { encoding: 'utf-8' }
85
+ );
86
+ return out
87
+ .trim()
88
+ .split('\n')
89
+ .filter(line => line.startsWith(SESSION_PREFIX))
90
+ .map(line => {
91
+ const [name, created, attached, windows] = line.split('||');
92
+ return {
93
+ name,
94
+ created: new Date(Number(created) * 1000).toISOString(),
95
+ attached: attached !== '0',
96
+ windows: Number(windows) || 1,
97
+ };
98
+ });
99
+ } catch {
100
+ return [];
101
+ }
102
+ }
103
+
104
+ const MAX_SESSIONS = 10;
105
+
106
+ function getDefaultCwd(): string {
107
+ try {
108
+ const settingsPath = join(getDataDir(), 'settings.yaml');
109
+ const raw = readFileSync(settingsPath, 'utf-8');
110
+ const match = raw.match(/projectRoots:\s*\n((?:\s+-\s+.+\n?)*)/);
111
+ if (match) {
112
+ const first = match[1].split('\n').map(l => l.replace(/^\s+-\s+/, '').trim()).filter(Boolean)[0];
113
+ if (first) return first.replace(/^~/, homedir());
114
+ }
115
+ } catch {}
116
+ return homedir();
117
+ }
118
+
119
+ function createTmuxSession(cols: number, rows: number): string {
120
+ // Auto-cleanup: if too many sessions, kill the oldest idle ones
121
+ const existing = listTmuxSessions();
122
+ if (existing.length >= MAX_SESSIONS) {
123
+ const idle = existing.filter(s => !s.attached);
124
+ // Kill oldest idle sessions to make room
125
+ const toKill = idle.slice(0, Math.max(1, idle.length - Math.floor(MAX_SESSIONS / 2)));
126
+ for (const s of toKill) {
127
+ console.log(`[terminal] Auto-cleanup: killing idle session "${s.name}"`);
128
+ killTmuxSession(s.name);
129
+ }
130
+ }
131
+
132
+ const id = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
133
+ const name = `${SESSION_PREFIX}${id}`;
134
+ try {
135
+ execSync(`${TMUX} new-session -d -s ${name} -x ${cols} -y ${rows}`, {
136
+ cwd: getDefaultCwd(),
137
+ env: { ...process.env, TERM: 'xterm-256color' },
138
+ });
139
+ } catch (e: any) {
140
+ const msg = e.stderr?.toString() || e.message || '';
141
+ if (msg.includes('posix_spawn') || msg.includes('fork failed') || msg.includes('No such file')) {
142
+ // PTY exhausted — aggressive cleanup: kill ALL idle sessions
143
+ console.error(`[terminal] PTY exhausted, cleaning up all idle sessions...`);
144
+ const all = listTmuxSessions();
145
+ for (const s of all) {
146
+ if (!s.attached) {
147
+ killTmuxSession(s.name);
148
+ console.log(`[terminal] Killed idle session: ${s.name}`);
149
+ }
150
+ }
151
+ // Retry once
152
+ try {
153
+ execSync(`${TMUX} new-session -d -s ${name} -x ${cols} -y ${rows}`, {
154
+ cwd: getDefaultCwd(),
155
+ env: { ...process.env, TERM: 'xterm-256color' },
156
+ });
157
+ } catch {
158
+ throw new Error('Failed to create terminal session. PTY devices exhausted. Run: sudo sysctl kern.tty.ptmx_max=2048');
159
+ }
160
+ } else {
161
+ throw e;
162
+ }
163
+ }
164
+ // Mouse and scrollback are set in attachToTmux (always called after create)
165
+ return name;
166
+ }
167
+
168
+ function killTmuxSession(name: string): boolean {
169
+ if (!name.startsWith(SESSION_PREFIX)) return false;
170
+ try {
171
+ execSync(`${TMUX} kill-session -t ${name} 2>/dev/null`);
172
+ return true;
173
+ } catch {
174
+ return false;
175
+ }
176
+ }
177
+
178
+ function tmuxSessionExists(name: string): boolean {
179
+ try {
180
+ execSync(`${TMUX} has-session -t ${name} 2>/dev/null`);
181
+ return true;
182
+ } catch {
183
+ return false;
184
+ }
185
+ }
186
+
187
+ // ─── Connection tracking (for orphan cleanup) ──────────────────
188
+
189
+ /** Map from tmux session name → Set of WebSocket clients attached to it */
190
+ const sessionClients = new Map<string, Set<WebSocket>>();
191
+
192
+
193
+ function trackAttach(ws: WebSocket, sessionName: string) {
194
+ if (!sessionClients.has(sessionName)) sessionClients.set(sessionName, new Set());
195
+ sessionClients.get(sessionName)!.add(ws);
196
+ }
197
+
198
+ function trackDetach(ws: WebSocket, sessionName: string) {
199
+ sessionClients.get(sessionName)?.delete(ws);
200
+ if (sessionClients.get(sessionName)?.size === 0) sessionClients.delete(sessionName);
201
+ }
202
+
203
+ // ─── Periodic orphan cleanup ─────────────────────────────────
204
+
205
+ /** Clean up detached tmux sessions that are not tracked in terminal-state.json */
206
+ function cleanupOrphanedSessions() {
207
+ const knownSessions = getKnownSessions();
208
+ const sessions = listTmuxSessions();
209
+ for (const s of sessions) {
210
+ if (s.attached) continue;
211
+ if (s.name.startsWith(`${SESSION_PREFIX}forge-`)) continue; // workspace agent session — managed by orchestrator
212
+ if (knownSessions.has(s.name)) continue; // saved in terminal state — preserve
213
+ const clients = sessionClients.get(s.name)?.size ?? 0;
214
+ if (clients === 0) {
215
+ console.log(`[terminal] Orphan cleanup: killing "${s.name}"`);
216
+ killTmuxSession(s.name);
217
+ }
218
+ }
219
+ }
220
+
221
+ /** Get all session names referenced in terminal-state.json (tabs + labels) */
222
+ function getKnownSessions(): Set<string> {
223
+ try {
224
+ const state = loadTerminalState() as any;
225
+ if (!state) return new Set();
226
+ const known = new Set<string>();
227
+ // From sessionLabels
228
+ if (state.sessionLabels) {
229
+ for (const name of Object.keys(state.sessionLabels)) known.add(name);
230
+ }
231
+ // From tab trees
232
+ if (state.tabs) {
233
+ for (const tab of state.tabs) {
234
+ collectTreeSessions(tab.tree, known);
235
+ }
236
+ }
237
+ return known;
238
+ } catch {
239
+ return new Set();
240
+ }
241
+ }
242
+
243
+ function collectTreeSessions(node: any, set: Set<string>) {
244
+ if (!node) return;
245
+ if (node.type === 'terminal' && node.sessionName) set.add(node.sessionName);
246
+ if (node.first) collectTreeSessions(node.first, set);
247
+ if (node.second) collectTreeSessions(node.second, set);
248
+ }
249
+
250
+ // Run cleanup every 60 seconds, with a 60s initial delay to let clients reconnect after restart
251
+ setTimeout(() => setInterval(cleanupOrphanedSessions, 60_000), 60_000);
252
+
253
+ // ─── WebSocket server ──────────────────────────────────────────
254
+
255
+ const wss = new WebSocketServer({ port: PORT });
256
+ console.log(`[terminal] WebSocket server on ws://0.0.0.0:${PORT} (tmux-backed)`);
257
+
258
+ wss.on('connection', (ws: WebSocket) => {
259
+ let term: pty.IPty | null = null;
260
+ let sessionName: string | null = null;
261
+
262
+ function attachToTmux(name: string, cols: number, rows: number) {
263
+ if (!tmuxSessionExists(name)) {
264
+ ws.send(JSON.stringify({ type: 'error', message: `session "${name}" no longer exists` }));
265
+ return;
266
+ }
267
+
268
+ // Kill previous pty process before attaching to new session (prevents PTY leak)
269
+ if (term) {
270
+ try { term.kill(); } catch {}
271
+ term = null;
272
+ }
273
+
274
+ // Ensure mouse mode is on (enables trackpad scrolling) and scrollback is set
275
+ try {
276
+ execSync(`${TMUX} set-option -t ${name} mouse on 2>/dev/null`);
277
+ execSync(`${TMUX} set-option -t ${name} history-limit 50000 2>/dev/null`);
278
+ } catch {}
279
+
280
+ // Detach from previous session if switching
281
+ if (sessionName) trackDetach(ws, sessionName);
282
+ sessionName = name;
283
+ trackAttach(ws, name);
284
+
285
+ // Attach to tmux session via pty
286
+ term = pty.spawn(TMUX, ['attach-session', '-t', name], {
287
+ name: 'xterm-256color',
288
+ cols,
289
+ rows,
290
+ cwd: homedir(),
291
+ env: {
292
+ ...process.env,
293
+ TERM: 'xterm-256color',
294
+ COLORTERM: 'truecolor',
295
+ } as Record<string, string>,
296
+ });
297
+
298
+ // Attached to tmux session (silent)
299
+ ws.send(JSON.stringify({ type: 'connected', sessionName: name }));
300
+
301
+ term.onData((data: string) => {
302
+ if (ws.readyState === WebSocket.OPEN) {
303
+ ws.send(JSON.stringify({ type: 'output', data }));
304
+ }
305
+ });
306
+
307
+ term.onExit(({ exitCode }) => {
308
+ if (ws.readyState === WebSocket.OPEN) {
309
+ ws.send(JSON.stringify({ type: 'exit', code: exitCode }));
310
+ }
311
+ term = null;
312
+ });
313
+ }
314
+
315
+ ws.on('message', (msg: Buffer) => {
316
+ try {
317
+ const parsed = JSON.parse(msg.toString());
318
+
319
+ switch (parsed.type) {
320
+ case 'list': {
321
+ const sessions = listTmuxSessions();
322
+ ws.send(JSON.stringify({ type: 'sessions', sessions }));
323
+ break;
324
+ }
325
+
326
+ case 'create': {
327
+ const cols = parsed.cols || 120;
328
+ const rows = parsed.rows || 30;
329
+ try {
330
+ // Support fixed session name (e.g. mw-docs-claude)
331
+ let name: string;
332
+ if (parsed.sessionName && parsed.sessionName.startsWith(SESSION_PREFIX)) {
333
+ // Create with fixed name if it doesn't exist, otherwise attach
334
+ if (tmuxSessionExists(parsed.sessionName)) {
335
+ attachToTmux(parsed.sessionName, cols, rows);
336
+ break;
337
+ }
338
+ name = parsed.sessionName;
339
+ execSync(`${TMUX} new-session -d -s ${name} -x ${cols} -y ${rows}`, {
340
+ cwd: homedir(),
341
+ env: { ...process.env, TERM: 'xterm-256color' },
342
+ });
343
+ // Mouse and scrollback are set in attachToTmux (always called after create)
344
+ } else {
345
+ name = createTmuxSession(cols, rows);
346
+ }
347
+ attachToTmux(name, cols, rows);
348
+ } catch (e: unknown) {
349
+ const errMsg = e instanceof Error ? e.message : 'unknown error';
350
+ console.error(`[terminal] Failed to create tmux session:`, errMsg);
351
+ ws.send(JSON.stringify({ type: 'error', message: `failed to create session: ${errMsg}` }));
352
+ }
353
+ break;
354
+ }
355
+
356
+ case 'attach': {
357
+ const cols = parsed.cols || 120;
358
+ const rows = parsed.rows || 30;
359
+ try {
360
+ attachToTmux(parsed.sessionName, cols, rows);
361
+ } catch (e: unknown) {
362
+ const errMsg = e instanceof Error ? e.message : 'unknown error';
363
+ console.error(`[terminal] Failed to attach to session:`, errMsg);
364
+ ws.send(JSON.stringify({ type: 'error', message: `failed to attach: ${errMsg}` }));
365
+ }
366
+ break;
367
+ }
368
+
369
+ case 'input': {
370
+ if (term) term.write(parsed.data);
371
+ break;
372
+ }
373
+
374
+ case 'resize': {
375
+ if (term) term.resize(parsed.cols, parsed.rows);
376
+ break;
377
+ }
378
+
379
+ case 'kill': {
380
+ if (parsed.sessionName) {
381
+ killTmuxSession(parsed.sessionName);
382
+ ws.send(JSON.stringify({ type: 'sessions', sessions: listTmuxSessions() }));
383
+ }
384
+ break;
385
+ }
386
+
387
+ case 'tmux-mouse': {
388
+ // Toggle mouse for ALL tmux sessions (global + per-session)
389
+ const val = parsed.mouse ? 'on' : 'off';
390
+ try {
391
+ execSync(`${TMUX} set -g mouse ${val}`, { timeout: 3000 });
392
+ // Also apply to every existing session (overrides per-session setting)
393
+ const sessions = listTmuxSessions();
394
+ for (const s of sessions) {
395
+ try { execSync(`${TMUX} set-option -t "${s.name}" mouse ${val}`, { timeout: 1000 }); } catch {}
396
+ }
397
+ ws.send(JSON.stringify({ type: 'tmux-mouse-result', ok: true, mouse: parsed.mouse }));
398
+ } catch (e: any) {
399
+ ws.send(JSON.stringify({ type: 'tmux-mouse-result', ok: false, error: e.message }));
400
+ }
401
+ break;
402
+ }
403
+
404
+ case 'load-state': {
405
+ const state = loadTerminalState();
406
+ ws.send(JSON.stringify({ type: 'terminal-state', data: state }));
407
+ break;
408
+ }
409
+
410
+ case 'save-state': {
411
+ if (parsed.data) {
412
+ saveTerminalState(parsed.data);
413
+ }
414
+ break;
415
+ }
416
+ }
417
+ } catch (e) {
418
+ console.error('[terminal] Error handling message:', e);
419
+ try {
420
+ ws.send(JSON.stringify({ type: 'error', message: 'internal server error' }));
421
+ } catch {}
422
+ }
423
+ });
424
+
425
+ ws.on('close', () => {
426
+ // Only kill the pty attach process, NOT the tmux session — it persists
427
+ if (term) {
428
+ term.kill();
429
+ // Detached from tmux session (silent)
430
+ }
431
+
432
+ // Untrack this client
433
+ if (sessionName) trackDetach(ws, sessionName);
434
+
435
+ // Orphan cleanup is handled by the periodic cleanupOrphanedSessions() (every 60s)
436
+ // which checks sessionClients and getKnownSessions() from terminal-state.json
437
+ });
438
+ });