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,355 @@
1
+ """Persistent-service support: non-interactive defaults, probes, finalize check.
2
+
3
+ Two independent reliability problems live here because both are about a command
4
+ that outlives -- or stalls -- the tool call that started it.
5
+
6
+ *Persistence.* A process the agent starts normally shares the agent's process
7
+ group, and that group is exactly what gets signalled at turn finalization and
8
+ session close. A service started that way is guaranteed to be dead by the time
9
+ an external verifier looks for it. Surviving means leaving the group: its own
10
+ session, no controlling terminal, stdio on files rather than pipes nobody will
11
+ drain.
12
+
13
+ *Non-interaction.* Nobody is at the keyboard. A command that stops to ask a
14
+ question holds its pipe open until the hard kill and spends the rest of the run
15
+ answering nothing.
16
+
17
+ Stdlib only, no package imports: the agent pulls this in from the tool layer and
18
+ the turn controller, and the tests load it straight from this file path in a
19
+ bare interpreter.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import errno
25
+ import os
26
+ import re
27
+ import socket
28
+ import subprocess
29
+ from collections.abc import Callable, Iterable
30
+ from dataclasses import dataclass
31
+ from typing import Any
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Non-interactive shell defaults
35
+ # ---------------------------------------------------------------------------
36
+
37
+ # The standard opt-outs for the three ecosystems that prompt most often. apt
38
+ # asks about config files and service restarts; git asks for credentials on a
39
+ # private remote; pip asks before overwriting. Each one is a silent stall.
40
+ NON_INTERACTIVE_ENV_DEFAULTS: dict[str, str] = {
41
+ "DEBIAN_FRONTEND": "noninteractive",
42
+ "GIT_TERMINAL_PROMPT": "0",
43
+ "PIP_NO_INPUT": "1",
44
+ }
45
+
46
+
47
+ def apply_non_interactive_defaults(env: dict[str, str]) -> dict[str, str]:
48
+ """Fill in the non-interactive defaults ``env`` does not already set.
49
+
50
+ Never overrides. A task that deliberately exports ``DEBIAN_FRONTEND=dialog``
51
+ keeps it -- these are defaults for the unconfigured case, not policy.
52
+ """
53
+
54
+ merged = dict(env)
55
+ for key, value in NON_INTERACTIVE_ENV_DEFAULTS.items():
56
+ if key not in merged:
57
+ merged[key] = value
58
+ return merged
59
+
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Detached spawn
63
+ # ---------------------------------------------------------------------------
64
+
65
+
66
+ def persist_spawn_kwargs() -> dict[str, Any]:
67
+ """Popen kwargs that move the child out of the agent's process group.
68
+
69
+ This is the whole of persistence. ``start_new_session`` makes the child a
70
+ session and group leader, so a ``killpg`` aimed at the agent's group cannot
71
+ reach it, and it has no controlling terminal to be hung up on. Windows has
72
+ no sessions; a new process group is the closest equivalent and at least
73
+ detaches the child from console Ctrl-C.
74
+ """
75
+
76
+ if os.name == "nt": # pragma: no cover - exercised on Windows
77
+ return {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP}
78
+ return {"start_new_session": True}
79
+
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # Liveness probes
83
+ # ---------------------------------------------------------------------------
84
+
85
+ LOOPBACK_HOST = "127.0.0.1"
86
+ DEFAULT_PROBE_TIMEOUT_S = 0.5
87
+ _MIN_PORT = 1
88
+ _MAX_PORT = 65535
89
+
90
+
91
+ def valid_port(port: object) -> bool:
92
+ return isinstance(port, int) and not isinstance(port, bool) and _MIN_PORT <= port <= _MAX_PORT
93
+
94
+
95
+ def probe_tcp_port(
96
+ port: object,
97
+ *,
98
+ host: str = LOOPBACK_HOST,
99
+ timeout_s: float = DEFAULT_PROBE_TIMEOUT_S,
100
+ ) -> bool:
101
+ """True when something accepts a TCP connection on ``port``.
102
+
103
+ Deliberately the cheapest possible check: connect, close. It answers the
104
+ only question that matters to a verifier about to curl the port.
105
+ """
106
+
107
+ if not valid_port(port):
108
+ return False
109
+ try:
110
+ with socket.create_connection(
111
+ (host, int(port)), # type: ignore[arg-type]
112
+ timeout=max(0.01, float(timeout_s)),
113
+ ):
114
+ return True
115
+ except OSError:
116
+ return False
117
+
118
+
119
+ def describe_port_probe(port: object, listening: bool) -> str:
120
+ if not valid_port(port):
121
+ return ""
122
+ number = int(port) # type: ignore[call-overload]
123
+ return f"listening on :{number}" if listening else f"nothing listening on :{number}"
124
+
125
+
126
+ def pid_alive(pid: object) -> bool:
127
+ """True when ``pid`` names a live process.
128
+
129
+ ``os.kill(pid, 0)`` is the POSIX existence probe. It is *not* one on
130
+ Windows, where signal 0 still terminates the target, so that platform takes
131
+ the OpenProcess route instead.
132
+ """
133
+
134
+ if not isinstance(pid, int) or isinstance(pid, bool) or pid <= 0:
135
+ return False
136
+ if os.name == "nt": # pragma: no cover - exercised on Windows
137
+ return _windows_pid_alive(pid)
138
+ try:
139
+ os.kill(pid, 0)
140
+ except OSError as exc:
141
+ if exc.errno == errno.EPERM:
142
+ # Alive, just not ours to signal.
143
+ return True
144
+ return False
145
+ return True
146
+
147
+
148
+ def _windows_pid_alive(pid: int) -> bool: # pragma: no cover - exercised on Windows
149
+ try:
150
+ import ctypes
151
+ from ctypes import wintypes
152
+ except Exception:
153
+ return False
154
+ process_query_limited_information = 0x1000
155
+ still_active = 259
156
+ kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined]
157
+ handle = kernel32.OpenProcess(process_query_limited_information, False, int(pid))
158
+ if not handle:
159
+ return False
160
+ try:
161
+ exit_code = wintypes.DWORD()
162
+ if not kernel32.GetExitCodeProcess(handle, ctypes.byref(exit_code)):
163
+ return False
164
+ return int(exit_code.value) == still_active
165
+ finally:
166
+ kernel32.CloseHandle(handle)
167
+
168
+
169
+ # ---------------------------------------------------------------------------
170
+ # Port inference
171
+ # ---------------------------------------------------------------------------
172
+
173
+ # First match wins, so the explicit forms come before the positional ones. This
174
+ # is a convenience heuristic only: an explicit probe_port argument always wins,
175
+ # and a miss costs nothing beyond a pid-only liveness line.
176
+ _PORT_PATTERNS: tuple[re.Pattern[str], ...] = (
177
+ re.compile(r"--port[=\s]+(\d{1,5})"),
178
+ re.compile(r"\bPORT=(\d{1,5})"),
179
+ # docker-style publish: the host port is the one a verifier can reach.
180
+ re.compile(r"\s-p[=\s]+(\d{1,5}):\d{1,5}"),
181
+ re.compile(r"\s-p[=\s]+(\d{1,5})\b"),
182
+ # 0.0.0.0:8000, localhost:3000, :5000
183
+ re.compile(r"(?:\d{1,3}(?:\.\d{1,3}){3}|localhost)?:(\d{1,5})\b"),
184
+ re.compile(r"\bport\s*=\s*(\d{1,5})", re.IGNORECASE),
185
+ )
186
+
187
+
188
+ def parse_probe_port(cmd: str) -> int | None:
189
+ """Best-effort port extraction from a service command line."""
190
+
191
+ text = str(cmd or "")
192
+ if not text.strip():
193
+ return None
194
+ for pattern in _PORT_PATTERNS:
195
+ match = pattern.search(text)
196
+ if match is None:
197
+ continue
198
+ try:
199
+ candidate = int(match.group(1))
200
+ except (TypeError, ValueError): # pragma: no cover - regex guarantees digits
201
+ continue
202
+ if valid_port(candidate):
203
+ return candidate
204
+ return None
205
+
206
+
207
+ def resolve_probe_port(*, requested: object, cmd: str) -> int | None:
208
+ """An explicit ``probe_port`` wins; otherwise infer one from the command."""
209
+
210
+ if requested is None or (isinstance(requested, str) and not requested.strip()):
211
+ return parse_probe_port(cmd)
212
+ try:
213
+ candidate = int(requested) # type: ignore[call-overload]
214
+ except (TypeError, ValueError):
215
+ raise ValueError("probe_port must be an integer between 1 and 65535") from None
216
+ if isinstance(requested, bool) or not valid_port(candidate):
217
+ raise ValueError("probe_port must be an integer between 1 and 65535")
218
+ return candidate
219
+
220
+
221
+ def readiness_spec_for_port(
222
+ port: int,
223
+ *,
224
+ host: str = LOOPBACK_HOST,
225
+ timeout_s: float = 5.0,
226
+ ) -> dict[str, Any]:
227
+ """A readiness spec the durable-service manager already understands."""
228
+
229
+ return {"type": "tcp", "host": host, "port": int(port), "timeout_s": float(timeout_s)}
230
+
231
+
232
+ # ---------------------------------------------------------------------------
233
+ # Registry
234
+ # ---------------------------------------------------------------------------
235
+
236
+
237
+ @dataclass(frozen=True)
238
+ class PersistentServiceRecord:
239
+ """One persist-mode start, as remembered for the finalization re-check."""
240
+
241
+ service_id: str
242
+ command: str
243
+ pid: int
244
+ probe_port: int | None = None
245
+
246
+ def summary(self) -> str:
247
+ label = " ".join(str(self.command or "").split()) or self.service_id
248
+ if len(label) > 120:
249
+ label = f"{label[:117]}..."
250
+ if self.probe_port is not None:
251
+ return f"{label} (pid {self.pid}, port {self.probe_port})"
252
+ return f"{label} (pid {self.pid})"
253
+
254
+
255
+ @dataclass(frozen=True)
256
+ class LivenessReport:
257
+ pid_alive: bool
258
+ probe_port: int | None = None
259
+ port_listening: bool | None = None
260
+
261
+ @property
262
+ def healthy(self) -> bool:
263
+ if not self.pid_alive:
264
+ return False
265
+ if self.probe_port is None:
266
+ return True
267
+ return bool(self.port_listening)
268
+
269
+ def describe(self) -> str:
270
+ parts = ["pid alive" if self.pid_alive else "pid not running"]
271
+ if self.probe_port is not None:
272
+ parts.append(describe_port_probe(self.probe_port, bool(self.port_listening)))
273
+ return "; ".join(part for part in parts if part)
274
+
275
+ def as_payload(self) -> dict[str, Any]:
276
+ payload: dict[str, Any] = {"pid_alive": self.pid_alive, "liveness": self.describe()}
277
+ if self.probe_port is not None:
278
+ payload["probe_port"] = self.probe_port
279
+ payload["port_listening"] = bool(self.port_listening)
280
+ return payload
281
+
282
+
283
+ PidProbe = Callable[[object], bool]
284
+ PortProbe = Callable[[object], bool]
285
+
286
+
287
+ def check_service(
288
+ record: PersistentServiceRecord,
289
+ *,
290
+ pid_probe: PidProbe = pid_alive,
291
+ port_probe: PortProbe = probe_tcp_port,
292
+ ) -> LivenessReport:
293
+ alive = bool(pid_probe(record.pid))
294
+ if record.probe_port is None:
295
+ return LivenessReport(pid_alive=alive)
296
+ # A dead pid cannot be serving; skip the connect and its timeout.
297
+ listening = bool(port_probe(record.probe_port)) if alive else False
298
+ return LivenessReport(pid_alive=alive, probe_port=record.probe_port, port_listening=listening)
299
+
300
+
301
+ class PersistentServiceRegistry:
302
+ """Session-scoped record of persist-mode starts, in start order."""
303
+
304
+ def __init__(self) -> None:
305
+ self._records: dict[str, PersistentServiceRecord] = {}
306
+
307
+ def register(self, record: PersistentServiceRecord) -> None:
308
+ self._records[record.service_id] = record
309
+
310
+ def forget(self, service_id: object) -> None:
311
+ self._records.pop(str(service_id), None)
312
+
313
+ def records(self) -> tuple[PersistentServiceRecord, ...]:
314
+ return tuple(self._records.values())
315
+
316
+ def __len__(self) -> int:
317
+ return len(self._records)
318
+
319
+ def __bool__(self) -> bool:
320
+ return bool(self._records)
321
+
322
+
323
+ # ---------------------------------------------------------------------------
324
+ # Finalization re-check
325
+ # ---------------------------------------------------------------------------
326
+
327
+ # The single model-visible string this module introduces. Kept as one constant
328
+ # so it is greppable and cannot drift.
329
+ SERVICE_CHECK_NOTICE_TEMPLATE = (
330
+ "Service check: process started as a persistent service is no longer running: "
331
+ "{summary}. Restart it or note why it is not needed."
332
+ )
333
+
334
+
335
+ def finalize_service_notice(
336
+ records: Iterable[PersistentServiceRecord],
337
+ *,
338
+ pid_probe: PidProbe = pid_alive,
339
+ port_probe: PortProbe = probe_tcp_port,
340
+ ) -> str | None:
341
+ """One notice covering every unhealthy service, or None when all are fine.
342
+
343
+ Deliberately a single message no matter how many services died: the point is
344
+ to tell the model something needs attention, not to spend the remaining
345
+ budget enumerating.
346
+ """
347
+
348
+ unhealthy: list[str] = []
349
+ for record in records:
350
+ report = check_service(record, pid_probe=pid_probe, port_probe=port_probe)
351
+ if not report.healthy:
352
+ unhealthy.append(f"{record.summary()} - {report.describe()}")
353
+ if not unhealthy:
354
+ return None
355
+ return SERVICE_CHECK_NOTICE_TEMPLATE.format(summary="; ".join(unhealthy))
@@ -0,0 +1,108 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path, PurePosixPath
5
+
6
+
7
+ def normalize_artifact_parts(parts: tuple[str, ...]) -> list[str]:
8
+ normalized: list[str] = []
9
+ for raw_part in parts:
10
+ text = str(raw_part).strip().replace("\\", "/")
11
+ if not text:
12
+ raise ValueError("artifact path part cannot be empty")
13
+ candidate = PurePosixPath(text)
14
+ if candidate.is_absolute():
15
+ raise ValueError("artifact path part must be relative")
16
+ for segment in candidate.parts:
17
+ if segment in {"", "."}:
18
+ continue
19
+ if segment == "..":
20
+ raise ValueError("artifact path cannot traverse upward")
21
+ normalized.append(segment)
22
+ if not normalized:
23
+ raise ValueError("artifact path cannot be empty")
24
+ return normalized
25
+
26
+
27
+ def _path_is_under_root(*, path: Path, root: Path | None) -> bool:
28
+ if root is None:
29
+ return False
30
+ try:
31
+ path.resolve().relative_to(root.resolve())
32
+ except ValueError:
33
+ return False
34
+ return True
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ModelArtifactReference:
39
+ locator: str
40
+ artifact_readable_via_fs: bool
41
+ artifact_location: str
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class SessionArtifactLayout:
46
+ filesystem_root: Path
47
+ locator_prefix: str = "session_artifacts"
48
+
49
+ def artifact_fs_path(self, *parts: str) -> Path:
50
+ return self.filesystem_root.joinpath(*normalize_artifact_parts(parts))
51
+
52
+ def artifact_locator(self, *parts: str) -> str:
53
+ locator_parts = normalize_artifact_parts((self.locator_prefix, *parts))
54
+ return "/".join(locator_parts)
55
+
56
+ def locator_for_path(self, artifact_path: Path) -> str:
57
+ rel = artifact_path.resolve().relative_to(self.filesystem_root.resolve()).as_posix()
58
+ return self.artifact_locator(rel)
59
+
60
+ def resolve_locator(self, locator: str) -> Path:
61
+ """Resolve one logical locator inside this session's artifact root."""
62
+
63
+ text = str(locator or "").strip()
64
+ if not text:
65
+ raise ValueError("locator cannot be empty")
66
+ if "\\" in text or text.startswith("/"):
67
+ raise ValueError("locator must be a relative POSIX locator")
68
+ segments = text.split("/")
69
+ if any(segment in {"", ".", ".."} for segment in segments):
70
+ raise ValueError("locator contains an invalid path segment")
71
+ prefix = normalize_artifact_parts((self.locator_prefix,))
72
+ if segments[: len(prefix)] != prefix or len(segments) == len(prefix):
73
+ raise ValueError(f"locator must start with {self.artifact_locator('x')[:-1]}")
74
+
75
+ root = self.filesystem_root.resolve()
76
+ candidate = root.joinpath(*segments[len(prefix) :]).resolve()
77
+ try:
78
+ candidate.relative_to(root)
79
+ except ValueError as exc:
80
+ raise ValueError("locator resolves outside the session artifact root") from exc
81
+ return candidate
82
+
83
+ def model_reference_for_path(
84
+ self,
85
+ *,
86
+ artifact_path: Path,
87
+ workspace_root: Path | None,
88
+ ) -> ModelArtifactReference:
89
+ readable_via_fs = _path_is_under_root(path=artifact_path, root=workspace_root)
90
+ return ModelArtifactReference(
91
+ locator=self.locator_for_path(artifact_path),
92
+ artifact_readable_via_fs=readable_via_fs,
93
+ artifact_location="workspace_root" if readable_via_fs else "external_session_store",
94
+ )
95
+
96
+ def display_reference_for_path(
97
+ self,
98
+ *,
99
+ artifact_path: Path,
100
+ workspace_root: Path | None,
101
+ ) -> str:
102
+ model_reference = self.model_reference_for_path(
103
+ artifact_path=artifact_path,
104
+ workspace_root=workspace_root,
105
+ )
106
+ if model_reference.artifact_readable_via_fs and workspace_root is not None:
107
+ return artifact_path.resolve().relative_to(workspace_root.resolve()).as_posix()
108
+ return model_reference.locator