alysis-code 0.13.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 (458) hide show
  1. alysis_code/__init__.py +3 -0
  2. alysis_code/__main__.py +4 -0
  3. alysis_code/_build_info.py +14 -0
  4. alysis_code/account_login.py +468 -0
  5. alysis_code/agent/README.md +35 -0
  6. alysis_code/agent/__init__.py +11 -0
  7. alysis_code/agent/acceptance_contract.py +2217 -0
  8. alysis_code/agent/blast_radius.py +1403 -0
  9. alysis_code/agent/cache_keepalive.py +227 -0
  10. alysis_code/agent/completion_certificate.py +366 -0
  11. alysis_code/agent/completion_gate.py +306 -0
  12. alysis_code/agent/empty_response_stall.py +403 -0
  13. alysis_code/agent/errors.py +28 -0
  14. alysis_code/agent/llm_calls.py +475 -0
  15. alysis_code/agent/mutation_classification.py +227 -0
  16. alysis_code/agent/prompt_context.py +2508 -0
  17. alysis_code/agent/read_ledger.py +253 -0
  18. alysis_code/agent/regression_baseline.py +642 -0
  19. alysis_code/agent/reproduction_first.py +610 -0
  20. alysis_code/agent/sensitive_output.py +629 -0
  21. alysis_code/agent/session.py +3218 -0
  22. alysis_code/agent/steering.py +191 -0
  23. alysis_code/agent/subagent_execution.py +5177 -0
  24. alysis_code/agent/subagent_workspace.py +666 -0
  25. alysis_code/agent/tools_assembly.py +4728 -0
  26. alysis_code/agent/turn/__init__.py +101 -0
  27. alysis_code/agent/turn/core.py +8483 -0
  28. alysis_code/agent/turn/events.py +113 -0
  29. alysis_code/agent/turn/exploration.py +590 -0
  30. alysis_code/agent/turn/interventions.py +65 -0
  31. alysis_code/agent/turn/read_cache.py +420 -0
  32. alysis_code/agent/turn/snapshot.py +179 -0
  33. alysis_code/agent/turn_contract.py +661 -0
  34. alysis_code/agent/turn_path.py +129 -0
  35. alysis_code/agent/verification.py +2885 -0
  36. alysis_code/agent/verification_commands.py +512 -0
  37. alysis_code/agent/verification_evidence.py +738 -0
  38. alysis_code/agent_loop.py +693 -0
  39. alysis_code/agent_runtimes/__init__.py +51 -0
  40. alysis_code/agent_runtimes/base.py +114 -0
  41. alysis_code/agent_runtimes/builtins.py +129 -0
  42. alysis_code/agent_runtimes/codex_cli.py +664 -0
  43. alysis_code/agent_runtimes/host.py +263 -0
  44. alysis_code/agent_runtimes/registry.py +64 -0
  45. alysis_code/agent_runtimes/service.py +150 -0
  46. alysis_code/agentbox_client.py +416 -0
  47. alysis_code/agentbox_integration.py +310 -0
  48. alysis_code/alysis_cloud.py +152 -0
  49. alysis_code/approval_scope.py +276 -0
  50. alysis_code/assets/README.md +33 -0
  51. alysis_code/assets/__init__.py +126 -0
  52. alysis_code/assets/asset_read_core.py +281 -0
  53. alysis_code/assets/budget_allocator.py +456 -0
  54. alysis_code/assets/comprehender.py +759 -0
  55. alysis_code/assets/index.py +654 -0
  56. alysis_code/assets/ingestion.py +275 -0
  57. alysis_code/assets/legacy_migration.py +413 -0
  58. alysis_code/assets/models.py +263 -0
  59. alysis_code/assets/ocr.py +239 -0
  60. alysis_code/assets/owl/ascii/f-000.txt +13 -0
  61. alysis_code/assets/owl/ascii/f-001.txt +13 -0
  62. alysis_code/assets/owl/ascii/f-002.txt +13 -0
  63. alysis_code/assets/owl/ascii/f-003.txt +13 -0
  64. alysis_code/assets/owl/ascii/f-004.txt +13 -0
  65. alysis_code/assets/owl/ascii/f-005.txt +13 -0
  66. alysis_code/assets/owl/ascii/f-006.txt +13 -0
  67. alysis_code/assets/owl/ascii/f-007.txt +13 -0
  68. alysis_code/assets/owl/ascii/f-008.txt +13 -0
  69. alysis_code/assets/owl/ascii/f-009.txt +13 -0
  70. alysis_code/assets/owl/ascii/f-010.txt +13 -0
  71. alysis_code/assets/owl/ascii/f-011.txt +13 -0
  72. alysis_code/assets/owl/ascii/f-012.txt +13 -0
  73. alysis_code/assets/owl/ascii/f-013.txt +13 -0
  74. alysis_code/assets/owl/ascii/f-014.txt +13 -0
  75. alysis_code/assets/owl/ascii/f-015.txt +13 -0
  76. alysis_code/assets/owl/ascii/f-016.txt +13 -0
  77. alysis_code/assets/owl/ascii/f-017.txt +13 -0
  78. alysis_code/assets/owl/ascii/f-018.txt +13 -0
  79. alysis_code/assets/owl/ascii/f-019.txt +13 -0
  80. alysis_code/assets/owl/ascii/f-020.txt +13 -0
  81. alysis_code/assets/owl/index.html +98 -0
  82. alysis_code/assets/owl/show-owl.sh +761 -0
  83. alysis_code/assets/paths.py +49 -0
  84. alysis_code/assets/plan_binding.py +326 -0
  85. alysis_code/assets/planner_context.py +466 -0
  86. alysis_code/assets/planner_tools.py +184 -0
  87. alysis_code/assets/prompts.py +101 -0
  88. alysis_code/assets/replanner_context.py +239 -0
  89. alysis_code/assets/surface.py +521 -0
  90. alysis_code/assets/untrusted_content.py +48 -0
  91. alysis_code/assets/usage_logger.py +94 -0
  92. alysis_code/assets/worker_mirror.py +428 -0
  93. alysis_code/assets/worker_section.py +303 -0
  94. alysis_code/assets/worker_tools.py +468 -0
  95. alysis_code/atomic_io.py +83 -0
  96. alysis_code/auth_diagnostics.py +272 -0
  97. alysis_code/background_runner.py +366 -0
  98. alysis_code/branding.py +270 -0
  99. alysis_code/budget_policy.py +390 -0
  100. alysis_code/build_identity.py +465 -0
  101. alysis_code/builtin_hooks/__init__.py +7 -0
  102. alysis_code/builtin_hooks/notify_done_windows.py +65 -0
  103. alysis_code/bwrap_etc.py +76 -0
  104. alysis_code/cancellation.py +41 -0
  105. alysis_code/capabilities.py +137 -0
  106. alysis_code/chatgpt_codex_static_provider.py +133 -0
  107. alysis_code/cli.py +51 -0
  108. alysis_code/cli_impl/__init__.py +1 -0
  109. alysis_code/cli_impl/assets_cli.py +537 -0
  110. alysis_code/cli_impl/assets_modal.py +412 -0
  111. alysis_code/cli_impl/chat/__init__.py +156 -0
  112. alysis_code/cli_impl/chat/commands.py +2616 -0
  113. alysis_code/cli_impl/chat/loop.py +4508 -0
  114. alysis_code/cli_impl/chat/mid_turn_policy.py +125 -0
  115. alysis_code/cli_impl/chat/rendering.py +444 -0
  116. alysis_code/cli_impl/chat/state.py +124 -0
  117. alysis_code/cli_impl/chat_resume.py +830 -0
  118. alysis_code/cli_impl/chat_slash_completer.py +258 -0
  119. alysis_code/cli_impl/commands/__init__.py +11 -0
  120. alysis_code/cli_impl/commands/_shared.py +89 -0
  121. alysis_code/cli_impl/commands/auth.py +623 -0
  122. alysis_code/cli_impl/commands/chat_resume_helpers.py +1531 -0
  123. alysis_code/cli_impl/commands/chat_state.py +158 -0
  124. alysis_code/cli_impl/commands/chat_status.py +1248 -0
  125. alysis_code/cli_impl/commands/chat_terminal.py +942 -0
  126. alysis_code/cli_impl/commands/chat_tui_panels.py +1018 -0
  127. alysis_code/cli_impl/commands/cli_common.py +1223 -0
  128. alysis_code/cli_impl/commands/cli_surface.py +77 -0
  129. alysis_code/cli_impl/commands/config.py +131 -0
  130. alysis_code/cli_impl/commands/conventions.py +85 -0
  131. alysis_code/cli_impl/commands/execution_helpers.py +350 -0
  132. alysis_code/cli_impl/commands/extensions.py +401 -0
  133. alysis_code/cli_impl/commands/forge.py +1282 -0
  134. alysis_code/cli_impl/commands/forge_asset_view.py +121 -0
  135. alysis_code/cli_impl/commands/forge_helpers.py +1215 -0
  136. alysis_code/cli_impl/commands/hooks.py +737 -0
  137. alysis_code/cli_impl/commands/ide_bridge.py +31 -0
  138. alysis_code/cli_impl/commands/mcp.py +700 -0
  139. alysis_code/cli_impl/commands/profile.py +453 -0
  140. alysis_code/cli_impl/commands/prompt_helpers.py +307 -0
  141. alysis_code/cli_impl/commands/report.py +88 -0
  142. alysis_code/cli_impl/commands/root.py +1118 -0
  143. alysis_code/cli_impl/commands/sandbox.py +184 -0
  144. alysis_code/cli_impl/commands/server.py +54 -0
  145. alysis_code/cli_impl/commands/sessions.py +252 -0
  146. alysis_code/cli_impl/commands/skills.py +404 -0
  147. alysis_code/cli_impl/commands/startup.py +946 -0
  148. alysis_code/cli_impl/commands/tools.py +335 -0
  149. alysis_code/cli_impl/commands/update.py +364 -0
  150. alysis_code/cli_impl/commands/welcome.py +972 -0
  151. alysis_code/cli_impl/config_menu.py +3882 -0
  152. alysis_code/cli_impl/forge.py +4509 -0
  153. alysis_code/cli_impl/forge_recovery.py +485 -0
  154. alysis_code/cli_impl/setup_wizard.py +2409 -0
  155. alysis_code/cli_impl/tui/__init__.py +58 -0
  156. alysis_code/cli_impl/tui/app.py +4551 -0
  157. alysis_code/cli_impl/tui/config.py +32 -0
  158. alysis_code/cli_impl/tui/config_flow.py +2754 -0
  159. alysis_code/cli_impl/tui/config_overlay.py +566 -0
  160. alysis_code/cli_impl/tui/content.py +78 -0
  161. alysis_code/cli_impl/tui/footer.py +218 -0
  162. alysis_code/cli_impl/tui/forge_status.py +136 -0
  163. alysis_code/cli_impl/tui/markdown.py +244 -0
  164. alysis_code/cli_impl/tui/owl.py +109 -0
  165. alysis_code/cli_impl/tui/plan_meta.py +477 -0
  166. alysis_code/cli_impl/tui/setup_app.py +519 -0
  167. alysis_code/cli_impl/tui/setup_flow.py +1622 -0
  168. alysis_code/cli_impl/tui/state.py +101 -0
  169. alysis_code/cli_impl/tui/subagent_identity.py +66 -0
  170. alysis_code/cli_impl/tui/subagent_panel.py +186 -0
  171. alysis_code/cli_impl/tui/surface.py +796 -0
  172. alysis_code/cli_impl/tui/transcript.py +514 -0
  173. alysis_code/cli_impl/tui/update_prompt.py +79 -0
  174. alysis_code/cli_impl/tui/workspace_guard.py +384 -0
  175. alysis_code/clipboard.py +172 -0
  176. alysis_code/code_review.py +1211 -0
  177. alysis_code/compaction/__init__.py +28 -0
  178. alysis_code/compaction/conversation_compactor.py +2932 -0
  179. alysis_code/compaction/importance.py +177 -0
  180. alysis_code/compaction/settings.py +297 -0
  181. alysis_code/compaction/tool_output_offload.py +447 -0
  182. alysis_code/config.py +3509 -0
  183. alysis_code/conflict_auto_resolver.py +895 -0
  184. alysis_code/context/__init__.py +1 -0
  185. alysis_code/context/tool_schema_budgeter.py +220 -0
  186. alysis_code/crash_diagnostics.py +282 -0
  187. alysis_code/custom_tools/README.md +34 -0
  188. alysis_code/custom_tools/__init__.py +43 -0
  189. alysis_code/custom_tools/discovery.py +903 -0
  190. alysis_code/custom_tools/runtime.py +1516 -0
  191. alysis_code/custom_tools/session.py +227 -0
  192. alysis_code/custom_tools/trust.py +232 -0
  193. alysis_code/diff_paths.py +113 -0
  194. alysis_code/direction_change.py +293 -0
  195. alysis_code/dispatch_timing.py +306 -0
  196. alysis_code/durable_service_manager.py +1236 -0
  197. alysis_code/edit_discipline.py +659 -0
  198. alysis_code/error_text.py +73 -0
  199. alysis_code/execution_budget.py +411 -0
  200. alysis_code/execution_context.py +915 -0
  201. alysis_code/execution_deadline.py +1065 -0
  202. alysis_code/execution_shared.py +1904 -0
  203. alysis_code/extensions/README.md +30 -0
  204. alysis_code/extensions/__init__.py +93 -0
  205. alysis_code/extensions/activation.py +138 -0
  206. alysis_code/extensions/install.py +1436 -0
  207. alysis_code/extensions/manifest.py +487 -0
  208. alysis_code/extensions/models.py +74 -0
  209. alysis_code/extensions/paths.py +56 -0
  210. alysis_code/extensions/registry.json +4 -0
  211. alysis_code/extensions/registry.py +52 -0
  212. alysis_code/extensions/state.py +83 -0
  213. alysis_code/extensions/workspace_trust.py +101 -0
  214. alysis_code/failed_task_evidence.py +369 -0
  215. alysis_code/failure_category.py +315 -0
  216. alysis_code/feedback_report.py +1647 -0
  217. alysis_code/file_classification.py +485 -0
  218. alysis_code/forge.py +2064 -0
  219. alysis_code/forge_completion.py +362 -0
  220. alysis_code/forge_events.py +475 -0
  221. alysis_code/frontmatter_utils.py +95 -0
  222. alysis_code/git_evidence.py +1181 -0
  223. alysis_code/git_ops.py +560 -0
  224. alysis_code/git_safe.py +62 -0
  225. alysis_code/git_worktrees.py +190 -0
  226. alysis_code/hooks/README.md +33 -0
  227. alysis_code/hooks/__init__.py +67 -0
  228. alysis_code/hooks/audit.py +171 -0
  229. alysis_code/hooks/config.py +225 -0
  230. alysis_code/hooks/dispatcher.py +1110 -0
  231. alysis_code/hooks/models.py +447 -0
  232. alysis_code/hooks/trust.py +202 -0
  233. alysis_code/host_actions.py +543 -0
  234. alysis_code/host_browser.py +103 -0
  235. alysis_code/ide/__init__.py +5 -0
  236. alysis_code/ide/activity_events.py +399 -0
  237. alysis_code/ide/approvals.py +337 -0
  238. alysis_code/ide/artifacts.py +153 -0
  239. alysis_code/ide/browser_egress_proxy.py +1076 -0
  240. alysis_code/ide/cdp_websocket_transport.py +1192 -0
  241. alysis_code/ide/change_ledger.py +1721 -0
  242. alysis_code/ide/context_blocks.py +979 -0
  243. alysis_code/ide/event_stream.py +531 -0
  244. alysis_code/ide/forge_protocol.py +3112 -0
  245. alysis_code/ide/forge_request_ledger.py +737 -0
  246. alysis_code/ide/health.py +965 -0
  247. alysis_code/ide/managed_browser.py +2251 -0
  248. alysis_code/ide/management_protocol.py +3414 -0
  249. alysis_code/ide/mcp_oauth_coordinator.py +744 -0
  250. alysis_code/ide/mcp_oauth_lifecycle.py +1504 -0
  251. alysis_code/ide/prompt_queue.py +1070 -0
  252. alysis_code/ide/protocol.py +191 -0
  253. alysis_code/ide/resumable_swarm.py +1543 -0
  254. alysis_code/ide/session_search.py +295 -0
  255. alysis_code/ide/stdio_bridge.py +9935 -0
  256. alysis_code/ide/structured_state.py +1579 -0
  257. alysis_code/ide/swarm_protocol.py +816 -0
  258. alysis_code/integration_gate.py +506 -0
  259. alysis_code/interactive_input_guard.py +39 -0
  260. alysis_code/interactive_plan_mode.py +26 -0
  261. alysis_code/internal_artifacts.py +179 -0
  262. alysis_code/knowledge_base.py +1409 -0
  263. alysis_code/knowledge_capture.py +1190 -0
  264. alysis_code/knowledge_librarian.py +605 -0
  265. alysis_code/language_policy.py +34 -0
  266. alysis_code/litellm_static_provider.py +535 -0
  267. alysis_code/llm/__init__.py +1 -0
  268. alysis_code/llm/anthropic_messages.py +2288 -0
  269. alysis_code/llm/base.py +71 -0
  270. alysis_code/llm/cache_capabilities.py +985 -0
  271. alysis_code/llm/cache_control_blocks.py +244 -0
  272. alysis_code/llm/cache_policy.py +388 -0
  273. alysis_code/llm/factory.py +373 -0
  274. alysis_code/llm/gemini_generate_content.py +2652 -0
  275. alysis_code/llm/gemini_interactions.py +739 -0
  276. alysis_code/llm/metadata.py +450 -0
  277. alysis_code/llm/openai_compat.py +2947 -0
  278. alysis_code/llm/openai_responses.py +2604 -0
  279. alysis_code/llm/protocols.py +609 -0
  280. alysis_code/llm/provider_limits.py +525 -0
  281. alysis_code/llm/request_plan.py +389 -0
  282. alysis_code/llm/request_shape.py +238 -0
  283. alysis_code/llm/streaming.py +108 -0
  284. alysis_code/llm/temperature_compat.py +78 -0
  285. alysis_code/llm/types.py +195 -0
  286. alysis_code/llm/usage_normalization.py +222 -0
  287. alysis_code/llm_error_display.py +315 -0
  288. alysis_code/logging_redaction.py +326 -0
  289. alysis_code/managed_host_deadline.py +196 -0
  290. alysis_code/mcp/README.md +33 -0
  291. alysis_code/mcp/__init__.py +24 -0
  292. alysis_code/mcp/client.py +1137 -0
  293. alysis_code/mcp/config.py +597 -0
  294. alysis_code/mcp/errors.py +113 -0
  295. alysis_code/mcp/forge_scope.py +154 -0
  296. alysis_code/mcp/jsonrpc.py +214 -0
  297. alysis_code/mcp/manager.py +2308 -0
  298. alysis_code/mcp/models.py +666 -0
  299. alysis_code/mcp/oauth.py +972 -0
  300. alysis_code/mcp/oauth_runtime.py +310 -0
  301. alysis_code/mcp/oauth_store.py +276 -0
  302. alysis_code/mcp/prompts.py +329 -0
  303. alysis_code/mcp/resources.py +295 -0
  304. alysis_code/mcp/roots.py +106 -0
  305. alysis_code/mcp/server_requests.py +75 -0
  306. alysis_code/mcp/token_store.py +859 -0
  307. alysis_code/mcp/transport_http.py +1338 -0
  308. alysis_code/mcp/transport_stdio.py +1267 -0
  309. alysis_code/mcp/untrusted_content.py +119 -0
  310. alysis_code/merge_conflict_reviewer.py +729 -0
  311. alysis_code/model_catalog/__init__.py +1 -0
  312. alysis_code/model_catalog/chatgpt_codex_subscription_snapshot.json +186 -0
  313. alysis_code/model_catalog/litellm_model_prices_snapshot.json +44715 -0
  314. alysis_code/model_catalog/litellm_model_prices_snapshot.meta.json +17 -0
  315. alysis_code/model_metadata_policy.py +223 -0
  316. alysis_code/model_metadata_utils.py +103 -0
  317. alysis_code/model_registry.py +1420 -0
  318. alysis_code/model_router.py +147 -0
  319. alysis_code/permission_policy.py +1016 -0
  320. alysis_code/personas.py +451 -0
  321. alysis_code/pipeline_facts.py +233 -0
  322. alysis_code/plan_assistant.py +4763 -0
  323. alysis_code/plan_mode.py +393 -0
  324. alysis_code/plan_reconciliation.py +1228 -0
  325. alysis_code/plan_repair.py +652 -0
  326. alysis_code/plan_validation.py +1099 -0
  327. alysis_code/planning_constraints.py +904 -0
  328. alysis_code/policy.py +95 -0
  329. alysis_code/preview_server.py +457 -0
  330. alysis_code/process_reaping.py +566 -0
  331. alysis_code/profile_presets.py +1834 -0
  332. alysis_code/profiles.py +666 -0
  333. alysis_code/provider_auth/__init__.py +29 -0
  334. alysis_code/provider_auth/base.py +99 -0
  335. alysis_code/provider_auth/openai_codex.py +951 -0
  336. alysis_code/provider_auth/registry.py +76 -0
  337. alysis_code/provider_auth/store.py +125 -0
  338. alysis_code/provider_diagnostics.py +1209 -0
  339. alysis_code/provider_model_catalog.py +685 -0
  340. alysis_code/provider_telemetry.py +1699 -0
  341. alysis_code/provider_url.py +75 -0
  342. alysis_code/reasoning_contracts.py +911 -0
  343. alysis_code/remote_sync.py +350 -0
  344. alysis_code/replanning.py +1195 -0
  345. alysis_code/repo_scan.py +1152 -0
  346. alysis_code/request_estimation.py +296 -0
  347. alysis_code/review_gate.py +617 -0
  348. alysis_code/run_lock.py +1141 -0
  349. alysis_code/run_outcome.py +58 -0
  350. alysis_code/run_provenance.py +774 -0
  351. alysis_code/run_state.py +445 -0
  352. alysis_code/runtime_artifacts.py +116 -0
  353. alysis_code/runtime_context_features.py +78 -0
  354. alysis_code/runtime_kind.py +52 -0
  355. alysis_code/safety/__init__.py +11 -0
  356. alysis_code/safety/mcp_sanitize.py +29 -0
  357. alysis_code/safety/safe_http.py +297 -0
  358. alysis_code/safety/subagent_report.py +184 -0
  359. alysis_code/sandbox_doctor.py +682 -0
  360. alysis_code/sandbox_runner.py +1025 -0
  361. alysis_code/sandbox_settings.py +423 -0
  362. alysis_code/serialized_paths.py +355 -0
  363. alysis_code/server/__init__.py +3 -0
  364. alysis_code/server/app.py +367 -0
  365. alysis_code/server/auth.py +34 -0
  366. alysis_code/server/job_config.py +30 -0
  367. alysis_code/server/settings.py +215 -0
  368. alysis_code/server/store.py +193 -0
  369. alysis_code/server/worker_runner.py +657 -0
  370. alysis_code/service_persistence.py +355 -0
  371. alysis_code/session_artifacts.py +108 -0
  372. alysis_code/session_metrics.py +331 -0
  373. alysis_code/session_store.py +624 -0
  374. alysis_code/skills/README.md +34 -0
  375. alysis_code/skills/__init__.py +104 -0
  376. alysis_code/skills/conventions.py +84 -0
  377. alysis_code/skills/discovery.py +176 -0
  378. alysis_code/skills/eval_models.py +232 -0
  379. alysis_code/skills/eval_runner.py +372 -0
  380. alysis_code/skills/evals.py +1344 -0
  381. alysis_code/skills/install.py +293 -0
  382. alysis_code/skills/loader.py +118 -0
  383. alysis_code/skills/matching.py +103 -0
  384. alysis_code/skills/models.py +71 -0
  385. alysis_code/skills/paths.py +56 -0
  386. alysis_code/skills/prompting.py +500 -0
  387. alysis_code/skills/scaffold.py +142 -0
  388. alysis_code/skills/state.py +441 -0
  389. alysis_code/skills/transactions.py +125 -0
  390. alysis_code/skills/validation.py +304 -0
  391. alysis_code/step_budget.py +238 -0
  392. alysis_code/subagent_labels.py +49 -0
  393. alysis_code/subagents.py +1072 -0
  394. alysis_code/surface/__init__.py +80 -0
  395. alysis_code/surface/base.py +305 -0
  396. alysis_code/surface/console.py +387 -0
  397. alysis_code/surface/events.py +372 -0
  398. alysis_code/surface/hidden_surface.py +529 -0
  399. alysis_code/surface/noop_surface.py +219 -0
  400. alysis_code/surface/rich_surface.py +1555 -0
  401. alysis_code/surface/styles.py +67 -0
  402. alysis_code/surface/theme.py +455 -0
  403. alysis_code/surface/types.py +100 -0
  404. alysis_code/swarm_backend.py +926 -0
  405. alysis_code/swarm_orchestrator.py +4020 -0
  406. alysis_code/swarm_scheduler.py +441 -0
  407. alysis_code/swarm_trace.py +429 -0
  408. alysis_code/swarm_worker.py +2119 -0
  409. alysis_code/swarm_write_guard.py +348 -0
  410. alysis_code/task_dependencies.py +170 -0
  411. alysis_code/task_readiness.py +992 -0
  412. alysis_code/task_scope.py +2148 -0
  413. alysis_code/terminal_manager.py +762 -0
  414. alysis_code/terminal_ownership.py +460 -0
  415. alysis_code/text_normalization.py +30 -0
  416. alysis_code/token_budget.py +97 -0
  417. alysis_code/tools/README.md +34 -0
  418. alysis_code/tools/__init__.py +1 -0
  419. alysis_code/tools/artifacts.py +127 -0
  420. alysis_code/tools/availability.py +188 -0
  421. alysis_code/tools/fs.py +1456 -0
  422. alysis_code/tools/git.py +461 -0
  423. alysis_code/tools/history.py +229 -0
  424. alysis_code/tools/http_timeout.py +78 -0
  425. alysis_code/tools/image_generation.py +552 -0
  426. alysis_code/tools/registry.py +2936 -0
  427. alysis_code/tools/repo_map.py +476 -0
  428. alysis_code/tools/search.py +563 -0
  429. alysis_code/tools/shell.py +135 -0
  430. alysis_code/tools/symbols.py +1350 -0
  431. alysis_code/tools/test_discovery.py +643 -0
  432. alysis_code/tools/web.py +482 -0
  433. alysis_code/tools/web_search.py +2012 -0
  434. alysis_code/tools/web_search_dashscope.py +557 -0
  435. alysis_code/tools/web_search_ddgs.py +221 -0
  436. alysis_code/tools/web_search_provider_adapters.py +1429 -0
  437. alysis_code/tools/web_search_tavily.py +194 -0
  438. alysis_code/updates.py +933 -0
  439. alysis_code/usage_tracker.py +1990 -0
  440. alysis_code/verification_command_analysis.py +1004 -0
  441. alysis_code/verification_contract.py +574 -0
  442. alysis_code/verification_failure_summary.py +273 -0
  443. alysis_code/verification_repair.py +385 -0
  444. alysis_code/verify_gate.py +3129 -0
  445. alysis_code/web_research.py +1872 -0
  446. alysis_code/web_search_adapters.py +66 -0
  447. alysis_code/web_search_policy.py +27 -0
  448. alysis_code/workspace_binding.py +389 -0
  449. alysis_code/workspace_binding_ui.py +408 -0
  450. alysis_code/workspace_context.py +273 -0
  451. alysis_code/workspace_isolation.py +138 -0
  452. alysis_code/workspace_provisioning.py +455 -0
  453. alysis_code-0.13.0.dist-info/METADATA +507 -0
  454. alysis_code-0.13.0.dist-info/RECORD +458 -0
  455. alysis_code-0.13.0.dist-info/WHEEL +4 -0
  456. alysis_code-0.13.0.dist-info/entry_points.txt +3 -0
  457. alysis_code-0.13.0.dist-info/licenses/LICENSE +176 -0
  458. alysis_code-0.13.0.dist-info/licenses/NOTICE +4 -0
@@ -0,0 +1,664 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import shutil
6
+ import signal
7
+ import subprocess
8
+ import tempfile
9
+ import uuid
10
+ from collections.abc import Mapping
11
+ from contextlib import suppress
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from ..config import AgentRuntimeSettings
16
+ from .base import (
17
+ AuthMethod,
18
+ RuntimeAccountStatus,
19
+ RuntimeCapabilities,
20
+ RuntimeProbeStatus,
21
+ RuntimeTurnRequest,
22
+ RuntimeTurnResult,
23
+ )
24
+
25
+ _VERSION_TIMEOUT_SECONDS = 5.0
26
+ _STATUS_TIMEOUT_SECONDS = 10.0
27
+ _PROCESS_TREE_GRACE_SECONDS = 2.0
28
+ _TIMEOUT_EXIT_CODE = 124
29
+ _MISSING_EXECUTABLE_EXIT_CODE = 127
30
+ _INVALID_REQUEST_EXIT_CODE = 2
31
+ _FORCED_CHATGPT_LOGIN_CONFIG = 'forced_login_method="chatgpt"'
32
+ _RUNTIME_ENV_ALLOWLIST = frozenset(
33
+ {
34
+ "ALL_PROXY",
35
+ "BROWSER",
36
+ "CODEX_HOME",
37
+ "COLORTERM",
38
+ "COMSPEC",
39
+ "CURL_CA_BUNDLE",
40
+ "DBUS_SESSION_BUS_ADDRESS",
41
+ "DISPLAY",
42
+ "HOME",
43
+ "HTTP_PROXY",
44
+ "HTTPS_PROXY",
45
+ "LANG",
46
+ "LC_ALL",
47
+ "LC_CTYPE",
48
+ "LOGNAME",
49
+ "NIX_SSL_CERT_FILE",
50
+ "NO_PROXY",
51
+ "PATH",
52
+ "PATHEXT",
53
+ "REQUESTS_CA_BUNDLE",
54
+ "SHELL",
55
+ "SSL_CERT_DIR",
56
+ "SSL_CERT_FILE",
57
+ "SYSTEMROOT",
58
+ "TEMP",
59
+ "TERM",
60
+ "TMP",
61
+ "TMPDIR",
62
+ "USER",
63
+ "USERPROFILE",
64
+ "WINDIR",
65
+ "XDG_CACHE_HOME",
66
+ "XDG_CONFIG_HOME",
67
+ "XDG_RUNTIME_DIR",
68
+ "WAYLAND_DISPLAY",
69
+ }
70
+ )
71
+
72
+
73
+ class CodexCliRuntimeAdapter:
74
+ """Account and installation boundary for the official Codex CLI.
75
+
76
+ The adapter invokes documented Codex commands and intentionally never reads
77
+ Codex's credential files or transports an OAuth token through Alysis Code.
78
+ """
79
+
80
+ runtime_id = "openai-codex"
81
+ adapter_id = "codex-cli"
82
+ display_name = "OpenAI Codex account"
83
+ description = "Delegate work to Codex using its official ChatGPT sign-in."
84
+ default_executable = "codex"
85
+ auth_hint = (
86
+ "Browser or device-code sign-in is managed by Codex; Alysis Code never reads its tokens."
87
+ )
88
+ capabilities = RuntimeCapabilities(
89
+ streaming=False,
90
+ session_resume=True,
91
+ image_inputs=True,
92
+ structured_output=True,
93
+ read_only=True,
94
+ workspace_write=True,
95
+ )
96
+ auth_methods = (
97
+ AuthMethod(id="browser", label="Sign in with ChatGPT in a browser"),
98
+ AuthMethod(id="device-code", label="Sign in with a device code"),
99
+ )
100
+
101
+ def probe(self, settings: AgentRuntimeSettings) -> RuntimeProbeStatus:
102
+ executable = _resolve_executable(settings.executable)
103
+ if executable is None:
104
+ return RuntimeProbeStatus(
105
+ available=False,
106
+ executable=settings.executable,
107
+ detail="Codex CLI is not installed or is not executable.",
108
+ )
109
+ try:
110
+ result = subprocess.run(
111
+ [executable, "--version"],
112
+ check=False,
113
+ capture_output=True,
114
+ text=True,
115
+ timeout=_VERSION_TIMEOUT_SECONDS,
116
+ env=_runtime_environment(),
117
+ )
118
+ except (OSError, subprocess.TimeoutExpired) as exc:
119
+ return RuntimeProbeStatus(
120
+ available=False,
121
+ executable=executable,
122
+ detail=f"Codex version check failed: {exc}",
123
+ )
124
+ version = _combined_output(result)
125
+ if result.returncode != 0:
126
+ return RuntimeProbeStatus(
127
+ available=False,
128
+ executable=executable,
129
+ version=version or None,
130
+ detail="Codex version check returned a non-zero exit status.",
131
+ )
132
+ return RuntimeProbeStatus(
133
+ available=True,
134
+ executable=executable,
135
+ version=version or None,
136
+ detail="Codex CLI is available.",
137
+ )
138
+
139
+ def account_status(self, settings: AgentRuntimeSettings) -> RuntimeAccountStatus:
140
+ probe = self.probe(settings)
141
+ if not probe.available or not probe.executable:
142
+ return RuntimeAccountStatus(authenticated=False, verified=False, detail=probe.detail)
143
+ try:
144
+ result = subprocess.run(
145
+ [probe.executable, "login", "status"],
146
+ check=False,
147
+ capture_output=True,
148
+ text=True,
149
+ timeout=_STATUS_TIMEOUT_SECONDS,
150
+ env=_runtime_environment(),
151
+ )
152
+ except (OSError, subprocess.TimeoutExpired) as exc:
153
+ return RuntimeAccountStatus(
154
+ authenticated=False,
155
+ verified=False,
156
+ detail=f"Codex login status check failed: {exc}",
157
+ )
158
+ output = _combined_output(result)
159
+ if result.returncode != 0:
160
+ return RuntimeAccountStatus(
161
+ authenticated=False,
162
+ detail=output or "Codex is not signed in.",
163
+ )
164
+ method = _login_method(output)
165
+ if method != "chatgpt":
166
+ return RuntimeAccountStatus(
167
+ authenticated=False,
168
+ verified=True,
169
+ auth_method_id=method,
170
+ detail=(
171
+ "Codex is signed in, but not with a ChatGPT account. "
172
+ "Run `alysis auth login openai-codex --switch-account`."
173
+ ),
174
+ )
175
+ return RuntimeAccountStatus(
176
+ authenticated=True,
177
+ auth_method_id=method,
178
+ account_label=_account_label(output),
179
+ detail=output or "Codex is signed in.",
180
+ )
181
+
182
+ def login(
183
+ self,
184
+ settings: AgentRuntimeSettings,
185
+ method_id: str,
186
+ ) -> RuntimeAccountStatus:
187
+ probe = self.probe(settings)
188
+ if not probe.available or not probe.executable:
189
+ return RuntimeAccountStatus(authenticated=False, verified=False, detail=probe.detail)
190
+ normalized = str(method_id or "").strip().lower()
191
+ if normalized not in {method.id for method in self.auth_methods}:
192
+ return RuntimeAccountStatus(
193
+ authenticated=False,
194
+ verified=False,
195
+ detail=f"Unsupported Codex authentication method: {method_id!r}.",
196
+ )
197
+ command = [probe.executable, "login"]
198
+ if normalized == "device-code":
199
+ command.append("--device-auth")
200
+ try:
201
+ result = subprocess.run(command, check=False, env=_runtime_environment())
202
+ except OSError as exc:
203
+ return RuntimeAccountStatus(
204
+ authenticated=False,
205
+ verified=False,
206
+ detail=f"Could not start Codex login: {exc}",
207
+ )
208
+ if result.returncode != 0:
209
+ return RuntimeAccountStatus(
210
+ authenticated=False,
211
+ verified=False,
212
+ detail="Codex login did not complete successfully.",
213
+ )
214
+ return self.account_status(settings)
215
+
216
+ def logout(self, settings: AgentRuntimeSettings) -> RuntimeAccountStatus:
217
+ probe = self.probe(settings)
218
+ if not probe.available or not probe.executable:
219
+ return RuntimeAccountStatus(authenticated=False, verified=False, detail=probe.detail)
220
+ try:
221
+ result = subprocess.run(
222
+ [probe.executable, "logout"],
223
+ check=False,
224
+ env=_runtime_environment(),
225
+ )
226
+ except OSError as exc:
227
+ return RuntimeAccountStatus(
228
+ authenticated=True,
229
+ verified=False,
230
+ detail=f"Could not start Codex logout: {exc}",
231
+ )
232
+ if result.returncode != 0:
233
+ return RuntimeAccountStatus(
234
+ authenticated=True,
235
+ verified=False,
236
+ detail="Codex logout did not complete successfully.",
237
+ )
238
+ return RuntimeAccountStatus(authenticated=False, detail="Codex is signed out.")
239
+
240
+ def run_turn(
241
+ self,
242
+ settings: AgentRuntimeSettings,
243
+ request: RuntimeTurnRequest,
244
+ ) -> RuntimeTurnResult:
245
+ if not settings.provider_managed_auth:
246
+ raise ValueError("The Codex account runtime requires provider-managed authentication.")
247
+ sandbox = _codex_sandbox(request.mode)
248
+ cwd = Path(request.cwd).expanduser().resolve()
249
+ if not cwd.is_dir():
250
+ return RuntimeTurnResult(
251
+ runtime_id=self.runtime_id,
252
+ command=(),
253
+ exit_code=_INVALID_REQUEST_EXIT_CODE,
254
+ error=f"Delegated runtime cwd is not a directory: {cwd}",
255
+ )
256
+ # Validate resume identifiers before probing the host executable. Invalid
257
+ # option-shaped input is a request error even on hosts without Codex.
258
+ _validated_session_id(request.session_id)
259
+ executable = _resolve_executable(settings.executable)
260
+ if executable is None:
261
+ return RuntimeTurnResult(
262
+ runtime_id=self.runtime_id,
263
+ command=(),
264
+ exit_code=_MISSING_EXECUTABLE_EXIT_CODE,
265
+ error="Codex CLI is not installed or is not executable.",
266
+ )
267
+
268
+ with tempfile.TemporaryDirectory(prefix="alysis-codex-turn-") as temp_dir:
269
+ final_message_path = Path(temp_dir) / "last-message.txt"
270
+ command = _build_turn_command(
271
+ executable=executable,
272
+ settings=settings,
273
+ request=request,
274
+ cwd=cwd,
275
+ sandbox=sandbox,
276
+ final_message_path=final_message_path,
277
+ )
278
+ stdout, stderr, exit_code, timed_out, launch_error = _run_process(
279
+ command,
280
+ prompt=request.prompt,
281
+ cwd=cwd,
282
+ timeout_seconds=settings.timeout_seconds,
283
+ )
284
+ events, warnings = _parse_jsonl_events(stdout)
285
+ if request.mode == "review":
286
+ warnings = (
287
+ "Review mode was enforced as read-only by the Codex runtime.",
288
+ *warnings,
289
+ )
290
+ final_message = _read_final_message(final_message_path) or _event_final_message(events)
291
+ session_id = (
292
+ None if request.no_log else (_event_session_id(events) or request.session_id)
293
+ )
294
+ usage = _event_usage(events)
295
+ error = launch_error
296
+ if timed_out:
297
+ error = f"Codex turn timed out after {settings.timeout_seconds:g} seconds."
298
+ elif exit_code != 0 and error is None:
299
+ error = (
300
+ _event_error(events)
301
+ or stderr.strip()
302
+ or (f"Codex turn exited with status {exit_code}.")
303
+ )
304
+ return RuntimeTurnResult(
305
+ runtime_id=self.runtime_id,
306
+ command=tuple(command),
307
+ exit_code=exit_code,
308
+ final_message=final_message,
309
+ session_id=session_id,
310
+ stdout=stdout,
311
+ stderr=stderr,
312
+ events=events,
313
+ usage=usage,
314
+ timed_out=timed_out,
315
+ error=error,
316
+ warnings=warnings,
317
+ )
318
+
319
+
320
+ def _codex_sandbox(mode: str) -> str:
321
+ normalized = str(mode or "").strip().lower().replace("_", "-")
322
+ if normalized in {"readonly", "read-only", "review"}:
323
+ return "read-only"
324
+ if normalized in {"auto", "workspace-write"}:
325
+ return "workspace-write"
326
+ if normalized in {"fullaccess", "full-access", "danger-full-access"}:
327
+ raise ValueError(
328
+ "The Codex account runtime does not support fullaccess; use readonly, review, or auto."
329
+ )
330
+ raise ValueError(f"Unsupported delegated runtime mode: {mode!r}.")
331
+
332
+
333
+ def _build_turn_command(
334
+ *,
335
+ executable: str,
336
+ settings: AgentRuntimeSettings,
337
+ request: RuntimeTurnRequest,
338
+ cwd: Path,
339
+ sandbox: str,
340
+ final_message_path: Path,
341
+ ) -> list[str]:
342
+ session_id = _validated_session_id(request.session_id)
343
+ if session_id:
344
+ command = [
345
+ executable,
346
+ "exec",
347
+ "resume",
348
+ "--json",
349
+ "-c",
350
+ _FORCED_CHATGPT_LOGIN_CONFIG,
351
+ "-c",
352
+ f'sandbox_mode="{sandbox}"',
353
+ "--ignore-user-config",
354
+ "--ignore-rules",
355
+ ]
356
+ else:
357
+ command = [
358
+ executable,
359
+ "exec",
360
+ "--json",
361
+ "--sandbox",
362
+ sandbox,
363
+ "-C",
364
+ os.fspath(cwd),
365
+ "-c",
366
+ _FORCED_CHATGPT_LOGIN_CONFIG,
367
+ "--ignore-user-config",
368
+ "--ignore-rules",
369
+ ]
370
+ command.extend(
371
+ [
372
+ "--skip-git-repo-check",
373
+ "--output-last-message",
374
+ os.fspath(final_message_path),
375
+ ]
376
+ )
377
+ model = str(settings.model or "").strip()
378
+ if model:
379
+ command.extend(["--model", model])
380
+ for image in request.images:
381
+ image_path = Path(image).expanduser()
382
+ if not image_path.is_absolute():
383
+ image_path = cwd / image_path
384
+ command.extend(["--image", os.fspath(image_path.resolve())])
385
+ if request.no_log:
386
+ command.append("--ephemeral")
387
+ if session_id:
388
+ command.append("--")
389
+ command.append(session_id)
390
+ command.append("-")
391
+ return command
392
+
393
+
394
+ def _run_process(
395
+ command: list[str],
396
+ *,
397
+ prompt: str,
398
+ cwd: Path,
399
+ timeout_seconds: float,
400
+ ) -> tuple[str, str, int, bool, str | None]:
401
+ popen_kwargs: dict[str, Any] = {
402
+ "cwd": os.fspath(cwd),
403
+ "env": _runtime_environment(),
404
+ "stdin": subprocess.PIPE,
405
+ "stdout": subprocess.PIPE,
406
+ "stderr": subprocess.PIPE,
407
+ "text": True,
408
+ "encoding": "utf-8",
409
+ "errors": "replace",
410
+ }
411
+ if os.name == "nt":
412
+ popen_kwargs["creationflags"] = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
413
+ else:
414
+ popen_kwargs["start_new_session"] = True
415
+ try:
416
+ process = subprocess.Popen(command, **popen_kwargs)
417
+ except OSError as exc:
418
+ message = f"Could not start Codex CLI: {exc}"
419
+ return "", message, _MISSING_EXECUTABLE_EXIT_CODE, False, message
420
+
421
+ try:
422
+ stdout, stderr = process.communicate(input=str(prompt), timeout=float(timeout_seconds))
423
+ except subprocess.TimeoutExpired as exc:
424
+ partial_stdout = _coerce_process_text(exc.stdout)
425
+ partial_stderr = _coerce_process_text(exc.stderr)
426
+ _terminate_process_tree(process)
427
+ try:
428
+ stdout, stderr = process.communicate()
429
+ except (OSError, ValueError):
430
+ stdout, stderr = partial_stdout, partial_stderr
431
+ return (
432
+ _coerce_process_text(stdout) or partial_stdout,
433
+ _coerce_process_text(stderr) or partial_stderr,
434
+ _TIMEOUT_EXIT_CODE,
435
+ True,
436
+ None,
437
+ )
438
+ except BaseException:
439
+ # KeyboardInterrupt/SystemExit must not orphan a provider process (or
440
+ # any descendants) after Alysis Code returns control to the terminal.
441
+ with suppress(BaseException):
442
+ _terminate_process_tree(process)
443
+ raise
444
+ return (
445
+ _coerce_process_text(stdout),
446
+ _coerce_process_text(stderr),
447
+ int(process.returncode if process.returncode is not None else 1),
448
+ False,
449
+ None,
450
+ )
451
+
452
+
453
+ def _runtime_environment(source: Mapping[str, str] | None = None) -> dict[str, str]:
454
+ environment = source if source is not None else os.environ
455
+ allowed: dict[str, str] = {}
456
+ for key, value in environment.items():
457
+ normalized = str(key).upper()
458
+ if normalized not in _RUNTIME_ENV_ALLOWLIST:
459
+ continue
460
+ if normalized.endswith(("_API_KEY", "_TOKEN", "_SECRET", "_PASSWORD")):
461
+ continue
462
+ allowed[str(key)] = str(value)
463
+ return allowed
464
+
465
+
466
+ def _terminate_process_tree(process: subprocess.Popen[str]) -> None:
467
+ if process.poll() is not None:
468
+ return
469
+ if os.name == "nt":
470
+ with suppress(OSError, subprocess.TimeoutExpired):
471
+ subprocess.run(
472
+ ["taskkill", "/PID", str(process.pid), "/T", "/F"],
473
+ check=False,
474
+ stdout=subprocess.DEVNULL,
475
+ stderr=subprocess.DEVNULL,
476
+ timeout=_PROCESS_TREE_GRACE_SECONDS,
477
+ )
478
+ if process.poll() is None:
479
+ with suppress(OSError):
480
+ process.kill()
481
+ with suppress(OSError, subprocess.TimeoutExpired):
482
+ process.wait(timeout=_PROCESS_TREE_GRACE_SECONDS)
483
+ return
484
+
485
+ used_process_group = False
486
+ try:
487
+ os.killpg(process.pid, signal.SIGTERM)
488
+ used_process_group = True
489
+ except ProcessLookupError:
490
+ return
491
+ except OSError:
492
+ with suppress(OSError):
493
+ process.terminate()
494
+ with suppress(OSError, subprocess.TimeoutExpired):
495
+ process.wait(timeout=_PROCESS_TREE_GRACE_SECONDS)
496
+ if used_process_group:
497
+ # The parent may have exited while a child ignored SIGTERM. Because the
498
+ # process started in a dedicated session, killing the group is safe and
499
+ # guarantees that no descendant survives the timed-out turn.
500
+ with suppress(ProcessLookupError, OSError):
501
+ os.killpg(process.pid, signal.SIGKILL)
502
+ elif process.poll() is None:
503
+ with suppress(OSError):
504
+ process.kill()
505
+ with suppress(OSError, subprocess.TimeoutExpired):
506
+ process.wait(timeout=_PROCESS_TREE_GRACE_SECONDS)
507
+
508
+
509
+ def _coerce_process_text(value: object) -> str:
510
+ if value is None:
511
+ return ""
512
+ if isinstance(value, bytes):
513
+ return value.decode("utf-8", errors="replace")
514
+ return str(value)
515
+
516
+
517
+ def _parse_jsonl_events(
518
+ stdout: str,
519
+ ) -> tuple[tuple[Mapping[str, object], ...], tuple[str, ...]]:
520
+ events: list[Mapping[str, object]] = []
521
+ warnings: list[str] = []
522
+ for line_number, line in enumerate(str(stdout or "").splitlines(), start=1):
523
+ text = line.strip()
524
+ if not text:
525
+ continue
526
+ try:
527
+ parsed = json.loads(text)
528
+ except json.JSONDecodeError:
529
+ warnings.append(f"Ignored invalid Codex JSONL event on line {line_number}.")
530
+ continue
531
+ if not isinstance(parsed, dict):
532
+ warnings.append(f"Ignored non-object Codex JSONL event on line {line_number}.")
533
+ continue
534
+ events.append(parsed)
535
+ return tuple(events), tuple(warnings)
536
+
537
+
538
+ def _read_final_message(path: Path) -> str:
539
+ try:
540
+ return path.read_text(encoding="utf-8").strip()
541
+ except OSError:
542
+ return ""
543
+
544
+
545
+ def _event_final_message(events: tuple[Mapping[str, object], ...]) -> str:
546
+ for event in reversed(events):
547
+ item = event.get("item")
548
+ if isinstance(item, Mapping) and str(item.get("type") or "") == "agent_message":
549
+ text = _message_text(item)
550
+ if text:
551
+ return text
552
+ event_type = str(event.get("type") or "")
553
+ if event_type in {"agent_message", "assistant_message", "message.completed"}:
554
+ text = _message_text(event)
555
+ if text:
556
+ return text
557
+ return ""
558
+
559
+
560
+ def _message_text(value: Mapping[str, object]) -> str:
561
+ direct = value.get("text") or value.get("message")
562
+ if isinstance(direct, str):
563
+ return direct.strip()
564
+ content = value.get("content")
565
+ if isinstance(content, str):
566
+ return content.strip()
567
+ if isinstance(content, list):
568
+ parts: list[str] = []
569
+ for block in content:
570
+ if isinstance(block, Mapping):
571
+ text = block.get("text")
572
+ if isinstance(text, str) and text.strip():
573
+ parts.append(text.strip())
574
+ return "\n".join(parts)
575
+ return ""
576
+
577
+
578
+ def _event_session_id(events: tuple[Mapping[str, object], ...]) -> str | None:
579
+ for event in events:
580
+ for key in ("thread_id", "session_id"):
581
+ value = event.get(key)
582
+ if isinstance(value, str) and value.strip():
583
+ return value.strip()
584
+ thread = event.get("thread")
585
+ if isinstance(thread, Mapping):
586
+ value = thread.get("id")
587
+ if isinstance(value, str) and value.strip():
588
+ return value.strip()
589
+ return None
590
+
591
+
592
+ def _event_usage(events: tuple[Mapping[str, object], ...]) -> Mapping[str, object] | None:
593
+ for event in reversed(events):
594
+ usage = event.get("usage")
595
+ if isinstance(usage, Mapping):
596
+ return dict(usage)
597
+ return None
598
+
599
+
600
+ def _event_error(events: tuple[Mapping[str, object], ...]) -> str | None:
601
+ for event in reversed(events):
602
+ event_type = str(event.get("type") or "")
603
+ if event_type not in {"error", "turn.failed", "item.failed"}:
604
+ continue
605
+ error = event.get("error")
606
+ if isinstance(error, Mapping):
607
+ message = error.get("message")
608
+ if isinstance(message, str) and message.strip():
609
+ return message.strip()
610
+ message = event.get("message")
611
+ if isinstance(message, str) and message.strip():
612
+ return message.strip()
613
+ return None
614
+
615
+
616
+ def _resolve_executable(configured: str) -> str | None:
617
+ value = str(configured or "").strip()
618
+ if not value:
619
+ return None
620
+ if os.path.basename(value) == value:
621
+ return shutil.which(value)
622
+ path = Path(value).expanduser()
623
+ if not path.is_file() or not os.access(path, os.X_OK):
624
+ return None
625
+ return os.fspath(path)
626
+
627
+
628
+ def _validated_session_id(raw: str | None) -> str:
629
+ value = str(raw or "").strip()
630
+ if not value:
631
+ return ""
632
+ try:
633
+ return str(uuid.UUID(value))
634
+ except ValueError as exc:
635
+ raise ValueError("Delegated Codex session ids must be UUIDs.") from exc
636
+
637
+
638
+ def _combined_output(result: subprocess.CompletedProcess[str]) -> str:
639
+ lines = []
640
+ for line in str(result.stdout or result.stderr or "").splitlines():
641
+ if line.strip().casefold().startswith("warning: proceeding"):
642
+ continue
643
+ lines.append(line)
644
+ return "\n".join(lines).strip()
645
+
646
+
647
+ def _login_method(output: str) -> str | None:
648
+ normalized = str(output or "").casefold()
649
+ if "chatgpt" in normalized or "chat gpt" in normalized:
650
+ return "chatgpt"
651
+ if "api key" in normalized or "api-key" in normalized:
652
+ return "api-key"
653
+ return "provider-managed"
654
+
655
+
656
+ def _account_label(output: str) -> str | None:
657
+ for line in str(output or "").splitlines():
658
+ stripped = line.strip()
659
+ if "@" in stripped and len(stripped) <= 200:
660
+ return stripped
661
+ return None
662
+
663
+
664
+ __all__ = ["CodexCliRuntimeAdapter"]