@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,287 @@
1
+ /**
2
+ * Request/Response Document System — structured YAML documents for
3
+ * multi-agent delivery workflows.
4
+ *
5
+ * Storage layout:
6
+ * <project>/.forge/requests/<id>/
7
+ * ├── request.yml — created by Architect
8
+ * └── response.yml — updated by Engineer, Reviewer, QA
9
+ *
10
+ * Inspired by Accord protocol: YAML frontmatter + structured content,
11
+ * status lifecycle (open → in_progress → review → qa → done).
12
+ */
13
+
14
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
15
+ import { join } from 'node:path';
16
+ import YAML from 'yaml';
17
+
18
+ // ─── Types ──────────────────────────────────────────────
19
+
20
+ export type RequestStatus = 'open' | 'in_progress' | 'review' | 'qa' | 'done' | 'rejected';
21
+ export type RequestPriority = 'high' | 'medium' | 'low';
22
+ export type RequestType = 'feature' | 'bugfix' | 'refactor' | 'task';
23
+
24
+ export interface RequestModule {
25
+ name: string;
26
+ description: string;
27
+ acceptance_criteria: string[];
28
+ }
29
+
30
+ export interface RequestDocument {
31
+ id: string;
32
+ batch: string; // groups requests into a delivery
33
+ title: string;
34
+ description: string;
35
+ type: RequestType;
36
+ modules: RequestModule[];
37
+ priority: RequestPriority;
38
+ status: RequestStatus;
39
+ assigned_to: string; // agent label
40
+ created_by: string; // agent label
41
+ created_at: string; // ISO timestamp
42
+ updated_at: string;
43
+ }
44
+
45
+ export interface EngineerResponse {
46
+ completed_at?: string;
47
+ files_changed: string[];
48
+ notes: string;
49
+ }
50
+
51
+ export interface ReviewResponse {
52
+ completed_at?: string;
53
+ result: 'approved' | 'changes_requested' | 'rejected';
54
+ findings: Array<{ severity: string; description: string }>;
55
+ }
56
+
57
+ export interface QaResponse {
58
+ completed_at?: string;
59
+ result: 'passed' | 'failed';
60
+ test_files: string[];
61
+ findings: Array<{ severity: string; description: string }>;
62
+ }
63
+
64
+ export interface ResponseDocument {
65
+ request_id: string;
66
+ status: RequestStatus;
67
+ engineer?: EngineerResponse;
68
+ review?: ReviewResponse;
69
+ qa?: QaResponse;
70
+ }
71
+
72
+ // ─── Paths ──────────────────────────────────────────────
73
+
74
+ function requestsRoot(projectPath: string): string {
75
+ return join(projectPath, '.forge', 'requests');
76
+ }
77
+
78
+ function requestDir(projectPath: string, requestId: string): string {
79
+ return join(requestsRoot(projectPath), requestId);
80
+ }
81
+
82
+ function ensureDir(dir: string): void {
83
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
84
+ }
85
+
86
+ // ─── CRUD ───────────────────────────────────────────────
87
+
88
+ /**
89
+ * Generate a request ID: REQ-YYYYMMDD-NNN
90
+ */
91
+ export function generateRequestId(projectPath: string): string {
92
+ const now = new Date();
93
+ const date = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
94
+ const root = requestsRoot(projectPath);
95
+ if (!existsSync(root)) return `REQ-${date}-001`;
96
+ const existing = readdirSync(root).filter(f => f.startsWith(`REQ-${date}-`));
97
+ const num = existing.length + 1;
98
+ return `REQ-${date}-${String(num).padStart(3, '0')}`;
99
+ }
100
+
101
+ /**
102
+ * Create a new request document.
103
+ * Returns the relative path to request.yml (for use as ref in bus messages).
104
+ */
105
+ export function createRequest(projectPath: string, doc: Omit<RequestDocument, 'id' | 'created_at' | 'updated_at'> & { id?: string }): string {
106
+ const id = doc.id || generateRequestId(projectPath);
107
+ const dir = requestDir(projectPath, id);
108
+ ensureDir(dir);
109
+
110
+ const now = new Date().toISOString();
111
+ const full: RequestDocument = {
112
+ ...doc,
113
+ id,
114
+ status: doc.status || 'open',
115
+ created_at: now,
116
+ updated_at: now,
117
+ };
118
+
119
+ const filePath = join(dir, 'request.yml');
120
+ writeFileSync(filePath, YAML.stringify(full), 'utf-8');
121
+ console.log(`[requests] Created ${id}: ${doc.title}`);
122
+
123
+ // Return relative path for bus ref
124
+ return `.forge/requests/${id}/request.yml`;
125
+ }
126
+
127
+ /**
128
+ * Get a request and its optional response.
129
+ */
130
+ export function getRequest(projectPath: string, requestId: string): { request: RequestDocument; response?: ResponseDocument } | null {
131
+ const dir = requestDir(projectPath, requestId);
132
+ const reqFile = join(dir, 'request.yml');
133
+ if (!existsSync(reqFile)) return null;
134
+
135
+ try {
136
+ const request: RequestDocument = YAML.parse(readFileSync(reqFile, 'utf-8'));
137
+ let response: ResponseDocument | undefined;
138
+ const resFile = join(dir, 'response.yml');
139
+ if (existsSync(resFile)) {
140
+ response = YAML.parse(readFileSync(resFile, 'utf-8'));
141
+ }
142
+ return { request, response };
143
+ } catch (err: any) {
144
+ console.error(`[requests] Failed to read ${requestId}: ${err.message}`);
145
+ return null;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * List all requests, optionally filtered by batch or status.
151
+ */
152
+ export function listRequests(projectPath: string, opts?: { batch?: string; status?: RequestStatus }): RequestDocument[] {
153
+ const root = requestsRoot(projectPath);
154
+ if (!existsSync(root)) return [];
155
+
156
+ const results: RequestDocument[] = [];
157
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
158
+ if (!entry.isDirectory()) continue;
159
+ const reqFile = join(root, entry.name, 'request.yml');
160
+ if (!existsSync(reqFile)) continue;
161
+ try {
162
+ const doc: RequestDocument = YAML.parse(readFileSync(reqFile, 'utf-8'));
163
+ if (opts?.batch && doc.batch !== opts.batch) continue;
164
+ if (opts?.status && doc.status !== opts.status) continue;
165
+ results.push(doc);
166
+ } catch {}
167
+ }
168
+
169
+ return results.sort((a, b) => a.created_at.localeCompare(b.created_at));
170
+ }
171
+
172
+ /**
173
+ * Update a response document section (engineer, review, or qa).
174
+ * Automatically advances request status:
175
+ * engineer → review, review(approved) → qa, qa(passed) → done
176
+ * Returns the relative path to response.yml.
177
+ */
178
+ export function updateResponse(
179
+ projectPath: string,
180
+ requestId: string,
181
+ section: 'engineer' | 'review' | 'qa',
182
+ data: Record<string, any>,
183
+ ): string {
184
+ const dir = requestDir(projectPath, requestId);
185
+ const reqFile = join(dir, 'request.yml');
186
+ if (!existsSync(reqFile)) throw new Error(`Request ${requestId} not found`);
187
+
188
+ ensureDir(dir);
189
+ const resFile = join(dir, 'response.yml');
190
+
191
+ // Load or create response
192
+ let response: ResponseDocument;
193
+ if (existsSync(resFile)) {
194
+ response = YAML.parse(readFileSync(resFile, 'utf-8'));
195
+ } else {
196
+ response = { request_id: requestId, status: 'in_progress' };
197
+ }
198
+
199
+ // Update section with timestamp
200
+ const now = new Date().toISOString();
201
+ (response as any)[section] = { ...data, completed_at: now };
202
+
203
+ // Auto-advance status
204
+ const request: RequestDocument = YAML.parse(readFileSync(reqFile, 'utf-8'));
205
+ let newStatus: RequestStatus = request.status;
206
+
207
+ if (section === 'engineer') {
208
+ newStatus = 'review';
209
+ } else if (section === 'review') {
210
+ newStatus = data.result === 'rejected' ? 'rejected' : data.result === 'changes_requested' ? 'in_progress' : 'qa';
211
+ } else if (section === 'qa') {
212
+ newStatus = data.result === 'passed' ? 'done' : 'in_progress'; // failed → back to engineer
213
+ }
214
+
215
+ response.status = newStatus;
216
+ request.status = newStatus;
217
+ request.updated_at = now;
218
+
219
+ // Write both files
220
+ writeFileSync(resFile, YAML.stringify(response), 'utf-8');
221
+ writeFileSync(reqFile, YAML.stringify(request), 'utf-8');
222
+
223
+ console.log(`[requests] ${requestId}: ${section} updated → status=${newStatus}`);
224
+ return `.forge/requests/${requestId}/response.yml`;
225
+ }
226
+
227
+ /**
228
+ * Claim a request — set assigned_to and status to in_progress.
229
+ * Returns true if claimed successfully, false if already claimed by someone else.
230
+ */
231
+ export function claimRequest(projectPath: string, requestId: string, agentLabel: string): { ok: boolean; claimedBy?: string } {
232
+ const dir = requestDir(projectPath, requestId);
233
+ const reqFile = join(dir, 'request.yml');
234
+ if (!existsSync(reqFile)) throw new Error(`Request ${requestId} not found`);
235
+
236
+ const doc: RequestDocument = YAML.parse(readFileSync(reqFile, 'utf-8'));
237
+
238
+ // Already claimed by someone else
239
+ if (doc.assigned_to && doc.assigned_to !== agentLabel) {
240
+ return { ok: false, claimedBy: doc.assigned_to };
241
+ }
242
+
243
+ // Already claimed by this agent (idempotent)
244
+ if (doc.assigned_to === agentLabel) {
245
+ return { ok: true };
246
+ }
247
+
248
+ // Only open requests can be claimed
249
+ if (doc.status !== 'open') {
250
+ return { ok: false, claimedBy: doc.assigned_to || `(status: ${doc.status})` };
251
+ }
252
+
253
+ doc.assigned_to = agentLabel;
254
+ doc.status = 'in_progress';
255
+ doc.updated_at = new Date().toISOString();
256
+ writeFileSync(reqFile, YAML.stringify(doc), 'utf-8');
257
+ console.log(`[requests] ${requestId}: claimed by ${agentLabel}`);
258
+ return { ok: true };
259
+ }
260
+
261
+ /**
262
+ * Manually update request status.
263
+ */
264
+ export function updateRequestStatus(projectPath: string, requestId: string, status: RequestStatus): void {
265
+ const dir = requestDir(projectPath, requestId);
266
+ const reqFile = join(dir, 'request.yml');
267
+ if (!existsSync(reqFile)) throw new Error(`Request ${requestId} not found`);
268
+
269
+ const doc: RequestDocument = YAML.parse(readFileSync(reqFile, 'utf-8'));
270
+ doc.status = status;
271
+ doc.updated_at = new Date().toISOString();
272
+ writeFileSync(reqFile, YAML.stringify(doc), 'utf-8');
273
+ }
274
+
275
+ /**
276
+ * Get batch completion status.
277
+ */
278
+ export function getBatchStatus(projectPath: string, batch: string): { total: number; done: number; allDone: boolean; requests: Array<{ id: string; title: string; status: RequestStatus }> } {
279
+ const all = listRequests(projectPath, { batch });
280
+ const done = all.filter(r => r.status === 'done' || r.status === 'rejected').length;
281
+ return {
282
+ total: all.length,
283
+ done,
284
+ allDone: all.length > 0 && done === all.length,
285
+ requests: all.map(r => ({ id: r.id, title: r.title, status: r.status })),
286
+ };
287
+ }
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Session File Monitor — detects agent running/idle state by watching
3
+ * Claude Code's .jsonl session files.
4
+ *
5
+ * How it works:
6
+ * - Each agent has a known session file path (boundSessionId/fixedSessionId/--session-id)
7
+ * - Monitor checks file mtime every 3s
8
+ * - mtime changing → agent is running (LLM streaming, tool use, etc.)
9
+ * - mtime stable for IDLE_THRESHOLD → check last lines for 'result' entry → done
10
+ * - No session file → idle (not started)
11
+ *
12
+ * Works for both terminal and headless modes — both write the same .jsonl format.
13
+ */
14
+
15
+ import { statSync, readFileSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { homedir } from 'node:os';
18
+ import { resolve } from 'node:path';
19
+ import { EventEmitter } from 'node:events';
20
+
21
+ export type SessionMonitorState = 'idle' | 'running' | 'done';
22
+
23
+ export interface SessionMonitorEvent {
24
+ agentId: string;
25
+ state: SessionMonitorState;
26
+ sessionFile: string;
27
+ detail?: string; // e.g., result summary
28
+ }
29
+
30
+ const POLL_INTERVAL = 3000; // check every 3s
31
+ const IDLE_THRESHOLD = 29 * 60 * 1000; // 29min of no file change → check for result entry
32
+ const STABLE_THRESHOLD = 30 * 60 * 1000; // 30min of no change → force done (fallback if hook missed)
33
+
34
+ export class SessionFileMonitor extends EventEmitter {
35
+ private timers = new Map<string, NodeJS.Timeout>();
36
+ private lastMtime = new Map<string, number>();
37
+ private lastSize = new Map<string, number>();
38
+ private lastStableTime = new Map<string, number>();
39
+ private currentState = new Map<string, SessionMonitorState>();
40
+
41
+ /**
42
+ * Start monitoring a session file for an agent.
43
+ * @param agentId - Agent identifier
44
+ * @param sessionFilePath - Full path to the .jsonl session file
45
+ */
46
+ startMonitoring(agentId: string, sessionFilePath: string): void {
47
+ this.stopMonitoring(agentId);
48
+ this.currentState.set(agentId, 'idle');
49
+ this.lastStableTime.set(agentId, Date.now());
50
+ this.warmupCount.set(agentId, 0); // reset warmup for fresh start
51
+
52
+ const timer = setInterval(() => {
53
+ this.checkFile(agentId, sessionFilePath);
54
+ }, POLL_INTERVAL);
55
+ timer.unref();
56
+ this.timers.set(agentId, timer);
57
+
58
+ console.log(`[session-monitor] Started monitoring ${agentId}: ${sessionFilePath}`);
59
+ }
60
+
61
+ /**
62
+ * Stop monitoring an agent's session file.
63
+ */
64
+ stopMonitoring(agentId: string): void {
65
+ const timer = this.timers.get(agentId);
66
+ if (timer) clearInterval(timer);
67
+ this.timers.delete(agentId);
68
+ this.lastMtime.delete(agentId);
69
+ this.lastSize.delete(agentId);
70
+ this.lastStableTime.delete(agentId);
71
+ this.currentState.delete(agentId);
72
+ }
73
+
74
+ /**
75
+ * Stop all monitors.
76
+ */
77
+ stopAll(): void {
78
+ for (const [id] of this.timers) {
79
+ this.stopMonitoring(id);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Get current state for an agent.
85
+ */
86
+ getState(agentId: string): SessionMonitorState {
87
+ return this.currentState.get(agentId) || 'idle';
88
+ }
89
+
90
+ /**
91
+ * Reset monitor state to idle and pause detection briefly.
92
+ * Call when orchestrator manually changes taskStatus (button/hook).
93
+ * Suppresses detection for 10s to avoid immediately flipping back.
94
+ */
95
+ private suppressUntil = new Map<string, number>();
96
+
97
+ resetState(agentId: string): void {
98
+ this.currentState.set(agentId, 'idle');
99
+ this.lastStableTime.set(agentId, Date.now());
100
+ // Don't reset warmupCount here — warmup only on startMonitoring (fresh start/restart)
101
+ // 10s suppress is enough to prevent immediate flip-back after hook/button
102
+ this.suppressUntil.set(agentId, Date.now() + 10_000);
103
+ }
104
+
105
+ /**
106
+ * Resolve session file path for a project + session ID.
107
+ */
108
+ static resolveSessionPath(projectPath: string, workDir: string | undefined, sessionId: string): string {
109
+ const fullPath = workDir && workDir !== './' && workDir !== '.'
110
+ ? join(projectPath, workDir) : projectPath;
111
+ // Claude Code encodes paths by replacing all non-alphanumeric chars with '-'
112
+ const encoded = resolve(fullPath).replace(/[^a-zA-Z0-9]/g, '-');
113
+ return join(homedir(), '.claude', 'projects', encoded, `${sessionId}.jsonl`);
114
+ }
115
+
116
+ private warmupCount = new Map<string, number>();
117
+ private checkFile(agentId: string, filePath: string): void {
118
+ try {
119
+ const stat = statSync(filePath);
120
+ const mtime = stat.mtimeMs;
121
+ const size = stat.size;
122
+
123
+ // Warmup: skip first 6 polls (~18s) to avoid false running during startup
124
+ // On poll 7 (first real check), just set new baseline — don't trigger running
125
+ const count = (this.warmupCount.get(agentId) || 0) + 1;
126
+ this.warmupCount.set(agentId, count);
127
+ if (count <= 7) {
128
+ this.lastMtime.set(agentId, mtime);
129
+ this.lastSize.set(agentId, size);
130
+ this.lastStableTime.set(agentId, Date.now());
131
+ if (count === 1) console.log(`[session-monitor] ${agentId}: warmup started`);
132
+ if (count === 7) console.log(`[session-monitor] ${agentId}: warmup done, monitoring active`);
133
+ return;
134
+ }
135
+
136
+ const prevMtime = this.lastMtime.get(agentId) || 0;
137
+ const prevSize = this.lastSize.get(agentId) || 0;
138
+ const prevState = this.currentState.get(agentId) || 'idle';
139
+ const now = Date.now();
140
+
141
+ this.lastMtime.set(agentId, mtime);
142
+ this.lastSize.set(agentId, size);
143
+
144
+ // File changed (mtime or size different) → running
145
+ if (mtime !== prevMtime || size !== prevSize) {
146
+ this.lastStableTime.set(agentId, now);
147
+ if (prevState !== 'running') {
148
+ this.setState(agentId, 'running', filePath);
149
+ }
150
+ return;
151
+ }
152
+
153
+ // File unchanged — how long has it been stable?
154
+ const stableFor = now - (this.lastStableTime.get(agentId) || now);
155
+
156
+ if (prevState === 'running') {
157
+ if (stableFor >= IDLE_THRESHOLD) {
158
+ // Check if session file has a 'result' entry at the end
159
+ const resultInfo = this.checkForResult(filePath, size);
160
+ if (resultInfo) {
161
+ this.setState(agentId, 'done', filePath, resultInfo);
162
+ return;
163
+ }
164
+ }
165
+ if (stableFor >= STABLE_THRESHOLD) {
166
+ // Force done after 60min even without result entry (ultimate fallback)
167
+ this.setState(agentId, 'done', filePath, 'stable timeout');
168
+ return;
169
+ }
170
+ }
171
+ } catch (err: any) {
172
+ if ((this.warmupCount.get(`err-${agentId}`) || 0) === 0) {
173
+ this.warmupCount.set(`err-${agentId}`, 1);
174
+ console.log(`[session-monitor] ${agentId}: checkFile error — ${err.message}`);
175
+ }
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Check the last few lines of the session file for a 'result' type entry.
181
+ * Claude Code writes this when a turn completes.
182
+ */
183
+ private checkForResult(filePath: string, fileSize?: number): string | null {
184
+ try {
185
+ // Read last 4KB of the file — fileSize passed from caller to avoid second statSync
186
+ const { openSync, readSync, closeSync } = require('node:fs') as typeof import('node:fs');
187
+ const size = fileSize ?? statSync(filePath).size;
188
+ const readSize = Math.min(4096, size);
189
+ const fd = openSync(filePath, 'r');
190
+ const buf = Buffer.alloc(readSize);
191
+ try {
192
+ readSync(fd, buf, 0, readSize, Math.max(0, size - readSize));
193
+ } finally {
194
+ closeSync(fd);
195
+ }
196
+
197
+ const tail = buf.toString('utf-8');
198
+ const lines = tail.split('\n').filter(l => l.trim());
199
+
200
+ // Scan last lines for result entry
201
+ for (let i = lines.length - 1; i >= Math.max(0, lines.length - 10); i--) {
202
+ try {
203
+ const entry = JSON.parse(lines[i]);
204
+ // Claude Code writes result entries with these fields
205
+ if (entry.type === 'result' || entry.result || entry.duration_ms !== undefined) {
206
+ const summary = entry.result?.slice?.(0, 200) || entry.summary?.slice?.(0, 200) || '';
207
+ return summary || 'completed';
208
+ }
209
+ // Also check for assistant message without tool_use (model stopped)
210
+ if (entry.type === 'assistant' && entry.message?.content) {
211
+ const content = entry.message.content;
212
+ const hasToolUse = Array.isArray(content)
213
+ ? content.some((b: any) => b.type === 'tool_use')
214
+ : false;
215
+ if (!hasToolUse) {
216
+ return 'model stopped (no tool_use)';
217
+ }
218
+ }
219
+ } catch {} // skip non-JSON lines
220
+ }
221
+ } catch {}
222
+ return null;
223
+ }
224
+
225
+ private setState(agentId: string, state: SessionMonitorState, filePath: string, detail?: string): void {
226
+ const prev = this.currentState.get(agentId);
227
+ if (prev === state) return;
228
+
229
+ // Suppress state changes if recently reset by orchestrator
230
+ const suppressed = this.suppressUntil.get(agentId);
231
+ if (suppressed && Date.now() < suppressed) {
232
+ return;
233
+ }
234
+
235
+ this.currentState.set(agentId, state);
236
+ const event: SessionMonitorEvent = { agentId, state, sessionFile: filePath, detail };
237
+ this.emit('stateChange', event);
238
+ console.log(`[session-monitor] ${agentId}: ${prev} → ${state}${detail ? ` (${detail})` : ''}`);
239
+ }
240
+ }