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,482 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import html
5
+ import ipaddress
6
+ import json
7
+ import re
8
+ import socket
9
+ from collections.abc import Callable
10
+ from typing import Any
11
+ from urllib.parse import urlsplit
12
+
13
+ import httpx
14
+
15
+ from ..safety import SafeHttpError, safe_http_request
16
+ from ..web_research import canonicalize_web_url_input, normalize_web_url
17
+ from .http_timeout import build_http_timeout_budget, format_http_timeout_error
18
+
19
+
20
+ class WebFetchError(RuntimeError):
21
+ """Raised for any web_fetch failure.
22
+
23
+ ``recoverable=True`` marks failures the calling model can fix by adjusting its
24
+ own tool arguments (input validation). The agent loop returns those to the
25
+ model as plain errors instead of disabling web tools for the rest of the turn.
26
+ """
27
+
28
+ def __init__(self, message: str, *, recoverable: bool = False) -> None:
29
+ super().__init__(message)
30
+ self.recoverable = recoverable
31
+
32
+
33
+ _DEFAULT_MAX_CHARS = 20_000
34
+ _MAX_MAX_CHARS = 50_000
35
+ _MAX_REDIRECTS = 5
36
+ _DEFAULT_TIMEOUT_SECONDS = 10.0
37
+ _MAX_RESPONSE_BYTES = 1_000_000
38
+ _REDIRECT_STATUS_CODES = {301, 302, 303, 307, 308}
39
+ _HTML_CONTENT_TYPES = {"text/html", "application/xhtml+xml"}
40
+ _JSON_CONTENT_TYPES = {"application/json", "text/json"}
41
+ _XML_CONTENT_TYPES = {"application/xml", "text/xml"}
42
+ _EXTRA_TEXT_CONTENT_TYPES = {
43
+ "application/javascript",
44
+ "application/x-javascript",
45
+ "application/ecmascript",
46
+ "application/x-www-form-urlencoded",
47
+ "application/x-yaml",
48
+ "application/yaml",
49
+ "application/toml",
50
+ }
51
+ _BLOCKED_IPV4_NETWORKS = (
52
+ ipaddress.ip_network("100.64.0.0/10"),
53
+ ipaddress.ip_network("198.18.0.0/15"),
54
+ ipaddress.ip_network("169.254.169.254/32"),
55
+ ipaddress.ip_network("100.100.100.200/32"),
56
+ )
57
+ _TEXT_HEADERS = {
58
+ "User-Agent": (
59
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
60
+ "(KHTML, like Gecko) Chrome/124.0 Safari/537.36"
61
+ ),
62
+ "Accept": (
63
+ "text/html,application/xhtml+xml,application/xml;q=0.9,"
64
+ "application/json;q=0.8,text/plain;q=0.8,*/*;q=0.5"
65
+ ),
66
+ "Accept-Language": "en-US,en;q=0.9",
67
+ }
68
+
69
+ _TAG_BLOCK_RE = re.compile(r"(?is)<(script|style|noscript)\b.*?>.*?</\1>")
70
+ _HEAD_BLOCK_RE = re.compile(r"(?is)<head\b.*?</head>")
71
+ _TITLE_RE = re.compile(r"(?is)<title[^>]*>(.*?)</title>")
72
+ _TAG_RE = re.compile(r"(?is)<[^>]+>")
73
+ _BREAK_TAG_RE = re.compile(r"(?is)<br\s*/?>")
74
+ _BLOCK_CLOSE_TAG_RE = re.compile(
75
+ r"(?is)</(p|div|section|article|header|footer|aside|nav|h[1-6]|li|tr|td|th|"
76
+ r"table|blockquote|ul|ol)>"
77
+ )
78
+ _HTML_HINT_RE = re.compile(r"(?is)<!doctype\s+html|<html\b|<body\b|<head\b|<title\b")
79
+ _CHARSET_RE = re.compile(r"charset=([^\s;]+)", re.IGNORECASE)
80
+ _REMOTE_CHALLENGE_BODY_RE = re.compile(
81
+ r"(?is)\b(cloudflare|checking your browser|just a moment|enable javascript and cookies)\b"
82
+ )
83
+
84
+
85
+ ResolverFn = Callable[[str, int], list[str]]
86
+
87
+
88
+ def _normalize_whitespace(text: str) -> str:
89
+ return " ".join(str(text).split())
90
+
91
+
92
+ def _normalize_content_type(raw: str) -> str:
93
+ text = str(raw or "").strip()
94
+ if not text:
95
+ return ""
96
+ return text.split(";", 1)[0].strip().lower()
97
+
98
+
99
+ def _extract_charset(raw_content_type: str) -> str | None:
100
+ match = _CHARSET_RE.search(str(raw_content_type or ""))
101
+ if match is None:
102
+ return None
103
+ value = str(match.group(1) or "").strip().strip('"').strip("'")
104
+ return value or None
105
+
106
+
107
+ def _decode_bytes(data: bytes, *, raw_content_type: str, fallback_encoding: str | None) -> str:
108
+ encoding = _extract_charset(raw_content_type) or (fallback_encoding or "").strip() or "utf-8"
109
+ try:
110
+ return data.decode(encoding, errors="replace")
111
+ except LookupError:
112
+ return data.decode("utf-8", errors="replace")
113
+
114
+
115
+ def _looks_binary(data: bytes) -> bool:
116
+ if not data:
117
+ return False
118
+ if b"\x00" in data:
119
+ return True
120
+ sample = data[:2048]
121
+ text_like = set(range(32, 127)) | {9, 10, 13}
122
+ non_text = sum(1 for byte in sample if byte not in text_like)
123
+ return (non_text / max(1, len(sample))) > 0.30
124
+
125
+
126
+ def _looks_like_html(text: str) -> bool:
127
+ return _HTML_HINT_RE.search(text) is not None
128
+
129
+
130
+ def _extract_html_title_and_text(html_text: str) -> tuple[str, str]:
131
+ title = ""
132
+ title_match = _TITLE_RE.search(html_text)
133
+ if title_match is not None:
134
+ raw_title = _TAG_RE.sub(" ", str(title_match.group(1) or ""))
135
+ title = _normalize_whitespace(html.unescape(raw_title))
136
+
137
+ body = _TAG_BLOCK_RE.sub(" ", html_text)
138
+ body = _HEAD_BLOCK_RE.sub(" ", body)
139
+ body = _BREAK_TAG_RE.sub("\n", body)
140
+ body = _BLOCK_CLOSE_TAG_RE.sub("\n", body)
141
+ body = _TAG_RE.sub(" ", body)
142
+ body = html.unescape(body)
143
+
144
+ lines = [_normalize_whitespace(line) for line in body.splitlines()]
145
+ text = "\n".join(line for line in lines if line)
146
+ if not text:
147
+ text = _normalize_whitespace(body)
148
+ return title, text
149
+
150
+
151
+ def _is_json_content_type(content_type: str) -> bool:
152
+ return content_type in _JSON_CONTENT_TYPES or content_type.endswith("+json")
153
+
154
+
155
+ def _is_xml_content_type(content_type: str) -> bool:
156
+ return content_type in _XML_CONTENT_TYPES or content_type.endswith("+xml")
157
+
158
+
159
+ def _is_text_like_content_type(content_type: str) -> bool:
160
+ if not content_type:
161
+ return True
162
+ if content_type.startswith("text/"):
163
+ return True
164
+ if _is_json_content_type(content_type):
165
+ return True
166
+ if _is_xml_content_type(content_type):
167
+ return True
168
+ return content_type in _EXTRA_TEXT_CONTENT_TYPES
169
+
170
+
171
+ def _clip_text(text: str, *, max_chars: int) -> tuple[str, bool]:
172
+ if len(text) <= max_chars:
173
+ return text, False
174
+ return text[:max_chars], True
175
+
176
+
177
+ def _coerce_max_chars(raw: Any) -> int:
178
+ try:
179
+ value = int(raw)
180
+ except (TypeError, ValueError) as e:
181
+ raise WebFetchError(
182
+ f"max_chars must be between 1 and {_MAX_MAX_CHARS}.",
183
+ recoverable=True,
184
+ ) from e
185
+ if value <= 0 or value > _MAX_MAX_CHARS:
186
+ raise WebFetchError(
187
+ f"max_chars must be between 1 and {_MAX_MAX_CHARS}.",
188
+ recoverable=True,
189
+ )
190
+ return value
191
+
192
+
193
+ def _split_url(raw_url: str) -> tuple[str, Any]:
194
+ url = str(raw_url or "").strip()
195
+ if not url:
196
+ raise WebFetchError("url must be a non-empty string.", recoverable=True)
197
+ split = urlsplit(url)
198
+ scheme = split.scheme.lower()
199
+ if scheme not in {"http", "https"}:
200
+ raise WebFetchError(
201
+ f"Unsupported URL scheme: {split.scheme or '(missing)'}",
202
+ recoverable=True,
203
+ )
204
+ if split.hostname is None:
205
+ raise WebFetchError("URL must include a valid host.", recoverable=True)
206
+ if split.username is not None or split.password is not None:
207
+ raise WebFetchError("Embedded URL credentials are not allowed.", recoverable=True)
208
+ try:
209
+ _ = split.port
210
+ except ValueError as e:
211
+ raise WebFetchError("URL has an invalid port value.", recoverable=True) from e
212
+ return url, split
213
+
214
+
215
+ def _blocked_ip_reason(ip: ipaddress._BaseAddress) -> str | None:
216
+ if ip.is_loopback:
217
+ return "loopback"
218
+ if ip.is_private:
219
+ return "private"
220
+ if ip.is_link_local:
221
+ return "link-local"
222
+ if ip.is_multicast:
223
+ return "multicast"
224
+ if ip.is_unspecified:
225
+ return "unspecified"
226
+ if ip.is_reserved:
227
+ return "reserved"
228
+ if isinstance(ip, ipaddress.IPv4Address):
229
+ if any(ip in network for network in _BLOCKED_IPV4_NETWORKS):
230
+ return "cloud-metadata-or-special-range"
231
+ return None
232
+
233
+
234
+ def _resolve_host_addresses(host: str, port: int) -> list[str]:
235
+ try:
236
+ infos = socket.getaddrinfo(host, port, type=socket.SOCK_STREAM)
237
+ except OSError as e:
238
+ raise WebFetchError(f"Failed to resolve host '{host}': {e}") from e
239
+
240
+ addresses: list[str] = []
241
+ seen: set[str] = set()
242
+ for _family, _socktype, _proto, _canonname, sockaddr in infos:
243
+ if not sockaddr:
244
+ continue
245
+ ip_text = str(sockaddr[0] or "").strip()
246
+ if not ip_text:
247
+ continue
248
+ if ip_text not in seen:
249
+ seen.add(ip_text)
250
+ addresses.append(ip_text)
251
+ if not addresses:
252
+ raise WebFetchError(f"Host '{host}' did not resolve to any IP address.")
253
+ return addresses
254
+
255
+
256
+ def _validate_fetch_target(url: str, *, resolver: ResolverFn) -> None:
257
+ _url, split = _split_url(url)
258
+ host = str(split.hostname or "")
259
+ host_normalized = host.rstrip(".").casefold()
260
+ if host_normalized == "localhost" or host_normalized.endswith(".localhost"):
261
+ raise WebFetchError(f"Blocked URL host '{host}': localhost is not allowed.")
262
+
263
+ try:
264
+ literal_ip = ipaddress.ip_address(host)
265
+ except ValueError:
266
+ literal_ip = None
267
+
268
+ if literal_ip is not None:
269
+ reason = _blocked_ip_reason(literal_ip)
270
+ if reason is not None:
271
+ raise WebFetchError(f"Blocked URL host '{host}': {reason} address is not allowed.")
272
+ return
273
+
274
+ port = split.port
275
+ if port is None:
276
+ port = 443 if split.scheme.lower() == "https" else 80
277
+
278
+ resolved_ips = resolver(host, port)
279
+ blocked_entries: list[str] = []
280
+ for ip_text in resolved_ips:
281
+ try:
282
+ parsed = ipaddress.ip_address(ip_text)
283
+ except ValueError as e:
284
+ raise WebFetchError(
285
+ f"Resolver returned a non-IP address for host '{host}': {ip_text!r}"
286
+ ) from e
287
+ reason = _blocked_ip_reason(parsed)
288
+ if reason is not None:
289
+ blocked_entries.append(f"{parsed.compressed} ({reason})")
290
+
291
+ if blocked_entries:
292
+ details = ", ".join(blocked_entries)
293
+ raise WebFetchError(
294
+ f"Blocked URL host '{host}': resolved to blocked/local address(es): {details}."
295
+ )
296
+
297
+
298
+ def _read_limited_response_bytes(response: httpx.Response, *, limit: int) -> tuple[bytes, bool]:
299
+ buf = bytearray()
300
+ truncated = False
301
+ for chunk in response.iter_bytes():
302
+ if not chunk:
303
+ continue
304
+ remaining = limit - len(buf)
305
+ if remaining <= 0:
306
+ truncated = True
307
+ break
308
+ if len(chunk) > remaining:
309
+ buf.extend(chunk[:remaining])
310
+ truncated = True
311
+ break
312
+ buf.extend(chunk)
313
+ return bytes(buf), truncated
314
+
315
+
316
+ def _http_status_error_message(
317
+ *,
318
+ status_code: int,
319
+ final_url: str,
320
+ response_headers: dict[str, str],
321
+ decoded_body: str,
322
+ ) -> str:
323
+ if status_code in {403, 429, 503}:
324
+ cf_mitigated = str(response_headers.get("cf-mitigated") or "").strip().casefold()
325
+ server = str(response_headers.get("server") or "").strip().casefold()
326
+ body_sample = decoded_body[:4096]
327
+ if cf_mitigated == "challenge" or (
328
+ "cloudflare" in server and _REMOTE_CHALLENGE_BODY_RE.search(body_sample)
329
+ ):
330
+ return (
331
+ f"HTTP error {status_code} while fetching '{final_url}': remote site "
332
+ "blocked automated retrieval with anti-bot/challenge protection."
333
+ )
334
+ return f"HTTP error {status_code} while fetching '{final_url}'."
335
+
336
+
337
+ def web_fetch(
338
+ *,
339
+ url: str,
340
+ max_chars: int = _DEFAULT_MAX_CHARS,
341
+ transport: httpx.BaseTransport | None = None,
342
+ ) -> dict[str, Any]:
343
+ # Defense-in-depth runtime guard: even if a web_fetch call reaches this layer
344
+ # through a path that bypassed tool registration (resume/replay, patched tool
345
+ # tables), the master web-tools switch still hard-blocks execution.
346
+ # Lazy import to avoid a module-level import cycle with config.
347
+ from ..config import resolve_web_tools_enabled
348
+
349
+ if not resolve_web_tools_enabled(None):
350
+ raise WebFetchError(
351
+ "web_fetch is disabled by the web-tools policy "
352
+ "(ALYSIS_WEB_TOOLS=off / web_tools_enabled=false). "
353
+ "No network fetch was performed.",
354
+ recoverable=False,
355
+ )
356
+ raw_input_url = str(url or "").strip()
357
+ requested_url, _split = _split_url(
358
+ normalize_web_url(raw_input_url)
359
+ or canonicalize_web_url_input(raw_input_url)
360
+ or raw_input_url
361
+ )
362
+ clipped_max_chars = _coerce_max_chars(max_chars)
363
+
364
+ resolver: ResolverFn = _resolve_host_addresses
365
+ timeout_budget = build_http_timeout_budget(_DEFAULT_TIMEOUT_SECONDS, profile="fetch")
366
+
367
+ current_url = requested_url
368
+ final_url = requested_url
369
+ status_code = 0
370
+ raw_content_type = ""
371
+ body = b""
372
+ body_truncated = False
373
+ response_encoding: str | None = None
374
+ response_headers: dict[str, str] = {}
375
+
376
+ _validate_fetch_target(current_url, resolver=resolver)
377
+ try:
378
+ response = asyncio.run(
379
+ safe_http_request(
380
+ "GET",
381
+ current_url,
382
+ timeout=_DEFAULT_TIMEOUT_SECONDS,
383
+ max_bytes=_MAX_RESPONSE_BYTES,
384
+ allow_redirects=True,
385
+ max_redirects=_MAX_REDIRECTS,
386
+ headers=_TEXT_HEADERS,
387
+ _transport=transport, # type: ignore[arg-type]
388
+ _resolver=resolver,
389
+ )
390
+ )
391
+ except SafeHttpError as e:
392
+ # safe_http classifies URL-/host-specific failures (oversized body,
393
+ # redirect problems, blocked or invalid hosts) as recoverable; propagate
394
+ # that so one bad target does not disable web tools for the whole turn.
395
+ raise WebFetchError(str(e), recoverable=getattr(e, "recoverable", False)) from e
396
+ except httpx.TimeoutException as e:
397
+ raise WebFetchError(
398
+ format_http_timeout_error(
399
+ operation=f"web_fetch request to '{current_url}'",
400
+ budget=timeout_budget,
401
+ error=e,
402
+ )
403
+ ) from e
404
+ except httpx.HTTPError as e:
405
+ raise WebFetchError(f"HTTP request failed for '{current_url}': {e}") from e
406
+
407
+ status_code = int(response.status_code)
408
+ final_url = str(response.url)
409
+ raw_content_type = str(response.headers.get("content-type") or "")
410
+ response_encoding = response.encoding
411
+ response_headers = {str(key).lower(): str(value) for key, value in response.headers.items()}
412
+ body = response.content
413
+ body_truncated = False
414
+
415
+ content_type = _normalize_content_type(raw_content_type)
416
+ decoded = _decode_bytes(
417
+ body, raw_content_type=raw_content_type, fallback_encoding=response_encoding
418
+ )
419
+ # The site responded, so the web itself is reachable: an HTTP error status or
420
+ # unusable payload is specific to this URL and the model can recover by
421
+ # choosing a different source. Only genuine connectivity signals (DNS
422
+ # resolution outage, connect failures, timeouts) remain unrecoverable.
423
+ if status_code >= 400:
424
+ raise WebFetchError(
425
+ _http_status_error_message(
426
+ status_code=status_code,
427
+ final_url=final_url,
428
+ response_headers=response_headers,
429
+ decoded_body=decoded,
430
+ ),
431
+ recoverable=True,
432
+ )
433
+
434
+ if content_type and not _is_text_like_content_type(content_type):
435
+ raise WebFetchError(
436
+ f"Unsupported content type for web_fetch: '{content_type}'. Only text-like responses "
437
+ "are supported.",
438
+ recoverable=True,
439
+ )
440
+ if not content_type and _looks_binary(body):
441
+ raise WebFetchError(
442
+ "Unsupported binary response without a text-like content type.",
443
+ recoverable=True,
444
+ )
445
+
446
+ title = ""
447
+ if content_type in _HTML_CONTENT_TYPES or (not content_type and _looks_like_html(decoded)):
448
+ if not content_type:
449
+ content_type = "text/html"
450
+ title, extracted = _extract_html_title_and_text(decoded)
451
+ readable_content = extracted
452
+ elif _is_json_content_type(content_type):
453
+ try:
454
+ parsed_json = json.loads(decoded)
455
+ except json.JSONDecodeError:
456
+ readable_content = decoded
457
+ else:
458
+ readable_content = json.dumps(parsed_json, ensure_ascii=False, indent=2)
459
+ else:
460
+ if not content_type:
461
+ content_type = "text/plain"
462
+ readable_content = decoded
463
+
464
+ readable_content = readable_content.replace("\r\n", "\n").replace("\r", "\n")
465
+ clipped_content, clipped = _clip_text(readable_content, max_chars=clipped_max_chars)
466
+ truncated = bool(body_truncated or clipped)
467
+
468
+ return {
469
+ "url": requested_url,
470
+ "final_url": final_url,
471
+ "status_code": status_code,
472
+ "content_type": content_type,
473
+ "title": title,
474
+ "content": clipped_content,
475
+ "truncated": truncated,
476
+ "backend": "httpx",
477
+ **(
478
+ {"raw_input_url": raw_input_url}
479
+ if raw_input_url and raw_input_url != requested_url
480
+ else {}
481
+ ),
482
+ }