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,830 @@
1
+ # ruff: noqa: F821
2
+ # Dependencies are injected at runtime from alysis_code.cli to preserve monkeypatch surfaces.
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from ..runtime_kind import RuntimeKind
8
+ from ..step_budget import normalize_step_budget_policy, step_budget_is_autonomous
9
+ from ..surface.styles import STYLE_CONTENT, STYLE_EMPHASIS
10
+
11
+ _PROTECTED_GLOBAL_NAMES: set[str] = set()
12
+
13
+
14
+ def _sync_cli_globals(cli_mod: Any) -> None:
15
+ module_globals = globals()
16
+ if not _PROTECTED_GLOBAL_NAMES:
17
+ for local_name, local_value in module_globals.items():
18
+ if callable(local_value):
19
+ _PROTECTED_GLOBAL_NAMES.add(local_name)
20
+ for name, value in cli_mod.__dict__.items():
21
+ if name.startswith("__") or name in _PROTECTED_GLOBAL_NAMES:
22
+ continue
23
+ module_globals[name] = value
24
+
25
+
26
+ def _resume_positive_int(raw: object, *, default: int) -> int:
27
+ try:
28
+ value = int(raw) if raw is not None else default
29
+ except (TypeError, ValueError):
30
+ return default
31
+ return value if value > 0 else default
32
+
33
+
34
+ def _resume_optional_positive_int(raw: object) -> int | None:
35
+ try:
36
+ value = int(raw) if raw is not None else None
37
+ except (TypeError, ValueError):
38
+ return None
39
+ if value is None or value <= 0:
40
+ return None
41
+ return value
42
+
43
+
44
+ def _select_chat_resume_interactive(
45
+ *,
46
+ current_session_id: str,
47
+ sessions: list[SessionInfo],
48
+ console: Console,
49
+ ) -> tuple[str | None, bool]:
50
+ if _is_non_interactive_terminal():
51
+ return None, False
52
+ if not sessions:
53
+ return None, True
54
+ try:
55
+ from prompt_toolkit.input import create_input
56
+ from prompt_toolkit.keys import Keys
57
+ except Exception:
58
+ return None, False
59
+
60
+ selected_index = 0
61
+ scroll_offset = 0
62
+ rename_mode = False
63
+ rename_buffer = ""
64
+ status_message = ""
65
+ display_rows = _chat_resume_display_rows(sessions=sessions)
66
+
67
+ def _selected_session_id() -> str | None:
68
+ if not sessions:
69
+ return None
70
+ if selected_index < 0 or selected_index >= len(sessions):
71
+ return None
72
+ return sessions[selected_index].session_id
73
+
74
+ def _page_size() -> int:
75
+ return max(1, _resume_visible_session_rows(has_status_message=bool(status_message)))
76
+
77
+ def _sync_viewport() -> None:
78
+ nonlocal scroll_offset
79
+ if not sessions:
80
+ scroll_offset = 0
81
+ return
82
+ if selected_index < 0:
83
+ selected_index_local = 0
84
+ elif selected_index >= len(sessions):
85
+ selected_index_local = len(sessions) - 1
86
+ else:
87
+ selected_index_local = selected_index
88
+ scroll_offset = _clamp_resume_scroll_offset(
89
+ total_rows=len(sessions),
90
+ selected_index=selected_index_local,
91
+ scroll_offset=scroll_offset,
92
+ visible_session_rows=_page_size(),
93
+ )
94
+
95
+ def _render_panel() -> Panel:
96
+ _sync_viewport()
97
+ selected_id = _selected_session_id()
98
+ return _chat_resume_panel(
99
+ current_session_id=current_session_id,
100
+ sessions=sessions,
101
+ selected_session_id=selected_id,
102
+ interactive=True,
103
+ display_rows=display_rows,
104
+ rename_session_id=selected_id if rename_mode else None,
105
+ rename_buffer=rename_buffer,
106
+ status_message=status_message or None,
107
+ scroll_offset=scroll_offset,
108
+ visible_session_rows=_page_size(),
109
+ )
110
+
111
+ input_reader = create_input()
112
+ try:
113
+ with _watch_terminal_resize() as consume_resize:
114
+ with _Live(
115
+ _render_panel(),
116
+ console=console,
117
+ auto_refresh=False,
118
+ transient=False,
119
+ screen=True,
120
+ ) as live:
121
+ with input_reader.raw_mode():
122
+ while True:
123
+ resized = consume_resize()
124
+ if resized:
125
+ _sync_viewport()
126
+ _clear_terminal_screen(console=console)
127
+ live.update(_render_panel(), refresh=True)
128
+
129
+ key_presses = _read_input_keys_with_timeout(
130
+ input_reader=input_reader,
131
+ timeout_s=0.12,
132
+ )
133
+ if not key_presses:
134
+ continue
135
+ panel_updated = False
136
+ for key_press in key_presses:
137
+ key = key_press.key
138
+ key_char = _resume_picker_key_char(key_press)
139
+
140
+ if _terminal_too_small():
141
+ if (
142
+ key == Keys.Escape
143
+ or key == Keys.ControlC
144
+ or key
145
+ in {
146
+ "escape",
147
+ "c-c",
148
+ }
149
+ ):
150
+ return None, True
151
+ continue
152
+
153
+ if rename_mode:
154
+ if key == Keys.Enter or key in {"enter", "\r", "\n"}:
155
+ if sessions and 0 <= selected_index < len(sessions):
156
+ renamed, status_message = (
157
+ _rename_resume_session_custom_title(
158
+ info=sessions[selected_index],
159
+ new_title=rename_buffer,
160
+ )
161
+ )
162
+ if renamed:
163
+ display_rows = _chat_resume_display_rows(
164
+ sessions=sessions
165
+ )
166
+ else:
167
+ status_message = "Rename canceled."
168
+ rename_mode = False
169
+ _sync_viewport()
170
+ panel_updated = True
171
+ continue
172
+ if (
173
+ key == Keys.Escape
174
+ or key == Keys.ControlC
175
+ or key
176
+ in {
177
+ "escape",
178
+ "c-c",
179
+ }
180
+ ):
181
+ rename_mode = False
182
+ status_message = "Rename canceled."
183
+ _sync_viewport()
184
+ panel_updated = True
185
+ continue
186
+ if key == Keys.Backspace or key in {"backspace", "c-h"}:
187
+ if rename_buffer:
188
+ rename_buffer = rename_buffer[:-1]
189
+ panel_updated = True
190
+ continue
191
+ if key_char and key_char in {"\x7f", "\b"}:
192
+ if rename_buffer:
193
+ rename_buffer = rename_buffer[:-1]
194
+ panel_updated = True
195
+ continue
196
+ if key_char and key_char >= " ":
197
+ rename_buffer += key_char
198
+ panel_updated = True
199
+ continue
200
+
201
+ if key == Keys.Up or key == "up":
202
+ if selected_index > 0:
203
+ selected_index -= 1
204
+ status_message = ""
205
+ _sync_viewport()
206
+ panel_updated = True
207
+ continue
208
+ if key == Keys.Down or key == "down":
209
+ if selected_index < len(sessions) - 1:
210
+ selected_index += 1
211
+ status_message = ""
212
+ _sync_viewport()
213
+ panel_updated = True
214
+ continue
215
+ if key == Keys.PageUp or key in {"pageup", "s-pageup"}:
216
+ selected_index = max(0, selected_index - _page_size())
217
+ status_message = ""
218
+ _sync_viewport()
219
+ panel_updated = True
220
+ continue
221
+ if key == Keys.PageDown or key in {"pagedown", "s-pagedown"}:
222
+ selected_index = min(
223
+ len(sessions) - 1, selected_index + _page_size()
224
+ )
225
+ status_message = ""
226
+ _sync_viewport()
227
+ panel_updated = True
228
+ continue
229
+ if key == Keys.Home or key == "home":
230
+ selected_index = 0
231
+ status_message = ""
232
+ _sync_viewport()
233
+ panel_updated = True
234
+ continue
235
+ if key == Keys.End or key == "end":
236
+ selected_index = len(sessions) - 1
237
+ status_message = ""
238
+ _sync_viewport()
239
+ panel_updated = True
240
+ continue
241
+ if isinstance(key, str) and key.isdigit():
242
+ idx = int(key) - 1
243
+ if 0 <= idx < len(sessions):
244
+ selected_index = idx
245
+ status_message = ""
246
+ _sync_viewport()
247
+ panel_updated = True
248
+ continue
249
+ if key == Keys.Enter or key in {"enter", "\r", "\n"}:
250
+ if not sessions:
251
+ return None, True
252
+ return sessions[selected_index].session_id, True
253
+ if (
254
+ key == Keys.Escape
255
+ or key == Keys.ControlC
256
+ or key
257
+ in {
258
+ "escape",
259
+ "c-c",
260
+ }
261
+ ):
262
+ return None, True
263
+ if key_char and key_char.lower() == "r":
264
+ selected_id = _selected_session_id()
265
+ if selected_id is None:
266
+ continue
267
+ current_row = next(
268
+ (
269
+ item
270
+ for item in display_rows
271
+ if item.session_id == selected_id
272
+ ),
273
+ None,
274
+ )
275
+ rename_buffer = (
276
+ current_row.preview if current_row is not None else ""
277
+ )
278
+ rename_mode = True
279
+ status_message = ""
280
+ _sync_viewport()
281
+ panel_updated = True
282
+ continue
283
+ if key_char and key_char.lower() == "d":
284
+ if not sessions:
285
+ continue
286
+ deleted, status_message = _delete_resume_session(
287
+ info=sessions[selected_index]
288
+ )
289
+ if deleted:
290
+ sessions.pop(selected_index)
291
+ if not sessions:
292
+ return None, True
293
+ selected_index = min(selected_index, len(sessions) - 1)
294
+ display_rows = _chat_resume_display_rows(sessions=sessions)
295
+ _sync_viewport()
296
+ panel_updated = True
297
+ continue
298
+ if panel_updated:
299
+ live.update(_render_panel(), refresh=True)
300
+ except Exception as e: # noqa: BLE001
301
+ console.print(f"[yellow]Resume picker unavailable:[/yellow] {e}")
302
+ return None, False
303
+ finally:
304
+ close = getattr(input_reader, "close", None)
305
+ if callable(close):
306
+ close()
307
+ return None, True
308
+
309
+
310
+ def _chat_resume_panel(
311
+ *,
312
+ current_session_id: str,
313
+ sessions: list[SessionInfo],
314
+ selected_session_id: str | None = None,
315
+ interactive: bool = False,
316
+ display_rows: list[_ResumeSessionRow] | None = None,
317
+ rename_session_id: str | None = None,
318
+ rename_buffer: str = "",
319
+ status_message: str | None = None,
320
+ scroll_offset: int = 0,
321
+ visible_session_rows: int | None = None,
322
+ ) -> Panel:
323
+ from rich.align import Align
324
+ from rich.text import Text
325
+
326
+ if _terminal_too_small():
327
+ return _terminal_too_small_panel()
328
+
329
+ _ = interactive
330
+ current = current_session_id.strip() or "-"
331
+ selected = (selected_session_id or "").strip().casefold()
332
+ rename_target = (rename_session_id or "").strip().casefold()
333
+ rows = (
334
+ display_rows if display_rows is not None else _chat_resume_display_rows(sessions=sessions)
335
+ )
336
+ selected_index = _resolve_resume_selected_index(
337
+ rows=rows, selected_session_id=selected_session_id
338
+ )
339
+ visible_count = (
340
+ int(visible_session_rows)
341
+ if visible_session_rows is not None
342
+ else _resume_visible_session_rows(has_status_message=bool(status_message))
343
+ )
344
+ normalized_offset = _clamp_resume_scroll_offset(
345
+ total_rows=len(rows),
346
+ selected_index=selected_index,
347
+ scroll_offset=scroll_offset,
348
+ visible_session_rows=visible_count,
349
+ )
350
+ start = normalized_offset
351
+ end = min(len(rows), start + max(1, visible_count))
352
+ window_rows = rows[start:end]
353
+ hidden_above = start
354
+ hidden_below = max(0, len(rows) - end)
355
+ columns, _ = _terminal_dimensions()
356
+
357
+ header = _Panel(
358
+ Align.center(Text("Resume Session", style="bold cyan")),
359
+ border_style="cyan",
360
+ padding=(0, 1),
361
+ )
362
+
363
+ current_info = Text()
364
+ current_info.append("Current Session: ", style=STYLE_EMPHASIS)
365
+ current_info.append("* ", style="bold yellow")
366
+ current_info.append(current, style=STYLE_EMPHASIS)
367
+ current_panel = _Panel(current_info, border_style="bright_blue", padding=(0, 1))
368
+
369
+ idx_width = 3 if columns < 72 else 4
370
+ when_width = 15
371
+ if columns >= 90:
372
+ when_width = 20
373
+ if columns >= 130:
374
+ when_width = 24
375
+
376
+ table = _Table(show_header=False, box=None, expand=True, padding=(0, 1), collapse_padding=True)
377
+ table.add_column("idx", justify="right", no_wrap=True, width=idx_width)
378
+ table.add_column("session", style=STYLE_CONTENT, no_wrap=False, ratio=6, overflow="ellipsis")
379
+ table.add_column("when", justify="right", no_wrap=True, width=when_width)
380
+
381
+ if hidden_above > 0:
382
+ suffix = "" if hidden_above == 1 else "s"
383
+ table.add_row("", Text(f"... {hidden_above} more session{suffix} above", style="dim"), "")
384
+
385
+ last_group = rows[start - 1].group_label if start > 0 else ""
386
+ if window_rows:
387
+ first_group = window_rows[0].group_label
388
+ if start > 0 and rows[start - 1].group_label == first_group:
389
+ table.add_row("", Text(first_group, style="bold magenta"), "")
390
+ last_group = first_group
391
+
392
+ for item in window_rows:
393
+ if item.group_label != last_group:
394
+ table.add_row("", Text(item.group_label, style="bold magenta"), "")
395
+ last_group = item.group_label
396
+
397
+ is_selected = bool(selected) and item.session_id.casefold() == selected
398
+ is_renaming = bool(rename_target) and item.session_id.casefold() == rename_target
399
+ if is_renaming:
400
+ row_style = "bold black on bright_yellow"
401
+ elif is_selected:
402
+ row_style = "bold black on bright_cyan"
403
+ else:
404
+ row_style = item.recency_style
405
+ marker = ">" if is_selected else " "
406
+ idx_text = Text(f"{item.index:>2}", style=row_style)
407
+ if is_renaming:
408
+ live_name = _truncate_preview(rename_buffer, max_chars=72)
409
+ label_text = Text(f"{marker} Rename: {live_name}", style=row_style)
410
+ else:
411
+ label_text = Text(f"{marker} {item.preview}", style=row_style)
412
+ when_style = (
413
+ row_style if is_selected else ("dim" if item.recency_style == "dim" else STYLE_CONTENT)
414
+ )
415
+ when_text = Text(item.when_label, style=when_style)
416
+ table.add_row(idx_text, label_text, when_text)
417
+
418
+ if hidden_below > 0:
419
+ suffix = "" if hidden_below == 1 else "s"
420
+ table.add_row("", Text(f"... {hidden_below} more session{suffix} below", style="dim"), "")
421
+
422
+ footer_text = (
423
+ "/ Navigate PgUp/PgDn Page Home/End Jump "
424
+ "Enter Select r Rename d Delete Esc Cancel"
425
+ )
426
+ if rename_target:
427
+ footer_text = "Type title Enter Save Esc Cancel"
428
+
429
+ footer = _Panel(
430
+ Align.center(
431
+ Text(
432
+ footer_text,
433
+ style="dim",
434
+ )
435
+ ),
436
+ border_style="bright_black",
437
+ padding=(0, 1),
438
+ )
439
+
440
+ content = _table_grid(expand=True)
441
+ content.add_row(header)
442
+ content.add_row("")
443
+ content.add_row(current_panel)
444
+ content.add_row("")
445
+ content.add_row(table)
446
+ if status_message:
447
+ content.add_row("")
448
+ content.add_row(Text(status_message, style="dim"))
449
+ content.add_row("")
450
+ content.add_row(footer)
451
+
452
+ return _Panel(content, border_style="cyan")
453
+
454
+
455
+ def _resume_chat_session(
456
+ *,
457
+ session: Any,
458
+ target_session_id: str,
459
+ ) -> tuple[bool, str, list[dict[str, Any]]]:
460
+ raw_requested_id = str(target_session_id or "").strip()
461
+ if not raw_requested_id:
462
+ return False, "[red]Usage:[/red] /resume <index|session_id>", []
463
+ requested_id = _normalize_chat_resume_session_id(raw_requested_id)
464
+ if requested_id is None:
465
+ return False, f"[red]Invalid session id:[/red] {raw_requested_id}", []
466
+
467
+ cfg = getattr(session, "cfg", None)
468
+ if not isinstance(cfg, AppConfig):
469
+ return False, "[red]Resume unavailable:[/red] missing session config.", []
470
+
471
+ store = getattr(session, "store", None)
472
+ sessions_dir_raw = getattr(store, "sessions_dir", None)
473
+ sessions_dir = (
474
+ Path(sessions_dir_raw) if sessions_dir_raw is not None else resolve_sessions_dir(cfg)
475
+ )
476
+
477
+ target_path = _resolve_chat_resume_session_path(
478
+ sessions_dir=sessions_dir,
479
+ session_id=requested_id,
480
+ )
481
+ if target_path is None:
482
+ return False, f"[red]Session not found:[/red] {requested_id}", []
483
+
484
+ current_session_id = str(getattr(store, "session_id", "") or "")
485
+ if current_session_id == requested_id:
486
+ return True, f"Session already active: {requested_id}", []
487
+
488
+ synthesized_tool_results: list[str] = []
489
+ history_messages = _load_chat_resume_messages(
490
+ target_path,
491
+ synthesized_tool_results=synthesized_tool_results,
492
+ )
493
+ resume_context_message = _build_chat_resume_context_message(target_path)
494
+ start_payload = _load_chat_resume_session_start(target_path)
495
+ runtime_settings = _load_chat_resume_runtime_settings(target_path)
496
+ active_workdir_relpath = _load_chat_resume_active_workdir_relpath(target_path)
497
+
498
+ current_mode = str(getattr(session, "mode", "review") or "review").strip().lower()
499
+ start_mode = str(start_payload.get("mode") or "").strip().lower()
500
+ mode = start_mode if start_mode in _CHAT_MODES else current_mode
501
+ if mode not in _CHAT_MODES:
502
+ mode = "review"
503
+
504
+ raw_yes = start_payload.get("yes")
505
+ if isinstance(raw_yes, bool):
506
+ yes = raw_yes
507
+ else:
508
+ yes = bool(getattr(session, "yes", False))
509
+
510
+ resume_cfg = clone_cfg(cfg)
511
+
512
+ start_step_budget_policy = str(start_payload.get("step_budget_policy") or "").strip().lower()
513
+ if start_step_budget_policy in {"autonomous", "limited", "adaptive", "fixed"}:
514
+ resume_cfg.step_budget_policy = normalize_step_budget_policy(start_step_budget_policy)
515
+ else:
516
+ resume_cfg.step_budget_policy = normalize_step_budget_policy(
517
+ getattr(resume_cfg, "step_budget_policy", "autonomous")
518
+ )
519
+ stored_task_max_steps = _resume_optional_positive_int(start_payload.get("task_max_steps"))
520
+ if stored_task_max_steps is not None:
521
+ resume_cfg.task_max_steps = stored_task_max_steps
522
+ stored_subagent_max_steps = _resume_optional_positive_int(
523
+ start_payload.get("subagent_max_steps")
524
+ )
525
+ if stored_subagent_max_steps is not None:
526
+ resume_cfg.subagent_max_steps = stored_subagent_max_steps
527
+
528
+ default_max_steps = _resume_positive_int(
529
+ getattr(resume_cfg, "max_steps", DEFAULT_CHAT_MAX_STEPS),
530
+ default=DEFAULT_CHAT_MAX_STEPS,
531
+ )
532
+ max_steps = _resume_positive_int(start_payload.get("max_steps"), default=default_max_steps)
533
+ resume_cfg.max_steps = max_steps
534
+
535
+ raw_enable_chat_turn_step_budget = start_payload.get("enable_chat_turn_step_budget")
536
+ enable_chat_turn_step_budget = (
537
+ True
538
+ if step_budget_is_autonomous(resume_cfg.step_budget_policy)
539
+ else (
540
+ raw_enable_chat_turn_step_budget
541
+ if isinstance(raw_enable_chat_turn_step_budget, bool)
542
+ else True
543
+ )
544
+ )
545
+ chat_turn_fixed_override = _resume_optional_positive_int(
546
+ start_payload.get("chat_turn_fixed_override")
547
+ )
548
+
549
+ api_key_override = str(getattr(getattr(session, "client", None), "api_key", "") or "").strip()
550
+ if not api_key_override:
551
+ api_key_override = None
552
+
553
+ root = Path(getattr(session, "root", Path(".")))
554
+ if active_workdir_relpath:
555
+ try:
556
+ _resolve_chat_resume_active_workdir_path(
557
+ workspace_root=root,
558
+ active_workdir_relpath=active_workdir_relpath,
559
+ )
560
+ except Exception as e: # noqa: BLE001
561
+ return (
562
+ False,
563
+ "[red]Failed to resume session:[/red] "
564
+ f"could not restore active workdir {active_workdir_relpath!r}: {e}",
565
+ [],
566
+ )
567
+ no_log = not bool(getattr(store, "enabled", False))
568
+ usage_role = str(getattr(session, "usage_role", "main") or "main")
569
+ usage_hud_enabled = _chat_usage_hud_enabled(session)
570
+
571
+ start_model = str(start_payload.get("model") or "").strip()
572
+ model_restore_reason = "no historical model recorded"
573
+ historical_model_restored = False
574
+ try:
575
+ from ..llm.metadata import endpoint_descriptor_matches
576
+ from ..profiles import get_active_profile, resolve_effective_base_url
577
+
578
+ current_profile = get_active_profile(resume_cfg)
579
+ current_provider_base_url = resolve_effective_base_url(
580
+ cfg=resume_cfg,
581
+ profile=current_profile,
582
+ ).rstrip("/")
583
+ connection_identity_keys = (
584
+ "profile_name",
585
+ "protocol",
586
+ "auth_provider",
587
+ "reasoning_trace_adapter",
588
+ )
589
+ has_connection_identity = all(key in start_payload for key in connection_identity_keys)
590
+ if "provider_base_url_descriptor" in start_payload:
591
+ endpoint_matches = endpoint_descriptor_matches(
592
+ current_provider_base_url,
593
+ start_payload.get("provider_base_url_descriptor"),
594
+ )
595
+ has_connection_identity = has_connection_identity and isinstance(
596
+ start_payload.get("provider_base_url_descriptor"), dict
597
+ )
598
+ else:
599
+ # Legacy sessions only have a raw URL. It may contain credentials
600
+ # and is not a trustworthy resume identity, so model restoration
601
+ # and provider continuation both fail closed.
602
+ has_connection_identity = False
603
+ endpoint_matches = False
604
+ connection_matches = has_connection_identity and (
605
+ str(start_payload.get("profile_name") or "") == current_profile.name
606
+ and str(start_payload.get("protocol") or "") == current_profile.protocol
607
+ and endpoint_matches
608
+ and str(start_payload.get("auth_provider") or "")
609
+ == str(current_profile.auth_provider or "")
610
+ and str(start_payload.get("reasoning_trace_adapter") or "auto")
611
+ == current_profile.reasoning_trace_adapter
612
+ )
613
+ except Exception: # noqa: BLE001 - legacy/corrupt identity must fail closed
614
+ has_connection_identity = False
615
+ connection_matches = False
616
+
617
+ if start_model and connection_matches:
618
+ resume_cfg.model = start_model
619
+ historical_model_restored = True
620
+ model_restore_reason = "stored provider connection matches the active connection"
621
+ elif start_model and has_connection_identity:
622
+ model_restore_reason = (
623
+ "stored provider connection differs from the active connection; using the current model"
624
+ )
625
+ elif start_model:
626
+ model_restore_reason = (
627
+ "legacy session has no verified provider identity; using the current model"
628
+ )
629
+
630
+ raw_temp = start_payload.get("temperature")
631
+ if raw_temp is not None:
632
+ try:
633
+ temp_value = float(raw_temp)
634
+ except (TypeError, ValueError):
635
+ temp_value = None
636
+ if temp_value is not None and temp_value >= 0:
637
+ _apply_temperature_override(resume_cfg, temp_value)
638
+
639
+ for key in (
640
+ "coding_temperature",
641
+ "review_temperature",
642
+ "planner_temperature",
643
+ "conflict_review_temperature",
644
+ "compactor_temperature",
645
+ "chat_temperature",
646
+ ):
647
+ raw_value = start_payload.get(key)
648
+ if raw_value is None:
649
+ continue
650
+ try:
651
+ parsed_value = float(raw_value)
652
+ except (TypeError, ValueError):
653
+ continue
654
+ if parsed_value < 0:
655
+ continue
656
+ setattr(resume_cfg, key, parsed_value)
657
+
658
+ raw_stream = runtime_settings.get("stream")
659
+ if isinstance(raw_stream, bool):
660
+ resume_cfg.stream = raw_stream
661
+
662
+ start_routing_mode = str(start_payload.get("routing_mode") or "").strip().lower()
663
+ if start_routing_mode in {"auto", "code_only"}:
664
+ resume_cfg.routing_mode = start_routing_mode
665
+
666
+ start_compaction_enabled = start_payload.get("compaction_enabled")
667
+ resume_enable_compaction = (
668
+ bool(start_compaction_enabled)
669
+ if isinstance(start_compaction_enabled, bool)
670
+ else (
671
+ getattr(session, "conversation_compactor", None) is not None
672
+ or getattr(session, "tool_output_offloader", None) is not None
673
+ )
674
+ )
675
+ start_tool_output_offload = start_payload.get("tool_output_offload_enabled")
676
+ resume_enable_tool_output_offload = (
677
+ bool(start_tool_output_offload) if isinstance(start_tool_output_offload, bool) else None
678
+ )
679
+ start_conversation_summarization = start_payload.get("conversation_summarization_enabled")
680
+ resume_enable_conversation_summarization = (
681
+ bool(start_conversation_summarization)
682
+ if isinstance(start_conversation_summarization, bool)
683
+ else None
684
+ )
685
+
686
+ try:
687
+ new_session = create_session(
688
+ cfg=resume_cfg,
689
+ root=root,
690
+ mode=mode,
691
+ runtime_kind=RuntimeKind.INTERACTIVE_CHAT,
692
+ yes=yes,
693
+ max_steps=max_steps,
694
+ no_log=no_log,
695
+ api_key_override=api_key_override,
696
+ console=getattr(session, "console", None),
697
+ surface=getattr(session, "surface", None),
698
+ non_interactive=_is_non_interactive_terminal(),
699
+ enable_chat_turn_step_budget=enable_chat_turn_step_budget,
700
+ chat_turn_fixed_override=chat_turn_fixed_override,
701
+ session_log_dir_override=sessions_dir,
702
+ session_id_override=requested_id,
703
+ usage_role=usage_role,
704
+ enable_compaction=resume_enable_compaction,
705
+ enable_tool_output_offload=resume_enable_tool_output_offload,
706
+ enable_conversation_summarization=resume_enable_conversation_summarization,
707
+ active_workdir_relpath_override=active_workdir_relpath,
708
+ session_source="resume",
709
+ session_source_metadata={
710
+ "from_session_id": current_session_id,
711
+ "resumed_session_id": requested_id,
712
+ "loaded_message_count": len(history_messages),
713
+ "active_workdir_relpath": active_workdir_relpath,
714
+ },
715
+ )
716
+ except Exception as e: # noqa: BLE001
717
+ return False, f"[red]Failed to resume session:[/red] {e}", []
718
+
719
+ # Provider-owned continuation state is only valid on the exact route that
720
+ # produced it. Require both the session-level connection/model identity
721
+ # above and the per-message route stamp to match the newly created client.
722
+ # Legacy sessions, clients without a route identity, and any mismatch fail
723
+ # closed while retaining public message content and tool calls for ordinary
724
+ # full-history replay.
725
+ from ..llm.metadata import (
726
+ ProviderRouteIdentity,
727
+ gate_messages_for_provider_route,
728
+ strip_provider_metadata_from_messages,
729
+ )
730
+
731
+ active_route_identity = getattr(getattr(new_session, "client", None), "route_identity", None)
732
+ if historical_model_restored and isinstance(active_route_identity, ProviderRouteIdentity):
733
+ history_messages = gate_messages_for_provider_route(
734
+ history_messages,
735
+ active_route_identity,
736
+ )
737
+ else:
738
+ history_messages = strip_provider_metadata_from_messages(history_messages)
739
+
740
+ restored_trace_level = str(runtime_settings.get("trace_level") or "compact")
741
+ trace_setter = getattr(getattr(new_session, "surface", None), "set_trace_level", None)
742
+ if callable(trace_setter):
743
+ try:
744
+ trace_setter(restored_trace_level)
745
+ except Exception:
746
+ pass
747
+
748
+ combined_resume_context = resume_context_message or ""
749
+ resume_context_loaded = _insert_chat_resume_context_message(
750
+ new_session,
751
+ combined_resume_context,
752
+ )
753
+
754
+ if history_messages:
755
+ new_session.messages.extend(history_messages)
756
+
757
+ # Restored compaction state (summary/pins) only becomes model-visible via
758
+ # _upsert_context_messages, which otherwise runs on the next compaction.
759
+ # Re-inject here, after the pinned resume context bumped pinned_prefix_len,
760
+ # so the memory message lands after the pinned prefix.
761
+ compaction_memory_reinjected = False
762
+ resume_compactor = getattr(new_session, "conversation_compactor", None)
763
+ if resume_compactor is not None:
764
+ new_session.messages[:] = resume_compactor.reinject_context_messages(new_session.messages)
765
+ compaction_memory_reinjected = (
766
+ getattr(getattr(resume_compactor, "state", None), "memory_message_index", None)
767
+ is not None
768
+ )
769
+
770
+ new_session.store.append(
771
+ "system_note",
772
+ {
773
+ "message": "chat_resume",
774
+ "from_session_id": current_session_id,
775
+ "resumed_session_id": requested_id,
776
+ "loaded_messages": len(history_messages),
777
+ "synthesized_tool_results": len(synthesized_tool_results),
778
+ "synthesized_tool_result_ids": list(synthesized_tool_results),
779
+ "resume_context_loaded": resume_context_loaded,
780
+ "resume_context_chars": len(combined_resume_context),
781
+ "compaction_memory_reinjected": compaction_memory_reinjected,
782
+ "historical_model_restored": historical_model_restored,
783
+ "model_restore_reason": model_restore_reason,
784
+ },
785
+ )
786
+
787
+ close_fn = getattr(session, "close", None)
788
+ close_warning = ""
789
+ if callable(close_fn):
790
+ try:
791
+ close_fn()
792
+ except Exception as e: # noqa: BLE001
793
+ close_warning = f"previous session close failed during resume: {e}"
794
+ new_session.store.append(
795
+ "system_note",
796
+ {
797
+ "message": "chat_resume_warning",
798
+ "warning": close_warning,
799
+ },
800
+ )
801
+
802
+ session.__dict__.clear()
803
+ session.__dict__.update(new_session.__dict__)
804
+ _set_chat_usage_hud_enabled(session, usage_hud_enabled)
805
+ _refresh_chat_hud_context_cache(session)
806
+ _ensure_session_summary_metadata(session=session, allow_model_summary=False)
807
+
808
+ turn_count = sum(1 for msg in history_messages if msg.get("role") == "user")
809
+ suffix = "" if turn_count == 1 else "s"
810
+ message = f"Resumed session: {requested_id} ({turn_count} turn{suffix} loaded)."
811
+ if start_model and not historical_model_restored:
812
+ message += " The current provider and model were kept for safety."
813
+ if close_warning:
814
+ message += f" [yellow]Warning:[/yellow] {close_warning}."
815
+ return (True, message, history_messages)
816
+
817
+
818
+ def _select_chat_resume_interactive_impl(cli_mod: Any, *args: Any, **kwargs: Any) -> Any:
819
+ _sync_cli_globals(cli_mod)
820
+ return _select_chat_resume_interactive(*args, **kwargs)
821
+
822
+
823
+ def _chat_resume_panel_impl(cli_mod: Any, *args: Any, **kwargs: Any) -> Any:
824
+ _sync_cli_globals(cli_mod)
825
+ return _chat_resume_panel(*args, **kwargs)
826
+
827
+
828
+ def _resume_chat_session_impl(cli_mod: Any, *args: Any, **kwargs: Any) -> Any:
829
+ _sync_cli_globals(cli_mod)
830
+ return _resume_chat_session(*args, **kwargs)