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,381 +0,0 @@
1
- """
2
- Document management routes: read/write docs and chat functionality.
3
- """
4
-
5
- from pathlib import Path
6
- from typing import Optional
7
-
8
- from fastapi import APIRouter, HTTPException, Request
9
- from fastapi.responses import StreamingResponse
10
-
11
- from ..core.doc_chat import (
12
- DocChatBusyError,
13
- DocChatConflictError,
14
- DocChatError,
15
- DocChatValidationError,
16
- _normalize_kind,
17
- )
18
- from ..core.snapshot import (
19
- SnapshotError,
20
- load_snapshot,
21
- load_snapshot_state,
22
- )
23
- from ..core.usage import (
24
- UsageError,
25
- default_codex_home,
26
- get_repo_usage_series_cached,
27
- get_repo_usage_summary_cached,
28
- parse_iso_datetime,
29
- )
30
- from ..core.utils import atomic_write
31
- from ..spec_ingest import (
32
- SpecIngestError,
33
- clear_work_docs,
34
- )
35
- from ..web.schemas import (
36
- DocChatPayload,
37
- DocContentRequest,
38
- DocsResponse,
39
- DocWriteResponse,
40
- IngestSpecRequest,
41
- IngestSpecResponse,
42
- RepoUsageResponse,
43
- SnapshotCreateResponse,
44
- SnapshotRequest,
45
- SnapshotResponse,
46
- UsageSeriesResponse,
47
- )
48
- from .shared import SSE_HEADERS
49
-
50
-
51
- def build_docs_routes() -> APIRouter:
52
- """Build routes for document management and chat."""
53
- router = APIRouter()
54
-
55
- @router.get("/api/docs", response_model=DocsResponse)
56
- def get_docs(request: Request):
57
- engine = request.app.state.engine
58
- return {
59
- "todo": engine.docs.read_doc("todo"),
60
- "progress": engine.docs.read_doc("progress"),
61
- "opinions": engine.docs.read_doc("opinions"),
62
- "spec": engine.docs.read_doc("spec"),
63
- "summary": engine.docs.read_doc("summary"),
64
- }
65
-
66
- @router.put("/api/docs/{kind}", response_model=DocWriteResponse)
67
- def put_doc(kind: str, payload: DocContentRequest, request: Request):
68
- engine = request.app.state.engine
69
- key = kind.lower()
70
- if key not in ("todo", "progress", "opinions", "spec", "summary"):
71
- raise HTTPException(status_code=400, detail="invalid doc kind")
72
- content = payload.content
73
- atomic_write(engine.config.doc_path(key), content)
74
- return {"kind": key, "content": content}
75
-
76
- @router.get("/api/snapshot", response_model=SnapshotResponse)
77
- def get_snapshot(request: Request):
78
- engine = request.app.state.engine
79
- content = load_snapshot(engine)
80
- state = load_snapshot_state(engine)
81
- return {"exists": bool(content), "content": content or "", "state": state or {}}
82
-
83
- @router.post("/api/snapshot", response_model=SnapshotCreateResponse)
84
- async def post_snapshot(
85
- request: Request, payload: Optional[SnapshotRequest] = None
86
- ):
87
- # Snapshot generation has a single default behavior now; we accept an
88
- # optional JSON object for backwards compatibility, but ignore any fields.
89
- snapshot_service = request.app.state.snapshot_service
90
- try:
91
- result = await snapshot_service.generate_snapshot()
92
- except SnapshotError as exc:
93
- raise HTTPException(status_code=400, detail=str(exc)) from exc
94
- except Exception as exc:
95
- raise HTTPException(status_code=500, detail=str(exc)) from exc
96
-
97
- return {
98
- "content": result.content,
99
- "truncated": result.truncated,
100
- "state": result.state,
101
- }
102
-
103
- async def _handle_doc_chat_request(
104
- request: Request,
105
- *,
106
- kind: Optional[str],
107
- payload: Optional[DocChatPayload],
108
- ):
109
- doc_chat = request.app.state.doc_chat
110
- repo_blocked = doc_chat.repo_blocked_reason()
111
- if repo_blocked:
112
- raise HTTPException(status_code=409, detail=repo_blocked)
113
- try:
114
- payload_dict = payload.model_dump(exclude_none=True) if payload else None
115
- doc_req = doc_chat.parse_request(payload_dict, kind=kind)
116
- except DocChatValidationError as exc:
117
- raise HTTPException(status_code=400, detail=str(exc)) from exc
118
-
119
- if doc_chat.doc_busy():
120
- raise HTTPException(
121
- status_code=409,
122
- detail="Doc chat already running",
123
- )
124
-
125
- if doc_req.stream:
126
- return StreamingResponse(
127
- doc_chat.stream(doc_req),
128
- media_type="text/event-stream",
129
- headers=SSE_HEADERS,
130
- )
131
-
132
- try:
133
- async with doc_chat.doc_lock():
134
- result = await doc_chat.execute(doc_req)
135
- except DocChatBusyError as exc:
136
- raise HTTPException(status_code=409, detail=str(exc)) from exc
137
-
138
- if result.get("status") != "ok":
139
- detail = result.get("detail") or "Doc chat failed"
140
- raise HTTPException(status_code=500, detail=detail)
141
- return result
142
-
143
- @router.post("/api/docs/chat")
144
- async def chat_docs(request: Request, payload: Optional[DocChatPayload] = None):
145
- return await _handle_doc_chat_request(request, kind=None, payload=payload)
146
-
147
- @router.post("/api/docs/{kind}/chat")
148
- async def chat_doc(
149
- kind: str, request: Request, payload: Optional[DocChatPayload] = None
150
- ):
151
- return await _handle_doc_chat_request(request, kind=kind, payload=payload)
152
-
153
- @router.post("/api/docs/chat/interrupt")
154
- async def interrupt_chat(request: Request):
155
- doc_chat = request.app.state.doc_chat
156
- try:
157
- return await doc_chat.interrupt()
158
- except DocChatValidationError as exc:
159
- raise HTTPException(status_code=400, detail=str(exc)) from exc
160
-
161
- @router.post("/api/docs/{kind}/chat/interrupt")
162
- async def interrupt_chat_kind(kind: str, request: Request):
163
- doc_chat = request.app.state.doc_chat
164
- try:
165
- return await doc_chat.interrupt(kind)
166
- except DocChatValidationError as exc:
167
- raise HTTPException(status_code=400, detail=str(exc)) from exc
168
-
169
- @router.post("/api/docs/{kind}/chat/apply")
170
- async def apply_chat_patch(kind: str, request: Request):
171
- doc_chat = request.app.state.doc_chat
172
- key = _normalize_kind(kind)
173
- repo_blocked = doc_chat.repo_blocked_reason()
174
- if repo_blocked:
175
- raise HTTPException(status_code=409, detail=repo_blocked)
176
-
177
- try:
178
- async with doc_chat.doc_lock(key):
179
- pending = doc_chat.pending_patch(key)
180
- content = doc_chat.apply_saved_patch(key)
181
- except DocChatBusyError as exc:
182
- raise HTTPException(status_code=409, detail=str(exc)) from exc
183
- except DocChatConflictError as exc:
184
- raise HTTPException(status_code=409, detail=str(exc)) from exc
185
- except DocChatError as exc:
186
- raise HTTPException(status_code=500, detail=str(exc)) from exc
187
- return {
188
- "status": "ok",
189
- "kind": key,
190
- "content": content,
191
- "agent_message": (pending or {}).get("agent_message")
192
- or f"Updated {key.upper()} via doc chat.",
193
- "created_at": (pending or {}).get("created_at"),
194
- "base_hash": (pending or {}).get("base_hash"),
195
- }
196
-
197
- @router.post("/api/docs/{kind}/chat/discard")
198
- async def discard_chat_patch(kind: str, request: Request):
199
- doc_chat = request.app.state.doc_chat
200
- key = _normalize_kind(kind)
201
- repo_blocked = doc_chat.repo_blocked_reason()
202
- if repo_blocked:
203
- raise HTTPException(status_code=409, detail=repo_blocked)
204
- try:
205
- async with doc_chat.doc_lock(key):
206
- content = doc_chat.discard_patch(key)
207
- except DocChatError as exc:
208
- raise HTTPException(status_code=500, detail=str(exc)) from exc
209
- return {"status": "ok", "kind": key, "content": content}
210
-
211
- @router.get("/api/docs/{kind}/chat/pending")
212
- async def pending_chat_patch(kind: str, request: Request):
213
- doc_chat = request.app.state.doc_chat
214
- key = _normalize_kind(kind)
215
- repo_blocked = doc_chat.repo_blocked_reason()
216
- if repo_blocked:
217
- raise HTTPException(status_code=409, detail=repo_blocked)
218
- pending = doc_chat.pending_patch(key)
219
- if not pending:
220
- raise HTTPException(status_code=404, detail="No pending patch")
221
- return pending
222
-
223
- @router.post("/api/ingest-spec", response_model=IngestSpecResponse)
224
- async def ingest_spec(
225
- request: Request, payload: Optional[IngestSpecRequest] = None
226
- ):
227
- engine = request.app.state.engine
228
- repo_blocked = engine.repo_busy_reason()
229
- if repo_blocked:
230
- raise HTTPException(status_code=409, detail=repo_blocked)
231
- spec_ingest = request.app.state.spec_ingest
232
- force = False
233
- spec_override: Optional[Path] = None
234
- message: Optional[str] = None
235
- agent: Optional[str] = None
236
- model: Optional[str] = None
237
- reasoning: Optional[str] = None
238
- if payload:
239
- force = payload.force
240
- if payload.spec_path:
241
- spec_override = Path(str(payload.spec_path))
242
- message = payload.message
243
- agent = payload.agent
244
- model = payload.model
245
- reasoning = payload.reasoning
246
- try:
247
- docs = await spec_ingest.execute(
248
- force=force,
249
- spec_path=spec_override,
250
- message=message,
251
- agent=agent,
252
- model=model,
253
- reasoning=reasoning,
254
- )
255
- except SpecIngestError as exc:
256
- raise HTTPException(status_code=400, detail=str(exc)) from exc
257
- return docs
258
-
259
- @router.post("/api/ingest-spec/interrupt", response_model=IngestSpecResponse)
260
- async def ingest_spec_interrupt(request: Request):
261
- spec_ingest = request.app.state.spec_ingest
262
- try:
263
- docs = await spec_ingest.interrupt()
264
- except SpecIngestError as exc:
265
- raise HTTPException(status_code=400, detail=str(exc)) from exc
266
- return docs
267
-
268
- @router.get("/api/ingest-spec/pending", response_model=IngestSpecResponse)
269
- def ingest_spec_pending(request: Request):
270
- engine = request.app.state.engine
271
- repo_blocked = engine.repo_busy_reason()
272
- if repo_blocked:
273
- raise HTTPException(status_code=409, detail=repo_blocked)
274
- spec_ingest = request.app.state.spec_ingest
275
- try:
276
- pending = spec_ingest.pending_patch()
277
- if not pending:
278
- raise HTTPException(
279
- status_code=404, detail="No pending spec ingest patch"
280
- )
281
- return pending
282
- except SpecIngestError as exc:
283
- raise HTTPException(status_code=400, detail=str(exc)) from exc
284
-
285
- @router.post("/api/ingest-spec/apply", response_model=IngestSpecResponse)
286
- def ingest_spec_apply(request: Request):
287
- engine = request.app.state.engine
288
- repo_blocked = engine.repo_busy_reason()
289
- if repo_blocked:
290
- raise HTTPException(status_code=409, detail=repo_blocked)
291
- spec_ingest = request.app.state.spec_ingest
292
- try:
293
- return spec_ingest.apply_patch()
294
- except SpecIngestError as exc:
295
- raise HTTPException(status_code=400, detail=str(exc)) from exc
296
-
297
- @router.post("/api/ingest-spec/discard", response_model=IngestSpecResponse)
298
- def ingest_spec_discard(request: Request):
299
- engine = request.app.state.engine
300
- repo_blocked = engine.repo_busy_reason()
301
- if repo_blocked:
302
- raise HTTPException(status_code=409, detail=repo_blocked)
303
- spec_ingest = request.app.state.spec_ingest
304
- try:
305
- return spec_ingest.discard_patch()
306
- except SpecIngestError as exc:
307
- raise HTTPException(status_code=400, detail=str(exc)) from exc
308
-
309
- @router.post("/api/docs/clear", response_model=DocsResponse)
310
- def clear_docs(request: Request):
311
- engine = request.app.state.engine
312
- try:
313
- docs = clear_work_docs(engine)
314
- docs["spec"] = engine.docs.read_doc("spec")
315
- docs["summary"] = engine.docs.read_doc("summary")
316
- except Exception as exc:
317
- raise HTTPException(status_code=500, detail=str(exc)) from exc
318
- return docs
319
-
320
- @router.get("/api/usage", response_model=RepoUsageResponse)
321
- def get_usage(
322
- request: Request, since: Optional[str] = None, until: Optional[str] = None
323
- ):
324
- engine = request.app.state.engine
325
- try:
326
- since_dt = parse_iso_datetime(since)
327
- until_dt = parse_iso_datetime(until)
328
- except UsageError as exc:
329
- raise HTTPException(status_code=400, detail=str(exc)) from exc
330
- summary, status = get_repo_usage_summary_cached(
331
- engine.repo_root,
332
- default_codex_home(),
333
- since=since_dt,
334
- until=until_dt,
335
- )
336
- return {
337
- "mode": "repo",
338
- "repo": str(engine.repo_root),
339
- "codex_home": str(default_codex_home()),
340
- "since": since,
341
- "until": until,
342
- "status": status,
343
- **summary.to_dict(),
344
- }
345
-
346
- @router.get("/api/usage/series", response_model=UsageSeriesResponse)
347
- def get_usage_series(
348
- request: Request,
349
- since: Optional[str] = None,
350
- until: Optional[str] = None,
351
- bucket: str = "day",
352
- segment: str = "none",
353
- ):
354
- engine = request.app.state.engine
355
- try:
356
- since_dt = parse_iso_datetime(since)
357
- until_dt = parse_iso_datetime(until)
358
- except UsageError as exc:
359
- raise HTTPException(status_code=400, detail=str(exc)) from exc
360
- try:
361
- series, status = get_repo_usage_series_cached(
362
- engine.repo_root,
363
- default_codex_home(),
364
- since=since_dt,
365
- until=until_dt,
366
- bucket=bucket,
367
- segment=segment,
368
- )
369
- except UsageError as exc:
370
- raise HTTPException(status_code=400, detail=str(exc)) from exc
371
- return {
372
- "mode": "repo",
373
- "repo": str(engine.repo_root),
374
- "codex_home": str(default_codex_home()),
375
- "since": since,
376
- "until": until,
377
- "status": status,
378
- **series,
379
- }
380
-
381
- return router