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,972 @@
1
+ # ruff: noqa: F401,F403,F405,I001
2
+ # Legacy split module: dependencies are synced by cli_surface.py.
3
+ from __future__ import annotations
4
+
5
+ from ...surface.theme import (
6
+ _hex_to_8bit,
7
+ _load_jsonc_file,
8
+ _strip_json_comments,
9
+ _theme_from_colorfgbg,
10
+ _theme_from_hex_background,
11
+ _theme_from_osc11,
12
+ _theme_from_windows_terminal_settings,
13
+ _windows_terminal_builtin_scheme_theme,
14
+ _windows_terminal_scheme_name,
15
+ _windows_terminal_settings_paths,
16
+ detect_terminal_theme as _detect_owl_theme,
17
+ )
18
+ from ...branding import env_get
19
+ from .cli_common import *
20
+
21
+
22
+ def stripAnsi(value: str) -> str:
23
+ return re.sub(r"\x1b\[[0-9;]*m", "", value)
24
+
25
+
26
+ def visibleLength(value: str) -> int:
27
+ return len(stripAnsi(value))
28
+
29
+
30
+ def padLine(content: str, width: int) -> str:
31
+ current = visibleLength(content)
32
+ if current >= width:
33
+ return content
34
+ return content + (" " * (width - current))
35
+
36
+
37
+ def _clip_visible_line(content: str, width: int) -> str:
38
+ if width <= 0:
39
+ return ""
40
+ if visibleLength(content) <= width:
41
+ return content
42
+ cells = _ansi_visible_cells(content)
43
+ clipped = "".join(cells[:width])
44
+ if "\x1b[" in clipped:
45
+ clipped += "\x1b[0m"
46
+ return clipped
47
+
48
+
49
+ def _welcome_stream(console: Console | None) -> Any | None:
50
+ if console is None:
51
+ return None
52
+ return console.file if getattr(console, "file", None) is not None else sys.stdout
53
+
54
+
55
+ def _welcome_stream_is_tty(stream: Any | None) -> bool:
56
+ isatty = getattr(stream, "isatty", None)
57
+ if not callable(isatty):
58
+ return False
59
+ try:
60
+ return bool(isatty())
61
+ except Exception:
62
+ return False
63
+
64
+
65
+ def _truthy_env(name: str) -> bool:
66
+ return str(env_get(name) or "").strip().lower() in {"1", "true", "yes", "on"}
67
+
68
+
69
+ def _owl_assets_dir() -> Path:
70
+ return Path(__file__).resolve().parents[2] / "assets" / "owl"
71
+
72
+
73
+ def _should_animate_owl_logo(stream: Any | None) -> bool:
74
+ if _truthy_env("ALYSIS_NO_OWL") or env_get("ALYSIS_NO_INTRO"):
75
+ return False
76
+ if os.environ.get("CI") or _truthy_env("ALYSIS_CI"):
77
+ return False
78
+ if os.environ.get("PYTEST_CURRENT_TEST"):
79
+ return False
80
+ if not _welcome_stream_is_tty(stream):
81
+ return False
82
+ stdout = getattr(sys, "stdout", None)
83
+ if stream is not stdout:
84
+ return False
85
+ if not _welcome_stream_is_tty(getattr(sys, "stdin", None)):
86
+ return False
87
+ return _welcome_stream_is_tty(stdout)
88
+
89
+
90
+ def _load_owl_logo_frames(
91
+ *,
92
+ stream: Any | None,
93
+ color_enabled: bool,
94
+ theme: str | None = None,
95
+ ) -> list[list[str]]:
96
+ if _truthy_env("ALYSIS_NO_OWL"):
97
+ return []
98
+ theme = theme or _detect_owl_theme(stream)
99
+ frame_dir_name = "ascii"
100
+ frame_dir = _owl_assets_dir() / frame_dir_name
101
+ try:
102
+ frames = [
103
+ path.read_text(encoding="utf-8", errors="replace").splitlines()
104
+ for path in sorted(frame_dir.glob("f-*.txt"))
105
+ ]
106
+ except Exception:
107
+ frames = []
108
+ if not frames:
109
+ return []
110
+ if not color_enabled or theme == "neutral":
111
+ # With no reliable background signal, use the terminal's default
112
+ # foreground. Terminal profiles are expected to keep that readable on
113
+ # their own background; retaining the asset's fixed gray ramp would
114
+ # instead make the owl disappear on one side of the light/dark divide.
115
+ return [[stripAnsi(line) for line in frame] for frame in frames]
116
+ return frames
117
+
118
+
119
+ def _paint_owl_light_panel(frames: list[list[str]]) -> list[list[str]]:
120
+ if not frames:
121
+ return frames
122
+ bg = "\x1b[48;5;231m"
123
+ reset = "\x1b[0m"
124
+ horizontal_padding = 2
125
+ # Runtime-only contrast remap for light owl grays on the white panel.
126
+ panel_gray_map = {
127
+ "238": "16",
128
+ "239": "16",
129
+ "240": "16",
130
+ "241": "234",
131
+ "242": "234",
132
+ "243": "234",
133
+ "244": "236",
134
+ "245": "236",
135
+ "246": "236",
136
+ "247": "238",
137
+ "248": "238",
138
+ }
139
+
140
+ def remap_panel_grays(row: str) -> str:
141
+ return re.sub(
142
+ r"\x1b\[38;5;(238|239|240|241|242|243|244|245|246|247|248)m",
143
+ lambda match: f"\x1b[38;5;{panel_gray_map[match.group(1)]}m",
144
+ row,
145
+ )
146
+
147
+ width = max((visibleLength(row) for frame in frames for row in frame), default=0)
148
+ if width <= 0:
149
+ return frames
150
+ side_pad = " " * horizontal_padding
151
+ blank = f"{bg}{' ' * (width + (horizontal_padding * 2))}{reset}"
152
+ painted_frames: list[list[str]] = []
153
+ for frame in frames:
154
+ painted = [blank]
155
+ for row in frame:
156
+ contrast_row = remap_panel_grays(row)
157
+ safe_row = contrast_row.replace(reset, f"{reset}{bg}")
158
+ painted.append(f"{bg}{side_pad}{padLine(safe_row, width)}{side_pad}{reset}")
159
+ painted.append(blank)
160
+ painted_frames.append(painted)
161
+ return painted_frames
162
+
163
+
164
+ def _ansi_visible_cells(line: str) -> list[str]:
165
+ cells: list[str] = []
166
+ active_style = ""
167
+ index = 0
168
+ for match in re.finditer(r"\x1b\[[0-9;]*m", line):
169
+ for char in line[index : match.start()]:
170
+ cells.append(f"{active_style}{char}" if active_style else char)
171
+ token = match.group(0)
172
+ code = match.group(0)[2:-1]
173
+ active_style = "" if code in {"0", "39"} else token
174
+ index = match.end()
175
+ for char in line[index:]:
176
+ cells.append(f"{active_style}{char}" if active_style else char)
177
+ return cells
178
+
179
+
180
+ def _crop_owl_logo_frames(frames: list[list[str]]) -> list[list[str]]:
181
+ if not frames:
182
+ return frames
183
+ plain_frames = [[stripAnsi(line) for line in frame] for frame in frames]
184
+ height = max((len(frame) for frame in plain_frames), default=0)
185
+ width = max((len(line) for frame in plain_frames for line in frame), default=0)
186
+ if height <= 0 or width <= 0:
187
+ return frames
188
+
189
+ occupied: list[tuple[int, int]] = []
190
+ for frame in plain_frames:
191
+ for row_index in range(height):
192
+ line = frame[row_index] if row_index < len(frame) else ""
193
+ for col_index, char in enumerate(line.ljust(width)):
194
+ if char != " ":
195
+ occupied.append((row_index, col_index))
196
+ if not occupied:
197
+ return frames
198
+
199
+ top = min(row for row, _ in occupied)
200
+ bottom = max(row for row, _ in occupied) + 1
201
+ left = min(col for _, col in occupied)
202
+ right = max(col for _, col in occupied) + 1
203
+
204
+ cropped_frames: list[list[str]] = []
205
+ for frame in frames:
206
+ cropped_frame: list[str] = []
207
+ for row_index in range(top, bottom):
208
+ line = frame[row_index] if row_index < len(frame) else ""
209
+ cells = _ansi_visible_cells(line)
210
+ cells.extend(" " for _ in range(max(width - len(cells), 0)))
211
+ row = "".join(cells[left:right])
212
+ if "\x1b[" in row:
213
+ row += "\x1b[0m"
214
+ cropped_frame.append(row)
215
+ cropped_frames.append(cropped_frame)
216
+ return cropped_frames
217
+
218
+
219
+ def _welcome_style(value: str, code: str, *, enabled: bool) -> str:
220
+ if not enabled or not code:
221
+ return value
222
+ return f"\x1b[{code}m{value}\x1b[0m"
223
+
224
+
225
+ def _welcome_palette(theme: str) -> dict[str, str]:
226
+ if theme == "dark":
227
+ return {
228
+ "primary": "1;97",
229
+ "muted": "97",
230
+ "label": "97",
231
+ "value": "97",
232
+ "rule": "97",
233
+ }
234
+ if theme == "light":
235
+ return {
236
+ "primary": "1;30",
237
+ "muted": "30",
238
+ "label": "30",
239
+ "value": "30",
240
+ "rule": "30",
241
+ }
242
+ return {
243
+ "primary": "1",
244
+ "muted": "",
245
+ "label": "",
246
+ "value": "",
247
+ "rule": "",
248
+ }
249
+
250
+
251
+ def _welcome_workspace_value(workspace: str | os.PathLike[str] | None) -> str:
252
+ if workspace is None:
253
+ return ""
254
+ raw = os.fspath(workspace).strip()
255
+ if not raw:
256
+ return ""
257
+ try:
258
+ resolved = Path(raw).expanduser().resolve()
259
+ home = Path.home().resolve()
260
+ try:
261
+ rel_home = resolved.relative_to(home)
262
+ display = "~" if os.fspath(rel_home) == "." else f"~/{rel_home.as_posix()}"
263
+ except ValueError:
264
+ display = os.fspath(resolved)
265
+ except Exception:
266
+ display = raw
267
+ if len(display) > 30:
268
+ basename = Path(display).name
269
+ if basename:
270
+ return basename
271
+ return display
272
+
273
+
274
+ def _welcome_model_value(model: str | None) -> str:
275
+ clean = str(model or "").strip()
276
+ if not clean:
277
+ return ""
278
+ if "/" in clean:
279
+ clean = clean.rsplit("/", 1)[-1].strip()
280
+ return clean
281
+
282
+
283
+ def _welcome_context_line(
284
+ *,
285
+ workspace: str | os.PathLike[str] | None,
286
+ model: str | None,
287
+ version: str | None,
288
+ color_enabled: bool,
289
+ palette: dict[str, str],
290
+ ) -> str:
291
+ def label_text(value: str) -> str:
292
+ return _welcome_style(value, palette["label"], enabled=color_enabled)
293
+
294
+ def value_text(value: str) -> str:
295
+ return _welcome_style(value, palette["value"], enabled=color_enabled)
296
+
297
+ parts: list[str] = []
298
+ workspace_value = _welcome_workspace_value(workspace)
299
+ model_value = _welcome_model_value(model)
300
+ version_value = str(version or "").strip()
301
+ if workspace_value:
302
+ parts.append(f"{label_text('workspace')} {value_text(workspace_value)}")
303
+ if model_value:
304
+ parts.append(f"{label_text('model')} {value_text(model_value)}")
305
+ if version_value:
306
+ parts.append(f"{label_text('version')} {value_text(version_value)}")
307
+ return " ".join(parts)
308
+
309
+
310
+ def _welcome_context_lines(
311
+ *,
312
+ workspace: str | os.PathLike[str] | None,
313
+ model: str | None,
314
+ version: str | None,
315
+ color_enabled: bool,
316
+ palette: dict[str, str],
317
+ max_width: int,
318
+ ) -> list[str]:
319
+ line = _welcome_context_line(
320
+ workspace=workspace,
321
+ model=model,
322
+ version=version,
323
+ color_enabled=color_enabled,
324
+ palette=palette,
325
+ )
326
+ if not line:
327
+ return []
328
+ if visibleLength(line) <= max_width:
329
+ return [line]
330
+
331
+ workspace_line = _welcome_context_line(
332
+ workspace=workspace,
333
+ model=None,
334
+ version=None,
335
+ color_enabled=color_enabled,
336
+ palette=palette,
337
+ )
338
+ model_version_line = _welcome_context_line(
339
+ workspace=None,
340
+ model=model,
341
+ version=version,
342
+ color_enabled=color_enabled,
343
+ palette=palette,
344
+ )
345
+ if model_version_line and visibleLength(model_version_line) > max_width:
346
+ model_line = _welcome_context_line(
347
+ workspace=None,
348
+ model=model,
349
+ version=None,
350
+ color_enabled=color_enabled,
351
+ palette=palette,
352
+ )
353
+ version_line = _welcome_context_line(
354
+ workspace=None,
355
+ model=None,
356
+ version=version,
357
+ color_enabled=color_enabled,
358
+ palette=palette,
359
+ )
360
+ return [line for line in (workspace_line, model_line, version_line) if line]
361
+ return [line for line in (workspace_line, model_version_line) if line]
362
+
363
+
364
+ def printWelcome(
365
+ console: Console | None = None,
366
+ *,
367
+ workspace: str | os.PathLike[str] | None = None,
368
+ model: str | None = None,
369
+ version: str | None = __version__,
370
+ ) -> str:
371
+ stream = _welcome_stream(console)
372
+ terminal_width = shutil.get_terminal_size((80, 20)).columns
373
+ safe_line_width = max(1, terminal_width - 1)
374
+ no_color = bool(os.environ.get("NO_COLOR"))
375
+ color_enabled = not no_color and (stream is None or _welcome_stream_is_tty(stream))
376
+ indent = " "
377
+ gap = " "
378
+ welcome_theme = _detect_owl_theme(stream)
379
+ palette = _welcome_palette(welcome_theme)
380
+ owl_frames = _load_owl_logo_frames(
381
+ stream=stream,
382
+ color_enabled=color_enabled,
383
+ theme=welcome_theme,
384
+ )
385
+ owl_frames = _crop_owl_logo_frames(owl_frames)
386
+ if welcome_theme == "dark" and color_enabled:
387
+ owl_frames = _paint_owl_light_panel(owl_frames)
388
+ fallback_icon_frames = [
389
+ [
390
+ " ◇ ",
391
+ " ╱ ╲ ",
392
+ " ◇ ◇ ",
393
+ " ╲ ╱ ",
394
+ " ◇ ",
395
+ ]
396
+ ]
397
+ logo_frames = owl_frames or fallback_icon_frames
398
+ logo_height = max((len(frame) for frame in logo_frames), default=0)
399
+ logo_width = max(
400
+ (visibleLength(row) for frame in logo_frames for row in frame),
401
+ default=0,
402
+ )
403
+ logo_frames = [
404
+ [padLine(row, logo_width) for row in frame]
405
+ + [(" " * logo_width) for _ in range(max(logo_height - len(frame), 0))]
406
+ for frame in logo_frames
407
+ ]
408
+ logo = logo_frames[0]
409
+ if terminal_width <= logo_width + visibleLength(indent):
410
+ indent = ""
411
+ narrow = safe_line_width < visibleLength(indent) + logo_width + visibleLength(gap) + 31
412
+
413
+ def primary_text(value: str) -> str:
414
+ return _welcome_style(value, palette["primary"], enabled=color_enabled)
415
+
416
+ def rule_text(value: str) -> str:
417
+ return _welcome_style(value, palette["rule"], enabled=color_enabled)
418
+
419
+ def body_text(value: str) -> str:
420
+ return _welcome_style(value, palette["muted"], enabled=color_enabled)
421
+
422
+ brand = f"{primary_text('Alysis Code')}{rule_text(' · ')}{primary_text('AlysisAI')}"
423
+ tagline = body_text("The autonomous coding agent")
424
+
425
+ def welcome_detail_lines(max_width: int) -> list[str]:
426
+ context_lines = _welcome_context_lines(
427
+ workspace=workspace,
428
+ model=model,
429
+ version=version,
430
+ color_enabled=color_enabled,
431
+ palette=palette,
432
+ max_width=max_width,
433
+ )
434
+ detail = [
435
+ brand,
436
+ tagline,
437
+ "",
438
+ *context_lines,
439
+ rule_text("─" * min(max(max_width, 1), 64)),
440
+ "",
441
+ f"{primary_text('/forge')} {body_text('begin an autonomous run')}",
442
+ f"{primary_text('/status')} {body_text('view run state and usage')}",
443
+ f"{primary_text('/help')} {body_text('show all commands')}",
444
+ ]
445
+ return detail
446
+
447
+ def welcome_lines_for_logo(logo_frame: list[str]) -> list[str]:
448
+ if narrow:
449
+ return [
450
+ *(f"{indent}{row.rstrip()}" for row in logo_frame),
451
+ "",
452
+ f"{indent}{brand}",
453
+ f"{indent}{tagline}",
454
+ "",
455
+ f"{indent}{primary_text('/forge')} {primary_text('/status')} {primary_text('/help')}",
456
+ ]
457
+
458
+ if owl_frames:
459
+ right_width = max(
460
+ safe_line_width - visibleLength(indent) - logo_width - visibleLength(gap),
461
+ 1,
462
+ )
463
+ detail_lines = welcome_detail_lines(right_width)
464
+ required_side_by_side_width = visibleLength(indent) + logo_width + visibleLength(gap)
465
+ required_side_by_side_width += max(
466
+ (visibleLength(line) for line in detail_lines),
467
+ default=0,
468
+ )
469
+ if safe_line_width >= required_side_by_side_width:
470
+ right_start = max((len(logo_frame) - len(detail_lines)) // 2, 0)
471
+ rendered_lines = []
472
+ total_rows = max(len(logo_frame), right_start + len(detail_lines))
473
+ for row_index in range(total_rows):
474
+ row = logo_frame[row_index] if row_index < len(logo_frame) else ""
475
+ line = f"{indent}{padLine(row, logo_width)}"
476
+ right_index = row_index - right_start
477
+ if 0 <= right_index < len(detail_lines):
478
+ line = f"{line}{gap}{detail_lines[right_index]}"
479
+ rendered_lines.append(_clip_visible_line(line.rstrip(), safe_line_width))
480
+ return rendered_lines
481
+ else:
482
+ rendered_lines = []
483
+ rendered_lines.extend(
484
+ _clip_visible_line(f"{indent}{row.rstrip()}".rstrip(), safe_line_width)
485
+ for row in logo_frame
486
+ )
487
+ rendered_lines.append("")
488
+ full_detail_width = max(safe_line_width - visibleLength(indent), 1)
489
+ full_detail_lines = welcome_detail_lines(full_detail_width)
490
+ rendered_lines.extend(
491
+ _clip_visible_line(f"{indent}{line}".rstrip(), safe_line_width)
492
+ for line in full_detail_lines
493
+ )
494
+ return rendered_lines
495
+
496
+ context_line = _welcome_context_line(
497
+ workspace=workspace,
498
+ model=model,
499
+ version=version,
500
+ color_enabled=color_enabled,
501
+ palette=palette,
502
+ )
503
+ rendered_lines = [
504
+ f"{indent}{logo_frame[0]}{gap}{brand}",
505
+ f"{indent}{logo_frame[1].rstrip()}",
506
+ f"{indent}{logo_frame[2]}{gap}{tagline}",
507
+ f"{indent}{logo_frame[3].rstrip()}",
508
+ f"{indent}{logo_frame[4].rstrip()}",
509
+ "",
510
+ ]
511
+ if context_line:
512
+ rendered_lines.append(f"{indent}{context_line}")
513
+ rendered_lines.extend(
514
+ [
515
+ f"{indent}{rule_text('─' * min(max(terminal_width - 4, 1), 64))}",
516
+ "",
517
+ f"{indent}{primary_text('/forge')} {body_text('begin an autonomous run')}",
518
+ f"{indent}{primary_text('/status')} {body_text('view run state and usage')}",
519
+ f"{indent}{primary_text('/help')} {body_text('show all commands')}",
520
+ ]
521
+ )
522
+ return rendered_lines
523
+
524
+ lines = welcome_lines_for_logo(logo)
525
+ lines = [_clip_visible_line(line, safe_line_width) for line in lines]
526
+
527
+ display_lines = ["", *lines, ""]
528
+ banner = "\n".join(display_lines)
529
+ if console is not None and stream is not None:
530
+ try:
531
+ should_animate = (
532
+ bool(owl_frames)
533
+ and _welcome_stream_is_tty(stream)
534
+ and not no_color
535
+ and _patchable("_should_animate_owl_logo", _should_animate_owl_logo)(stream)
536
+ )
537
+ if should_animate:
538
+ animation_frames = [
539
+ welcome_lines_for_logo(frame)
540
+ for frame in (logo_frames[0], *logo_frames[1:], logo_frames[0])
541
+ ]
542
+ block_height = max(
543
+ (len(frame_lines) for frame_lines in animation_frames), default=0
544
+ )
545
+ block_height += 1
546
+
547
+ def write_animation_block(frame_lines: list[str]) -> None:
548
+ for row_index in range(block_height - 1):
549
+ line = frame_lines[row_index] if row_index < len(frame_lines) else ""
550
+ line = _clip_visible_line(line, safe_line_width)
551
+ stream.write(f"\r\x1b[2K{line}\n")
552
+ stream.write("\r\x1b[2K\n")
553
+
554
+ stream.write("\n")
555
+ write_animation_block(animation_frames[0])
556
+ stream.flush()
557
+ for frame_lines in animation_frames[1:]:
558
+ time.sleep(0.10)
559
+ stream.write(f"\x1b[{block_height}A\r")
560
+ write_animation_block(frame_lines)
561
+ stream.flush()
562
+ stream.flush()
563
+ else:
564
+ stream.write(banner + "\n")
565
+ stream.flush()
566
+ except Exception:
567
+ console.print(banner, markup=False, highlight=False, soft_wrap=True)
568
+ return banner
569
+
570
+
571
+ def _chat_session_info_line(*, workspace: Path, model: str, mode: str) -> str:
572
+ workspace_name = workspace.name or os.fspath(workspace)
573
+ parts = [workspace_name, str(model or "?").strip() or "?", str(mode or "?").strip() or "?"]
574
+ return " · ".join(parts)
575
+
576
+
577
+ def _render_command_sections_panel(
578
+ *,
579
+ title: str,
580
+ sections: list[tuple[str, list[tuple[str, str]]]],
581
+ ) -> Panel:
582
+ if _patchable("_is_narrow_terminal", _is_narrow_terminal)():
583
+ lines: list[str] = []
584
+ for section_name, rows in sections:
585
+ lines.append(f"\n[bold]{section_name}[/bold]")
586
+ for cmd, desc in rows:
587
+ lines.append(f" {cmd} {desc}")
588
+ return _Panel("\n".join(lines).strip(), title=title, border_style="bright_black")
589
+
590
+ table = _Table(show_header=False, box=None, expand=True, padding=(0, 1), collapse_padding=True)
591
+ table.add_column("command", style=STYLE_EMPHASIS, no_wrap=True, ratio=2)
592
+ table.add_column("description", style="dim", no_wrap=False, ratio=5, overflow="fold")
593
+ for idx, (section_name, rows) in enumerate(sections):
594
+ table.add_row(f"[bold]{section_name}[/bold]", "")
595
+ for cmd, desc in rows:
596
+ table.add_row(cmd, desc)
597
+ if idx < len(sections) - 1:
598
+ table.add_row("", "")
599
+ return _Panel(table, title=title, border_style="bright_black")
600
+
601
+
602
+ def _forge_help_footer_lines() -> list[str]:
603
+ return [
604
+ "Plain /forge starts a fresh run for a new chat session.",
605
+ "/forge resume explicitly attaches to the current run pointer.",
606
+ "After /back, plain /forge resumes this chat session's run only in the same workspace.",
607
+ "Same-workspace re-entry keeps the same run id and tracks the chat's current focus.",
608
+ "Changing workspaces starts a fresh run instead of resuming prior session-local state.",
609
+ "Type freely to add requirements or talk to the planner.",
610
+ "Chat Plan Mode is unavailable.",
611
+ "In Forge, use:",
612
+ "/back",
613
+ "Forge plan commands:",
614
+ "/show",
615
+ "/plan markdown",
616
+ "/plan edit",
617
+ ]
618
+
619
+
620
+ def _is_forge_ui_mode(ui_mode: str) -> bool:
621
+ return str(ui_mode or "").strip().lower() == "forge"
622
+
623
+
624
+ def _forge_commands_panel() -> Panel:
625
+ sections = _chat_command_sections(ui_mode="forge")
626
+ footer_lines = _forge_help_footer_lines()
627
+ if _patchable("_is_narrow_terminal", _is_narrow_terminal)():
628
+ lines: list[str] = []
629
+ for section_name, rows in sections:
630
+ lines.append(f"\n[bold]{section_name}[/bold]")
631
+ for cmd, desc in rows:
632
+ lines.append(f" {cmd} {desc}")
633
+ lines.append("")
634
+ for line in footer_lines:
635
+ lines.append(f"[dim]{line}[/dim]")
636
+ return _Panel("\n".join(lines).strip(), title="Forge Commands", border_style="bright_black")
637
+
638
+ table = _Table(show_header=False, box=None, expand=True, padding=(0, 1), collapse_padding=True)
639
+ table.add_column("command", style=STYLE_EMPHASIS, no_wrap=True, ratio=2)
640
+ table.add_column("description", style="dim", no_wrap=False, ratio=5, overflow="fold")
641
+ for idx, (section_name, rows) in enumerate(sections):
642
+ table.add_row(f"[bold]{section_name}[/bold]", "")
643
+ for cmd, desc in rows:
644
+ table.add_row(cmd, desc)
645
+ if idx < len(sections) - 1:
646
+ table.add_row("", "")
647
+
648
+ content = _table_grid(expand=True)
649
+ content.add_row(table)
650
+ content.add_row("")
651
+ for line in footer_lines:
652
+ content.add_row(_forge_bar_text(text=line, style="dim"))
653
+ return _Panel(content, title="Forge Commands", border_style="bright_black")
654
+
655
+
656
+ def _chat_command_sections(*, ui_mode: str = "chat") -> list[tuple[str, list[tuple[str, str]]]]:
657
+ if _is_forge_ui_mode(ui_mode):
658
+ return [
659
+ (
660
+ "Forge",
661
+ [
662
+ ("/help", "show Forge & chat commands"),
663
+ ("/show", "show the current plan summary"),
664
+ ("/plan markdown|md", "preview PLAN.md for the current run"),
665
+ ("/plan edit", "edit plan.json and reload Forge state"),
666
+ ("/execute plan", "save, validate, and run scoped planned tasks"),
667
+ ("/goal <text>", "set project goal"),
668
+ (
669
+ "/task <title>",
670
+ "add a task; ambiguous/mutating work must name repo-relative file paths",
671
+ ),
672
+ ("/done", "save + validate the plan, then return to chat"),
673
+ ("/back", "return to chat without that final save/validate step"),
674
+ ],
675
+ ),
676
+ (
677
+ "Session",
678
+ [
679
+ ("/status", "session details"),
680
+ ("/subagents", "open an active subagent run"),
681
+ ("/terminals", "list/read/kill background processes"),
682
+ ("/pwd", "show active workdir, focus dir, and workspace root"),
683
+ ("/usage", "token count & cost; /usage hud on|off toggles HUD"),
684
+ ("/mode", "change execution mode"),
685
+ ("/persona", "switch persona (code, architect, ask, debug)"),
686
+ ("/stream", "toggle streaming"),
687
+ ("/trace", "reasoning detail (off/compact/full)"),
688
+ ],
689
+ ),
690
+ (
691
+ "Context",
692
+ [
693
+ ("/context", "context window left"),
694
+ ("/compact [focus]", "force compaction"),
695
+ ("/resume [id]", "continue previous session"),
696
+ ("/report [text]", "create feedback bundle + issue draft"),
697
+ ("/feedback [text]", "alias for /report"),
698
+ ],
699
+ ),
700
+ (
701
+ "Tools & Config",
702
+ [
703
+ ("/login", "choose Alysis Code or an AI subscription connection"),
704
+ (
705
+ "/skill",
706
+ "no args lists; <name> shows info; <name> <task> attaches",
707
+ ),
708
+ ("/image [path]", "add image (path, clipboard, Ctrl+Alt+V)"),
709
+ ("/assets", "open assets for the active Forge run"),
710
+ ("/config", "inline config menu; /config show|set|clear for model metadata"),
711
+ ("/toolbar", "customize toolbar items"),
712
+ ("/exit", "quit"),
713
+ ],
714
+ ),
715
+ ]
716
+
717
+ return [
718
+ (
719
+ "Getting Started",
720
+ [
721
+ ("/help", "commands & config"),
722
+ ("/status", "session details"),
723
+ ("/subagents", "open an active subagent run"),
724
+ ("/terminals", "list/read/kill background processes"),
725
+ ("/pwd", "show active workdir, focus dir, and workspace root"),
726
+ ("/usage", "token count & cost; /usage hud on|off toggles HUD"),
727
+ ],
728
+ ),
729
+ (
730
+ "Execution",
731
+ [
732
+ ("/mode", "change execution mode"),
733
+ ("/persona", "switch persona (code, architect, ask, debug)"),
734
+ ("/ask <question>", "one read-only turn; mode restored afterwards"),
735
+ (
736
+ "/plan <task>",
737
+ "default planning path: draft, review, approve, then execute; bare /plan shows usage",
738
+ ),
739
+ (
740
+ "/plan mode",
741
+ "secondary persistent readonly planning overlay; it does not execute by itself",
742
+ ),
743
+ (
744
+ "/plan approve",
745
+ "while Plan Mode is on, leave readonly planning and execute the stored draft",
746
+ ),
747
+ ("/trace", "reasoning detail (off/compact/full)"),
748
+ ],
749
+ ),
750
+ (
751
+ "Context",
752
+ [
753
+ ("/context", "context window left"),
754
+ ("/compact [focus]", "force compaction"),
755
+ ("/clear", "wipe conversation (keeps session id + log; Ctrl+L clears terminal)"),
756
+ ("/resume [id]", "continue previous session"),
757
+ ("/report [text]", "create feedback bundle + issue draft"),
758
+ ("/feedback [text]", "alias for /report"),
759
+ ],
760
+ ),
761
+ (
762
+ "Tools & Subagents",
763
+ [
764
+ (
765
+ "/skill",
766
+ "no args lists; <name> shows info; <name> <task> attaches",
767
+ ),
768
+ ("/image [path]", "add image (path, clipboard, Ctrl+Alt+V)"),
769
+ ("/assets", "open assets for the current Forge run pointer"),
770
+ (
771
+ "/forge [resume]",
772
+ "fresh run by default; same-workspace re-entry resumes session-local state and tracks the current focus; resume explicitly loads the current pointer",
773
+ ),
774
+ ],
775
+ ),
776
+ (
777
+ "Configuration",
778
+ [
779
+ ("/login", "choose Alysis Code or an AI subscription connection"),
780
+ ("/config", "inline config menu; /config show|set|clear for model metadata"),
781
+ ("/toolbar", "customize toolbar items"),
782
+ ("/exit", "quit"),
783
+ ],
784
+ ),
785
+ ]
786
+
787
+
788
+ def _chat_commands_panel(*, ui_mode: str = "chat") -> Panel:
789
+ if _is_forge_ui_mode(ui_mode):
790
+ return _forge_commands_panel()
791
+ title = "Forge Commands" if _is_forge_ui_mode(ui_mode) else "Commands"
792
+ return _render_command_sections_panel(
793
+ title=title,
794
+ sections=_chat_command_sections(ui_mode=ui_mode),
795
+ )
796
+
797
+
798
+ def _chat_quick_commands_panel(*, ui_mode: str = "chat") -> Panel:
799
+ return _chat_commands_panel(ui_mode=ui_mode)
800
+
801
+
802
+ def _chat_visible_commands(*, ui_mode: str = "chat") -> list[str]:
803
+ if _is_forge_ui_mode(ui_mode):
804
+ return list(_FORGE_SUGGESTION_COMMANDS)
805
+ return list(_CHAT_GLOBAL_VISIBLE_COMMANDS)
806
+
807
+
808
+ def _chat_completer_commands(*, ui_mode: str = "chat") -> list[str]:
809
+ if _is_forge_ui_mode(ui_mode):
810
+ return list(_FORGE_COMPLETER_COMMANDS)
811
+ return _ordered_unique_strings(
812
+ _CHAT_GLOBAL_VISIBLE_COMMANDS
813
+ + [
814
+ "/forge resume",
815
+ "/usage hud",
816
+ "/usage hud on",
817
+ "/usage hud off",
818
+ "/usage hud status",
819
+ "/terminals list",
820
+ "/terminals show",
821
+ "/terminals kill",
822
+ "/terminals help",
823
+ "/plan mode",
824
+ "/plan approve",
825
+ ]
826
+ )
827
+
828
+
829
+ def _suggest_chat_command(raw_command: str, *, ui_mode: str = "chat") -> str | None:
830
+ candidate = raw_command.strip().lower()
831
+ if not candidate.startswith("/"):
832
+ return None
833
+ if candidate in _CHAT_RETIRED_COMMANDS:
834
+ return None
835
+ if candidate in _CHAT_COMMANDS:
836
+ return None
837
+ matches = get_close_matches(candidate, _chat_visible_commands(ui_mode=ui_mode), n=1, cutoff=0.6)
838
+ return matches[0] if matches else None
839
+
840
+
841
+ def _rebuild_session_tools_for_mode(*, session: Any, mode: str) -> None:
842
+ scheduler_holder: dict[str, Any] = {}
843
+ build_kwargs = _session_build_tools_kwargs(session=session, mode=mode)
844
+ build_kwargs["child_scheduler_sink"] = lambda scheduler: scheduler_holder.__setitem__(
845
+ "scheduler", scheduler
846
+ )
847
+ tools = build_tools(**build_kwargs)
848
+ previous_scheduler = getattr(session, "child_scheduler", None)
849
+ replacement_scheduler = scheduler_holder.get("scheduler")
850
+ lifecycle_listener = getattr(previous_scheduler, "lifecycle_listener", None)
851
+ set_lifecycle_listener = getattr(
852
+ replacement_scheduler,
853
+ "set_lifecycle_listener",
854
+ None,
855
+ )
856
+ if callable(set_lifecycle_listener) and callable(lifecycle_listener):
857
+ set_lifecycle_listener(lifecycle_listener)
858
+ session.tools = tools
859
+ session.tool_list = [tool.as_openai_tool() for tool in tools.values()]
860
+ session.child_scheduler = replacement_scheduler
861
+ if previous_scheduler is not None and previous_scheduler is not replacement_scheduler:
862
+ clear_lifecycle_listener = getattr(
863
+ previous_scheduler,
864
+ "set_lifecycle_listener",
865
+ None,
866
+ )
867
+ if callable(clear_lifecycle_listener):
868
+ clear_lifecycle_listener(None)
869
+ notify_replaced = getattr(session, "on_child_scheduler_replaced", None)
870
+ if callable(notify_replaced):
871
+ notify_replaced("settings applied")
872
+ shutdown = getattr(previous_scheduler, "shutdown", None)
873
+ if callable(shutdown):
874
+ shutdown(cancel_pending=True)
875
+
876
+
877
+ def _session_build_tools_kwargs(*, session: Any, mode: str) -> dict[str, Any]:
878
+ cfg = getattr(session, "cfg", None)
879
+ max_steps = int(
880
+ getattr(session, "max_steps", getattr(cfg, "max_steps", DEFAULT_CHAT_MAX_STEPS))
881
+ or DEFAULT_CHAT_MAX_STEPS
882
+ )
883
+ authoritative_verification_commands = getattr(
884
+ session,
885
+ "authoritative_verification_commands",
886
+ None,
887
+ )
888
+ verification_selection = _agent_loop_module()._session_verify_command_selection(session)
889
+ effective_verification_commands = getattr(
890
+ session,
891
+ "effective_verification_commands",
892
+ None,
893
+ )
894
+ return {
895
+ "root": Path(getattr(session, "root", Path("."))),
896
+ "console": getattr(session, "console", None),
897
+ "surface": getattr(session, "surface", None),
898
+ "store": session.store,
899
+ # Carried across tool rebuilds (/mode and plan-mode transitions):
900
+ # dropping it would silently stop tracking the verifier's process groups
901
+ # for the rest of the session.
902
+ "process_group_registry": getattr(session, "process_group_registry", None),
903
+ "mode": mode,
904
+ "yes": bool(getattr(session, "yes", False)),
905
+ "cfg": cfg,
906
+ # Carried across tool rebuilds so an active switch_mode coordination
907
+ # cell (and the tool itself) survives /mode and persona transitions.
908
+ "persona_switch_state": getattr(session, "persona_switch_state", None),
909
+ "api_key": str(getattr(session, "api_key", "") or "") or None,
910
+ "max_steps": max_steps,
911
+ "no_log": bool(getattr(session, "no_log", False)),
912
+ "usage_role": str(getattr(session, "usage_role", "main") or "main"),
913
+ "usage_summary": getattr(session, "usage_summary", None),
914
+ "model_registry": getattr(session, "model_registry", None),
915
+ "deny_write_prefixes": getattr(session, "deny_write_prefixes", None),
916
+ "allow_write_globs": getattr(session, "allow_write_globs", None),
917
+ "persona_allow_write_globs": getattr(session, "persona_allow_write_globs", None),
918
+ "non_interactive": bool(getattr(session, "non_interactive", False)),
919
+ "shell_runner": getattr(session, "shell_runner", None),
920
+ "terminal_manager": getattr(session, "terminal_manager", None),
921
+ # Carried across tool rebuilds for the same reason as the process-group
922
+ # registry: the rewrite guard is per-run and fires once per file, so
923
+ # dropping it here would silently stop warning for the rest of the
924
+ # session and would forget which files it had already warned about.
925
+ "edit_discipline": getattr(session, "edit_discipline", None),
926
+ # Carried for the same reason again: the registry is what remembers
927
+ # which durable services this run started, so dropping it would leave
928
+ # the finalization liveness check blind to every service started before
929
+ # the rebuild and silently stop reporting a dead one.
930
+ "persistent_service_registry": getattr(session, "persistent_service_registry", None),
931
+ "verification_enabled": bool(getattr(session, "verification_enabled", True)),
932
+ "authoritative_verification_commands": (
933
+ list(authoritative_verification_commands)
934
+ if authoritative_verification_commands is not None
935
+ else None
936
+ ),
937
+ "effective_verification_commands": list(effective_verification_commands or []),
938
+ "verify_command_selection": verification_selection,
939
+ "get_verify_command_selection": lambda: (
940
+ _agent_loop_module()._session_verify_command_selection(session)
941
+ ),
942
+ "one_shot_execution": bool(getattr(session, "one_shot_execution", False)),
943
+ "skills_enabled": bool(getattr(session, "skills_enabled", True)),
944
+ "skill_registry": getattr(session, "skill_registry", None),
945
+ "subagents_enabled": bool(getattr(session, "subagents_enabled", False)),
946
+ "subagent_depth": int(getattr(session, "subagent_depth", 0) or 0),
947
+ "subagent_registry": getattr(session, "subagent_registry", None),
948
+ "parent_steer_inbox": getattr(session, "steer_inbox", None),
949
+ "session_log_dir_override": getattr(session, "session_log_dir_override", None),
950
+ "step_budget_runtime": getattr(session, "step_budget_runtime", None),
951
+ "runtime_kind": getattr(session, "runtime_kind", "interactive_chat"),
952
+ "mcp_manager": getattr(session, "mcp_manager", None),
953
+ "custom_tool_session_state": getattr(session, "custom_tool_session_state", None),
954
+ "managed_browser_service": getattr(session, "managed_browser_service", None),
955
+ "managed_browser_owner_id": getattr(session, "managed_browser_owner_id", None),
956
+ "managed_browser_cancel_check": getattr(session, "managed_browser_cancel_check", None),
957
+ "get_active_workdir_relpath": lambda: resolve_session_active_workdir_relpath(session),
958
+ "set_active_workdir_callback": (
959
+ lambda raw_path, source: set_session_active_workdir(
960
+ session,
961
+ raw_path,
962
+ source=source,
963
+ )
964
+ ),
965
+ }
966
+
967
+
968
+ def _chat_help_panel(*, ui_mode: str = "chat") -> Panel:
969
+ return _chat_commands_panel(ui_mode=ui_mode)
970
+
971
+
972
+ __all__ = [name for name in globals() if (not name.startswith("__") or name == "__version__")]