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,623 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ import typer
7
+ from rich.table import Table
8
+
9
+ from ...agent_runtimes.service import (
10
+ RuntimeConnectionError,
11
+ activate_runtime,
12
+ login_runtime,
13
+ logout_runtime,
14
+ runtime_connection_snapshot,
15
+ )
16
+ from ...auth_diagnostics import credential_backend_fields
17
+ from ...config import AppConfig, ConfigError, load_config, save_config
18
+ from ...profiles import (
19
+ SUBSCRIPTION_SELECTION_REQUIRED_KEY,
20
+ ProfileSpec,
21
+ add_profile,
22
+ get_active_profile,
23
+ get_profile,
24
+ set_active_profile,
25
+ subscription_selection_supported,
26
+ )
27
+ from ...provider_auth import (
28
+ ProviderAuthError,
29
+ create_provider_auth,
30
+ provider_auth_setup_options,
31
+ )
32
+ from . import _patchable
33
+ from ._shared import _console
34
+
35
+ auth_app = typer.Typer(
36
+ add_completion=False,
37
+ help="Manage secure AI subscription connections.",
38
+ )
39
+
40
+ ALYSIS_LOGIN_CONNECTION_ID = "alysis"
41
+
42
+ JSON_OPTION_HELP = (
43
+ "Emit one machine-readable JSON object on stdout and nothing else. "
44
+ "Exit code 0 means the command ran; the payload carries the auth state."
45
+ )
46
+
47
+
48
+ def _status_payload(
49
+ *,
50
+ connection: str,
51
+ authenticated: bool,
52
+ account_label: str | None = None,
53
+ method: str | None = None,
54
+ detail: str | None = None,
55
+ transport: str | None = None,
56
+ error: str | None = None,
57
+ **extra: Any,
58
+ ) -> dict[str, Any]:
59
+ """Build the auth status contract a supervising app can read.
60
+
61
+ ``authenticated`` is data, not an outcome: a false value is still a
62
+ successful probe, so the caller never has to disambiguate it from a failure
63
+ of the command itself. The keyring fields explain why a spawned process can
64
+ see different auth state than the terminal the user logged in from.
65
+ """
66
+
67
+ payload: dict[str, Any] = {
68
+ "connection": connection,
69
+ "authenticated": bool(authenticated),
70
+ "account_label": account_label,
71
+ "method": method,
72
+ "detail": detail,
73
+ "transport": transport,
74
+ "error": error,
75
+ }
76
+ payload.update(credential_backend_fields())
77
+ payload.update(extra)
78
+ return payload
79
+
80
+
81
+ def _emit_json(payload: dict[str, Any]) -> None:
82
+ """Write exactly one JSON object to stdout."""
83
+
84
+ typer.echo(json.dumps(payload, sort_keys=True))
85
+
86
+
87
+ def _print_credential_backend_note(console: Any) -> None:
88
+ """Say so when credentials are not protected by the OS keyring.
89
+
90
+ Silent degradation is what makes auth state look inconsistent between a
91
+ terminal and a spawned process, so an interactive user gets told too.
92
+ """
93
+
94
+ fields = credential_backend_fields()
95
+ if fields["keyring_available"]:
96
+ return
97
+ console.print(
98
+ f"[yellow]OS keyring unavailable[/yellow] "
99
+ f"(backend: {fields['keyring_backend'] or 'unresolved'}) — credentials use the "
100
+ f"{fields['credential_fallback']} key store instead. "
101
+ "Run `alysis doctor auth` for details."
102
+ )
103
+
104
+
105
+ def login_connection_rows() -> list[tuple[str, str, str]]:
106
+ """Return every account connection offered by the unified login picker."""
107
+
108
+ rows = [
109
+ (
110
+ ALYSIS_LOGIN_CONNECTION_ID,
111
+ "Alysis Code account",
112
+ "Connect Alysis Code-hosted model access and account entitlements.",
113
+ )
114
+ ]
115
+ rows.extend(
116
+ (option.id, option.label, option.description) for option in provider_auth_setup_options()
117
+ )
118
+ return rows
119
+
120
+
121
+ def login_connection_interactively(
122
+ connection_id: str,
123
+ *,
124
+ console: Any | None = None,
125
+ ) -> None:
126
+ """Run one connection's browser flow outside the alternate-screen TUI."""
127
+
128
+ resolved = str(connection_id or "").strip()
129
+ output = console or _console()
130
+ if resolved == ALYSIS_LOGIN_CONNECTION_ID:
131
+ from ... import account_login
132
+
133
+ cfg = _patchable("load_config", load_config)()
134
+ try:
135
+ result = account_login.login(
136
+ cfg,
137
+ output_write=lambda message: output.print(message, highlight=False),
138
+ )
139
+ except (account_login.AlysisLoginError, ConfigError) as exc:
140
+ output.print(f"[red]{exc}[/red]")
141
+ raise typer.Exit(code=1) from exc
142
+ who = f" as [bold]{result.email}[/bold]" if result.email else ""
143
+ output.print(f"[green]Logged in{who}.[/green] Your Alysis Code account is connected.")
144
+ if result.model:
145
+ output.print(
146
+ f"Active profile: [bold]{result.profile_name}[/bold] · default model: "
147
+ f"[bold]{result.model}[/bold]"
148
+ )
149
+ else:
150
+ output.print(
151
+ f"Active profile: [bold]{result.profile_name}[/bold] · choose a model in /config."
152
+ )
153
+ return
154
+ known = {row[0] for row in login_connection_rows()}
155
+ if resolved not in known:
156
+ choices = ", ".join(row[0] for row in login_connection_rows())
157
+ output.print(
158
+ f"[red]Unknown login connection:[/red] {resolved or '(empty)'}. Available: {choices}."
159
+ )
160
+ raise typer.Exit(code=2)
161
+ login_provider_interactively(runtime_id=resolved)
162
+
163
+
164
+ @auth_app.command("list")
165
+ def auth_list(
166
+ json_output: bool = typer.Option(False, "--json", help=JSON_OPTION_HELP),
167
+ ) -> None:
168
+ """List supported subscription connections and account state."""
169
+
170
+ cfg = _patchable("load_config", load_config)()
171
+ if json_output is True:
172
+ _emit_json(_auth_list_payload(cfg))
173
+ return
174
+ table = Table(title="AI subscription connections")
175
+ table.add_column("connection")
176
+ table.add_column("availability")
177
+ table.add_column("account")
178
+ table.add_column("active")
179
+ for option in provider_auth_setup_options():
180
+ if _is_direct_provider(option.id):
181
+ try:
182
+ status = create_provider_auth(option.id).account_status()
183
+ installation = "built in"
184
+ account = _account_status_label(status.connected, status.detail)
185
+ except ProviderAuthError as exc:
186
+ installation = "built in"
187
+ account = str(exc)
188
+ active = "yes" if _active_auth_provider(cfg) == option.id else ""
189
+ table.add_row(option.label, installation, account, active)
190
+ continue
191
+ try:
192
+ snapshot = runtime_connection_snapshot(cfg, option.id)
193
+ installation = snapshot.probe.version or (
194
+ "available" if snapshot.probe.available else "missing"
195
+ )
196
+ account = _account_status_label(snapshot.account.authenticated, snapshot.account.detail)
197
+ except RuntimeConnectionError as exc:
198
+ installation = "unavailable"
199
+ account = str(exc)
200
+ active = (
201
+ "yes"
202
+ if cfg.execution.backend == "delegated" and cfg.execution.runtime == option.id
203
+ else ""
204
+ )
205
+ table.add_row(option.label, installation, account, active)
206
+ console = _console()
207
+ console.print(table)
208
+ _print_credential_backend_note(console)
209
+
210
+
211
+ def _auth_list_payload(cfg: AppConfig) -> dict[str, Any]:
212
+ """Describe every offered connection without printing human output."""
213
+
214
+ connections: list[dict[str, Any]] = []
215
+ for option in provider_auth_setup_options():
216
+ if _is_direct_provider(option.id):
217
+ active = _active_auth_provider(cfg) == option.id
218
+ try:
219
+ adapter = create_provider_auth(option.id)
220
+ status = adapter.account_status()
221
+ except ProviderAuthError as exc:
222
+ connections.append(
223
+ _status_payload(
224
+ connection=option.id,
225
+ authenticated=False,
226
+ error=str(exc),
227
+ display_name=option.label,
228
+ availability="built in",
229
+ active=active,
230
+ )
231
+ )
232
+ continue
233
+ connections.append(
234
+ _status_payload(
235
+ connection=option.id,
236
+ authenticated=status.connected,
237
+ account_label=status.account_label,
238
+ detail=status.detail,
239
+ transport=f"native Alysis Code client ({adapter.protocol})",
240
+ display_name=option.label,
241
+ availability="built in",
242
+ verified=bool(status.verified),
243
+ active=active,
244
+ )
245
+ )
246
+ continue
247
+ active = (
248
+ cfg.execution.backend == "delegated" and str(cfg.execution.runtime or "") == option.id
249
+ )
250
+ try:
251
+ snapshot = runtime_connection_snapshot(cfg, option.id)
252
+ except RuntimeConnectionError as exc:
253
+ connections.append(
254
+ _status_payload(
255
+ connection=option.id,
256
+ authenticated=False,
257
+ error=str(exc),
258
+ display_name=option.label,
259
+ availability="unavailable",
260
+ active=active,
261
+ )
262
+ )
263
+ continue
264
+ connections.append(
265
+ _status_payload(
266
+ connection=option.id,
267
+ authenticated=snapshot.account.authenticated,
268
+ account_label=snapshot.account.account_label,
269
+ method=snapshot.account.auth_method_id,
270
+ detail=snapshot.account.detail,
271
+ transport=f"delegated runtime ({snapshot.option.adapter})",
272
+ display_name=option.label,
273
+ availability=snapshot.probe.version
274
+ or ("available" if snapshot.probe.available else "missing"),
275
+ installed=bool(snapshot.probe.available),
276
+ verified=bool(snapshot.account.verified),
277
+ active=active,
278
+ )
279
+ )
280
+ payload: dict[str, Any] = {"connections": connections, "error": None}
281
+ payload.update(credential_backend_fields())
282
+ return payload
283
+
284
+
285
+ @auth_app.command("status")
286
+ def auth_status(
287
+ runtime_id: str | None = typer.Argument(
288
+ None,
289
+ help="Connection id. Defaults to the selected connection (or the only adapter).",
290
+ ),
291
+ json_output: bool = typer.Option(False, "--json", help=JSON_OPTION_HELP),
292
+ ) -> None:
293
+ """Show availability and provider-owned account status."""
294
+
295
+ emit_json = json_output is True
296
+ cfg = _patchable("load_config", load_config)()
297
+ resolved = _cli_runtime_id(cfg, runtime_id)
298
+ if _use_direct_provider(cfg, resolved, requested=runtime_id):
299
+ try:
300
+ adapter = create_provider_auth(resolved)
301
+ status = adapter.account_status()
302
+ except ProviderAuthError as exc:
303
+ if emit_json:
304
+ # The probe answered: the connection is not usable, and the
305
+ # reason is data rather than a failure of this command.
306
+ _emit_json(
307
+ _status_payload(
308
+ connection=resolved,
309
+ authenticated=False,
310
+ error=str(exc),
311
+ )
312
+ )
313
+ return
314
+ _console().print(f"[red]Authentication error:[/red] {exc}")
315
+ raise typer.Exit(code=2) from exc
316
+ if emit_json:
317
+ _emit_json(
318
+ _status_payload(
319
+ connection=resolved,
320
+ authenticated=status.connected,
321
+ account_label=status.account_label,
322
+ detail=status.detail,
323
+ transport=f"native Alysis Code client ({adapter.protocol})",
324
+ display_name=adapter.display_name,
325
+ verified=bool(status.verified),
326
+ )
327
+ )
328
+ return
329
+ console = _console()
330
+ console.print(f"Connection: {adapter.display_name} ({resolved})")
331
+ console.print(f"Transport: native Alysis Code client ({adapter.protocol})")
332
+ console.print(f"Authenticated: {'yes' if status.connected else 'no'}")
333
+ if status.account_label:
334
+ console.print(f"Account: {status.account_label}")
335
+ if status.detail:
336
+ console.print(f"Status: {status.detail}")
337
+ _print_credential_backend_note(console)
338
+ if not status.connected:
339
+ console.print(f"[dim]Run `alysis auth login {resolved}` to reconnect.[/dim]")
340
+ raise typer.Exit(code=1)
341
+ return
342
+ try:
343
+ snapshot = runtime_connection_snapshot(cfg, resolved)
344
+ except RuntimeConnectionError as exc:
345
+ if emit_json:
346
+ _emit_json(
347
+ _status_payload(
348
+ connection=resolved,
349
+ authenticated=False,
350
+ error=str(exc),
351
+ )
352
+ )
353
+ return
354
+ _console().print(f"[red]Runtime error:[/red] {exc}")
355
+ raise typer.Exit(code=2) from exc
356
+ if emit_json:
357
+ _emit_json(
358
+ _status_payload(
359
+ connection=resolved,
360
+ authenticated=snapshot.account.authenticated,
361
+ account_label=snapshot.account.account_label,
362
+ method=snapshot.account.auth_method_id,
363
+ detail=snapshot.account.detail,
364
+ transport=f"delegated runtime ({snapshot.option.adapter})",
365
+ display_name=snapshot.option.label,
366
+ executable=snapshot.probe.executable or snapshot.settings.executable,
367
+ installed=bool(snapshot.probe.available),
368
+ version=snapshot.probe.version,
369
+ verified=bool(snapshot.account.verified),
370
+ )
371
+ )
372
+ return
373
+ console = _console()
374
+ console.print(f"Runtime: {snapshot.option.label} ({snapshot.option.id})")
375
+ console.print(f"Executable: {snapshot.probe.executable or snapshot.settings.executable}")
376
+ console.print(f"Installed: {'yes' if snapshot.probe.available else 'no'}")
377
+ if snapshot.probe.version:
378
+ console.print(f"Version: {snapshot.probe.version}")
379
+ console.print(f"Authenticated: {'yes' if snapshot.account.authenticated else 'no'}")
380
+ if snapshot.account.auth_method_id:
381
+ console.print(f"Method: {snapshot.account.auth_method_id}")
382
+ if snapshot.account.account_label:
383
+ console.print(f"Account: {snapshot.account.account_label}")
384
+ if snapshot.account.detail:
385
+ console.print(f"Status: {snapshot.account.detail}")
386
+ _print_credential_backend_note(console)
387
+ if not snapshot.probe.available or not snapshot.account.authenticated:
388
+ raise typer.Exit(code=1)
389
+
390
+
391
+ @auth_app.command("login")
392
+ def auth_login(
393
+ runtime_id: str | None = typer.Argument(
394
+ None,
395
+ help="Connection id. Defaults to the selected connection (or the only adapter).",
396
+ ),
397
+ device_code: bool = typer.Option(
398
+ False,
399
+ "--device-code",
400
+ help="Use the provider's device-code flow instead of browser callback login.",
401
+ ),
402
+ switch_account: bool = typer.Option(
403
+ False,
404
+ "--switch-account",
405
+ help="Sign out from the provider before starting login.",
406
+ ),
407
+ ) -> None:
408
+ """Connect a provider subscription and activate it in Alysis Code."""
409
+
410
+ cfg = _patchable("load_config", load_config)()
411
+ resolved = _cli_runtime_id(cfg, runtime_id)
412
+ console = _console()
413
+ if _use_direct_provider(cfg, resolved, requested=runtime_id):
414
+ try:
415
+ adapter = create_provider_auth(resolved)
416
+ if switch_account:
417
+ adapter.logout()
418
+ status = adapter.login(
419
+ method="device-code" if device_code else "browser",
420
+ output_write=lambda message: console.print(message, highlight=False),
421
+ )
422
+ if not status.connected:
423
+ raise ProviderAuthError(status.detail or "Authentication failed.")
424
+ _activate_direct_provider(cfg, resolved, adapter)
425
+ _patchable("save_config", save_config)(cfg)
426
+ except ProviderAuthError as exc:
427
+ console.print(f"[red]Authentication failed:[/red] {exc}")
428
+ raise typer.Exit(code=1) from exc
429
+ console.print(f"[green]Connected:[/green] {status.account_label or resolved}")
430
+ console.print(
431
+ "[dim]Alysis Code stores refreshable credentials in its encrypted provider vault "
432
+ "and keeps the native agent/TUI active.[/dim]"
433
+ )
434
+ console.print(
435
+ "[dim]Choose the subscription model and reasoning effort in "
436
+ "`/config` → Default model.[/dim]"
437
+ )
438
+ return
439
+ try:
440
+ activate_runtime(cfg, resolved)
441
+ if switch_account:
442
+ logout_status = logout_runtime(cfg, resolved)
443
+ if not logout_status.verified or logout_status.authenticated:
444
+ detail = logout_status.detail or "provider credentials could not be removed"
445
+ raise RuntimeConnectionError(f"Could not switch account: {detail}")
446
+ method_id = "device-code" if device_code else "browser"
447
+ status = login_runtime(cfg, resolved, method_id=method_id)
448
+ except RuntimeConnectionError as exc:
449
+ console.print(f"[red]Runtime error:[/red] {exc}")
450
+ raise typer.Exit(code=2) from exc
451
+ if not status.verified or not status.authenticated:
452
+ console.print(f"[red]Authentication failed:[/red] {status.detail or 'unknown error'}")
453
+ raise typer.Exit(code=1)
454
+ _patchable("save_config", save_config)(cfg)
455
+ label = status.account_label or resolved
456
+ console.print(f"[green]Connected:[/green] {label}")
457
+ console.print(
458
+ "[dim]The provider runtime owns and refreshes its credentials; "
459
+ "Alysis Code stores only the selected runtime settings.[/dim]"
460
+ )
461
+
462
+
463
+ @auth_app.command("logout")
464
+ def auth_logout(
465
+ runtime_id: str | None = typer.Argument(
466
+ None,
467
+ help="Connection id. Defaults to the selected connection (or the only adapter).",
468
+ ),
469
+ ) -> None:
470
+ """Disconnect a provider subscription and remove locally stored credentials."""
471
+
472
+ cfg = _patchable("load_config", load_config)()
473
+ resolved = _cli_runtime_id(cfg, runtime_id)
474
+ if _use_direct_provider(cfg, resolved, requested=runtime_id):
475
+ try:
476
+ status = create_provider_auth(resolved).logout()
477
+ except ProviderAuthError as exc:
478
+ _console().print(f"[red]Logout failed:[/red] {exc}")
479
+ raise typer.Exit(code=1) from exc
480
+ _console().print(f"[green]Disconnected:[/green] {resolved}")
481
+ if not status.verified and status.detail:
482
+ _console().print(f"[yellow]{status.detail}[/yellow]")
483
+ return
484
+ try:
485
+ status = logout_runtime(cfg, resolved)
486
+ except RuntimeConnectionError as exc:
487
+ _console().print(f"[red]Runtime error:[/red] {exc}")
488
+ raise typer.Exit(code=2) from exc
489
+ if not status.verified or status.authenticated:
490
+ _console().print(f"[red]Logout failed:[/red] {status.detail or 'unknown error'}")
491
+ raise typer.Exit(code=1)
492
+ _console().print(f"[green]Disconnected:[/green] {resolved}")
493
+
494
+
495
+ def login_provider_interactively(
496
+ *,
497
+ runtime_id: str | None = None,
498
+ device_code: bool = False,
499
+ ) -> None:
500
+ """Run a provider login flow from the unified login entry point."""
501
+
502
+ auth_login(runtime_id=runtime_id, device_code=device_code, switch_account=False)
503
+
504
+
505
+ def _cli_runtime_id(cfg: AppConfig, requested: str | None) -> str:
506
+ explicit = str(requested or "").strip()
507
+ if explicit:
508
+ return explicit
509
+ selected = str(cfg.execution.runtime or "").strip()
510
+ if selected:
511
+ return selected
512
+ active_provider = _active_auth_provider(cfg)
513
+ if active_provider:
514
+ return active_provider
515
+ options = provider_auth_setup_options()
516
+ if len(options) == 1:
517
+ return options[0].id
518
+ choices = ", ".join(option.id for option in options) or "none"
519
+ raise typer.BadParameter(f"Choose a connection id. Available connections: {choices}.")
520
+
521
+
522
+ def _account_status_label(authenticated: bool, detail: str | None) -> str:
523
+ if authenticated:
524
+ return detail or "connected"
525
+ return detail or "not connected"
526
+
527
+
528
+ def _is_direct_provider(provider_id: str) -> bool:
529
+ from ...provider_auth import provider_auth_ids
530
+
531
+ return str(provider_id or "").strip() in provider_auth_ids()
532
+
533
+
534
+ def _use_direct_provider(
535
+ cfg: AppConfig,
536
+ provider_id: str,
537
+ *,
538
+ requested: str | None,
539
+ ) -> bool:
540
+ """Disambiguate the native subscription adapter from the legacy runtime.
541
+
542
+ ``openai-codex`` was already used by the original delegated ``codex`` CLI
543
+ integration. An explicit provider id now means the native subscription
544
+ connection, while an omitted id continues to honor an already-selected
545
+ delegated runtime so existing advanced configurations remain operable.
546
+ """
547
+
548
+ if not _is_direct_provider(provider_id):
549
+ return False
550
+ if str(requested or "").strip():
551
+ return True
552
+ return not (
553
+ cfg.execution.backend == "delegated"
554
+ and str(cfg.execution.runtime or "").strip() == provider_id
555
+ )
556
+
557
+
558
+ def _active_auth_provider(cfg: AppConfig) -> str | None:
559
+ try:
560
+ return get_active_profile(cfg).auth_provider
561
+ except Exception:
562
+ return None
563
+
564
+
565
+ def _activate_direct_provider(cfg: AppConfig, provider_id: str, adapter: object) -> None:
566
+ models = adapter.list_models(refresh=True) # type: ignore[attr-defined]
567
+ if not models:
568
+ raise ProviderAuthError("The connected account did not advertise any available models.")
569
+ existing_profile = get_profile(cfg, str(adapter.profile_name)) # type: ignore[attr-defined]
570
+ preserve_existing = bool(
571
+ existing_profile is not None and existing_profile.auth_provider == provider_id
572
+ )
573
+ profile = ProfileSpec(
574
+ name=str(adapter.profile_name), # type: ignore[attr-defined]
575
+ protocol=str(adapter.protocol), # type: ignore[attr-defined]
576
+ base_url=str(adapter.base_url), # type: ignore[attr-defined]
577
+ api_key_env=(existing_profile.api_key_env if preserve_existing else None),
578
+ auth_provider=provider_id,
579
+ extra_headers=(dict(existing_profile.extra_headers) if preserve_existing else {}),
580
+ default_model=(existing_profile.default_model if preserve_existing else ""),
581
+ reasoning_effort=(existing_profile.reasoning_effort if preserve_existing else None),
582
+ reasoning_trace_adapter=(
583
+ existing_profile.reasoning_trace_adapter if preserve_existing else "auto"
584
+ ),
585
+ web_search_adapter=(existing_profile.web_search_adapter if preserve_existing else "auto"),
586
+ web_search_model=(existing_profile.web_search_model if preserve_existing else ""),
587
+ notes=(
588
+ existing_profile.notes
589
+ if preserve_existing
590
+ else f"{adapter.display_name}. Uses Alysis Code's native agent loop." # type: ignore[attr-defined]
591
+ ),
592
+ cache_capability=(existing_profile.cache_capability if preserve_existing else None),
593
+ )
594
+ add_profile(cfg, profile, allow_auth_profile_update=True)
595
+ set_active_profile(cfg, profile.name)
596
+ cfg.execution.backend = "native"
597
+ cfg.execution.runtime = None
598
+ cfg.model = profile.default_model
599
+ effective_effort = (
600
+ None if profile.reasoning_effort in {None, "auto"} else profile.reasoning_effort
601
+ )
602
+ cfg.llm_reasoning_effort = effective_effort
603
+ cfg.llm_enable_thinking = None if effective_effort is None else effective_effort != "none"
604
+ confirmation_pending = str(
605
+ cfg.extra_fields.get(SUBSCRIPTION_SELECTION_REQUIRED_KEY) or ""
606
+ ).strip().lower() in {"true", provider_id}
607
+ selection_ready = subscription_selection_supported(profile, models) and not confirmation_pending
608
+ cfg.extra_fields["onboarded"] = selection_ready
609
+ cfg.extra_fields.pop("subscription_reconnect_required", None)
610
+ if not selection_ready:
611
+ cfg.extra_fields[SUBSCRIPTION_SELECTION_REQUIRED_KEY] = provider_id
612
+
613
+
614
+ __all__ = [
615
+ "auth_app",
616
+ "auth_list",
617
+ "auth_login",
618
+ "auth_logout",
619
+ "auth_status",
620
+ "login_connection_interactively",
621
+ "login_connection_rows",
622
+ "login_provider_interactively",
623
+ ]