4runr-os 2.10.85 → 2.10.86

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 (871) hide show
  1. package/README.md +2 -2
  2. package/SETUP.md +2 -2
  3. package/USER-PRIVACY.md +2 -2
  4. package/apps/os-server/DEPLOYMENT.md +426 -0
  5. package/apps/os-server/JWT-RATE-LIMIT-VALIDATION.md +462 -0
  6. package/apps/os-server/PERSISTENCE-AND-AUTH.md +227 -0
  7. package/apps/os-server/PHASE5-TESTING.md +222 -0
  8. package/apps/os-server/README.md +368 -0
  9. package/apps/os-server/TROUBLESHOOTING.md +541 -0
  10. package/apps/os-server/dist/apps/os-server/src/adapters/gateway-cancel-adapter.d.ts +3 -0
  11. package/apps/os-server/dist/apps/os-server/src/adapters/gateway-cancel-adapter.d.ts.map +1 -0
  12. package/apps/os-server/dist/apps/os-server/src/adapters/gateway-cancel-adapter.js +28 -0
  13. package/apps/os-server/dist/apps/os-server/src/adapters/gateway-cancel-adapter.js.map +1 -0
  14. package/apps/os-server/dist/apps/os-server/src/adapters/redis-sentinel-publisher.d.ts +23 -0
  15. package/apps/os-server/dist/apps/os-server/src/adapters/redis-sentinel-publisher.d.ts.map +1 -0
  16. package/apps/os-server/dist/apps/os-server/src/adapters/redis-sentinel-publisher.js +95 -0
  17. package/apps/os-server/dist/apps/os-server/src/adapters/redis-sentinel-publisher.js.map +1 -0
  18. package/apps/os-server/dist/apps/os-server/src/adapters/sentinel-event-stream.d.ts +14 -0
  19. package/apps/os-server/dist/apps/os-server/src/adapters/sentinel-event-stream.d.ts.map +1 -0
  20. package/apps/os-server/dist/apps/os-server/src/adapters/sentinel-event-stream.js +69 -0
  21. package/apps/os-server/dist/apps/os-server/src/adapters/sentinel-event-stream.js.map +1 -0
  22. package/apps/os-server/dist/apps/os-server/src/agents/definitions-simple.d.ts +10 -0
  23. package/apps/os-server/dist/apps/os-server/src/agents/definitions-simple.d.ts.map +1 -0
  24. package/apps/os-server/dist/apps/os-server/src/agents/definitions-simple.js +377 -0
  25. package/apps/os-server/dist/apps/os-server/src/agents/definitions-simple.js.map +1 -0
  26. package/apps/os-server/dist/apps/os-server/src/agents/definitions.d.ts +14 -0
  27. package/apps/os-server/dist/apps/os-server/src/agents/definitions.d.ts.map +1 -0
  28. package/apps/os-server/dist/apps/os-server/src/agents/definitions.js +210 -0
  29. package/apps/os-server/dist/apps/os-server/src/agents/definitions.js.map +1 -0
  30. package/apps/os-server/dist/apps/os-server/src/agents/local-model-provider.d.ts +25 -0
  31. package/apps/os-server/dist/apps/os-server/src/agents/local-model-provider.d.ts.map +1 -0
  32. package/apps/os-server/dist/apps/os-server/src/agents/local-model-provider.js +144 -0
  33. package/apps/os-server/dist/apps/os-server/src/agents/local-model-provider.js.map +1 -0
  34. package/apps/os-server/dist/apps/os-server/src/agents/tools.d.ts +24 -0
  35. package/apps/os-server/dist/apps/os-server/src/agents/tools.d.ts.map +1 -0
  36. package/apps/os-server/dist/apps/os-server/src/agents/tools.js +110 -0
  37. package/apps/os-server/dist/apps/os-server/src/agents/tools.js.map +1 -0
  38. package/apps/os-server/dist/apps/os-server/src/ai-providers/anthropic-provider.d.ts +14 -0
  39. package/apps/os-server/dist/apps/os-server/src/ai-providers/anthropic-provider.d.ts.map +1 -0
  40. package/apps/os-server/dist/apps/os-server/src/ai-providers/anthropic-provider.js +139 -0
  41. package/apps/os-server/dist/apps/os-server/src/ai-providers/anthropic-provider.js.map +1 -0
  42. package/apps/os-server/dist/apps/os-server/src/ai-providers/index.d.ts +6 -0
  43. package/apps/os-server/dist/apps/os-server/src/ai-providers/index.d.ts.map +1 -0
  44. package/apps/os-server/dist/apps/os-server/src/ai-providers/index.js +6 -0
  45. package/apps/os-server/dist/apps/os-server/src/ai-providers/index.js.map +1 -0
  46. package/apps/os-server/dist/apps/os-server/src/ai-providers/openai-provider.d.ts +14 -0
  47. package/apps/os-server/dist/apps/os-server/src/ai-providers/openai-provider.d.ts.map +1 -0
  48. package/apps/os-server/dist/apps/os-server/src/ai-providers/openai-provider.js +136 -0
  49. package/apps/os-server/dist/apps/os-server/src/ai-providers/openai-provider.js.map +1 -0
  50. package/apps/os-server/dist/apps/os-server/src/ai-providers/provider-manager.d.ts +18 -0
  51. package/apps/os-server/dist/apps/os-server/src/ai-providers/provider-manager.d.ts.map +1 -0
  52. package/apps/os-server/dist/apps/os-server/src/ai-providers/provider-manager.js +91 -0
  53. package/apps/os-server/dist/apps/os-server/src/ai-providers/provider-manager.js.map +1 -0
  54. package/apps/os-server/dist/apps/os-server/src/ai-providers/redis-credentials-store.d.ts +15 -0
  55. package/apps/os-server/dist/apps/os-server/src/ai-providers/redis-credentials-store.d.ts.map +1 -0
  56. package/apps/os-server/dist/apps/os-server/src/ai-providers/redis-credentials-store.js +181 -0
  57. package/apps/os-server/dist/apps/os-server/src/ai-providers/redis-credentials-store.js.map +1 -0
  58. package/apps/os-server/dist/apps/os-server/src/ai-providers/types.d.ts +54 -0
  59. package/apps/os-server/dist/apps/os-server/src/ai-providers/types.d.ts.map +1 -0
  60. package/apps/os-server/dist/apps/os-server/src/ai-providers/types.js +2 -0
  61. package/apps/os-server/dist/apps/os-server/src/ai-providers/types.js.map +1 -0
  62. package/apps/os-server/dist/apps/os-server/src/config/persistence.d.ts +5 -0
  63. package/apps/os-server/dist/apps/os-server/src/config/persistence.d.ts.map +1 -0
  64. package/apps/os-server/dist/apps/os-server/src/config/persistence.js +14 -0
  65. package/apps/os-server/dist/apps/os-server/src/config/persistence.js.map +1 -0
  66. package/apps/os-server/dist/apps/os-server/src/crypto/envelope.d.ts +28 -0
  67. package/apps/os-server/dist/apps/os-server/src/crypto/envelope.d.ts.map +1 -0
  68. package/apps/os-server/dist/apps/os-server/src/crypto/envelope.js +111 -0
  69. package/apps/os-server/dist/apps/os-server/src/crypto/envelope.js.map +1 -0
  70. package/apps/os-server/dist/apps/os-server/src/crypto/mfa-storage.d.ts +6 -0
  71. package/apps/os-server/dist/apps/os-server/src/crypto/mfa-storage.d.ts.map +1 -0
  72. package/apps/os-server/dist/apps/os-server/src/crypto/mfa-storage.js +61 -0
  73. package/apps/os-server/dist/apps/os-server/src/crypto/mfa-storage.js.map +1 -0
  74. package/apps/os-server/dist/apps/os-server/src/db/docker-manager.d.ts +21 -0
  75. package/apps/os-server/dist/apps/os-server/src/db/docker-manager.d.ts.map +1 -0
  76. package/apps/os-server/dist/apps/os-server/src/db/docker-manager.js +166 -0
  77. package/apps/os-server/dist/apps/os-server/src/db/docker-manager.js.map +1 -0
  78. package/apps/os-server/dist/apps/os-server/src/db/init.d.ts +11 -0
  79. package/apps/os-server/dist/apps/os-server/src/db/init.d.ts.map +1 -0
  80. package/apps/os-server/dist/apps/os-server/src/db/init.js +202 -0
  81. package/apps/os-server/dist/apps/os-server/src/db/init.js.map +1 -0
  82. package/apps/os-server/dist/apps/os-server/src/db/memory-db.d.ts +149 -0
  83. package/apps/os-server/dist/apps/os-server/src/db/memory-db.d.ts.map +1 -0
  84. package/apps/os-server/dist/apps/os-server/src/db/memory-db.js +295 -0
  85. package/apps/os-server/dist/apps/os-server/src/db/memory-db.js.map +1 -0
  86. package/apps/os-server/dist/apps/os-server/src/db/prisma.d.ts +4 -0
  87. package/apps/os-server/dist/apps/os-server/src/db/prisma.d.ts.map +1 -0
  88. package/apps/os-server/dist/apps/os-server/src/db/prisma.js +31 -0
  89. package/apps/os-server/dist/apps/os-server/src/db/prisma.js.map +1 -0
  90. package/apps/os-server/dist/apps/os-server/src/db/redis.d.ts +5 -0
  91. package/apps/os-server/dist/apps/os-server/src/db/redis.d.ts.map +1 -0
  92. package/apps/os-server/dist/apps/os-server/src/db/redis.js +69 -0
  93. package/apps/os-server/dist/apps/os-server/src/db/redis.js.map +1 -0
  94. package/apps/os-server/dist/apps/os-server/src/devkit/agents-api.d.ts +3 -0
  95. package/apps/os-server/dist/apps/os-server/src/devkit/agents-api.d.ts.map +1 -0
  96. package/apps/os-server/dist/apps/os-server/src/devkit/agents-api.js +369 -0
  97. package/apps/os-server/dist/apps/os-server/src/devkit/agents-api.js.map +1 -0
  98. package/apps/os-server/dist/apps/os-server/src/devkit/metrics-parser.d.ts +46 -0
  99. package/apps/os-server/dist/apps/os-server/src/devkit/metrics-parser.d.ts.map +1 -0
  100. package/apps/os-server/dist/apps/os-server/src/devkit/metrics-parser.js +114 -0
  101. package/apps/os-server/dist/apps/os-server/src/devkit/metrics-parser.js.map +1 -0
  102. package/apps/os-server/dist/apps/os-server/src/devkit/middleware.d.ts +9 -0
  103. package/apps/os-server/dist/apps/os-server/src/devkit/middleware.d.ts.map +1 -0
  104. package/apps/os-server/dist/apps/os-server/src/devkit/middleware.js +23 -0
  105. package/apps/os-server/dist/apps/os-server/src/devkit/middleware.js.map +1 -0
  106. package/apps/os-server/dist/apps/os-server/src/devkit/routes.d.ts +3 -0
  107. package/apps/os-server/dist/apps/os-server/src/devkit/routes.d.ts.map +1 -0
  108. package/apps/os-server/dist/apps/os-server/src/devkit/routes.js +270 -0
  109. package/apps/os-server/dist/apps/os-server/src/devkit/routes.js.map +1 -0
  110. package/apps/os-server/dist/apps/os-server/src/devkit/tools-api.d.ts +3 -0
  111. package/apps/os-server/dist/apps/os-server/src/devkit/tools-api.d.ts.map +1 -0
  112. package/apps/os-server/dist/apps/os-server/src/devkit/tools-api.js +183 -0
  113. package/apps/os-server/dist/apps/os-server/src/devkit/tools-api.js.map +1 -0
  114. package/apps/os-server/dist/apps/os-server/src/health/index.d.ts +36 -0
  115. package/apps/os-server/dist/apps/os-server/src/health/index.d.ts.map +1 -0
  116. package/apps/os-server/dist/apps/os-server/src/health/index.js +275 -0
  117. package/apps/os-server/dist/apps/os-server/src/health/index.js.map +1 -0
  118. package/apps/os-server/dist/apps/os-server/src/index.d.ts +2 -0
  119. package/apps/os-server/dist/apps/os-server/src/index.d.ts.map +1 -0
  120. package/apps/os-server/dist/apps/os-server/src/index.js +1131 -0
  121. package/apps/os-server/dist/apps/os-server/src/index.js.map +1 -0
  122. package/apps/os-server/dist/apps/os-server/src/metrics/index.d.ts +48 -0
  123. package/apps/os-server/dist/apps/os-server/src/metrics/index.d.ts.map +1 -0
  124. package/apps/os-server/dist/apps/os-server/src/metrics/index.js +296 -0
  125. package/apps/os-server/dist/apps/os-server/src/metrics/index.js.map +1 -0
  126. package/apps/os-server/dist/apps/os-server/src/metrics/monitoring-detail.d.ts +18 -0
  127. package/apps/os-server/dist/apps/os-server/src/metrics/monitoring-detail.d.ts.map +1 -0
  128. package/apps/os-server/dist/apps/os-server/src/metrics/monitoring-detail.js +117 -0
  129. package/apps/os-server/dist/apps/os-server/src/metrics/monitoring-detail.js.map +1 -0
  130. package/apps/os-server/dist/apps/os-server/src/middleware/auth.d.ts +5 -0
  131. package/apps/os-server/dist/apps/os-server/src/middleware/auth.d.ts.map +1 -0
  132. package/apps/os-server/dist/apps/os-server/src/middleware/auth.js +64 -0
  133. package/apps/os-server/dist/apps/os-server/src/middleware/auth.js.map +1 -0
  134. package/apps/os-server/dist/apps/os-server/src/middleware/authApiKey.d.ts +4 -0
  135. package/apps/os-server/dist/apps/os-server/src/middleware/authApiKey.d.ts.map +1 -0
  136. package/apps/os-server/dist/apps/os-server/src/middleware/authApiKey.js +105 -0
  137. package/apps/os-server/dist/apps/os-server/src/middleware/authApiKey.js.map +1 -0
  138. package/apps/os-server/dist/apps/os-server/src/middleware/authJwt.d.ts +12 -0
  139. package/apps/os-server/dist/apps/os-server/src/middleware/authJwt.d.ts.map +1 -0
  140. package/apps/os-server/dist/apps/os-server/src/middleware/authJwt.js +75 -0
  141. package/apps/os-server/dist/apps/os-server/src/middleware/authJwt.js.map +1 -0
  142. package/apps/os-server/dist/apps/os-server/src/middleware/correlationId.d.ts +6 -0
  143. package/apps/os-server/dist/apps/os-server/src/middleware/correlationId.d.ts.map +1 -0
  144. package/apps/os-server/dist/apps/os-server/src/middleware/correlationId.js +21 -0
  145. package/apps/os-server/dist/apps/os-server/src/middleware/correlationId.js.map +1 -0
  146. package/apps/os-server/dist/apps/os-server/src/middleware/ddos-protection.d.ts +13 -0
  147. package/apps/os-server/dist/apps/os-server/src/middleware/ddos-protection.d.ts.map +1 -0
  148. package/apps/os-server/dist/apps/os-server/src/middleware/ddos-protection.js +202 -0
  149. package/apps/os-server/dist/apps/os-server/src/middleware/ddos-protection.js.map +1 -0
  150. package/apps/os-server/dist/apps/os-server/src/middleware/errorHandler.d.ts +14 -0
  151. package/apps/os-server/dist/apps/os-server/src/middleware/errorHandler.d.ts.map +1 -0
  152. package/apps/os-server/dist/apps/os-server/src/middleware/errorHandler.js +98 -0
  153. package/apps/os-server/dist/apps/os-server/src/middleware/errorHandler.js.map +1 -0
  154. package/apps/os-server/dist/apps/os-server/src/middleware/log-capture.d.ts +2 -0
  155. package/apps/os-server/dist/apps/os-server/src/middleware/log-capture.d.ts.map +1 -0
  156. package/apps/os-server/dist/apps/os-server/src/middleware/log-capture.js +63 -0
  157. package/apps/os-server/dist/apps/os-server/src/middleware/log-capture.js.map +1 -0
  158. package/apps/os-server/dist/apps/os-server/src/middleware/mfa.d.ts +3 -0
  159. package/apps/os-server/dist/apps/os-server/src/middleware/mfa.d.ts.map +1 -0
  160. package/apps/os-server/dist/apps/os-server/src/middleware/mfa.js +96 -0
  161. package/apps/os-server/dist/apps/os-server/src/middleware/mfa.js.map +1 -0
  162. package/apps/os-server/dist/apps/os-server/src/middleware/rateLimit.d.ts +6 -0
  163. package/apps/os-server/dist/apps/os-server/src/middleware/rateLimit.d.ts.map +1 -0
  164. package/apps/os-server/dist/apps/os-server/src/middleware/rateLimit.js +54 -0
  165. package/apps/os-server/dist/apps/os-server/src/middleware/rateLimit.js.map +1 -0
  166. package/apps/os-server/dist/apps/os-server/src/middleware/rbac.d.ts +19 -0
  167. package/apps/os-server/dist/apps/os-server/src/middleware/rbac.d.ts.map +1 -0
  168. package/apps/os-server/dist/apps/os-server/src/middleware/rbac.js +229 -0
  169. package/apps/os-server/dist/apps/os-server/src/middleware/rbac.js.map +1 -0
  170. package/apps/os-server/dist/apps/os-server/src/middleware/security.d.ts +10 -0
  171. package/apps/os-server/dist/apps/os-server/src/middleware/security.d.ts.map +1 -0
  172. package/apps/os-server/dist/apps/os-server/src/middleware/security.js +146 -0
  173. package/apps/os-server/dist/apps/os-server/src/middleware/security.js.map +1 -0
  174. package/apps/os-server/dist/apps/os-server/src/middleware/validate.d.ts +7 -0
  175. package/apps/os-server/dist/apps/os-server/src/middleware/validate.d.ts.map +1 -0
  176. package/apps/os-server/dist/apps/os-server/src/middleware/validate.js +156 -0
  177. package/apps/os-server/dist/apps/os-server/src/middleware/validate.js.map +1 -0
  178. package/apps/os-server/dist/apps/os-server/src/queue/config.d.ts +26 -0
  179. package/apps/os-server/dist/apps/os-server/src/queue/config.d.ts.map +1 -0
  180. package/apps/os-server/dist/apps/os-server/src/queue/config.js +21 -0
  181. package/apps/os-server/dist/apps/os-server/src/queue/config.js.map +1 -0
  182. package/apps/os-server/dist/apps/os-server/src/queue/index.d.ts +13 -0
  183. package/apps/os-server/dist/apps/os-server/src/queue/index.d.ts.map +1 -0
  184. package/apps/os-server/dist/apps/os-server/src/queue/index.js +152 -0
  185. package/apps/os-server/dist/apps/os-server/src/queue/index.js.map +1 -0
  186. package/apps/os-server/dist/apps/os-server/src/queue/processor.d.ts +16 -0
  187. package/apps/os-server/dist/apps/os-server/src/queue/processor.d.ts.map +1 -0
  188. package/apps/os-server/dist/apps/os-server/src/queue/processor.js +378 -0
  189. package/apps/os-server/dist/apps/os-server/src/queue/processor.js.map +1 -0
  190. package/apps/os-server/dist/apps/os-server/src/queue/sentinel-execute-watch.d.ts +7 -0
  191. package/apps/os-server/dist/apps/os-server/src/queue/sentinel-execute-watch.d.ts.map +1 -0
  192. package/apps/os-server/dist/apps/os-server/src/queue/sentinel-execute-watch.js +22 -0
  193. package/apps/os-server/dist/apps/os-server/src/queue/sentinel-execute-watch.js.map +1 -0
  194. package/apps/os-server/dist/apps/os-server/src/routes/ai-providers-simple.d.ts +3 -0
  195. package/apps/os-server/dist/apps/os-server/src/routes/ai-providers-simple.d.ts.map +1 -0
  196. package/apps/os-server/dist/apps/os-server/src/routes/ai-providers-simple.js +175 -0
  197. package/apps/os-server/dist/apps/os-server/src/routes/ai-providers-simple.js.map +1 -0
  198. package/apps/os-server/dist/apps/os-server/src/routes/chats.d.ts +3 -0
  199. package/apps/os-server/dist/apps/os-server/src/routes/chats.d.ts.map +1 -0
  200. package/apps/os-server/dist/apps/os-server/src/routes/chats.js +150 -0
  201. package/apps/os-server/dist/apps/os-server/src/routes/chats.js.map +1 -0
  202. package/apps/os-server/dist/apps/os-server/src/routes/gdpr.d.ts +3 -0
  203. package/apps/os-server/dist/apps/os-server/src/routes/gdpr.d.ts.map +1 -0
  204. package/apps/os-server/dist/apps/os-server/src/routes/gdpr.js +252 -0
  205. package/apps/os-server/dist/apps/os-server/src/routes/gdpr.js.map +1 -0
  206. package/apps/os-server/dist/apps/os-server/src/routes/mfa.d.ts +3 -0
  207. package/apps/os-server/dist/apps/os-server/src/routes/mfa.d.ts.map +1 -0
  208. package/apps/os-server/dist/apps/os-server/src/routes/mfa.js +240 -0
  209. package/apps/os-server/dist/apps/os-server/src/routes/mfa.js.map +1 -0
  210. package/apps/os-server/dist/apps/os-server/src/routes/monitoring.d.ts +16 -0
  211. package/apps/os-server/dist/apps/os-server/src/routes/monitoring.d.ts.map +1 -0
  212. package/apps/os-server/dist/apps/os-server/src/routes/monitoring.js +168 -0
  213. package/apps/os-server/dist/apps/os-server/src/routes/monitoring.js.map +1 -0
  214. package/apps/os-server/dist/apps/os-server/src/routes/sentinel-policies.d.ts +3 -0
  215. package/apps/os-server/dist/apps/os-server/src/routes/sentinel-policies.d.ts.map +1 -0
  216. package/apps/os-server/dist/apps/os-server/src/routes/sentinel-policies.js +209 -0
  217. package/apps/os-server/dist/apps/os-server/src/routes/sentinel-policies.js.map +1 -0
  218. package/apps/os-server/dist/apps/os-server/src/routes/sentinel-predictive.d.ts +3 -0
  219. package/apps/os-server/dist/apps/os-server/src/routes/sentinel-predictive.d.ts.map +1 -0
  220. package/apps/os-server/dist/apps/os-server/src/routes/sentinel-predictive.js +88 -0
  221. package/apps/os-server/dist/apps/os-server/src/routes/sentinel-predictive.js.map +1 -0
  222. package/apps/os-server/dist/apps/os-server/src/routes/shield.d.ts +3 -0
  223. package/apps/os-server/dist/apps/os-server/src/routes/shield.d.ts.map +1 -0
  224. package/apps/os-server/dist/apps/os-server/src/routes/shield.js +85 -0
  225. package/apps/os-server/dist/apps/os-server/src/routes/shield.js.map +1 -0
  226. package/apps/os-server/dist/apps/os-server/src/routes/tool-credentials.d.ts +3 -0
  227. package/apps/os-server/dist/apps/os-server/src/routes/tool-credentials.d.ts.map +1 -0
  228. package/apps/os-server/dist/apps/os-server/src/routes/tool-credentials.js +209 -0
  229. package/apps/os-server/dist/apps/os-server/src/routes/tool-credentials.js.map +1 -0
  230. package/apps/os-server/dist/apps/os-server/src/routes/tool-proxy.d.ts +3 -0
  231. package/apps/os-server/dist/apps/os-server/src/routes/tool-proxy.d.ts.map +1 -0
  232. package/apps/os-server/dist/apps/os-server/src/routes/tool-proxy.js +206 -0
  233. package/apps/os-server/dist/apps/os-server/src/routes/tool-proxy.js.map +1 -0
  234. package/apps/os-server/dist/apps/os-server/src/runs/index.d.ts +5 -0
  235. package/apps/os-server/dist/apps/os-server/src/runs/index.d.ts.map +1 -0
  236. package/apps/os-server/dist/apps/os-server/src/runs/index.js +19 -0
  237. package/apps/os-server/dist/apps/os-server/src/runs/index.js.map +1 -0
  238. package/apps/os-server/dist/apps/os-server/src/runs/memoryRunStore.d.ts +12 -0
  239. package/apps/os-server/dist/apps/os-server/src/runs/memoryRunStore.d.ts.map +1 -0
  240. package/apps/os-server/dist/apps/os-server/src/runs/memoryRunStore.js +78 -0
  241. package/apps/os-server/dist/apps/os-server/src/runs/memoryRunStore.js.map +1 -0
  242. package/apps/os-server/dist/apps/os-server/src/runs/postgresRunStore.d.ts +15 -0
  243. package/apps/os-server/dist/apps/os-server/src/runs/postgresRunStore.d.ts.map +1 -0
  244. package/apps/os-server/dist/apps/os-server/src/runs/postgresRunStore.js +148 -0
  245. package/apps/os-server/dist/apps/os-server/src/runs/postgresRunStore.js.map +1 -0
  246. package/apps/os-server/dist/apps/os-server/src/runs/run-kill.d.ts +20 -0
  247. package/apps/os-server/dist/apps/os-server/src/runs/run-kill.d.ts.map +1 -0
  248. package/apps/os-server/dist/apps/os-server/src/runs/run-kill.js +69 -0
  249. package/apps/os-server/dist/apps/os-server/src/runs/run-kill.js.map +1 -0
  250. package/apps/os-server/dist/apps/os-server/src/runs/run-retention.d.ts +17 -0
  251. package/apps/os-server/dist/apps/os-server/src/runs/run-retention.d.ts.map +1 -0
  252. package/apps/os-server/dist/apps/os-server/src/runs/run-retention.js +124 -0
  253. package/apps/os-server/dist/apps/os-server/src/runs/run-retention.js.map +1 -0
  254. package/apps/os-server/dist/apps/os-server/src/runs/runStore.d.ts +10 -0
  255. package/apps/os-server/dist/apps/os-server/src/runs/runStore.d.ts.map +1 -0
  256. package/apps/os-server/dist/apps/os-server/src/runs/runStore.js +2 -0
  257. package/apps/os-server/dist/apps/os-server/src/runs/runStore.js.map +1 -0
  258. package/apps/os-server/dist/apps/os-server/src/runs/types.d.ts +44 -0
  259. package/apps/os-server/dist/apps/os-server/src/runs/types.d.ts.map +1 -0
  260. package/apps/os-server/dist/apps/os-server/src/runs/types.js +2 -0
  261. package/apps/os-server/dist/apps/os-server/src/runs/types.js.map +1 -0
  262. package/apps/os-server/dist/apps/os-server/src/schemas/runs.d.ts +126 -0
  263. package/apps/os-server/dist/apps/os-server/src/schemas/runs.d.ts.map +1 -0
  264. package/apps/os-server/dist/apps/os-server/src/schemas/runs.js +63 -0
  265. package/apps/os-server/dist/apps/os-server/src/schemas/runs.js.map +1 -0
  266. package/apps/os-server/dist/apps/os-server/src/security/agent-token.d.ts +51 -0
  267. package/apps/os-server/dist/apps/os-server/src/security/agent-token.d.ts.map +1 -0
  268. package/apps/os-server/dist/apps/os-server/src/security/agent-token.js +161 -0
  269. package/apps/os-server/dist/apps/os-server/src/security/agent-token.js.map +1 -0
  270. package/apps/os-server/dist/apps/os-server/src/security/outbound-url.d.ts +2 -0
  271. package/apps/os-server/dist/apps/os-server/src/security/outbound-url.d.ts.map +1 -0
  272. package/apps/os-server/dist/apps/os-server/src/security/outbound-url.js +65 -0
  273. package/apps/os-server/dist/apps/os-server/src/security/outbound-url.js.map +1 -0
  274. package/apps/os-server/dist/apps/os-server/src/security/sentinel-config-store.d.ts +14 -0
  275. package/apps/os-server/dist/apps/os-server/src/security/sentinel-config-store.d.ts.map +1 -0
  276. package/apps/os-server/dist/apps/os-server/src/security/sentinel-config-store.js +168 -0
  277. package/apps/os-server/dist/apps/os-server/src/security/sentinel-config-store.js.map +1 -0
  278. package/apps/os-server/dist/apps/os-server/src/security/sentinel-run-failure.d.ts +25 -0
  279. package/apps/os-server/dist/apps/os-server/src/security/sentinel-run-failure.d.ts.map +1 -0
  280. package/apps/os-server/dist/apps/os-server/src/security/sentinel-run-failure.js +46 -0
  281. package/apps/os-server/dist/apps/os-server/src/security/sentinel-run-failure.js.map +1 -0
  282. package/apps/os-server/dist/apps/os-server/src/security/shield-config.d.ts +14 -0
  283. package/apps/os-server/dist/apps/os-server/src/security/shield-config.d.ts.map +1 -0
  284. package/apps/os-server/dist/apps/os-server/src/security/shield-config.js +70 -0
  285. package/apps/os-server/dist/apps/os-server/src/security/shield-config.js.map +1 -0
  286. package/apps/os-server/dist/apps/os-server/src/utils/circuit-breaker.d.ts +13 -0
  287. package/apps/os-server/dist/apps/os-server/src/utils/circuit-breaker.d.ts.map +1 -0
  288. package/apps/os-server/dist/apps/os-server/src/utils/circuit-breaker.js +72 -0
  289. package/apps/os-server/dist/apps/os-server/src/utils/circuit-breaker.js.map +1 -0
  290. package/apps/os-server/dist/apps/os-server/src/utils/log-encryption.d.ts +10 -0
  291. package/apps/os-server/dist/apps/os-server/src/utils/log-encryption.d.ts.map +1 -0
  292. package/apps/os-server/dist/apps/os-server/src/utils/log-encryption.js +140 -0
  293. package/apps/os-server/dist/apps/os-server/src/utils/log-encryption.js.map +1 -0
  294. package/apps/os-server/docker-compose.local.yml +46 -0
  295. package/apps/os-server/find-gateway.sh +37 -0
  296. package/apps/os-server/jest.config.cjs +52 -0
  297. package/apps/os-server/package-lock.json +8809 -0
  298. package/apps/os-server/package.json +66 -0
  299. package/apps/os-server/scripts/setup-test-env.sh +67 -0
  300. package/apps/os-server/src/__tests__/integration/README.md +92 -0
  301. package/apps/os-server/src/__tests__/integration/helpers/test-server.ts +178 -0
  302. package/apps/os-server/src/__tests__/integration/sentinel.test.ts +364 -0
  303. package/apps/os-server/src/adapters/redis-sentinel-publisher.ts +176 -0
  304. package/apps/os-server/src/agents/definitions-simple.ts +536 -0
  305. package/apps/os-server/src/ai-providers/anthropic-provider.ts +194 -0
  306. package/apps/os-server/src/ai-providers/openai-provider.ts +193 -0
  307. package/apps/os-server/src/ai-providers/redis-credentials-store.ts +224 -0
  308. package/apps/os-server/src/db/docker-manager.ts +262 -0
  309. package/apps/os-server/src/db/init.ts +292 -0
  310. package/apps/os-server/src/devkit/routes.ts +349 -0
  311. package/apps/os-server/src/health/index.ts +375 -0
  312. package/apps/os-server/src/index.ts +1506 -0
  313. package/apps/os-server/src/middleware/correlationId.ts +56 -0
  314. package/apps/os-server/src/middleware/ddos-protection.ts +318 -0
  315. package/apps/os-server/src/middleware/log-capture.ts +92 -0
  316. package/apps/os-server/src/middleware/security.ts +222 -0
  317. package/apps/os-server/src/routes/ai-providers-simple.ts +220 -0
  318. package/apps/os-server/src/routes/gdpr.ts +337 -0
  319. package/apps/os-server/src/routes/monitoring.ts +315 -0
  320. package/apps/os-server/src/routes/sentinel-policies.ts +293 -0
  321. package/apps/os-server/src/routes/sentinel-predictive.ts +129 -0
  322. package/apps/os-server/src/routes/tool-proxy.ts +285 -0
  323. package/apps/os-server/src/security/outbound-url.ts +83 -0
  324. package/apps/os-server/update-and-test.sh +36 -0
  325. package/dist/boot-sequence.js +1 -1
  326. package/dist/boot-sequence.js.map +1 -1
  327. package/dist/gateway-api-key-bootstrap.js +1 -1
  328. package/dist/gateway-api-key-bootstrap.js.map +1 -1
  329. package/dist/gateway-prisma-bootstrap.d.ts +1 -1
  330. package/dist/gateway-prisma-bootstrap.js +1 -1
  331. package/dist/index.js +8 -8
  332. package/dist/index.js.map +1 -1
  333. package/dist/local-gateway-pid.d.ts +1 -1
  334. package/dist/local-gateway-pid.js +1 -1
  335. package/dist/os-tui-binary-bootstrap.d.ts +18 -0
  336. package/dist/os-tui-binary-bootstrap.d.ts.map +1 -0
  337. package/dist/os-tui-binary-bootstrap.js +175 -0
  338. package/dist/os-tui-binary-bootstrap.js.map +1 -0
  339. package/dist/rust-check.js +2 -2
  340. package/dist/rust-check.js.map +1 -1
  341. package/dist/system-verify.js +8 -8
  342. package/dist/system-verify.js.map +1 -1
  343. package/dist/tui-handlers.js +12 -12
  344. package/dist/tui-handlers.js.map +1 -1
  345. package/dist/watchdog.js +73 -73
  346. package/os-tui/Cargo.lock +1118 -0
  347. package/os-tui/Cargo.toml +17 -0
  348. package/os-tui/bin/os-tui.js +63 -0
  349. package/os-tui/binaries/win32-x64/os-tui.exe +0 -0
  350. package/os-tui/src/app/render_scheduler.rs +111 -0
  351. package/os-tui/src/app.rs +3814 -0
  352. package/os-tui/src/debug_log.rs +131 -0
  353. package/os-tui/src/io/mod.rs +63 -0
  354. package/os-tui/src/io/protocol.rs +14 -0
  355. package/os-tui/src/io/stdio.rs +31 -0
  356. package/os-tui/src/io/ws.rs +25 -0
  357. package/os-tui/src/main.rs +2090 -0
  358. package/os-tui/src/monitoring/mod.rs +439 -0
  359. package/os-tui/src/screens/mod.rs +254 -0
  360. package/os-tui/src/storage/cache.rs +221 -0
  361. package/os-tui/src/storage/mod.rs +5 -0
  362. package/os-tui/src/ui/agent_builder.rs +1148 -0
  363. package/os-tui/src/ui/agent_list.rs +344 -0
  364. package/os-tui/src/ui/boot.rs +145 -0
  365. package/os-tui/src/ui/connection_portal.rs +839 -0
  366. package/os-tui/src/ui/help.rs +340 -0
  367. package/os-tui/src/ui/layout.rs +978 -0
  368. package/os-tui/src/ui/mod.rs +13 -0
  369. package/os-tui/src/ui/portal_monitoring.rs +1128 -0
  370. package/os-tui/src/ui/run_manager.rs +922 -0
  371. package/os-tui/src/ui/safe_viewport.rs +236 -0
  372. package/os-tui/src/ui/sentinel_config.rs +828 -0
  373. package/os-tui/src/ui/settings.rs +414 -0
  374. package/os-tui/src/ui/setup_portal.rs +547 -0
  375. package/os-tui/src/ui/shield_dashboard.rs +1081 -0
  376. package/os-tui/src/websocket.rs +315 -0
  377. package/package.json +121 -121
  378. package/prisma/schema.prisma +470 -470
  379. package/scripts/cleanup-build-artifacts.js +2 -2
  380. package/scripts/os-tools-smoke.cjs +549 -549
  381. package/scripts/postinstall-os-server.js +63 -0
  382. package/scripts/postinstall-os-tui.js +64 -0
  383. package/scripts/seed-os-server-api-key.cjs +77 -0
  384. package/scripts/setup.js +11 -11
  385. package/src/security/package-integrity.ts +188 -188
  386. package/src/security/ssrf-protection.ts +147 -147
  387. package/apps/gateway/DEPLOYMENT.md +0 -426
  388. package/apps/gateway/JWT-RATE-LIMIT-VALIDATION.md +0 -462
  389. package/apps/gateway/PERSISTENCE-AND-AUTH.md +0 -227
  390. package/apps/gateway/PHASE5-TESTING.md +0 -222
  391. package/apps/gateway/README.md +0 -368
  392. package/apps/gateway/TROUBLESHOOTING.md +0 -541
  393. package/apps/gateway/docker-compose.local.yml +0 -46
  394. package/apps/gateway/find-gateway.sh +0 -37
  395. package/apps/gateway/jest.config.cjs +0 -52
  396. package/apps/gateway/package-lock.json +0 -8810
  397. package/apps/gateway/package.json +0 -66
  398. package/apps/gateway/scripts/setup-test-env.sh +0 -67
  399. package/apps/gateway/src/__tests__/integration/README.md +0 -92
  400. package/apps/gateway/src/__tests__/integration/helpers/test-server.ts +0 -178
  401. package/apps/gateway/src/__tests__/integration/sentinel.test.ts +0 -364
  402. package/apps/gateway/src/adapters/redis-sentinel-publisher.ts +0 -176
  403. package/apps/gateway/src/agents/definitions-simple.ts +0 -536
  404. package/apps/gateway/src/ai-providers/anthropic-provider.ts +0 -194
  405. package/apps/gateway/src/ai-providers/openai-provider.ts +0 -193
  406. package/apps/gateway/src/ai-providers/redis-credentials-store.ts +0 -224
  407. package/apps/gateway/src/db/docker-manager.ts +0 -262
  408. package/apps/gateway/src/db/init.ts +0 -292
  409. package/apps/gateway/src/devkit/routes.ts +0 -349
  410. package/apps/gateway/src/health/index.ts +0 -375
  411. package/apps/gateway/src/index.ts +0 -1506
  412. package/apps/gateway/src/middleware/correlationId.ts +0 -56
  413. package/apps/gateway/src/middleware/ddos-protection.ts +0 -318
  414. package/apps/gateway/src/middleware/log-capture.ts +0 -92
  415. package/apps/gateway/src/middleware/security.ts +0 -222
  416. package/apps/gateway/src/routes/ai-providers-simple.ts +0 -220
  417. package/apps/gateway/src/routes/gdpr.ts +0 -337
  418. package/apps/gateway/src/routes/monitoring.ts +0 -315
  419. package/apps/gateway/src/routes/sentinel-policies.ts +0 -293
  420. package/apps/gateway/src/routes/sentinel-predictive.ts +0 -129
  421. package/apps/gateway/src/routes/tool-proxy.ts +0 -285
  422. package/apps/gateway/src/security/outbound-url.ts +0 -83
  423. package/apps/gateway/update-and-test.sh +0 -36
  424. package/mk3-tui/Cargo.lock +0 -1118
  425. package/mk3-tui/Cargo.toml +0 -17
  426. package/mk3-tui/bin/mk3-tui.js +0 -63
  427. package/mk3-tui/binaries/win32-x64/mk3-tui.exe +0 -0
  428. package/mk3-tui/src/app/render_scheduler.rs +0 -111
  429. package/mk3-tui/src/app.rs +0 -3813
  430. package/mk3-tui/src/debug_log.rs +0 -131
  431. package/mk3-tui/src/io/mod.rs +0 -63
  432. package/mk3-tui/src/io/protocol.rs +0 -14
  433. package/mk3-tui/src/io/stdio.rs +0 -31
  434. package/mk3-tui/src/io/ws.rs +0 -25
  435. package/mk3-tui/src/main.rs +0 -2090
  436. package/mk3-tui/src/monitoring/mod.rs +0 -439
  437. package/mk3-tui/src/screens/mod.rs +0 -254
  438. package/mk3-tui/src/storage/cache.rs +0 -221
  439. package/mk3-tui/src/storage/mod.rs +0 -5
  440. package/mk3-tui/src/ui/agent_builder.rs +0 -1148
  441. package/mk3-tui/src/ui/agent_list.rs +0 -344
  442. package/mk3-tui/src/ui/boot.rs +0 -145
  443. package/mk3-tui/src/ui/connection_portal.rs +0 -839
  444. package/mk3-tui/src/ui/help.rs +0 -340
  445. package/mk3-tui/src/ui/layout.rs +0 -978
  446. package/mk3-tui/src/ui/mod.rs +0 -13
  447. package/mk3-tui/src/ui/portal_monitoring.rs +0 -1128
  448. package/mk3-tui/src/ui/run_manager.rs +0 -913
  449. package/mk3-tui/src/ui/safe_viewport.rs +0 -236
  450. package/mk3-tui/src/ui/sentinel_config.rs +0 -828
  451. package/mk3-tui/src/ui/settings.rs +0 -414
  452. package/mk3-tui/src/ui/setup_portal.rs +0 -547
  453. package/mk3-tui/src/ui/shield_dashboard.rs +0 -1081
  454. package/mk3-tui/src/websocket.rs +0 -315
  455. package/scripts/postinstall-gateway.js +0 -63
  456. package/scripts/postinstall-mk3.js +0 -64
  457. package/scripts/seed-gateway-api-key.cjs +0 -77
  458. /package/apps/{gateway → os-server}/.dockerignore +0 -0
  459. /package/apps/{gateway → os-server}/.eslintrc.json +0 -0
  460. /package/apps/{gateway → os-server}/Dockerfile +0 -0
  461. /package/apps/{gateway → os-server}/Dockerfile.bak +0 -0
  462. /package/apps/{gateway → os-server}/ENV_VARIABLES.md +0 -0
  463. /package/apps/{gateway → os-server}/create-test-script.sh +0 -0
  464. /package/apps/{gateway → os-server}/debug-api-responses.sh +0 -0
  465. /package/apps/{gateway → os-server}/debug-failing-tests-v2.sh +0 -0
  466. /package/apps/{gateway → os-server}/debug-failing-tests.sh +0 -0
  467. /package/apps/{gateway → os-server}/debug-responses.sh +0 -0
  468. /package/apps/{gateway → os-server}/debug-test-responses.sh +0 -0
  469. /package/apps/{gateway → os-server}/debug-tests.sh +0 -0
  470. /package/apps/{gateway → os-server}/diagnose-test-failure.sh +0 -0
  471. /package/apps/{gateway → os-server}/dist/apps/gateway/src/adapters/gateway-cancel-adapter.d.ts +0 -0
  472. /package/apps/{gateway → os-server}/dist/apps/gateway/src/adapters/gateway-cancel-adapter.d.ts.map +0 -0
  473. /package/apps/{gateway → os-server}/dist/apps/gateway/src/adapters/gateway-cancel-adapter.js +0 -0
  474. /package/apps/{gateway → os-server}/dist/apps/gateway/src/adapters/gateway-cancel-adapter.js.map +0 -0
  475. /package/apps/{gateway → os-server}/dist/apps/gateway/src/adapters/redis-sentinel-publisher.d.ts +0 -0
  476. /package/apps/{gateway → os-server}/dist/apps/gateway/src/adapters/redis-sentinel-publisher.d.ts.map +0 -0
  477. /package/apps/{gateway → os-server}/dist/apps/gateway/src/adapters/redis-sentinel-publisher.js +0 -0
  478. /package/apps/{gateway → os-server}/dist/apps/gateway/src/adapters/redis-sentinel-publisher.js.map +0 -0
  479. /package/apps/{gateway → os-server}/dist/apps/gateway/src/adapters/sentinel-event-stream.d.ts +0 -0
  480. /package/apps/{gateway → os-server}/dist/apps/gateway/src/adapters/sentinel-event-stream.d.ts.map +0 -0
  481. /package/apps/{gateway → os-server}/dist/apps/gateway/src/adapters/sentinel-event-stream.js +0 -0
  482. /package/apps/{gateway → os-server}/dist/apps/gateway/src/adapters/sentinel-event-stream.js.map +0 -0
  483. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/definitions-simple.d.ts +0 -0
  484. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/definitions-simple.d.ts.map +0 -0
  485. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/definitions-simple.js +0 -0
  486. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/definitions-simple.js.map +0 -0
  487. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/definitions.d.ts +0 -0
  488. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/definitions.d.ts.map +0 -0
  489. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/definitions.js +0 -0
  490. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/definitions.js.map +0 -0
  491. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/local-model-provider.d.ts +0 -0
  492. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/local-model-provider.d.ts.map +0 -0
  493. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/local-model-provider.js +0 -0
  494. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/local-model-provider.js.map +0 -0
  495. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/tools.d.ts +0 -0
  496. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/tools.d.ts.map +0 -0
  497. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/tools.js +0 -0
  498. /package/apps/{gateway → os-server}/dist/apps/gateway/src/agents/tools.js.map +0 -0
  499. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/anthropic-provider.d.ts +0 -0
  500. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/anthropic-provider.d.ts.map +0 -0
  501. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/anthropic-provider.js +0 -0
  502. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/anthropic-provider.js.map +0 -0
  503. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/index.d.ts +0 -0
  504. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/index.d.ts.map +0 -0
  505. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/index.js +0 -0
  506. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/index.js.map +0 -0
  507. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/openai-provider.d.ts +0 -0
  508. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/openai-provider.d.ts.map +0 -0
  509. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/openai-provider.js +0 -0
  510. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/openai-provider.js.map +0 -0
  511. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/provider-manager.d.ts +0 -0
  512. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/provider-manager.d.ts.map +0 -0
  513. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/provider-manager.js +0 -0
  514. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/provider-manager.js.map +0 -0
  515. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/redis-credentials-store.d.ts +0 -0
  516. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/redis-credentials-store.d.ts.map +0 -0
  517. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/redis-credentials-store.js +0 -0
  518. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/redis-credentials-store.js.map +0 -0
  519. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/types.d.ts +0 -0
  520. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/types.d.ts.map +0 -0
  521. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/types.js +0 -0
  522. /package/apps/{gateway → os-server}/dist/apps/gateway/src/ai-providers/types.js.map +0 -0
  523. /package/apps/{gateway → os-server}/dist/apps/gateway/src/config/persistence.d.ts +0 -0
  524. /package/apps/{gateway → os-server}/dist/apps/gateway/src/config/persistence.d.ts.map +0 -0
  525. /package/apps/{gateway → os-server}/dist/apps/gateway/src/config/persistence.js +0 -0
  526. /package/apps/{gateway → os-server}/dist/apps/gateway/src/config/persistence.js.map +0 -0
  527. /package/apps/{gateway → os-server}/dist/apps/gateway/src/crypto/envelope.d.ts +0 -0
  528. /package/apps/{gateway → os-server}/dist/apps/gateway/src/crypto/envelope.d.ts.map +0 -0
  529. /package/apps/{gateway → os-server}/dist/apps/gateway/src/crypto/envelope.js +0 -0
  530. /package/apps/{gateway → os-server}/dist/apps/gateway/src/crypto/envelope.js.map +0 -0
  531. /package/apps/{gateway → os-server}/dist/apps/gateway/src/crypto/mfa-storage.d.ts +0 -0
  532. /package/apps/{gateway → os-server}/dist/apps/gateway/src/crypto/mfa-storage.d.ts.map +0 -0
  533. /package/apps/{gateway → os-server}/dist/apps/gateway/src/crypto/mfa-storage.js +0 -0
  534. /package/apps/{gateway → os-server}/dist/apps/gateway/src/crypto/mfa-storage.js.map +0 -0
  535. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/docker-manager.d.ts +0 -0
  536. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/docker-manager.d.ts.map +0 -0
  537. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/docker-manager.js +0 -0
  538. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/docker-manager.js.map +0 -0
  539. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/init.d.ts +0 -0
  540. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/init.d.ts.map +0 -0
  541. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/init.js +0 -0
  542. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/init.js.map +0 -0
  543. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/memory-db.d.ts +0 -0
  544. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/memory-db.d.ts.map +0 -0
  545. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/memory-db.js +0 -0
  546. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/memory-db.js.map +0 -0
  547. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/prisma.d.ts +0 -0
  548. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/prisma.d.ts.map +0 -0
  549. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/prisma.js +0 -0
  550. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/prisma.js.map +0 -0
  551. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/redis.d.ts +0 -0
  552. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/redis.d.ts.map +0 -0
  553. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/redis.js +0 -0
  554. /package/apps/{gateway → os-server}/dist/apps/gateway/src/db/redis.js.map +0 -0
  555. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/agents-api.d.ts +0 -0
  556. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/agents-api.d.ts.map +0 -0
  557. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/agents-api.js +0 -0
  558. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/agents-api.js.map +0 -0
  559. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/metrics-parser.d.ts +0 -0
  560. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/metrics-parser.d.ts.map +0 -0
  561. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/metrics-parser.js +0 -0
  562. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/metrics-parser.js.map +0 -0
  563. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/middleware.d.ts +0 -0
  564. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/middleware.d.ts.map +0 -0
  565. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/middleware.js +0 -0
  566. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/middleware.js.map +0 -0
  567. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/routes.d.ts +0 -0
  568. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/routes.d.ts.map +0 -0
  569. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/routes.js +0 -0
  570. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/routes.js.map +0 -0
  571. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/tools-api.d.ts +0 -0
  572. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/tools-api.d.ts.map +0 -0
  573. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/tools-api.js +0 -0
  574. /package/apps/{gateway → os-server}/dist/apps/gateway/src/devkit/tools-api.js.map +0 -0
  575. /package/apps/{gateway → os-server}/dist/apps/gateway/src/health/index.d.ts +0 -0
  576. /package/apps/{gateway → os-server}/dist/apps/gateway/src/health/index.d.ts.map +0 -0
  577. /package/apps/{gateway → os-server}/dist/apps/gateway/src/health/index.js +0 -0
  578. /package/apps/{gateway → os-server}/dist/apps/gateway/src/health/index.js.map +0 -0
  579. /package/apps/{gateway → os-server}/dist/apps/gateway/src/index.d.ts +0 -0
  580. /package/apps/{gateway → os-server}/dist/apps/gateway/src/index.d.ts.map +0 -0
  581. /package/apps/{gateway → os-server}/dist/apps/gateway/src/index.js +0 -0
  582. /package/apps/{gateway → os-server}/dist/apps/gateway/src/index.js.map +0 -0
  583. /package/apps/{gateway → os-server}/dist/apps/gateway/src/metrics/index.d.ts +0 -0
  584. /package/apps/{gateway → os-server}/dist/apps/gateway/src/metrics/index.d.ts.map +0 -0
  585. /package/apps/{gateway → os-server}/dist/apps/gateway/src/metrics/index.js +0 -0
  586. /package/apps/{gateway → os-server}/dist/apps/gateway/src/metrics/index.js.map +0 -0
  587. /package/apps/{gateway → os-server}/dist/apps/gateway/src/metrics/monitoring-detail.d.ts +0 -0
  588. /package/apps/{gateway → os-server}/dist/apps/gateway/src/metrics/monitoring-detail.d.ts.map +0 -0
  589. /package/apps/{gateway → os-server}/dist/apps/gateway/src/metrics/monitoring-detail.js +0 -0
  590. /package/apps/{gateway → os-server}/dist/apps/gateway/src/metrics/monitoring-detail.js.map +0 -0
  591. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/auth.d.ts +0 -0
  592. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/auth.d.ts.map +0 -0
  593. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/auth.js +0 -0
  594. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/auth.js.map +0 -0
  595. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/authApiKey.d.ts +0 -0
  596. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/authApiKey.d.ts.map +0 -0
  597. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/authApiKey.js +0 -0
  598. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/authApiKey.js.map +0 -0
  599. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/authJwt.d.ts +0 -0
  600. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/authJwt.d.ts.map +0 -0
  601. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/authJwt.js +0 -0
  602. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/authJwt.js.map +0 -0
  603. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/correlationId.d.ts +0 -0
  604. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/correlationId.d.ts.map +0 -0
  605. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/correlationId.js +0 -0
  606. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/correlationId.js.map +0 -0
  607. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/ddos-protection.d.ts +0 -0
  608. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/ddos-protection.d.ts.map +0 -0
  609. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/ddos-protection.js +0 -0
  610. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/ddos-protection.js.map +0 -0
  611. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/errorHandler.d.ts +0 -0
  612. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/errorHandler.d.ts.map +0 -0
  613. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/errorHandler.js +0 -0
  614. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/errorHandler.js.map +0 -0
  615. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/log-capture.d.ts +0 -0
  616. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/log-capture.d.ts.map +0 -0
  617. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/log-capture.js +0 -0
  618. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/log-capture.js.map +0 -0
  619. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/mfa.d.ts +0 -0
  620. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/mfa.d.ts.map +0 -0
  621. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/mfa.js +0 -0
  622. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/mfa.js.map +0 -0
  623. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/rateLimit.d.ts +0 -0
  624. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/rateLimit.d.ts.map +0 -0
  625. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/rateLimit.js +0 -0
  626. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/rateLimit.js.map +0 -0
  627. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/rbac.d.ts +0 -0
  628. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/rbac.d.ts.map +0 -0
  629. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/rbac.js +0 -0
  630. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/rbac.js.map +0 -0
  631. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/security.d.ts +0 -0
  632. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/security.d.ts.map +0 -0
  633. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/security.js +0 -0
  634. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/security.js.map +0 -0
  635. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/validate.d.ts +0 -0
  636. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/validate.d.ts.map +0 -0
  637. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/validate.js +0 -0
  638. /package/apps/{gateway → os-server}/dist/apps/gateway/src/middleware/validate.js.map +0 -0
  639. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/config.d.ts +0 -0
  640. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/config.d.ts.map +0 -0
  641. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/config.js +0 -0
  642. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/config.js.map +0 -0
  643. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/index.d.ts +0 -0
  644. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/index.d.ts.map +0 -0
  645. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/index.js +0 -0
  646. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/index.js.map +0 -0
  647. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/processor.d.ts +0 -0
  648. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/processor.d.ts.map +0 -0
  649. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/processor.js +0 -0
  650. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/processor.js.map +0 -0
  651. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/sentinel-execute-watch.d.ts +0 -0
  652. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/sentinel-execute-watch.d.ts.map +0 -0
  653. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/sentinel-execute-watch.js +0 -0
  654. /package/apps/{gateway → os-server}/dist/apps/gateway/src/queue/sentinel-execute-watch.js.map +0 -0
  655. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/ai-providers-simple.d.ts +0 -0
  656. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/ai-providers-simple.d.ts.map +0 -0
  657. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/ai-providers-simple.js +0 -0
  658. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/ai-providers-simple.js.map +0 -0
  659. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/chats.d.ts +0 -0
  660. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/chats.d.ts.map +0 -0
  661. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/chats.js +0 -0
  662. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/chats.js.map +0 -0
  663. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/gdpr.d.ts +0 -0
  664. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/gdpr.d.ts.map +0 -0
  665. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/gdpr.js +0 -0
  666. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/gdpr.js.map +0 -0
  667. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/mfa.d.ts +0 -0
  668. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/mfa.d.ts.map +0 -0
  669. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/mfa.js +0 -0
  670. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/mfa.js.map +0 -0
  671. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/monitoring.d.ts +0 -0
  672. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/monitoring.d.ts.map +0 -0
  673. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/monitoring.js +0 -0
  674. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/monitoring.js.map +0 -0
  675. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/sentinel-policies.d.ts +0 -0
  676. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/sentinel-policies.d.ts.map +0 -0
  677. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/sentinel-policies.js +0 -0
  678. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/sentinel-policies.js.map +0 -0
  679. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/sentinel-predictive.d.ts +0 -0
  680. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/sentinel-predictive.d.ts.map +0 -0
  681. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/sentinel-predictive.js +0 -0
  682. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/sentinel-predictive.js.map +0 -0
  683. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/shield.d.ts +0 -0
  684. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/shield.d.ts.map +0 -0
  685. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/shield.js +0 -0
  686. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/shield.js.map +0 -0
  687. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/tool-credentials.d.ts +0 -0
  688. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/tool-credentials.d.ts.map +0 -0
  689. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/tool-credentials.js +0 -0
  690. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/tool-credentials.js.map +0 -0
  691. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/tool-proxy.d.ts +0 -0
  692. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/tool-proxy.d.ts.map +0 -0
  693. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/tool-proxy.js +0 -0
  694. /package/apps/{gateway → os-server}/dist/apps/gateway/src/routes/tool-proxy.js.map +0 -0
  695. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/index.d.ts +0 -0
  696. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/index.d.ts.map +0 -0
  697. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/index.js +0 -0
  698. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/index.js.map +0 -0
  699. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/memoryRunStore.d.ts +0 -0
  700. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/memoryRunStore.d.ts.map +0 -0
  701. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/memoryRunStore.js +0 -0
  702. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/memoryRunStore.js.map +0 -0
  703. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/postgresRunStore.d.ts +0 -0
  704. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/postgresRunStore.d.ts.map +0 -0
  705. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/postgresRunStore.js +0 -0
  706. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/postgresRunStore.js.map +0 -0
  707. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/run-kill.d.ts +0 -0
  708. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/run-kill.d.ts.map +0 -0
  709. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/run-kill.js +0 -0
  710. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/run-kill.js.map +0 -0
  711. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/run-retention.d.ts +0 -0
  712. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/run-retention.d.ts.map +0 -0
  713. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/run-retention.js +0 -0
  714. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/run-retention.js.map +0 -0
  715. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/runStore.d.ts +0 -0
  716. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/runStore.d.ts.map +0 -0
  717. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/runStore.js +0 -0
  718. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/runStore.js.map +0 -0
  719. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/types.d.ts +0 -0
  720. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/types.d.ts.map +0 -0
  721. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/types.js +0 -0
  722. /package/apps/{gateway → os-server}/dist/apps/gateway/src/runs/types.js.map +0 -0
  723. /package/apps/{gateway → os-server}/dist/apps/gateway/src/schemas/runs.d.ts +0 -0
  724. /package/apps/{gateway → os-server}/dist/apps/gateway/src/schemas/runs.d.ts.map +0 -0
  725. /package/apps/{gateway → os-server}/dist/apps/gateway/src/schemas/runs.js +0 -0
  726. /package/apps/{gateway → os-server}/dist/apps/gateway/src/schemas/runs.js.map +0 -0
  727. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/agent-token.d.ts +0 -0
  728. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/agent-token.d.ts.map +0 -0
  729. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/agent-token.js +0 -0
  730. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/agent-token.js.map +0 -0
  731. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/outbound-url.d.ts +0 -0
  732. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/outbound-url.d.ts.map +0 -0
  733. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/outbound-url.js +0 -0
  734. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/outbound-url.js.map +0 -0
  735. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/sentinel-config-store.d.ts +0 -0
  736. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/sentinel-config-store.d.ts.map +0 -0
  737. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/sentinel-config-store.js +0 -0
  738. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/sentinel-config-store.js.map +0 -0
  739. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/sentinel-run-failure.d.ts +0 -0
  740. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/sentinel-run-failure.d.ts.map +0 -0
  741. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/sentinel-run-failure.js +0 -0
  742. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/sentinel-run-failure.js.map +0 -0
  743. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/shield-config.d.ts +0 -0
  744. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/shield-config.d.ts.map +0 -0
  745. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/shield-config.js +0 -0
  746. /package/apps/{gateway → os-server}/dist/apps/gateway/src/security/shield-config.js.map +0 -0
  747. /package/apps/{gateway → os-server}/dist/apps/gateway/src/utils/circuit-breaker.d.ts +0 -0
  748. /package/apps/{gateway → os-server}/dist/apps/gateway/src/utils/circuit-breaker.d.ts.map +0 -0
  749. /package/apps/{gateway → os-server}/dist/apps/gateway/src/utils/circuit-breaker.js +0 -0
  750. /package/apps/{gateway → os-server}/dist/apps/gateway/src/utils/circuit-breaker.js.map +0 -0
  751. /package/apps/{gateway → os-server}/dist/apps/gateway/src/utils/log-encryption.d.ts +0 -0
  752. /package/apps/{gateway → os-server}/dist/apps/gateway/src/utils/log-encryption.d.ts.map +0 -0
  753. /package/apps/{gateway → os-server}/dist/apps/gateway/src/utils/log-encryption.js +0 -0
  754. /package/apps/{gateway → os-server}/dist/apps/gateway/src/utils/log-encryption.js.map +0 -0
  755. /package/apps/{gateway → os-server}/dist/index.js +0 -0
  756. /package/apps/{gateway → os-server}/dist/packages/adapters/redis/IdempotencyStore.d.ts +0 -0
  757. /package/apps/{gateway → os-server}/dist/packages/adapters/redis/IdempotencyStore.d.ts.map +0 -0
  758. /package/apps/{gateway → os-server}/dist/packages/adapters/redis/IdempotencyStore.js +0 -0
  759. /package/apps/{gateway → os-server}/dist/packages/adapters/redis/IdempotencyStore.js.map +0 -0
  760. /package/apps/{gateway → os-server}/dist/packages/middleware/idempotency/idempotency.d.ts +0 -0
  761. /package/apps/{gateway → os-server}/dist/packages/middleware/idempotency/idempotency.d.ts.map +0 -0
  762. /package/apps/{gateway → os-server}/dist/packages/middleware/idempotency/idempotency.js +0 -0
  763. /package/apps/{gateway → os-server}/dist/packages/middleware/idempotency/idempotency.js.map +0 -0
  764. /package/apps/{gateway → os-server}/load-tests/README.md +0 -0
  765. /package/apps/{gateway → os-server}/load-tests/k6-basic.js +0 -0
  766. /package/apps/{gateway → os-server}/load-tests/k6-rate-limit.js +0 -0
  767. /package/apps/{gateway → os-server}/minimal-test.sh +0 -0
  768. /package/apps/{gateway → os-server}/public/sentinel-dashboard.html +0 -0
  769. /package/apps/{gateway → os-server}/quick-debug.sh +0 -0
  770. /package/apps/{gateway → os-server}/scripts/seed-api-key.ts +0 -0
  771. /package/apps/{gateway → os-server}/scripts/verify-sentinel-persist.mjs +0 -0
  772. /package/apps/{gateway → os-server}/simple-test.sh +0 -0
  773. /package/apps/{gateway → os-server}/src/__tests__/agent-token.test.ts +0 -0
  774. /package/apps/{gateway → os-server}/src/__tests__/auth.test.ts +0 -0
  775. /package/apps/{gateway → os-server}/src/__tests__/correlationId.test.ts +0 -0
  776. /package/apps/{gateway → os-server}/src/__tests__/devkit-api.test.ts +0 -0
  777. /package/apps/{gateway → os-server}/src/__tests__/integration/authentication.test.ts +0 -0
  778. /package/apps/{gateway → os-server}/src/__tests__/integration/e2e-comprehensive-fixed.test.ts +0 -0
  779. /package/apps/{gateway → os-server}/src/__tests__/integration/e2e-workflow.test.ts +0 -0
  780. /package/apps/{gateway → os-server}/src/__tests__/integration/idempotency.test.ts +0 -0
  781. /package/apps/{gateway → os-server}/src/__tests__/integration/postgres-persistence.test.ts +0 -0
  782. /package/apps/{gateway → os-server}/src/__tests__/integration/rate-limiting.test.ts +0 -0
  783. /package/apps/{gateway → os-server}/src/__tests__/integration/shield.test.ts +0 -0
  784. /package/apps/{gateway → os-server}/src/__tests__/log-capture.test.ts +0 -0
  785. /package/apps/{gateway → os-server}/src/__tests__/no-persistence-mode.test.ts +0 -0
  786. /package/apps/{gateway → os-server}/src/__tests__/outbound-url.test.ts +0 -0
  787. /package/apps/{gateway → os-server}/src/__tests__/rateLimit.test.ts +0 -0
  788. /package/apps/{gateway → os-server}/src/__tests__/run-kill.test.ts +0 -0
  789. /package/apps/{gateway → os-server}/src/__tests__/run-retention.test.ts +0 -0
  790. /package/apps/{gateway → os-server}/src/__tests__/sentinel-config-persist-http.test.ts +0 -0
  791. /package/apps/{gateway → os-server}/src/__tests__/sentinel-config-store.test.ts +0 -0
  792. /package/apps/{gateway → os-server}/src/__tests__/sentinel-events.test.ts +0 -0
  793. /package/apps/{gateway → os-server}/src/__tests__/sentinel-execute-watch.test.ts +0 -0
  794. /package/apps/{gateway → os-server}/src/__tests__/sentinel-policies.test.ts +0 -0
  795. /package/apps/{gateway → os-server}/src/__tests__/sentinel-publish-kill.test.ts +0 -0
  796. /package/apps/{gateway → os-server}/src/__tests__/sentinel-run-failure.test.ts +0 -0
  797. /package/apps/{gateway → os-server}/src/__tests__/shield-config.test.ts +0 -0
  798. /package/apps/{gateway → os-server}/src/__tests__/shield-health.test.ts +0 -0
  799. /package/apps/{gateway → os-server}/src/__tests__/shield-metrics.test.ts +0 -0
  800. /package/apps/{gateway → os-server}/src/__tests__/tool-proxy.test.ts +0 -0
  801. /package/apps/{gateway → os-server}/src/__tests__/validateSecrets.test.ts +0 -0
  802. /package/apps/{gateway → os-server}/src/__tests__/validation.test.ts +0 -0
  803. /package/apps/{gateway → os-server}/src/adapters/gateway-cancel-adapter.ts +0 -0
  804. /package/apps/{gateway → os-server}/src/adapters/sentinel-event-stream.ts +0 -0
  805. /package/apps/{gateway → os-server}/src/agents/definitions.ts +0 -0
  806. /package/apps/{gateway → os-server}/src/agents/local-model-provider.ts +0 -0
  807. /package/apps/{gateway → os-server}/src/agents/tools.ts +0 -0
  808. /package/apps/{gateway → os-server}/src/ai-providers/index.ts +0 -0
  809. /package/apps/{gateway → os-server}/src/ai-providers/provider-manager.ts +0 -0
  810. /package/apps/{gateway → os-server}/src/ai-providers/types.ts +0 -0
  811. /package/apps/{gateway → os-server}/src/config/persistence.ts +0 -0
  812. /package/apps/{gateway → os-server}/src/crypto/envelope.ts +0 -0
  813. /package/apps/{gateway → os-server}/src/crypto/mfa-storage.ts +0 -0
  814. /package/apps/{gateway → os-server}/src/db/memory-db.ts +0 -0
  815. /package/apps/{gateway → os-server}/src/db/prisma.ts +0 -0
  816. /package/apps/{gateway → os-server}/src/db/redis.ts +0 -0
  817. /package/apps/{gateway → os-server}/src/devkit/agents-api.ts +0 -0
  818. /package/apps/{gateway → os-server}/src/devkit/metrics-parser.ts +0 -0
  819. /package/apps/{gateway → os-server}/src/devkit/middleware.ts +0 -0
  820. /package/apps/{gateway → os-server}/src/devkit/tools-api.ts +0 -0
  821. /package/apps/{gateway → os-server}/src/metrics/index.ts +0 -0
  822. /package/apps/{gateway → os-server}/src/metrics/monitoring-detail.ts +0 -0
  823. /package/apps/{gateway → os-server}/src/middleware/auth.ts +0 -0
  824. /package/apps/{gateway → os-server}/src/middleware/authApiKey.ts +0 -0
  825. /package/apps/{gateway → os-server}/src/middleware/authJwt.ts +0 -0
  826. /package/apps/{gateway → os-server}/src/middleware/errorHandler.ts +0 -0
  827. /package/apps/{gateway → os-server}/src/middleware/mfa.ts +0 -0
  828. /package/apps/{gateway → os-server}/src/middleware/rateLimit.ts +0 -0
  829. /package/apps/{gateway → os-server}/src/middleware/rbac.ts +0 -0
  830. /package/apps/{gateway → os-server}/src/middleware/validate.ts +0 -0
  831. /package/apps/{gateway → os-server}/src/middleware/validate.ts.backup +0 -0
  832. /package/apps/{gateway → os-server}/src/queue/config.ts +0 -0
  833. /package/apps/{gateway → os-server}/src/queue/index.ts +0 -0
  834. /package/apps/{gateway → os-server}/src/queue/processor.ts +0 -0
  835. /package/apps/{gateway → os-server}/src/queue/sentinel-execute-watch.ts +0 -0
  836. /package/apps/{gateway → os-server}/src/routes/chats.ts +0 -0
  837. /package/apps/{gateway → os-server}/src/routes/mfa.ts +0 -0
  838. /package/apps/{gateway → os-server}/src/routes/shield.ts +0 -0
  839. /package/apps/{gateway → os-server}/src/routes/tool-credentials.ts +0 -0
  840. /package/apps/{gateway → os-server}/src/runs/index.ts +0 -0
  841. /package/apps/{gateway → os-server}/src/runs/memoryRunStore.ts +0 -0
  842. /package/apps/{gateway → os-server}/src/runs/postgresRunStore.ts +0 -0
  843. /package/apps/{gateway → os-server}/src/runs/run-kill.ts +0 -0
  844. /package/apps/{gateway → os-server}/src/runs/run-retention.ts +0 -0
  845. /package/apps/{gateway → os-server}/src/runs/runStore.ts +0 -0
  846. /package/apps/{gateway → os-server}/src/runs/types.ts +0 -0
  847. /package/apps/{gateway → os-server}/src/schemas/runs.ts +0 -0
  848. /package/apps/{gateway → os-server}/src/security/agent-token.ts +0 -0
  849. /package/apps/{gateway → os-server}/src/security/sentinel-config-store.ts +0 -0
  850. /package/apps/{gateway → os-server}/src/security/sentinel-run-failure.ts +0 -0
  851. /package/apps/{gateway → os-server}/src/security/shield-config.ts +0 -0
  852. /package/apps/{gateway → os-server}/src/types/fastify-rate-limit.d.ts +0 -0
  853. /package/apps/{gateway → os-server}/src/utils/circuit-breaker.ts +0 -0
  854. /package/apps/{gateway → os-server}/src/utils/log-encryption.ts +0 -0
  855. /package/apps/{gateway → os-server}/test-all-individual.sh +0 -0
  856. /package/apps/{gateway → os-server}/test-all-phases-final.sh +0 -0
  857. /package/apps/{gateway → os-server}/test-all-phases-fixed-v2.sh +0 -0
  858. /package/apps/{gateway → os-server}/test-all-phases-fixed.sh +0 -0
  859. /package/apps/{gateway → os-server}/test-all-phases.sh +0 -0
  860. /package/apps/{gateway → os-server}/test-concurrency.sh +0 -0
  861. /package/apps/{gateway → os-server}/test-debug.sh +0 -0
  862. /package/apps/{gateway → os-server}/test-e2e.sh +0 -0
  863. /package/apps/{gateway → os-server}/test-extraction.sh +0 -0
  864. /package/apps/{gateway → os-server}/test-full-operations-final.sh +0 -0
  865. /package/apps/{gateway → os-server}/test-full-operations.sh +0 -0
  866. /package/apps/{gateway → os-server}/test-idempotency-only.sh +0 -0
  867. /package/apps/{gateway → os-server}/test-idempotency.sh +0 -0
  868. /package/apps/{gateway → os-server}/test-individual.sh +0 -0
  869. /package/apps/{gateway → os-server}/test-queue.sh +0 -0
  870. /package/apps/{gateway → os-server}/tsconfig.json +0 -0
  871. /package/{mk3-tui → os-tui}/src/ui/connection_portal.rs.backup +0 -0
@@ -1,3813 +0,0 @@
1
- use crate::io::IoHandler;
2
- use crate::monitoring::{MetricsDrillPanel, MonitoringSection, MonitoringState};
3
- use crate::screens::{NavigationState, Screen};
4
- use crate::storage::Cache;
5
- use crate::ui::agent_builder::AgentBuilderState;
6
- use crate::ui::run_manager::RunManagerState;
7
- use crate::ui::sentinel_config::SentinelConfigState;
8
- use crate::ui::shield_dashboard::ShieldDashboardState;
9
- use crate::ui::settings::SettingsState;
10
- use crate::websocket::WebSocketClient;
11
- use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
12
- use ratatui::prelude::*;
13
- use serde::{Deserialize, Serialize};
14
- use std::collections::{HashMap, VecDeque};
15
- use std::time::{Duration, Instant};
16
-
17
- /// `HH:MM:SS` for Connection Portal activity log (UTC clock; avoids pulling in `chrono`).
18
- fn wall_clock_hms() -> String {
19
- let secs = std::time::SystemTime::now()
20
- .duration_since(std::time::UNIX_EPOCH)
21
- .map(|d| d.as_secs())
22
- .unwrap_or(0);
23
- let h = (secs / 3600) % 24;
24
- let m = (secs / 60) % 60;
25
- let s = secs % 60;
26
- format!("{h:02}:{m:02}:{s:02}")
27
- }
28
-
29
- mod render_scheduler;
30
- pub use render_scheduler::{RenderScheduler, RunMode};
31
-
32
- /// Upper bound for the operations log (`VecDeque`). Keeps TUI RAM stable under heavy traffic.
33
- /// See `docs/REDUCING-MEMORY-AND-DISK-USAGE.md` §8.
34
- const MAX_OPERATION_LOG_LINES: usize = 250;
35
-
36
- #[derive(Debug, Clone, Serialize, Deserialize)]
37
- pub enum Message {
38
- Log { message: String, ts: String },
39
- Posture { status: String },
40
- Metrics { cpu: f64, mem: f64 },
41
- Network { status: String },
42
- Capabilities { items: Vec<String> },
43
- }
44
-
45
- #[derive(Debug, Clone, PartialEq)]
46
- pub enum AppMode {
47
- Boot,
48
- Main,
49
- }
50
-
51
- #[derive(Debug, Clone, PartialEq)]
52
- pub enum OperationMode {
53
- Local, // No Gateway connection
54
- Connected, // Connected to Gateway
55
- }
56
-
57
- impl OperationMode {
58
- pub fn as_str(&self) -> &str {
59
- match self {
60
- OperationMode::Local => "LOCAL",
61
- OperationMode::Connected => "CONNECTED",
62
- }
63
- }
64
- }
65
-
66
- #[cfg(test)]
67
- mod portal_monitoring_tests {
68
- use super::*;
69
-
70
- #[test]
71
- fn metric_history_totals_contract_keys() {
72
- let totals = serde_json::json!({
73
- "httpRequests": 10,
74
- "httpRequestErrors": 1,
75
- "queueJobsWaiting": 2,
76
- "queueJobsActive": 3,
77
- "queueJobsFailed": 4,
78
- "runsActive": 5,
79
- "sseActive": 6
80
- });
81
- let entry = metric_history_entry_from_totals(
82
- "2026-01-01T00:00:00.000Z".to_string(),
83
- totals.as_object().expect("totals object"),
84
- );
85
-
86
- assert_eq!(entry.http_requests, 10);
87
- assert_eq!(entry.http_errors, 1);
88
- assert_eq!(entry.queue_waiting, 2);
89
- assert_eq!(entry.queue_active, 3);
90
- assert_eq!(entry.queue_failed, 4);
91
- assert_eq!(entry.runs_active, 5);
92
- assert_eq!(entry.sse_active, 6);
93
- }
94
- }
95
-
96
- #[derive(Debug, Clone, PartialEq)]
97
- pub enum PortalField {
98
- GatewayUrl,
99
- Username,
100
- }
101
-
102
- #[derive(Debug, Clone, PartialEq)]
103
- pub enum GatewayType {
104
- FourRunrServer, // External 4Runr cloud server
105
- LocalBundle, // Local Gateway bundle (included with Full OS)
106
- CustomUrl, // User's own Gateway server
107
- }
108
-
109
- impl GatewayType {
110
- pub fn as_str(&self) -> &str {
111
- match self {
112
- GatewayType::FourRunrServer => "4Runr Server",
113
- GatewayType::LocalBundle => "Local Bundle",
114
- GatewayType::CustomUrl => "Custom URL",
115
- }
116
- }
117
-
118
- pub fn default_url(&self) -> &str {
119
- match self {
120
- GatewayType::FourRunrServer => "https://gateway.4runr.com",
121
- GatewayType::LocalBundle => "http://localhost:3001",
122
- GatewayType::CustomUrl => "",
123
- }
124
- }
125
- }
126
-
127
- // Activity Log for Connection Portal
128
- #[derive(Debug, Clone)]
129
- pub struct ActivityLogEntry {
130
- pub timestamp: String, // HH:MM:SS format
131
- pub level: LogLevel,
132
- pub message: String,
133
- }
134
-
135
- #[derive(Debug, Clone, PartialEq)]
136
- pub enum LogLevel {
137
- Info,
138
- Success,
139
- Warning,
140
- Error,
141
- }
142
-
143
- #[derive(Debug, Clone)]
144
- pub struct ConnectionPortalState {
145
- pub gateway_url: String,
146
- pub username: String,
147
- pub focused_field: PortalField,
148
- pub connecting: bool,
149
- /// When `connecting` became true (for elapsed timer in the portal UI).
150
- pub connecting_started: Option<Instant>,
151
- pub connection_success: bool,
152
- pub error: Option<String>,
153
- pub last_successful_url: Option<String>,
154
- pub activity_log: Vec<ActivityLogEntry>,
155
- }
156
-
157
- impl Default for ConnectionPortalState {
158
- fn default() -> Self {
159
- Self {
160
- gateway_url: "http://localhost:3001".to_string(),
161
- username: String::new(),
162
- focused_field: PortalField::GatewayUrl,
163
- connecting: false,
164
- connecting_started: None,
165
- connection_success: false,
166
- error: None,
167
- last_successful_url: None,
168
- activity_log: Vec::new(),
169
- }
170
- }
171
- }
172
-
173
- impl ConnectionPortalState {
174
- pub fn reset(&mut self) {
175
- self.gateway_url = self
176
- .last_successful_url
177
- .clone()
178
- .unwrap_or_else(|| "http://localhost:3001".to_string());
179
- self.username = String::new();
180
- self.focused_field = PortalField::GatewayUrl;
181
- self.connecting = false;
182
- self.connecting_started = None;
183
- self.connection_success = false;
184
- self.error = None;
185
- self.activity_log.clear();
186
- }
187
-
188
- pub fn begin_connecting(&mut self) {
189
- self.connecting = true;
190
- self.connecting_started = Some(Instant::now());
191
- }
192
-
193
- pub fn finish_connecting(&mut self) {
194
- self.connecting = false;
195
- self.connecting_started = None;
196
- }
197
-
198
- pub fn toggle_field(&mut self) {
199
- self.focused_field = match self.focused_field {
200
- PortalField::GatewayUrl => PortalField::Username,
201
- PortalField::Username => PortalField::GatewayUrl,
202
- };
203
- }
204
-
205
- pub fn add_log_entry(&mut self, timestamp: String, level: LogLevel, message: String) {
206
- self.activity_log.push(ActivityLogEntry {
207
- timestamp,
208
- level,
209
- message,
210
- });
211
-
212
- // Keep last N lines so the portal can show a full connect trace on small terminals
213
- const MAX_ACTIVITY: usize = 80;
214
- if self.activity_log.len() > MAX_ACTIVITY {
215
- self.activity_log.remove(0);
216
- }
217
- }
218
- }
219
-
220
- /// Gateway traffic snapshot (Prometheus `/metrics` via CLI WebSocket handler).
221
- #[derive(Debug, Clone, Serialize)]
222
- pub struct MetricHistoryEntry {
223
- pub timestamp: String,
224
- pub http_requests: u64,
225
- pub http_errors: u64,
226
- pub queue_waiting: u64,
227
- pub queue_active: u64,
228
- pub queue_failed: u64,
229
- pub runs_active: u64,
230
- pub sse_active: u64,
231
- }
232
-
233
- /// Phase 5 contract with `packages/os-cli/src/gateway-observability.ts` `totals`.
234
- /// If CLI keys change, update this mapping and `metric_history_totals_contract_keys`.
235
- pub fn metric_history_entry_from_totals(
236
- timestamp: String,
237
- totals: &serde_json::Map<String, serde_json::Value>,
238
- ) -> MetricHistoryEntry {
239
- fn read_u64(totals: &serde_json::Map<String, serde_json::Value>, key: &str) -> u64 {
240
- totals
241
- .get(key)
242
- .and_then(|v| v.as_u64().or_else(|| v.as_f64().map(|f| f.max(0.0) as u64)))
243
- .unwrap_or(0)
244
- }
245
-
246
- MetricHistoryEntry {
247
- timestamp,
248
- http_requests: read_u64(totals, "httpRequests"),
249
- http_errors: read_u64(totals, "httpRequestErrors"),
250
- queue_waiting: read_u64(totals, "queueJobsWaiting"),
251
- queue_active: read_u64(totals, "queueJobsActive"),
252
- queue_failed: read_u64(totals, "queueJobsFailed"),
253
- runs_active: read_u64(totals, "runsActive"),
254
- sse_active: read_u64(totals, "sseActive"),
255
- }
256
- }
257
-
258
- #[derive(Debug, Clone, Serialize)]
259
- pub struct DependencyAlertEntry {
260
- pub timestamp: String,
261
- pub previous: String,
262
- pub current: String,
263
- }
264
-
265
- #[derive(Debug, Clone)]
266
- pub struct PortalMonitoringState {
267
- pub loading: bool,
268
- pub error: Option<String>,
269
- pub last_updated: Option<String>,
270
- pub content_lines: Vec<String>,
271
- pub scroll_offset: usize,
272
- /// Width-based clip for section summaries; updated each render (Phase 1).
273
- pub summary_clip_width: usize,
274
- /// Body inner height from last render (for scroll bounds).
275
- pub viewport_lines: usize,
276
- /// Auto-refresh timer
277
- pub last_refresh: Option<Instant>,
278
- pub auto_refresh_interval: Duration,
279
- pub auto_refresh_enabled: bool,
280
- /// Previous HTTP total (from `totals.httpRequests`) for a per-refresh "live" delta line.
281
- pub last_http_for_delta: Option<u64>,
282
- /// Wall clock at last successful observability poll (for rate estimate).
283
- pub last_instant_for_delta: Option<Instant>,
284
- /// Phase 2: per-section lines from `monitoring.refresh` / `monitoring.logs` (merged over snapshot).
285
- pub section_overrides: HashMap<MonitoringSection, Vec<String>>,
286
- /// Which section is waiting on `monitoring.refresh`.
287
- pub section_refresh_loading: Option<MonitoringSection>,
288
- pub logs_fetch_loading: bool,
289
- /// Phase 3: drill-down inside Metrics (`→` / `←`); which prom-client slice is shown.
290
- pub metrics_drill: Option<MetricsDrillPanel>,
291
- pub metrics_drill_lines: Vec<String>,
292
- pub metrics_drill_loading: bool,
293
- /// Last N HTTP request totals from full snapshots (for ASCII sparkline).
294
- pub http_total_sparkline: VecDeque<u64>,
295
- /// Phase 5: bounded sample history (720 samples; wall-clock span depends on refresh cadence).
296
- pub metric_history: VecDeque<MetricHistoryEntry>,
297
- pub dependency_alerts: VecDeque<DependencyAlertEntry>,
298
- pub last_dependency_status: Option<String>,
299
- pub trend_view: bool,
300
- pub last_export_path: Option<String>,
301
- pub help_visible: bool,
302
- }
303
-
304
- impl Default for PortalMonitoringState {
305
- fn default() -> Self {
306
- Self {
307
- loading: false,
308
- error: None,
309
- last_updated: None,
310
- content_lines: Vec::new(),
311
- scroll_offset: 0,
312
- summary_clip_width: 80,
313
- viewport_lines: 18,
314
- last_refresh: None,
315
- auto_refresh_interval: Duration::from_secs(5),
316
- auto_refresh_enabled: false,
317
- last_http_for_delta: None,
318
- last_instant_for_delta: None,
319
- section_overrides: HashMap::new(),
320
- section_refresh_loading: None,
321
- logs_fetch_loading: false,
322
- metrics_drill: None,
323
- metrics_drill_lines: Vec::new(),
324
- metrics_drill_loading: false,
325
- http_total_sparkline: VecDeque::new(),
326
- metric_history: VecDeque::new(),
327
- dependency_alerts: VecDeque::new(),
328
- last_dependency_status: None,
329
- trend_view: false,
330
- last_export_path: None,
331
- help_visible: false,
332
- }
333
- }
334
- }
335
-
336
- /// Section-based monitoring UI state (Phase 1).
337
- #[derive(Debug, Clone)]
338
- pub struct AdvancedMonitoringState {
339
- pub monitoring_state: MonitoringState,
340
- /// Optional mirror of the connected Gateway URL at construction time. **Live URL for UI is always
341
- /// `AppState.gateway_url`** (and `MonitoringState::gateway_url` is synced from there in Portal Monitoring render).
342
- #[allow(dead_code)]
343
- pub gateway_url: Option<String>,
344
- }
345
-
346
- impl Default for AdvancedMonitoringState {
347
- fn default() -> Self {
348
- Self {
349
- monitoring_state: MonitoringState::default(),
350
- gateway_url: None,
351
- }
352
- }
353
- }
354
-
355
- impl AdvancedMonitoringState {
356
- pub fn new(gateway_url: Option<String>) -> Self {
357
- Self {
358
- monitoring_state: MonitoringState::new(gateway_url.clone()),
359
- gateway_url,
360
- }
361
- }
362
- }
363
-
364
- #[derive(Debug, Clone)]
365
- pub struct AppState {
366
- // Navigation state (NEW - replaces simple mode)
367
- pub navigation: NavigationState,
368
-
369
- // App mode (DEPRECATED - keeping for backward compatibility during transition)
370
- pub mode: AppMode,
371
-
372
- // Boot state
373
- pub boot_progress: u16, // 0-100
374
- pub boot_lines: VecDeque<String>,
375
- pub boot_done: bool,
376
- pub boot_started_at: Instant,
377
- // Connection state
378
- pub connected: bool,
379
- #[allow(dead_code)]
380
- pub gateway_url: Option<String>,
381
-
382
- // Real component status
383
- #[allow(dead_code)]
384
- pub gateway_healthy: bool,
385
- /// Last `gateway.health` hint for 4Runr DB (from `/health` → `fourRunrDb`).
386
- pub gateway_db_summary: Option<String>,
387
- pub posture_status: String,
388
- pub shield_mode: String, // "off", "monitor", "enforce"
389
- pub shield_detectors: Vec<String>, // ["pii", "injection", "hallucination"]
390
- pub shield_blocks_total: u64,
391
- pub shield_masks_total: u64,
392
- pub sentinel_state: String, // "idle", "watching", "triggered"
393
- pub sentinel_active_runs: usize,
394
-
395
- // Real metrics
396
- pub total_runs: u64,
397
- #[allow(dead_code)]
398
- pub active_sse_connections: u64,
399
- #[allow(dead_code)]
400
- pub idempotency_store_size: u64,
401
-
402
- // System resources
403
- pub cpu: f64,
404
- pub mem: f64,
405
- pub network_status: String,
406
-
407
- // Real logs
408
- pub logs: VecDeque<String>,
409
-
410
- // Capabilities
411
- pub capabilities: Vec<String>,
412
-
413
- // UI state
414
- pub command_input: String,
415
- pub command_focused: bool,
416
- pub log_scroll: usize,
417
-
418
- // Animation state
419
- pub tick: u64,
420
- pub spinner_frame: usize,
421
- pub uptime_secs: u64, // Updated from real clock in main loop, not from tick()
422
-
423
- // Perf overlay
424
- pub perf_overlay: bool,
425
- pub render_count: u64,
426
- pub last_render_time: std::time::Instant,
427
- pub render_durations: VecDeque<u64>, // ms
428
- pub render_scheduled_count: u64,
429
- pub log_write_count: u64,
430
-
431
- // Screen-specific state
432
- pub agent_builder: AgentBuilderState,
433
- pub run_manager: RunManagerState,
434
- pub sentinel_config: SentinelConfigState,
435
- pub shield_dashboard: ShieldDashboardState,
436
- pub settings: SettingsState,
437
- pub agent_list: AgentListState,
438
- pub connection_portal: ConnectionPortalState,
439
- pub setup_portal: SetupPortalState,
440
- pub portal_monitoring: PortalMonitoringState,
441
- /// Last successful observability pull (for auto-refresh interval).
442
- pub portal_observability_last_poll: Option<Instant>,
443
-
444
- /// Section selection / expand state for Portal Monitoring (Phase 1).
445
- pub advanced_monitoring: AdvancedMonitoringState,
446
-
447
- // Command tracking for response handling (Step 5.1)
448
- pub pending_agent_create_id: Option<String>,
449
- pub pending_agent_list_id: Option<String>,
450
- pub pending_agent_delete_id: Option<String>,
451
- pub pending_gateway_connect_id: Option<String>,
452
- pub pending_gateway_health_id: Option<String>, // Phase 2.1: Track health checks
453
- pub pending_gateway_observability_id: Option<String>,
454
- pub pending_monitoring_refresh_id: Option<String>,
455
- pub pending_monitoring_logs_id: Option<String>,
456
- pub pending_monitoring_drill_id: Option<String>,
457
- pub pending_setup_detect_id: Option<String>, // Track setup.detect command
458
-
459
- // Step 6: Gateway runs
460
- pub pending_run_list_id: Option<String>,
461
- pub pending_run_list_silent: bool,
462
- pub pending_run_get_id: Option<String>,
463
- pub pending_run_cancel_id: Option<String>,
464
- pub pending_run_quick_id: Option<String>,
465
-
466
- pub pending_sentinel_load_id: Option<String>,
467
- pub pending_sentinel_apply_id: Option<String>,
468
- pub pending_shield_load_id: Option<String>,
469
-
470
- // Deletion confirmation (Step 5.5)
471
- pub agent_delete_requested: bool,
472
-
473
- // Mode tracking (Step 7)
474
- pub operation_mode: OperationMode,
475
-
476
- // Local cache
477
- pub cache: Option<Cache>,
478
- pub cache_loaded: bool,
479
- }
480
-
481
- impl Default for AppState {
482
- fn default() -> Self {
483
- Self {
484
- navigation: NavigationState::new(),
485
- mode: AppMode::Boot,
486
- boot_progress: 0,
487
- boot_lines: VecDeque::new(),
488
- boot_done: false,
489
- boot_started_at: Instant::now(),
490
- connected: false,
491
- gateway_url: None,
492
- gateway_healthy: false,
493
- gateway_db_summary: None,
494
- posture_status: "Demo Mode".to_string(),
495
- shield_mode: "enforce".to_string(),
496
- shield_detectors: vec!["pii".into(), "injection".into(), "hallucination".into()],
497
- shield_blocks_total: 0,
498
- shield_masks_total: 0,
499
- sentinel_state: "idle".to_string(),
500
- sentinel_active_runs: 0,
501
- total_runs: 0,
502
- active_sse_connections: 0,
503
- idempotency_store_size: 0,
504
- cpu: 0.15,
505
- mem: 0.25,
506
- network_status: "Not Connected".to_string(),
507
- logs: VecDeque::from([
508
- "[SYSTEM] 4Runr AI Agent OS initialized".to_string(),
509
- "[MODE] Starting in LOCAL mode (offline)".to_string(),
510
- "[SHIELD] Safety layer active (enforce mode)".to_string(),
511
- "[SHIELD] Detectors: PII, Injection, Hallucination".to_string(),
512
- "[SENTINEL] Real-time monitoring ready".to_string(),
513
- "[HELP] Type 'connect portal' to connect to Gateway server".to_string(),
514
- "[HELP] Type 'help' for available commands".to_string(),
515
- ]),
516
- capabilities: vec![
517
- "text-analysis".to_string(),
518
- "code-review".to_string(),
519
- "data-extraction".to_string(),
520
- ],
521
- command_input: String::new(),
522
- command_focused: false,
523
- log_scroll: 0,
524
- tick: 0,
525
- spinner_frame: 0,
526
- uptime_secs: 0,
527
- perf_overlay: false,
528
- render_count: 0,
529
- last_render_time: std::time::Instant::now(),
530
- render_durations: VecDeque::with_capacity(60),
531
- render_scheduled_count: 0,
532
- log_write_count: 0,
533
- agent_builder: AgentBuilderState::default(),
534
- run_manager: RunManagerState::default(),
535
- sentinel_config: SentinelConfigState::default(),
536
- shield_dashboard: ShieldDashboardState::default(),
537
- settings: SettingsState::default(),
538
- agent_list: AgentListState::default(),
539
- connection_portal: ConnectionPortalState::default(),
540
- setup_portal: SetupPortalState::default(),
541
- portal_monitoring: PortalMonitoringState::default(),
542
- portal_observability_last_poll: None,
543
- advanced_monitoring: AdvancedMonitoringState::default(),
544
- pending_agent_create_id: None,
545
- pending_agent_list_id: None,
546
- pending_agent_delete_id: None,
547
- pending_gateway_connect_id: None,
548
- pending_gateway_health_id: None, // Phase 2.1: Initialize health check tracking
549
- pending_gateway_observability_id: None,
550
- pending_monitoring_refresh_id: None,
551
- pending_monitoring_logs_id: None,
552
- pending_monitoring_drill_id: None,
553
- pending_setup_detect_id: None,
554
- pending_run_list_id: None,
555
- pending_run_list_silent: false,
556
- pending_run_get_id: None,
557
- pending_run_cancel_id: None,
558
- pending_run_quick_id: None,
559
- pending_sentinel_load_id: None,
560
- pending_sentinel_apply_id: None,
561
- pending_shield_load_id: None,
562
- agent_delete_requested: false,
563
- operation_mode: OperationMode::Local,
564
- cache: Cache::new().ok(),
565
- cache_loaded: false,
566
- }
567
- }
568
- }
569
-
570
- #[derive(Debug, Clone, PartialEq)]
571
- pub enum GatewayOption {
572
- LocalBundle,
573
- CloudServer,
574
- CustomUrl,
575
- }
576
-
577
- impl GatewayOption {
578
- pub fn as_str(&self) -> &str {
579
- match self {
580
- GatewayOption::LocalBundle => "Local Bundle",
581
- GatewayOption::CloudServer => "4Runr Server",
582
- GatewayOption::CustomUrl => "Custom URL",
583
- }
584
- }
585
-
586
- pub fn default_url(&self) -> &str {
587
- match self {
588
- GatewayOption::LocalBundle => "http://localhost:3001",
589
- GatewayOption::CloudServer => "https://api.4runr.com",
590
- GatewayOption::CustomUrl => "",
591
- }
592
- }
593
- }
594
-
595
- #[derive(Debug, Clone)]
596
- pub struct SetupPortalState {
597
- pub selected_option: GatewayOption,
598
- pub detecting: bool,
599
- /// When detection started (for timeout)
600
- pub detecting_since: Option<std::time::Instant>,
601
- pub detection_result: Option<DetectionResult>,
602
- pub error: Option<String>,
603
- }
604
-
605
- #[derive(Debug, Clone)]
606
- pub struct DetectionResult {
607
- pub local_bundle: BundleStatus,
608
- pub cloud_server: CloudStatus,
609
- }
610
-
611
- #[derive(Debug, Clone)]
612
- pub struct BundleStatus {
613
- pub available: bool,
614
- pub running: bool,
615
- pub path: Option<String>,
616
- }
617
-
618
- #[derive(Debug, Clone)]
619
- pub struct CloudStatus {
620
- pub available: bool,
621
- pub status: String,
622
- }
623
-
624
- impl Default for SetupPortalState {
625
- fn default() -> Self {
626
- Self {
627
- selected_option: GatewayOption::LocalBundle,
628
- detecting: false,
629
- detecting_since: None,
630
- detection_result: None,
631
- error: None,
632
- }
633
- }
634
- }
635
-
636
- impl SetupPortalState {
637
- /// Re-open Setup without wiping last detection (so the screen still looks “warm” while a refresh runs).
638
- pub fn soft_open(&mut self) {
639
- self.error = None;
640
- }
641
-
642
- /// Cycle to next/previous option
643
- /// direction: 1 for down/next, -1 for up/previous
644
- pub fn cycle_option(&mut self, direction: i8) {
645
- use GatewayOption::*;
646
- self.selected_option = match (&self.selected_option, direction) {
647
- // Forward (Down key, Scroll Down)
648
- (LocalBundle, 1) => CloudServer,
649
- (CloudServer, 1) => CustomUrl,
650
- (CustomUrl, 1) => LocalBundle, // Wrap to top
651
- // Backward (Up key, Scroll Up)
652
- (LocalBundle, -1) => CustomUrl, // Wrap to bottom
653
- (CloudServer, -1) => LocalBundle,
654
- (CustomUrl, -1) => CloudServer,
655
- _ => return, // No change for invalid direction
656
- };
657
- }
658
-
659
- /// Clear detection state and errors (full reset; prefer [`soft_open`](Self::soft_open) when reopening Setup).
660
- #[allow(dead_code)]
661
- pub fn reset(&mut self) {
662
- self.detecting = false;
663
- self.detecting_since = None;
664
- self.detection_result = None;
665
- self.error = None;
666
- }
667
- }
668
-
669
- #[derive(Debug, Clone)]
670
- pub struct AgentListState {
671
- pub agents: Vec<AgentInfo>,
672
- pub selected_index: usize,
673
- pub detail_view: Option<usize>, // None = list view, Some(index) = detail popup
674
- }
675
-
676
- impl Default for AgentListState {
677
- fn default() -> Self {
678
- Self {
679
- agents: Vec::new(),
680
- selected_index: 0,
681
- detail_view: None,
682
- }
683
- }
684
- }
685
-
686
- #[derive(Debug, Clone)]
687
- pub struct AgentInfo {
688
- pub name: String,
689
- pub description: Option<String>,
690
- pub model: String,
691
- pub provider: String,
692
- pub system_prompt: Option<String>,
693
- pub temperature: Option<f32>,
694
- pub max_tokens: Option<u32>,
695
- pub tools: Vec<String>,
696
- }
697
-
698
- pub struct App {
699
- pub(crate) state: AppState,
700
- render_scheduler: RenderScheduler,
701
- pub(crate) input_debounce: Option<Instant>,
702
- pub(crate) input_debounce_duration: Duration,
703
- }
704
-
705
- impl App {
706
- pub fn new() -> Self {
707
- let render_scheduler = RenderScheduler::new();
708
- let run_mode = render_scheduler.run_mode();
709
-
710
- // Input debounce: browser 10ms, local 5ms (minimal delay for smooth typing)
711
- let input_debounce_duration = match run_mode {
712
- RunMode::Browser => Duration::from_millis(10),
713
- RunMode::Local => Duration::from_millis(5),
714
- };
715
-
716
- let mut state = AppState::default();
717
- Self::load_cached_data(&mut state);
718
-
719
- Self {
720
- state,
721
- render_scheduler,
722
- input_debounce: None,
723
- input_debounce_duration,
724
- }
725
- }
726
-
727
- /// True when the session still has a Gateway URL / connected mode (portal may show linked UI).
728
- pub fn gateway_session_linked(&self) -> bool {
729
- self.state.connection_portal.connection_success
730
- || (matches!(self.state.operation_mode, OperationMode::Connected)
731
- && self.state.gateway_url.is_some())
732
- }
733
-
734
- fn request_gateway_health_check(&mut self, ws_client: Option<&WebSocketClient>) {
735
- if let Some(ws) = ws_client {
736
- if let Ok(id) = ws.send_command("gateway.health", None) {
737
- self.state.pending_gateway_health_id = Some(id);
738
- self.add_log("[GATEWAY] Re-verifying link (/health + /ready)...".to_string());
739
- }
740
- }
741
- }
742
-
743
- fn request_gateway_disconnect(&mut self, ws_client: Option<&WebSocketClient>) {
744
- if let Some(ws) = ws_client {
745
- match ws.send_command("gateway.disconnect", None) {
746
- Ok(id) => {
747
- let short_id = &id[id.len().saturating_sub(8)..];
748
- self.state
749
- .logs
750
- .push_back(format!("[GATEWAY] Disconnecting (id: {})", short_id));
751
- }
752
- Err(e) => {
753
- self.state
754
- .logs
755
- .push_back(format!("[ERROR] Failed to disconnect: {}", e));
756
- }
757
- }
758
- } else {
759
- self.state
760
- .logs
761
- .push_back("[ERROR] WebSocket not connected".into());
762
- }
763
- }
764
-
765
- /// Ask CLI to pull Gateway `GET /metrics` and return a condensed snapshot for Portal Monitoring.
766
- ///
767
- /// **Intentional command split:** the TUI uses this `gateway.observability` path for the
768
- /// **Overview** row (full health + Prometheus snapshot in one response). Per-section refresh
769
- /// uses `begin_monitoring_refresh_request` → `monitoring.refresh` instead. The CLI also maps
770
- /// `monitoring.refresh` with `section: "overview"` to the same handler for non-TUI callers.
771
- pub fn begin_portal_observability_request(&mut self, ws: &WebSocketClient) {
772
- if self.state.pending_gateway_observability_id.is_some() {
773
- return;
774
- }
775
- if self.state.pending_monitoring_refresh_id.is_some()
776
- || self.state.pending_monitoring_logs_id.is_some()
777
- || self.state.pending_monitoring_drill_id.is_some()
778
- {
779
- return;
780
- }
781
- self.state.portal_monitoring.loading = true;
782
- self.state.portal_monitoring.error = None;
783
- let nonce = std::time::SystemTime::now()
784
- .duration_since(std::time::UNIX_EPOCH)
785
- .map(|d| d.as_millis())
786
- .unwrap_or(0);
787
- let data = serde_json::json!({ "nonce": nonce });
788
- match ws.send_command("gateway.observability", Some(data)) {
789
- Ok(id) => {
790
- self.state.pending_gateway_observability_id = Some(id);
791
- }
792
- Err(e) => {
793
- self.state.portal_monitoring.loading = false;
794
- self.state.portal_monitoring.error = Some(format!("{}", e));
795
- self.request_immediate_render("portal_obs_send_err");
796
- }
797
- }
798
- }
799
-
800
- /// Fetch run list from Gateway (`run.list`). `silent` = background poll (no loading spinner).
801
- pub fn begin_run_list_request(&mut self, ws: &WebSocketClient, silent: bool) {
802
- if self.state.operation_mode != OperationMode::Connected {
803
- return;
804
- }
805
- if self.state.pending_run_list_id.is_some() {
806
- return;
807
- }
808
- if !silent && self.state.run_manager.loading {
809
- return;
810
- }
811
- let data = serde_json::json!({ "limit": 50 });
812
- match ws.send_command("run.list", Some(data)) {
813
- Ok(id) => {
814
- self.state.pending_run_list_id = Some(id);
815
- self.state.pending_run_list_silent = silent;
816
- if !silent {
817
- self.state.run_manager.loading = true;
818
- }
819
- }
820
- Err(e) => {
821
- self.add_log(format!("[RUN] run.list failed: {}", e));
822
- }
823
- }
824
- }
825
-
826
- /// Refresh selected run detail (`run.get`) during live polling.
827
- pub fn begin_run_get_request(&mut self, ws: &WebSocketClient, run_id: &str) {
828
- if self.state.operation_mode != OperationMode::Connected {
829
- return;
830
- }
831
- if self.state.pending_run_get_id.is_some() {
832
- return;
833
- }
834
- let data = serde_json::json!({ "runId": run_id });
835
- if let Ok(id) = ws.send_command("run.get", Some(data)) {
836
- self.state.pending_run_get_id = Some(id);
837
- }
838
- }
839
-
840
- pub fn open_shield_dashboard(&mut self, ws: Option<&WebSocketClient>) {
841
- self.push_overlay(Screen::ShieldDashboard);
842
- self.state
843
- .logs
844
- .push_back("[NAV] Opening Shield (production safety layer)...".into());
845
- if self.state.operation_mode == OperationMode::Connected {
846
- if let Some(ws) = ws {
847
- self.begin_shield_load_request(ws, false);
848
- }
849
- } else {
850
- self.state.shield_dashboard.loading = false;
851
- self.state.shield_dashboard.error = Some(
852
- "Connect to Gateway first (connect portal).".to_string(),
853
- );
854
- }
855
- self.request_immediate_render("open_shield_dashboard");
856
- }
857
-
858
- pub fn begin_shield_load_request(&mut self, ws: &WebSocketClient, force: bool) {
859
- if self.state.operation_mode != OperationMode::Connected {
860
- return;
861
- }
862
- if !force && self.state.pending_shield_load_id.is_some() {
863
- return;
864
- }
865
- if force {
866
- self.state.pending_shield_load_id = None;
867
- }
868
- self.state.shield_dashboard.mark_loading();
869
- self.state.shield_dashboard.error = None;
870
- if let Ok(id) = ws.send_command("shield.load", None) {
871
- self.state.pending_shield_load_id = Some(id);
872
- } else {
873
- self.state.shield_dashboard.fail_loading(
874
- "Failed to send shield.load to CLI.".to_string(),
875
- );
876
- }
877
- }
878
-
879
- pub fn open_run_from_shield_block(&mut self, ws: Option<&WebSocketClient>, run_id: &str) {
880
- self.push_overlay(Screen::RunManager);
881
- self.state.run_manager.detail_run_id = Some(run_id.to_string());
882
- self.add_log(format!(
883
- "[SHIELD] Opening blocked run {} in Run Manager",
884
- &run_id[run_id.len().saturating_sub(8)..]
885
- ));
886
- if let Some(ws) = ws {
887
- self.begin_run_list_request(ws, false);
888
- self.begin_run_get_request(ws, run_id);
889
- }
890
- self.request_immediate_render("shield_open_run");
891
- }
892
-
893
- pub fn open_sentinel_config(&mut self, ws: Option<&WebSocketClient>) {
894
- self.state.pending_sentinel_load_id = None;
895
- self.state.pending_sentinel_apply_id = None;
896
- self.push_overlay(Screen::SentinelConfig);
897
- self.state
898
- .logs
899
- .push_back("[NAV] Opening Sentinel Configuration...".into());
900
- if self.state.operation_mode == OperationMode::Connected {
901
- if let Some(ws) = ws {
902
- self.begin_sentinel_load_request(ws);
903
- }
904
- } else {
905
- self.state.sentinel_config.error = Some(
906
- "Connect to Gateway first (connect portal).".to_string(),
907
- );
908
- }
909
- self.request_immediate_render("open_sentinel_config");
910
- }
911
-
912
- pub fn begin_sentinel_load_request(&mut self, ws: &WebSocketClient) {
913
- if self.state.operation_mode != OperationMode::Connected {
914
- return;
915
- }
916
- if self.state.pending_sentinel_load_id.is_some() {
917
- return;
918
- }
919
- self.state.sentinel_config.loading = true;
920
- self.state.sentinel_config.error = None;
921
- self.state.sentinel_config.status_message = None;
922
- if let Ok(id) = ws.send_command("sentinel.load", None) {
923
- self.state.pending_sentinel_load_id = Some(id);
924
- } else {
925
- self.state.sentinel_config.loading = false;
926
- }
927
- }
928
-
929
- pub fn begin_sentinel_apply_request(&mut self, ws: &WebSocketClient, template_key: &str) {
930
- if self.state.operation_mode != OperationMode::Connected {
931
- return;
932
- }
933
- if self.state.pending_sentinel_load_id.is_some()
934
- || self.state.pending_sentinel_apply_id.is_some()
935
- {
936
- return;
937
- }
938
- self.state.sentinel_config.applying = true;
939
- self.state.sentinel_config.error = None;
940
- self.state.sentinel_config.status_message = None;
941
- let data = serde_json::json!({ "template": template_key });
942
- if let Ok(id) = ws.send_command("sentinel.apply", Some(data)) {
943
- self.state.pending_sentinel_apply_id = Some(id);
944
- } else {
945
- self.state.sentinel_config.applying = false;
946
- }
947
- }
948
-
949
- pub fn begin_sentinel_apply_custom_request(&mut self, ws: &WebSocketClient, draft: &crate::ui::sentinel_config::SentinelCurrentConfig) {
950
- if self.state.operation_mode != OperationMode::Connected {
951
- return;
952
- }
953
- if self.state.pending_sentinel_load_id.is_some()
954
- || self.state.pending_sentinel_apply_id.is_some()
955
- {
956
- return;
957
- }
958
- self.state.sentinel_config.applying = true;
959
- self.state.sentinel_config.error = None;
960
- self.state.sentinel_config.status_message = None;
961
- let data = serde_json::json!({
962
- "enabled": draft.enabled,
963
- "runIdleMs": draft.idle_ms,
964
- "runMaxDurationMs": draft.duration_ms,
965
- "runMaxTokens": draft.max_tokens,
966
- "runMaxCost": draft.max_cost,
967
- "loopWindow": draft.loop_window,
968
- "loopMax": draft.loop_max,
969
- });
970
- if let Ok(id) = ws.send_command("sentinel.applyCustom", Some(data)) {
971
- self.state.pending_sentinel_apply_id = Some(id);
972
- } else {
973
- self.state.sentinel_config.applying = false;
974
- }
975
- }
976
-
977
- /// Phase 2: refresh one Portal Monitoring section via CLI (`monitoring.refresh` → Gateway APIs).
978
- pub fn begin_monitoring_refresh_request(
979
- &mut self,
980
- ws: &WebSocketClient,
981
- section: MonitoringSection,
982
- ) {
983
- if self.state.pending_gateway_observability_id.is_some()
984
- || self.state.pending_monitoring_refresh_id.is_some()
985
- || self.state.pending_monitoring_logs_id.is_some()
986
- || self.state.pending_monitoring_drill_id.is_some()
987
- {
988
- return;
989
- }
990
- let nonce = std::time::SystemTime::now()
991
- .duration_since(std::time::UNIX_EPOCH)
992
- .map(|d| d.as_millis())
993
- .unwrap_or(0);
994
- let slug = section.cli_slug();
995
- let data = serde_json::json!({ "section": slug, "nonce": nonce });
996
- match ws.send_command("monitoring.refresh", Some(data)) {
997
- Ok(id) => {
998
- self.state.pending_monitoring_refresh_id = Some(id);
999
- self.state.portal_monitoring.section_refresh_loading = Some(section);
1000
- self.state.portal_monitoring.error = None;
1001
- }
1002
- Err(e) => {
1003
- self.state.portal_monitoring.section_refresh_loading = None;
1004
- self.state.portal_monitoring.error = Some(format!("{}", e));
1005
- self.request_immediate_render("monitoring_refresh_send_err");
1006
- }
1007
- }
1008
- }
1009
-
1010
- /// Phase 2: fetch Gateway log buffer (`monitoring.logs`).
1011
- pub fn begin_monitoring_logs_request(&mut self, ws: &WebSocketClient) {
1012
- if self.state.pending_gateway_observability_id.is_some()
1013
- || self.state.pending_monitoring_refresh_id.is_some()
1014
- || self.state.pending_monitoring_logs_id.is_some()
1015
- || self.state.pending_monitoring_drill_id.is_some()
1016
- {
1017
- return;
1018
- }
1019
- let nonce = std::time::SystemTime::now()
1020
- .duration_since(std::time::UNIX_EPOCH)
1021
- .map(|d| d.as_millis())
1022
- .unwrap_or(0);
1023
- let data = serde_json::json!({ "limit": 100u32, "nonce": nonce });
1024
- match ws.send_command("monitoring.logs", Some(data)) {
1025
- Ok(id) => {
1026
- self.state.pending_monitoring_logs_id = Some(id);
1027
- self.state.portal_monitoring.logs_fetch_loading = true;
1028
- self.state.portal_monitoring.error = None;
1029
- }
1030
- Err(e) => {
1031
- self.state.portal_monitoring.logs_fetch_loading = false;
1032
- self.state.portal_monitoring.error = Some(format!("{}", e));
1033
- self.request_immediate_render("monitoring_logs_send_err");
1034
- }
1035
- }
1036
- }
1037
-
1038
- /// Phase 3: Gateway drill-down (`monitoring.drill`) for Metrics sub-panels (HTTP / Runs / Queue / SSE).
1039
- pub fn begin_monitoring_drill_request(
1040
- &mut self,
1041
- ws: &WebSocketClient,
1042
- panel: MetricsDrillPanel,
1043
- ) {
1044
- if self.state.pending_gateway_observability_id.is_some()
1045
- || self.state.pending_monitoring_refresh_id.is_some()
1046
- || self.state.pending_monitoring_logs_id.is_some()
1047
- || self.state.pending_monitoring_drill_id.is_some()
1048
- {
1049
- return;
1050
- }
1051
- let nonce = std::time::SystemTime::now()
1052
- .duration_since(std::time::UNIX_EPOCH)
1053
- .map(|d| d.as_millis())
1054
- .unwrap_or(0);
1055
- let target = panel.cli_target();
1056
- let data = serde_json::json!({ "target": target, "nonce": nonce });
1057
- match ws.send_command("monitoring.drill", Some(data)) {
1058
- Ok(id) => {
1059
- self.state.pending_monitoring_drill_id = Some(id);
1060
- self.state.portal_monitoring.metrics_drill = Some(panel);
1061
- self.state.portal_monitoring.metrics_drill_loading = true;
1062
- self.state.portal_monitoring.metrics_drill_lines.clear();
1063
- self.state.portal_monitoring.error = None;
1064
- }
1065
- Err(e) => {
1066
- self.state.portal_monitoring.metrics_drill_loading = false;
1067
- self.state.portal_monitoring.error = Some(format!("{}", e));
1068
- self.request_immediate_render("monitoring_drill_send_err");
1069
- }
1070
- }
1071
- }
1072
-
1073
- /// Phase 3: dependency pools + queue counts (`dependencies/detail`).
1074
- pub fn begin_dependencies_detail_drill(&mut self, ws: &WebSocketClient) {
1075
- if self.state.pending_gateway_observability_id.is_some()
1076
- || self.state.pending_monitoring_refresh_id.is_some()
1077
- || self.state.pending_monitoring_logs_id.is_some()
1078
- || self.state.pending_monitoring_drill_id.is_some()
1079
- {
1080
- return;
1081
- }
1082
- let nonce = std::time::SystemTime::now()
1083
- .duration_since(std::time::UNIX_EPOCH)
1084
- .map(|d| d.as_millis())
1085
- .unwrap_or(0);
1086
- let data = serde_json::json!({ "target": "dependencies_detail", "nonce": nonce });
1087
- match ws.send_command("monitoring.drill", Some(data)) {
1088
- Ok(id) => {
1089
- self.state.pending_monitoring_drill_id = Some(id);
1090
- self.state.portal_monitoring.section_refresh_loading =
1091
- Some(MonitoringSection::Dependencies);
1092
- self.state.portal_monitoring.error = None;
1093
- }
1094
- Err(e) => {
1095
- self.state.portal_monitoring.section_refresh_loading = None;
1096
- self.state.portal_monitoring.error = Some(format!("{}", e));
1097
- self.request_immediate_render("deps_drill_send_err");
1098
- }
1099
- }
1100
- }
1101
-
1102
- /// Phase 4: run local CLI/host diagnostics for the System section.
1103
- pub fn begin_system_diagnostics_request(&mut self, ws: &WebSocketClient) {
1104
- if self.state.pending_gateway_observability_id.is_some()
1105
- || self.state.pending_monitoring_refresh_id.is_some()
1106
- || self.state.pending_monitoring_logs_id.is_some()
1107
- || self.state.pending_monitoring_drill_id.is_some()
1108
- {
1109
- return;
1110
- }
1111
- let nonce = std::time::SystemTime::now()
1112
- .duration_since(std::time::UNIX_EPOCH)
1113
- .map(|d| d.as_millis())
1114
- .unwrap_or(0);
1115
- let data = serde_json::json!({ "target": "system_diagnostics", "nonce": nonce });
1116
- match ws.send_command("monitoring.drill", Some(data)) {
1117
- Ok(id) => {
1118
- self.state.pending_monitoring_drill_id = Some(id);
1119
- self.state.portal_monitoring.section_refresh_loading =
1120
- Some(MonitoringSection::System);
1121
- self.state.portal_monitoring.error = None;
1122
- }
1123
- Err(e) => {
1124
- self.state.portal_monitoring.section_refresh_loading = None;
1125
- self.state.portal_monitoring.error = Some(format!("{}", e));
1126
- self.request_immediate_render("system_diagnostics_send_err");
1127
- }
1128
- }
1129
- }
1130
-
1131
- pub fn export_portal_monitoring_snapshot(&mut self) -> anyhow::Result<String> {
1132
- let mut overrides = serde_json::Map::new();
1133
- for (section, lines) in &self.state.portal_monitoring.section_overrides {
1134
- overrides.insert(section.cli_slug().to_string(), serde_json::json!(lines));
1135
- }
1136
- let payload = serde_json::json!({
1137
- "exportedAt": wall_clock_hms(),
1138
- "gatewayUrl": self.state.gateway_url.clone(),
1139
- "lastUpdated": self.state.portal_monitoring.last_updated.clone(),
1140
- "contentLines": self.state.portal_monitoring.content_lines.clone(),
1141
- "sectionOverrides": overrides,
1142
- "metricHistory": self.state.portal_monitoring.metric_history.clone(),
1143
- "dependencyAlerts": self.state.portal_monitoring.dependency_alerts.clone(),
1144
- "trendView": self.state.portal_monitoring.trend_view,
1145
- });
1146
- let unix = std::time::SystemTime::now()
1147
- .duration_since(std::time::UNIX_EPOCH)
1148
- .map(|d| d.as_secs())
1149
- .unwrap_or(0);
1150
- let mut path = std::env::current_dir().unwrap_or_else(|_| std::env::temp_dir());
1151
- path.push(format!("4runr-monitoring-export-{unix}.json"));
1152
- let body = serde_json::to_string_pretty(&payload)?;
1153
- std::fs::write(&path, body)?;
1154
- let display = path.display().to_string();
1155
- self.state.portal_monitoring.last_export_path = Some(display.clone());
1156
- Ok(display)
1157
- }
1158
-
1159
- pub fn scroll_portal_monitoring_by(&mut self, delta: isize) {
1160
- use crate::ui::portal_monitoring::portal_monitoring_scroll_max;
1161
- let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
1162
- let sw = self.state.portal_monitoring.summary_clip_width.max(24);
1163
- let max_scroll = portal_monitoring_scroll_max(&self.state, sw, inner_h);
1164
- let current = self.state.portal_monitoring.scroll_offset;
1165
- self.state.portal_monitoring.scroll_offset = if delta < 0 {
1166
- current.saturating_sub(delta.unsigned_abs())
1167
- } else {
1168
- current.saturating_add(delta as usize).min(max_scroll)
1169
- };
1170
- self.request_immediate_render("portal_obs_scroll");
1171
- }
1172
-
1173
- fn cancel_portal_monitoring_requests(&mut self) {
1174
- self.state.pending_gateway_observability_id = None;
1175
- self.state.pending_monitoring_refresh_id = None;
1176
- self.state.pending_monitoring_logs_id = None;
1177
- self.state.pending_monitoring_drill_id = None;
1178
- self.state.portal_monitoring.loading = false;
1179
- self.state.portal_monitoring.section_refresh_loading = None;
1180
- self.state.portal_monitoring.logs_fetch_loading = false;
1181
- self.state.portal_monitoring.metrics_drill_loading = false;
1182
- }
1183
-
1184
- /// CLI ↔ TUI WebSocket lost: drop Gateway link UI and leave Portal Monitoring / Connection Portal so the session does not appear still "connected".
1185
- pub fn on_cli_backend_disconnect(&mut self) {
1186
- use crate::screens::Screen;
1187
- self.state.operation_mode = OperationMode::Local;
1188
- self.state.gateway_url = None;
1189
- self.state.connected = false;
1190
- self.state.gateway_healthy = false;
1191
- self.state.gateway_db_summary = None;
1192
- self.state.pending_gateway_connect_id = None;
1193
- self.state.pending_gateway_health_id = None;
1194
- self.state.pending_gateway_observability_id = None;
1195
- self.state.pending_monitoring_refresh_id = None;
1196
- self.state.pending_monitoring_logs_id = None;
1197
- self.state.pending_monitoring_drill_id = None;
1198
- self.state.portal_observability_last_poll = None;
1199
- self.state.portal_monitoring = PortalMonitoringState::default();
1200
- self.state.advanced_monitoring = AdvancedMonitoringState::default();
1201
- self.state.connection_portal.finish_connecting();
1202
- self.state.connection_portal.connection_success = false;
1203
- self.state.connection_portal.connecting = false;
1204
- self.state.run_manager.clear_runs();
1205
-
1206
- let screen = self.state.navigation.current_screen();
1207
- if matches!(
1208
- screen,
1209
- &Screen::PortalMonitoring | &Screen::ConnectionPortal
1210
- ) {
1211
- self.state.navigation.navigate_to_base(Screen::Main);
1212
- self.request_immediate_render("cli_backend_disconnect_nav_main");
1213
- } else {
1214
- self.request_immediate_render("cli_backend_disconnect");
1215
- }
1216
- }
1217
-
1218
- /// Load cached data on startup
1219
- fn load_cached_data(state: &mut AppState) {
1220
- if let Some(cache) = &state.cache {
1221
- // Load cached agents
1222
- let agents = cache.get_agents();
1223
- if !agents.is_empty() {
1224
- state.capabilities = agents.iter().map(|a| a.name.clone()).collect();
1225
- state.cache_loaded = true;
1226
- }
1227
-
1228
- // Load cached system status
1229
- if let Some(status) = cache.get_system_status() {
1230
- state.network_status = if status.connected {
1231
- "Connected (cached)".to_string()
1232
- } else {
1233
- "Disconnected (cached)".to_string()
1234
- };
1235
- state.posture_status = format!("{} (cached)", status.posture);
1236
- }
1237
- }
1238
- }
1239
-
1240
- pub fn request_render(&mut self, reason: &str) -> bool {
1241
- self.render_scheduler.request_render(reason)
1242
- }
1243
-
1244
- /// Request immediate render (bypass throttling) - for critical input events
1245
- pub fn request_immediate_render(&mut self, reason: &str) -> bool {
1246
- self.render_scheduler.request_immediate_render(reason)
1247
- }
1248
-
1249
- pub fn should_render(&mut self) -> bool {
1250
- self.render_scheduler.should_render()
1251
- }
1252
-
1253
- pub fn record_render(&mut self, duration_ms: u64) {
1254
- self.state.render_count += 1;
1255
- self.state.render_durations.push_back(duration_ms);
1256
- if self.state.render_durations.len() > 60 {
1257
- self.state.render_durations.pop_front();
1258
- }
1259
- self.state.last_render_time = Instant::now();
1260
- }
1261
-
1262
- pub fn run_mode(&self) -> RunMode {
1263
- self.render_scheduler.run_mode()
1264
- }
1265
-
1266
- pub fn render_scheduler_stats(&self) -> (u64, u64) {
1267
- (
1268
- self.render_scheduler.render_scheduled_count(),
1269
- self.render_scheduler.min_render_interval_ms(),
1270
- )
1271
- }
1272
-
1273
- /// IMPORTANT: Only call tick() when poll() times out (NO keyboard input)!
1274
- /// Otherwise animations will flash weirdly when typing.
1275
- ///
1276
- /// NOTE: Uptime is now tracked via Instant::now() in main loop,
1277
- /// so we don't increment it here anymore.
1278
- pub fn tick(&mut self) {
1279
- self.state.tick = self.state.tick.wrapping_add(1);
1280
-
1281
- // Update spinner frame (for "Processing..." indicator only)
1282
- // Cycle through 8 braille spinner frames
1283
- self.state.spinner_frame = (self.state.spinner_frame + 1) % 8;
1284
-
1285
- // Boot timeline: update progress every ~150-250ms
1286
- if self.state.mode == AppMode::Boot && !self.state.boot_done {
1287
- let elapsed = self.state.boot_started_at.elapsed();
1288
- let boot_steps = vec![
1289
- (Duration::from_millis(200), "Initializing gateway…"),
1290
- (Duration::from_millis(500), "Loading policies…"),
1291
- (Duration::from_millis(800), "Agent registry online…"),
1292
- (Duration::from_millis(1100), "Telemetry connected…"),
1293
- (Duration::from_millis(1400), "System ready."),
1294
- ];
1295
-
1296
- let mut current_progress = 0;
1297
- let mut step_idx = 0;
1298
-
1299
- for (i, (delay, msg)) in boot_steps.iter().enumerate() {
1300
- if elapsed >= *delay {
1301
- step_idx = i + 1;
1302
- current_progress = ((i + 1) * 100 / boot_steps.len()) as u16;
1303
-
1304
- // Add line if not already added
1305
- if self.state.boot_lines.len() <= i {
1306
- self.state.boot_lines.push_back(msg.to_string());
1307
- self.request_render("boot_line");
1308
- }
1309
- }
1310
- }
1311
-
1312
- self.state.boot_progress = current_progress.min(100);
1313
-
1314
- // Mark boot done after last step
1315
- if step_idx >= boot_steps.len() && self.state.boot_progress >= 100 {
1316
- self.state.boot_done = true;
1317
- self.request_render("boot_done");
1318
- }
1319
- }
1320
-
1321
- // NOTE: pulse_frame is NOT updated here - we use static dots for status indicators
1322
- // to prevent flashing when typing. Only use animated pulse for non-status elements.
1323
- }
1324
-
1325
- pub fn handle_input(
1326
- &mut self,
1327
- key: KeyEvent,
1328
- _io: &mut IoHandler,
1329
- ws_client: Option<&WebSocketClient>,
1330
- ) -> anyhow::Result<bool> {
1331
- use crossterm::event::KeyModifiers;
1332
-
1333
- // === EXIT SHORTCUTS (always checked first) ===
1334
- if key.modifiers.contains(KeyModifiers::CONTROL) {
1335
- match key.code {
1336
- KeyCode::Char('c') | KeyCode::Char('q') | KeyCode::Char('d') => {
1337
- return Ok(true); // Exit
1338
- }
1339
- _ => return Ok(false), // Ignore other Ctrl combos
1340
- }
1341
- }
1342
-
1343
- if key.code == KeyCode::F(10) {
1344
- return Ok(true); // Exit
1345
- }
1346
-
1347
- // === BOOT MODE: Any key to continue ===
1348
- if self.state.mode == AppMode::Boot && self.state.boot_done {
1349
- // Any key press switches to main dashboard
1350
- self.navigate_to(Screen::Main);
1351
- return Ok(false);
1352
- }
1353
-
1354
- // In boot mode, ignore other input until boot is done
1355
- if self.state.mode == AppMode::Boot {
1356
- return Ok(false);
1357
- }
1358
-
1359
- // F12 - Toggle perf overlay
1360
- if key.code == KeyCode::F(12) {
1361
- self.state.perf_overlay = !self.state.perf_overlay;
1362
- self.request_render("perf_toggle");
1363
- return Ok(false);
1364
- }
1365
-
1366
- // === AGENT BUILDER INPUT HANDLING ===
1367
- if self.state.navigation.current_screen() == &Screen::AgentBuilder {
1368
- return self.handle_agent_builder_input(key, ws_client);
1369
- }
1370
-
1371
- // === RUN MANAGER INPUT HANDLING ===
1372
- if self.state.navigation.current_screen() == &Screen::RunManager {
1373
- return self.handle_run_manager_input(key, ws_client);
1374
- }
1375
-
1376
- // === SENTINEL CONFIG INPUT HANDLING ===
1377
- if self.state.navigation.current_screen() == &Screen::SentinelConfig {
1378
- return self.handle_sentinel_config_input(key, ws_client);
1379
- }
1380
-
1381
- // === SHIELD DASHBOARD INPUT HANDLING ===
1382
- if self.state.navigation.current_screen() == &Screen::ShieldDashboard {
1383
- return self.handle_shield_dashboard_input(key, ws_client);
1384
- }
1385
-
1386
- // === SETTINGS INPUT HANDLING ===
1387
- if self.state.navigation.current_screen() == &Screen::Settings {
1388
- return self.handle_settings_input(key);
1389
- }
1390
-
1391
- // === CONNECTION PORTAL INPUT HANDLING ===
1392
- if self.state.navigation.current_screen() == &Screen::ConnectionPortal {
1393
- return self.handle_connection_portal_input(key, ws_client);
1394
- }
1395
-
1396
- // === PORTAL MONITORING (Gateway traffic view) ===
1397
- if self.state.navigation.current_screen() == &Screen::PortalMonitoring {
1398
- return self.handle_portal_monitoring_input(key, ws_client);
1399
- }
1400
-
1401
- // === SETUP PORTAL INPUT HANDLING ===
1402
- if self.state.navigation.current_screen() == &Screen::SetupPortal {
1403
- return self.handle_setup_portal_input(key, ws_client);
1404
- }
1405
-
1406
- // === AGENT LIST INPUT HANDLING ===
1407
- if self.state.navigation.current_screen() == &Screen::AgentList {
1408
- return self.handle_agent_list_input(key, ws_client);
1409
- }
1410
-
1411
- // === MAIN INPUT HANDLING ===
1412
- match key.code {
1413
- // F2 - Open Setup Portal (Gateway options) from main screen
1414
- KeyCode::F(2) => {
1415
- self.state.navigation.navigate_to_base(Screen::SetupPortal);
1416
- self.state.setup_portal.soft_open();
1417
- if let Some(ws) = ws_client {
1418
- self.state.setup_portal.detecting = true;
1419
- self.state.setup_portal.detecting_since = Some(std::time::Instant::now());
1420
- if let Ok(id) = ws.send_command("setup.detect", None) {
1421
- self.state.pending_setup_detect_id = Some(id);
1422
- self.add_log("[SETUP] Detecting Gateway options...".to_string());
1423
- } else {
1424
- self.state.setup_portal.detecting = false;
1425
- self.state.setup_portal.detecting_since = None;
1426
- self.state.setup_portal.error =
1427
- Some("Failed to start detection".to_string());
1428
- }
1429
- } else {
1430
- self.state.setup_portal.error = Some(
1431
- "CLI backend not connected — auto-detect skipped. Choose an option and press Enter; set GATEWAY_URL in the shell if needed."
1432
- .into(),
1433
- );
1434
- }
1435
- self.state
1436
- .logs
1437
- .push_back("[NAV] Opening Setup Portal (F2)...".into());
1438
- self.request_immediate_render("open_setup_portal");
1439
- }
1440
- // Typing - ALL characters go to command input (immediate render for responsiveness)
1441
- KeyCode::Char(c) => {
1442
- // CRITICAL: Always set focused when typing to show cursor
1443
- self.state.command_focused = true;
1444
- self.state.command_input.push(c);
1445
- // OPTIMIZATION: Immediate render for instant typing feedback
1446
- self.request_immediate_render("typing_input");
1447
- }
1448
-
1449
- // Submit command
1450
- KeyCode::Enter => {
1451
- if !self.state.command_input.is_empty() {
1452
- let cmd: String = self.state.command_input.drain(..).collect();
1453
- self.state.command_focused = false;
1454
- self.input_debounce = None;
1455
-
1456
- match cmd.to_lowercase().as_str() {
1457
- "quit" | "exit" => return Ok(true),
1458
- "clear" => self.state.logs.clear(),
1459
- "help" => {
1460
- // Display help in operations log - clean and organized
1461
- // NOTE: VecDeque displays newest items first, so we push in REVERSE order
1462
- // (Last item pushed appears at top of log)
1463
-
1464
- // Push Keyboard Shortcuts FIRST (will appear at BOTTOM)
1465
- self.state.logs.push_back(
1466
- " Settings: ↑/↓=Navigate, Space=Toggle, Enter=Save"
1467
- .to_string(),
1468
- );
1469
- self.state.logs.push_back(
1470
- " Run Manager: ↑/↓=Navigate, F=Filter, S=Sort, R=Refresh"
1471
- .to_string(),
1472
- );
1473
- self.state.logs.push_back(
1474
- " Agent Builder: Enter=Next, Backspace=Back, ESC=Cancel"
1475
- .to_string(),
1476
- );
1477
- self.state.logs.push_back(
1478
- "────────────────────────────────────────────────────────────────"
1479
- .to_string(),
1480
- );
1481
- self.state
1482
- .logs
1483
- .push_back("⌨️ KEYBOARD SHORTCUTS".to_string());
1484
- self.state.logs.push_back("".to_string());
1485
-
1486
- // WebSocket Commands
1487
- self.state.logs.push_back(
1488
- " tool.list - List available tools".to_string(),
1489
- );
1490
- self.state.logs.push_back(
1491
- " run.list - List runs (requires gateway)".to_string(),
1492
- );
1493
- self.state
1494
- .logs
1495
- .push_back(" system.status - Get system status".to_string());
1496
- self.state.logs.push_back(
1497
- " agent.delete - Delete agent (data: {name})".to_string(),
1498
- );
1499
- self.state.logs.push_back(
1500
- " agent.create - Create new agent (use Agent Builder)"
1501
- .to_string(),
1502
- );
1503
- self.state.logs.push_back(
1504
- " agent.get - Get agent details (data: {name})"
1505
- .to_string(),
1506
- );
1507
- self.state
1508
- .logs
1509
- .push_back(" agent.list - List all agents".to_string());
1510
- self.state.logs.push_back(
1511
- "────────────────────────────────────────────────────────────────"
1512
- .to_string(),
1513
- );
1514
- self.state.logs.push_back(
1515
- "🌐 WEBSOCKET COMMANDS (requires connection)".to_string(),
1516
- );
1517
- self.state.logs.push_back("".to_string());
1518
-
1519
- // Local Commands
1520
- self.state.logs.push_back(
1521
- " :perf - Show performance stats".to_string(),
1522
- );
1523
- self.state
1524
- .logs
1525
- .push_back(" help - Show this help".to_string());
1526
- self.state.logs.push_back(
1527
- " clear - Clear operations log".to_string(),
1528
- );
1529
- self.state
1530
- .logs
1531
- .push_back(" quit, exit - Exit application".to_string());
1532
- self.state.logs.push_back(
1533
- "────────────────────────────────────────────────────────────────"
1534
- .to_string(),
1535
- );
1536
- self.state.logs.push_back("💻 LOCAL COMMANDS".to_string());
1537
- self.state.logs.push_back("".to_string());
1538
-
1539
- // Navigation Commands
1540
- self.state.logs.push_back(" disconnect - Disconnect from Gateway (return to LOCAL mode)".to_string());
1541
- self.state.logs.push_back(" portal monitoring - Gateway /metrics traffic (R refresh, ↑↓ scroll)".to_string());
1542
- self.state.logs.push_back(" connect portal - Open Connection Portal (connect to Gateway)".to_string());
1543
- self.state.logs.push_back(
1544
- " a, agents - Open Agent List (view, select, delete)"
1545
- .to_string(),
1546
- );
1547
- self.state.logs.push_back(
1548
- " config, settings - Open Settings (mode, AI provider)"
1549
- .to_string(),
1550
- );
1551
- self.state.logs.push_back(
1552
- " runs - Open Run Manager (list, filter, sort)"
1553
- .to_string(),
1554
- );
1555
- self.state.logs.push_back(
1556
- " sentinel - Configure Sentinel policies (templates)"
1557
- .to_string(),
1558
- );
1559
- self.state.logs.push_back(
1560
- " shield - Shield safety dashboard (live metrics + enforcement)"
1561
- .to_string(),
1562
- );
1563
- self.state.logs.push_back(
1564
- " build - Open Agent Builder (6-step wizard)"
1565
- .to_string(),
1566
- );
1567
- self.state.logs.push_back(
1568
- "────────────────────────────────────────────────────────────────"
1569
- .to_string(),
1570
- );
1571
- self.state
1572
- .logs
1573
- .push_back("🧭 NAVIGATION COMMANDS".to_string());
1574
- self.state.logs.push_back("".to_string());
1575
-
1576
- // Push Header LAST (will appear at TOP)
1577
- self.state.logs.push_back(
1578
- "════════════════════════════════════════════════════════════════"
1579
- .to_string(),
1580
- );
1581
- self.state
1582
- .logs
1583
- .push_back("📖 4RUNR AI AGENT OS - COMMAND REFERENCE".to_string());
1584
- self.state.logs.push_back(
1585
- "════════════════════════════════════════════════════════════════"
1586
- .to_string(),
1587
- );
1588
-
1589
- self.request_render("help_command");
1590
- }
1591
- ":perf" => {
1592
- // Perf self-check command
1593
- let rps = if !self.state.render_durations.is_empty() {
1594
- let avg_ms: f64 = self.state.render_durations.iter().sum::<u64>()
1595
- as f64
1596
- / self.state.render_durations.len() as f64;
1597
- if avg_ms > 0.0 {
1598
- (1000.0 / avg_ms) as u64
1599
- } else {
1600
- 0
1601
- }
1602
- } else {
1603
- 0
1604
- };
1605
-
1606
- let mode_str = match self.run_mode() {
1607
- RunMode::Browser => "browser",
1608
- RunMode::Local => "local",
1609
- };
1610
-
1611
- let (scheduled_count, interval_ms) = self.render_scheduler_stats();
1612
-
1613
- self.state.logs.push_back(format!(
1614
- "[PERF] Mode: {} | RPS: {} | Render interval: {}ms | Scheduled: {}",
1615
- mode_str, rps, interval_ms, scheduled_count
1616
- ));
1617
- }
1618
- // Navigation commands
1619
- "build" | "build new" | "agent new" => {
1620
- self.push_overlay(Screen::AgentBuilder);
1621
- self.state
1622
- .logs
1623
- .push_back("[NAV] Opening Agent Builder...".into());
1624
- }
1625
- "runs" | "run list" | "run manager" => {
1626
- self.push_overlay(Screen::RunManager);
1627
- self.state
1628
- .logs
1629
- .push_back("[NAV] Opening Run Manager...".into());
1630
- if self.state.operation_mode == OperationMode::Connected {
1631
- if let Some(ws) = ws_client {
1632
- self.begin_run_list_request(ws, false);
1633
- }
1634
- } else {
1635
- self.add_log(
1636
- "[RUN] Connect to Gateway (connect portal) to load live runs."
1637
- .to_string(),
1638
- );
1639
- }
1640
- }
1641
- "sentinel" | "sentinel config" | "sentinel policies" => {
1642
- self.open_sentinel_config(ws_client);
1643
- }
1644
- "shield" | "shield status" | "shield dashboard" => {
1645
- self.open_shield_dashboard(ws_client);
1646
- }
1647
- "config" | "settings" => {
1648
- self.push_overlay(Screen::Settings);
1649
- self.state
1650
- .logs
1651
- .push_back("[NAV] Opening Settings...".into());
1652
- }
1653
- "a" | "agents" | "agent list" => {
1654
- self.push_overlay(Screen::AgentList);
1655
- self.state
1656
- .logs
1657
- .push_back("[NAV] Opening Agent List...".into());
1658
- // Request agent list from backend
1659
- if let Some(ws) = ws_client {
1660
- if let Ok(list_id) = ws.send_command("agent.list", None) {
1661
- self.state.pending_agent_list_id = Some(list_id);
1662
- }
1663
- }
1664
- }
1665
- "portal monitoring" | "portal monitor" => {
1666
- if self.state.operation_mode == OperationMode::Connected {
1667
- self.state
1668
- .navigation
1669
- .navigate_to_base(Screen::PortalMonitoring);
1670
- self.state.advanced_monitoring =
1671
- AdvancedMonitoringState::new(self.state.gateway_url.clone());
1672
- self.state.portal_monitoring.scroll_offset = 0;
1673
- self.state
1674
- .logs
1675
- .push_back("[NAV] Opening Portal Monitoring...".into());
1676
- if let Some(ws) = ws_client {
1677
- self.begin_portal_observability_request(ws);
1678
- }
1679
- self.request_immediate_render("open_portal_monitoring");
1680
- } else {
1681
- self.state.logs.push_back(
1682
- "[NAV] Connect to a Gateway first (connect portal), then open Portal Monitoring."
1683
- .into(),
1684
- );
1685
- self.request_render("portal_monitoring_need_gateway");
1686
- }
1687
- }
1688
- "connect portal" | "portal" => {
1689
- let relink = self.gateway_session_linked();
1690
- self.state.connection_portal.reset();
1691
- self.state
1692
- .navigation
1693
- .navigate_to_base(Screen::ConnectionPortal);
1694
- self.state
1695
- .logs
1696
- .push_back("[NAV] Opening Connection Portal...".into());
1697
- if relink {
1698
- self.request_gateway_health_check(ws_client);
1699
- }
1700
- self.request_immediate_render("open_connection_portal");
1701
- }
1702
- "setup" | "setup gateway" | "setup portal" => {
1703
- // Navigate to portal as base screen (standalone, not overlay)
1704
- self.state.navigation.navigate_to_base(Screen::SetupPortal);
1705
- self.state.setup_portal.soft_open();
1706
-
1707
- // Auto-detect Gateway options when portal opens
1708
- if let Some(ws) = ws_client {
1709
- self.state.setup_portal.detecting = true;
1710
- self.state.setup_portal.detecting_since =
1711
- Some(std::time::Instant::now());
1712
- if let Ok(id) = ws.send_command("setup.detect", None) {
1713
- self.state.pending_setup_detect_id = Some(id);
1714
- self.add_log(
1715
- "[SETUP] Detecting Gateway options...".to_string(),
1716
- );
1717
- } else {
1718
- self.state.setup_portal.detecting = false;
1719
- self.state.setup_portal.detecting_since = None;
1720
- self.state.setup_portal.error =
1721
- Some("Failed to start detection".to_string());
1722
- }
1723
- } else {
1724
- self.state.setup_portal.error = Some(
1725
- "CLI backend not connected — auto-detect skipped. Choose an option and press Enter."
1726
- .into(),
1727
- );
1728
- }
1729
-
1730
- self.state
1731
- .logs
1732
- .push_back("[NAV] Opening Setup Portal...".into());
1733
- self.request_immediate_render("open_setup_portal");
1734
- }
1735
- "disconnect" => {
1736
- // Disconnect from Gateway
1737
- if let Some(ws) = ws_client {
1738
- match ws.send_command("gateway.disconnect", None) {
1739
- Ok(id) => {
1740
- let short_id = &id[id.len().saturating_sub(8)..];
1741
- self.state.logs.push_back(format!(
1742
- "[GATEWAY] Disconnecting (id: {})",
1743
- short_id
1744
- ));
1745
- // Mode will be updated when response is received
1746
- }
1747
- Err(e) => {
1748
- self.state.logs.push_back(format!(
1749
- "[ERROR] Failed to disconnect: {}",
1750
- e
1751
- ));
1752
- }
1753
- }
1754
- } else {
1755
- self.state
1756
- .logs
1757
- .push_back("[ERROR] WebSocket not connected".into());
1758
- }
1759
- }
1760
- _ => {
1761
- // Try to parse as WebSocket command
1762
- if let Some(ws) = ws_client {
1763
- // Check if command looks like a WebSocket command (contains '.')
1764
- if cmd.contains('.') {
1765
- // Send as WebSocket command
1766
- match ws.send_command(&cmd, None) {
1767
- Ok(id) => {
1768
- let short_id = &id[id.len().saturating_sub(8)..];
1769
- self.state
1770
- .logs
1771
- .push_back(format!("> {} [{}]", cmd, short_id));
1772
- }
1773
- Err(e) => {
1774
- self.state.logs.push_back(format!(
1775
- "[ERROR] Failed to send '{}': {}",
1776
- cmd, e
1777
- ));
1778
- }
1779
- }
1780
- } else {
1781
- // Unknown local command - provide helpful guidance
1782
- self.state
1783
- .logs
1784
- .push_back(format!("> {} (unknown command)", cmd));
1785
- self.state.logs.push_back("[HELP] Try: help, build, runs, config, agent.list, system.status".into());
1786
- }
1787
- } else {
1788
- // No WebSocket connection - guide to local commands
1789
- self.state
1790
- .logs
1791
- .push_back(format!("> {} (WebSocket not connected)", cmd));
1792
- self.state.logs.push_back("[WARN] Backend not running. Start backend or use local commands.".into());
1793
- self.state.logs.push_back(
1794
- "[HELP] Local: help, build, runs, config, clear".into(),
1795
- );
1796
- }
1797
- }
1798
- }
1799
- self.request_render("command_submit");
1800
- }
1801
- }
1802
-
1803
- // Delete character
1804
- KeyCode::Backspace => {
1805
- self.state.command_input.pop();
1806
- if self.state.command_input.is_empty() {
1807
- self.state.command_focused = false;
1808
- }
1809
- // OPTIMIZATION: Immediate render for instant feedback
1810
- self.request_immediate_render("backspace_input");
1811
- }
1812
-
1813
- // Clear input or close overlay/popup
1814
- KeyCode::Esc => {
1815
- // Priority: close popup > close overlay > clear input
1816
- if self.state.navigation.has_popup() {
1817
- self.pop_popup();
1818
- } else if self.state.navigation.has_overlay() {
1819
- self.pop_overlay();
1820
- } else if !self.state.command_input.is_empty() {
1821
- self.state.command_input.clear();
1822
- self.state.command_focused = false;
1823
- self.input_debounce = None;
1824
- self.request_render("clear_input");
1825
- }
1826
- }
1827
-
1828
- // === SCROLL: Arrow keys (only when not typing) ===
1829
- // Calculate max valid scroll based on visible height
1830
- KeyCode::Up if self.state.command_input.is_empty() => {
1831
- let visible_height = 15; // Approximate visible log height (adjust based on actual panel size)
1832
- let total_logs = self.state.logs.len();
1833
- let max_scroll = total_logs.saturating_sub(visible_height.max(1));
1834
-
1835
- // Clamp scroll to valid range [0, max_scroll]
1836
- self.state.log_scroll = (self.state.log_scroll + 1).min(max_scroll);
1837
- self.request_render("scroll_up");
1838
- }
1839
-
1840
- KeyCode::Down if self.state.command_input.is_empty() => {
1841
- self.state.log_scroll = self.state.log_scroll.saturating_sub(1);
1842
- self.request_render("scroll_down");
1843
- }
1844
-
1845
- KeyCode::PageUp if self.state.command_input.is_empty() => {
1846
- let visible_height = 15;
1847
- let total_logs = self.state.logs.len();
1848
- let max_scroll = total_logs.saturating_sub(visible_height.max(1));
1849
-
1850
- self.state.log_scroll = (self.state.log_scroll + 10).min(max_scroll);
1851
- self.request_render("scroll_page_up");
1852
- }
1853
-
1854
- KeyCode::PageDown if self.state.command_input.is_empty() => {
1855
- self.state.log_scroll = self.state.log_scroll.saturating_sub(10);
1856
- self.request_render("scroll_page_down");
1857
- }
1858
-
1859
- _ => {}
1860
- }
1861
-
1862
- Ok(false)
1863
- }
1864
-
1865
- pub fn update(&mut self, message: Message) {
1866
- match message {
1867
- Message::Log { message, ts } => {
1868
- let log_line = format!("[{}] {}", ts, message);
1869
- self.state.logs.push_back(log_line);
1870
- while self.state.logs.len() > MAX_OPERATION_LOG_LINES {
1871
- self.state.logs.pop_front();
1872
- }
1873
- }
1874
- Message::Posture { status } => {
1875
- self.state.posture_status = status;
1876
- }
1877
- Message::Metrics { cpu, mem } => {
1878
- self.state.cpu = cpu;
1879
- self.state.mem = mem;
1880
- }
1881
- Message::Network { status } => {
1882
- self.state.network_status = status;
1883
- }
1884
- Message::Capabilities { items } => {
1885
- self.state.capabilities = items;
1886
- }
1887
- }
1888
- }
1889
-
1890
- pub fn render(&mut self, f: &mut Frame) {
1891
- // Use new navigation system - render based on current screen
1892
- let current = self.state.navigation.current_screen();
1893
-
1894
- // DEBUG: Log render decisions
1895
- #[cfg(debug_assertions)]
1896
- {
1897
- eprintln!("[RENDER] Current screen: {:?}", current);
1898
- eprintln!(
1899
- "[RENDER] Base screen: {:?}",
1900
- self.state.navigation.base_screen
1901
- );
1902
- eprintln!(
1903
- "[RENDER] Overlay stack: {:?}",
1904
- self.state.navigation.overlay_stack
1905
- );
1906
- }
1907
-
1908
- match current {
1909
- Screen::Boot => {
1910
- use crate::ui::boot;
1911
- boot::render_boot(f, f.size(), &self.state);
1912
- }
1913
- Screen::Main => {
1914
- #[cfg(debug_assertions)]
1915
- eprintln!("[RENDER] Rendering Main screen (layout)");
1916
- use crate::ui::layout;
1917
- layout::render(f, &self.state);
1918
- }
1919
- Screen::AgentBuilder => {
1920
- use crate::ui::agent_builder;
1921
- agent_builder::render(f, &self.state);
1922
- }
1923
- Screen::RunManager => {
1924
- use crate::ui::run_manager;
1925
- run_manager::render(f, &self.state);
1926
- }
1927
- Screen::SentinelConfig => {
1928
- use crate::ui::sentinel_config;
1929
- sentinel_config::render(f, &self.state);
1930
- }
1931
- Screen::ShieldDashboard => {
1932
- use crate::ui::shield_dashboard;
1933
- shield_dashboard::render(f, &self.state);
1934
- }
1935
- Screen::Settings => {
1936
- use crate::ui::settings;
1937
- settings::render(f, &self.state);
1938
- }
1939
- Screen::AgentList => {
1940
- use crate::ui::agent_list;
1941
- agent_list::render(f, &self.state);
1942
- }
1943
- Screen::ConnectionPortal => {
1944
- #[cfg(debug_assertions)]
1945
- eprintln!("[RENDER] Rendering Connection Portal (standalone)");
1946
- // Render Connection Portal as standalone screen (not overlay)
1947
- // This eliminates double-rendering and improves performance
1948
- use crate::ui::connection_portal;
1949
- connection_portal::render(f, &self.state);
1950
- }
1951
- Screen::SetupPortal => {
1952
- #[cfg(debug_assertions)]
1953
- eprintln!("[RENDER] Rendering Setup Portal (standalone)");
1954
- // Render Setup Portal as standalone screen (not overlay)
1955
- // This eliminates double-rendering and improves performance
1956
- use crate::ui::setup_portal;
1957
- setup_portal::render(f, &mut self.state);
1958
- }
1959
- Screen::PortalMonitoring => {
1960
- use crate::ui::portal_monitoring;
1961
- portal_monitoring::render(f, &mut self.state);
1962
- }
1963
- Screen::Confirmation { message, action } => {
1964
- // TODO: Implement confirmation popup
1965
- // For now, just render the base screen
1966
- use crate::ui::layout;
1967
- layout::render(f, &self.state);
1968
- let _ = (message, action); // Suppress warnings
1969
- }
1970
- Screen::Alert { message } => {
1971
- // TODO: Implement alert popup
1972
- // For now, just render the base screen
1973
- use crate::ui::layout;
1974
- layout::render(f, &self.state);
1975
- let _ = message; // Suppress warning
1976
- }
1977
- }
1978
- }
1979
-
1980
- /// Add a log message
1981
- pub fn add_log(&mut self, message: String) {
1982
- self.state.logs.push_back(message);
1983
- while self.state.logs.len() > MAX_OPERATION_LOG_LINES {
1984
- self.state.logs.pop_front();
1985
- }
1986
- self.state.log_write_count += 1;
1987
- }
1988
-
1989
- // ============================================================
1990
- // NAVIGATION METHODS (NEW - Step 4.1)
1991
- // ============================================================
1992
-
1993
- /// Navigate to a base screen (Boot or Main)
1994
- pub fn navigate_to(&mut self, screen: Screen) {
1995
- if screen.is_base() {
1996
- self.state.navigation.navigate_to_base(screen.clone());
1997
-
1998
- // Update legacy mode field for backward compatibility
1999
- self.state.mode = match screen {
2000
- Screen::Boot => AppMode::Boot,
2001
- Screen::Main => AppMode::Main,
2002
- _ => self.state.mode.clone(),
2003
- };
2004
-
2005
- self.request_render("navigate_to");
2006
- }
2007
- }
2008
-
2009
- /// Push an overlay screen (Agent Builder, Run Manager, Settings)
2010
- pub fn push_overlay(&mut self, screen: Screen) {
2011
- if screen.is_overlay() {
2012
- self.state.navigation.push_overlay(screen);
2013
- self.request_render("push_overlay");
2014
- }
2015
- }
2016
-
2017
- /// Pop the current overlay and return to previous screen
2018
- pub fn pop_overlay(&mut self) -> Option<Screen> {
2019
- let result = self.state.navigation.pop_overlay();
2020
- if result.is_some() {
2021
- self.request_render("pop_overlay");
2022
- }
2023
- result
2024
- }
2025
-
2026
- /// Push a popup screen (Confirmation, Alert, Help)
2027
- pub fn push_popup(&mut self, screen: Screen) {
2028
- if screen.is_popup() {
2029
- self.state.navigation.push_popup(screen);
2030
- self.request_render("push_popup");
2031
- }
2032
- }
2033
-
2034
- /// Pop the current popup
2035
- pub fn pop_popup(&mut self) -> Option<Screen> {
2036
- let result = self.state.navigation.pop_popup();
2037
- if result.is_some() {
2038
- self.request_render("pop_popup");
2039
- }
2040
- result
2041
- }
2042
-
2043
- /// Close all overlays and popups (return to base screen)
2044
- #[allow(dead_code)]
2045
- pub fn close_all_overlays(&mut self) {
2046
- self.state.navigation.close_all();
2047
- self.request_render("close_all");
2048
- }
2049
-
2050
- /// Get the currently visible screen
2051
- #[allow(dead_code)]
2052
- pub fn current_screen(&self) -> &Screen {
2053
- self.state.navigation.current_screen()
2054
- }
2055
-
2056
- /// Check if we're on the base screen (no overlays/popups)
2057
- #[allow(dead_code)]
2058
- pub fn is_on_base_screen(&self) -> bool {
2059
- self.state.navigation.is_on_base()
2060
- }
2061
-
2062
- // ============================================================
2063
- // AGENT BUILDER INPUT HANDLING (Step 4.5)
2064
- // ============================================================
2065
-
2066
- /// Handle input when Agent Builder screen is active
2067
- fn handle_agent_builder_input(
2068
- &mut self,
2069
- key: KeyEvent,
2070
- ws_client: Option<&WebSocketClient>,
2071
- ) -> anyhow::Result<bool> {
2072
- use crossterm::event::KeyModifiers;
2073
-
2074
- // Ctrl+C/Q to exit
2075
- if key.modifiers.contains(KeyModifiers::CONTROL) {
2076
- match key.code {
2077
- KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
2078
- _ => {}
2079
- }
2080
- }
2081
-
2082
- match key.code {
2083
- // Character input - route to appropriate field
2084
- KeyCode::Char(c) => {
2085
- self.handle_agent_builder_char_input(c);
2086
- self.request_render("agent_builder_input");
2087
- }
2088
- // Backspace - delete character from current field
2089
- KeyCode::Backspace => {
2090
- self.handle_agent_builder_backspace();
2091
- self.request_render("agent_builder_backspace");
2092
- }
2093
- // Left Arrow - go back to previous step
2094
- KeyCode::Left => {
2095
- if self.state.agent_builder.current_step > 1 {
2096
- self.state.agent_builder.prev_step();
2097
- self.add_log("Agent Builder: Moved to previous step".to_string());
2098
- self.request_render("agent_builder_prev_step");
2099
- }
2100
- }
2101
- // Right Arrow - move to next step (same as Enter)
2102
- KeyCode::Right => {
2103
- if self.state.agent_builder.current_step == 6 {
2104
- // Final step - create agent
2105
- self.create_agent_from_builder(ws_client);
2106
- } else {
2107
- // Move to next step
2108
- if self.state.agent_builder.next_step() {
2109
- self.request_render("agent_builder_next");
2110
- } else {
2111
- // Validation failed - errors are in state
2112
- self.request_render("agent_builder_validation");
2113
- }
2114
- }
2115
- }
2116
- // Tab - move to next field (within step)
2117
- KeyCode::Tab => {
2118
- let shift = key.modifiers.contains(KeyModifiers::SHIFT);
2119
- if shift {
2120
- self.state.agent_builder.prev_field();
2121
- } else {
2122
- self.state.agent_builder.next_field();
2123
- }
2124
- self.request_render("agent_builder_field_nav");
2125
- }
2126
- // Enter - move to next step
2127
- KeyCode::Enter => {
2128
- if self.state.agent_builder.current_step == 6 {
2129
- // Final step - create agent
2130
- self.create_agent_from_builder(ws_client);
2131
- } else {
2132
- // Move to next step
2133
- if self.state.agent_builder.next_step() {
2134
- self.request_render("agent_builder_next");
2135
- } else {
2136
- // Validation failed - errors are in state
2137
- self.request_render("agent_builder_validation");
2138
- }
2139
- }
2140
- }
2141
- // Esc - cancel and return to main
2142
- KeyCode::Esc => {
2143
- self.pop_overlay();
2144
- // Reset wizard state
2145
- self.state.agent_builder = AgentBuilderState::default();
2146
- }
2147
- // Arrow keys - navigate fields
2148
- KeyCode::Up => {
2149
- self.state.agent_builder.prev_field();
2150
- self.request_render("agent_builder_field_nav");
2151
- }
2152
- KeyCode::Down => {
2153
- self.state.agent_builder.next_field();
2154
- self.request_render("agent_builder_field_nav");
2155
- }
2156
- _ => {}
2157
- }
2158
-
2159
- Ok(false)
2160
- }
2161
-
2162
- /// Handle character input for Agent Builder fields
2163
- fn handle_agent_builder_char_input(&mut self, c: char) {
2164
- let step = self.state.agent_builder.current_step;
2165
- let field = self.state.agent_builder.focused_field;
2166
-
2167
- match step {
2168
- 1 => {
2169
- // Step 1: Basic Info
2170
- match field {
2171
- 0 => self.state.agent_builder.name.push(c),
2172
- 1 => self.state.agent_builder.description.push(c),
2173
- _ => {}
2174
- }
2175
- }
2176
- 2 => {
2177
- // Step 2: Model Selection
2178
- match field {
2179
- 0 => self.state.agent_builder.provider.push(c),
2180
- 1 => self.state.agent_builder.model.push(c),
2181
- 2 => self.state.agent_builder.local_provider.push(c),
2182
- 3 => self.state.agent_builder.local_model.push(c),
2183
- 4 => self.state.agent_builder.local_url.push(c),
2184
- _ => {}
2185
- }
2186
- }
2187
- 3 => {
2188
- // Step 3: System Prompt
2189
- self.state.agent_builder.system_prompt.push(c);
2190
- }
2191
- 4 => {
2192
- // Step 4: Parameters (numeric input)
2193
- match field {
2194
- 0 => {
2195
- // Temperature - allow digits and decimal point
2196
- if c.is_ascii_digit() || c == '.' {
2197
- let mut temp_str = format!("{}", self.state.agent_builder.temperature);
2198
- temp_str.push(c);
2199
- if let Ok(val) = temp_str.parse::<f32>() {
2200
- self.state.agent_builder.temperature = val;
2201
- }
2202
- }
2203
- }
2204
- 1 => {
2205
- // Max tokens - digits only
2206
- if c.is_ascii_digit() {
2207
- let mut tokens_str = format!("{}", self.state.agent_builder.max_tokens);
2208
- tokens_str.push(c);
2209
- if let Ok(val) = tokens_str.parse::<u32>() {
2210
- self.state.agent_builder.max_tokens = val;
2211
- }
2212
- }
2213
- }
2214
- _ => {}
2215
- }
2216
- }
2217
- 5 => {
2218
- // Step 5: Tools - toggle with space
2219
- if c == ' ' {
2220
- let tools = &self.state.agent_builder.available_tools;
2221
- if field < tools.len() {
2222
- let tool = tools[field].clone();
2223
- if self.state.agent_builder.selected_tools.contains(&tool) {
2224
- self.state
2225
- .agent_builder
2226
- .selected_tools
2227
- .retain(|t| t != &tool);
2228
- } else {
2229
- self.state.agent_builder.selected_tools.push(tool);
2230
- }
2231
- }
2232
- }
2233
- }
2234
- _ => {}
2235
- }
2236
- }
2237
-
2238
- /// Handle backspace for Agent Builder fields
2239
- fn handle_agent_builder_backspace(&mut self) {
2240
- let step = self.state.agent_builder.current_step;
2241
- let field = self.state.agent_builder.focused_field;
2242
-
2243
- match step {
2244
- 1 => match field {
2245
- 0 => {
2246
- self.state.agent_builder.name.pop();
2247
- }
2248
- 1 => {
2249
- self.state.agent_builder.description.pop();
2250
- }
2251
- _ => {}
2252
- },
2253
- 2 => match field {
2254
- 0 => {
2255
- self.state.agent_builder.provider.pop();
2256
- }
2257
- 1 => {
2258
- self.state.agent_builder.model.pop();
2259
- }
2260
- 2 => {
2261
- self.state.agent_builder.local_provider.pop();
2262
- }
2263
- 3 => {
2264
- self.state.agent_builder.local_model.pop();
2265
- }
2266
- 4 => {
2267
- self.state.agent_builder.local_url.pop();
2268
- }
2269
- _ => {}
2270
- },
2271
- 3 => {
2272
- self.state.agent_builder.system_prompt.pop();
2273
- }
2274
- 4 => {
2275
- // Parameters - backspace on numeric fields
2276
- match field {
2277
- 0 => {
2278
- let mut temp_str = format!("{}", self.state.agent_builder.temperature);
2279
- temp_str.pop();
2280
- if let Ok(val) = temp_str.parse::<f32>() {
2281
- self.state.agent_builder.temperature = val;
2282
- } else if temp_str.is_empty() {
2283
- self.state.agent_builder.temperature = 0.0;
2284
- }
2285
- }
2286
- 1 => {
2287
- let mut tokens_str = format!("{}", self.state.agent_builder.max_tokens);
2288
- tokens_str.pop();
2289
- if let Ok(val) = tokens_str.parse::<u32>() {
2290
- self.state.agent_builder.max_tokens = val;
2291
- } else if tokens_str.is_empty() {
2292
- self.state.agent_builder.max_tokens = 0;
2293
- }
2294
- }
2295
- _ => {}
2296
- }
2297
- }
2298
- _ => {}
2299
- }
2300
- }
2301
-
2302
- /// Create agent from builder state and send to backend
2303
- fn create_agent_from_builder(&mut self, ws_client: Option<&WebSocketClient>) {
2304
- // Validate one final time
2305
- if !self.state.agent_builder.validate_step() {
2306
- self.add_log(
2307
- "[ERROR] Agent validation failed. Please fix errors and try again.".to_string(),
2308
- );
2309
- // Clone errors to avoid borrow checker issues
2310
- let errors = self.state.agent_builder.validation_errors.clone();
2311
- for error in &errors {
2312
- self.add_log(format!(" - {}", error));
2313
- }
2314
- return;
2315
- }
2316
-
2317
- // Clone name before borrowing mutably
2318
- let agent_name = self.state.agent_builder.name.clone();
2319
-
2320
- // Validate name is not empty
2321
- if agent_name.trim().is_empty() {
2322
- self.add_log("[ERROR] Agent name cannot be empty".to_string());
2323
- return;
2324
- }
2325
-
2326
- // Build agent data
2327
- let builder = &self.state.agent_builder;
2328
- let agent_data = serde_json::json!({
2329
- "name": builder.name,
2330
- "description": builder.description,
2331
- "baseAgent": if builder.use_local_model {
2332
- builder.local_model.clone()
2333
- } else {
2334
- builder.model.clone()
2335
- },
2336
- "systemPrompt": builder.system_prompt,
2337
- "temperature": builder.temperature,
2338
- "maxTokens": builder.max_tokens,
2339
- "useLocalModel": builder.use_local_model,
2340
- "localModelProvider": if builder.use_local_model {
2341
- Some(builder.local_provider.clone())
2342
- } else {
2343
- None
2344
- },
2345
- "localModelName": if builder.use_local_model {
2346
- Some(builder.local_model.clone())
2347
- } else {
2348
- None
2349
- },
2350
- "localModelUrl": if builder.use_local_model {
2351
- Some(builder.local_url.clone())
2352
- } else {
2353
- None
2354
- },
2355
- "topP": builder.top_p,
2356
- "frequencyPenalty": builder.frequency_penalty,
2357
- "presencePenalty": builder.presence_penalty,
2358
- "tools": builder.selected_tools,
2359
- });
2360
-
2361
- // Log the creation attempt
2362
- self.add_log(format!("[AGENT] Creating agent '{}'...", agent_name));
2363
-
2364
- // Send agent.create command via WebSocket
2365
- if let Some(ws) = ws_client {
2366
- match ws.send_command("agent.create", Some(agent_data)) {
2367
- Ok(id) => {
2368
- let short_id = &id[id.len().saturating_sub(8)..];
2369
- self.add_log(format!("[AGENT] Sent agent.create (id: {})", short_id));
2370
- // Store command ID for response tracking (Step 5.1)
2371
- self.state.pending_agent_create_id = Some(id);
2372
- }
2373
- Err(e) => {
2374
- self.add_log(format!("[ERROR] Failed to create agent: {}", e));
2375
- return;
2376
- }
2377
- }
2378
- } else {
2379
- self.add_log("[ERROR] WebSocket not connected. Cannot create agent.".to_string());
2380
- self.add_log("[HELP] Start the backend server and reconnect.".to_string());
2381
- return;
2382
- }
2383
-
2384
- // Close the builder
2385
- self.pop_overlay();
2386
-
2387
- // Reset wizard state
2388
- self.state.agent_builder = AgentBuilderState::default();
2389
-
2390
- // Suppress unused variable warning
2391
- let _ = agent_data;
2392
- }
2393
-
2394
- // ============================================================
2395
- // RUN MANAGER INPUT HANDLING (Step 4.7)
2396
- // ============================================================
2397
-
2398
- /// Handle input when Run Manager screen is active
2399
- fn handle_run_manager_input(
2400
- &mut self,
2401
- key: KeyEvent,
2402
- ws_client: Option<&WebSocketClient>,
2403
- ) -> anyhow::Result<bool> {
2404
- use crossterm::event::KeyModifiers;
2405
-
2406
- // Ctrl+C/Q to exit
2407
- if key.modifiers.contains(KeyModifiers::CONTROL) {
2408
- match key.code {
2409
- KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
2410
- _ => return Ok(false),
2411
- }
2412
- }
2413
-
2414
- match key.code {
2415
- // Navigation — list only; detail view locks to pinned run
2416
- KeyCode::Up => {
2417
- if self.state.run_manager.is_detail_view() {
2418
- self.state.run_manager.detail_log_scroll_up();
2419
- } else {
2420
- self.state.run_manager.select_previous();
2421
- }
2422
- self.request_render("run_manager_up");
2423
- }
2424
- KeyCode::Down => {
2425
- if self.state.run_manager.is_detail_view() {
2426
- self.state.run_manager.detail_log_scroll_down();
2427
- } else {
2428
- self.state.run_manager.select_next();
2429
- }
2430
- self.request_render("run_manager_down");
2431
- }
2432
-
2433
- // Filter (list view only)
2434
- KeyCode::Char('f') | KeyCode::Char('F') => {
2435
- if !self.state.run_manager.is_detail_view() {
2436
- self.state.run_manager.next_filter();
2437
- self.request_render("run_manager_filter");
2438
- }
2439
- }
2440
-
2441
- // Sort (list view only)
2442
- KeyCode::Char('s') | KeyCode::Char('S') => {
2443
- if !self.state.run_manager.is_detail_view() {
2444
- self.state.run_manager.next_sort();
2445
- self.request_render("run_manager_sort");
2446
- }
2447
- }
2448
-
2449
- // Refresh (list or pinned detail)
2450
- KeyCode::Char('r') | KeyCode::Char('R') => {
2451
- if self.state.operation_mode == OperationMode::Connected {
2452
- if let Some(ws) = ws_client {
2453
- if self.state.run_manager.is_detail_view() {
2454
- if let Some(run) = self.state.run_manager.detail_run() {
2455
- let run_id = run.id.clone();
2456
- self.begin_run_get_request(ws, &run_id);
2457
- self.add_log("[RUN] Refreshing run detail...".to_string());
2458
- }
2459
- } else {
2460
- self.begin_run_list_request(ws, false);
2461
- self.add_log("[RUN] Refreshing run list from Gateway...".to_string());
2462
- }
2463
- } else {
2464
- self.add_log("[RUN] WebSocket not connected".to_string());
2465
- }
2466
- } else {
2467
- self.add_log("[RUN] Connect to Gateway to refresh runs".to_string());
2468
- }
2469
- self.request_render("run_manager_refresh");
2470
- }
2471
-
2472
- // Toggle live auto-refresh (poll every ~3s)
2473
- KeyCode::Char('a') | KeyCode::Char('A') => {
2474
- self.state.run_manager.auto_refresh_enabled =
2475
- !self.state.run_manager.auto_refresh_enabled;
2476
- let status = if self.state.run_manager.auto_refresh_enabled {
2477
- "on"
2478
- } else {
2479
- "off"
2480
- };
2481
- self.add_log(format!("[RUN] Live auto-refresh {}", status));
2482
- self.request_render("run_manager_live_toggle");
2483
- }
2484
-
2485
- // View details / Close detail view
2486
- KeyCode::Enter => {
2487
- if self.state.run_manager.is_detail_view() {
2488
- self.state.run_manager.close_detail_view();
2489
- self.add_log("[RUN] Closed detail view".to_string());
2490
- } else if let Some(run_id) = self.state.run_manager.open_detail_view() {
2491
- if let Some(run) = self.state.run_manager.detail_run() {
2492
- self.add_log(format!("[RUN] Viewing run: {}", run.name));
2493
- }
2494
- if self.state.operation_mode == OperationMode::Connected {
2495
- if let Some(ws) = ws_client {
2496
- self.begin_run_get_request(ws, &run_id);
2497
- }
2498
- }
2499
- } else {
2500
- self.add_log("[RUN] No run selected".to_string());
2501
- }
2502
- self.request_render("run_manager_view");
2503
- }
2504
-
2505
- // Cancel run (list selection or pinned detail run)
2506
- KeyCode::Char('c') | KeyCode::Char('C') => {
2507
- let run = if self.state.run_manager.is_detail_view() {
2508
- self.state.run_manager.detail_run()
2509
- } else {
2510
- self.state.run_manager.selected_run()
2511
- };
2512
- if let Some(run) = run {
2513
- if matches!(
2514
- run.status,
2515
- crate::ui::run_manager::RunStatus::Running
2516
- | crate::ui::run_manager::RunStatus::Pending
2517
- ) {
2518
- if self.state.operation_mode == OperationMode::Connected {
2519
- if let Some(ws) = ws_client {
2520
- let data = serde_json::json!({ "runId": run.id.clone() });
2521
- if let Ok(id) = ws.send_command("run.cancel", Some(data)) {
2522
- self.state.pending_run_cancel_id = Some(id);
2523
- self.add_log(format!("[RUN] Cancelling run: {}", run.name));
2524
- }
2525
- } else {
2526
- self.add_log("[RUN] WebSocket not connected".to_string());
2527
- }
2528
- } else {
2529
- self.add_log("[RUN] Connect to Gateway to cancel runs".to_string());
2530
- }
2531
- } else {
2532
- self.add_log("[RUN] Cannot cancel completed/failed run".to_string());
2533
- }
2534
- }
2535
- self.request_render("run_manager_cancel");
2536
- }
2537
-
2538
- // Delete run
2539
- KeyCode::Char('d') | KeyCode::Char('D') => {
2540
- self.add_log("[RUN] Delete run is not supported by the Gateway API (use cancel for active runs).".to_string());
2541
- self.request_render("run_manager_delete");
2542
- }
2543
-
2544
- // Close detail view or Run Manager
2545
- KeyCode::Esc => {
2546
- if self.state.run_manager.is_detail_view() {
2547
- // If we have other overlays beneath RunManager (e.g. Shield),
2548
- // close RunManager entirely instead of just closing detail view
2549
- if self.state.navigation.has_overlay() {
2550
- let current_count = self.state.navigation.overlay_stack.len();
2551
- if current_count > 1 {
2552
- // Multiple overlays - pop RunManager entirely
2553
- self.pop_overlay();
2554
- self.add_log("[RUN] Returned to previous screen".to_string());
2555
- self.request_render("run_manager_close");
2556
- } else {
2557
- // Just RunManager open - close detail view first
2558
- self.state.run_manager.close_detail_view();
2559
- self.add_log("[RUN] Closed detail view".to_string());
2560
- self.request_render("run_manager_close_detail");
2561
- }
2562
- } else {
2563
- // No other overlays - close detail view first
2564
- self.state.run_manager.close_detail_view();
2565
- self.add_log("[RUN] Closed detail view".to_string());
2566
- self.request_render("run_manager_close_detail");
2567
- }
2568
- } else {
2569
- // Close Run Manager (list view)
2570
- self.pop_overlay();
2571
- }
2572
- }
2573
-
2574
- _ => {}
2575
- }
2576
-
2577
- Ok(false)
2578
- }
2579
-
2580
- pub fn sentinel_scroll_custom_value(&mut self, increase: bool) {
2581
- self.sentinel_adjust_custom_field(increase);
2582
- }
2583
-
2584
- fn sentinel_adjust_custom_field(&mut self, increase: bool) {
2585
- use crate::ui::sentinel_config::SentinelViewMode;
2586
- if self.state.sentinel_config.view_mode != SentinelViewMode::Custom {
2587
- return;
2588
- }
2589
- self.state.sentinel_config.adjust_custom_field(increase);
2590
- // Clear stale apply banner so edits are obvious; shortcuts return in footer
2591
- self.state.sentinel_config.status_message = None;
2592
- self.request_immediate_render(if increase {
2593
- "sentinel_inc"
2594
- } else {
2595
- "sentinel_dec"
2596
- });
2597
- }
2598
-
2599
- /// Handle input when Sentinel Config screen is active
2600
- fn handle_sentinel_config_input(
2601
- &mut self,
2602
- key: KeyEvent,
2603
- ws_client: Option<&WebSocketClient>,
2604
- ) -> anyhow::Result<bool> {
2605
- use crate::ui::sentinel_config::SentinelViewMode;
2606
- use crossterm::event::KeyModifiers;
2607
-
2608
- if key.modifiers.contains(KeyModifiers::CONTROL) {
2609
- match key.code {
2610
- KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
2611
- _ => return Ok(false),
2612
- }
2613
- }
2614
-
2615
- match key.code {
2616
- KeyCode::Tab => {
2617
- self.state.sentinel_config.cycle_view_mode();
2618
- self.request_immediate_render("sentinel_tab");
2619
- }
2620
- KeyCode::Up => {
2621
- self.state.sentinel_config.select_previous();
2622
- self.request_immediate_render("sentinel_up");
2623
- }
2624
- KeyCode::Down => {
2625
- self.state.sentinel_config.select_next();
2626
- self.request_immediate_render("sentinel_down");
2627
- }
2628
- KeyCode::Left => {
2629
- if self.state.sentinel_config.view_mode == SentinelViewMode::Custom {
2630
- self.sentinel_adjust_custom_field(false);
2631
- }
2632
- }
2633
- KeyCode::Right => {
2634
- if self.state.sentinel_config.view_mode == SentinelViewMode::Custom {
2635
- self.sentinel_adjust_custom_field(true);
2636
- }
2637
- }
2638
- KeyCode::Char('+') | KeyCode::Char('=') => {
2639
- if self.state.sentinel_config.view_mode == SentinelViewMode::Custom {
2640
- self.sentinel_adjust_custom_field(true);
2641
- }
2642
- }
2643
- KeyCode::Char('-') | KeyCode::Char('_') => {
2644
- if self.state.sentinel_config.view_mode == SentinelViewMode::Custom {
2645
- self.sentinel_adjust_custom_field(false);
2646
- }
2647
- }
2648
- KeyCode::Char(' ') => {
2649
- if self.state.sentinel_config.view_mode == SentinelViewMode::Custom {
2650
- use crate::ui::sentinel_config::CustomField;
2651
- if self.state.sentinel_config.custom_field == CustomField::Enabled {
2652
- self.sentinel_adjust_custom_field(true);
2653
- }
2654
- }
2655
- }
2656
- KeyCode::Char('r') | KeyCode::Char('R') => {
2657
- if self.state.operation_mode == OperationMode::Connected {
2658
- if let Some(ws) = ws_client {
2659
- self.begin_sentinel_load_request(ws);
2660
- }
2661
- }
2662
- self.request_render("sentinel_refresh");
2663
- }
2664
- KeyCode::Enter => {
2665
- if self.state.operation_mode != OperationMode::Connected {
2666
- self.state.sentinel_config.error =
2667
- Some("Connect to Gateway first.".to_string());
2668
- } else if let Some(ws) = ws_client {
2669
- // Apply custom configuration
2670
- let draft = self.state.sentinel_config.custom_draft.clone();
2671
- self.begin_sentinel_apply_custom_request(ws, &draft);
2672
- self.add_log("[SENTINEL] Applying custom limits...".to_string());
2673
- }
2674
- self.request_render("sentinel_apply");
2675
- }
2676
- KeyCode::Esc => {
2677
- self.pop_overlay();
2678
- self.request_render("sentinel_close");
2679
- }
2680
- _ => {}
2681
- }
2682
-
2683
- Ok(false)
2684
- }
2685
-
2686
- fn handle_shield_dashboard_input(
2687
- &mut self,
2688
- key: KeyEvent,
2689
- ws_client: Option<&WebSocketClient>,
2690
- ) -> anyhow::Result<bool> {
2691
- use crate::ui::shield_dashboard::ShieldTab;
2692
- use crossterm::event::KeyModifiers;
2693
-
2694
- if key.modifiers.contains(KeyModifiers::CONTROL) {
2695
- match key.code {
2696
- KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
2697
- _ => return Ok(false),
2698
- }
2699
- }
2700
-
2701
- match key.code {
2702
- KeyCode::Tab => {
2703
- self.state.shield_dashboard.tab = self.state.shield_dashboard.tab.next();
2704
- self.request_render("shield_tab");
2705
- }
2706
- KeyCode::BackTab => {
2707
- self.state.shield_dashboard.tab = self.state.shield_dashboard.tab.prev();
2708
- self.request_render("shield_tab_back");
2709
- }
2710
- KeyCode::Left => {
2711
- self.state.shield_dashboard.tab = self.state.shield_dashboard.tab.prev();
2712
- self.request_render("shield_tab_left");
2713
- }
2714
- KeyCode::Right => {
2715
- self.state.shield_dashboard.tab = self.state.shield_dashboard.tab.next();
2716
- self.request_render("shield_tab_right");
2717
- }
2718
- KeyCode::Up => {
2719
- if self.state.shield_dashboard.tab == ShieldTab::Activity
2720
- && self.state.shield_dashboard.selected_activity_index > 0
2721
- {
2722
- self.state.shield_dashboard.selected_activity_index -= 1;
2723
- self.request_render("shield_activity_up");
2724
- } else if self.state.shield_dashboard.tab == ShieldTab::Enforcement
2725
- && self.state.shield_dashboard.selected_block_index > 0
2726
- {
2727
- self.state.shield_dashboard.selected_block_index -= 1;
2728
- self.request_render("shield_block_up");
2729
- }
2730
- }
2731
- KeyCode::Down => {
2732
- if self.state.shield_dashboard.tab == ShieldTab::Activity {
2733
- let max = self.state.shield_dashboard.activity_events.len();
2734
- if max > 0 && self.state.shield_dashboard.selected_activity_index + 1 < max {
2735
- self.state.shield_dashboard.selected_activity_index += 1;
2736
- self.request_render("shield_activity_down");
2737
- }
2738
- } else if self.state.shield_dashboard.tab == ShieldTab::Enforcement {
2739
- let max = self.state.shield_dashboard.recent_blocks.len();
2740
- if max > 0 && self.state.shield_dashboard.selected_block_index + 1 < max {
2741
- self.state.shield_dashboard.selected_block_index += 1;
2742
- self.request_render("shield_block_down");
2743
- }
2744
- }
2745
- }
2746
- KeyCode::Enter => {
2747
- if self.state.shield_dashboard.tab == ShieldTab::Activity {
2748
- let run_id = self
2749
- .state
2750
- .shield_dashboard
2751
- .activity_events
2752
- .get(self.state.shield_dashboard.selected_activity_index)
2753
- .and_then(|row| row.run_id.clone());
2754
- if let Some(run_id) = run_id {
2755
- self.open_run_from_shield_block(ws_client, &run_id);
2756
- }
2757
- } else if self.state.shield_dashboard.tab == ShieldTab::Enforcement {
2758
- if let Some(row) = self
2759
- .state
2760
- .shield_dashboard
2761
- .recent_blocks
2762
- .get(self.state.shield_dashboard.selected_block_index)
2763
- {
2764
- let run_id = row.id.clone();
2765
- self.open_run_from_shield_block(ws_client, &run_id);
2766
- }
2767
- }
2768
- }
2769
- KeyCode::Char('r') | KeyCode::Char('R') => {
2770
- if self.state.operation_mode == OperationMode::Connected {
2771
- if let Some(ws) = ws_client {
2772
- self.begin_shield_load_request(ws, true);
2773
- }
2774
- } else {
2775
- self.state.shield_dashboard.fail_loading(
2776
- "Connect to Gateway first (connect portal).".to_string(),
2777
- );
2778
- }
2779
- self.request_render("shield_refresh");
2780
- }
2781
- KeyCode::Char('o') | KeyCode::Char('O') => {
2782
- self.push_overlay(Screen::RunManager);
2783
- if let Some(ws) = ws_client {
2784
- self.begin_run_list_request(ws, false);
2785
- }
2786
- self.add_log("[SHIELD] Opened Run Manager".to_string());
2787
- self.request_render("shield_open_runs");
2788
- }
2789
- KeyCode::Char('p') | KeyCode::Char('P') => {
2790
- if self.state.operation_mode == OperationMode::Connected {
2791
- self.state.navigation.navigate_to_base(Screen::PortalMonitoring);
2792
- if let Some(ws) = ws_client {
2793
- self.begin_portal_observability_request(ws);
2794
- }
2795
- self.add_log("[SHIELD] Opened Portal Monitoring".to_string());
2796
- } else {
2797
- self.add_log("[SHIELD] Connect to Gateway first.".to_string());
2798
- }
2799
- self.request_render("shield_open_monitoring");
2800
- }
2801
- KeyCode::Char('a') | KeyCode::Char('A') => {
2802
- self.state.shield_dashboard.auto_refresh_enabled =
2803
- !self.state.shield_dashboard.auto_refresh_enabled;
2804
- let status = if self.state.shield_dashboard.auto_refresh_enabled {
2805
- "ON"
2806
- } else {
2807
- "OFF"
2808
- };
2809
- self.add_log(format!("[SHIELD] Live refresh {}", status));
2810
- self.request_render("shield_live_toggle");
2811
- }
2812
- KeyCode::Esc => {
2813
- self.pop_overlay();
2814
- self.request_render("shield_close");
2815
- }
2816
- _ => {}
2817
- }
2818
-
2819
- Ok(false)
2820
- }
2821
-
2822
- // ============================================================
2823
- // SETTINGS INPUT HANDLING (Step 4.8)
2824
- // ============================================================
2825
-
2826
- /// Handle input when Settings screen is active
2827
- fn handle_settings_input(&mut self, key: KeyEvent) -> anyhow::Result<bool> {
2828
- use crossterm::event::KeyModifiers;
2829
-
2830
- // Ctrl+C/Q to exit
2831
- if key.modifiers.contains(KeyModifiers::CONTROL) {
2832
- match key.code {
2833
- KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
2834
- _ => return Ok(false),
2835
- }
2836
- }
2837
-
2838
- match key.code {
2839
- // Navigation
2840
- KeyCode::Up => {
2841
- self.state.settings.prev_section();
2842
- self.request_render("settings_up");
2843
- }
2844
- KeyCode::Down => {
2845
- self.state.settings.next_section();
2846
- self.request_render("settings_down");
2847
- }
2848
-
2849
- // Toggle current setting
2850
- KeyCode::Char(' ') => {
2851
- match self.state.settings.focused_section {
2852
- crate::ui::settings::SettingsSection::Mode => {
2853
- self.state.settings.toggle_mode();
2854
- self.add_log(format!(
2855
- "[SETTINGS] Mode: {}",
2856
- self.state.settings.mode.as_str()
2857
- ));
2858
- }
2859
- crate::ui::settings::SettingsSection::AIProvider => {
2860
- self.state.settings.next_provider();
2861
- self.add_log(format!(
2862
- "[SETTINGS] Provider: {}",
2863
- self.state.settings.ai_provider
2864
- ));
2865
- }
2866
- crate::ui::settings::SettingsSection::AutoUpdate => {
2867
- self.state.settings.toggle_auto_update();
2868
- self.add_log(format!(
2869
- "[SETTINGS] Auto-update: {}",
2870
- self.state.settings.auto_update
2871
- ));
2872
- }
2873
- _ => {}
2874
- }
2875
- self.request_render("settings_toggle");
2876
- }
2877
-
2878
- // Save settings
2879
- KeyCode::Enter => {
2880
- if self.state.settings.has_changes {
2881
- self.state.settings.save();
2882
- self.add_log("[SETTINGS] Settings saved successfully".to_string());
2883
- // TODO: Send config.update command via WebSocket
2884
- self.request_render("settings_save");
2885
- }
2886
- }
2887
-
2888
- // Close (with discard warning if changes)
2889
- KeyCode::Esc => {
2890
- if self.state.settings.has_changes {
2891
- self.add_log("[SETTINGS] Discarding unsaved changes".to_string());
2892
- self.state.settings.discard();
2893
- }
2894
- self.pop_overlay();
2895
- }
2896
-
2897
- _ => {}
2898
- }
2899
-
2900
- Ok(false)
2901
- }
2902
-
2903
- /// Handle input when Agent List screen is active
2904
- fn handle_agent_list_input(
2905
- &mut self,
2906
- key: KeyEvent,
2907
- ws_client: Option<&WebSocketClient>,
2908
- ) -> anyhow::Result<bool> {
2909
- use crossterm::event::KeyModifiers;
2910
-
2911
- // Ctrl+C/Q to exit
2912
- if key.modifiers.contains(KeyModifiers::CONTROL) {
2913
- match key.code {
2914
- KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
2915
- _ => return Ok(false),
2916
- }
2917
- }
2918
-
2919
- match key.code {
2920
- // Navigation
2921
- KeyCode::Up => {
2922
- if self.state.agent_list.selected_index > 0 {
2923
- self.state.agent_list.selected_index -= 1;
2924
- }
2925
- self.request_render("agent_list_up");
2926
- }
2927
- KeyCode::Down => {
2928
- let max = self.state.agent_list.agents.len().saturating_sub(1);
2929
- if self.state.agent_list.selected_index < max {
2930
- self.state.agent_list.selected_index += 1;
2931
- }
2932
- self.request_render("agent_list_down");
2933
- }
2934
-
2935
- // View details / Close detail view
2936
- KeyCode::Enter => {
2937
- if self.state.agent_list.detail_view.is_some() {
2938
- // Close detail view
2939
- self.state.agent_list.detail_view = None;
2940
- } else {
2941
- // Open detail view for selected agent
2942
- self.state.agent_list.detail_view = Some(self.state.agent_list.selected_index);
2943
- }
2944
- self.request_render("agent_list_toggle_detail");
2945
- }
2946
-
2947
- // Close
2948
- KeyCode::Esc => {
2949
- if self.state.agent_list.detail_view.is_some() {
2950
- // Close detail popup first
2951
- self.state.agent_list.detail_view = None;
2952
- self.request_render("agent_list_close_detail");
2953
- } else {
2954
- // Close agent list
2955
- self.pop_overlay();
2956
- self.request_render("agent_list_close");
2957
- }
2958
- }
2959
-
2960
- // Delete agent (Step 5.5)
2961
- KeyCode::Delete | KeyCode::Char('d') | KeyCode::Char('D') => {
2962
- // Only allow deletion in list view (not in detail view)
2963
- if self.state.agent_list.detail_view.is_none()
2964
- && !self.state.agent_list.agents.is_empty()
2965
- {
2966
- self.state.agent_delete_requested = true;
2967
- self.request_render("agent_delete_requested");
2968
- }
2969
- }
2970
-
2971
- // Step 6: queue a Gateway run (uses built-in `test` agent executor on the server)
2972
- KeyCode::Char('x') | KeyCode::Char('X') => {
2973
- if self.state.operation_mode != OperationMode::Connected {
2974
- self.add_log(
2975
- "[AGENT] Connect to Gateway first to run on the server (key x)."
2976
- .to_string(),
2977
- );
2978
- } else if self.state.agent_list.agents.is_empty() {
2979
- self.add_log("[AGENT] No agents in list.".to_string());
2980
- } else if let Some((agent_name, agent_model)) = self
2981
- .get_selected_agent()
2982
- .map(|a| (a.name.clone(), a.model.clone()))
2983
- {
2984
- if let Some(ws) = ws_client {
2985
- let name = format!("TUI: {}", agent_name);
2986
- let input = serde_json::json!({
2987
- "agent_id": "test",
2988
- "data": {
2989
- "prompt": format!("Quick run for local agent profile '{}' (model {}).", agent_name, agent_model)
2990
- }
2991
- });
2992
- let data = serde_json::json!({ "name": name, "input": input });
2993
- match ws.send_command("run.quick", Some(data)) {
2994
- Ok(id) => {
2995
- self.state.pending_run_quick_id = Some(id);
2996
- self.add_log(format!(
2997
- "[AGENT] Queued Gateway run for profile '{}' (x)",
2998
- agent_name
2999
- ));
3000
- }
3001
- Err(e) => self.add_log(format!("[ERROR] run.quick: {}", e)),
3002
- }
3003
- }
3004
- }
3005
- self.request_render("agent_list_quick_run");
3006
- }
3007
-
3008
- _ => {}
3009
- }
3010
-
3011
- Ok(false)
3012
- }
3013
-
3014
- /// Delete selected agent (Step 5.5)
3015
- pub fn delete_selected_agent(&mut self, ws_client: &Option<crate::websocket::WebSocketClient>) {
3016
- if self.state.agent_list.agents.is_empty() {
3017
- return;
3018
- }
3019
-
3020
- let agent_name = self.state.agent_list.agents[self.state.agent_list.selected_index]
3021
- .name
3022
- .clone();
3023
-
3024
- // Send agent.delete command via WebSocket
3025
- if let Some(ws) = ws_client {
3026
- let delete_data = serde_json::json!({
3027
- "name": agent_name
3028
- });
3029
-
3030
- match ws.send_command("agent.delete", Some(delete_data)) {
3031
- Ok(id) => {
3032
- let short_id = &id[id.len().saturating_sub(8)..];
3033
- self.add_log(format!(
3034
- "[AGENT] Sent agent.delete for '{}' (id: {})",
3035
- agent_name, short_id
3036
- ));
3037
- // Store command ID for response tracking
3038
- self.state.pending_agent_delete_id = Some(id);
3039
- }
3040
- Err(e) => {
3041
- self.add_log(format!("[ERROR] Failed to delete agent: {}", e));
3042
- }
3043
- }
3044
- } else {
3045
- self.add_log("[ERROR] WebSocket not connected. Cannot delete agent.".to_string());
3046
- }
3047
- }
3048
-
3049
- /// Get selected agent (Step 5.6)
3050
- pub fn get_selected_agent(&self) -> Option<&AgentInfo> {
3051
- if self.state.agent_list.agents.is_empty() {
3052
- return None;
3053
- }
3054
- self.state
3055
- .agent_list
3056
- .agents
3057
- .get(self.state.agent_list.selected_index)
3058
- }
3059
-
3060
- /// Get selected agent name (Step 5.6)
3061
- pub fn get_selected_agent_name(&self) -> Option<String> {
3062
- self.get_selected_agent().map(|agent| agent.name.clone())
3063
- }
3064
-
3065
- /// Handle input when Connection Portal screen is active (Step 7)
3066
- fn handle_connection_portal_input(
3067
- &mut self,
3068
- key: KeyEvent,
3069
- ws_client: Option<&WebSocketClient>,
3070
- ) -> anyhow::Result<bool> {
3071
- use crossterm::event::KeyModifiers;
3072
-
3073
- // Ctrl+C/Q to exit
3074
- if key.modifiers.contains(KeyModifiers::CONTROL) {
3075
- match key.code {
3076
- KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
3077
- _ => return Ok(false),
3078
- }
3079
- }
3080
-
3081
- // Disable input if connecting
3082
- if self.state.connection_portal.connecting {
3083
- return Ok(false);
3084
- }
3085
-
3086
- match key.code {
3087
- // ESC - Close portal and return to Main
3088
- KeyCode::Esc => {
3089
- // Drop stale connect attempt + UI state so the next open is not "pre-broken".
3090
- self.state.pending_gateway_connect_id = None;
3091
- self.state.connection_portal.finish_connecting();
3092
- self.state.connection_portal.error = None;
3093
- self.state.connection_portal.activity_log.clear();
3094
- self.state.navigation.navigate_to_base(Screen::Main);
3095
- self.add_log("[NAV] Connection Portal closed".to_string());
3096
- self.request_immediate_render("portal_close");
3097
- }
3098
-
3099
- // M — Portal Monitoring (after successful connect)
3100
- KeyCode::Char('m') | KeyCode::Char('M')
3101
- if self.state.connection_portal.connection_success
3102
- || (matches!(self.state.operation_mode, OperationMode::Connected)
3103
- && self.state.gateway_url.is_some()) =>
3104
- {
3105
- self.state
3106
- .navigation
3107
- .navigate_to_base(Screen::PortalMonitoring);
3108
- self.state.advanced_monitoring =
3109
- AdvancedMonitoringState::new(self.state.gateway_url.clone());
3110
- self.state.portal_monitoring.scroll_offset = 0;
3111
- self.add_log("[NAV] Opening Portal Monitoring".to_string());
3112
- if let Some(ws) = ws_client {
3113
- self.begin_portal_observability_request(ws);
3114
- }
3115
- self.request_immediate_render("open_portal_monitoring");
3116
- }
3117
-
3118
- // D — Disconnect Gateway (drop CLI link; reconnect with Enter after)
3119
- KeyCode::Char('d') | KeyCode::Char('D') if self.gateway_session_linked() => {
3120
- self.request_gateway_disconnect(ws_client);
3121
- self.request_immediate_render("portal_disconnect");
3122
- }
3123
-
3124
- // R — Re-verify /health + /ready (detect stale link when Docker/Gateway stopped)
3125
- KeyCode::Char('r') | KeyCode::Char('R') if self.gateway_session_linked() => {
3126
- self.state.gateway_healthy = false;
3127
- self.request_gateway_health_check(ws_client);
3128
- self.request_immediate_render("portal_reverify");
3129
- }
3130
-
3131
- // L - Continue in local-only mode (no Gateway connection)
3132
- KeyCode::Char('l') | KeyCode::Char('L') => {
3133
- if self.gateway_session_linked() {
3134
- self.request_gateway_disconnect(ws_client);
3135
- }
3136
- use crate::app::OperationMode;
3137
- self.state.operation_mode = OperationMode::Local;
3138
- self.state.gateway_url = None;
3139
- self.state.connected = false;
3140
- self.state.gateway_healthy = false;
3141
- self.state.gateway_db_summary = None;
3142
- self.state.connection_portal.finish_connecting();
3143
- self.state.connection_portal.connection_success = false;
3144
- self.state.connection_portal.error = None;
3145
- self.state.pending_gateway_connect_id = None;
3146
- self.state.pending_gateway_health_id = None;
3147
- self.state.pending_gateway_observability_id = None;
3148
- self.state.pending_monitoring_refresh_id = None;
3149
- self.state.pending_monitoring_logs_id = None;
3150
- self.state.pending_monitoring_drill_id = None;
3151
- self.state.portal_monitoring = PortalMonitoringState::default();
3152
- self.state.advanced_monitoring = AdvancedMonitoringState::default();
3153
- self.state.portal_observability_last_poll = None;
3154
- self.state.navigation.navigate_to_base(Screen::Main);
3155
- self.add_log("[MODE] Continuing in LOCAL mode (no Gateway connection)".to_string());
3156
- self.request_immediate_render("portal_local_mode");
3157
- }
3158
-
3159
- // Tab - Switch between URL and Username fields
3160
- KeyCode::Tab | KeyCode::BackTab => {
3161
- if self.state.connection_portal.connection_success {
3162
- return Ok(false);
3163
- }
3164
- self.state.connection_portal.toggle_field();
3165
- self.request_immediate_render("portal_field_toggle");
3166
- }
3167
-
3168
- // Up/Down - Navigate between fields
3169
- KeyCode::Up | KeyCode::Down => {
3170
- if self.state.connection_portal.connection_success {
3171
- return Ok(false);
3172
- }
3173
- self.state.connection_portal.toggle_field();
3174
- self.request_immediate_render("portal_navigate");
3175
- }
3176
-
3177
- // Enter - Connect to Gateway
3178
- KeyCode::Enter => {
3179
- let raw = self.state.connection_portal.gateway_url.clone();
3180
- let url = raw
3181
- .lines()
3182
- .map(str::trim)
3183
- .find(|l| !l.is_empty())
3184
- .unwrap_or("")
3185
- .to_string();
3186
- self.state.connection_portal.gateway_url = url.clone();
3187
-
3188
- // Validate URL
3189
- if url.is_empty() {
3190
- self.state.connection_portal.error =
3191
- Some("Gateway URL is required".to_string());
3192
- self.request_immediate_render("portal_error");
3193
- return Ok(false);
3194
- }
3195
-
3196
- if !url.starts_with("http://") && !url.starts_with("https://") {
3197
- self.state.connection_portal.error =
3198
- Some("URL must start with http:// or https://".to_string());
3199
- self.request_immediate_render("portal_error");
3200
- return Ok(false);
3201
- }
3202
-
3203
- // Connect to Gateway
3204
- self.state.connection_portal.error = None;
3205
- self.state.connection_portal.begin_connecting();
3206
-
3207
- if let Some(ws) = ws_client {
3208
- let connect_data = serde_json::json!({
3209
- "url": url,
3210
- "username": self.state.connection_portal.username
3211
- });
3212
-
3213
- match ws.send_command("gateway.connect", Some(connect_data)) {
3214
- Ok(id) => {
3215
- let short_id = &id[id.len().saturating_sub(8)..];
3216
- self.add_log(format!(
3217
- "[GATEWAY] Connecting to {} (id: {})",
3218
- url, short_id
3219
- ));
3220
- self.state.pending_gateway_connect_id = Some(id);
3221
- }
3222
- Err(e) => {
3223
- self.state.connection_portal.finish_connecting();
3224
- self.state.connection_portal.error =
3225
- Some(format!("Failed to connect: {}", e));
3226
- self.add_log(format!("[ERROR] Gateway connection failed: {}", e));
3227
- }
3228
- }
3229
- } else {
3230
- self.state.connection_portal.finish_connecting();
3231
- self.state.connection_portal.error =
3232
- Some("WebSocket not connected".to_string());
3233
- self.add_log("[ERROR] WebSocket not connected".to_string());
3234
- }
3235
-
3236
- // CRITICAL: Use immediate render for instant status update
3237
- self.request_immediate_render("portal_connect");
3238
- }
3239
-
3240
- // Typing - edit focused field (immediate render for responsiveness)
3241
- KeyCode::Char(c) => {
3242
- if self.state.connection_portal.connection_success {
3243
- // After connect, URL field is read-only; use M for monitoring, ESC to leave.
3244
- return Ok(false);
3245
- }
3246
- match self.state.connection_portal.focused_field {
3247
- PortalField::GatewayUrl => {
3248
- self.state.connection_portal.gateway_url.push(c);
3249
- self.state.connection_portal.error = None;
3250
- // OPTIMIZATION: Immediate render for instant typing feedback
3251
- self.request_immediate_render("portal_input");
3252
- }
3253
- PortalField::Username => {
3254
- self.state.connection_portal.username.push(c);
3255
- self.state.connection_portal.error = None;
3256
- // OPTIMIZATION: Immediate render for instant typing feedback
3257
- self.request_immediate_render("portal_input");
3258
- }
3259
- }
3260
- }
3261
-
3262
- // Backspace - delete character (immediate render for responsiveness)
3263
- KeyCode::Backspace => {
3264
- if self.state.connection_portal.connection_success {
3265
- return Ok(false);
3266
- }
3267
- match self.state.connection_portal.focused_field {
3268
- PortalField::GatewayUrl => {
3269
- self.state.connection_portal.gateway_url.pop();
3270
- }
3271
- PortalField::Username => {
3272
- self.state.connection_portal.username.pop();
3273
- }
3274
- }
3275
- self.state.connection_portal.error = None;
3276
- // OPTIMIZATION: Immediate render for instant feedback
3277
- self.request_immediate_render("portal_delete");
3278
- }
3279
-
3280
- _ => {}
3281
- }
3282
-
3283
- Ok(false)
3284
- }
3285
-
3286
- fn handle_portal_monitoring_input(
3287
- &mut self,
3288
- key: KeyEvent,
3289
- ws_client: Option<&WebSocketClient>,
3290
- ) -> anyhow::Result<bool> {
3291
- use crate::ui::portal_monitoring::{
3292
- portal_monitoring_scroll_max, portal_monitoring_section_offset,
3293
- };
3294
- use crossterm::event::KeyModifiers;
3295
- if key.modifiers.contains(KeyModifiers::CONTROL) {
3296
- match key.code {
3297
- KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
3298
- _ => return Ok(false),
3299
- }
3300
- }
3301
- if key.code == KeyCode::F(10) {
3302
- return Ok(true);
3303
- }
3304
- if key.kind != KeyEventKind::Press {
3305
- return Ok(false);
3306
- }
3307
- if self.state.portal_monitoring.help_visible {
3308
- if key.code == KeyCode::Esc || key.code == KeyCode::Char('?') {
3309
- self.state.portal_monitoring.help_visible = false;
3310
- self.request_immediate_render("portal_help_close");
3311
- }
3312
- return Ok(false);
3313
- }
3314
- if key.code == KeyCode::Esc {
3315
- self.state.pending_gateway_observability_id = None;
3316
- self.state.pending_monitoring_refresh_id = None;
3317
- self.state.pending_monitoring_logs_id = None;
3318
- self.state.pending_monitoring_drill_id = None;
3319
- self.state.portal_monitoring.section_refresh_loading = None;
3320
- self.state.portal_monitoring.logs_fetch_loading = false;
3321
- self.state.portal_monitoring.metrics_drill = None;
3322
- self.state.portal_monitoring.metrics_drill_lines.clear();
3323
- self.state.portal_monitoring.metrics_drill_loading = false;
3324
- self.state.navigation.navigate_to_base(Screen::Main);
3325
- self.add_log("[NAV] Portal Monitoring closed".to_string());
3326
- self.request_immediate_render("portal_monitoring_close");
3327
- return Ok(false);
3328
- }
3329
- if key.code == KeyCode::Char('?') {
3330
- self.state.portal_monitoring.help_visible = true;
3331
- self.request_immediate_render("portal_help_open");
3332
- return Ok(false);
3333
- }
3334
- // R is deliberately global: refresh the full snapshot without changing the current view.
3335
- if key.code == KeyCode::Char('r') || key.code == KeyCode::Char('R') {
3336
- if let Some(ws) = ws_client {
3337
- self.state.portal_observability_last_poll = None;
3338
- self.cancel_portal_monitoring_requests();
3339
- self.state.portal_monitoring.last_refresh = Some(std::time::Instant::now());
3340
- self.begin_portal_observability_request(ws);
3341
- self.request_immediate_render("portal_obs_refresh");
3342
- } else {
3343
- self.state.portal_monitoring.error =
3344
- Some("WebSocket to CLI is not connected.".to_string());
3345
- self.request_immediate_render("portal_obs_no_ws");
3346
- }
3347
- return Ok(false);
3348
- }
3349
- if key.code == KeyCode::Char('l') || key.code == KeyCode::Char('L') {
3350
- let logs_open = self
3351
- .state
3352
- .advanced_monitoring
3353
- .monitoring_state
3354
- .selected_section
3355
- == Some(MonitoringSection::Logs)
3356
- && self
3357
- .state
3358
- .advanced_monitoring
3359
- .monitoring_state
3360
- .sections
3361
- .get(&MonitoringSection::Logs)
3362
- .map(|section| section.expanded)
3363
- .unwrap_or(false);
3364
- if logs_open && !self.state.portal_monitoring.logs_fetch_loading {
3365
- self.state
3366
- .advanced_monitoring
3367
- .monitoring_state
3368
- .collapse_section(MonitoringSection::Logs);
3369
- self.state.portal_monitoring.scroll_offset = 0;
3370
- self.request_immediate_render("portal_monitoring_logs_close");
3371
- return Ok(false);
3372
- }
3373
- self.state
3374
- .advanced_monitoring
3375
- .monitoring_state
3376
- .select_section(MonitoringSection::Logs);
3377
- self.state
3378
- .advanced_monitoring
3379
- .monitoring_state
3380
- .expand_section(MonitoringSection::Logs);
3381
- if let Some(ws) = ws_client {
3382
- self.cancel_portal_monitoring_requests();
3383
- self.begin_monitoring_logs_request(ws);
3384
- let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
3385
- let sw = self.state.portal_monitoring.summary_clip_width.max(24);
3386
- self.state.portal_monitoring.scroll_offset = portal_monitoring_section_offset(
3387
- &self.state,
3388
- sw,
3389
- inner_h,
3390
- MonitoringSection::Logs,
3391
- );
3392
- self.request_immediate_render("portal_monitoring_logs");
3393
- } else {
3394
- self.state.portal_monitoring.error =
3395
- Some("WebSocket to CLI is not connected.".to_string());
3396
- self.request_immediate_render("portal_obs_no_ws");
3397
- }
3398
- return Ok(false);
3399
- }
3400
-
3401
- if key.code == KeyCode::Char('h') || key.code == KeyCode::Char('H') {
3402
- self.state
3403
- .advanced_monitoring
3404
- .monitoring_state
3405
- .select_section(MonitoringSection::Metrics);
3406
- self.state
3407
- .advanced_monitoring
3408
- .monitoring_state
3409
- .expand_section(MonitoringSection::Metrics);
3410
- self.state.portal_monitoring.trend_view = !self.state.portal_monitoring.trend_view;
3411
- let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
3412
- let sw = self.state.portal_monitoring.summary_clip_width.max(24);
3413
- self.state.portal_monitoring.scroll_offset = portal_monitoring_section_offset(
3414
- &self.state,
3415
- sw,
3416
- inner_h,
3417
- MonitoringSection::Metrics,
3418
- );
3419
- let status = if self.state.portal_monitoring.trend_view {
3420
- "enabled"
3421
- } else {
3422
- "disabled"
3423
- };
3424
- self.add_log(format!("[PORTAL] Trend/history view {}", status));
3425
- self.request_immediate_render("portal_trend_toggle");
3426
- return Ok(false);
3427
- }
3428
-
3429
- if key.code == KeyCode::Char('e') || key.code == KeyCode::Char('E') {
3430
- match self.export_portal_monitoring_snapshot() {
3431
- Ok(path) => {
3432
- self.add_log(format!("[PORTAL] Exported monitoring snapshot: {}", path));
3433
- self.state.portal_monitoring.error = None;
3434
- }
3435
- Err(e) => {
3436
- self.state.portal_monitoring.error = Some(format!("Export failed: {}", e));
3437
- }
3438
- }
3439
- self.request_immediate_render("portal_export");
3440
- return Ok(false);
3441
- }
3442
-
3443
- // Phase 3: extended dependency view (GET /api/monitoring/dependencies/detail)
3444
- if key.code == KeyCode::Char('t') || key.code == KeyCode::Char('T') {
3445
- self.state
3446
- .advanced_monitoring
3447
- .monitoring_state
3448
- .select_section(MonitoringSection::Dependencies);
3449
- self.state
3450
- .advanced_monitoring
3451
- .monitoring_state
3452
- .expand_section(MonitoringSection::Dependencies);
3453
- let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
3454
- let sw = self.state.portal_monitoring.summary_clip_width.max(24);
3455
- self.state.portal_monitoring.scroll_offset = portal_monitoring_section_offset(
3456
- &self.state,
3457
- sw,
3458
- inner_h,
3459
- MonitoringSection::Dependencies,
3460
- );
3461
- if let Some(ws) = ws_client {
3462
- self.cancel_portal_monitoring_requests();
3463
- self.begin_dependencies_detail_drill(ws);
3464
- self.request_immediate_render("portal_deps_drill");
3465
- } else {
3466
- self.state.portal_monitoring.error =
3467
- Some("WebSocket to CLI is not connected.".to_string());
3468
- self.request_immediate_render("portal_obs_no_ws");
3469
- }
3470
- return Ok(false);
3471
- }
3472
-
3473
- // Phase 4: diagnostics for the local CLI/TUI bridge + Gateway reachability.
3474
- // S toggles: show diagnostics → hide (remove override). While loading, S aborts the
3475
- // in-flight drill so a later S can fetch again (avoids orphaned request ids vs cancel+refetch).
3476
- if key.code == KeyCode::Char('s') || key.code == KeyCode::Char('S') {
3477
- self.state
3478
- .advanced_monitoring
3479
- .monitoring_state
3480
- .select_section(MonitoringSection::System);
3481
- self.state
3482
- .advanced_monitoring
3483
- .monitoring_state
3484
- .expand_section(MonitoringSection::System);
3485
- let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
3486
- let sw = self.state.portal_monitoring.summary_clip_width.max(24);
3487
- self.state.portal_monitoring.scroll_offset = portal_monitoring_section_offset(
3488
- &self.state,
3489
- sw,
3490
- inner_h,
3491
- MonitoringSection::System,
3492
- );
3493
-
3494
- let pm = &mut self.state.portal_monitoring;
3495
- let has_system_diag =
3496
- pm.section_overrides.contains_key(&MonitoringSection::System);
3497
- let system_loading = pm.section_refresh_loading == Some(MonitoringSection::System);
3498
-
3499
- if has_system_diag && !system_loading {
3500
- pm.section_overrides.remove(&MonitoringSection::System);
3501
- self.request_immediate_render("portal_system_dismiss");
3502
- return Ok(false);
3503
- }
3504
- if system_loading {
3505
- self.state.pending_monitoring_drill_id = None;
3506
- self.state.portal_monitoring.section_refresh_loading = None;
3507
- self.request_immediate_render("portal_system_abort_fetch");
3508
- return Ok(false);
3509
- }
3510
-
3511
- if let Some(ws) = ws_client {
3512
- self.cancel_portal_monitoring_requests();
3513
- self.begin_system_diagnostics_request(ws);
3514
- self.request_immediate_render("portal_system_diagnostics");
3515
- } else {
3516
- self.state.portal_monitoring.error =
3517
- Some("WebSocket to CLI is not connected.".to_string());
3518
- self.request_immediate_render("portal_obs_no_ws");
3519
- }
3520
- return Ok(false);
3521
- }
3522
-
3523
- if key.code == KeyCode::Right {
3524
- self.state
3525
- .advanced_monitoring
3526
- .monitoring_state
3527
- .select_section(MonitoringSection::Metrics);
3528
- self.state
3529
- .advanced_monitoring
3530
- .monitoring_state
3531
- .expand_section(MonitoringSection::Metrics);
3532
- let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
3533
- let sw = self.state.portal_monitoring.summary_clip_width.max(24);
3534
- self.state.portal_monitoring.scroll_offset = portal_monitoring_section_offset(
3535
- &self.state,
3536
- sw,
3537
- inner_h,
3538
- MonitoringSection::Metrics,
3539
- );
3540
- if let Some(ws) = ws_client {
3541
- self.cancel_portal_monitoring_requests();
3542
- let panel = self
3543
- .state
3544
- .portal_monitoring
3545
- .metrics_drill
3546
- .map(|p| p.next())
3547
- .unwrap_or(MetricsDrillPanel::Http);
3548
- self.begin_monitoring_drill_request(ws, panel);
3549
- self.request_immediate_render("portal_metrics_drill");
3550
- } else {
3551
- self.state.portal_monitoring.error =
3552
- Some("WebSocket to CLI is not connected.".to_string());
3553
- self.request_immediate_render("portal_obs_no_ws");
3554
- }
3555
- return Ok(false);
3556
- }
3557
-
3558
- if key.code == KeyCode::Left {
3559
- if self.state.portal_monitoring.metrics_drill.is_some() {
3560
- self.state.portal_monitoring.metrics_drill = None;
3561
- self.state.portal_monitoring.metrics_drill_lines.clear();
3562
- self.state.portal_monitoring.metrics_drill_loading = false;
3563
- self.request_immediate_render("portal_metrics_drill_exit");
3564
- return Ok(false);
3565
- }
3566
- }
3567
-
3568
- // Toggle auto-refresh with 'A' key
3569
- if key.code == KeyCode::Char('a') || key.code == KeyCode::Char('A') {
3570
- self.state.portal_monitoring.auto_refresh_enabled =
3571
- !self.state.portal_monitoring.auto_refresh_enabled;
3572
- self.state.advanced_monitoring.monitoring_state.live_mode =
3573
- self.state.portal_monitoring.auto_refresh_enabled;
3574
- let status = if self.state.portal_monitoring.auto_refresh_enabled {
3575
- "enabled"
3576
- } else {
3577
- "disabled"
3578
- };
3579
- self.add_log(format!("[PORTAL] Auto-refresh {}", status));
3580
- self.request_immediate_render("portal_obs_auto_toggle");
3581
- return Ok(false);
3582
- }
3583
-
3584
- // Phase 1: Enter / Space — expand or collapse selected section
3585
- if key.code == KeyCode::Enter || key.code == KeyCode::Char(' ') {
3586
- let idx = self
3587
- .state
3588
- .advanced_monitoring
3589
- .monitoring_state
3590
- .selected_index;
3591
- self.state
3592
- .advanced_monitoring
3593
- .monitoring_state
3594
- .toggle_section_at_index(idx);
3595
- self.request_immediate_render("portal_monitoring_toggle_section");
3596
- return Ok(false);
3597
- }
3598
-
3599
- let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
3600
- let sw = self.state.portal_monitoring.summary_clip_width.max(24);
3601
- let max_scroll = portal_monitoring_scroll_max(&self.state, sw, inner_h);
3602
- let logs_selected_and_open = self
3603
- .state
3604
- .advanced_monitoring
3605
- .monitoring_state
3606
- .selected_section
3607
- == Some(MonitoringSection::Logs)
3608
- && self
3609
- .state
3610
- .advanced_monitoring
3611
- .monitoring_state
3612
- .sections
3613
- .get(&MonitoringSection::Logs)
3614
- .map(|section| section.expanded)
3615
- .unwrap_or(false);
3616
-
3617
- let system_selected_and_diagnostics = self
3618
- .state
3619
- .advanced_monitoring
3620
- .monitoring_state
3621
- .selected_section
3622
- == Some(MonitoringSection::System)
3623
- && self
3624
- .state
3625
- .advanced_monitoring
3626
- .monitoring_state
3627
- .sections
3628
- .get(&MonitoringSection::System)
3629
- .map(|section| section.expanded)
3630
- .unwrap_or(false)
3631
- && self
3632
- .state
3633
- .portal_monitoring
3634
- .section_overrides
3635
- .contains_key(&MonitoringSection::System);
3636
-
3637
- match key.code {
3638
- KeyCode::Up => {
3639
- if logs_selected_and_open || system_selected_and_diagnostics {
3640
- self.state.portal_monitoring.scroll_offset =
3641
- self.state.portal_monitoring.scroll_offset.saturating_sub(1);
3642
- self.request_immediate_render("portal_obs_scroll");
3643
- } else {
3644
- self.state
3645
- .advanced_monitoring
3646
- .monitoring_state
3647
- .select_previous_wrapped();
3648
- self.state.portal_monitoring.scroll_offset = 0;
3649
- self.request_immediate_render("portal_monitoring_select");
3650
- }
3651
- }
3652
- KeyCode::Down => {
3653
- if logs_selected_and_open || system_selected_and_diagnostics {
3654
- self.state.portal_monitoring.scroll_offset =
3655
- (self.state.portal_monitoring.scroll_offset + 1).min(max_scroll);
3656
- self.request_immediate_render("portal_obs_scroll");
3657
- } else {
3658
- self.state
3659
- .advanced_monitoring
3660
- .monitoring_state
3661
- .select_next_wrapped();
3662
- self.state.portal_monitoring.scroll_offset = 0;
3663
- self.request_immediate_render("portal_monitoring_select");
3664
- }
3665
- }
3666
- KeyCode::PageUp => {
3667
- let step = inner_h.saturating_sub(3).max(1);
3668
- self.state.portal_monitoring.scroll_offset = self
3669
- .state
3670
- .portal_monitoring
3671
- .scroll_offset
3672
- .saturating_sub(step);
3673
- self.request_immediate_render("portal_obs_scroll");
3674
- }
3675
- KeyCode::PageDown => {
3676
- let step = inner_h.saturating_sub(3).max(1);
3677
- self.state.portal_monitoring.scroll_offset =
3678
- (self.state.portal_monitoring.scroll_offset + step).min(max_scroll);
3679
- self.request_immediate_render("portal_obs_scroll");
3680
- }
3681
- _ => {}
3682
- }
3683
- Ok(false)
3684
- }
3685
-
3686
- /// After Setup Portal: reset Connection Portal so stale success/logs/errors are gone, pre-fill
3687
- /// URL from the selected row, and copy detection hints onto the Connection screen.
3688
- fn open_connection_portal_from_setup(&mut self) {
3689
- use GatewayOption::*;
3690
- self.state.connection_portal.reset();
3691
- let url = self.state.setup_portal.selected_option.default_url();
3692
- self.state.connection_portal.gateway_url = url.to_string();
3693
- self.state.connection_portal.username.clear();
3694
- self.state.connection_portal.focused_field = PortalField::GatewayUrl;
3695
- self.state.connection_portal.connection_success = false;
3696
- self.state.connection_portal.finish_connecting();
3697
-
3698
- if let Some(ref det) = self.state.setup_portal.detection_result {
3699
- let now = wall_clock_hms();
3700
- match self.state.setup_portal.selected_option {
3701
- LocalBundle if !det.local_bundle.available => {
3702
- self.state.connection_portal.add_log_entry(
3703
- now,
3704
- LogLevel::Warning,
3705
- "Setup did not find a vendored local Gateway. Install 4runr-os globally, run from the repo, or set your Gateway URL manually.".to_string()
3706
- );
3707
- }
3708
- LocalBundle if det.local_bundle.available && !det.local_bundle.running => {
3709
- self.state.connection_portal.add_log_entry(
3710
- now,
3711
- LogLevel::Info,
3712
- "Local Gateway bundle detected. Start the Gateway (cd apps/gateway && npm start), then press Enter to connect.".to_string()
3713
- );
3714
- }
3715
- CloudServer if !det.cloud_server.available => {
3716
- self.state.connection_portal.add_log_entry(
3717
- now,
3718
- LogLevel::Warning,
3719
- "4Runr Cloud is not available in this build (Coming soon). Use Local Bundle or enter a Custom / self-hosted Gateway URL.".to_string()
3720
- );
3721
- }
3722
- CustomUrl if url.is_empty() => {
3723
- self.state.connection_portal.add_log_entry(
3724
- now,
3725
- LogLevel::Info,
3726
- "Type your Gateway URL (https://…), then press Enter to connect."
3727
- .to_string(),
3728
- );
3729
- }
3730
- _ => {}
3731
- }
3732
- } else if matches!(self.state.setup_portal.selected_option, CustomUrl) && url.is_empty() {
3733
- let now = wall_clock_hms();
3734
- self.state.connection_portal.add_log_entry(
3735
- now,
3736
- LogLevel::Info,
3737
- "Type your Gateway URL, then press Enter to connect.".to_string(),
3738
- );
3739
- }
3740
-
3741
- self.state
3742
- .navigation
3743
- .navigate_to_base(Screen::ConnectionPortal);
3744
- }
3745
-
3746
- // ============================================================
3747
- // SETUP PORTAL INPUT HANDLING
3748
- // ============================================================
3749
-
3750
- fn handle_setup_portal_input(
3751
- &mut self,
3752
- key: KeyEvent,
3753
- _ws_client: Option<&WebSocketClient>,
3754
- ) -> anyhow::Result<bool> {
3755
- // ESC always closes the portal (even when detecting), so user can always get out
3756
- if key.code == KeyCode::Esc {
3757
- self.state.setup_portal.detecting = false;
3758
- self.state.setup_portal.detecting_since = None;
3759
- self.state.navigation.navigate_to_base(Screen::Main);
3760
- self.add_log("[NAV] Setup Portal closed".to_string());
3761
- self.request_immediate_render("setup_portal_close");
3762
- return Ok(false);
3763
- }
3764
-
3765
- // Belt-and-suspenders: only Press for navigation (ESC Release handled in main loop + above).
3766
- if key.kind != KeyEventKind::Press {
3767
- return Ok(false);
3768
- }
3769
-
3770
- // First scan (no cached result): wait before Enter; still allow ↑↓ so the UI matches behavior.
3771
- if self.state.setup_portal.detecting && self.state.setup_portal.detection_result.is_none() {
3772
- if matches!(key.code, KeyCode::Enter) {
3773
- return Ok(false);
3774
- }
3775
- }
3776
-
3777
- match key.code {
3778
- // F2 - Go directly to Connection Portal (same pre-fill + reset as Enter)
3779
- KeyCode::F(2) => {
3780
- self.open_connection_portal_from_setup();
3781
- self.add_log("[NAV] Opening Connection Portal (F2)...".to_string());
3782
- self.request_immediate_render("open_connection_portal");
3783
- }
3784
-
3785
- // Up/Down - Navigate options (immediate render so windowed terminals always redraw)
3786
- KeyCode::Up => {
3787
- self.state.setup_portal.cycle_option(-1);
3788
- self.request_immediate_render("setup_nav_up");
3789
- }
3790
-
3791
- KeyCode::Down => {
3792
- self.state.setup_portal.cycle_option(1);
3793
- self.request_immediate_render("setup_nav_down");
3794
- }
3795
-
3796
- // Enter - Connection Portal with pre-filled URL and fresh portal state
3797
- KeyCode::Enter => {
3798
- self.state.setup_portal.error = None;
3799
- self.open_connection_portal_from_setup();
3800
-
3801
- self.add_log(format!(
3802
- "[SETUP] Selected {} - Opening Connection Portal",
3803
- self.state.setup_portal.selected_option.as_str()
3804
- ));
3805
- self.request_immediate_render("setup_enter");
3806
- }
3807
-
3808
- _ => {}
3809
- }
3810
-
3811
- Ok(false)
3812
- }
3813
- }