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,3 @@
1
+ __all__ = ["__version__"]
2
+
3
+ __version__ = "0.13.0"
@@ -0,0 +1,4 @@
1
+ from .cli import app
2
+
3
+ if __name__ == "__main__":
4
+ app()
@@ -0,0 +1,14 @@
1
+ """Generated build identity. Do not edit by hand.
2
+
3
+ Regenerate with ``python3 scripts/generate_build_info.py``. The copy
4
+ committed to the repository is the dev default and deliberately reports an
5
+ unidentifiable build, so that an artifact built without running the
6
+ generator is refused by ``--require-clean-build`` instead of inheriting a
7
+ stale commit hash.
8
+ """
9
+
10
+ BUILD_COMMIT = ""
11
+ BUILD_TIMESTAMP = ""
12
+ BUILD_DIRTY = True
13
+ BUILD_SOURCE = "dev-default"
14
+ BUILD_INFO_SCHEMA_VERSION = 1
@@ -0,0 +1,468 @@
1
+ """Device-flow login for Alysis Code Pro (`alysis login`).
2
+
3
+ The CLI asks the account backend for a short user code (``ABCD-EFGH``), opens
4
+ the website's /activate page where the signed-in user approves it, and polls
5
+ until approval — then receives the user's long-lived gateway key (``slk_…``),
6
+ stores it locally, and activates the ``alysis`` provider profile pointed at
7
+ the Alysis Code LLM gateway (OpenAI-compatible; credits are metered server-side).
8
+
9
+ The device_code secret and the gateway key only ever travel in HTTPS request
10
+ and response bodies — never in URLs. The short user_code does appear in the
11
+ opened URL, but it is single-use, expires in minutes, and is worthless without
12
+ the device_code it is paired with.
13
+
14
+ This flow needs no localhost listener, so it works over SSH and inside WSL the
15
+ same as on a desktop.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import math
22
+ import platform
23
+ import time
24
+ import urllib.error
25
+ import urllib.request
26
+ from collections.abc import Callable
27
+ from dataclasses import dataclass
28
+ from datetime import UTC, datetime
29
+
30
+ from . import alysis_cloud as cloud
31
+ from .config import (
32
+ AppConfig,
33
+ clear_persisted_profile_key,
34
+ load_persisted_profile_keys,
35
+ save_config,
36
+ save_persisted_profile_key,
37
+ )
38
+ from .host_browser import open_url
39
+ from .profile_presets import get_preset, make_profile_from_preset
40
+ from .profiles import ProfileSpec, add_profile, get_profile, set_active_profile
41
+
42
+ # Matches the server-side device-code expiry (15 min) so we poll until the code
43
+ # itself dies rather than giving up while the user is still signing in.
44
+ _DEFAULT_TIMEOUT_S = 900.0
45
+ _HTTP_TIMEOUT_S = 15.0
46
+ _DEFAULT_POLL_INTERVAL_S = 5.0
47
+ # Kept short: model discovery runs inline while rendering the interactive `/config`
48
+ # model picker, so an offline/slow gateway must not stall the menu for long.
49
+ _MODELS_TIMEOUT_S = 6.0
50
+
51
+ _FRESH_LOGIN_DEFAULT_MODEL = "deepseek-v4-flash"
52
+
53
+
54
+ class AlysisLoginError(Exception):
55
+ """Raised when the device login handshake fails."""
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class LoginResult:
60
+ email: str | None
61
+ profile_name: str
62
+ base_url: str
63
+ model: str
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class LoginStatus:
68
+ logged_in: bool
69
+ profile_name: str
70
+ base_url: str
71
+ active: bool
72
+ key_preview: str | None
73
+
74
+
75
+ @dataclass(frozen=True)
76
+ class TrialStatus:
77
+ """Account status snapshot (kept name-compatible with the old MiMo trial)."""
78
+
79
+ plan: str | None
80
+ email: str | None
81
+ trial_ends_at: str | None
82
+ tokens_total: int | None
83
+ tokens_used: int | None
84
+ tokens_remaining: int | None
85
+
86
+
87
+ # ---------------------------------------------------------------------------
88
+ # Public API
89
+ # ---------------------------------------------------------------------------
90
+ def login(
91
+ cfg: AppConfig,
92
+ *,
93
+ timeout_s: float = _DEFAULT_TIMEOUT_S,
94
+ browser_opener: Callable[[str], bool] | None = None,
95
+ output_write: Callable[[str], None] | None = None,
96
+ ) -> LoginResult:
97
+ """Run the device-flow login and persist the resulting gateway key."""
98
+ writer = output_write or (lambda _msg: None)
99
+
100
+ # Fail fast on an insecure (non-https) cloud URL before starting, so
101
+ # neither the device_code nor the gateway key could leave over http://.
102
+ try:
103
+ cloud.device_code_url()
104
+ cloud.device_token_url()
105
+ cloud.gateway_base_url()
106
+ except cloud.AlysisCloudConfigError as exc:
107
+ raise AlysisLoginError(str(exc)) from exc
108
+
109
+ grant = _request_device_code()
110
+
111
+ writer(f"Your one-time code: [bold]{grant.user_code}[/bold]")
112
+ writer(f"Approve it in your browser:\n {grant.verification_url}")
113
+ opener = browser_opener or _open_browser
114
+ if not _safe_open(opener, grant.verification_url):
115
+ writer("Could not open a browser automatically. Open the URL above manually.")
116
+ writer("Waiting for approval…")
117
+
118
+ key = _poll_for_key(grant, timeout_s=timeout_s)
119
+
120
+ try:
121
+ # Create + activate the profile first (this saves config); only then
122
+ # persist the key, so a save failure never leaves a stored key pointing
123
+ # at an unconfigured profile. resolve_api_key then returns the gateway
124
+ # key as the Bearer for the `alysis` profile (no other wiring).
125
+ result = _activate_alysis_profile(cfg, email=None)
126
+ save_persisted_profile_key(cloud.PROFILE_KEY, key)
127
+ except OSError as exc:
128
+ raise AlysisLoginError(f"Logged in, but couldn't save your session locally: {exc}") from exc
129
+ return result
130
+
131
+
132
+ def logout(cfg: AppConfig) -> bool:
133
+ """Revoke the gateway key server-side and forget it locally.
134
+
135
+ The server revocation is what actually ends access: any client still
136
+ holding the key in memory (including the current chat session) gets a
137
+ 401 on its next request. Best-effort — offline logout still clears the
138
+ local key, and the orphaned server key can be revoked from the website
139
+ later. Returns True if a stored key was cleared.
140
+ """
141
+ stored = load_persisted_profile_keys().get(cloud.PROFILE_KEY)
142
+
143
+ if stored:
144
+ try:
145
+ request = urllib.request.Request(
146
+ f"{cloud.gateway_base_url()}/logout",
147
+ data=b"{}",
148
+ headers={
149
+ "Authorization": f"Bearer {stored}",
150
+ "Content-Type": "application/json",
151
+ },
152
+ method="POST",
153
+ )
154
+ urllib.request.urlopen(request, timeout=_HTTP_TIMEOUT_S).close()
155
+ except Exception: # noqa: BLE001 - revocation is best-effort
156
+ pass
157
+
158
+ return clear_persisted_profile_key(cloud.PROFILE_KEY)
159
+
160
+
161
+ def login_status(cfg: AppConfig) -> LoginStatus:
162
+ """Report whether an Alysis Code Pro session is connected."""
163
+ stored = load_persisted_profile_keys().get(cloud.PROFILE_KEY)
164
+ active = str((cfg.extra_fields or {}).get("active_profile") or "").strip()
165
+ profile = get_profile(cfg, cloud.PROFILE_KEY)
166
+ base_url = profile.base_url if profile is not None else cloud.gateway_base_url()
167
+ preview = None
168
+ if stored:
169
+ preview = stored[:8] + "…" if len(stored) > 8 else stored
170
+ return LoginStatus(
171
+ logged_in=bool(stored),
172
+ profile_name=cloud.PROFILE_KEY,
173
+ base_url=base_url,
174
+ active=active == cloud.PROFILE_KEY,
175
+ key_preview=preview,
176
+ )
177
+
178
+
179
+ def fetch_trial_status(cfg: AppConfig) -> TrialStatus | None:
180
+ """Fetch live account status. Currently returns None (no status endpoint).
181
+
182
+ Plan status, credit balance, and usage live on the product site's /account
183
+ page (see ``alysis_cloud.account_url``). A CLI-visible status endpoint is planned;
184
+ until then this stays best-effort-None so callers degrade exactly like the
185
+ old offline path — `whoami` still shows local connection state.
186
+ """
187
+ return None
188
+
189
+
190
+ def list_trial_models(cfg: AppConfig) -> list[str]:
191
+ """List the model ids the gateway currently serves, via ``/v1/models``.
192
+
193
+ The gateway exposes its allowlist as an OpenAI-shaped
194
+ ``{"data": [{"id": …}]}``. Surfacing it lets `/config` show the live model
195
+ list instead of pinning static ids. Best-effort: returns ``[]`` on any
196
+ failure (offline, non-200, malformed) so callers fall back to the static
197
+ preset models. The stored gateway key is sent when present (the gateway may
198
+ require it) and never required for the call to be attempted.
199
+ """
200
+ try:
201
+ url = cloud.models_url()
202
+ except cloud.AlysisCloudConfigError:
203
+ return []
204
+ headers = {}
205
+ access_key = load_persisted_profile_keys().get(cloud.PROFILE_KEY)
206
+ if access_key:
207
+ headers["Authorization"] = f"Bearer {access_key}"
208
+ request = urllib.request.Request(url, headers=headers, method="GET")
209
+ try:
210
+ with urllib.request.urlopen(request, timeout=_MODELS_TIMEOUT_S) as response:
211
+ body = json.loads(response.read().decode("utf-8"))
212
+ except (urllib.error.URLError, TimeoutError, OSError, ValueError, UnicodeDecodeError):
213
+ return []
214
+ data = body.get("data") if isinstance(body, dict) else None
215
+ if not isinstance(data, list):
216
+ return []
217
+ models: list[str] = []
218
+ for item in data:
219
+ if not isinstance(item, dict):
220
+ continue
221
+ model_id = str(item.get("id") or "").strip()
222
+ if model_id and model_id not in models:
223
+ models.append(model_id)
224
+ return models
225
+
226
+
227
+ def format_trial_status_line(status: TrialStatus) -> str | None:
228
+ """A one-line account summary for `whoami`, or None if there's nothing to show."""
229
+ parts: list[str] = []
230
+ days = _days_left(status.trial_ends_at)
231
+ if days is not None:
232
+ parts.append("expired" if days <= 0 else f"{days} day{'s' if days != 1 else ''} left")
233
+ ends = _format_date(status.trial_ends_at)
234
+ if ends:
235
+ parts.append(f"ends {ends}")
236
+ tokens = _format_token_usage(status.tokens_used, status.tokens_total)
237
+ if tokens:
238
+ parts.append(tokens)
239
+ if not parts:
240
+ return None
241
+ label = (status.plan or "Account").capitalize()
242
+ return f"{label}: " + " · ".join(parts)
243
+
244
+
245
+ # ---------------------------------------------------------------------------
246
+ # Device flow internals
247
+ # ---------------------------------------------------------------------------
248
+ @dataclass(frozen=True)
249
+ class _DeviceGrant:
250
+ device_code: str
251
+ user_code: str
252
+ verification_url: str
253
+ interval_s: float
254
+ expires_in_s: float
255
+
256
+
257
+ def _function_headers() -> dict[str, str]:
258
+ # Supabase routes function calls with the public anon key; the device
259
+ # endpoints themselves are public by design (verify_jwt off server-side).
260
+ return {
261
+ "Content-Type": "application/json",
262
+ "apikey": cloud.ANON_KEY,
263
+ "Authorization": f"Bearer {cloud.ANON_KEY}",
264
+ }
265
+
266
+
267
+ def _post_json(url: str, payload: dict) -> dict:
268
+ request = urllib.request.Request(
269
+ url,
270
+ data=json.dumps(payload).encode("utf-8"),
271
+ headers=_function_headers(),
272
+ method="POST",
273
+ )
274
+ try:
275
+ with urllib.request.urlopen(request, timeout=_HTTP_TIMEOUT_S) as response:
276
+ body = json.loads(response.read().decode("utf-8"))
277
+ except urllib.error.HTTPError as exc:
278
+ raise AlysisLoginError(_http_error_message(exc)) from exc
279
+ except urllib.error.URLError as exc:
280
+ raise AlysisLoginError(
281
+ f"Could not reach the Alysis Code login service: {exc.reason}"
282
+ ) from exc
283
+ except (ValueError, UnicodeDecodeError) as exc:
284
+ raise AlysisLoginError("Login service returned an invalid response.") from exc
285
+ except (TimeoutError, OSError) as exc:
286
+ raise AlysisLoginError(f"Login request failed: {exc}") from exc
287
+ if not isinstance(body, dict):
288
+ raise AlysisLoginError("Login service returned an invalid response.")
289
+ return body
290
+
291
+
292
+ def _http_error_message(exc: urllib.error.HTTPError) -> str:
293
+ try:
294
+ detail = json.loads(exc.read().decode("utf-8"))
295
+ message = detail.get("error")
296
+ if isinstance(message, dict):
297
+ message = message.get("message")
298
+ if message:
299
+ return str(message)
300
+ except Exception: # noqa: BLE001 - best-effort error extraction
301
+ pass
302
+ return f"Login request failed (HTTP {exc.code})."
303
+
304
+
305
+ def _request_device_code() -> _DeviceGrant:
306
+ client_name = f"alysis-cli @ {platform.node() or 'unknown-host'}"[:80]
307
+ body = _post_json(cloud.device_code_url(), {"client_name": client_name})
308
+
309
+ device_code = str(body.get("device_code") or "").strip()
310
+ user_code = str(body.get("user_code") or "").strip()
311
+ if not device_code or not user_code:
312
+ raise AlysisLoginError("Login service did not return a device code.")
313
+
314
+ verification_url = str(
315
+ body.get("verification_url_complete") or body.get("verification_url") or ""
316
+ ).strip()
317
+ if not verification_url:
318
+ verification_url = f"{cloud.activate_url()}?code={user_code}"
319
+
320
+ try:
321
+ interval_s = max(float(body.get("interval", _DEFAULT_POLL_INTERVAL_S)), 0.0)
322
+ except (TypeError, ValueError):
323
+ interval_s = _DEFAULT_POLL_INTERVAL_S
324
+ try:
325
+ expires_in_s = max(float(body.get("expires_in", _DEFAULT_TIMEOUT_S)), 1.0)
326
+ except (TypeError, ValueError):
327
+ expires_in_s = _DEFAULT_TIMEOUT_S
328
+
329
+ return _DeviceGrant(
330
+ device_code=device_code,
331
+ user_code=user_code,
332
+ verification_url=verification_url,
333
+ interval_s=interval_s,
334
+ expires_in_s=expires_in_s,
335
+ )
336
+
337
+
338
+ def _poll_for_key(grant: _DeviceGrant, *, timeout_s: float) -> str:
339
+ deadline = time.monotonic() + max(1.0, min(timeout_s, grant.expires_in_s))
340
+ while True:
341
+ body = _post_json(cloud.device_token_url(), {"device_code": grant.device_code})
342
+ status = str(body.get("status") or "").strip().lower()
343
+
344
+ if status == "approved":
345
+ key = str(body.get("key") or "").strip()
346
+ if not key:
347
+ raise AlysisLoginError("Login service did not return a key.")
348
+ return key
349
+ if status == "denied":
350
+ raise AlysisLoginError("Login was rejected on the website.")
351
+ if status in {"expired", "not_found", "already_claimed"}:
352
+ raise AlysisLoginError("This login code expired. Run `alysis login` again.")
353
+ # status == "pending" (or anything unknown): keep waiting.
354
+
355
+ remaining = deadline - time.monotonic()
356
+ if remaining <= 0:
357
+ raise AlysisLoginError(
358
+ f"Timed out after {int(timeout_s)}s waiting for approval. Run `alysis login` again."
359
+ )
360
+ time.sleep(min(grant.interval_s, max(remaining, 0.0)) if grant.interval_s else 0.0)
361
+
362
+
363
+ # ---------------------------------------------------------------------------
364
+ # Profile + formatting internals
365
+ # ---------------------------------------------------------------------------
366
+ def _str_or_none(value: object) -> str | None:
367
+ text = str(value).strip() if value is not None else ""
368
+ return text or None
369
+
370
+
371
+ def _int_or_none(value: object) -> int | None:
372
+ try:
373
+ return int(value) # type: ignore[arg-type]
374
+ except (TypeError, ValueError):
375
+ return None
376
+
377
+
378
+ def _parse_iso(value: str | None) -> datetime | None:
379
+ if not value:
380
+ return None
381
+ try:
382
+ dt = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
383
+ except ValueError:
384
+ return None
385
+ return dt if dt.tzinfo is not None else dt.replace(tzinfo=UTC)
386
+
387
+
388
+ def _days_left(ends_at: str | None) -> int | None:
389
+ ends = _parse_iso(ends_at)
390
+ if ends is None:
391
+ return None
392
+ seconds = (ends - datetime.now(UTC)).total_seconds()
393
+ return 0 if seconds <= 0 else math.ceil(seconds / 86400)
394
+
395
+
396
+ def _format_date(value: str | None) -> str | None:
397
+ dt = _parse_iso(value)
398
+ return dt.date().isoformat() if dt is not None else None
399
+
400
+
401
+ def _format_token_usage(used: int | None, total: int | None) -> str | None:
402
+ if used is None and total is None:
403
+ return None
404
+ if total:
405
+ return f"{used or 0:,} / {total:,} tokens used"
406
+ return f"{used or 0:,} tokens used"
407
+
408
+
409
+ def _activate_alysis_profile(cfg: AppConfig, *, email: str | None) -> LoginResult:
410
+ """Create + activate the `alysis` profile, keeping any model the user chose.
411
+
412
+ Fresh logins default to the Pro flagship (``deepseek-v4-flash``) so
413
+ subscribe → login → chat works with zero extra steps. Re-logins preserve
414
+ whatever model the user selected since, so logging in again never undoes
415
+ their choice. Legacy MiMo ids (the retired Xiaomi trial) are remapped to
416
+ the current default via the preset's model_aliases at config load.
417
+ """
418
+ preset = get_preset(cloud.PROFILE_KEY)
419
+ if preset is not None:
420
+ profile = make_profile_from_preset(preset, name=cloud.PROFILE_KEY)
421
+ else: # pragma: no cover - preset is always present
422
+ profile = ProfileSpec(name=cloud.PROFILE_KEY, protocol="openai_compat")
423
+
424
+ existing = get_profile(cfg, cloud.PROFILE_KEY)
425
+ existing_model = str(getattr(existing, "default_model", "") or "").strip() if existing else ""
426
+ chosen_model = existing_model or _FRESH_LOGIN_DEFAULT_MODEL
427
+
428
+ # Always pin to the live gateway URL (env-overridable for tests); keep the
429
+ # user's chosen model.
430
+ profile = ProfileSpec(
431
+ name=profile.name,
432
+ protocol=profile.protocol,
433
+ base_url=cloud.gateway_base_url(),
434
+ api_key_env=None,
435
+ extra_headers=dict(profile.extra_headers),
436
+ default_model=chosen_model,
437
+ reasoning_effort=(existing.reasoning_effort if existing is not None else None),
438
+ reasoning_trace_adapter=(
439
+ existing.reasoning_trace_adapter
440
+ if existing is not None
441
+ else profile.reasoning_trace_adapter
442
+ ),
443
+ web_search_adapter=profile.web_search_adapter,
444
+ web_search_model=profile.web_search_model,
445
+ notes=profile.notes,
446
+ )
447
+
448
+ add_profile(cfg, profile)
449
+ set_active_profile(cfg, profile.name)
450
+ save_config(cfg)
451
+
452
+ return LoginResult(
453
+ email=email,
454
+ profile_name=profile.name,
455
+ base_url=profile.base_url,
456
+ model=profile.default_model,
457
+ )
458
+
459
+
460
+ def _open_browser(url: str) -> bool:
461
+ return open_url(url)
462
+
463
+
464
+ def _safe_open(opener: Callable[[str], bool], url: str) -> bool:
465
+ try:
466
+ return bool(opener(url))
467
+ except Exception: # noqa: BLE001 - browser launch is best-effort
468
+ return False
@@ -0,0 +1,35 @@
1
+ # Agent Runtime
2
+
3
+ This package contains the core runtime for interactive chat, one-shot runs, and
4
+ managed execution flows.
5
+
6
+ ## Contents
7
+
8
+ - `session.py` builds session state from configuration, workspace context, and
9
+ runtime kind.
10
+ - `tools_assembly.py` builds the concrete tool surface for a session.
11
+ - `turn.py` runs the turn loop and tool-call iteration.
12
+ - `routing.py` handles routing and final response shaping.
13
+ - `prompt_context.py` prepares workspace, convention, skill, plugin, and
14
+ verification context.
15
+ - `verification.py` and `verification_commands.py` support verification flows.
16
+
17
+ ## Scope
18
+
19
+ Tool exposure is not implied by imports. It depends on runtime kind, execution
20
+ mode, workspace policy, configuration, and readiness checks.
21
+
22
+ Repository files, web content, MCP output, OCR text, custom tool output, and
23
+ model output should remain treated as untrusted unless a higher layer has
24
+ explicitly established otherwise.
25
+
26
+ ## Development
27
+
28
+ Prompt shape, tool ordering, and result summaries can affect behavior. Keep
29
+ changes narrow and test the runtime path being changed.
30
+
31
+ ## See Also
32
+
33
+ - [Architecture](../../../docs/architecture.md)
34
+ - [Security model](../../../docs/security_model.md)
35
+ - [Forge](../../../docs/forge.md)
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from typing import TypeVar
5
+
6
+ _T = TypeVar("_T")
7
+
8
+
9
+ def _patchable(name: str, default: _T) -> _T:
10
+ mod = sys.modules.get("alysis_code.agent_loop")
11
+ return getattr(mod, name, default) if mod is not None else default