@gokulvenkatareddy/cortex 0.1.7

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 (299) hide show
  1. package/README.md +1295 -0
  2. package/apps/octogent/.github/workflows/ci.yml +40 -0
  3. package/apps/octogent/.shims/claude +4 -0
  4. package/apps/octogent/AGENTS.md +71 -0
  5. package/apps/octogent/CONTRIBUTING.md +72 -0
  6. package/apps/octogent/LICENSE +21 -0
  7. package/apps/octogent/README.md +184 -0
  8. package/apps/octogent/apps/api/AGENTS.md +32 -0
  9. package/apps/octogent/apps/api/package.json +19 -0
  10. package/apps/octogent/apps/api/src/agentStateDetection.ts +181 -0
  11. package/apps/octogent/apps/api/src/claudeSessionScanner.ts +235 -0
  12. package/apps/octogent/apps/api/src/claudeSkills.ts +182 -0
  13. package/apps/octogent/apps/api/src/claudeUsage.ts +922 -0
  14. package/apps/octogent/apps/api/src/cli.ts +595 -0
  15. package/apps/octogent/apps/api/src/codeIntelStore.ts +46 -0
  16. package/apps/octogent/apps/api/src/codexUsage.ts +278 -0
  17. package/apps/octogent/apps/api/src/createApiServer/codeIntelRoutes.ts +60 -0
  18. package/apps/octogent/apps/api/src/createApiServer/conversationRoutes.ts +128 -0
  19. package/apps/octogent/apps/api/src/createApiServer/deckRoutes.ts +873 -0
  20. package/apps/octogent/apps/api/src/createApiServer/gitParsers.ts +140 -0
  21. package/apps/octogent/apps/api/src/createApiServer/gitRoutes.ts +214 -0
  22. package/apps/octogent/apps/api/src/createApiServer/miscRoutes.ts +316 -0
  23. package/apps/octogent/apps/api/src/createApiServer/monitorParsers.ts +137 -0
  24. package/apps/octogent/apps/api/src/createApiServer/monitorRoutes.ts +95 -0
  25. package/apps/octogent/apps/api/src/createApiServer/requestHandler.ts +311 -0
  26. package/apps/octogent/apps/api/src/createApiServer/requestParsers.ts +25 -0
  27. package/apps/octogent/apps/api/src/createApiServer/routeHelpers.ts +97 -0
  28. package/apps/octogent/apps/api/src/createApiServer/security.ts +70 -0
  29. package/apps/octogent/apps/api/src/createApiServer/terminalParsers.ts +167 -0
  30. package/apps/octogent/apps/api/src/createApiServer/terminalRoutes.ts +315 -0
  31. package/apps/octogent/apps/api/src/createApiServer/types.ts +24 -0
  32. package/apps/octogent/apps/api/src/createApiServer/uiStateParsers.ts +255 -0
  33. package/apps/octogent/apps/api/src/createApiServer/upgradeHandler.ts +38 -0
  34. package/apps/octogent/apps/api/src/createApiServer/usageRoutes.ts +84 -0
  35. package/apps/octogent/apps/api/src/createApiServer.ts +176 -0
  36. package/apps/octogent/apps/api/src/deck/readDeckTentacles.ts +595 -0
  37. package/apps/octogent/apps/api/src/githubRepoSummary.ts +397 -0
  38. package/apps/octogent/apps/api/src/logging.ts +9 -0
  39. package/apps/octogent/apps/api/src/monitor/defaults.ts +3 -0
  40. package/apps/octogent/apps/api/src/monitor/index.ts +8 -0
  41. package/apps/octogent/apps/api/src/monitor/repository.ts +303 -0
  42. package/apps/octogent/apps/api/src/monitor/service.ts +349 -0
  43. package/apps/octogent/apps/api/src/monitor/types.ts +120 -0
  44. package/apps/octogent/apps/api/src/monitor/xProvider.ts +587 -0
  45. package/apps/octogent/apps/api/src/projectPersistence.ts +377 -0
  46. package/apps/octogent/apps/api/src/prompts/index.ts +10 -0
  47. package/apps/octogent/apps/api/src/prompts/promptResolver.ts +145 -0
  48. package/apps/octogent/apps/api/src/runtimeMetadata.ts +69 -0
  49. package/apps/octogent/apps/api/src/server.ts +80 -0
  50. package/apps/octogent/apps/api/src/setupState.ts +80 -0
  51. package/apps/octogent/apps/api/src/setupStatus.ts +174 -0
  52. package/apps/octogent/apps/api/src/startupPrerequisites.ts +146 -0
  53. package/apps/octogent/apps/api/src/terminalRuntime/channelMessaging.ts +87 -0
  54. package/apps/octogent/apps/api/src/terminalRuntime/claudeTranscript.ts +279 -0
  55. package/apps/octogent/apps/api/src/terminalRuntime/constants.ts +15 -0
  56. package/apps/octogent/apps/api/src/terminalRuntime/conversations.ts +492 -0
  57. package/apps/octogent/apps/api/src/terminalRuntime/gitOperations.ts +341 -0
  58. package/apps/octogent/apps/api/src/terminalRuntime/hookProcessor.ts +405 -0
  59. package/apps/octogent/apps/api/src/terminalRuntime/protocol.ts +46 -0
  60. package/apps/octogent/apps/api/src/terminalRuntime/ptyEnvironment.ts +50 -0
  61. package/apps/octogent/apps/api/src/terminalRuntime/registry.ts +423 -0
  62. package/apps/octogent/apps/api/src/terminalRuntime/sessionRuntime.ts +671 -0
  63. package/apps/octogent/apps/api/src/terminalRuntime/systemClients.ts +432 -0
  64. package/apps/octogent/apps/api/src/terminalRuntime/types.ts +157 -0
  65. package/apps/octogent/apps/api/src/terminalRuntime/worktreeManager.ts +135 -0
  66. package/apps/octogent/apps/api/src/terminalRuntime.ts +567 -0
  67. package/apps/octogent/apps/api/src/usageUtils.ts +16 -0
  68. package/apps/octogent/apps/api/src/ws-shim.d.ts +28 -0
  69. package/apps/octogent/apps/api/tests/agentStateDetection.test.ts +67 -0
  70. package/apps/octogent/apps/api/tests/claudeUsage.test.ts +583 -0
  71. package/apps/octogent/apps/api/tests/codexUsage.test.ts +107 -0
  72. package/apps/octogent/apps/api/tests/createApiServer.test.ts +3207 -0
  73. package/apps/octogent/apps/api/tests/githubRepoSummary.test.ts +100 -0
  74. package/apps/octogent/apps/api/tests/logging.test.ts +33 -0
  75. package/apps/octogent/apps/api/tests/monitorApi.test.ts +467 -0
  76. package/apps/octogent/apps/api/tests/monitorCore.test.ts +104 -0
  77. package/apps/octogent/apps/api/tests/promptResolver.test.ts +109 -0
  78. package/apps/octogent/apps/api/tests/protocol.test.ts +14 -0
  79. package/apps/octogent/apps/api/tests/sessionRuntime.test.ts +608 -0
  80. package/apps/octogent/apps/api/tests/startupPrerequisites.test.ts +70 -0
  81. package/apps/octogent/apps/api/tests/upgradeHandler.test.ts +40 -0
  82. package/apps/octogent/apps/api/tests/xMonitorProvider.test.ts +109 -0
  83. package/apps/octogent/apps/api/tsconfig.json +7 -0
  84. package/apps/octogent/apps/api/vitest.config.ts +7 -0
  85. package/apps/octogent/apps/web/AGENTS.md +38 -0
  86. package/apps/octogent/apps/web/index.html +13 -0
  87. package/apps/octogent/apps/web/package.json +32 -0
  88. package/apps/octogent/apps/web/public/octopus-favicon.svg +26 -0
  89. package/apps/octogent/apps/web/src/App.tsx +646 -0
  90. package/apps/octogent/apps/web/src/app/canvas/types.ts +34 -0
  91. package/apps/octogent/apps/web/src/app/codeIntelAggregation.ts +278 -0
  92. package/apps/octogent/apps/web/src/app/constants.ts +28 -0
  93. package/apps/octogent/apps/web/src/app/conversationNormalizers.ts +135 -0
  94. package/apps/octogent/apps/web/src/app/formatTimestamp.ts +18 -0
  95. package/apps/octogent/apps/web/src/app/githubMetrics.ts +76 -0
  96. package/apps/octogent/apps/web/src/app/githubNormalizers.ts +91 -0
  97. package/apps/octogent/apps/web/src/app/hooks/useAgentRuntimeStates.ts +18 -0
  98. package/apps/octogent/apps/web/src/app/hooks/useBackendLivenessPolling.ts +53 -0
  99. package/apps/octogent/apps/web/src/app/hooks/useCanvasGraphData.ts +449 -0
  100. package/apps/octogent/apps/web/src/app/hooks/useCanvasTransform.ts +260 -0
  101. package/apps/octogent/apps/web/src/app/hooks/useClaudeUsagePolling.ts +40 -0
  102. package/apps/octogent/apps/web/src/app/hooks/useClickOutside.ts +30 -0
  103. package/apps/octogent/apps/web/src/app/hooks/useCodeIntelRuntime.ts +83 -0
  104. package/apps/octogent/apps/web/src/app/hooks/useCodexUsagePolling.ts +35 -0
  105. package/apps/octogent/apps/web/src/app/hooks/useConsoleKeyboardShortcuts.ts +31 -0
  106. package/apps/octogent/apps/web/src/app/hooks/useConversationsRuntime.ts +377 -0
  107. package/apps/octogent/apps/web/src/app/hooks/useForceSimulation.ts +319 -0
  108. package/apps/octogent/apps/web/src/app/hooks/useGitHubPrimaryViewModel.ts +143 -0
  109. package/apps/octogent/apps/web/src/app/hooks/useGithubSummaryPolling.ts +28 -0
  110. package/apps/octogent/apps/web/src/app/hooks/useInitialColumnsHydration.ts +64 -0
  111. package/apps/octogent/apps/web/src/app/hooks/useMonitorRuntime.ts +220 -0
  112. package/apps/octogent/apps/web/src/app/hooks/usePersistedUiState.ts +536 -0
  113. package/apps/octogent/apps/web/src/app/hooks/usePollingData.ts +79 -0
  114. package/apps/octogent/apps/web/src/app/hooks/usePromptLibrary.ts +185 -0
  115. package/apps/octogent/apps/web/src/app/hooks/useTentacleGitLifecycle.ts +530 -0
  116. package/apps/octogent/apps/web/src/app/hooks/useTerminalCompletionNotification.ts +94 -0
  117. package/apps/octogent/apps/web/src/app/hooks/useTerminalMutations.ts +266 -0
  118. package/apps/octogent/apps/web/src/app/hooks/useTerminalStateReconciliation.ts +23 -0
  119. package/apps/octogent/apps/web/src/app/hooks/useUsageHeatmapPolling.ts +43 -0
  120. package/apps/octogent/apps/web/src/app/hooks/useWorkspaceSetup.ts +80 -0
  121. package/apps/octogent/apps/web/src/app/hotkeys.ts +31 -0
  122. package/apps/octogent/apps/web/src/app/monitorNormalizers.ts +145 -0
  123. package/apps/octogent/apps/web/src/app/notificationSounds.ts +164 -0
  124. package/apps/octogent/apps/web/src/app/terminalRuntimeStateStore.ts +261 -0
  125. package/apps/octogent/apps/web/src/app/terminalState.ts +21 -0
  126. package/apps/octogent/apps/web/src/app/types.ts +42 -0
  127. package/apps/octogent/apps/web/src/app/uiStateNormalizers.ts +113 -0
  128. package/apps/octogent/apps/web/src/app/usageNormalizers.ts +58 -0
  129. package/apps/octogent/apps/web/src/components/ActiveAgentsSidebar.tsx +60 -0
  130. package/apps/octogent/apps/web/src/components/ActivityPrimaryView.tsx +21 -0
  131. package/apps/octogent/apps/web/src/components/AgentStateBadge.tsx +47 -0
  132. package/apps/octogent/apps/web/src/components/CanvasPrimaryView.tsx +1532 -0
  133. package/apps/octogent/apps/web/src/components/ClearAllConversationsDialog.tsx +33 -0
  134. package/apps/octogent/apps/web/src/components/CodeIntelArcDiagram.tsx +245 -0
  135. package/apps/octogent/apps/web/src/components/CodeIntelPrimaryView.tsx +104 -0
  136. package/apps/octogent/apps/web/src/components/CodeIntelTreemap.tsx +138 -0
  137. package/apps/octogent/apps/web/src/components/ConsolePrimaryNav.tsx +31 -0
  138. package/apps/octogent/apps/web/src/components/ConversationsPrimaryView.tsx +243 -0
  139. package/apps/octogent/apps/web/src/components/DeckPrimaryView.tsx +613 -0
  140. package/apps/octogent/apps/web/src/components/DeleteTentacleDialog.tsx +91 -0
  141. package/apps/octogent/apps/web/src/components/EmptyOctopus.tsx +715 -0
  142. package/apps/octogent/apps/web/src/components/GitHubPrimaryView.tsx +494 -0
  143. package/apps/octogent/apps/web/src/components/MonitorPrimaryView.tsx +475 -0
  144. package/apps/octogent/apps/web/src/components/PrimaryViewRouter.tsx +99 -0
  145. package/apps/octogent/apps/web/src/components/PromptsPrimaryView.tsx +243 -0
  146. package/apps/octogent/apps/web/src/components/RuntimeStatusStrip.tsx +273 -0
  147. package/apps/octogent/apps/web/src/components/SettingsPrimaryView.tsx +92 -0
  148. package/apps/octogent/apps/web/src/components/SidebarActionPanel.tsx +124 -0
  149. package/apps/octogent/apps/web/src/components/SidebarConversationsList.tsx +279 -0
  150. package/apps/octogent/apps/web/src/components/SidebarPromptsList.tsx +116 -0
  151. package/apps/octogent/apps/web/src/components/TelemetryTape.tsx +106 -0
  152. package/apps/octogent/apps/web/src/components/TentacleGitActionsDialog.tsx +341 -0
  153. package/apps/octogent/apps/web/src/components/Terminal.tsx +524 -0
  154. package/apps/octogent/apps/web/src/components/TerminalPromptPicker.tsx +140 -0
  155. package/apps/octogent/apps/web/src/components/UsageHeatmap.tsx +702 -0
  156. package/apps/octogent/apps/web/src/components/canvas/CanvasTentaclePanel.tsx +485 -0
  157. package/apps/octogent/apps/web/src/components/canvas/CanvasTerminalColumn.tsx +89 -0
  158. package/apps/octogent/apps/web/src/components/canvas/DeleteAllTerminalsDialog.tsx +221 -0
  159. package/apps/octogent/apps/web/src/components/canvas/OctopusNode.tsx +307 -0
  160. package/apps/octogent/apps/web/src/components/canvas/SessionNode.tsx +185 -0
  161. package/apps/octogent/apps/web/src/components/deck/ActionCards.tsx +118 -0
  162. package/apps/octogent/apps/web/src/components/deck/AddTentacleForm.tsx +269 -0
  163. package/apps/octogent/apps/web/src/components/deck/DeckBottomActions.tsx +56 -0
  164. package/apps/octogent/apps/web/src/components/deck/TentaclePod.tsx +334 -0
  165. package/apps/octogent/apps/web/src/components/deck/WorkspaceSetupCard.tsx +105 -0
  166. package/apps/octogent/apps/web/src/components/deck/octopusVisuals.ts +72 -0
  167. package/apps/octogent/apps/web/src/components/terminalReplay.ts +62 -0
  168. package/apps/octogent/apps/web/src/components/terminalWheel.ts +54 -0
  169. package/apps/octogent/apps/web/src/components/ui/ActionButton.tsx +34 -0
  170. package/apps/octogent/apps/web/src/components/ui/ConfirmationDialog.tsx +86 -0
  171. package/apps/octogent/apps/web/src/components/ui/MarkdownContent.tsx +43 -0
  172. package/apps/octogent/apps/web/src/components/ui/SettingsToggle.tsx +34 -0
  173. package/apps/octogent/apps/web/src/components/ui/StatusBadge.tsx +24 -0
  174. package/apps/octogent/apps/web/src/main.tsx +17 -0
  175. package/apps/octogent/apps/web/src/runtime/HttpTerminalSnapshotReader.ts +87 -0
  176. package/apps/octogent/apps/web/src/runtime/runtimeEndpoints.ts +412 -0
  177. package/apps/octogent/apps/web/src/styles/chrome-and-buttons.css +272 -0
  178. package/apps/octogent/apps/web/src/styles/console-canvas-activity.css +358 -0
  179. package/apps/octogent/apps/web/src/styles/console-canvas-canvas.css +1843 -0
  180. package/apps/octogent/apps/web/src/styles/console-canvas-code-intel.css +227 -0
  181. package/apps/octogent/apps/web/src/styles/console-canvas-conversations.css +705 -0
  182. package/apps/octogent/apps/web/src/styles/console-canvas-deck.css +1524 -0
  183. package/apps/octogent/apps/web/src/styles/console-canvas-github.css +541 -0
  184. package/apps/octogent/apps/web/src/styles/console-canvas-monitor.css +595 -0
  185. package/apps/octogent/apps/web/src/styles/console-canvas-pixpack.css +81 -0
  186. package/apps/octogent/apps/web/src/styles/console-canvas-prompts.css +474 -0
  187. package/apps/octogent/apps/web/src/styles/console-canvas-settings.css +207 -0
  188. package/apps/octogent/apps/web/src/styles/console-chrome-status-nav.css +441 -0
  189. package/apps/octogent/apps/web/src/styles/console-overrides-telemetry.css +320 -0
  190. package/apps/octogent/apps/web/src/styles/console-theme-tokens.css +25 -0
  191. package/apps/octogent/apps/web/src/styles/cortex-theme.css +412 -0
  192. package/apps/octogent/apps/web/src/styles/foundation.css +100 -0
  193. package/apps/octogent/apps/web/src/styles/sidebar-and-scrollbars.css +447 -0
  194. package/apps/octogent/apps/web/src/styles/terminal-and-status.css +356 -0
  195. package/apps/octogent/apps/web/src/styles.css +25 -0
  196. package/apps/octogent/apps/web/src/types/ws.d.ts +23 -0
  197. package/apps/octogent/apps/web/tests/CanvasPrimaryView.test.tsx +347 -0
  198. package/apps/octogent/apps/web/tests/HttpTerminalSnapshotReader.test.tsx +54 -0
  199. package/apps/octogent/apps/web/tests/RuntimeStatusStrip.test.tsx +70 -0
  200. package/apps/octogent/apps/web/tests/Terminal.test.tsx +87 -0
  201. package/apps/octogent/apps/web/tests/add-tentacle-form.test.tsx +48 -0
  202. package/apps/octogent/apps/web/tests/app-github-runtime.test.tsx +162 -0
  203. package/apps/octogent/apps/web/tests/app-monitor-runtime.test.tsx +657 -0
  204. package/apps/octogent/apps/web/tests/app-shell-navigation.test.tsx +109 -0
  205. package/apps/octogent/apps/web/tests/app-swarm-refresh.test.tsx +268 -0
  206. package/apps/octogent/apps/web/tests/app-ui-state-persistence.test.tsx +116 -0
  207. package/apps/octogent/apps/web/tests/app-workspace-setup.test.tsx +217 -0
  208. package/apps/octogent/apps/web/tests/canvas-tentacle-panel.test.tsx +195 -0
  209. package/apps/octogent/apps/web/tests/delete-all-terminals-dialog.test.tsx +76 -0
  210. package/apps/octogent/apps/web/tests/githubMetrics.test.tsx +52 -0
  211. package/apps/octogent/apps/web/tests/hotkeys.test.tsx +44 -0
  212. package/apps/octogent/apps/web/tests/runtimeEndpoints.test.tsx +240 -0
  213. package/apps/octogent/apps/web/tests/setup.ts +39 -0
  214. package/apps/octogent/apps/web/tests/tentacle-pod.test.tsx +62 -0
  215. package/apps/octogent/apps/web/tests/terminalReplay.test.ts +71 -0
  216. package/apps/octogent/apps/web/tests/terminalState.test.tsx +49 -0
  217. package/apps/octogent/apps/web/tests/terminalWheel.test.tsx +51 -0
  218. package/apps/octogent/apps/web/tests/test-utils/appTestHarness.ts +48 -0
  219. package/apps/octogent/apps/web/tests/uiPrimitives.test.tsx +31 -0
  220. package/apps/octogent/apps/web/tests/useAgentRuntimeStates.test.tsx +47 -0
  221. package/apps/octogent/apps/web/tsconfig.json +8 -0
  222. package/apps/octogent/apps/web/vite.api.bundle.config.mts +32 -0
  223. package/apps/octogent/apps/web/vite.config.ts +22 -0
  224. package/apps/octogent/bin/octogent +3 -0
  225. package/apps/octogent/biome.json +21 -0
  226. package/apps/octogent/docs/concepts/mental-model.md +79 -0
  227. package/apps/octogent/docs/concepts/runtime-and-api.md +60 -0
  228. package/apps/octogent/docs/concepts/tentacles.md +85 -0
  229. package/apps/octogent/docs/getting-started/installation.md +54 -0
  230. package/apps/octogent/docs/getting-started/quickstart.md +79 -0
  231. package/apps/octogent/docs/guides/inter-agent-messaging.md +43 -0
  232. package/apps/octogent/docs/guides/orchestrating-child-agents.md +49 -0
  233. package/apps/octogent/docs/guides/working-with-todos.md +56 -0
  234. package/apps/octogent/docs/index.md +40 -0
  235. package/apps/octogent/docs/reference/api.md +103 -0
  236. package/apps/octogent/docs/reference/cli.md +71 -0
  237. package/apps/octogent/docs/reference/experimental-features.md +28 -0
  238. package/apps/octogent/docs/reference/filesystem-layout.md +62 -0
  239. package/apps/octogent/docs/reference/troubleshooting.md +49 -0
  240. package/apps/octogent/package.json +35 -0
  241. package/apps/octogent/packages/core/AGENTS.md +31 -0
  242. package/apps/octogent/packages/core/package.json +12 -0
  243. package/apps/octogent/packages/core/src/adapters/InMemoryTerminalSnapshotReader.ts +10 -0
  244. package/apps/octogent/packages/core/src/application/buildTerminalList.ts +13 -0
  245. package/apps/octogent/packages/core/src/domain/agentRuntime.ts +18 -0
  246. package/apps/octogent/packages/core/src/domain/channel.ts +8 -0
  247. package/apps/octogent/packages/core/src/domain/completionSound.ts +14 -0
  248. package/apps/octogent/packages/core/src/domain/conversation.ts +48 -0
  249. package/apps/octogent/packages/core/src/domain/deck.ts +33 -0
  250. package/apps/octogent/packages/core/src/domain/git.ts +32 -0
  251. package/apps/octogent/packages/core/src/domain/monitor.ts +62 -0
  252. package/apps/octogent/packages/core/src/domain/setup.ts +27 -0
  253. package/apps/octogent/packages/core/src/domain/terminal.ts +17 -0
  254. package/apps/octogent/packages/core/src/domain/uiState.ts +22 -0
  255. package/apps/octogent/packages/core/src/domain/usage.ts +60 -0
  256. package/apps/octogent/packages/core/src/index.ts +15 -0
  257. package/apps/octogent/packages/core/src/ports/TerminalSnapshotReader.ts +5 -0
  258. package/apps/octogent/packages/core/src/util/typeCoercion.ts +20 -0
  259. package/apps/octogent/packages/core/tests/buildTerminalList.test.ts +75 -0
  260. package/apps/octogent/packages/core/tsconfig.json +7 -0
  261. package/apps/octogent/packages/core/tsconfig.tsbuildinfo +1 -0
  262. package/apps/octogent/packages/core/vitest.config.ts +7 -0
  263. package/apps/octogent/pnpm-lock.yaml +3212 -0
  264. package/apps/octogent/pnpm-workspace.yaml +3 -0
  265. package/apps/octogent/prompts/meta-prompt-generator.md +223 -0
  266. package/apps/octogent/prompts/octoboss-clean-contexts.md +30 -0
  267. package/apps/octogent/prompts/octoboss-reorganize-tentacles.md +29 -0
  268. package/apps/octogent/prompts/octoboss-reorganize-todos.md +27 -0
  269. package/apps/octogent/prompts/sandbox-init.md +3 -0
  270. package/apps/octogent/prompts/swarm-parent.md +83 -0
  271. package/apps/octogent/prompts/swarm-worker.md +50 -0
  272. package/apps/octogent/prompts/tentacle-context-init.md +1 -0
  273. package/apps/octogent/prompts/tentacle-planner.md +110 -0
  274. package/apps/octogent/prompts/tentacle-reorganize-todos.md +20 -0
  275. package/apps/octogent/prompts/tentacle-update-tentacle.md +18 -0
  276. package/apps/octogent/scripts/build-package.mjs +23 -0
  277. package/apps/octogent/scripts/dev.mjs +158 -0
  278. package/apps/octogent/scripts/smoke-public-install.mjs +271 -0
  279. package/apps/octogent/static/images/octogent-header.png +0 -0
  280. package/apps/octogent/static/images/preview_1.jpg +0 -0
  281. package/apps/octogent/static/images/preview_2.jpg +0 -0
  282. package/apps/octogent/static/images/preview_3.jpg +0 -0
  283. package/apps/octogent/static/images/preview_4.jpg +0 -0
  284. package/apps/octogent/static/images/preview_5.jpg +0 -0
  285. package/apps/octogent/static/images/preview_6.jpg +0 -0
  286. package/apps/octogent/tsconfig.base.json +16 -0
  287. package/bin/AGI +3 -0
  288. package/bin/AGI-install-app +71 -0
  289. package/bin/AGI-ui +16 -0
  290. package/bin/AGI-voice +15 -0
  291. package/bin/AGI-web +16 -0
  292. package/bin/cortex +109 -0
  293. package/bin/cortex-octogent +99 -0
  294. package/bin/import-specifier.mjs +13 -0
  295. package/bin/import-specifier.test.mjs +13 -0
  296. package/bin/octo +150 -0
  297. package/dist/cli.mjs +555650 -0
  298. package/package.json +157 -0
  299. package/scripts/setup-wizard.ts +390 -0
@@ -0,0 +1,608 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
3
+ import type { IncomingMessage } from "node:http";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import type { Duplex } from "node:stream";
7
+
8
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
9
+
10
+ const { createShellEnvironmentMock, ensureSpawnHelperMock, spawnMock } = vi.hoisted(() => ({
11
+ createShellEnvironmentMock: vi.fn(() => ({})),
12
+ ensureSpawnHelperMock: vi.fn(),
13
+ spawnMock: vi.fn(),
14
+ }));
15
+
16
+ vi.mock("node-pty", () => ({
17
+ spawn: spawnMock,
18
+ }));
19
+
20
+ vi.mock("../src/terminalRuntime/ptyEnvironment", () => ({
21
+ createShellEnvironment: createShellEnvironmentMock,
22
+ ensureNodePtySpawnHelperExecutable: ensureSpawnHelperMock,
23
+ }));
24
+
25
+ import { createSessionRuntime } from "../src/terminalRuntime/sessionRuntime";
26
+ import type { PersistedTerminal, TerminalSession } from "../src/terminalRuntime/types";
27
+
28
+ class FakePty extends EventEmitter {
29
+ write = vi.fn();
30
+ resize = vi.fn();
31
+ kill = vi.fn();
32
+
33
+ onData(listener: (chunk: string) => void) {
34
+ this.on("data", listener);
35
+ return {
36
+ dispose: () => {
37
+ this.off("data", listener);
38
+ },
39
+ };
40
+ }
41
+
42
+ onExit(listener: (event: { exitCode: number; signal: number }) => void) {
43
+ this.on("exit", listener);
44
+ return {
45
+ dispose: () => {
46
+ this.off("exit", listener);
47
+ },
48
+ };
49
+ }
50
+
51
+ emitData(chunk: string) {
52
+ this.emit("data", chunk);
53
+ }
54
+ }
55
+
56
+ class FakeWebSocket extends EventEmitter {
57
+ readyState = 1;
58
+ sentMessages: string[] = [];
59
+ send = vi.fn((payload: string) => {
60
+ this.sentMessages.push(payload);
61
+ });
62
+ close = vi.fn(() => {
63
+ if (this.readyState !== 1) {
64
+ return;
65
+ }
66
+
67
+ this.readyState = 3;
68
+ this.emit("close");
69
+ });
70
+ }
71
+
72
+ class FakeWebSocketServer {
73
+ nextSocket: FakeWebSocket | null = null;
74
+
75
+ handleUpgrade = vi.fn(
76
+ (
77
+ _request: IncomingMessage,
78
+ _socket: Duplex,
79
+ _head: Buffer,
80
+ callback: (socket: FakeWebSocket) => void,
81
+ ) => {
82
+ if (!this.nextSocket) {
83
+ throw new Error("Missing websocket for upgrade.");
84
+ }
85
+
86
+ const socket = this.nextSocket;
87
+ this.nextSocket = null;
88
+ callback(socket);
89
+ },
90
+ );
91
+ }
92
+
93
+ const createUpgradeRequest = (tentacleId: string) =>
94
+ ({
95
+ url: `/api/terminals/${tentacleId}/ws`,
96
+ }) as IncomingMessage;
97
+
98
+ const parseSentMessages = (socket: FakeWebSocket) =>
99
+ socket.sentMessages.map((raw) => JSON.parse(raw) as { type: string; data?: string });
100
+
101
+ describe("createSessionRuntime", () => {
102
+ const temporaryDirectories: string[] = [];
103
+
104
+ const createTemporaryDirectory = () => {
105
+ const directory = mkdtempSync(join(tmpdir(), "octogent-session-runtime-test-"));
106
+ temporaryDirectories.push(directory);
107
+ return directory;
108
+ };
109
+
110
+ beforeEach(() => {
111
+ createShellEnvironmentMock.mockClear();
112
+ ensureSpawnHelperMock.mockClear();
113
+ spawnMock.mockReset();
114
+ });
115
+
116
+ afterEach(() => {
117
+ vi.useRealTimers();
118
+ for (const directory of temporaryDirectories) {
119
+ rmSync(directory, { recursive: true, force: true });
120
+ }
121
+ temporaryDirectories.length = 0;
122
+ });
123
+
124
+ it("keeps a session alive across reconnects and replays scrollback history", () => {
125
+ const tentacleId = "tentacle-1";
126
+ const terminals = new Map<string, PersistedTerminal>([
127
+ [
128
+ tentacleId,
129
+ {
130
+ terminalId: tentacleId,
131
+ tentacleId,
132
+ tentacleName: tentacleId,
133
+ createdAt: new Date().toISOString(),
134
+ workspaceMode: "shared",
135
+ },
136
+ ],
137
+ ]);
138
+ const sessions = new Map<string, TerminalSession>();
139
+ const websocketServer = new FakeWebSocketServer();
140
+ const pty = new FakePty();
141
+ const transcriptDirectoryPath = createTemporaryDirectory();
142
+ spawnMock.mockReturnValue(pty);
143
+
144
+ const runtime = createSessionRuntime({
145
+ websocketServer: websocketServer as unknown as import("ws").WebSocketServer,
146
+ terminals,
147
+ sessions,
148
+ getTentacleWorkspaceCwd: () => process.cwd(),
149
+ isDebugPtyLogsEnabled: false,
150
+ ptyLogDir: process.cwd(),
151
+ transcriptDirectoryPath,
152
+ sessionIdleGraceMs: 60_000,
153
+ scrollbackMaxBytes: 1024,
154
+ });
155
+
156
+ const firstSocket = new FakeWebSocket();
157
+ websocketServer.nextSocket = firstSocket;
158
+ expect(
159
+ runtime.handleUpgrade(createUpgradeRequest(tentacleId), {} as Duplex, Buffer.alloc(0)),
160
+ ).toBe(true);
161
+
162
+ pty.emitData("first line\r\n");
163
+ pty.emitData("second line\r\n");
164
+ firstSocket.close();
165
+ expect(sessions.has(tentacleId)).toBe(true);
166
+
167
+ const secondSocket = new FakeWebSocket();
168
+ websocketServer.nextSocket = secondSocket;
169
+ expect(
170
+ runtime.handleUpgrade(createUpgradeRequest(tentacleId), {} as Duplex, Buffer.alloc(0)),
171
+ ).toBe(true);
172
+
173
+ const secondMessages = parseSentMessages(secondSocket);
174
+ expect(secondMessages.find((message) => message.type === "history")).toEqual({
175
+ type: "history",
176
+ data: "first line\r\nsecond line\r\n",
177
+ });
178
+ expect(spawnMock).toHaveBeenCalledTimes(1);
179
+ expect(pty.write).toHaveBeenCalledTimes(1);
180
+
181
+ runtime.close();
182
+ });
183
+
184
+ it("closes idle sessions after the configured grace timeout", () => {
185
+ vi.useFakeTimers();
186
+
187
+ const tentacleId = "tentacle-1";
188
+ const terminals = new Map<string, PersistedTerminal>([
189
+ [
190
+ tentacleId,
191
+ {
192
+ terminalId: tentacleId,
193
+ tentacleId,
194
+ tentacleName: tentacleId,
195
+ createdAt: new Date().toISOString(),
196
+ workspaceMode: "shared",
197
+ },
198
+ ],
199
+ ]);
200
+ const sessions = new Map<string, TerminalSession>();
201
+ const websocketServer = new FakeWebSocketServer();
202
+ const pty = new FakePty();
203
+ const transcriptDirectoryPath = createTemporaryDirectory();
204
+ spawnMock.mockReturnValue(pty);
205
+
206
+ const runtime = createSessionRuntime({
207
+ websocketServer: websocketServer as unknown as import("ws").WebSocketServer,
208
+ terminals,
209
+ sessions,
210
+ getTentacleWorkspaceCwd: () => process.cwd(),
211
+ isDebugPtyLogsEnabled: false,
212
+ ptyLogDir: process.cwd(),
213
+ transcriptDirectoryPath,
214
+ sessionIdleGraceMs: 1000,
215
+ scrollbackMaxBytes: 1024,
216
+ });
217
+
218
+ const socket = new FakeWebSocket();
219
+ websocketServer.nextSocket = socket;
220
+ expect(
221
+ runtime.handleUpgrade(createUpgradeRequest(tentacleId), {} as Duplex, Buffer.alloc(0)),
222
+ ).toBe(true);
223
+ socket.close();
224
+
225
+ expect(sessions.has(tentacleId)).toBe(true);
226
+ vi.advanceTimersByTime(999);
227
+ expect(sessions.has(tentacleId)).toBe(true);
228
+
229
+ vi.advanceTimersByTime(1);
230
+ expect(pty.kill).toHaveBeenCalledTimes(1);
231
+ expect(sessions.has(tentacleId)).toBe(false);
232
+
233
+ runtime.close();
234
+ });
235
+
236
+ it("truncates oversize chunks to the configured scrollback size", () => {
237
+ const tentacleId = "tentacle-1";
238
+ const terminals = new Map<string, PersistedTerminal>([
239
+ [
240
+ tentacleId,
241
+ {
242
+ terminalId: tentacleId,
243
+ tentacleId,
244
+ tentacleName: tentacleId,
245
+ createdAt: new Date().toISOString(),
246
+ workspaceMode: "shared",
247
+ },
248
+ ],
249
+ ]);
250
+ const sessions = new Map<string, TerminalSession>();
251
+ const websocketServer = new FakeWebSocketServer();
252
+ const pty = new FakePty();
253
+ const transcriptDirectoryPath = createTemporaryDirectory();
254
+ spawnMock.mockReturnValue(pty);
255
+
256
+ const runtime = createSessionRuntime({
257
+ websocketServer: websocketServer as unknown as import("ws").WebSocketServer,
258
+ terminals,
259
+ sessions,
260
+ getTentacleWorkspaceCwd: () => process.cwd(),
261
+ isDebugPtyLogsEnabled: false,
262
+ ptyLogDir: process.cwd(),
263
+ transcriptDirectoryPath,
264
+ sessionIdleGraceMs: 60_000,
265
+ scrollbackMaxBytes: 8,
266
+ });
267
+
268
+ const firstSocket = new FakeWebSocket();
269
+ websocketServer.nextSocket = firstSocket;
270
+ expect(
271
+ runtime.handleUpgrade(createUpgradeRequest(tentacleId), {} as Duplex, Buffer.alloc(0)),
272
+ ).toBe(true);
273
+ pty.emitData("123456789012");
274
+ firstSocket.close();
275
+
276
+ const secondSocket = new FakeWebSocket();
277
+ websocketServer.nextSocket = secondSocket;
278
+ expect(
279
+ runtime.handleUpgrade(createUpgradeRequest(tentacleId), {} as Duplex, Buffer.alloc(0)),
280
+ ).toBe(true);
281
+
282
+ const secondMessages = parseSentMessages(secondSocket);
283
+ expect(secondMessages.find((message) => message.type === "history")).toEqual({
284
+ type: "history",
285
+ data: "56789012",
286
+ });
287
+
288
+ runtime.close();
289
+ });
290
+
291
+ it("strips a broken leading ANSI fragment from replayed history after truncation", () => {
292
+ const tentacleId = "tentacle-1";
293
+ const terminals = new Map<string, PersistedTerminal>([
294
+ [
295
+ tentacleId,
296
+ {
297
+ terminalId: tentacleId,
298
+ tentacleId,
299
+ tentacleName: tentacleId,
300
+ createdAt: new Date().toISOString(),
301
+ workspaceMode: "shared",
302
+ },
303
+ ],
304
+ ]);
305
+ const sessions = new Map<string, TerminalSession>();
306
+ const websocketServer = new FakeWebSocketServer();
307
+ const pty = new FakePty();
308
+ const transcriptDirectoryPath = createTemporaryDirectory();
309
+ spawnMock.mockReturnValue(pty);
310
+
311
+ const runtime = createSessionRuntime({
312
+ websocketServer: websocketServer as unknown as import("ws").WebSocketServer,
313
+ terminals,
314
+ sessions,
315
+ getTentacleWorkspaceCwd: () => process.cwd(),
316
+ isDebugPtyLogsEnabled: false,
317
+ ptyLogDir: process.cwd(),
318
+ transcriptDirectoryPath,
319
+ sessionIdleGraceMs: 60_000,
320
+ scrollbackMaxBytes: 18,
321
+ });
322
+
323
+ const firstSocket = new FakeWebSocket();
324
+ websocketServer.nextSocket = firstSocket;
325
+ expect(
326
+ runtime.handleUpgrade(createUpgradeRequest(tentacleId), {} as Duplex, Buffer.alloc(0)),
327
+ ).toBe(true);
328
+ pty.emitData("\u001b[48;2;55;55;55mHELLO\r\n");
329
+ firstSocket.close();
330
+
331
+ const secondSocket = new FakeWebSocket();
332
+ websocketServer.nextSocket = secondSocket;
333
+ expect(
334
+ runtime.handleUpgrade(createUpgradeRequest(tentacleId), {} as Duplex, Buffer.alloc(0)),
335
+ ).toBe(true);
336
+
337
+ const secondMessages = parseSentMessages(secondSocket);
338
+ expect(secondMessages.find((message) => message.type === "history")).toEqual({
339
+ type: "history",
340
+ data: "HELLO\r\n",
341
+ });
342
+
343
+ runtime.close();
344
+ });
345
+
346
+ it("ignores duplicate resize payloads for the same terminal size", () => {
347
+ const tentacleId = "tentacle-1";
348
+ const terminals = new Map<string, PersistedTerminal>([
349
+ [
350
+ tentacleId,
351
+ {
352
+ terminalId: tentacleId,
353
+ tentacleId,
354
+ tentacleName: tentacleId,
355
+ createdAt: new Date().toISOString(),
356
+ workspaceMode: "shared",
357
+ },
358
+ ],
359
+ ]);
360
+ const sessions = new Map<string, TerminalSession>();
361
+ const websocketServer = new FakeWebSocketServer();
362
+ const pty = new FakePty();
363
+ const transcriptDirectoryPath = createTemporaryDirectory();
364
+ spawnMock.mockReturnValue(pty);
365
+
366
+ const runtime = createSessionRuntime({
367
+ websocketServer: websocketServer as unknown as import("ws").WebSocketServer,
368
+ terminals,
369
+ sessions,
370
+ getTentacleWorkspaceCwd: () => process.cwd(),
371
+ isDebugPtyLogsEnabled: false,
372
+ ptyLogDir: process.cwd(),
373
+ transcriptDirectoryPath,
374
+ sessionIdleGraceMs: 60_000,
375
+ scrollbackMaxBytes: 1024,
376
+ });
377
+
378
+ const socket = new FakeWebSocket();
379
+ websocketServer.nextSocket = socket;
380
+ expect(
381
+ runtime.handleUpgrade(createUpgradeRequest(tentacleId), {} as Duplex, Buffer.alloc(0)),
382
+ ).toBe(true);
383
+
384
+ socket.emit("message", JSON.stringify({ type: "resize", cols: 120, rows: 35 }));
385
+ socket.emit("message", JSON.stringify({ type: "resize", cols: 120, rows: 35 }));
386
+ socket.emit("message", JSON.stringify({ type: "resize", cols: 121, rows: 35 }));
387
+
388
+ expect(pty.resize).toHaveBeenCalledTimes(1);
389
+ expect(pty.resize).toHaveBeenLastCalledWith(121, 35);
390
+
391
+ runtime.close();
392
+ });
393
+
394
+ it("writes normalized transcript events for each terminal session", async () => {
395
+ const tentacleId = "tentacle-1";
396
+ const terminals = new Map<string, PersistedTerminal>([
397
+ [
398
+ tentacleId,
399
+ {
400
+ terminalId: tentacleId,
401
+ tentacleId,
402
+ tentacleName: tentacleId,
403
+ createdAt: new Date().toISOString(),
404
+ workspaceMode: "shared",
405
+ },
406
+ ],
407
+ ]);
408
+ const sessions = new Map<string, TerminalSession>();
409
+ const websocketServer = new FakeWebSocketServer();
410
+ const pty = new FakePty();
411
+ const transcriptDirectoryPath = createTemporaryDirectory();
412
+ spawnMock.mockReturnValue(pty);
413
+
414
+ const runtime = createSessionRuntime({
415
+ websocketServer: websocketServer as unknown as import("ws").WebSocketServer,
416
+ terminals,
417
+ sessions,
418
+ getTentacleWorkspaceCwd: () => process.cwd(),
419
+ isDebugPtyLogsEnabled: false,
420
+ ptyLogDir: process.cwd(),
421
+ transcriptDirectoryPath,
422
+ sessionIdleGraceMs: 60_000,
423
+ scrollbackMaxBytes: 1024,
424
+ });
425
+
426
+ const socket = new FakeWebSocket();
427
+ websocketServer.nextSocket = socket;
428
+ expect(
429
+ runtime.handleUpgrade(createUpgradeRequest(tentacleId), {} as Duplex, Buffer.alloc(0)),
430
+ ).toBe(true);
431
+
432
+ socket.emit("message", JSON.stringify({ type: "input", data: "echo hi\r" }));
433
+ pty.emitData("\u001b[31mred\u001b[0m\r\n");
434
+ runtime.close();
435
+
436
+ const transcriptPath = join(transcriptDirectoryPath, `${encodeURIComponent(tentacleId)}.jsonl`);
437
+ for (let attempt = 0; attempt < 20; attempt += 1) {
438
+ if (existsSync(transcriptPath)) {
439
+ break;
440
+ }
441
+ await new Promise((resolve) => setTimeout(resolve, 10));
442
+ }
443
+ const transcriptEvents = readFileSync(transcriptPath, "utf8")
444
+ .trim()
445
+ .split(/\r?\n/)
446
+ .map((line) => JSON.parse(line) as { type: string; text?: string; reason?: string });
447
+
448
+ expect(transcriptEvents.some((event) => event.type === "session_start")).toBe(true);
449
+ expect(
450
+ transcriptEvents.some(
451
+ (event) => event.type === "session_end" && event.reason === "session_close",
452
+ ),
453
+ ).toBe(true);
454
+ });
455
+
456
+ it("can start a prompted session headlessly and submits the prompt automatically", () => {
457
+ vi.useFakeTimers();
458
+
459
+ const tentacleId = "tentacle-1";
460
+ const terminals = new Map<string, PersistedTerminal>([
461
+ [
462
+ tentacleId,
463
+ {
464
+ terminalId: tentacleId,
465
+ tentacleId,
466
+ tentacleName: tentacleId,
467
+ createdAt: new Date().toISOString(),
468
+ workspaceMode: "shared",
469
+ initialPrompt: "Investigate and report back.",
470
+ },
471
+ ],
472
+ ]);
473
+ const sessions = new Map<string, TerminalSession>();
474
+ const websocketServer = new FakeWebSocketServer();
475
+ const pty = new FakePty();
476
+ const transcriptDirectoryPath = createTemporaryDirectory();
477
+ spawnMock.mockReturnValue(pty);
478
+
479
+ const runtime = createSessionRuntime({
480
+ websocketServer: websocketServer as unknown as import("ws").WebSocketServer,
481
+ terminals,
482
+ sessions,
483
+ getTentacleWorkspaceCwd: () => process.cwd(),
484
+ isDebugPtyLogsEnabled: false,
485
+ ptyLogDir: process.cwd(),
486
+ transcriptDirectoryPath,
487
+ sessionIdleGraceMs: 1000,
488
+ scrollbackMaxBytes: 1024,
489
+ });
490
+
491
+ expect(runtime.startSession(tentacleId)).toBe(true);
492
+ expect(sessions.has(tentacleId)).toBe(true);
493
+ expect(pty.write).toHaveBeenNthCalledWith(1, "claude\r");
494
+
495
+ vi.advanceTimersByTime(4_000);
496
+ expect(pty.write).toHaveBeenNthCalledWith(
497
+ 2,
498
+ "\u001b[200~Investigate and report back.\u001b[201~",
499
+ );
500
+
501
+ vi.advanceTimersByTime(150);
502
+ expect(pty.write).toHaveBeenNthCalledWith(3, "\r");
503
+
504
+ vi.advanceTimersByTime(10_000);
505
+ expect(sessions.has(tentacleId)).toBe(true);
506
+
507
+ runtime.close();
508
+ });
509
+
510
+ it("pastes an initial input draft without submitting it", () => {
511
+ vi.useFakeTimers();
512
+
513
+ const tentacleId = "tentacle-1";
514
+ const terminals = new Map<string, PersistedTerminal>([
515
+ [
516
+ tentacleId,
517
+ {
518
+ terminalId: tentacleId,
519
+ tentacleId,
520
+ tentacleName: tentacleId,
521
+ createdAt: new Date().toISOString(),
522
+ workspaceMode: "shared",
523
+ initialInputDraft: "You are working on docs.",
524
+ },
525
+ ],
526
+ ]);
527
+ const sessions = new Map<string, TerminalSession>();
528
+ const websocketServer = new FakeWebSocketServer();
529
+ const pty = new FakePty();
530
+ const transcriptDirectoryPath = createTemporaryDirectory();
531
+ spawnMock.mockReturnValue(pty);
532
+
533
+ const runtime = createSessionRuntime({
534
+ websocketServer: websocketServer as unknown as import("ws").WebSocketServer,
535
+ terminals,
536
+ sessions,
537
+ getTentacleWorkspaceCwd: () => process.cwd(),
538
+ isDebugPtyLogsEnabled: false,
539
+ ptyLogDir: process.cwd(),
540
+ transcriptDirectoryPath,
541
+ sessionIdleGraceMs: 1_000,
542
+ scrollbackMaxBytes: 1_024,
543
+ });
544
+
545
+ const socket = new FakeWebSocket();
546
+ websocketServer.nextSocket = socket;
547
+ expect(
548
+ runtime.handleUpgrade(createUpgradeRequest(tentacleId), {} as Duplex, Buffer.alloc(0)),
549
+ ).toBe(true);
550
+
551
+ expect(pty.write).toHaveBeenNthCalledWith(1, "claude\r");
552
+
553
+ vi.advanceTimersByTime(4_000);
554
+ expect(pty.write).toHaveBeenNthCalledWith(2, "\u001b[200~You are working on docs.\u001b[201~");
555
+
556
+ vi.advanceTimersByTime(150);
557
+ expect(pty.write).toHaveBeenCalledTimes(2);
558
+
559
+ runtime.close();
560
+ });
561
+
562
+ it("reports runtime state changes through the state-change callback", () => {
563
+ const tentacleId = "tentacle-1";
564
+ const terminals = new Map<string, PersistedTerminal>([
565
+ [
566
+ tentacleId,
567
+ {
568
+ terminalId: tentacleId,
569
+ tentacleId,
570
+ tentacleName: tentacleId,
571
+ createdAt: new Date().toISOString(),
572
+ workspaceMode: "shared",
573
+ },
574
+ ],
575
+ ]);
576
+ const sessions = new Map<string, TerminalSession>();
577
+ const websocketServer = new FakeWebSocketServer();
578
+ const pty = new FakePty();
579
+ const transcriptDirectoryPath = createTemporaryDirectory();
580
+ const onStateChange = vi.fn();
581
+ spawnMock.mockReturnValue(pty);
582
+
583
+ const runtime = createSessionRuntime({
584
+ websocketServer: websocketServer as unknown as import("ws").WebSocketServer,
585
+ terminals,
586
+ sessions,
587
+ getTentacleWorkspaceCwd: () => process.cwd(),
588
+ isDebugPtyLogsEnabled: false,
589
+ ptyLogDir: process.cwd(),
590
+ transcriptDirectoryPath,
591
+ sessionIdleGraceMs: 60_000,
592
+ scrollbackMaxBytes: 1024,
593
+ onStateChange,
594
+ });
595
+
596
+ const socket = new FakeWebSocket();
597
+ websocketServer.nextSocket = socket;
598
+ expect(
599
+ runtime.handleUpgrade(createUpgradeRequest(tentacleId), {} as Duplex, Buffer.alloc(0)),
600
+ ).toBe(true);
601
+
602
+ socket.emit("message", JSON.stringify({ type: "input", data: "echo hi\r" }));
603
+
604
+ expect(onStateChange).toHaveBeenCalledWith(tentacleId, "processing", undefined);
605
+
606
+ runtime.close();
607
+ });
608
+ });
@@ -0,0 +1,70 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ collectStartupPrerequisiteReport,
5
+ formatStartupPrerequisiteReport,
6
+ isCommandAvailable,
7
+ } from "../src/startupPrerequisites";
8
+
9
+ describe("startup prerequisites", () => {
10
+ it("passes cleanly when every prerequisite is installed", () => {
11
+ const report = collectStartupPrerequisiteReport(() => true);
12
+
13
+ expect(report.errors).toEqual([]);
14
+ expect(report.warnings).toEqual([]);
15
+ expect(formatStartupPrerequisiteReport(report)).toEqual([]);
16
+ });
17
+
18
+ it("fails startup when no agent CLI is installed", () => {
19
+ const report = collectStartupPrerequisiteReport((command) => command === "git");
20
+
21
+ expect(report.errors).toHaveLength(1);
22
+ expect(report.errors[0]?.summary).toContain("Neither `claude` nor `codex`");
23
+ expect(report.warnings.map((issue) => issue.command)).toEqual(["gh", "curl"]);
24
+ });
25
+
26
+ it("warns for degraded optional integrations when one provider is available", () => {
27
+ const report = collectStartupPrerequisiteReport((command) => command === "codex");
28
+
29
+ expect(report.errors).toEqual([]);
30
+ expect(report.warnings.map((issue) => issue.command)).toEqual(["claude", "git", "gh", "curl"]);
31
+ expect(formatStartupPrerequisiteReport(report)).toEqual([
32
+ "Octogent startup preflight:",
33
+ " Warning: `claude` is not installed.",
34
+ " Claude-backed terminals are unavailable. Install Claude Code and run `claude login` if you want the default Claude provider.",
35
+ " Warning: `git` is not installed.",
36
+ " Worktree terminals and git lifecycle actions are unavailable. Install Git to enable branch/worktree flows.",
37
+ " Warning: `gh` is not installed.",
38
+ " GitHub pull request features are unavailable. Install GitHub CLI and run `gh auth login` to enable PR actions.",
39
+ " Warning: `curl` is not installed.",
40
+ " Claude hook command callbacks for SessionStart, UserPromptSubmit, and Stop are unavailable. Install curl to restore full Claude hook delivery.",
41
+ ]);
42
+ });
43
+
44
+ it("uses where on Windows and which elsewhere when checking commands", () => {
45
+ const calls: Array<{ file: string; args: string[] }> = [];
46
+
47
+ const windowsAvailable = isCommandAvailable("claude", {
48
+ platform: "win32",
49
+ execFileSyncImpl: ((file, args) => {
50
+ calls.push({ file, args: args as string[] });
51
+ return Buffer.from("");
52
+ }) as typeof import("node:child_process").execFileSync,
53
+ });
54
+
55
+ const unixAvailable = isCommandAvailable("codex", {
56
+ platform: "linux",
57
+ execFileSyncImpl: ((file, args) => {
58
+ calls.push({ file, args: args as string[] });
59
+ return Buffer.from("");
60
+ }) as typeof import("node:child_process").execFileSync,
61
+ });
62
+
63
+ expect(windowsAvailable).toBe(true);
64
+ expect(unixAvailable).toBe(true);
65
+ expect(calls).toEqual([
66
+ { file: "where", args: ["claude"] },
67
+ { file: "which", args: ["codex"] },
68
+ ]);
69
+ });
70
+ });