@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,249 @@
1
+ /**
2
+ * Usage Scanner — scans Claude Code JSONL session files for token usage data.
3
+ * Stores per-day aggregated results in SQLite for accurate daily breakdown.
4
+ */
5
+
6
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
7
+ import { join, basename } from 'node:path';
8
+ import { getDb } from '@/src/core/db/database';
9
+ import { getDbPath } from '@/src/config';
10
+ import { getClaudeDir } from './dirs';
11
+
12
+ function db() { return getDb(getDbPath()); }
13
+
14
+ const PRICING: Record<string, { input: number; output: number }> = {
15
+ 'claude-opus-4': { input: 15, output: 75 },
16
+ 'claude-sonnet-4': { input: 3, output: 15 },
17
+ 'claude-haiku-4': { input: 0.80, output: 4 },
18
+ 'default': { input: 3, output: 15 },
19
+ };
20
+
21
+ function getModelFamily(model: string): string {
22
+ if (!model) return 'unknown';
23
+ if (model.includes('opus')) return 'claude-opus-4';
24
+ if (model.includes('haiku')) return 'claude-haiku-4';
25
+ if (model.includes('sonnet')) return 'claude-sonnet-4';
26
+ return 'unknown';
27
+ }
28
+
29
+ function calcCost(family: string, input: number, output: number): number {
30
+ const p = PRICING[family] || PRICING['default'];
31
+ // Only count input + output tokens. Cache tokens excluded from cost estimate
32
+ // because subscriptions (Max/Pro) don't charge per-token for cache.
33
+ return (input * p.input / 1_000_000) + (output * p.output / 1_000_000);
34
+ }
35
+
36
+ function dirToProjectPath(dirName: string): string {
37
+ return dirName.replace(/^-/, '/').replace(/-/g, '/');
38
+ }
39
+
40
+ function dirToProjectName(dirName: string): string {
41
+ return dirToProjectPath(dirName).split('/').pop() || dirName;
42
+ }
43
+
44
+ /** Get local date string from UTC timestamp */
45
+ function toLocalDate(ts: string): string {
46
+ if (!ts) return 'unknown';
47
+ try {
48
+ const d = new Date(ts);
49
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
50
+ } catch {
51
+ return ts.slice(0, 10) || 'unknown';
52
+ }
53
+ }
54
+
55
+ interface DayModelBucket {
56
+ input: number; output: number; cacheRead: number; cacheCreate: number; count: number;
57
+ }
58
+
59
+ /** Parse JSONL and aggregate by day + model */
60
+ function parseByDayModel(content: string): Map<string, DayModelBucket> {
61
+ // key: "day|model"
62
+ const buckets = new Map<string, DayModelBucket>();
63
+
64
+ for (const line of content.split('\n')) {
65
+ if (!line.trim()) continue;
66
+ try {
67
+ const obj = JSON.parse(line);
68
+ if (obj.type === 'assistant' && obj.message?.usage) {
69
+ const u = obj.message.usage;
70
+ const model = getModelFamily(obj.message.model || '');
71
+ const day = toLocalDate(obj.timestamp || '');
72
+ const key = `${day}|${model}`;
73
+
74
+ let b = buckets.get(key);
75
+ if (!b) { b = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0, count: 0 }; buckets.set(key, b); }
76
+ b.input += u.input_tokens || 0;
77
+ b.output += u.output_tokens || 0;
78
+ b.cacheRead += u.cache_read_input_tokens || 0;
79
+ b.cacheCreate += u.cache_creation_input_tokens || 0;
80
+ b.count++;
81
+ }
82
+ } catch {}
83
+ }
84
+ return buckets;
85
+ }
86
+
87
+ /** Scan all JSONL files */
88
+ export function scanUsage(): { scanned: number; updated: number; errors: number } {
89
+ const projectsDir = join(getClaudeDir(), 'projects');
90
+ let scanned = 0, updated = 0, errors = 0;
91
+
92
+ let projectDirs: string[];
93
+ try { projectDirs = readdirSync(projectsDir); } catch { return { scanned: 0, updated: 0, errors: 0 }; }
94
+
95
+ const upsert = db().prepare(`
96
+ INSERT INTO token_usage (session_id, source, project_path, project_name, model, day, input_tokens, output_tokens, cache_read_tokens, cache_create_tokens, cost_usd, message_count)
97
+ VALUES (?, 'terminal', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
98
+ ON CONFLICT(session_id, source, model, day) DO UPDATE SET
99
+ input_tokens = excluded.input_tokens, output_tokens = excluded.output_tokens,
100
+ cache_read_tokens = excluded.cache_read_tokens, cache_create_tokens = excluded.cache_create_tokens,
101
+ cost_usd = excluded.cost_usd, message_count = excluded.message_count
102
+ `);
103
+
104
+ const getScanState = db().prepare('SELECT last_size FROM usage_scan_state WHERE file_path = ?');
105
+ const setScanState = db().prepare(`
106
+ INSERT INTO usage_scan_state (file_path, last_size) VALUES (?, ?)
107
+ ON CONFLICT(file_path) DO UPDATE SET last_size = excluded.last_size, last_scan = datetime('now')
108
+ `);
109
+
110
+ for (const projDir of projectDirs) {
111
+ const projPath = join(projectsDir, projDir);
112
+ try { if (!statSync(projPath).isDirectory()) continue; } catch { continue; }
113
+
114
+ const projectPath = dirToProjectPath(projDir);
115
+ const projectName = dirToProjectName(projDir);
116
+ let files: string[];
117
+ try { files = readdirSync(projPath).filter(f => f.endsWith('.jsonl') && !f.startsWith('agent-')); } catch { continue; }
118
+
119
+ for (const file of files) {
120
+ const filePath = join(projPath, file);
121
+ const sessionId = basename(file, '.jsonl');
122
+ scanned++;
123
+
124
+ try {
125
+ const currentSize = statSync(filePath).size;
126
+ const scanState = getScanState.get(filePath) as { last_size: number } | undefined;
127
+ if (currentSize === (scanState?.last_size || 0)) continue;
128
+
129
+ const content = readFileSync(filePath, 'utf-8');
130
+ const buckets = parseByDayModel(content);
131
+
132
+ if (buckets.size === 0) { setScanState.run(filePath, currentSize); continue; }
133
+
134
+ for (const [key, b] of buckets) {
135
+ const [day, model] = key.split('|');
136
+ const cost = calcCost(model, b.input, b.output);
137
+ upsert.run(sessionId, projectPath, projectName, model, day, b.input, b.output, b.cacheRead, b.cacheCreate, cost, b.count);
138
+ }
139
+
140
+ setScanState.run(filePath, currentSize);
141
+ updated++;
142
+ } catch { errors++; }
143
+ }
144
+ }
145
+ return { scanned, updated, errors };
146
+ }
147
+
148
+ /** Record usage from task/mobile/pipeline */
149
+ export function recordUsage(opts: {
150
+ sessionId: string;
151
+ source: 'task' | 'mobile' | 'pipeline';
152
+ projectPath: string;
153
+ projectName: string;
154
+ model: string;
155
+ inputTokens: number;
156
+ outputTokens: number;
157
+ cacheReadTokens?: number;
158
+ cacheCreateTokens?: number;
159
+ taskId?: string;
160
+ }): void {
161
+ const family = getModelFamily(opts.model);
162
+ const cost = calcCost(family, opts.inputTokens, opts.outputTokens);
163
+ const day = toLocalDate(new Date().toISOString());
164
+
165
+ db().prepare(`
166
+ INSERT INTO token_usage (session_id, source, project_path, project_name, model, day, input_tokens, output_tokens, cache_read_tokens, cache_create_tokens, cost_usd, message_count, task_id)
167
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)
168
+ ON CONFLICT(session_id, source, model, day) DO UPDATE SET
169
+ input_tokens = token_usage.input_tokens + excluded.input_tokens,
170
+ output_tokens = token_usage.output_tokens + excluded.output_tokens,
171
+ cost_usd = token_usage.cost_usd + excluded.cost_usd,
172
+ message_count = token_usage.message_count + 1
173
+ `).run(opts.sessionId, opts.source, opts.projectPath, opts.projectName, family, day, opts.inputTokens, opts.outputTokens, opts.cacheReadTokens || 0, opts.cacheCreateTokens || 0, cost, opts.taskId || null);
174
+ }
175
+
176
+ /** Query usage data */
177
+ export function queryUsage(opts: {
178
+ days?: number;
179
+ projectName?: string;
180
+ source?: string;
181
+ model?: string;
182
+ }): {
183
+ total: { input: number; output: number; cost: number; sessions: number; messages: number };
184
+ byProject: { name: string; input: number; output: number; cost: number; sessions: number }[];
185
+ byModel: { model: string; input: number; output: number; cost: number; messages: number }[];
186
+ byDay: { date: string; input: number; output: number; cost: number }[];
187
+ bySource: { source: string; input: number; output: number; cost: number; messages: number }[];
188
+ } {
189
+ let where = '1=1';
190
+ const params: any[] = [];
191
+
192
+ if (opts.days) {
193
+ const cutoff = new Date();
194
+ cutoff.setDate(cutoff.getDate() - opts.days);
195
+ const cutoffDay = `${cutoff.getFullYear()}-${String(cutoff.getMonth() + 1).padStart(2, '0')}-${String(cutoff.getDate()).padStart(2, '0')}`;
196
+ where += ' AND day >= ?';
197
+ params.push(cutoffDay);
198
+ }
199
+ if (opts.projectName) { where += ' AND project_name = ?'; params.push(opts.projectName); }
200
+ if (opts.source) { where += ' AND source = ?'; params.push(opts.source); }
201
+ if (opts.model) { where += ' AND model = ?'; params.push(opts.model); }
202
+
203
+ const totalRow = db().prepare(`
204
+ SELECT COALESCE(SUM(input_tokens), 0) as input, COALESCE(SUM(output_tokens), 0) as output,
205
+ COALESCE(SUM(cost_usd), 0) as cost, COUNT(DISTINCT session_id) as sessions,
206
+ COALESCE(SUM(message_count), 0) as messages
207
+ FROM token_usage WHERE ${where}
208
+ `).get(...params) as any;
209
+
210
+ const byProject = (db().prepare(`
211
+ SELECT project_name as name, SUM(input_tokens) as input, SUM(output_tokens) as output,
212
+ SUM(cost_usd) as cost, COUNT(DISTINCT session_id) as sessions
213
+ FROM token_usage WHERE ${where}
214
+ GROUP BY project_name ORDER BY cost DESC LIMIT 20
215
+ `).all(...params) as any[]).map(r => ({
216
+ name: r.name, input: r.input, output: r.output, cost: +r.cost.toFixed(4), sessions: r.sessions,
217
+ }));
218
+
219
+ const byModel = (db().prepare(`
220
+ SELECT model, SUM(input_tokens) as input, SUM(output_tokens) as output,
221
+ SUM(cost_usd) as cost, SUM(message_count) as messages
222
+ FROM token_usage WHERE ${where}
223
+ GROUP BY model ORDER BY cost DESC
224
+ `).all(...params) as any[]).map(r => ({
225
+ model: r.model, input: r.input, output: r.output, cost: +r.cost.toFixed(4), messages: r.messages,
226
+ }));
227
+
228
+ const byDay = (db().prepare(`
229
+ SELECT day as date, SUM(input_tokens) as input, SUM(output_tokens) as output, SUM(cost_usd) as cost
230
+ FROM token_usage WHERE ${where} AND day != 'unknown'
231
+ GROUP BY day ORDER BY day DESC LIMIT 30
232
+ `).all(...params) as any[]).map(r => ({
233
+ date: r.date, input: r.input, output: r.output, cost: +r.cost.toFixed(4),
234
+ }));
235
+
236
+ const bySource = (db().prepare(`
237
+ SELECT source, SUM(input_tokens) as input, SUM(output_tokens) as output,
238
+ SUM(cost_usd) as cost, SUM(message_count) as messages
239
+ FROM token_usage WHERE ${where}
240
+ GROUP BY source ORDER BY cost DESC
241
+ `).all(...params) as any[]).map(r => ({
242
+ source: r.source, input: r.input, output: r.output, cost: +r.cost.toFixed(4), messages: r.messages,
243
+ }));
244
+
245
+ return {
246
+ total: { input: totalRow.input, output: totalRow.output, cost: +totalRow.cost.toFixed(4), sessions: totalRow.sessions, messages: totalRow.messages },
247
+ byProject, byModel, byDay, bySource,
248
+ };
249
+ }
@@ -0,0 +1,388 @@
1
+ /**
2
+ * State Machine Tests — verify smith/task status transitions and message flow.
3
+ *
4
+ * Usage: npx tsx lib/workspace/__tests__/state-machine.test.ts
5
+ */
6
+
7
+ import assert from 'node:assert';
8
+ import { WorkspaceOrchestrator } from '../orchestrator';
9
+ import { AgentBus } from '../agent-bus';
10
+ import type { WorkspaceAgentConfig, AgentState, BusMessage, WorkerEvent } from '../types';
11
+
12
+ const TEST_WS = 'test-sm-' + Date.now();
13
+ const TEST_PATH = '/tmp/test-sm';
14
+
15
+ // ─── Helpers ─────────────────────────────────────────────
16
+
17
+ function createOrch(): WorkspaceOrchestrator {
18
+ return new WorkspaceOrchestrator(TEST_WS, TEST_PATH, 'test');
19
+ }
20
+
21
+ function addInput(orch: WorkspaceOrchestrator, id = 'input-1'): void {
22
+ orch.addAgent({
23
+ id, label: 'Requirements', icon: '📝', type: 'input',
24
+ content: '', entries: [], role: '', backend: 'cli',
25
+ dependsOn: [], outputs: [], steps: [],
26
+ });
27
+ }
28
+
29
+ function addAgent(orch: WorkspaceOrchestrator, id: string, label: string, dependsOn: string[]): void {
30
+ orch.addAgent({
31
+ id, label, icon: '🤖', role: 'test', backend: 'cli',
32
+ dependsOn, outputs: [], workDir: id, // unique workDir to avoid conflicts
33
+ steps: [{ id: 's1', label: 'Step 1', prompt: 'do something' }],
34
+ });
35
+ }
36
+
37
+ function getState(orch: WorkspaceOrchestrator, id: string): AgentState {
38
+ return orch.getAllAgentStates()[id];
39
+ }
40
+
41
+ function collectEvents(orch: WorkspaceOrchestrator): any[] {
42
+ const events: any[] = [];
43
+ orch.on('event', (e: any) => events.push(e));
44
+ return events;
45
+ }
46
+
47
+ let passed = 0;
48
+ let failed = 0;
49
+ let testNum = 0;
50
+
51
+ function ok(condition: boolean, msg: string) {
52
+ if (condition) {
53
+ console.log(` ✅ ${msg}`);
54
+ passed++;
55
+ } else {
56
+ console.log(` ❌ ${msg}`);
57
+ failed++;
58
+ }
59
+ }
60
+
61
+ function test(name: string, fn: () => void | Promise<void>) {
62
+ testNum++;
63
+ console.log(`\n📋 Test ${testNum}: ${name}`);
64
+ try {
65
+ const result = fn();
66
+ if (result instanceof Promise) {
67
+ return result.catch(e => {
68
+ console.log(` 💥 Crashed: ${e.message}`);
69
+ failed++;
70
+ });
71
+ }
72
+ } catch (e: any) {
73
+ console.log(` 💥 Crashed: ${e.message}`);
74
+ failed++;
75
+ }
76
+ }
77
+
78
+ // ─── Tests ───────────────────────────────────────────────
79
+
80
+ async function runAll() {
81
+ console.log('🧪 State Machine Tests\n');
82
+
83
+ // Test 1: Initial state
84
+ await test('Initial agent state', () => {
85
+ const orch = createOrch();
86
+ addAgent(orch, 'a1', 'Agent1', []);
87
+ const s = getState(orch, 'a1');
88
+ ok(s.smithStatus === 'down', `smithStatus = down (got ${s.smithStatus})`);
89
+ ok(s.taskStatus === 'idle', `taskStatus = idle (got ${s.taskStatus})`);
90
+ // mode field removed — execution method determined by tmuxSession presence
91
+ });
92
+
93
+ // Test 2: startDaemon sets all smiths to active
94
+ await test('startDaemon sets smithStatus=active', async () => {
95
+ const orch = createOrch();
96
+ addInput(orch);
97
+ addAgent(orch, 'pm', 'PM', ['input-1']);
98
+ addAgent(orch, 'eng', 'Engineer', ['pm']);
99
+
100
+ ok(getState(orch, 'pm').smithStatus === 'down', 'PM starts down');
101
+ ok(getState(orch, 'eng').smithStatus === 'down', 'Engineer starts down');
102
+
103
+ await orch.startDaemon();
104
+
105
+ ok(getState(orch, 'pm').smithStatus === 'active', 'PM active after startDaemon');
106
+ ok(getState(orch, 'eng').smithStatus === 'active', 'Engineer active after startDaemon');
107
+ ok(orch.isDaemonActive(), 'daemonActive = true');
108
+
109
+ orch.stopDaemon();
110
+ ok(getState(orch, 'pm').smithStatus === 'down', 'PM down after stopDaemon');
111
+ ok(getState(orch, 'eng').smithStatus === 'down', 'Engineer down after stopDaemon');
112
+ ok(!orch.isDaemonActive(), 'daemonActive = false');
113
+ });
114
+
115
+ // Test 3: stopDaemon preserves taskStatus
116
+ await test('stopDaemon preserves taskStatus', async () => {
117
+ const orch = createOrch();
118
+ addAgent(orch, 'a1', 'Agent1', []);
119
+
120
+ // Manually set to done to simulate completed agent
121
+ const states = orch.getAllAgentStates();
122
+ (states['a1'] as any).taskStatus = 'done';
123
+ // Hack: directly modify internal state for testing
124
+ (orch as any).agents.get('a1').state.taskStatus = 'done';
125
+
126
+ await orch.startDaemon();
127
+ ok(getState(orch, 'a1').taskStatus === 'done', 'taskStatus stays done after startDaemon');
128
+
129
+ orch.stopDaemon();
130
+ ok(getState(orch, 'a1').taskStatus === 'done', 'taskStatus stays done after stopDaemon');
131
+ });
132
+
133
+ // Test 4: loadSnapshot resets smith to down and pending messages to failed
134
+ await test('loadSnapshot resets state correctly', () => {
135
+ const orch = createOrch();
136
+
137
+ const busLog: BusMessage[] = [
138
+ { id: 'm1', from: 'a', to: 'b', type: 'notify', payload: { action: 'test' }, timestamp: Date.now(), status: 'pending' },
139
+ { id: 'm2', from: 'a', to: 'b', type: 'notify', payload: { action: 'test2' }, timestamp: Date.now(), status: 'done' },
140
+ { id: 'm3', from: 'a', to: 'b', type: 'notify', payload: { action: 'test3' }, timestamp: Date.now(), status: 'pending' },
141
+ ];
142
+
143
+ orch.loadSnapshot({
144
+ agents: [{
145
+ id: 'a1', label: 'Agent1', icon: '🤖', role: '', backend: 'cli',
146
+ dependsOn: [], outputs: [], workDir: 'a1',
147
+ steps: [{ id: 's1', label: 'Step1', prompt: 'test' }],
148
+ }],
149
+ agentStates: {
150
+ 'a1': {
151
+ smithStatus: 'active', taskStatus: 'running',
152
+ history: [], artifacts: [],
153
+ } as AgentState,
154
+ },
155
+ busLog,
156
+ });
157
+
158
+ const s = getState(orch, 'a1');
159
+ ok(s.smithStatus === 'down', 'smithStatus reset to down after load');
160
+ ok(s.taskStatus === 'failed', 'running taskStatus becomes failed after load');
161
+
162
+ // Check bus messages
163
+ const log = orch.getBusLog();
164
+ const m1 = log.find(m => m.id === 'm1');
165
+ const m2 = log.find(m => m.id === 'm2');
166
+ const m3 = log.find(m => m.id === 'm3');
167
+ ok(m1?.status === 'failed', 'pending message m1 marked failed');
168
+ ok(m2?.status === 'done', 'acked message m2 unchanged');
169
+ ok(m3?.status === 'failed', 'pending message m3 marked failed');
170
+ });
171
+
172
+ // Test 5: completeInput sends input_updated messages (no daemon to avoid CLI spawn)
173
+ await test('completeInput sends bus messages to downstream', () => {
174
+ const orch = createOrch();
175
+ addInput(orch);
176
+ addAgent(orch, 'pm', 'PM', ['input-1']);
177
+
178
+ // Don't start daemon — just test that completeInput sends bus messages
179
+ const busLog = orch.getBusLog();
180
+ const before = busLog.length;
181
+
182
+ orch.completeInput('input-1', 'Build a todo app');
183
+
184
+ const after = busLog.length;
185
+ ok(after > before, `New bus messages sent (${before} → ${after})`);
186
+
187
+ const inputMsgs = busLog.filter(m => m.payload.action === 'input_updated');
188
+ ok(inputMsgs.length > 0, 'input_updated message found');
189
+ ok(inputMsgs[0].from === 'input-1', 'from = input-1');
190
+ ok(inputMsgs[0].to === 'pm', 'to = pm');
191
+ // Smith is down so message stays pending (not acked)
192
+ ok(inputMsgs[0].status === 'pending', `message pending (smith down) — got ${inputMsgs[0].status}`);
193
+ });
194
+
195
+ // Test 6: broadcastCompletion sends upstream_complete (no daemon)
196
+ await test('broadcastCompletion sends upstream_complete', () => {
197
+ const orch = createOrch();
198
+ addInput(orch);
199
+ addAgent(orch, 'pm', 'PM', ['input-1']);
200
+ addAgent(orch, 'eng', 'Engineer', ['pm']);
201
+
202
+ // Set PM to done with artifacts
203
+ (orch as any).agents.get('pm').state.taskStatus = 'done';
204
+ (orch as any).agents.get('pm').state.artifacts = [{ type: 'file', path: 'docs/prd.md' }];
205
+ (orch as any).agents.get('pm').state.history = [
206
+ { type: 'result', subtype: 'final_summary', content: 'PRD completed', timestamp: new Date().toISOString() }
207
+ ];
208
+
209
+ (orch as any).broadcastCompletion('pm');
210
+
211
+ const busLog = orch.getBusLog();
212
+ const upstreamMsgs = busLog.filter(m => m.payload.action === 'upstream_complete' && m.from === 'pm');
213
+ ok(upstreamMsgs.length > 0, 'upstream_complete message sent');
214
+ ok(upstreamMsgs[0].to === 'eng', 'sent to Engineer');
215
+ ok(upstreamMsgs[0].payload.files?.includes('docs/prd.md') === true, 'includes file path');
216
+ });
217
+
218
+ // Test 7: Bus message ACK flow
219
+ await test('Bus message ACK: pending → acked on success, pending → failed on error', () => {
220
+ const bus = new AgentBus();
221
+ bus.setAgentStatus('a', 'alive');
222
+ bus.setAgentStatus('b', 'alive');
223
+
224
+ const msg = bus.send('a', 'b', 'notify', { action: 'test', content: 'hello' });
225
+ ok(msg.status === 'pending', 'starts as pending');
226
+
227
+ // Simulate ack
228
+ msg.status = 'done';
229
+ ok(msg.status === 'done', 'acked after processing');
230
+
231
+ // Test failed path
232
+ const msg2 = bus.send('a', 'b', 'notify', { action: 'test2', content: 'world' });
233
+ msg2.status = 'failed';
234
+ ok(msg2.status === 'failed', 'failed on error');
235
+
236
+ // Test retry
237
+ const retried = bus.retryMessage(msg2.id);
238
+ ok(retried !== null, 'retryMessage returns message');
239
+ ok(retried!.status === 'pending', 'retried message back to pending');
240
+ });
241
+
242
+ // Test 8: markAllPendingAsFailed
243
+ await test('markAllPendingAsFailed on restart', () => {
244
+ const bus = new AgentBus();
245
+ bus.setAgentStatus('a', 'alive');
246
+ bus.setAgentStatus('b', 'alive');
247
+
248
+ const m1 = bus.send('a', 'b', 'notify', { action: 'test1' });
249
+ const m2 = bus.send('a', 'b', 'notify', { action: 'test2' });
250
+ m1.status = 'done'; // already processed
251
+ // m2 stays pending
252
+
253
+ bus.markAllPendingAsFailed();
254
+
255
+ ok(m1.status === 'done', 'acked message unchanged');
256
+ ok(m2.status === 'failed', 'pending message marked failed');
257
+ });
258
+
259
+ // Test 9: Manual run (force) dep check logic — test validateCanRun vs force dep check
260
+ await test('Force run checks smith active, normal run checks taskStatus done', () => {
261
+ const orch = createOrch();
262
+ addInput(orch);
263
+ addAgent(orch, 'pm', 'PM', ['input-1']);
264
+ addAgent(orch, 'eng', 'Engineer', ['pm']);
265
+
266
+ // Normal validateCanRun: PM idle → should fail
267
+ let error1 = '';
268
+ try { orch.validateCanRun('eng'); } catch (e: any) { error1 = e.message; }
269
+ ok(error1.includes('not completed'), `Normal: rejects when PM idle`);
270
+
271
+ // PM done → should pass
272
+ (orch as any).agents.get('pm').state.taskStatus = 'done';
273
+ let error2 = '';
274
+ try { orch.validateCanRun('eng'); } catch (e: any) { error2 = e.message; }
275
+ ok(!error2, `Normal: passes when PM done`);
276
+
277
+ // Force dep check: PM smith down → should fail
278
+ (orch as any).agents.get('pm').state.taskStatus = 'idle';
279
+ (orch as any).agents.get('pm').state.smithStatus = 'down';
280
+ // Simulate the force dep check from runAgentDaemon
281
+ const config = (orch as any).agents.get('eng').config;
282
+ let forceError = '';
283
+ for (const depId of config.dependsOn) {
284
+ const dep = (orch as any).agents.get(depId);
285
+ if (dep && dep.config.type !== 'input' && dep.state.smithStatus !== 'active') {
286
+ forceError = `${dep.config.label} smith not active`;
287
+ }
288
+ }
289
+ ok(forceError.includes('not active'), `Force: rejects when PM smith down`);
290
+
291
+ // PM smith active → should pass
292
+ (orch as any).agents.get('pm').state.smithStatus = 'active';
293
+ forceError = '';
294
+ for (const depId of config.dependsOn) {
295
+ const dep = (orch as any).agents.get(depId);
296
+ if (dep && dep.config.type !== 'input' && dep.state.smithStatus !== 'active') {
297
+ forceError = `${dep.config.label} smith not active`;
298
+ }
299
+ }
300
+ ok(!forceError, `Force: passes when PM smith active`);
301
+ });
302
+
303
+ // Test 10: Auto trigger requires taskStatus=done (check via validateCanRun)
304
+ await test('Auto trigger requires dep taskStatus=done', () => {
305
+ const orch = createOrch();
306
+ addInput(orch);
307
+ addAgent(orch, 'pm', 'PM', ['input-1']);
308
+ addAgent(orch, 'eng', 'Engineer', ['pm']);
309
+
310
+ // PM is idle — validateCanRun for Engineer should fail
311
+ let error = '';
312
+ try {
313
+ orch.validateCanRun('eng');
314
+ } catch (e: any) {
315
+ error = e.message;
316
+ }
317
+ ok(error.includes('not completed'), `validateCanRun rejects when PM idle: ${error}`);
318
+
319
+ // Set PM to done — should pass
320
+ (orch as any).agents.get('pm').state.taskStatus = 'done';
321
+ let error2 = '';
322
+ try {
323
+ orch.validateCanRun('eng');
324
+ } catch (e: any) {
325
+ error2 = e.message;
326
+ }
327
+ ok(!error2, `validateCanRun passes when PM done (got: ${error2 || 'no error'})`);
328
+ });
329
+
330
+ // Test 11: taskStatus transitions - never goes from done to idle
331
+ await test('taskStatus never goes from done→idle', async () => {
332
+ const orch = createOrch();
333
+ addAgent(orch, 'a1', 'Agent1', []);
334
+
335
+ // Set to done
336
+ (orch as any).agents.get('a1').state.taskStatus = 'done';
337
+ (orch as any).agents.get('a1').state.smithStatus = 'active';
338
+
339
+ // Stop daemon should NOT change taskStatus
340
+ await orch.startDaemon();
341
+ orch.stopDaemon();
342
+
343
+ ok(getState(orch, 'a1').taskStatus === 'done',
344
+ `taskStatus stays done after stop (got ${getState(orch, 'a1').taskStatus})`);
345
+ });
346
+
347
+ // Test 12: startDaemon doesn't re-send input messages
348
+ await test('startDaemon does NOT broadcast existing input', async () => {
349
+ const orch = createOrch();
350
+ addInput(orch);
351
+ addAgent(orch, 'pm', 'PM', ['input-1']);
352
+
353
+ // Complete input first
354
+ orch.completeInput('input-1', 'test content');
355
+
356
+ const busLogBefore = orch.getBusLog().length;
357
+
358
+ await orch.startDaemon();
359
+
360
+ const busLogAfter = orch.getBusLog().length;
361
+ const newMsgs = orch.getBusLog().slice(busLogBefore);
362
+ const inputMsgs = newMsgs.filter(m => m.payload.action === 'input_updated');
363
+
364
+ ok(inputMsgs.length === 0, `No input_updated sent by startDaemon (got ${inputMsgs.length})`);
365
+
366
+ orch.stopDaemon();
367
+ });
368
+
369
+ // ─── Summary ──────────────────────────────────────────
370
+
371
+ console.log('\n' + '═'.repeat(40));
372
+ console.log(`Results: ${passed} passed, ${failed} failed`);
373
+ console.log('═'.repeat(40));
374
+
375
+ console.log('');
376
+ if (failed > 0) {
377
+ process.exit(1);
378
+ } else {
379
+ process.exit(0);
380
+ }
381
+ }
382
+
383
+ // Force exit after 8s in case of dangling timers/workers
384
+ setTimeout(() => {
385
+ console.log('\n(Force exit due to dangling timers)');
386
+ process.exit(passed > 0 && failed === 0 ? 0 : 1);
387
+ }, 8000);
388
+ runAll();