gobby 0.2.5__py3-none-any.whl

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 (383) hide show
  1. gobby/__init__.py +3 -0
  2. gobby/adapters/__init__.py +30 -0
  3. gobby/adapters/base.py +93 -0
  4. gobby/adapters/claude_code.py +276 -0
  5. gobby/adapters/codex.py +1292 -0
  6. gobby/adapters/gemini.py +343 -0
  7. gobby/agents/__init__.py +37 -0
  8. gobby/agents/codex_session.py +120 -0
  9. gobby/agents/constants.py +112 -0
  10. gobby/agents/context.py +362 -0
  11. gobby/agents/definitions.py +133 -0
  12. gobby/agents/gemini_session.py +111 -0
  13. gobby/agents/registry.py +618 -0
  14. gobby/agents/runner.py +968 -0
  15. gobby/agents/session.py +259 -0
  16. gobby/agents/spawn.py +916 -0
  17. gobby/agents/spawners/__init__.py +77 -0
  18. gobby/agents/spawners/base.py +142 -0
  19. gobby/agents/spawners/cross_platform.py +266 -0
  20. gobby/agents/spawners/embedded.py +225 -0
  21. gobby/agents/spawners/headless.py +226 -0
  22. gobby/agents/spawners/linux.py +125 -0
  23. gobby/agents/spawners/macos.py +277 -0
  24. gobby/agents/spawners/windows.py +308 -0
  25. gobby/agents/tty_config.py +319 -0
  26. gobby/autonomous/__init__.py +32 -0
  27. gobby/autonomous/progress_tracker.py +447 -0
  28. gobby/autonomous/stop_registry.py +269 -0
  29. gobby/autonomous/stuck_detector.py +383 -0
  30. gobby/cli/__init__.py +67 -0
  31. gobby/cli/__main__.py +8 -0
  32. gobby/cli/agents.py +529 -0
  33. gobby/cli/artifacts.py +266 -0
  34. gobby/cli/daemon.py +329 -0
  35. gobby/cli/extensions.py +526 -0
  36. gobby/cli/github.py +263 -0
  37. gobby/cli/init.py +53 -0
  38. gobby/cli/install.py +614 -0
  39. gobby/cli/installers/__init__.py +37 -0
  40. gobby/cli/installers/antigravity.py +65 -0
  41. gobby/cli/installers/claude.py +363 -0
  42. gobby/cli/installers/codex.py +192 -0
  43. gobby/cli/installers/gemini.py +294 -0
  44. gobby/cli/installers/git_hooks.py +377 -0
  45. gobby/cli/installers/shared.py +737 -0
  46. gobby/cli/linear.py +250 -0
  47. gobby/cli/mcp.py +30 -0
  48. gobby/cli/mcp_proxy.py +698 -0
  49. gobby/cli/memory.py +304 -0
  50. gobby/cli/merge.py +384 -0
  51. gobby/cli/projects.py +79 -0
  52. gobby/cli/sessions.py +622 -0
  53. gobby/cli/tasks/__init__.py +30 -0
  54. gobby/cli/tasks/_utils.py +658 -0
  55. gobby/cli/tasks/ai.py +1025 -0
  56. gobby/cli/tasks/commits.py +169 -0
  57. gobby/cli/tasks/crud.py +685 -0
  58. gobby/cli/tasks/deps.py +135 -0
  59. gobby/cli/tasks/labels.py +63 -0
  60. gobby/cli/tasks/main.py +273 -0
  61. gobby/cli/tasks/search.py +178 -0
  62. gobby/cli/tui.py +34 -0
  63. gobby/cli/utils.py +513 -0
  64. gobby/cli/workflows.py +927 -0
  65. gobby/cli/worktrees.py +481 -0
  66. gobby/config/__init__.py +129 -0
  67. gobby/config/app.py +551 -0
  68. gobby/config/extensions.py +167 -0
  69. gobby/config/features.py +472 -0
  70. gobby/config/llm_providers.py +98 -0
  71. gobby/config/logging.py +66 -0
  72. gobby/config/mcp.py +346 -0
  73. gobby/config/persistence.py +247 -0
  74. gobby/config/servers.py +141 -0
  75. gobby/config/sessions.py +250 -0
  76. gobby/config/tasks.py +784 -0
  77. gobby/hooks/__init__.py +104 -0
  78. gobby/hooks/artifact_capture.py +213 -0
  79. gobby/hooks/broadcaster.py +243 -0
  80. gobby/hooks/event_handlers.py +723 -0
  81. gobby/hooks/events.py +218 -0
  82. gobby/hooks/git.py +169 -0
  83. gobby/hooks/health_monitor.py +171 -0
  84. gobby/hooks/hook_manager.py +856 -0
  85. gobby/hooks/hook_types.py +575 -0
  86. gobby/hooks/plugins.py +813 -0
  87. gobby/hooks/session_coordinator.py +396 -0
  88. gobby/hooks/verification_runner.py +268 -0
  89. gobby/hooks/webhooks.py +339 -0
  90. gobby/install/claude/commands/gobby/bug.md +51 -0
  91. gobby/install/claude/commands/gobby/chore.md +51 -0
  92. gobby/install/claude/commands/gobby/epic.md +52 -0
  93. gobby/install/claude/commands/gobby/eval.md +235 -0
  94. gobby/install/claude/commands/gobby/feat.md +49 -0
  95. gobby/install/claude/commands/gobby/nit.md +52 -0
  96. gobby/install/claude/commands/gobby/ref.md +52 -0
  97. gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
  98. gobby/install/claude/hooks/hook_dispatcher.py +364 -0
  99. gobby/install/claude/hooks/validate_settings.py +102 -0
  100. gobby/install/claude/hooks-template.json +118 -0
  101. gobby/install/codex/hooks/hook_dispatcher.py +153 -0
  102. gobby/install/codex/prompts/forget.md +7 -0
  103. gobby/install/codex/prompts/memories.md +7 -0
  104. gobby/install/codex/prompts/recall.md +7 -0
  105. gobby/install/codex/prompts/remember.md +13 -0
  106. gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
  107. gobby/install/gemini/hooks-template.json +138 -0
  108. gobby/install/shared/plugins/code_guardian.py +456 -0
  109. gobby/install/shared/plugins/example_notify.py +331 -0
  110. gobby/integrations/__init__.py +10 -0
  111. gobby/integrations/github.py +145 -0
  112. gobby/integrations/linear.py +145 -0
  113. gobby/llm/__init__.py +40 -0
  114. gobby/llm/base.py +120 -0
  115. gobby/llm/claude.py +578 -0
  116. gobby/llm/claude_executor.py +503 -0
  117. gobby/llm/codex.py +322 -0
  118. gobby/llm/codex_executor.py +513 -0
  119. gobby/llm/executor.py +316 -0
  120. gobby/llm/factory.py +34 -0
  121. gobby/llm/gemini.py +258 -0
  122. gobby/llm/gemini_executor.py +339 -0
  123. gobby/llm/litellm.py +287 -0
  124. gobby/llm/litellm_executor.py +303 -0
  125. gobby/llm/resolver.py +499 -0
  126. gobby/llm/service.py +236 -0
  127. gobby/mcp_proxy/__init__.py +29 -0
  128. gobby/mcp_proxy/actions.py +175 -0
  129. gobby/mcp_proxy/daemon_control.py +198 -0
  130. gobby/mcp_proxy/importer.py +436 -0
  131. gobby/mcp_proxy/lazy.py +325 -0
  132. gobby/mcp_proxy/manager.py +798 -0
  133. gobby/mcp_proxy/metrics.py +609 -0
  134. gobby/mcp_proxy/models.py +139 -0
  135. gobby/mcp_proxy/registries.py +215 -0
  136. gobby/mcp_proxy/schema_hash.py +381 -0
  137. gobby/mcp_proxy/semantic_search.py +706 -0
  138. gobby/mcp_proxy/server.py +549 -0
  139. gobby/mcp_proxy/services/__init__.py +0 -0
  140. gobby/mcp_proxy/services/fallback.py +306 -0
  141. gobby/mcp_proxy/services/recommendation.py +224 -0
  142. gobby/mcp_proxy/services/server_mgmt.py +214 -0
  143. gobby/mcp_proxy/services/system.py +72 -0
  144. gobby/mcp_proxy/services/tool_filter.py +231 -0
  145. gobby/mcp_proxy/services/tool_proxy.py +309 -0
  146. gobby/mcp_proxy/stdio.py +565 -0
  147. gobby/mcp_proxy/tools/__init__.py +27 -0
  148. gobby/mcp_proxy/tools/agents.py +1103 -0
  149. gobby/mcp_proxy/tools/artifacts.py +207 -0
  150. gobby/mcp_proxy/tools/hub.py +335 -0
  151. gobby/mcp_proxy/tools/internal.py +337 -0
  152. gobby/mcp_proxy/tools/memory.py +543 -0
  153. gobby/mcp_proxy/tools/merge.py +422 -0
  154. gobby/mcp_proxy/tools/metrics.py +283 -0
  155. gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
  156. gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
  157. gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
  158. gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
  159. gobby/mcp_proxy/tools/orchestration/review.py +736 -0
  160. gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
  161. gobby/mcp_proxy/tools/session_messages.py +1056 -0
  162. gobby/mcp_proxy/tools/task_dependencies.py +219 -0
  163. gobby/mcp_proxy/tools/task_expansion.py +591 -0
  164. gobby/mcp_proxy/tools/task_github.py +393 -0
  165. gobby/mcp_proxy/tools/task_linear.py +379 -0
  166. gobby/mcp_proxy/tools/task_orchestration.py +77 -0
  167. gobby/mcp_proxy/tools/task_readiness.py +522 -0
  168. gobby/mcp_proxy/tools/task_sync.py +351 -0
  169. gobby/mcp_proxy/tools/task_validation.py +843 -0
  170. gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
  171. gobby/mcp_proxy/tools/tasks/_context.py +112 -0
  172. gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
  173. gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
  174. gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
  175. gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
  176. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
  177. gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
  178. gobby/mcp_proxy/tools/tasks/_search.py +215 -0
  179. gobby/mcp_proxy/tools/tasks/_session.py +125 -0
  180. gobby/mcp_proxy/tools/workflows.py +973 -0
  181. gobby/mcp_proxy/tools/worktrees.py +1264 -0
  182. gobby/mcp_proxy/transports/__init__.py +0 -0
  183. gobby/mcp_proxy/transports/base.py +95 -0
  184. gobby/mcp_proxy/transports/factory.py +44 -0
  185. gobby/mcp_proxy/transports/http.py +139 -0
  186. gobby/mcp_proxy/transports/stdio.py +213 -0
  187. gobby/mcp_proxy/transports/websocket.py +136 -0
  188. gobby/memory/backends/__init__.py +116 -0
  189. gobby/memory/backends/mem0.py +408 -0
  190. gobby/memory/backends/memu.py +485 -0
  191. gobby/memory/backends/null.py +111 -0
  192. gobby/memory/backends/openmemory.py +537 -0
  193. gobby/memory/backends/sqlite.py +304 -0
  194. gobby/memory/context.py +87 -0
  195. gobby/memory/manager.py +1001 -0
  196. gobby/memory/protocol.py +451 -0
  197. gobby/memory/search/__init__.py +66 -0
  198. gobby/memory/search/text.py +127 -0
  199. gobby/memory/viz.py +258 -0
  200. gobby/prompts/__init__.py +13 -0
  201. gobby/prompts/defaults/expansion/system.md +119 -0
  202. gobby/prompts/defaults/expansion/user.md +48 -0
  203. gobby/prompts/defaults/external_validation/agent.md +72 -0
  204. gobby/prompts/defaults/external_validation/external.md +63 -0
  205. gobby/prompts/defaults/external_validation/spawn.md +83 -0
  206. gobby/prompts/defaults/external_validation/system.md +6 -0
  207. gobby/prompts/defaults/features/import_mcp.md +22 -0
  208. gobby/prompts/defaults/features/import_mcp_github.md +17 -0
  209. gobby/prompts/defaults/features/import_mcp_search.md +16 -0
  210. gobby/prompts/defaults/features/recommend_tools.md +32 -0
  211. gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
  212. gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
  213. gobby/prompts/defaults/features/server_description.md +20 -0
  214. gobby/prompts/defaults/features/server_description_system.md +6 -0
  215. gobby/prompts/defaults/features/task_description.md +31 -0
  216. gobby/prompts/defaults/features/task_description_system.md +6 -0
  217. gobby/prompts/defaults/features/tool_summary.md +17 -0
  218. gobby/prompts/defaults/features/tool_summary_system.md +6 -0
  219. gobby/prompts/defaults/research/step.md +58 -0
  220. gobby/prompts/defaults/validation/criteria.md +47 -0
  221. gobby/prompts/defaults/validation/validate.md +38 -0
  222. gobby/prompts/loader.py +346 -0
  223. gobby/prompts/models.py +113 -0
  224. gobby/py.typed +0 -0
  225. gobby/runner.py +488 -0
  226. gobby/search/__init__.py +23 -0
  227. gobby/search/protocol.py +104 -0
  228. gobby/search/tfidf.py +232 -0
  229. gobby/servers/__init__.py +7 -0
  230. gobby/servers/http.py +636 -0
  231. gobby/servers/models.py +31 -0
  232. gobby/servers/routes/__init__.py +23 -0
  233. gobby/servers/routes/admin.py +416 -0
  234. gobby/servers/routes/dependencies.py +118 -0
  235. gobby/servers/routes/mcp/__init__.py +24 -0
  236. gobby/servers/routes/mcp/hooks.py +135 -0
  237. gobby/servers/routes/mcp/plugins.py +121 -0
  238. gobby/servers/routes/mcp/tools.py +1337 -0
  239. gobby/servers/routes/mcp/webhooks.py +159 -0
  240. gobby/servers/routes/sessions.py +582 -0
  241. gobby/servers/websocket.py +766 -0
  242. gobby/sessions/__init__.py +13 -0
  243. gobby/sessions/analyzer.py +322 -0
  244. gobby/sessions/lifecycle.py +240 -0
  245. gobby/sessions/manager.py +563 -0
  246. gobby/sessions/processor.py +225 -0
  247. gobby/sessions/summary.py +532 -0
  248. gobby/sessions/transcripts/__init__.py +41 -0
  249. gobby/sessions/transcripts/base.py +125 -0
  250. gobby/sessions/transcripts/claude.py +386 -0
  251. gobby/sessions/transcripts/codex.py +143 -0
  252. gobby/sessions/transcripts/gemini.py +195 -0
  253. gobby/storage/__init__.py +21 -0
  254. gobby/storage/agents.py +409 -0
  255. gobby/storage/artifact_classifier.py +341 -0
  256. gobby/storage/artifacts.py +285 -0
  257. gobby/storage/compaction.py +67 -0
  258. gobby/storage/database.py +357 -0
  259. gobby/storage/inter_session_messages.py +194 -0
  260. gobby/storage/mcp.py +680 -0
  261. gobby/storage/memories.py +562 -0
  262. gobby/storage/merge_resolutions.py +550 -0
  263. gobby/storage/migrations.py +860 -0
  264. gobby/storage/migrations_legacy.py +1359 -0
  265. gobby/storage/projects.py +166 -0
  266. gobby/storage/session_messages.py +251 -0
  267. gobby/storage/session_tasks.py +97 -0
  268. gobby/storage/sessions.py +817 -0
  269. gobby/storage/task_dependencies.py +223 -0
  270. gobby/storage/tasks/__init__.py +42 -0
  271. gobby/storage/tasks/_aggregates.py +180 -0
  272. gobby/storage/tasks/_crud.py +449 -0
  273. gobby/storage/tasks/_id.py +104 -0
  274. gobby/storage/tasks/_lifecycle.py +311 -0
  275. gobby/storage/tasks/_manager.py +889 -0
  276. gobby/storage/tasks/_models.py +300 -0
  277. gobby/storage/tasks/_ordering.py +119 -0
  278. gobby/storage/tasks/_path_cache.py +110 -0
  279. gobby/storage/tasks/_queries.py +343 -0
  280. gobby/storage/tasks/_search.py +143 -0
  281. gobby/storage/workflow_audit.py +393 -0
  282. gobby/storage/worktrees.py +547 -0
  283. gobby/sync/__init__.py +29 -0
  284. gobby/sync/github.py +333 -0
  285. gobby/sync/linear.py +304 -0
  286. gobby/sync/memories.py +284 -0
  287. gobby/sync/tasks.py +641 -0
  288. gobby/tasks/__init__.py +8 -0
  289. gobby/tasks/build_verification.py +193 -0
  290. gobby/tasks/commits.py +633 -0
  291. gobby/tasks/context.py +747 -0
  292. gobby/tasks/criteria.py +342 -0
  293. gobby/tasks/enhanced_validator.py +226 -0
  294. gobby/tasks/escalation.py +263 -0
  295. gobby/tasks/expansion.py +626 -0
  296. gobby/tasks/external_validator.py +764 -0
  297. gobby/tasks/issue_extraction.py +171 -0
  298. gobby/tasks/prompts/expand.py +327 -0
  299. gobby/tasks/research.py +421 -0
  300. gobby/tasks/tdd.py +352 -0
  301. gobby/tasks/tree_builder.py +263 -0
  302. gobby/tasks/validation.py +712 -0
  303. gobby/tasks/validation_history.py +357 -0
  304. gobby/tasks/validation_models.py +89 -0
  305. gobby/tools/__init__.py +0 -0
  306. gobby/tools/summarizer.py +170 -0
  307. gobby/tui/__init__.py +5 -0
  308. gobby/tui/api_client.py +281 -0
  309. gobby/tui/app.py +327 -0
  310. gobby/tui/screens/__init__.py +25 -0
  311. gobby/tui/screens/agents.py +333 -0
  312. gobby/tui/screens/chat.py +450 -0
  313. gobby/tui/screens/dashboard.py +377 -0
  314. gobby/tui/screens/memory.py +305 -0
  315. gobby/tui/screens/metrics.py +231 -0
  316. gobby/tui/screens/orchestrator.py +904 -0
  317. gobby/tui/screens/sessions.py +412 -0
  318. gobby/tui/screens/tasks.py +442 -0
  319. gobby/tui/screens/workflows.py +289 -0
  320. gobby/tui/screens/worktrees.py +174 -0
  321. gobby/tui/widgets/__init__.py +21 -0
  322. gobby/tui/widgets/chat.py +210 -0
  323. gobby/tui/widgets/conductor.py +104 -0
  324. gobby/tui/widgets/menu.py +132 -0
  325. gobby/tui/widgets/message_panel.py +160 -0
  326. gobby/tui/widgets/review_gate.py +224 -0
  327. gobby/tui/widgets/task_tree.py +99 -0
  328. gobby/tui/widgets/token_budget.py +166 -0
  329. gobby/tui/ws_client.py +258 -0
  330. gobby/utils/__init__.py +3 -0
  331. gobby/utils/daemon_client.py +235 -0
  332. gobby/utils/git.py +222 -0
  333. gobby/utils/id.py +38 -0
  334. gobby/utils/json_helpers.py +161 -0
  335. gobby/utils/logging.py +376 -0
  336. gobby/utils/machine_id.py +135 -0
  337. gobby/utils/metrics.py +589 -0
  338. gobby/utils/project_context.py +182 -0
  339. gobby/utils/project_init.py +263 -0
  340. gobby/utils/status.py +256 -0
  341. gobby/utils/validation.py +80 -0
  342. gobby/utils/version.py +23 -0
  343. gobby/workflows/__init__.py +4 -0
  344. gobby/workflows/actions.py +1310 -0
  345. gobby/workflows/approval_flow.py +138 -0
  346. gobby/workflows/artifact_actions.py +103 -0
  347. gobby/workflows/audit_helpers.py +110 -0
  348. gobby/workflows/autonomous_actions.py +286 -0
  349. gobby/workflows/context_actions.py +394 -0
  350. gobby/workflows/definitions.py +130 -0
  351. gobby/workflows/detection_helpers.py +208 -0
  352. gobby/workflows/engine.py +485 -0
  353. gobby/workflows/evaluator.py +669 -0
  354. gobby/workflows/git_utils.py +96 -0
  355. gobby/workflows/hooks.py +169 -0
  356. gobby/workflows/lifecycle_evaluator.py +613 -0
  357. gobby/workflows/llm_actions.py +70 -0
  358. gobby/workflows/loader.py +333 -0
  359. gobby/workflows/mcp_actions.py +60 -0
  360. gobby/workflows/memory_actions.py +272 -0
  361. gobby/workflows/premature_stop.py +164 -0
  362. gobby/workflows/session_actions.py +139 -0
  363. gobby/workflows/state_actions.py +123 -0
  364. gobby/workflows/state_manager.py +104 -0
  365. gobby/workflows/stop_signal_actions.py +163 -0
  366. gobby/workflows/summary_actions.py +344 -0
  367. gobby/workflows/task_actions.py +249 -0
  368. gobby/workflows/task_enforcement_actions.py +901 -0
  369. gobby/workflows/templates.py +52 -0
  370. gobby/workflows/todo_actions.py +84 -0
  371. gobby/workflows/webhook.py +223 -0
  372. gobby/workflows/webhook_executor.py +399 -0
  373. gobby/worktrees/__init__.py +5 -0
  374. gobby/worktrees/git.py +690 -0
  375. gobby/worktrees/merge/__init__.py +20 -0
  376. gobby/worktrees/merge/conflict_parser.py +177 -0
  377. gobby/worktrees/merge/resolver.py +485 -0
  378. gobby-0.2.5.dist-info/METADATA +351 -0
  379. gobby-0.2.5.dist-info/RECORD +383 -0
  380. gobby-0.2.5.dist-info/WHEEL +5 -0
  381. gobby-0.2.5.dist-info/entry_points.txt +2 -0
  382. gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
  383. gobby-0.2.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,333 @@
1
+ """Agents screen with running agents and spawn controls."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import Any
7
+
8
+ from textual.app import ComposeResult
9
+ from textual.containers import Container, Horizontal, Vertical
10
+ from textual.reactive import reactive
11
+ from textual.widget import Widget
12
+ from textual.widgets import (
13
+ Button,
14
+ DataTable,
15
+ LoadingIndicator,
16
+ Select,
17
+ Static,
18
+ TextArea,
19
+ )
20
+
21
+ from gobby.tui.api_client import GobbyAPIClient
22
+ from gobby.tui.ws_client import GobbyWebSocketClient
23
+
24
+
25
+ class SpawnAgentDialog(Widget):
26
+ """Dialog for spawning a new agent."""
27
+
28
+ DEFAULT_CSS = """
29
+ SpawnAgentDialog {
30
+ width: 100%;
31
+ height: auto;
32
+ padding: 1;
33
+ border: round #7c3aed;
34
+ background: #1e1e2e;
35
+ }
36
+
37
+ SpawnAgentDialog .dialog-title {
38
+ text-style: bold;
39
+ color: #a78bfa;
40
+ padding-bottom: 1;
41
+ }
42
+
43
+ SpawnAgentDialog .form-row {
44
+ height: auto;
45
+ margin-bottom: 1;
46
+ }
47
+
48
+ SpawnAgentDialog .form-label {
49
+ color: #a6adc8;
50
+ margin-bottom: 0;
51
+ }
52
+
53
+ SpawnAgentDialog #prompt-input {
54
+ height: 5;
55
+ }
56
+
57
+ SpawnAgentDialog .button-row {
58
+ layout: horizontal;
59
+ height: 3;
60
+ margin-top: 1;
61
+ }
62
+
63
+ SpawnAgentDialog .button-row Button {
64
+ margin-right: 1;
65
+ }
66
+ """
67
+
68
+ def compose(self) -> ComposeResult:
69
+ yield Static("🚀 Spawn New Agent", classes="dialog-title")
70
+
71
+ with Vertical(classes="form-row"):
72
+ yield Static("Prompt:", classes="form-label")
73
+ yield TextArea(id="prompt-input")
74
+
75
+ with Horizontal(classes="form-row"):
76
+ with Vertical():
77
+ yield Static("Mode:", classes="form-label")
78
+ yield Select(
79
+ [
80
+ (label, value)
81
+ for label, value in [
82
+ ("Terminal", "terminal"),
83
+ ("Embedded", "embedded"),
84
+ ("Headless", "headless"),
85
+ ]
86
+ ],
87
+ value="terminal",
88
+ id="mode-select",
89
+ )
90
+ with Vertical():
91
+ yield Static("Workflow:", classes="form-label")
92
+ yield Select(
93
+ [
94
+ (label, value)
95
+ for label, value in [
96
+ ("None", ""),
97
+ ("Plan-Execute", "plan-execute"),
98
+ ("Test-Driven", "test-driven"),
99
+ ("Auto-Task", "auto-task"),
100
+ ]
101
+ ],
102
+ value="",
103
+ id="workflow-select",
104
+ )
105
+
106
+ with Horizontal(classes="button-row"):
107
+ yield Button("Spawn", variant="primary", id="btn-spawn")
108
+ yield Button("Cancel", id="btn-cancel-spawn")
109
+
110
+ def get_values(self) -> dict[str, Any]:
111
+ """Get the form values."""
112
+ prompt = self.query_one("#prompt-input", TextArea).text
113
+ mode = str(self.query_one("#mode-select", Select).value)
114
+ workflow = str(self.query_one("#workflow-select", Select).value)
115
+ return {"prompt": prompt, "mode": mode, "workflow": workflow or None}
116
+
117
+ def clear(self) -> None:
118
+ """Clear the form."""
119
+ self.query_one("#prompt-input", TextArea).clear()
120
+
121
+
122
+ class AgentsScreen(Widget):
123
+ """Agents screen showing running agents and spawn controls."""
124
+
125
+ DEFAULT_CSS = """
126
+ AgentsScreen {
127
+ width: 1fr;
128
+ height: 1fr;
129
+ }
130
+
131
+ AgentsScreen .screen-header {
132
+ height: auto;
133
+ padding: 1;
134
+ background: #313244;
135
+ }
136
+
137
+ AgentsScreen .header-row {
138
+ layout: horizontal;
139
+ }
140
+
141
+ AgentsScreen .panel-title {
142
+ text-style: bold;
143
+ color: #a78bfa;
144
+ width: 1fr;
145
+ }
146
+
147
+ AgentsScreen #agents-table {
148
+ height: 1fr;
149
+ }
150
+
151
+ AgentsScreen #spawn-dialog {
152
+ display: none;
153
+ margin: 1;
154
+ }
155
+
156
+ AgentsScreen #spawn-dialog.--visible {
157
+ display: block;
158
+ }
159
+
160
+ AgentsScreen .loading-container {
161
+ width: 1fr;
162
+ height: 1fr;
163
+ content-align: center middle;
164
+ }
165
+
166
+ AgentsScreen .empty-state {
167
+ content-align: center middle;
168
+ height: 1fr;
169
+ color: #a6adc8;
170
+ }
171
+ """
172
+
173
+ loading = reactive(True)
174
+ agents: reactive[list[dict[str, Any]]] = reactive(list)
175
+ show_spawn_dialog = reactive(False)
176
+ selected_agent_id: reactive[str | None] = reactive(None)
177
+
178
+ def __init__(
179
+ self,
180
+ api_client: GobbyAPIClient,
181
+ ws_client: GobbyWebSocketClient,
182
+ **kwargs: Any,
183
+ ) -> None:
184
+ super().__init__(**kwargs)
185
+ self.api_client = api_client
186
+ self.ws_client = ws_client
187
+
188
+ def compose(self) -> ComposeResult:
189
+ with Vertical(classes="screen-header"):
190
+ with Horizontal(classes="header-row"):
191
+ yield Static("🤖 Agents", classes="panel-title")
192
+ yield Button("+ Spawn Agent", variant="primary", id="btn-show-spawn")
193
+ yield Button("Cancel Selected", id="btn-cancel-agent")
194
+ yield Button("Refresh", id="btn-refresh")
195
+
196
+ yield SpawnAgentDialog(id="spawn-dialog")
197
+
198
+ if self.loading:
199
+ with Container(classes="loading-container"):
200
+ yield LoadingIndicator()
201
+ else:
202
+ yield DataTable(id="agents-table")
203
+
204
+ async def on_mount(self) -> None:
205
+ """Load data when mounted."""
206
+ await self.refresh_data()
207
+
208
+ async def refresh_data(self) -> None:
209
+ """Refresh agent list."""
210
+ try:
211
+ async with GobbyAPIClient(self.api_client.base_url) as client:
212
+ agents = await client.list_agents()
213
+ self.agents = agents
214
+ except Exception as e:
215
+ self.notify(f"Failed to load agents: {e}", severity="error")
216
+ finally:
217
+ self.loading = False
218
+ await self._setup_table()
219
+
220
+ async def _setup_table(self) -> None:
221
+ """Set up and populate the agents table."""
222
+ try:
223
+ table = self.query_one("#agents-table", DataTable)
224
+ table.clear(columns=True)
225
+ table.add_columns("ID", "Status", "Mode", "Prompt", "Duration")
226
+ table.cursor_type = "row"
227
+
228
+ for agent in self.agents:
229
+ run_id = agent.get("run_id", "")[:12]
230
+ status = agent.get("status", "unknown")
231
+ mode = agent.get("mode", "?")
232
+ prompt_val = agent.get("prompt") or ""
233
+ prompt = prompt_val[:40] + "..." if len(prompt_val) > 40 else prompt_val
234
+
235
+ # Calculate duration
236
+ started = agent.get("started_at", "")
237
+ if started and status == "running":
238
+ try:
239
+ started_dt = datetime.fromisoformat(started.replace("Z", "+00:00"))
240
+ duration = datetime.now(started_dt.tzinfo) - started_dt
241
+ duration_str = f"{duration.seconds // 60}m"
242
+ except Exception:
243
+ duration_str = "?"
244
+ else:
245
+ duration_str = "-"
246
+
247
+ table.add_row(run_id, status, mode, prompt, duration_str, key=agent.get("run_id"))
248
+
249
+ except Exception:
250
+ pass # nosec B110 - TUI update failure is non-critical
251
+
252
+ def watch_show_spawn_dialog(self, show: bool) -> None:
253
+ """Toggle spawn dialog visibility."""
254
+ try:
255
+ dialog = self.query_one("#spawn-dialog", SpawnAgentDialog)
256
+ dialog.set_class(show, "--visible")
257
+ except Exception:
258
+ pass # nosec B110 - widget may not be mounted yet
259
+
260
+ def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
261
+ """Handle agent selection."""
262
+ self.selected_agent_id = str(event.row_key.value) if event.row_key else None
263
+
264
+ async def on_button_pressed(self, event: Button.Pressed) -> None:
265
+ """Handle button presses."""
266
+ button_id = event.button.id
267
+
268
+ if button_id == "btn-show-spawn":
269
+ self.show_spawn_dialog = True
270
+
271
+ elif button_id == "btn-cancel-spawn":
272
+ self.show_spawn_dialog = False
273
+ try:
274
+ dialog = self.query_one("#spawn-dialog", SpawnAgentDialog)
275
+ dialog.clear()
276
+ except Exception:
277
+ pass # nosec B110 - widget may not be mounted yet
278
+
279
+ elif button_id == "btn-spawn":
280
+ await self._spawn_agent()
281
+
282
+ elif button_id == "btn-cancel-agent":
283
+ await self._cancel_agent()
284
+
285
+ elif button_id == "btn-refresh":
286
+ self.loading = True
287
+ await self.refresh_data()
288
+
289
+ async def _spawn_agent(self) -> None:
290
+ """Spawn a new agent."""
291
+ try:
292
+ dialog = self.query_one("#spawn-dialog", SpawnAgentDialog)
293
+ values = dialog.get_values()
294
+
295
+ if not values.get("prompt"):
296
+ self.notify("Prompt is required", severity="error")
297
+ return
298
+
299
+ async with GobbyAPIClient(self.api_client.base_url) as client:
300
+ result = await client.start_agent(
301
+ prompt=values["prompt"],
302
+ mode=values["mode"],
303
+ workflow=values.get("workflow"),
304
+ )
305
+ self.notify(f"Agent spawned: {result.get('run_id', 'unknown')[:12]}")
306
+
307
+ self.show_spawn_dialog = False
308
+ dialog.clear()
309
+ await self.refresh_data()
310
+
311
+ except Exception as e:
312
+ self.notify(f"Failed to spawn agent: {e}", severity="error")
313
+
314
+ async def _cancel_agent(self) -> None:
315
+ """Cancel the selected agent."""
316
+ if not self.selected_agent_id:
317
+ self.notify("No agent selected", severity="warning")
318
+ return
319
+
320
+ try:
321
+ async with GobbyAPIClient(self.api_client.base_url) as client:
322
+ await client.cancel_agent(self.selected_agent_id)
323
+ self.notify(f"Agent cancelled: {self.selected_agent_id[:12]}")
324
+
325
+ await self.refresh_data()
326
+
327
+ except Exception as e:
328
+ self.notify(f"Failed to cancel agent: {e}", severity="error")
329
+
330
+ def on_ws_event(self, event_type: str, data: dict[str, Any]) -> None:
331
+ """Handle WebSocket events."""
332
+ if event_type == "agent_event":
333
+ self.run_worker(self.refresh_data(), name="refresh_data", exclusive=True)