@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,671 @@
1
+ import { type WriteStream, createWriteStream, existsSync, mkdirSync } from "node:fs";
2
+ import type { IncomingMessage } from "node:http";
3
+ import { join } from "node:path";
4
+ import type { Duplex } from "node:stream";
5
+
6
+ import { type IPty, spawn } from "node-pty";
7
+ import type { WebSocket, WebSocketServer } from "ws";
8
+
9
+ import { type AgentRuntimeState, AgentStateTracker } from "../agentStateDetection";
10
+ import {
11
+ DEFAULT_AGENT_PROVIDER,
12
+ TERMINAL_BOOTSTRAP_COMMANDS,
13
+ TERMINAL_SCROLLBACK_MAX_BYTES,
14
+ TERMINAL_SESSION_IDLE_GRACE_MS,
15
+ } from "./constants";
16
+ import {
17
+ type ConversationTranscriptEvent,
18
+ type ConversationTranscriptEventPayload,
19
+ ensureTranscriptDirectory,
20
+ transcriptFilenameForSession,
21
+ } from "./conversations";
22
+ import { broadcastMessage, getTerminalId, sendMessage } from "./protocol";
23
+ import { createShellEnvironment, ensureNodePtySpawnHelperExecutable } from "./ptyEnvironment";
24
+ import { toErrorMessage } from "./systemClients";
25
+ import type { DirectSessionListener, PersistedTerminal, TerminalSession } from "./types";
26
+
27
+ type CreateSessionRuntimeOptions = {
28
+ websocketServer: WebSocketServer;
29
+ terminals: Map<string, PersistedTerminal>;
30
+ sessions: Map<string, TerminalSession>;
31
+ resolveTerminalSession?: (terminalId: string) => {
32
+ sessionId: string;
33
+ tentacleId: string;
34
+ } | null;
35
+ getTentacleWorkspaceCwd: (tentacleId: string) => string;
36
+ isDebugPtyLogsEnabled: boolean;
37
+ ptyLogDir: string;
38
+ transcriptDirectoryPath: string;
39
+ sessionIdleGraceMs?: number;
40
+ scrollbackMaxBytes?: number;
41
+ onStateChange?: (terminalId: string, state: AgentRuntimeState, toolName?: string) => void;
42
+ };
43
+
44
+ const ANSI_BEL = String.fromCharCode(0x07);
45
+ const ANSI_ESCAPE = String.fromCharCode(0x1b);
46
+ const BROKEN_OSC_TAIL_RE = new RegExp(
47
+ `^\\][^${ANSI_BEL}${ANSI_ESCAPE}]*(?:${ANSI_BEL}|${ANSI_ESCAPE}\\\\)`,
48
+ );
49
+
50
+ export const createSessionRuntime = ({
51
+ websocketServer,
52
+ terminals,
53
+ sessions,
54
+ resolveTerminalSession,
55
+ getTentacleWorkspaceCwd,
56
+ isDebugPtyLogsEnabled,
57
+ ptyLogDir,
58
+ transcriptDirectoryPath,
59
+ sessionIdleGraceMs = TERMINAL_SESSION_IDLE_GRACE_MS,
60
+ scrollbackMaxBytes = TERMINAL_SCROLLBACK_MAX_BYTES,
61
+ onStateChange,
62
+ }: CreateSessionRuntimeOptions) => {
63
+ const DEFAULT_PTY_COLS = 120;
64
+ const DEFAULT_PTY_ROWS = 35;
65
+
66
+ const getShellLaunch = () => {
67
+ if (process.platform === "win32") {
68
+ return {
69
+ command: process.env.ComSpec ?? "cmd.exe",
70
+ args: [],
71
+ };
72
+ }
73
+
74
+ const shellFromEnvironment = process.env.SHELL?.trim();
75
+ if (shellFromEnvironment && shellFromEnvironment.length > 0) {
76
+ return {
77
+ command: shellFromEnvironment,
78
+ args: ["-i"],
79
+ };
80
+ }
81
+
82
+ return {
83
+ command: "/bin/bash",
84
+ args: ["-i"],
85
+ };
86
+ };
87
+
88
+ const createDebugLog = (sessionId: string) => {
89
+ if (!isDebugPtyLogsEnabled) {
90
+ return undefined;
91
+ }
92
+
93
+ mkdirSync(ptyLogDir, { recursive: true });
94
+ const filename = `${sessionId}-${Date.now()}.log`;
95
+ return createWriteStream(join(ptyLogDir, filename), {
96
+ flags: "a",
97
+ encoding: "utf8",
98
+ });
99
+ };
100
+
101
+ const appendDebugLog = (session: TerminalSession, line: string) => {
102
+ session.debugLog?.write(`${new Date().toISOString()} ${line}\n`);
103
+ };
104
+
105
+ const createTranscriptLog = (sessionId: string) => {
106
+ ensureTranscriptDirectory(transcriptDirectoryPath);
107
+ const filename = transcriptFilenameForSession(sessionId);
108
+ const stream = createWriteStream(join(transcriptDirectoryPath, filename), {
109
+ flags: "a",
110
+ encoding: "utf8",
111
+ });
112
+ stream.on("error", () => {
113
+ // Keep terminal flow alive even if transcript writes fail.
114
+ });
115
+ return stream;
116
+ };
117
+
118
+ const appendTranscriptEvent = (
119
+ session: TerminalSession,
120
+ sessionId: string,
121
+ event: ConversationTranscriptEventPayload,
122
+ ) => {
123
+ if (!session.transcriptLog) {
124
+ return;
125
+ }
126
+
127
+ const nextEventCount = (session.transcriptEventCount ?? 0) + 1;
128
+ session.transcriptEventCount = nextEventCount;
129
+ const payload: ConversationTranscriptEvent = {
130
+ ...event,
131
+ eventId: `${sessionId}:${nextEventCount}`,
132
+ sessionId,
133
+ tentacleId: session.tentacleId,
134
+ } as ConversationTranscriptEvent;
135
+ session.transcriptLog.write(`${JSON.stringify(payload)}\n`);
136
+ };
137
+
138
+ const closeTranscript = (
139
+ session: TerminalSession,
140
+ sessionId: string,
141
+ event: ConversationTranscriptEventPayload,
142
+ ) => {
143
+ if (session.hasTranscriptEnded) {
144
+ return;
145
+ }
146
+
147
+ appendTranscriptEvent(session, sessionId, event);
148
+ session.hasTranscriptEnded = true;
149
+ session.transcriptLog?.end();
150
+ session.transcriptLog = undefined;
151
+ };
152
+
153
+ const emitStateIfChanged = (
154
+ session: TerminalSession,
155
+ sessionId: string,
156
+ nextState: AgentRuntimeState | null,
157
+ ) => {
158
+ if (!nextState || nextState === session.agentState) {
159
+ return;
160
+ }
161
+
162
+ session.agentState = nextState;
163
+ appendDebugLog(session, `state-change session=${sessionId} state=${nextState}`);
164
+ appendTranscriptEvent(session, sessionId, {
165
+ type: "state_change",
166
+ state: nextState,
167
+ timestamp: new Date().toISOString(),
168
+ });
169
+ onStateChange?.(sessionId, nextState, session.lastToolName);
170
+ broadcastMessage(session, {
171
+ type: "state",
172
+ state: nextState,
173
+ ...(session.lastToolName ? { toolName: session.lastToolName } : {}),
174
+ });
175
+ };
176
+
177
+ const resolveSession =
178
+ resolveTerminalSession ??
179
+ ((terminalId: string) => {
180
+ if (!terminals.has(terminalId)) {
181
+ return null;
182
+ }
183
+ const terminal = terminals.get(terminalId);
184
+ return {
185
+ sessionId: terminalId,
186
+ tentacleId: terminal?.tentacleId ?? terminalId,
187
+ };
188
+ });
189
+
190
+ const clearIdleCloseTimer = (session: TerminalSession) => {
191
+ if (!session.idleCloseTimer) {
192
+ return;
193
+ }
194
+
195
+ clearTimeout(session.idleCloseTimer);
196
+ session.idleCloseTimer = undefined;
197
+ };
198
+
199
+ const appendScrollback = (session: TerminalSession, chunk: string) => {
200
+ let nextChunk = chunk;
201
+ let nextChunkBytes = Buffer.byteLength(nextChunk, "utf8");
202
+ if (nextChunkBytes > scrollbackMaxBytes) {
203
+ const chunkBuffer = Buffer.from(nextChunk, "utf8");
204
+ nextChunk = chunkBuffer.subarray(chunkBuffer.length - scrollbackMaxBytes).toString("utf8");
205
+ nextChunkBytes = Buffer.byteLength(nextChunk, "utf8");
206
+ session.scrollbackChunks = [];
207
+ session.scrollbackBytes = 0;
208
+ }
209
+
210
+ session.scrollbackChunks.push(nextChunk);
211
+ session.scrollbackBytes += nextChunkBytes;
212
+ while (session.scrollbackBytes > scrollbackMaxBytes && session.scrollbackChunks.length > 0) {
213
+ const removedChunk = session.scrollbackChunks.shift();
214
+ if (!removedChunk) {
215
+ break;
216
+ }
217
+
218
+ session.scrollbackBytes -= Buffer.byteLength(removedChunk, "utf8");
219
+ }
220
+ };
221
+
222
+ const stripBrokenLeadingAnsi = (text: string): string => {
223
+ let nextText = text;
224
+
225
+ while (nextText.length > 0) {
226
+ if (nextText.startsWith("\u001b")) {
227
+ return nextText;
228
+ }
229
+
230
+ const oscMatch = nextText.match(BROKEN_OSC_TAIL_RE);
231
+ if (oscMatch) {
232
+ nextText = nextText.slice(oscMatch[0].length);
233
+ continue;
234
+ }
235
+
236
+ const csiTailMatch = nextText.match(/^\[[0-9:;<=>?]*[ -/]*[@-~]/);
237
+ if (csiTailMatch) {
238
+ nextText = nextText.slice(csiTailMatch[0].length);
239
+ continue;
240
+ }
241
+
242
+ const orphanedCsiTailMatch = nextText.match(
243
+ /^(?=[0-9:;<=>?]*[;:<=>?])[0-9:;<=>?]*[ -/]*[@-~]/,
244
+ );
245
+ if (orphanedCsiTailMatch) {
246
+ nextText = nextText.slice(orphanedCsiTailMatch[0].length);
247
+ continue;
248
+ }
249
+
250
+ break;
251
+ }
252
+
253
+ return nextText;
254
+ };
255
+
256
+ const sendHistory = (websocket: WebSocket, session: TerminalSession) => {
257
+ if (session.scrollbackChunks.length === 0) {
258
+ return;
259
+ }
260
+
261
+ sendMessage(websocket, {
262
+ type: "history",
263
+ data: stripBrokenLeadingAnsi(session.scrollbackChunks.join("")),
264
+ });
265
+ };
266
+
267
+ const closeSession = (sessionId: string): boolean => {
268
+ const session = sessions.get(sessionId);
269
+ if (!session) {
270
+ return false;
271
+ }
272
+
273
+ clearIdleCloseTimer(session);
274
+ closeTranscript(session, sessionId, {
275
+ type: "session_end",
276
+ reason: "session_close",
277
+ timestamp: new Date().toISOString(),
278
+ });
279
+ try {
280
+ session.pty.kill();
281
+ } catch {
282
+ // Ignore teardown errors; session will still be discarded.
283
+ }
284
+
285
+ if (session.statePollTimer) {
286
+ clearInterval(session.statePollTimer);
287
+ }
288
+ session.debugLog?.end();
289
+ sessions.delete(sessionId);
290
+ return true;
291
+ };
292
+
293
+ const INITIAL_PROMPT_DELAY_MS = 4_000;
294
+ const INITIAL_PROMPT_SUBMIT_DELAY_MS = 150;
295
+ const BRACKETED_PASTE_START = "\x1b[200~";
296
+ const BRACKETED_PASTE_END = "\x1b[201~";
297
+
298
+ const scheduleIdleCloseIfNeeded = (session: TerminalSession, sessionId: string) => {
299
+ if (session.keepAliveWithoutClients) {
300
+ return;
301
+ }
302
+
303
+ if (session.clients.size > 0 || session.directListeners.size > 0) {
304
+ return;
305
+ }
306
+
307
+ appendDebugLog(
308
+ session,
309
+ `idle-grace-start session=${sessionId} timeoutMs=${sessionIdleGraceMs}`,
310
+ );
311
+ clearIdleCloseTimer(session);
312
+ session.idleCloseTimer = setTimeout(() => {
313
+ appendDebugLog(session, `idle-grace-expired session=${sessionId}`);
314
+ closeSession(sessionId);
315
+ }, sessionIdleGraceMs);
316
+ };
317
+
318
+ const ensureAgentBootstrapped = (sessionId: string, session: TerminalSession) => {
319
+ if (session.isBootstrapCommandSent) {
320
+ return;
321
+ }
322
+
323
+ session.isBootstrapCommandSent = true;
324
+ const terminal = terminals.get(session.terminalId);
325
+ const provider = terminal?.agentProvider ?? DEFAULT_AGENT_PROVIDER;
326
+
327
+ const bootstrapCommand =
328
+ TERMINAL_BOOTSTRAP_COMMANDS[provider] ?? TERMINAL_BOOTSTRAP_COMMANDS[DEFAULT_AGENT_PROVIDER];
329
+ appendDebugLog(session, `bootstrap session=${sessionId} command=${bootstrapCommand}`);
330
+ session.pty.write(`${bootstrapCommand}\r`);
331
+
332
+ // Schedule initial prompt injection after Claude Code has had time to boot.
333
+ if (session.initialPrompt && !session.isInitialPromptSent) {
334
+ setTimeout(() => {
335
+ if (session.isInitialPromptSent) {
336
+ return;
337
+ }
338
+ session.isInitialPromptSent = true;
339
+ appendDebugLog(session, `initial-prompt session=${sessionId}`);
340
+ const prompt = session.initialPrompt ?? "";
341
+ session.pty.write(`${BRACKETED_PASTE_START}${prompt}${BRACKETED_PASTE_END}`);
342
+ setTimeout(() => {
343
+ if (sessions.get(sessionId) !== session) {
344
+ return;
345
+ }
346
+ appendDebugLog(session, `initial-prompt-submit session=${sessionId}`);
347
+ session.pty.write("\r");
348
+ }, INITIAL_PROMPT_SUBMIT_DELAY_MS);
349
+ }, INITIAL_PROMPT_DELAY_MS);
350
+ }
351
+
352
+ if (session.initialInputDraft && !session.isInitialInputDraftSent && !session.initialPrompt) {
353
+ setTimeout(() => {
354
+ if (session.isInitialInputDraftSent) {
355
+ return;
356
+ }
357
+ session.isInitialInputDraftSent = true;
358
+ appendDebugLog(session, `initial-input-draft session=${sessionId}`);
359
+ const draft = session.initialInputDraft ?? "";
360
+ session.pty.write(`${BRACKETED_PASTE_START}${draft}${BRACKETED_PASTE_END}`);
361
+ }, INITIAL_PROMPT_DELAY_MS);
362
+ }
363
+ };
364
+
365
+ const ensureSession = (sessionId: string, tentacleId: string) => {
366
+ const existingSession = sessions.get(sessionId);
367
+ if (existingSession) {
368
+ return existingSession;
369
+ }
370
+
371
+ const terminalRecord = terminals.get(sessionId);
372
+
373
+ const tentacleCwd = getTentacleWorkspaceCwd(tentacleId);
374
+ if (!existsSync(tentacleCwd)) {
375
+ throw new Error(`Terminal working directory does not exist: ${tentacleCwd}`);
376
+ }
377
+
378
+ ensureNodePtySpawnHelperExecutable();
379
+ const shellLaunch = getShellLaunch();
380
+
381
+ let pty: IPty;
382
+ try {
383
+ pty = spawn(shellLaunch.command, shellLaunch.args, {
384
+ cols: DEFAULT_PTY_COLS,
385
+ rows: DEFAULT_PTY_ROWS,
386
+ cwd: tentacleCwd,
387
+ env: createShellEnvironment({ octogentSessionId: sessionId }),
388
+ name: "xterm-256color",
389
+ });
390
+ } catch (error) {
391
+ throw new Error(
392
+ `Unable to start terminal shell (${shellLaunch.command}): ${toErrorMessage(error)}`,
393
+ );
394
+ }
395
+
396
+ const stateTracker = new AgentStateTracker();
397
+ const debugLog = createDebugLog(sessionId);
398
+ const transcriptLog = createTranscriptLog(sessionId);
399
+ const session: TerminalSession = {
400
+ terminalId: sessionId,
401
+ tentacleId,
402
+ pty,
403
+ clients: new Set(),
404
+ directListeners: new Set(),
405
+ cols: DEFAULT_PTY_COLS,
406
+ rows: DEFAULT_PTY_ROWS,
407
+ agentState: stateTracker.currentState,
408
+ stateTracker,
409
+ isBootstrapCommandSent: false,
410
+ scrollbackChunks: [],
411
+ scrollbackBytes: 0,
412
+ transcriptEventCount: 0,
413
+ pendingInput: "",
414
+ hasTranscriptEnded: false,
415
+ keepAliveWithoutClients: Boolean(terminalRecord?.initialPrompt),
416
+ };
417
+ if (debugLog) {
418
+ session.debugLog = debugLog;
419
+ }
420
+ session.transcriptLog = transcriptLog;
421
+
422
+ appendDebugLog(session, `session-start session=${sessionId} tentacle=${tentacleId}`);
423
+ appendTranscriptEvent(session, sessionId, {
424
+ type: "session_start",
425
+ timestamp: new Date().toISOString(),
426
+ });
427
+ session.statePollTimer = setInterval(() => {
428
+ emitStateIfChanged(session, sessionId, session.stateTracker.poll(Date.now()));
429
+ }, 300);
430
+
431
+ session.pty.onData((chunk) => {
432
+ appendDebugLog(session, `pty-output session=${sessionId} chunk=${JSON.stringify(chunk)}`);
433
+ appendScrollback(session, chunk);
434
+ const nextState = session.stateTracker.observeChunk(chunk, Date.now());
435
+ broadcastMessage(session, {
436
+ type: "output",
437
+ data: chunk,
438
+ });
439
+ emitStateIfChanged(session, sessionId, nextState);
440
+ });
441
+
442
+ session.pty.onExit(({ exitCode, signal }) => {
443
+ const message = `\r\n[terminal exited (code ${exitCode}, signal ${signal})]\r\n`;
444
+ broadcastMessage(session, {
445
+ type: "output",
446
+ data: message,
447
+ });
448
+ for (const client of session.clients) {
449
+ if (client.readyState === 1) {
450
+ client.close();
451
+ }
452
+ }
453
+
454
+ appendDebugLog(
455
+ session,
456
+ `session-exit session=${sessionId} code=${exitCode} signal=${signal}`,
457
+ );
458
+ closeTranscript(session, sessionId, {
459
+ type: "session_end",
460
+ reason: "pty_exit",
461
+ ...(Number.isFinite(exitCode) ? { exitCode } : {}),
462
+ ...(Number.isFinite(signal) ? { signal } : {}),
463
+ timestamp: new Date().toISOString(),
464
+ });
465
+ if (session.statePollTimer) {
466
+ clearInterval(session.statePollTimer);
467
+ }
468
+ session.debugLog?.end();
469
+ sessions.delete(sessionId);
470
+ });
471
+
472
+ // Propagate initial prompt from the terminal definition, if set.
473
+ if (terminalRecord?.initialPrompt) {
474
+ session.initialPrompt = terminalRecord.initialPrompt;
475
+ }
476
+ if (terminalRecord?.initialInputDraft) {
477
+ session.initialInputDraft = terminalRecord.initialInputDraft;
478
+ }
479
+
480
+ sessions.set(sessionId, session);
481
+ return session;
482
+ };
483
+
484
+ const handleUpgrade = (request: IncomingMessage, socket: Duplex, head: Buffer): boolean => {
485
+ const terminalId = getTerminalId(request);
486
+ if (!terminalId) {
487
+ return false;
488
+ }
489
+
490
+ const resolvedSession = resolveSession(terminalId);
491
+ if (!resolvedSession) {
492
+ return false;
493
+ }
494
+ const { sessionId, tentacleId } = resolvedSession;
495
+
496
+ websocketServer.handleUpgrade(request, socket, head, (websocket: WebSocket) => {
497
+ let session: TerminalSession;
498
+ try {
499
+ session = ensureSession(sessionId, tentacleId);
500
+ } catch (error) {
501
+ sendMessage(websocket, {
502
+ type: "output",
503
+ data: `\r\n[terminal failed to start: ${toErrorMessage(error)}]\r\n`,
504
+ });
505
+ websocket.close();
506
+ return;
507
+ }
508
+
509
+ session.clients.add(websocket);
510
+ appendDebugLog(session, `ws-open session=${sessionId} clients=${session.clients.size}`);
511
+ clearIdleCloseTimer(session);
512
+ ensureAgentBootstrapped(sessionId, session);
513
+ sendHistory(websocket, session);
514
+ sendMessage(websocket, {
515
+ type: "state",
516
+ state: session.agentState,
517
+ });
518
+
519
+ websocket.on("message", (raw: unknown) => {
520
+ const text =
521
+ typeof raw === "string" ? raw : raw instanceof Buffer ? raw.toString() : String(raw);
522
+ try {
523
+ const payload = JSON.parse(text) as
524
+ | { type: "input"; data: string }
525
+ | { type: "resize"; cols: number; rows: number };
526
+
527
+ if (payload.type === "input" && typeof payload.data === "string") {
528
+ appendDebugLog(
529
+ session,
530
+ `ws-input session=${sessionId} data=${JSON.stringify(payload.data)}`,
531
+ );
532
+ session.pty.write(payload.data);
533
+ if (/[\r\n]/.test(payload.data)) {
534
+ emitStateIfChanged(
535
+ session,
536
+ sessionId,
537
+ session.stateTracker.observeSubmit(Date.now()),
538
+ );
539
+ }
540
+ return;
541
+ }
542
+
543
+ if (
544
+ payload.type === "resize" &&
545
+ Number.isFinite(payload.cols) &&
546
+ Number.isFinite(payload.rows)
547
+ ) {
548
+ const nextCols = Math.max(20, Math.floor(payload.cols));
549
+ const nextRows = Math.max(10, Math.floor(payload.rows));
550
+ if (session.cols === nextCols && session.rows === nextRows) {
551
+ return;
552
+ }
553
+
554
+ session.cols = nextCols;
555
+ session.rows = nextRows;
556
+ session.pty.resize(nextCols, nextRows);
557
+ }
558
+ } catch {
559
+ session.pty.write(text);
560
+ }
561
+ });
562
+
563
+ websocket.on("close", () => {
564
+ session.clients.delete(websocket);
565
+ appendDebugLog(session, `ws-close session=${sessionId} clients=${session.clients.size}`);
566
+ scheduleIdleCloseIfNeeded(session, sessionId);
567
+ });
568
+ });
569
+
570
+ return true;
571
+ };
572
+
573
+ const close = () => {
574
+ for (const sessionId of sessions.keys()) {
575
+ closeSession(sessionId);
576
+ }
577
+ };
578
+
579
+ const connectDirect = (
580
+ terminalId: string,
581
+ listener: DirectSessionListener,
582
+ ): (() => void) | null => {
583
+ const resolvedSession = resolveSession(terminalId);
584
+ if (!resolvedSession) {
585
+ return null;
586
+ }
587
+ const { sessionId, tentacleId } = resolvedSession;
588
+
589
+ let session: TerminalSession;
590
+ try {
591
+ session = ensureSession(sessionId, tentacleId);
592
+ } catch {
593
+ return null;
594
+ }
595
+
596
+ session.directListeners.add(listener);
597
+ clearIdleCloseTimer(session);
598
+ ensureAgentBootstrapped(sessionId, session);
599
+
600
+ // Send history and current state to the new listener
601
+ if (session.scrollbackChunks.length > 0) {
602
+ listener({ type: "history", data: session.scrollbackChunks.join("") });
603
+ }
604
+ listener({ type: "state", state: session.agentState });
605
+
606
+ return () => {
607
+ session.directListeners.delete(listener);
608
+ scheduleIdleCloseIfNeeded(session, sessionId);
609
+ };
610
+ };
611
+
612
+ const startSession = (terminalId: string): boolean => {
613
+ const resolvedSession = resolveSession(terminalId);
614
+ if (!resolvedSession) {
615
+ return false;
616
+ }
617
+
618
+ const { sessionId, tentacleId } = resolvedSession;
619
+ let session: TerminalSession;
620
+ try {
621
+ session = ensureSession(sessionId, tentacleId);
622
+ } catch {
623
+ return false;
624
+ }
625
+
626
+ clearIdleCloseTimer(session);
627
+ ensureAgentBootstrapped(sessionId, session);
628
+ return true;
629
+ };
630
+
631
+ const writeInput = (terminalId: string, data: string): boolean => {
632
+ const session = sessions.get(terminalId);
633
+ if (!session) {
634
+ return false;
635
+ }
636
+
637
+ session.pty.write(data);
638
+ if (/[\r\n]/.test(data)) {
639
+ emitStateIfChanged(session, terminalId, session.stateTracker.observeSubmit(Date.now()));
640
+ }
641
+ return true;
642
+ };
643
+
644
+ const resizeSession = (terminalId: string, cols: number, rows: number): boolean => {
645
+ const session = sessions.get(terminalId);
646
+ if (!session) {
647
+ return false;
648
+ }
649
+
650
+ const nextCols = Math.max(20, Math.floor(cols));
651
+ const nextRows = Math.max(10, Math.floor(rows));
652
+ if (session.cols === nextCols && session.rows === nextRows) {
653
+ return true;
654
+ }
655
+
656
+ session.cols = nextCols;
657
+ session.rows = nextRows;
658
+ session.pty.resize(nextCols, nextRows);
659
+ return true;
660
+ };
661
+
662
+ return {
663
+ closeSession,
664
+ handleUpgrade,
665
+ connectDirect,
666
+ startSession,
667
+ writeInput,
668
+ resizeSession,
669
+ close,
670
+ };
671
+ };