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,557 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ from collections.abc import Callable
6
+ from typing import Any
7
+ from urllib.parse import urlsplit
8
+
9
+ import httpx
10
+
11
+ from ..llm.provider_limits import (
12
+ ProviderRetrySettings,
13
+ best_effort_provider_key,
14
+ run_provider_limited_call,
15
+ )
16
+ from ..safety import SafeHttpError, safe_http_request
17
+ from ..safety.safe_http import Resolver
18
+ from ..web_research import extract_public_web_urls, normalize_web_url
19
+ from .http_timeout import build_http_timeout_budget, format_http_timeout_error
20
+
21
+
22
+ class DashScopeChatSearchError(RuntimeError):
23
+ pass
24
+
25
+
26
+ _DASHSCOPE_RESPONSES_SEARCH_MODEL_PREFIXES = (
27
+ "qwen3.7-plus",
28
+ "qwen3.7-max",
29
+ "qwen3.6-plus",
30
+ "qwen3.6-flash",
31
+ "qwen3.6-max",
32
+ )
33
+ _DASHSCOPE_CHAT_SEARCH_MODEL_PREFIXES = (
34
+ "qwen3.5-plus",
35
+ "qwen3.5-flash",
36
+ "qwen3-max",
37
+ "qwen-plus",
38
+ "qwen-flash",
39
+ "qwen-max",
40
+ )
41
+
42
+
43
+ def _normalized_dashscope_model(model: str | None) -> str:
44
+ normalized = str(model or "").strip().lower()
45
+ if "/" in normalized:
46
+ normalized = normalized.rsplit("/", 1)[-1]
47
+ return normalized
48
+
49
+
50
+ def dashscope_model_uses_responses_search(model: str | None) -> bool:
51
+ normalized = _normalized_dashscope_model(model)
52
+ return any(
53
+ normalized.startswith(prefix) for prefix in _DASHSCOPE_RESPONSES_SEARCH_MODEL_PREFIXES
54
+ )
55
+
56
+
57
+ def dashscope_model_supports_web_search(model: str | None) -> bool:
58
+ normalized = _normalized_dashscope_model(model)
59
+ return dashscope_model_uses_responses_search(normalized) or any(
60
+ normalized.startswith(prefix) for prefix in _DASHSCOPE_CHAT_SEARCH_MODEL_PREFIXES
61
+ )
62
+
63
+
64
+ def _extract_error_message(data: Any) -> str | None:
65
+ if not isinstance(data, dict):
66
+ return None
67
+ error_obj = data.get("error")
68
+ if isinstance(error_obj, dict):
69
+ message = str(error_obj.get("message") or "").strip()
70
+ if message:
71
+ code = str(error_obj.get("code") or "").strip()
72
+ return f"{code}: {message}" if code else message
73
+ message = str(data.get("message") or "").strip()
74
+ if message:
75
+ return message
76
+ return None
77
+
78
+
79
+ def _dashscope_error_from_response(
80
+ response: httpx.Response,
81
+ *,
82
+ operation_label: str = "DashScope chat search",
83
+ ) -> DashScopeChatSearchError:
84
+ try:
85
+ data = response.json()
86
+ except Exception:
87
+ body = response.text
88
+ if len(body) > 1000:
89
+ body = body[:1000] + "...(truncated)"
90
+ return DashScopeChatSearchError(f"{operation_label} error {response.status_code}: {body}")
91
+ if isinstance(data, dict):
92
+ error_message = _extract_error_message(data)
93
+ if error_message:
94
+ return DashScopeChatSearchError(
95
+ f"{operation_label} error {response.status_code}: {error_message}"
96
+ )
97
+ return DashScopeChatSearchError(f"{operation_label} error {response.status_code}: {data!r}")
98
+
99
+
100
+ def _choice_message_text(data: dict[str, Any]) -> str:
101
+ choices = data.get("choices")
102
+ if not isinstance(choices, list):
103
+ return ""
104
+ for choice in choices:
105
+ if not isinstance(choice, dict):
106
+ continue
107
+ message = choice.get("message")
108
+ if not isinstance(message, dict):
109
+ continue
110
+ content = message.get("content")
111
+ if isinstance(content, str):
112
+ return content.strip()
113
+ return ""
114
+
115
+
116
+ def _responses_message_text(data: dict[str, Any]) -> str:
117
+ output_text = data.get("output_text")
118
+ if isinstance(output_text, str) and output_text.strip():
119
+ return output_text.strip()
120
+ parts: list[str] = []
121
+ output = data.get("output")
122
+ for item in output if isinstance(output, list) else []:
123
+ if not isinstance(item, dict) or str(item.get("type") or "") != "message":
124
+ continue
125
+ content = item.get("content")
126
+ for chunk in content if isinstance(content, list) else []:
127
+ if not isinstance(chunk, dict):
128
+ continue
129
+ text = chunk.get("text")
130
+ if isinstance(text, str):
131
+ parts.append(text)
132
+ return "".join(parts).strip()
133
+
134
+
135
+ def _responses_sources(data: dict[str, Any]) -> list[dict[str, str]]:
136
+ sources: list[dict[str, str]] = []
137
+
138
+ def _walk(value: Any) -> None:
139
+ if isinstance(value, dict):
140
+ source = _coerce_source(value)
141
+ if source is not None:
142
+ sources.append(source)
143
+ for nested in value.values():
144
+ if isinstance(nested, (dict, list)):
145
+ _walk(nested)
146
+ elif isinstance(value, list):
147
+ for item in value:
148
+ _walk(item)
149
+
150
+ _walk(data.get("output"))
151
+ return sources
152
+
153
+
154
+ def _responses_queries(data: dict[str, Any]) -> list[str]:
155
+ queries: list[str] = []
156
+
157
+ def _append(raw: Any) -> None:
158
+ value = str(raw or "").strip()
159
+ if value and value not in queries:
160
+ queries.append(value)
161
+
162
+ def _walk(value: Any) -> None:
163
+ if isinstance(value, dict):
164
+ _append(value.get("query"))
165
+ raw_queries = value.get("queries")
166
+ if isinstance(raw_queries, list):
167
+ for query in raw_queries:
168
+ _append(query)
169
+ for nested in value.values():
170
+ if isinstance(nested, (dict, list)):
171
+ _walk(nested)
172
+ elif isinstance(value, list):
173
+ for item in value:
174
+ _walk(item)
175
+
176
+ _walk(data.get("output"))
177
+ return queries
178
+
179
+
180
+ def _sse_json_events(text: str) -> list[dict[str, Any]]:
181
+ events: list[dict[str, Any]] = []
182
+ for raw_line in text.splitlines():
183
+ line = raw_line.strip()
184
+ if not line.startswith("data:"):
185
+ continue
186
+ payload = line.removeprefix("data:").strip()
187
+ if not payload or payload == "[DONE]":
188
+ continue
189
+ try:
190
+ parsed = json.loads(payload)
191
+ except json.JSONDecodeError:
192
+ continue
193
+ if isinstance(parsed, dict):
194
+ events.append(parsed)
195
+ return events
196
+
197
+
198
+ def _coalesce_streaming_response(text: str) -> dict[str, Any] | None:
199
+ events = _sse_json_events(text)
200
+ if not events:
201
+ return None
202
+
203
+ content_parts: list[str] = []
204
+ response_id: str | None = None
205
+ response_model: str | None = None
206
+ sources: list[dict[str, str]] = []
207
+ for event in events:
208
+ if response_id is None:
209
+ response_id = str(event.get("id") or "").strip() or None
210
+ if response_model is None:
211
+ response_model = str(event.get("model") or "").strip() or None
212
+ sources.extend(_sources_from_json_payload(event))
213
+
214
+ choices = event.get("choices")
215
+ if not isinstance(choices, list):
216
+ continue
217
+ for choice in choices:
218
+ if not isinstance(choice, dict):
219
+ continue
220
+ delta = choice.get("delta")
221
+ if not isinstance(delta, dict):
222
+ delta = choice.get("message")
223
+ if not isinstance(delta, dict):
224
+ continue
225
+ content = delta.get("content")
226
+ if isinstance(content, str):
227
+ content_parts.append(content)
228
+
229
+ return {
230
+ "id": response_id,
231
+ "model": response_model,
232
+ "choices": [{"message": {"content": "".join(content_parts)}}],
233
+ "search_info": {"search_results": sources},
234
+ }
235
+
236
+
237
+ def _coerce_source(raw: Any) -> dict[str, str] | None:
238
+ if not isinstance(raw, dict):
239
+ return None
240
+ url = str(raw.get("url") or raw.get("link") or "").strip()
241
+ if not url:
242
+ return None
243
+ title = str(raw.get("title") or raw.get("name") or "").strip()
244
+ snippet = str(raw.get("snippet") or raw.get("content") or raw.get("summary") or "").strip()
245
+ source = {"url": url, "title": title}
246
+ normalized_url = normalize_web_url(url)
247
+ if normalized_url:
248
+ source["normalized_url"] = normalized_url
249
+ if snippet:
250
+ source["snippet"] = snippet[:500]
251
+ return source
252
+
253
+
254
+ def _sources_from_json_payload(value: Any) -> list[dict[str, str]]:
255
+ sources: list[dict[str, str]] = []
256
+ if isinstance(value, dict):
257
+ for key in ("sources", "citations", "search_results", "results"):
258
+ raw_sources = value.get(key)
259
+ if isinstance(raw_sources, list):
260
+ for raw_source in raw_sources:
261
+ source = _coerce_source(raw_source)
262
+ if source is not None:
263
+ sources.append(source)
264
+ search_info = value.get("search_info")
265
+ if isinstance(search_info, dict):
266
+ sources.extend(_sources_from_json_payload(search_info))
267
+ return sources
268
+
269
+
270
+ def _strip_json_code_fence(text: str) -> str:
271
+ stripped = text.strip()
272
+ if not stripped.startswith("```"):
273
+ return stripped
274
+ lines = stripped.splitlines()
275
+ if len(lines) < 2 or not lines[-1].strip().startswith("```"):
276
+ return stripped
277
+ first_line = lines[0].strip().lower()
278
+ if first_line not in {"```", "```json", "```javascript", "```js"}:
279
+ return stripped
280
+ return "\n".join(lines[1:-1]).strip()
281
+
282
+
283
+ def _json_answer_and_sources(text: str) -> tuple[str, list[dict[str, str]]]:
284
+ stripped = _strip_json_code_fence(text)
285
+ if not stripped:
286
+ return "", []
287
+ try:
288
+ parsed = json.loads(stripped)
289
+ except json.JSONDecodeError:
290
+ return stripped, []
291
+ if not isinstance(parsed, dict):
292
+ return stripped, []
293
+ answer = str(
294
+ parsed.get("answer")
295
+ or parsed.get("summary")
296
+ or parsed.get("content")
297
+ or parsed.get("text")
298
+ or ""
299
+ ).strip()
300
+ sources = _sources_from_json_payload(parsed)
301
+ return answer or stripped, sources
302
+
303
+
304
+ def _sources_from_urls(text: str) -> list[dict[str, str]]:
305
+ return [
306
+ {
307
+ "url": entry["url"],
308
+ "normalized_url": entry["normalized_url"],
309
+ "title": "",
310
+ }
311
+ for entry in extract_public_web_urls(text)
312
+ ]
313
+
314
+
315
+ def _effective_source_url(source: dict[str, str]) -> str | None:
316
+ normalized_url = normalize_web_url(source.get("normalized_url"))
317
+ if normalized_url:
318
+ return normalized_url
319
+ return normalize_web_url(source.get("url"))
320
+
321
+
322
+ def _host_matches_allowed_domains(url: str, allowed_domains: list[str] | None) -> bool:
323
+ if not allowed_domains:
324
+ return True
325
+ try:
326
+ host = (urlsplit(url).hostname or "").rstrip(".").lower()
327
+ except ValueError:
328
+ return False
329
+ for domain in allowed_domains:
330
+ normalized = str(domain or "").strip().rstrip(".").lower()
331
+ if host == normalized or host.endswith(f".{normalized}"):
332
+ return True
333
+ return False
334
+
335
+
336
+ def _dedupe_sources(
337
+ sources: list[dict[str, str]],
338
+ *,
339
+ allowed_domains: list[str] | None,
340
+ ) -> list[dict[str, str]]:
341
+ deduped: list[dict[str, str]] = []
342
+ index_by_url: dict[str, int] = {}
343
+ for source in sources:
344
+ raw_url = str(source.get("url") or "").strip()
345
+ if not raw_url:
346
+ continue
347
+ effective_url = _effective_source_url(source)
348
+ dedupe_key = effective_url or raw_url
349
+ if dedupe_key in index_by_url:
350
+ existing = deduped[index_by_url[dedupe_key]]
351
+ if not existing.get("title") and source.get("title"):
352
+ existing["title"] = str(source["title"]).strip()
353
+ if not existing.get("snippet") and source.get("snippet"):
354
+ existing["snippet"] = str(source["snippet"]).strip()
355
+ continue
356
+ if not _host_matches_allowed_domains(effective_url or raw_url, allowed_domains):
357
+ continue
358
+ index_by_url[dedupe_key] = len(deduped)
359
+ cleaned = {
360
+ "url": effective_url or raw_url,
361
+ "title": str(source.get("title") or "").strip(),
362
+ }
363
+ snippet = str(source.get("snippet") or "").strip()
364
+ if snippet:
365
+ cleaned["snippet"] = snippet
366
+ deduped.append(cleaned)
367
+ return deduped
368
+
369
+
370
+ def _search_prompt(query: str, allowed_domains: list[str] | None) -> str:
371
+ domain_text = ""
372
+ if allowed_domains:
373
+ domain_text = (
374
+ "\nOnly use results from these domains: "
375
+ + ", ".join(str(domain).strip() for domain in allowed_domains)
376
+ + "."
377
+ )
378
+ return (
379
+ "Use live web search to answer the query. Return only JSON with this shape: "
380
+ '{"answer":"...", "sources":[{"title":"...", "url":"..."}]}. '
381
+ "Include source URLs whenever the search backend provides them."
382
+ f"{domain_text}\nQuery: {query}"
383
+ )
384
+
385
+
386
+ def dashscope_chat_search(
387
+ *,
388
+ query: str,
389
+ base_url: str,
390
+ api_key: str,
391
+ model: str,
392
+ max_results: int = 8,
393
+ include_domains: list[str] | None = None,
394
+ timeout_s: float = 45.0,
395
+ transport: httpx.BaseTransport | None = None,
396
+ resolver: Resolver | None = None,
397
+ provider_key: str | None = None,
398
+ provider_concurrency_caps: dict[str, int] | None = None,
399
+ provider_retry_settings: ProviderRetrySettings | None = None,
400
+ provider_sleep_fn: Callable[[float], None] | None = None,
401
+ provider_random_fn: Callable[[], float] | None = None,
402
+ ) -> dict[str, Any]:
403
+ if not str(query or "").strip():
404
+ raise DashScopeChatSearchError("query must be a non-empty string.")
405
+ if not str(api_key or "").strip():
406
+ raise DashScopeChatSearchError("API key is required for DashScope chat search.")
407
+ if not str(base_url or "").strip():
408
+ raise DashScopeChatSearchError("base_url is required for DashScope chat search.")
409
+ if not str(model or "").strip():
410
+ raise DashScopeChatSearchError("model is required for DashScope chat search.")
411
+
412
+ use_responses = dashscope_model_uses_responses_search(model)
413
+ operation_label = "DashScope Responses search" if use_responses else "DashScope chat search"
414
+ if use_responses:
415
+ url = f"{base_url.rstrip('/')}/responses"
416
+ prompt = f"Use live web search and cite source URLs.\n\n{query}"
417
+ if include_domains:
418
+ prompt += "\nOnly use sources from these domains: " + ", ".join(include_domains) + "."
419
+ payload: dict[str, Any] = {
420
+ "model": model,
421
+ "input": prompt,
422
+ "tools": [{"type": "web_search"}],
423
+ "stream": False,
424
+ }
425
+ else:
426
+ url = f"{base_url.rstrip('/')}/chat/completions"
427
+ payload = {
428
+ "model": model,
429
+ "messages": [{"role": "user", "content": _search_prompt(query, include_domains)}],
430
+ "enable_search": True,
431
+ "search_options": {
432
+ "forced_search": True,
433
+ "search_strategy": "agent",
434
+ "enable_source": True,
435
+ },
436
+ "enable_thinking": False,
437
+ "stream": True,
438
+ }
439
+ headers = {
440
+ "Authorization": f"Bearer {api_key}",
441
+ "Content-Type": "application/json",
442
+ "User-Agent": "alysis-code/0.1.0",
443
+ }
444
+ timeout_budget = build_http_timeout_budget(timeout_s, profile="search")
445
+
446
+ resolved_provider_key = provider_key or best_effort_provider_key(
447
+ base_url=base_url,
448
+ model=model,
449
+ )
450
+
451
+ def _send_request() -> httpx.Response:
452
+ try:
453
+ response = asyncio.run(
454
+ safe_http_request(
455
+ "POST",
456
+ url,
457
+ timeout=timeout_s,
458
+ headers=headers,
459
+ json=payload,
460
+ _transport=transport, # type: ignore[arg-type]
461
+ _resolver=resolver,
462
+ )
463
+ )
464
+ except httpx.TimeoutException as e:
465
+ raise DashScopeChatSearchError(
466
+ format_http_timeout_error(
467
+ operation=operation_label,
468
+ budget=timeout_budget,
469
+ error=e,
470
+ )
471
+ ) from e
472
+ except SafeHttpError as e:
473
+ raise DashScopeChatSearchError(f"{operation_label} request blocked: {e}") from e
474
+ except Exception as e: # noqa: BLE001
475
+ raise DashScopeChatSearchError(f"{operation_label} request failed: {e}") from e
476
+ if response.status_code >= 400:
477
+ raise _dashscope_error_from_response(response, operation_label=operation_label)
478
+ return response
479
+
480
+ response = run_provider_limited_call(
481
+ call=_send_request,
482
+ provider_key=resolved_provider_key,
483
+ provider_concurrency_caps=provider_concurrency_caps,
484
+ retry_settings=provider_retry_settings,
485
+ operation=("dashscope_responses_search" if use_responses else "dashscope_chat_search"),
486
+ sleep_fn=provider_sleep_fn,
487
+ random_fn=provider_random_fn,
488
+ )
489
+
490
+ try:
491
+ data = response.json()
492
+ except Exception as e: # noqa: BLE001
493
+ stream_data = _coalesce_streaming_response(response.text)
494
+ if stream_data is None:
495
+ raise DashScopeChatSearchError(
496
+ "DashScope chat search returned non-JSON response"
497
+ ) from e
498
+ data = stream_data
499
+
500
+ if not isinstance(data, dict):
501
+ raise DashScopeChatSearchError("Unexpected DashScope chat search payload")
502
+
503
+ if response.status_code >= 400:
504
+ error_message = _extract_error_message(data)
505
+ if error_message:
506
+ raise DashScopeChatSearchError(
507
+ f"DashScope chat search error {response.status_code}: {error_message}"
508
+ )
509
+ raise DashScopeChatSearchError(
510
+ f"DashScope chat search error {response.status_code}: {data!r}"
511
+ )
512
+
513
+ raw_answer = _responses_message_text(data) if use_responses else _choice_message_text(data)
514
+ if use_responses:
515
+ answer = raw_answer
516
+ json_sources: list[dict[str, str]] = []
517
+ payload_sources = _responses_sources(data)
518
+ queries = _responses_queries(data) or [query]
519
+ else:
520
+ answer, json_sources = _json_answer_and_sources(raw_answer)
521
+ payload_sources = _sources_from_json_payload(data)
522
+ queries = [query]
523
+ url_sources = _sources_from_urls(answer)
524
+ deduped_sources = _dedupe_sources(
525
+ [*json_sources, *payload_sources, *url_sources],
526
+ allowed_domains=include_domains,
527
+ )
528
+ sources_truncated = len(deduped_sources) > max_results
529
+ final_sources = deduped_sources[:max_results]
530
+ citations = [
531
+ {
532
+ "title": str(source.get("title") or "").strip(),
533
+ "url": str(source.get("url") or "").strip(),
534
+ "start_index": None,
535
+ "end_index": None,
536
+ }
537
+ for source in final_sources
538
+ ]
539
+
540
+ if not answer and not final_sources:
541
+ raise DashScopeChatSearchError("DashScope chat search payload missing answer and sources")
542
+
543
+ response_id = str(data.get("id") or "").strip() or None
544
+ response_model = str(data.get("model") or "").strip() or None
545
+ return {
546
+ "query": query,
547
+ "answer": answer,
548
+ "citations": citations,
549
+ "sources": final_sources,
550
+ "queries": queries,
551
+ "model": response_model or model,
552
+ "backend": "dashscope_responses" if use_responses else "dashscope_chat",
553
+ "allowed_domains": include_domains or [],
554
+ "external_web_access": True,
555
+ "response_id": response_id,
556
+ "sources_truncated": sources_truncated,
557
+ }