codex-autorunner 0.1.2__py3-none-any.whl → 1.1.0__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 (276) hide show
  1. codex_autorunner/__init__.py +12 -1
  2. codex_autorunner/__main__.py +4 -0
  3. codex_autorunner/agents/codex/harness.py +1 -1
  4. codex_autorunner/agents/opencode/client.py +68 -35
  5. codex_autorunner/agents/opencode/constants.py +3 -0
  6. codex_autorunner/agents/opencode/harness.py +6 -1
  7. codex_autorunner/agents/opencode/logging.py +21 -5
  8. codex_autorunner/agents/opencode/run_prompt.py +1 -0
  9. codex_autorunner/agents/opencode/runtime.py +176 -47
  10. codex_autorunner/agents/opencode/supervisor.py +36 -48
  11. codex_autorunner/agents/registry.py +155 -8
  12. codex_autorunner/api.py +25 -0
  13. codex_autorunner/bootstrap.py +22 -37
  14. codex_autorunner/cli.py +5 -1156
  15. codex_autorunner/codex_cli.py +20 -84
  16. codex_autorunner/core/__init__.py +4 -0
  17. codex_autorunner/core/about_car.py +49 -32
  18. codex_autorunner/core/adapter_utils.py +21 -0
  19. codex_autorunner/core/app_server_ids.py +59 -0
  20. codex_autorunner/core/app_server_logging.py +7 -3
  21. codex_autorunner/core/app_server_prompts.py +27 -260
  22. codex_autorunner/core/app_server_threads.py +26 -28
  23. codex_autorunner/core/app_server_utils.py +165 -0
  24. codex_autorunner/core/archive.py +349 -0
  25. codex_autorunner/core/codex_runner.py +12 -2
  26. codex_autorunner/core/config.py +587 -103
  27. codex_autorunner/core/docs.py +10 -2
  28. codex_autorunner/core/drafts.py +136 -0
  29. codex_autorunner/core/engine.py +1531 -866
  30. codex_autorunner/core/exceptions.py +4 -0
  31. codex_autorunner/core/flows/__init__.py +25 -0
  32. codex_autorunner/core/flows/controller.py +202 -0
  33. codex_autorunner/core/flows/definition.py +82 -0
  34. codex_autorunner/core/flows/models.py +88 -0
  35. codex_autorunner/core/flows/reasons.py +52 -0
  36. codex_autorunner/core/flows/reconciler.py +131 -0
  37. codex_autorunner/core/flows/runtime.py +382 -0
  38. codex_autorunner/core/flows/store.py +568 -0
  39. codex_autorunner/core/flows/transition.py +138 -0
  40. codex_autorunner/core/flows/ux_helpers.py +257 -0
  41. codex_autorunner/core/flows/worker_process.py +242 -0
  42. codex_autorunner/core/git_utils.py +62 -0
  43. codex_autorunner/core/hub.py +136 -16
  44. codex_autorunner/core/locks.py +4 -0
  45. codex_autorunner/core/notifications.py +14 -2
  46. codex_autorunner/core/ports/__init__.py +28 -0
  47. codex_autorunner/core/ports/agent_backend.py +150 -0
  48. codex_autorunner/core/ports/backend_orchestrator.py +41 -0
  49. codex_autorunner/core/ports/run_event.py +91 -0
  50. codex_autorunner/core/prompt.py +15 -7
  51. codex_autorunner/core/redaction.py +29 -0
  52. codex_autorunner/core/review_context.py +5 -8
  53. codex_autorunner/core/run_index.py +6 -0
  54. codex_autorunner/core/runner_process.py +5 -2
  55. codex_autorunner/core/state.py +0 -88
  56. codex_autorunner/core/state_roots.py +57 -0
  57. codex_autorunner/core/supervisor_protocol.py +15 -0
  58. codex_autorunner/core/supervisor_utils.py +67 -0
  59. codex_autorunner/core/text_delta_coalescer.py +54 -0
  60. codex_autorunner/core/ticket_linter_cli.py +201 -0
  61. codex_autorunner/core/ticket_manager_cli.py +432 -0
  62. codex_autorunner/core/update.py +24 -16
  63. codex_autorunner/core/update_paths.py +28 -0
  64. codex_autorunner/core/update_runner.py +2 -0
  65. codex_autorunner/core/usage.py +164 -12
  66. codex_autorunner/core/utils.py +120 -11
  67. codex_autorunner/discovery.py +2 -4
  68. codex_autorunner/flows/review/__init__.py +17 -0
  69. codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
  70. codex_autorunner/flows/ticket_flow/__init__.py +3 -0
  71. codex_autorunner/flows/ticket_flow/definition.py +98 -0
  72. codex_autorunner/integrations/agents/__init__.py +17 -0
  73. codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
  74. codex_autorunner/integrations/agents/codex_adapter.py +90 -0
  75. codex_autorunner/integrations/agents/codex_backend.py +448 -0
  76. codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
  77. codex_autorunner/integrations/agents/opencode_backend.py +598 -0
  78. codex_autorunner/integrations/agents/runner.py +91 -0
  79. codex_autorunner/integrations/agents/wiring.py +271 -0
  80. codex_autorunner/integrations/app_server/client.py +583 -152
  81. codex_autorunner/integrations/app_server/env.py +2 -107
  82. codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
  83. codex_autorunner/integrations/app_server/supervisor.py +59 -33
  84. codex_autorunner/integrations/telegram/adapter.py +204 -165
  85. codex_autorunner/integrations/telegram/api_schemas.py +120 -0
  86. codex_autorunner/integrations/telegram/config.py +221 -0
  87. codex_autorunner/integrations/telegram/constants.py +17 -2
  88. codex_autorunner/integrations/telegram/dispatch.py +17 -0
  89. codex_autorunner/integrations/telegram/doctor.py +47 -0
  90. codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -4
  91. codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
  92. codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
  93. codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
  94. codex_autorunner/integrations/telegram/handlers/commands/flows.py +1364 -0
  95. codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
  96. codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
  97. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
  98. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +137 -478
  99. codex_autorunner/integrations/telegram/handlers/commands_spec.py +17 -4
  100. codex_autorunner/integrations/telegram/handlers/messages.py +121 -9
  101. codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
  102. codex_autorunner/integrations/telegram/helpers.py +111 -16
  103. codex_autorunner/integrations/telegram/outbox.py +208 -37
  104. codex_autorunner/integrations/telegram/progress_stream.py +3 -10
  105. codex_autorunner/integrations/telegram/service.py +221 -42
  106. codex_autorunner/integrations/telegram/state.py +100 -2
  107. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +611 -0
  108. codex_autorunner/integrations/telegram/transport.py +39 -4
  109. codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
  110. codex_autorunner/manifest.py +2 -0
  111. codex_autorunner/plugin_api.py +22 -0
  112. codex_autorunner/routes/__init__.py +37 -67
  113. codex_autorunner/routes/agents.py +2 -137
  114. codex_autorunner/routes/analytics.py +3 -0
  115. codex_autorunner/routes/app_server.py +2 -131
  116. codex_autorunner/routes/base.py +2 -624
  117. codex_autorunner/routes/file_chat.py +7 -0
  118. codex_autorunner/routes/flows.py +7 -0
  119. codex_autorunner/routes/messages.py +7 -0
  120. codex_autorunner/routes/repos.py +2 -196
  121. codex_autorunner/routes/review.py +2 -147
  122. codex_autorunner/routes/sessions.py +2 -175
  123. codex_autorunner/routes/settings.py +2 -168
  124. codex_autorunner/routes/shared.py +2 -275
  125. codex_autorunner/routes/system.py +4 -188
  126. codex_autorunner/routes/usage.py +3 -0
  127. codex_autorunner/routes/voice.py +2 -119
  128. codex_autorunner/routes/workspace.py +3 -0
  129. codex_autorunner/server.py +3 -2
  130. codex_autorunner/static/agentControls.js +41 -11
  131. codex_autorunner/static/agentEvents.js +248 -0
  132. codex_autorunner/static/app.js +35 -24
  133. codex_autorunner/static/archive.js +826 -0
  134. codex_autorunner/static/archiveApi.js +37 -0
  135. codex_autorunner/static/autoRefresh.js +36 -8
  136. codex_autorunner/static/bootstrap.js +1 -0
  137. codex_autorunner/static/bus.js +1 -0
  138. codex_autorunner/static/cache.js +1 -0
  139. codex_autorunner/static/constants.js +20 -4
  140. codex_autorunner/static/dashboard.js +344 -325
  141. codex_autorunner/static/diffRenderer.js +37 -0
  142. codex_autorunner/static/docChatCore.js +324 -0
  143. codex_autorunner/static/docChatStorage.js +65 -0
  144. codex_autorunner/static/docChatVoice.js +65 -0
  145. codex_autorunner/static/docEditor.js +133 -0
  146. codex_autorunner/static/env.js +1 -0
  147. codex_autorunner/static/eventSummarizer.js +166 -0
  148. codex_autorunner/static/fileChat.js +182 -0
  149. codex_autorunner/static/health.js +155 -0
  150. codex_autorunner/static/hub.js +126 -185
  151. codex_autorunner/static/index.html +839 -863
  152. codex_autorunner/static/liveUpdates.js +1 -0
  153. codex_autorunner/static/loader.js +1 -0
  154. codex_autorunner/static/messages.js +873 -0
  155. codex_autorunner/static/mobileCompact.js +2 -1
  156. codex_autorunner/static/preserve.js +17 -0
  157. codex_autorunner/static/settings.js +149 -217
  158. codex_autorunner/static/smartRefresh.js +52 -0
  159. codex_autorunner/static/styles.css +8850 -3876
  160. codex_autorunner/static/tabs.js +175 -11
  161. codex_autorunner/static/terminal.js +32 -0
  162. codex_autorunner/static/terminalManager.js +34 -59
  163. codex_autorunner/static/ticketChatActions.js +333 -0
  164. codex_autorunner/static/ticketChatEvents.js +16 -0
  165. codex_autorunner/static/ticketChatStorage.js +16 -0
  166. codex_autorunner/static/ticketChatStream.js +264 -0
  167. codex_autorunner/static/ticketEditor.js +844 -0
  168. codex_autorunner/static/ticketVoice.js +9 -0
  169. codex_autorunner/static/tickets.js +1988 -0
  170. codex_autorunner/static/utils.js +43 -3
  171. codex_autorunner/static/voice.js +1 -0
  172. codex_autorunner/static/workspace.js +765 -0
  173. codex_autorunner/static/workspaceApi.js +53 -0
  174. codex_autorunner/static/workspaceFileBrowser.js +504 -0
  175. codex_autorunner/surfaces/__init__.py +5 -0
  176. codex_autorunner/surfaces/cli/__init__.py +6 -0
  177. codex_autorunner/surfaces/cli/cli.py +1224 -0
  178. codex_autorunner/surfaces/cli/codex_cli.py +20 -0
  179. codex_autorunner/surfaces/telegram/__init__.py +3 -0
  180. codex_autorunner/surfaces/web/__init__.py +1 -0
  181. codex_autorunner/surfaces/web/app.py +2019 -0
  182. codex_autorunner/surfaces/web/hub_jobs.py +192 -0
  183. codex_autorunner/surfaces/web/middleware.py +587 -0
  184. codex_autorunner/surfaces/web/pty_session.py +370 -0
  185. codex_autorunner/surfaces/web/review.py +6 -0
  186. codex_autorunner/surfaces/web/routes/__init__.py +78 -0
  187. codex_autorunner/surfaces/web/routes/agents.py +138 -0
  188. codex_autorunner/surfaces/web/routes/analytics.py +277 -0
  189. codex_autorunner/surfaces/web/routes/app_server.py +132 -0
  190. codex_autorunner/surfaces/web/routes/archive.py +357 -0
  191. codex_autorunner/surfaces/web/routes/base.py +615 -0
  192. codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
  193. codex_autorunner/surfaces/web/routes/flows.py +1164 -0
  194. codex_autorunner/surfaces/web/routes/messages.py +459 -0
  195. codex_autorunner/surfaces/web/routes/repos.py +197 -0
  196. codex_autorunner/surfaces/web/routes/review.py +148 -0
  197. codex_autorunner/surfaces/web/routes/sessions.py +176 -0
  198. codex_autorunner/surfaces/web/routes/settings.py +169 -0
  199. codex_autorunner/surfaces/web/routes/shared.py +280 -0
  200. codex_autorunner/surfaces/web/routes/system.py +196 -0
  201. codex_autorunner/surfaces/web/routes/usage.py +89 -0
  202. codex_autorunner/surfaces/web/routes/voice.py +120 -0
  203. codex_autorunner/surfaces/web/routes/workspace.py +271 -0
  204. codex_autorunner/surfaces/web/runner_manager.py +25 -0
  205. codex_autorunner/surfaces/web/schemas.py +417 -0
  206. codex_autorunner/surfaces/web/static_assets.py +490 -0
  207. codex_autorunner/surfaces/web/static_refresh.py +86 -0
  208. codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
  209. codex_autorunner/tickets/__init__.py +27 -0
  210. codex_autorunner/tickets/agent_pool.py +399 -0
  211. codex_autorunner/tickets/files.py +89 -0
  212. codex_autorunner/tickets/frontmatter.py +55 -0
  213. codex_autorunner/tickets/lint.py +102 -0
  214. codex_autorunner/tickets/models.py +97 -0
  215. codex_autorunner/tickets/outbox.py +244 -0
  216. codex_autorunner/tickets/replies.py +179 -0
  217. codex_autorunner/tickets/runner.py +881 -0
  218. codex_autorunner/tickets/spec_ingest.py +77 -0
  219. codex_autorunner/web/__init__.py +5 -1
  220. codex_autorunner/web/app.py +2 -1771
  221. codex_autorunner/web/hub_jobs.py +2 -191
  222. codex_autorunner/web/middleware.py +2 -587
  223. codex_autorunner/web/pty_session.py +2 -369
  224. codex_autorunner/web/runner_manager.py +2 -24
  225. codex_autorunner/web/schemas.py +2 -396
  226. codex_autorunner/web/static_assets.py +4 -484
  227. codex_autorunner/web/static_refresh.py +2 -85
  228. codex_autorunner/web/terminal_sessions.py +2 -77
  229. codex_autorunner/workspace/__init__.py +40 -0
  230. codex_autorunner/workspace/paths.py +335 -0
  231. codex_autorunner-1.1.0.dist-info/METADATA +154 -0
  232. codex_autorunner-1.1.0.dist-info/RECORD +308 -0
  233. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +1 -1
  234. codex_autorunner/agents/execution/policy.py +0 -292
  235. codex_autorunner/agents/factory.py +0 -52
  236. codex_autorunner/agents/orchestrator.py +0 -358
  237. codex_autorunner/core/doc_chat.py +0 -1446
  238. codex_autorunner/core/snapshot.py +0 -580
  239. codex_autorunner/integrations/github/chatops.py +0 -268
  240. codex_autorunner/integrations/github/pr_flow.py +0 -1314
  241. codex_autorunner/routes/docs.py +0 -381
  242. codex_autorunner/routes/github.py +0 -327
  243. codex_autorunner/routes/runs.py +0 -250
  244. codex_autorunner/spec_ingest.py +0 -812
  245. codex_autorunner/static/docChatActions.js +0 -287
  246. codex_autorunner/static/docChatEvents.js +0 -300
  247. codex_autorunner/static/docChatRender.js +0 -205
  248. codex_autorunner/static/docChatStream.js +0 -361
  249. codex_autorunner/static/docs.js +0 -20
  250. codex_autorunner/static/docsClipboard.js +0 -69
  251. codex_autorunner/static/docsCrud.js +0 -257
  252. codex_autorunner/static/docsDocUpdates.js +0 -62
  253. codex_autorunner/static/docsDrafts.js +0 -16
  254. codex_autorunner/static/docsElements.js +0 -69
  255. codex_autorunner/static/docsInit.js +0 -285
  256. codex_autorunner/static/docsParse.js +0 -160
  257. codex_autorunner/static/docsSnapshot.js +0 -87
  258. codex_autorunner/static/docsSpecIngest.js +0 -263
  259. codex_autorunner/static/docsState.js +0 -127
  260. codex_autorunner/static/docsThreadRegistry.js +0 -44
  261. codex_autorunner/static/docsUi.js +0 -153
  262. codex_autorunner/static/docsVoice.js +0 -56
  263. codex_autorunner/static/github.js +0 -504
  264. codex_autorunner/static/logs.js +0 -678
  265. codex_autorunner/static/review.js +0 -157
  266. codex_autorunner/static/runs.js +0 -418
  267. codex_autorunner/static/snapshot.js +0 -124
  268. codex_autorunner/static/state.js +0 -94
  269. codex_autorunner/static/todoPreview.js +0 -27
  270. codex_autorunner/workspace.py +0 -16
  271. codex_autorunner-0.1.2.dist-info/METADATA +0 -249
  272. codex_autorunner-0.1.2.dist-info/RECORD +0 -222
  273. /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
  274. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
  275. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
  276. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
@@ -1,120 +1,3 @@
1
- """
2
- Voice transcription and configuration routes.
3
- """
1
+ """Backward-compatible voice routes."""
4
2
 
5
- import asyncio
6
- import logging
7
- from typing import Optional
8
-
9
- from fastapi import APIRouter, File, HTTPException, Request, UploadFile
10
-
11
- from ..voice import VoiceService, VoiceServiceError
12
-
13
- logger = logging.getLogger("codex_autorunner.routes.voice")
14
-
15
-
16
- def build_voice_routes() -> APIRouter:
17
- """Build routes for voice transcription and config."""
18
- router = APIRouter()
19
-
20
- @router.get("/api/voice/config")
21
- def get_voice_config(request: Request):
22
- voice_service: Optional[VoiceService] = request.app.state.voice_service
23
- voice_config = request.app.state.voice_config
24
- missing_reason = getattr(request.app.state, "voice_missing_reason", None)
25
- if missing_reason:
26
- return {
27
- "enabled": False,
28
- "provider": voice_config.provider,
29
- "latency_mode": voice_config.latency_mode,
30
- "chunk_ms": voice_config.chunk_ms,
31
- "sample_rate": voice_config.sample_rate,
32
- "warn_on_remote_api": voice_config.warn_on_remote_api,
33
- "has_api_key": False,
34
- "push_to_talk": {
35
- "max_ms": voice_config.push_to_talk.max_ms,
36
- "silence_auto_stop_ms": voice_config.push_to_talk.silence_auto_stop_ms,
37
- "min_hold_ms": voice_config.push_to_talk.min_hold_ms,
38
- },
39
- "missing_extra": missing_reason,
40
- }
41
- if voice_service is None:
42
- # Degrade gracefully: still return config to the UI even if service init failed.
43
- try:
44
- return VoiceService(
45
- voice_config, logger=request.app.state.logger
46
- ).config_payload()
47
- except (ValueError, TypeError, OSError) as exc:
48
- logger.debug("Failed to create VoiceService for config: %s", exc)
49
- return {
50
- "enabled": False,
51
- "provider": voice_config.provider,
52
- "latency_mode": voice_config.latency_mode,
53
- "chunk_ms": voice_config.chunk_ms,
54
- "sample_rate": voice_config.sample_rate,
55
- "warn_on_remote_api": voice_config.warn_on_remote_api,
56
- "has_api_key": False,
57
- "push_to_talk": {
58
- "max_ms": voice_config.push_to_talk.max_ms,
59
- "silence_auto_stop_ms": voice_config.push_to_talk.silence_auto_stop_ms,
60
- "min_hold_ms": voice_config.push_to_talk.min_hold_ms,
61
- },
62
- }
63
- return voice_service.config_payload()
64
-
65
- @router.post("/api/voice/transcribe")
66
- async def transcribe_voice(
67
- request: Request,
68
- file: Optional[UploadFile] = File(None),
69
- language: Optional[str] = None,
70
- ):
71
- voice_service: Optional[VoiceService] = request.app.state.voice_service
72
- voice_config = request.app.state.voice_config
73
- missing_reason = getattr(request.app.state, "voice_missing_reason", None)
74
- if missing_reason:
75
- raise HTTPException(status_code=503, detail=missing_reason)
76
- if not voice_service or not voice_config.enabled:
77
- raise HTTPException(status_code=400, detail="Voice is disabled")
78
-
79
- filename: Optional[str] = None
80
- content_type: Optional[str] = None
81
- if file is not None:
82
- filename = file.filename
83
- content_type = file.content_type
84
- try:
85
- audio_bytes = await file.read()
86
- except Exception as exc:
87
- raise HTTPException(
88
- status_code=400, detail="Unable to read audio upload"
89
- ) from exc
90
- else:
91
- audio_bytes = await request.body()
92
- try:
93
- result = await asyncio.to_thread(
94
- voice_service.transcribe,
95
- audio_bytes,
96
- client="web",
97
- user_agent=request.headers.get("user-agent"),
98
- language=language,
99
- filename=filename,
100
- content_type=content_type,
101
- )
102
- except VoiceServiceError as exc:
103
- if exc.reason == "unauthorized":
104
- status = 401
105
- elif exc.reason == "forbidden":
106
- status = 403
107
- elif exc.reason == "audio_too_large":
108
- status = 413
109
- elif exc.reason == "rate_limited":
110
- status = 429
111
- else:
112
- status = (
113
- 400
114
- if exc.reason in ("disabled", "empty_audio", "invalid_audio")
115
- else 502
116
- )
117
- raise HTTPException(status_code=status, detail=exc.detail) from exc
118
- return {"status": "ok", **result}
119
-
120
- return router
3
+ from ..surfaces.web.routes.voice import * # noqa: F401,F403
@@ -0,0 +1,3 @@
1
+ """Backward-compatible workspace routes."""
2
+
3
+ from ..surfaces.web.routes.workspace import * # noqa: F401,F403
@@ -1,8 +1,8 @@
1
1
  from importlib import resources
2
2
 
3
3
  from .core.engine import Engine, LockError, clear_stale_lock, doctor
4
- from .web.app import create_app, create_hub_app
5
- from .web.middleware import BasePathRouterMiddleware
4
+ from .surfaces.web.app import create_app, create_hub_app, create_repo_app
5
+ from .surfaces.web.middleware import BasePathRouterMiddleware
6
6
 
7
7
  __all__ = [
8
8
  "Engine",
@@ -11,6 +11,7 @@ __all__ = [
11
11
  "clear_stale_lock",
12
12
  "create_app",
13
13
  "create_hub_app",
14
+ "create_repo_app",
14
15
  "doctor",
15
16
  "resources",
16
17
  ]
@@ -1,4 +1,6 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
1
2
  import { api, flash } from "./utils.js";
3
+ import { createSmartRefresh } from "./smartRefresh.js";
2
4
  const STORAGE_KEYS = {
3
5
  selected: "car.agent.selected",
4
6
  model: (agent) => `car.agent.${agent}.model`,
@@ -14,6 +16,22 @@ let agentList = [...FALLBACK_AGENTS];
14
16
  let defaultAgent = "codex";
15
17
  const modelCatalogs = new Map();
16
18
  const modelCatalogPromises = new Map();
19
+ const agentControlsRefresh = createSmartRefresh({
20
+ getSignature: (payload) => {
21
+ const agentsSig = payload.agents
22
+ .map((agent) => `${agent.id}:${agent.name || ""}:${agent.version || ""}:${agent.protocol_version || ""}`)
23
+ .join("|");
24
+ const catalogSig = payload.catalog
25
+ ? `${payload.catalog.default_model || ""}:${payload.catalog.models
26
+ .map((model) => `${model.id}:${model.display_name || ""}:${model.supports_reasoning ? "1" : "0"}:${model.reasoning_options.join(",")}`)
27
+ .join("|")}`
28
+ : "none";
29
+ return `${agentsSig}::${payload.defaultAgent}::${catalogSig}`;
30
+ },
31
+ render: (payload) => {
32
+ renderAgentControls(payload);
33
+ },
34
+ });
17
35
  function safeGetStorage(key) {
18
36
  try {
19
37
  return localStorage.getItem(key);
@@ -241,7 +259,7 @@ function resolveSelectedReasoning(agent, model) {
241
259
  }
242
260
  return model.reasoning_options[0] || "";
243
261
  }
244
- async function refreshControls() {
262
+ async function loadAgentControlsPayload() {
245
263
  try {
246
264
  await loadAgents();
247
265
  }
@@ -250,11 +268,6 @@ async function refreshControls() {
250
268
  ensureFallbackAgents();
251
269
  }
252
270
  const selectedAgent = getSelectedAgent();
253
- // Always update agent options first (uses in-memory agentList)
254
- controls.forEach((control) => {
255
- ensureAgentOptions(control.agentSelect);
256
- });
257
- // Then try to load model catalog
258
271
  let catalog = modelCatalogs.get(selectedAgent);
259
272
  if (!catalog) {
260
273
  try {
@@ -265,6 +278,20 @@ async function refreshControls() {
265
278
  catalog = null;
266
279
  }
267
280
  }
281
+ return {
282
+ agents: [...agentList],
283
+ defaultAgent,
284
+ selectedAgent,
285
+ catalog: catalog || null,
286
+ };
287
+ }
288
+ function renderAgentControls(payload) {
289
+ const selectedAgent = payload.selectedAgent;
290
+ // Always update agent options first (uses in-memory agentList)
291
+ controls.forEach((control) => {
292
+ ensureAgentOptions(control.agentSelect);
293
+ });
294
+ const catalog = payload.catalog;
268
295
  // Update model and reasoning options
269
296
  controls.forEach((control) => {
270
297
  ensureModelOptions(control.modelSelect, catalog);
@@ -287,6 +314,9 @@ async function refreshControls() {
287
314
  }
288
315
  });
289
316
  }
317
+ export async function refreshAgentControls(request = {}) {
318
+ await agentControlsRefresh.refresh(loadAgentControlsPayload, request);
319
+ }
290
320
  async function handleAgentChange(nextAgent) {
291
321
  const previous = getSelectedAgent();
292
322
  setSelectedAgent(nextAgent);
@@ -297,17 +327,17 @@ async function handleAgentChange(nextAgent) {
297
327
  setSelectedAgent(previous);
298
328
  flash(`Failed to load ${getLabelText(nextAgent)} models; staying on ${getLabelText(previous)}.`, "error");
299
329
  }
300
- await refreshControls();
330
+ await refreshAgentControls({ force: true, reason: "manual" });
301
331
  }
302
332
  async function handleModelChange(nextModel) {
303
333
  const agent = getSelectedAgent();
304
334
  setSelectedModel(agent, nextModel);
305
- await refreshControls();
335
+ await refreshAgentControls({ force: true, reason: "manual" });
306
336
  }
307
337
  async function handleReasoningChange(nextReasoning) {
308
338
  const agent = getSelectedAgent();
309
339
  setSelectedReasoning(agent, nextReasoning);
310
- await refreshControls();
340
+ await refreshAgentControls({ force: true, reason: "manual" });
311
341
  }
312
342
  /**
313
343
  * @param {AgentControlConfig} [config]
@@ -342,10 +372,10 @@ export function initAgentControls(config = {}) {
342
372
  });
343
373
  }
344
374
  // Async refresh to load from API (will update if API returns different data)
345
- refreshControls().catch((err) => {
375
+ refreshAgentControls({ force: true, reason: "initial" }).catch((err) => {
346
376
  console.warn("Failed to refresh agent controls", err);
347
377
  });
348
378
  }
349
379
  export async function ensureAgentCatalog() {
350
- await refreshControls();
380
+ await refreshAgentControls({ force: true, reason: "manual" });
351
381
  }
@@ -0,0 +1,248 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
2
+ /**
3
+ * Shared parsing helpers for agent (app-server) events.
4
+ * Used by ticket chat and live agent output to render rich activity.
5
+ */
6
+ function extractCommand(item, params) {
7
+ const command = item?.command ?? params?.command;
8
+ if (Array.isArray(command)) {
9
+ return command
10
+ .map((part) => String(part))
11
+ .join(" ")
12
+ .trim();
13
+ }
14
+ if (typeof command === "string")
15
+ return command.trim();
16
+ return "";
17
+ }
18
+ function extractFiles(payload) {
19
+ const files = [];
20
+ const addEntry = (entry) => {
21
+ if (typeof entry === "string" && entry.trim()) {
22
+ files.push(entry.trim());
23
+ return;
24
+ }
25
+ if (entry && typeof entry === "object") {
26
+ const entryObj = entry;
27
+ const path = entryObj.path || entryObj.file || entryObj.name;
28
+ if (typeof path === "string" && path.trim()) {
29
+ files.push(path.trim());
30
+ }
31
+ }
32
+ };
33
+ if (!payload || typeof payload !== "object")
34
+ return files;
35
+ for (const key of ["files", "fileChanges", "paths"]) {
36
+ const value = payload[key];
37
+ if (Array.isArray(value)) {
38
+ value.forEach(addEntry);
39
+ }
40
+ }
41
+ for (const key of ["path", "file", "name"]) {
42
+ addEntry(payload[key]);
43
+ }
44
+ return files;
45
+ }
46
+ function extractErrorMessage(params) {
47
+ if (!params || typeof params !== "object")
48
+ return "";
49
+ const err = params.error;
50
+ if (err && typeof err === "object") {
51
+ const errObj = err;
52
+ const message = typeof errObj.message === "string" ? errObj.message : "";
53
+ const details = typeof errObj.additionalDetails === "string"
54
+ ? errObj.additionalDetails
55
+ : typeof errObj.details === "string"
56
+ ? errObj.details
57
+ : "";
58
+ if (message && details && message !== details) {
59
+ return `${message} (${details})`;
60
+ }
61
+ return message || details;
62
+ }
63
+ if (typeof err === "string")
64
+ return err;
65
+ if (typeof params.message === "string")
66
+ return params.message;
67
+ return "";
68
+ }
69
+ function hasMeaningfulText(summary, detail) {
70
+ return Boolean(summary.trim() || detail.trim());
71
+ }
72
+ function inferSignificance(kind, method) {
73
+ if (kind === "thinking")
74
+ return true;
75
+ if (kind === "error")
76
+ return true;
77
+ if (["tool", "command", "file", "output"].includes(kind))
78
+ return true;
79
+ if (method.includes("requestApproval"))
80
+ return true;
81
+ return false;
82
+ }
83
+ /**
84
+ * Extract output delta text from an event payload.
85
+ */
86
+ export function extractOutputDelta(payload) {
87
+ const message = payload && typeof payload === "object" ? payload.message || payload : payload;
88
+ if (!message || typeof message !== "object")
89
+ return "";
90
+ const method = String(message.method || "").toLowerCase();
91
+ if (!method.includes("outputdelta"))
92
+ return "";
93
+ const params = message.params || {};
94
+ if (typeof params.delta === "string")
95
+ return params.delta;
96
+ if (typeof params.text === "string")
97
+ return params.text;
98
+ if (typeof params.output === "string")
99
+ return params.output;
100
+ return "";
101
+ }
102
+ /**
103
+ * Parse an app-server event payload into a normalized AgentEvent plus merge hints.
104
+ */
105
+ export function parseAppServerEvent(payload) {
106
+ const message = payload && typeof payload === "object" ? payload.message || payload : payload;
107
+ if (!message || typeof message !== "object")
108
+ return null;
109
+ const messageObj = message;
110
+ const method = messageObj.method || "app-server";
111
+ const params = messageObj.params || {};
112
+ const item = params.item || {};
113
+ const itemId = params.itemId || item.id || item.itemId || null;
114
+ const receivedAt = payload && typeof payload === "object"
115
+ ? payload.received_at || payload.receivedAt || Date.now()
116
+ : Date.now();
117
+ // Handle reasoning/thinking deltas - accumulate into existing event
118
+ if (method === "item/reasoning/summaryTextDelta") {
119
+ const delta = params.delta || "";
120
+ if (!delta)
121
+ return null;
122
+ const event = {
123
+ id: payload?.id || `${Date.now()}`,
124
+ title: "Thinking",
125
+ summary: delta,
126
+ detail: "",
127
+ kind: "thinking",
128
+ isSignificant: true,
129
+ time: receivedAt,
130
+ itemId,
131
+ method,
132
+ };
133
+ return { event, mergeStrategy: "append" };
134
+ }
135
+ // Handle reasoning part added (paragraph break)
136
+ if (method === "item/reasoning/summaryPartAdded") {
137
+ const event = {
138
+ id: payload?.id || `${Date.now()}`,
139
+ title: "Thinking",
140
+ summary: "",
141
+ detail: "",
142
+ kind: "thinking",
143
+ isSignificant: true,
144
+ time: receivedAt,
145
+ itemId,
146
+ method,
147
+ };
148
+ return { event, mergeStrategy: "newline" };
149
+ }
150
+ let title = method;
151
+ let summary = "";
152
+ let detail = "";
153
+ let kind = "event";
154
+ // Handle generic status updates
155
+ if (method === "status" || params.status) {
156
+ title = "Status";
157
+ summary = params.status || "Processing";
158
+ kind = "status";
159
+ }
160
+ else if (method === "item/completed") {
161
+ const itemType = item.type;
162
+ if (itemType === "commandExecution") {
163
+ title = "Command";
164
+ summary = extractCommand(item, params);
165
+ kind = "command";
166
+ if (item.exitCode !== undefined && item.exitCode !== null) {
167
+ detail = `exit ${item.exitCode}`;
168
+ }
169
+ }
170
+ else if (itemType === "fileChange") {
171
+ title = "File change";
172
+ const files = extractFiles(item);
173
+ summary = files.join(", ") || "Updated files";
174
+ kind = "file";
175
+ }
176
+ else if (itemType === "tool") {
177
+ title = "Tool";
178
+ summary =
179
+ item.name ||
180
+ item.tool ||
181
+ item.id ||
182
+ "Tool call";
183
+ kind = "tool";
184
+ }
185
+ else if (itemType === "agentMessage") {
186
+ title = "Agent";
187
+ summary = item.text || "Agent message";
188
+ kind = "output";
189
+ }
190
+ else {
191
+ title = itemType ? `Item ${itemType}` : "Item completed";
192
+ summary = item.text || item.message || "";
193
+ }
194
+ }
195
+ else if (method === "item/commandExecution/requestApproval") {
196
+ title = "Command approval";
197
+ summary = extractCommand(item, params) || "Approval requested";
198
+ kind = "command";
199
+ }
200
+ else if (method === "item/fileChange/requestApproval") {
201
+ title = "File approval";
202
+ const files = extractFiles(params);
203
+ summary = files.join(", ") || "Approval requested";
204
+ kind = "file";
205
+ }
206
+ else if (method === "turn/completed") {
207
+ title = "Turn completed";
208
+ summary = params.status || "completed";
209
+ kind = "status";
210
+ }
211
+ else if (method === "error") {
212
+ title = "Error";
213
+ summary = extractErrorMessage(params) || "App-server error";
214
+ kind = "error";
215
+ }
216
+ else if (method.includes("outputDelta")) {
217
+ title = "Output";
218
+ summary = params.delta || params.text || "";
219
+ kind = "output";
220
+ }
221
+ else if (params.delta) {
222
+ title = "Delta";
223
+ summary = params.delta;
224
+ }
225
+ const summaryText = typeof summary === "string" ? summary : String(summary ?? "");
226
+ const detailText = typeof detail === "string" ? detail : String(detail ?? "");
227
+ const meaningful = hasMeaningfulText(summaryText, detailText);
228
+ const isStarted = method.includes("item/started");
229
+ if (!meaningful && isStarted) {
230
+ return null;
231
+ }
232
+ if (!meaningful) {
233
+ return null;
234
+ }
235
+ const isSignificant = inferSignificance(kind, method);
236
+ const event = {
237
+ id: payload?.id || `${Date.now()}`,
238
+ title,
239
+ summary: summaryText,
240
+ detail: detailText,
241
+ kind,
242
+ isSignificant,
243
+ time: receivedAt,
244
+ itemId,
245
+ method,
246
+ };
247
+ return { event };
248
+ }
@@ -1,19 +1,21 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
1
2
  import { REPO_ID, HUB_BASE } from "./env.js";
2
3
  import { initHub } from "./hub.js";
3
- import { initTabs, registerTab } from "./tabs.js";
4
- import { initDashboard } from "./dashboard.js";
5
- import { initDocs } from "./docs.js";
6
- import { initLogs } from "./logs.js";
4
+ import { initTabs, registerTab, registerHamburgerAction } from "./tabs.js";
7
5
  import { initTerminal } from "./terminal.js";
8
- import { initRuns } from "./runs.js";
9
- import { loadState } from "./state.js";
10
- import { initGitHub } from "./github.js";
6
+ import { initTicketFlow } from "./tickets.js";
7
+ import { initMessages, initMessageBell } from "./messages.js";
11
8
  import { initMobileCompact } from "./mobileCompact.js";
12
9
  import { subscribe } from "./bus.js";
13
- import { initRepoSettingsPanel } from "./settings.js";
10
+ import { initRepoSettingsPanel, openRepoSettings } from "./settings.js";
14
11
  import { flash } from "./utils.js";
15
12
  import { initLiveUpdates } from "./liveUpdates.js";
16
- function initRepoShell() {
13
+ import { initHealthGate } from "./health.js";
14
+ import { initWorkspace } from "./workspace.js";
15
+ import { initDashboard } from "./dashboard.js";
16
+ import { initArchive } from "./archive.js";
17
+ async function initRepoShell() {
18
+ await initHealthGate();
17
19
  if (REPO_ID) {
18
20
  const navBar = document.querySelector(".nav-bar");
19
21
  if (navBar) {
@@ -32,23 +34,34 @@ function initRepoShell() {
32
34
  brand.insertAdjacentElement("afterend", repoName);
33
35
  }
34
36
  }
35
- registerTab("dashboard", "Dashboard");
36
- registerTab("docs", "Docs");
37
- registerTab("runs", "Runs");
38
- registerTab("logs", "Logs");
37
+ const defaultTab = REPO_ID ? "tickets" : "analytics";
38
+ registerTab("tickets", "Tickets");
39
+ registerTab("inbox", "Inbox");
40
+ registerTab("workspace", "Workspace");
39
41
  registerTab("terminal", "Terminal");
42
+ // Menu tabs (shown in hamburger menu)
43
+ registerTab("analytics", "Analytics", { menuTab: true, icon: "📊" });
44
+ registerTab("archive", "Archive", { menuTab: true, icon: "📦" });
45
+ // Settings action in hamburger menu
46
+ registerHamburgerAction("settings", "Settings", "⚙", () => openRepoSettings());
40
47
  const initializedTabs = new Set();
41
48
  const lazyInit = (tabId) => {
42
49
  if (initializedTabs.has(tabId))
43
50
  return;
44
- if (tabId === "docs") {
45
- initDocs();
51
+ if (tabId === "workspace") {
52
+ initWorkspace();
53
+ }
54
+ else if (tabId === "inbox" || tabId === "messages") {
55
+ initMessages();
56
+ }
57
+ else if (tabId === "analytics") {
58
+ initDashboard();
46
59
  }
47
- else if (tabId === "logs") {
48
- initLogs();
60
+ else if (tabId === "archive") {
61
+ initArchive();
49
62
  }
50
- else if (tabId === "runs") {
51
- initRuns();
63
+ else if (tabId === "tickets") {
64
+ initTicketFlow();
52
65
  }
53
66
  initializedTabs.add(tabId);
54
67
  };
@@ -58,7 +71,7 @@ function initRepoShell() {
58
71
  }
59
72
  lazyInit(tabId);
60
73
  });
61
- initTabs();
74
+ initTabs(defaultTab);
62
75
  const activePanel = document.querySelector(".panel.active");
63
76
  if (activePanel?.id) {
64
77
  lazyInit(activePanel.id);
@@ -67,12 +80,10 @@ function initRepoShell() {
67
80
  terminalPanel?.addEventListener("pointerdown", () => {
68
81
  lazyInit("terminal");
69
82
  }, { once: true });
70
- initDashboard();
83
+ initMessageBell();
71
84
  initLiveUpdates();
72
85
  initRepoSettingsPanel();
73
- initGitHub();
74
86
  initMobileCompact();
75
- loadState();
76
87
  const repoShell = document.getElementById("repo-shell");
77
88
  if (repoShell?.hasAttribute("inert")) {
78
89
  const openModals = document.querySelectorAll(".modal-overlay:not([hidden])");
@@ -97,6 +108,6 @@ function bootstrap() {
97
108
  repoShell.classList.remove("hidden");
98
109
  if (hubShell)
99
110
  hubShell.classList.add("hidden");
100
- initRepoShell();
111
+ void initRepoShell();
101
112
  }
102
113
  bootstrap();