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,337 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ import threading
6
+ import time
7
+ import uuid
8
+ from collections.abc import Callable
9
+ from dataclasses import dataclass, field
10
+ from datetime import UTC, datetime, timedelta
11
+ from typing import Any
12
+
13
+ from ..approval_scope import ApprovalSessionScope, approval_session_scope_for_request
14
+ from ..surface.events import Event, PromptForInput
15
+ from ..surface.types import ApprovalDecision, ApprovalRequest
16
+ from .protocol import ProtocolError, redact_secrets
17
+
18
+ DEFAULT_APPROVAL_TIMEOUT_SECONDS = 120.0
19
+ APPROVAL_ID_MAX_LENGTH = 128
20
+ APPROVAL_ID_PATTERN = re.compile(r"^[A-Za-z0-9_.:-]{1,128}$")
21
+ DEFAULT_RESOLVED_RETENTION_SECONDS = 300.0
22
+ DEFAULT_MAX_RESOLVED_APPROVALS = 512
23
+
24
+
25
+ def _now_iso() -> str:
26
+ return datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z")
27
+
28
+
29
+ def _expires_at(timeout_seconds: float) -> str:
30
+ return (
31
+ (datetime.now(UTC) + timedelta(seconds=max(0.0, timeout_seconds)))
32
+ .isoformat(timespec="milliseconds")
33
+ .replace("+00:00", "Z")
34
+ )
35
+
36
+
37
+ @dataclass
38
+ class PendingApproval:
39
+ approval_id: str
40
+ session_id: str
41
+ kind: str
42
+ reason: str
43
+ preview: str
44
+ created_at: str
45
+ expires_at: str
46
+ deadline: float
47
+ done: threading.Event = field(default_factory=threading.Event)
48
+ decision: ApprovalDecision | None = None
49
+ state: str = "pending"
50
+ allow_for_session_supported: bool = False
51
+ allow_for_session_scope: dict[str, Any] | None = None
52
+ allow_for_session_key: str | None = None
53
+ allow_for_session_warning: str | None = None
54
+ resolved_at: float | None = None
55
+
56
+
57
+ class ApprovalBroker:
58
+ def __init__(
59
+ self,
60
+ *,
61
+ timeout_seconds: float = DEFAULT_APPROVAL_TIMEOUT_SECONDS,
62
+ resolved_retention_seconds: float = DEFAULT_RESOLVED_RETENTION_SECONDS,
63
+ max_resolved_approvals: int = DEFAULT_MAX_RESOLVED_APPROVALS,
64
+ ) -> None:
65
+ self.timeout_seconds = max(0.01, float(timeout_seconds))
66
+ self.resolved_retention_seconds = max(0.0, float(resolved_retention_seconds))
67
+ self.max_resolved_approvals = max(0, int(max_resolved_approvals))
68
+ self._lock = threading.RLock()
69
+ self._approvals: dict[str, PendingApproval] = {}
70
+ self._session_allowances: set[tuple[str, str]] = set()
71
+
72
+ def request(
73
+ self,
74
+ *,
75
+ session_id: str,
76
+ request: ApprovalRequest,
77
+ emit_event: Callable[[Event], None],
78
+ ) -> ApprovalDecision:
79
+ kind = str(redact_secrets(request.kind or "approval"))
80
+ scope_info = approval_session_scope_for_request(request)
81
+ allowance_key = _allowance_key(session_id, scope_info)
82
+ with self._lock:
83
+ self._prune_locked(time.monotonic())
84
+ if allowance_key is not None and allowance_key in self._session_allowances:
85
+ return ApprovalDecision(allow=True, allow_for_session=True)
86
+
87
+ approval_id = _make_approval_id()
88
+ preview = str(redact_secrets(request.preview or ""))
89
+ reason = str(redact_secrets(request.reason or ""))
90
+ pending = PendingApproval(
91
+ approval_id=approval_id,
92
+ session_id=session_id,
93
+ kind=kind,
94
+ reason=reason,
95
+ preview=preview,
96
+ created_at=_now_iso(),
97
+ expires_at=_expires_at(self.timeout_seconds),
98
+ deadline=time.monotonic() + self.timeout_seconds,
99
+ allow_for_session_supported=scope_info.supported,
100
+ allow_for_session_scope=scope_info.scope,
101
+ allow_for_session_key=scope_info.key,
102
+ allow_for_session_warning=scope_info.warning,
103
+ )
104
+ with self._lock:
105
+ self._approvals[approval_id] = pending
106
+
107
+ emit_event(_approval_event(request, pending))
108
+
109
+ if not pending.done.wait(timeout=self.timeout_seconds):
110
+ expired_event: Event | None = None
111
+ with self._lock:
112
+ if pending.state == "pending":
113
+ pending.state = "expired"
114
+ pending.decision = ApprovalDecision(allow=False)
115
+ pending.resolved_at = time.monotonic()
116
+ expired_event = _approval_result_event(pending, status="expired")
117
+ self._prune_locked(pending.resolved_at)
118
+ if expired_event is not None:
119
+ emit_event(expired_event)
120
+ return ApprovalDecision(allow=False)
121
+
122
+ with self._lock:
123
+ decision = pending.decision or ApprovalDecision(allow=False)
124
+ if (
125
+ decision.allow
126
+ and decision.allow_for_session
127
+ and pending.allow_for_session_key is not None
128
+ ):
129
+ self._session_allowances.add((session_id, pending.allow_for_session_key))
130
+ if pending.resolved_at is None:
131
+ pending.resolved_at = time.monotonic()
132
+ result_status = "expired" if pending.state == "expired" else "resolved"
133
+ self._prune_locked(pending.resolved_at)
134
+ emit_event(_approval_result_event(pending, status=result_status, decision=decision))
135
+ return decision
136
+
137
+ def resolve(
138
+ self,
139
+ *,
140
+ session_id: str,
141
+ approval_id: str,
142
+ allow: bool,
143
+ allow_for_session: bool,
144
+ ) -> dict[str, Any]:
145
+ _validate_approval_id(approval_id)
146
+ with self._lock:
147
+ pending = self._approvals.get(approval_id)
148
+ if pending is None:
149
+ raise ProtocolError("approval_not_found", "Approval id is not pending.")
150
+ if pending.session_id != session_id:
151
+ raise ProtocolError(
152
+ "approval_not_found", "Approval id is not pending for this session."
153
+ )
154
+ if pending.state == "resolved":
155
+ raise ProtocolError(
156
+ "approval_already_resolved", "Approval id was already resolved."
157
+ )
158
+ if pending.state == "expired" or time.monotonic() > pending.deadline:
159
+ pending.state = "expired"
160
+ pending.decision = ApprovalDecision(allow=False)
161
+ pending.resolved_at = time.monotonic()
162
+ pending.done.set()
163
+ raise ProtocolError("approval_expired", "Approval id has expired.")
164
+ requested_allow_for_session = bool(allow_for_session)
165
+ effective_allow_for_session = (
166
+ bool(allow)
167
+ and requested_allow_for_session
168
+ and pending.allow_for_session_supported
169
+ and pending.allow_for_session_key is not None
170
+ )
171
+ warning = pending.allow_for_session_warning
172
+ if bool(allow) and requested_allow_for_session and not effective_allow_for_session:
173
+ warning = warning or (
174
+ "Allow for session was downgraded to allow once because this approval "
175
+ "does not have a supported exact scope."
176
+ )
177
+ pending.allow_for_session_warning = warning
178
+ pending.state = "resolved"
179
+ pending.decision = ApprovalDecision(
180
+ allow=bool(allow),
181
+ allow_for_session=effective_allow_for_session,
182
+ )
183
+ pending.resolved_at = time.monotonic()
184
+ if pending.decision.allow and pending.decision.allow_for_session:
185
+ self._session_allowances.add((session_id, pending.allow_for_session_key or ""))
186
+ pending.done.set()
187
+ self._prune_locked(pending.resolved_at)
188
+ return {
189
+ "session_id": session_id,
190
+ "approval_id": approval_id,
191
+ "status": "resolved",
192
+ "allow": bool(allow),
193
+ "allow_for_session": effective_allow_for_session,
194
+ "allow_for_session_supported": pending.allow_for_session_supported,
195
+ "allow_for_session_scope": _json_safe_value(
196
+ redact_secrets(pending.allow_for_session_scope)
197
+ ),
198
+ "allow_for_session_warning": str(redact_secrets(warning)) if warning else None,
199
+ }
200
+
201
+ def clear_session(self, session_id: str) -> None:
202
+ with self._lock:
203
+ self._session_allowances = {
204
+ key for key in self._session_allowances if key[0] != session_id
205
+ }
206
+ for approval_id, pending in list(self._approvals.items()):
207
+ if pending.session_id != session_id:
208
+ continue
209
+ if pending.state == "pending":
210
+ pending.state = "expired"
211
+ pending.decision = ApprovalDecision(allow=False)
212
+ pending.resolved_at = time.monotonic()
213
+ pending.done.set()
214
+ del self._approvals[approval_id]
215
+
216
+ def _prune_locked(self, now: float) -> None:
217
+ if self.resolved_retention_seconds > 0:
218
+ cutoff = now - self.resolved_retention_seconds
219
+ for approval_id, pending in list(self._approvals.items()):
220
+ if pending.state == "pending":
221
+ continue
222
+ if pending.resolved_at is not None and pending.resolved_at < cutoff:
223
+ del self._approvals[approval_id]
224
+
225
+ resolved = [
226
+ (pending.resolved_at or now, approval_id)
227
+ for approval_id, pending in self._approvals.items()
228
+ if pending.state != "pending"
229
+ ]
230
+ overflow = len(resolved) - self.max_resolved_approvals
231
+ if overflow <= 0:
232
+ return
233
+ for _, approval_id in sorted(resolved)[:overflow]:
234
+ self._approvals.pop(approval_id, None)
235
+
236
+
237
+ def _approval_event(request: ApprovalRequest, pending: PendingApproval) -> Event:
238
+ preview = pending.preview
239
+ reason = pending.reason
240
+ prompt_text = preview or reason or f"Approval required: {pending.kind}"
241
+ command = request.command
242
+ if command is not None:
243
+ command = str(redact_secrets(command))
244
+ files = [str(redact_secrets(path)) for path in request.files]
245
+ metadata = _json_safe(redact_secrets(request.metadata))
246
+ metadata.setdefault("approval_kind", pending.kind)
247
+ allow_for_session_scope = _json_safe_value(redact_secrets(pending.allow_for_session_scope))
248
+ allow_for_session_warning = (
249
+ str(redact_secrets(pending.allow_for_session_warning))
250
+ if pending.allow_for_session_warning
251
+ else None
252
+ )
253
+ return PromptForInput(
254
+ prompt_id=pending.approval_id,
255
+ prompt_text=prompt_text,
256
+ kind="approval",
257
+ approval_kind=pending.kind,
258
+ approval_id=pending.approval_id,
259
+ reason=reason,
260
+ preview=preview,
261
+ files=files,
262
+ command=command,
263
+ metadata=metadata,
264
+ expires_at=pending.expires_at,
265
+ allow_for_session_supported=pending.allow_for_session_supported,
266
+ allow_for_session_scope=allow_for_session_scope,
267
+ allow_for_session_warning=allow_for_session_warning,
268
+ )
269
+
270
+
271
+ def _approval_result_event(
272
+ pending: PendingApproval,
273
+ *,
274
+ status: str,
275
+ decision: ApprovalDecision | None = None,
276
+ ) -> Event:
277
+ decision = decision or pending.decision or ApprovalDecision(allow=False)
278
+ return PromptForInput(
279
+ prompt_id=pending.approval_id,
280
+ prompt_text=pending.preview or pending.reason or f"Approval {status}: {pending.kind}",
281
+ kind="approval_result",
282
+ approval_id=pending.approval_id,
283
+ reason=pending.reason,
284
+ preview=pending.preview,
285
+ metadata={
286
+ "status": status,
287
+ "kind": pending.kind,
288
+ "approval_kind": pending.kind,
289
+ "allow": bool(decision.allow),
290
+ "allow_for_session": bool(decision.allow_for_session),
291
+ "allow_for_session_supported": pending.allow_for_session_supported,
292
+ "allow_for_session_scope": _json_safe_value(
293
+ redact_secrets(pending.allow_for_session_scope)
294
+ ),
295
+ "allow_for_session_warning": str(redact_secrets(pending.allow_for_session_warning))
296
+ if pending.allow_for_session_warning
297
+ else None,
298
+ },
299
+ approval_kind=pending.kind,
300
+ expires_at=pending.expires_at,
301
+ allow_for_session_supported=pending.allow_for_session_supported,
302
+ allow_for_session_scope=_json_safe_value(redact_secrets(pending.allow_for_session_scope)),
303
+ allow_for_session_warning=str(redact_secrets(pending.allow_for_session_warning))
304
+ if pending.allow_for_session_warning
305
+ else None,
306
+ )
307
+
308
+
309
+ def _json_safe(value: Any) -> dict[str, Any]:
310
+ if not isinstance(value, dict):
311
+ return {}
312
+ return json.loads(json.dumps(value, ensure_ascii=True, default=str))
313
+
314
+
315
+ def _json_safe_value(value: Any) -> Any:
316
+ return json.loads(json.dumps(value, ensure_ascii=True, default=str))
317
+
318
+
319
+ def _allowance_key(
320
+ session_id: str,
321
+ scope_info: ApprovalSessionScope,
322
+ ) -> tuple[str, str] | None:
323
+ if not scope_info.supported or scope_info.key is None:
324
+ return None
325
+ return (session_id, scope_info.key)
326
+
327
+
328
+ def _make_approval_id() -> str:
329
+ ts = datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ")
330
+ return f"approval_{ts}_{uuid.uuid4().hex[:12]}"
331
+
332
+
333
+ def _validate_approval_id(approval_id: str) -> None:
334
+ if not approval_id or len(approval_id) > APPROVAL_ID_MAX_LENGTH:
335
+ raise ProtocolError("invalid_approval_id", "approval_id length is invalid.")
336
+ if APPROVAL_ID_PATTERN.fullmatch(approval_id) is None:
337
+ raise ProtocolError("invalid_approval_id", "approval_id contains unsupported characters.")
@@ -0,0 +1,153 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path, PurePosixPath
5
+ from typing import Any
6
+
7
+ from .protocol import ProtocolError, redact_secrets
8
+
9
+ DEFAULT_MAX_ARTIFACT_BYTES = 64 * 1024
10
+ MAX_ARTIFACT_BYTES = 1024 * 1024
11
+ DEFAULT_MAX_ARTIFACTS = 500
12
+ MAX_ARTIFACTS = 5_000
13
+ DEFAULT_MAX_ARTIFACT_DEPTH = 8
14
+ MAX_ARTIFACT_DEPTH = 20
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class ArtifactRoot:
19
+ name: str
20
+ path: Path
21
+
22
+
23
+ class ArtifactStore:
24
+ def __init__(self, roots: list[ArtifactRoot]) -> None:
25
+ self._roots = {root.name: root.path.resolve() for root in roots}
26
+
27
+ def add_root(self, root: ArtifactRoot) -> None:
28
+ self._roots[root.name] = root.path.resolve()
29
+
30
+ def list(
31
+ self,
32
+ *,
33
+ max_items: Any = DEFAULT_MAX_ARTIFACTS,
34
+ max_depth: Any = DEFAULT_MAX_ARTIFACT_DEPTH,
35
+ ) -> dict[str, Any]:
36
+ max_items = _bounded_int(max_items, default=DEFAULT_MAX_ARTIFACTS, upper=MAX_ARTIFACTS)
37
+ max_depth = _bounded_int(
38
+ max_depth, default=DEFAULT_MAX_ARTIFACT_DEPTH, upper=MAX_ARTIFACT_DEPTH
39
+ )
40
+ artifacts: list[dict[str, Any]] = []
41
+ truncated = False
42
+ for root_name, root in sorted(self._roots.items()):
43
+ if not root.exists() or not root.is_dir():
44
+ continue
45
+ stack: list[tuple[Path, int]] = [(root, 0)]
46
+ while stack:
47
+ current, depth = stack.pop()
48
+ try:
49
+ entries = sorted(current.iterdir(), key=lambda path: path.name)
50
+ except OSError:
51
+ continue
52
+ for path in entries:
53
+ if path.is_symlink():
54
+ continue
55
+ if path.is_dir():
56
+ if depth < max_depth:
57
+ stack.append((path, depth + 1))
58
+ else:
59
+ truncated = True
60
+ continue
61
+ if not path.is_file():
62
+ continue
63
+ if len(artifacts) >= max_items:
64
+ truncated = True
65
+ return {
66
+ "artifacts": artifacts,
67
+ "truncated": truncated,
68
+ "max_items": max_items,
69
+ "max_depth": max_depth,
70
+ }
71
+ rel = path.relative_to(root).as_posix()
72
+ try:
73
+ size = path.stat().st_size
74
+ except OSError:
75
+ continue
76
+ artifacts.append(
77
+ {
78
+ "artifact_id": f"{root_name}:{rel}",
79
+ "root": root_name,
80
+ "path": rel,
81
+ "size_bytes": size,
82
+ }
83
+ )
84
+ return {
85
+ "artifacts": artifacts,
86
+ "truncated": truncated,
87
+ "max_items": max_items,
88
+ "max_depth": max_depth,
89
+ }
90
+
91
+ def read(
92
+ self, artifact_id: str, *, max_bytes: Any = DEFAULT_MAX_ARTIFACT_BYTES
93
+ ) -> dict[str, Any]:
94
+ root_name, rel = _parse_artifact_id(artifact_id)
95
+ root = self._roots.get(root_name)
96
+ if root is None:
97
+ raise ProtocolError("artifact_not_found", "Artifact root is not available.")
98
+ max_bytes = _bounded_int(
99
+ max_bytes, default=DEFAULT_MAX_ARTIFACT_BYTES, upper=MAX_ARTIFACT_BYTES
100
+ )
101
+ path = _resolve_safe(root, rel)
102
+ if not path.exists() or not path.is_file():
103
+ raise ProtocolError("artifact_not_found", "Artifact was not found.")
104
+ try:
105
+ size = path.stat().st_size
106
+ with path.open("rb") as handle:
107
+ payload = handle.read(max_bytes + 1)
108
+ except OSError as e:
109
+ raise ProtocolError("artifact_not_found", "Artifact was not readable.") from e
110
+ truncated = len(payload) > max_bytes or size > max_bytes
111
+ payload = payload[:max_bytes]
112
+ return {
113
+ "artifact_id": artifact_id,
114
+ "path": rel,
115
+ "size_bytes": size,
116
+ "truncated": truncated,
117
+ "max_bytes": max_bytes,
118
+ "encoding": "utf-8-replace",
119
+ "content": redact_secrets(payload.decode("utf-8", errors="replace")),
120
+ }
121
+
122
+
123
+ def _parse_artifact_id(artifact_id: str) -> tuple[str, str]:
124
+ if ":" not in artifact_id:
125
+ raise ProtocolError("invalid_artifact_id", "Artifact id must use '<root>:<path>'.")
126
+ root, rel = artifact_id.split(":", 1)
127
+ root = root.strip()
128
+ rel = rel.strip()
129
+ if not root or not rel:
130
+ raise ProtocolError("invalid_artifact_id", "Artifact id root and path are required.")
131
+ return root, rel
132
+
133
+
134
+ def _resolve_safe(root: Path, rel: str) -> Path:
135
+ pure = PurePosixPath(rel.replace("\\", "/"))
136
+ if pure.is_absolute() or any(part == ".." for part in pure.parts):
137
+ raise ProtocolError("invalid_artifact_id", "Artifact path escapes its root.")
138
+ candidate = (root / Path(*pure.parts)).resolve()
139
+ try:
140
+ candidate.relative_to(root)
141
+ except ValueError as e:
142
+ raise ProtocolError("invalid_artifact_id", "Artifact path escapes its root.") from e
143
+ return candidate
144
+
145
+
146
+ def _bounded_int(value: Any, *, default: int, upper: int) -> int:
147
+ if value is None or value == "":
148
+ return default
149
+ try:
150
+ parsed = int(value)
151
+ except (TypeError, ValueError) as e:
152
+ raise ProtocolError("invalid_request", "Numeric artifact limits must be integers.") from e
153
+ return max(1, min(parsed, upper))