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,125 @@
1
+ """Classify submissions made while an agent turn is running.
2
+
3
+ The policy is an allowlist. A command is safe mid-turn only when it does not
4
+ mutate state read by the running turn, start or replace a turn or session, or
5
+ tear down the application. New and unknown commands therefore block until they
6
+ are reviewed explicitly.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from enum import Enum
12
+
13
+ EXIT_WORDS = frozenset({"/exit", "/quit", ":q", "exit", "quit"})
14
+
15
+
16
+ class MidTurnAction(Enum):
17
+ """How the TUI should route a submission made during a turn."""
18
+
19
+ MESSAGE = "message"
20
+ ALLOW = "allow"
21
+ DEFER = "defer"
22
+ BLOCK = "block"
23
+
24
+
25
+ _ALLOWED_ALWAYS = frozenset(
26
+ {
27
+ "/help",
28
+ "/",
29
+ "/status",
30
+ "/subagents",
31
+ "/pwd",
32
+ "/context",
33
+ "/ctx",
34
+ "/usage",
35
+ "/model-info",
36
+ "/trace",
37
+ "/toolbar",
38
+ "/images",
39
+ "/image",
40
+ "/paste-image",
41
+ "/clear-images",
42
+ "/terminals",
43
+ }
44
+ )
45
+
46
+ _ALLOWED_BARE_ONLY = frozenset({"/skill"})
47
+
48
+ _DEFERRED = frozenset({"/config", "/mode", "/model", "/persona"})
49
+
50
+ _BLOCK_REASONS = {
51
+ "/clear": "clearing would discard the conversation this turn is writing to",
52
+ "/resume": "resuming replaces the session this turn is using",
53
+ "/compact": "compaction rewrites the history this turn is appending to",
54
+ "/plan": "plan mode changes the permissions this turn is using",
55
+ "/ask": "that starts a new turn",
56
+ "/forge": "Forge takes over the session",
57
+ ":forge": "Forge takes over the session",
58
+ "/login": "signing in replaces the active session",
59
+ "/logout": "signing out replaces the active session",
60
+ "/stream": "streaming is read at each step and would disrupt live output",
61
+ "/report": "report generation requires a settled session",
62
+ "/feedback": "feedback generation requires a settled session",
63
+ "/assets": "the assets view requires a settled Forge session",
64
+ }
65
+
66
+ _GENERIC_BLOCK_REASON = "that command cannot run while a turn is in flight"
67
+ _ESCAPE_HATCH = "Esc to interrupt, or wait for the turn to finish."
68
+
69
+
70
+ def _command_token(text: str) -> str:
71
+ stripped = str(text or "").strip()
72
+ if not stripped:
73
+ return ""
74
+ return stripped.split(maxsplit=1)[0].lower()
75
+
76
+
77
+ def is_command(text: str) -> bool:
78
+ """Return whether text should be routed as a command rather than prose."""
79
+ stripped = str(text or "").strip()
80
+ if not stripped:
81
+ return False
82
+ if stripped.lower() in EXIT_WORDS:
83
+ return True
84
+ return stripped.startswith(("/", ":"))
85
+
86
+
87
+ def classify_mid_turn(text: str) -> MidTurnAction:
88
+ """Classify text submitted while an agent turn is running."""
89
+ stripped = str(text or "").strip()
90
+ if not is_command(stripped):
91
+ return MidTurnAction.MESSAGE
92
+ if stripped.lower() in EXIT_WORDS:
93
+ return MidTurnAction.BLOCK
94
+
95
+ token = _command_token(stripped)
96
+ has_argument = len(stripped.split(maxsplit=1)) > 1
97
+ if token in _ALLOWED_ALWAYS:
98
+ return MidTurnAction.ALLOW
99
+ if token in _ALLOWED_BARE_ONLY and not has_argument:
100
+ return MidTurnAction.ALLOW
101
+ if token in _DEFERRED:
102
+ return MidTurnAction.DEFER
103
+ return MidTurnAction.BLOCK
104
+
105
+
106
+ def defer_message(text: str) -> str:
107
+ """Explain that a state-changing command will run after this turn."""
108
+ token = _command_token(text) or "That command"
109
+ return f"{token} is staged and will apply when this turn finishes."
110
+
111
+
112
+ def block_message(text: str) -> str:
113
+ """Explain why a submission is blocked and name the escape hatch."""
114
+ stripped = str(text or "").strip()
115
+ token = _command_token(stripped)
116
+
117
+ if stripped.lower() in EXIT_WORDS:
118
+ return f"Exit is unavailable while a turn is running. {_ESCAPE_HATCH}"
119
+ if token in _ALLOWED_BARE_ONLY:
120
+ reason = f"{token} with arguments changes what this turn is using"
121
+ else:
122
+ reason = _BLOCK_REASONS.get(token, _GENERIC_BLOCK_REASON)
123
+
124
+ label = token or "That"
125
+ return f"{label} is unavailable right now: {reason}. {_ESCAPE_HATCH}"
@@ -0,0 +1,444 @@
1
+ # ruff: noqa: F821
2
+ from __future__ import annotations
3
+
4
+ import re
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from ...surface.styles import STYLE_CHROME, STYLE_CONTENT, STYLE_EMPHASIS
9
+
10
+ _PROTECTED_RENDERING_GLOBAL_NAMES: set[str] = set()
11
+
12
+
13
+ def _sync_rendering_globals(source_globals: dict[str, Any]) -> None:
14
+ module_globals = globals()
15
+ if not _PROTECTED_RENDERING_GLOBAL_NAMES:
16
+ for local_name, local_value in module_globals.items():
17
+ if callable(local_value):
18
+ _PROTECTED_RENDERING_GLOBAL_NAMES.add(local_name)
19
+ for name, value in source_globals.items():
20
+ if name.startswith("__") or name in _PROTECTED_RENDERING_GLOBAL_NAMES:
21
+ continue
22
+ module_globals[name] = value
23
+
24
+
25
+ def _forge_plan_command_guidance_lines() -> tuple[str, ...]:
26
+ return (
27
+ "Chat Plan Mode is unavailable.",
28
+ "In Forge, use:",
29
+ "/back",
30
+ "Forge plan commands:",
31
+ "/show",
32
+ "/plan markdown",
33
+ "/plan edit",
34
+ )
35
+
36
+
37
+ def _print_forge_plan_command_guidance(*, console: Any) -> None:
38
+ for line in _forge_plan_command_guidance_lines():
39
+ console.print(line)
40
+
41
+
42
+ def _chat_skill_usage_lines() -> tuple[str, ...]:
43
+ return (
44
+ "[yellow]Usage:[/yellow] /skill",
45
+ " /skill <name>",
46
+ " /skill <name> <task>",
47
+ )
48
+
49
+
50
+ def _artifact_display_ref(*, root: Path, store_obj: Any, artifact_path: Path | None) -> str:
51
+ if artifact_path is None:
52
+ return "-"
53
+ layout = getattr(store_obj, "session_artifact_layout", None)
54
+ display_fn = getattr(layout, "display_reference_for_path", None)
55
+ if callable(display_fn):
56
+ try:
57
+ return str(display_fn(artifact_path=artifact_path, workspace_root=root))
58
+ except Exception: # noqa: BLE001
59
+ pass
60
+ try:
61
+ return artifact_path.resolve().relative_to(root.resolve()).as_posix()
62
+ except ValueError:
63
+ return artifact_path.name
64
+
65
+
66
+ def _render_planner_reply(
67
+ *, console: Any, message: str, questions: list[str] | None = None
68
+ ) -> None:
69
+ console.print("[bold]Planner:[/bold]")
70
+ console.print(message)
71
+ if questions:
72
+ console.print("[dim]Planner questions[/dim]")
73
+ for question in questions:
74
+ console.print(f"- {question}")
75
+
76
+
77
+ def _render_labeled_chat_message(*, console: Any, label: str, message: str) -> None:
78
+ clean = str(message or "").strip() or "(empty message)"
79
+ prefix = ""
80
+ if label and str(label).strip().lower() not in {"you", "user"}:
81
+ prefix = f"{str(label).strip()} · "
82
+ console.print(
83
+ _forge_bar_text(
84
+ text=f"{prefix}{clean}",
85
+ style=STYLE_EMPHASIS,
86
+ bar_style=STYLE_EMPHASIS,
87
+ ),
88
+ highlight=False,
89
+ )
90
+
91
+
92
+ def _render_plan_draft(*, console: Any, draft: str) -> None:
93
+ from rich.console import Group
94
+
95
+ clean = str(draft or "").strip() or "(empty plan)"
96
+ lines = [line.rstrip() for line in clean.splitlines() if line.strip()]
97
+ numbered_line_count = sum(1 for line in lines if re.match(r"^\s*\d+[\.\)]\s+", line))
98
+ header = "Plan (draft)"
99
+ if numbered_line_count > 0:
100
+ suffix = "step" if numbered_line_count == 1 else "steps"
101
+ header = f"{header} {numbered_line_count} {suffix}"
102
+
103
+ renderables: list[Any] = [
104
+ _forge_bar_text(text=header, style=STYLE_EMPHASIS, bar_style=STYLE_CHROME)
105
+ ]
106
+ if not lines:
107
+ lines = ["(empty plan)"]
108
+ for line in lines:
109
+ renderables.append(
110
+ _forge_bar_text(
111
+ text=f" {line}",
112
+ style=STYLE_CONTENT,
113
+ bar_style=STYLE_CHROME,
114
+ )
115
+ )
116
+ console.print(Group(*renderables), highlight=False)
117
+
118
+
119
+ def _render_chat_plan_mode_status(*, console: Console, plan_mode_state: Any) -> None:
120
+ if _chat_plan_mode_enabled(plan_mode_state):
121
+ restore_mode = _chat_plan_mode_restore_mode(plan_mode_state) or "readonly"
122
+ console.print(
123
+ "Plan Mode: on "
124
+ f"(persistent readonly planning overlay; /plan <task> stays the default draft/review/approve path; restores {_chat_mode_display(restore_mode)} on /plan off)"
125
+ )
126
+ latest_task = _chat_plan_mode_latest_task(plan_mode_state)
127
+ latest_draft = _chat_plan_mode_latest_draft(plan_mode_state)
128
+ if latest_draft is None or latest_task is None:
129
+ console.print(
130
+ "Stored draft: none yet. Draft here with a normal chat message, or use /plan off then /plan <task> for the default execution path."
131
+ )
132
+ return
133
+ console.print(f"Stored task: {_chat_plan_task_preview(latest_task)}")
134
+ if restore_mode == "readonly":
135
+ console.print(
136
+ "Stored draft: captured, but exact /plan approve cannot execute because this overlay started from Read-Only mode."
137
+ )
138
+ return
139
+ console.print(
140
+ f"Stored draft: ready for exact /plan approve (leaves readonly planning, restores {_chat_mode_display(restore_mode)}, and executes)."
141
+ )
142
+ return
143
+ console.print("Plan Mode: off")
144
+
145
+
146
+ def _print_chat_context(*, console: Console, session: Any) -> None:
147
+ context_fn = getattr(session, "context_left", None)
148
+ if not callable(context_fn):
149
+ console.print("Context tracking unavailable for this session.")
150
+ return
151
+ _refresh_chat_hud_context_cache(session)
152
+ ctx = getattr(session, "_hud_context_cache", None)
153
+ if ctx is None:
154
+ console.print("Context tracking unavailable for this session.")
155
+ return
156
+ context_window_tokens = getattr(
157
+ ctx,
158
+ "context_window_tokens",
159
+ getattr(ctx, "max_input_tokens", "n/a"),
160
+ )
161
+ context_window_remaining = getattr(
162
+ ctx,
163
+ "context_window_remaining_tokens",
164
+ getattr(ctx, "remaining_tokens", "n/a"),
165
+ )
166
+ context_window_percent = getattr(
167
+ ctx,
168
+ "context_window_percent_left",
169
+ getattr(ctx, "percent_left", None),
170
+ )
171
+ table = _Table(title="Context Window Left")
172
+ table.add_column("field")
173
+ table.add_column("value")
174
+ table.add_row("model", str(getattr(ctx, "model_name", "-")))
175
+ table.add_row("model_metadata_source", str(getattr(ctx, "source", "-")))
176
+ table.add_row(
177
+ "capacity_provider_key",
178
+ str(getattr(ctx, "capacity_provider_key", None) or "-"),
179
+ )
180
+ table.add_row(
181
+ "context_window_source",
182
+ str(getattr(ctx, "context_window_source", None) or "-"),
183
+ )
184
+ table.add_row(
185
+ "max_output_source",
186
+ str(getattr(ctx, "max_output_source", None) or "-"),
187
+ )
188
+ table.add_row("token_count_source", str(getattr(ctx, "token_count_source", "-")))
189
+ table.add_row(
190
+ "token_count_confidence",
191
+ str(getattr(ctx, "token_count_confidence", "-")),
192
+ )
193
+ anchor_source = getattr(ctx, "anchor_token_count_source", None)
194
+ if anchor_source:
195
+ table.add_row("request_anchor_source", str(anchor_source))
196
+ table.add_row(
197
+ "request_anchor_confidence",
198
+ str(getattr(ctx, "anchor_token_count_confidence", "-")),
199
+ )
200
+ table.add_row("context_window_tokens", str(context_window_tokens))
201
+ table.add_row(
202
+ "projected_total_request_tokens",
203
+ str(getattr(ctx, "used_input_tokens", "n/a")),
204
+ )
205
+ table.add_row(
206
+ "local_request_estimate_tokens",
207
+ str(getattr(ctx, "local_request_estimate_tokens", "n/a")),
208
+ )
209
+ table.add_row("context_window_left_tokens", str(context_window_remaining))
210
+ table.add_row(
211
+ "context_window_left_percent",
212
+ f"{float(context_window_percent):.1f}%" if context_window_percent is not None else "n/a",
213
+ )
214
+ console.print(table)
215
+
216
+ messages_obj = getattr(session, "messages", [])
217
+ messages = messages_obj if isinstance(messages_obj, list) else []
218
+ tool_list_obj = getattr(session, "tool_list", None)
219
+ tool_list = tool_list_obj if isinstance(tool_list_obj, list) else None
220
+ compactor = getattr(session, "conversation_compactor", None)
221
+ registry = getattr(session, "model_registry", None)
222
+ model_name = str(getattr(getattr(session, "client", None), "model", "") or "").strip()
223
+ if not model_name:
224
+ model_name = str(getattr(ctx, "model_name", "") or "").strip()
225
+
226
+ cfg = getattr(session, "cfg", None)
227
+ if isinstance(cfg, AppConfig):
228
+ compaction_settings = _resolve_compaction_settings(cfg)
229
+ else:
230
+ compaction_settings = _resolve_compaction_settings(AppConfig())
231
+
232
+ model_meta = None
233
+ if registry is not None and model_name:
234
+ try:
235
+ model_meta = registry.get(model_name)
236
+ except Exception: # noqa: BLE001
237
+ model_meta = None
238
+
239
+ model_context_window_tokens = getattr(model_meta, "context_window_tokens", None)
240
+ max_output_tokens = getattr(model_meta, "max_output_tokens", None)
241
+ safety_margin_tokens = compaction_settings.safety_margin_tokens
242
+ effective_input_budget = (
243
+ getattr(ctx, "effective_input_budget", None)
244
+ or compute_input_budget(model_meta, safety_margin=safety_margin_tokens)
245
+ if model_meta is not None
246
+ else None
247
+ )
248
+
249
+ pinned_prefix_len = int(getattr(session, "pinned_prefix_len", 0) or 0)
250
+ if pinned_prefix_len <= 0 and compactor is not None and hasattr(compactor, "state"):
251
+ pinned_prefix_len = int(getattr(compactor.state, "pinned_prefix_len", 0) or 0)
252
+ if pinned_prefix_len <= 0:
253
+ for idx, msg in enumerate(messages):
254
+ if str(msg.get("role") or "") != "user":
255
+ continue
256
+ content = msg.get("content")
257
+ if isinstance(content, str) and content.startswith("<environment_context>"):
258
+ pinned_prefix_len = idx + 1
259
+ break
260
+
261
+ request_breakdown = estimate_request_token_breakdown(
262
+ messages=messages,
263
+ tool_list=tool_list,
264
+ pinned_prefix_len=pinned_prefix_len,
265
+ )
266
+ try:
267
+ total_request_tokens = max(0, int(getattr(ctx, "used_input_tokens", None)))
268
+ except (TypeError, ValueError):
269
+ total_request_tokens = request_breakdown.total_tokens
270
+ tokens_tools = max(0, int(getattr(request_breakdown, "tool_schema_tokens", 0) or 0))
271
+ if tokens_tools > total_request_tokens:
272
+ tokens_tools = total_request_tokens
273
+ tokens_messages = max(0, total_request_tokens - tokens_tools)
274
+ remaining_to_budget = getattr(ctx, "effective_remaining_tokens", None)
275
+ if remaining_to_budget is None and effective_input_budget is not None:
276
+ remaining_to_budget = max(0, effective_input_budget - total_request_tokens)
277
+ percent_budget_left = getattr(ctx, "effective_percent_left", None)
278
+ if (
279
+ percent_budget_left is None
280
+ and effective_input_budget is not None
281
+ and effective_input_budget > 0
282
+ and remaining_to_budget is not None
283
+ ):
284
+ percent_budget_left = (remaining_to_budget / effective_input_budget) * 100.0
285
+
286
+ budget_table = _Table(title="Effective Input Budget")
287
+ budget_table.add_column("field")
288
+ budget_table.add_column("value")
289
+ budget_table.add_row("context_window_tokens", str(model_context_window_tokens or "n/a"))
290
+ budget_table.add_row("max_output_tokens", str(max_output_tokens or "n/a"))
291
+ budget_table.add_row("safety_margin_tokens", str(safety_margin_tokens))
292
+ budget_table.add_row(
293
+ "effective_input_budget",
294
+ str(effective_input_budget) if effective_input_budget is not None else "n/a",
295
+ )
296
+ budget_table.add_row("estimated_messages_tokens", str(tokens_messages))
297
+ budget_table.add_row("estimated_tools_tokens", str(tokens_tools))
298
+ budget_table.add_row("estimated_total_request_tokens", str(total_request_tokens))
299
+ budget_table.add_row(
300
+ "remaining_to_budget",
301
+ str(remaining_to_budget) if remaining_to_budget is not None else "n/a",
302
+ )
303
+ budget_table.add_row(
304
+ "percent_budget_left",
305
+ f"{percent_budget_left:.1f}%" if percent_budget_left is not None else "n/a",
306
+ )
307
+ console.print(budget_table)
308
+
309
+ dynamic_percent_left = getattr(ctx, "dynamic_context_percent_left", None)
310
+ dynamic_table = _Table(title="Conversation Context Left")
311
+ dynamic_table.add_column("field")
312
+ dynamic_table.add_column("value")
313
+ dynamic_table.add_row(
314
+ "startup_baseline_tokens",
315
+ str(getattr(ctx, "startup_baseline_tokens", "n/a")),
316
+ )
317
+ dynamic_table.add_row(
318
+ "dynamic_context_budget_tokens",
319
+ str(getattr(ctx, "dynamic_context_budget_tokens", "n/a")),
320
+ )
321
+ dynamic_table.add_row(
322
+ "dynamic_context_used_tokens",
323
+ str(getattr(ctx, "dynamic_context_used_tokens", "n/a")),
324
+ )
325
+ dynamic_table.add_row(
326
+ "dynamic_context_left_tokens",
327
+ str(getattr(ctx, "dynamic_context_remaining_tokens", "n/a")),
328
+ )
329
+ dynamic_table.add_row(
330
+ "dynamic_context_left_percent",
331
+ f"{dynamic_percent_left:.1f}%" if dynamic_percent_left is not None else "n/a",
332
+ )
333
+ console.print(dynamic_table)
334
+
335
+ if effective_input_budget is not None and total_request_tokens > effective_input_budget:
336
+ console.print(
337
+ "[yellow]Request estimate exceeds effective input budget.[/yellow] "
338
+ "Run /compact to reduce context."
339
+ )
340
+
341
+ sanitized_messages = _sanitize_messages_for_estimation(messages)
342
+ pinned_messages: list[dict[str, Any]] = []
343
+ memory_messages: list[dict[str, Any]] = []
344
+ pins_messages: list[dict[str, Any]] = []
345
+ conversation_messages: list[dict[str, Any]] = []
346
+ for idx, msg in enumerate(sanitized_messages):
347
+ if idx < pinned_prefix_len:
348
+ pinned_messages.append(msg)
349
+ continue
350
+ content = msg.get("content")
351
+ if isinstance(content, str) and content.startswith(_memory_marker()):
352
+ memory_messages.append(msg)
353
+ continue
354
+ if isinstance(content, str) and content.startswith(_pins_marker()):
355
+ pins_messages.append(msg)
356
+ continue
357
+ conversation_messages.append(msg)
358
+
359
+ def _bucket_tokens(bucket: list[dict[str, Any]]) -> int:
360
+ if not bucket:
361
+ return 0
362
+ return estimate_tokens(json.dumps(bucket, ensure_ascii=False, sort_keys=True))
363
+
364
+ pinned_tokens = _bucket_tokens(pinned_messages)
365
+ memory_tokens = _bucket_tokens(memory_messages)
366
+ pins_tokens = _bucket_tokens(pins_messages)
367
+ conversation_tokens = _bucket_tokens(conversation_messages)
368
+
369
+ breakdown_table = _Table(title="Context Breakdown")
370
+ breakdown_table.add_column("field")
371
+ breakdown_table.add_column("value")
372
+ breakdown_table.add_row("pinned_prefix_tokens", str(pinned_tokens))
373
+ breakdown_table.add_row("memory_tokens", str(memory_tokens))
374
+ breakdown_table.add_row("pins_tokens", str(pins_tokens))
375
+ breakdown_table.add_row("conversation_tokens", str(conversation_tokens))
376
+ breakdown_table.add_row("total_messages_count", str(len(messages)))
377
+ console.print(breakdown_table)
378
+
379
+ root = Path(getattr(session, "root", Path(".")))
380
+ store_obj = getattr(session, "store", None)
381
+ session_id = str(getattr(store_obj, "session_id", "") or "")
382
+ history_session_dir = None
383
+ if compactor is not None and hasattr(compactor, "history_dir"):
384
+ history_dir_obj = getattr(compactor, "history_dir", None)
385
+ if isinstance(history_dir_obj, Path):
386
+ history_session_dir = history_dir_obj.parent
387
+ if history_session_dir is None and session_id:
388
+ history_session_dir = root / ".alysis" / "sessions" / _safe_component(session_id)
389
+ tool_output_session_dir = getattr(store_obj, "session_artifact_root", None)
390
+ history_chunks_count = 0
391
+ offloaded_tool_outputs_count = 0
392
+ summary_exists = False
393
+ pins_file_exists = False
394
+ if history_session_dir is not None:
395
+ history_chunks_count = len(list((history_session_dir / "history").glob("chunk_*.jsonl")))
396
+ summary_exists = (history_session_dir / "memory" / "summary.json").exists()
397
+ pins_file_exists = (history_session_dir / "memory" / "pins.json").exists()
398
+ if (
399
+ history_session_dir is not None
400
+ and session_id
401
+ and history_chunks_count == 0
402
+ and not summary_exists
403
+ and not pins_file_exists
404
+ ):
405
+ legacy_history_session_dir = root / ".alysis" / "sessions" / _safe_component(session_id)
406
+ if legacy_history_session_dir != history_session_dir:
407
+ history_chunks_count = len(
408
+ list((legacy_history_session_dir / "history").glob("chunk_*.jsonl"))
409
+ )
410
+ summary_exists = (legacy_history_session_dir / "memory" / "summary.json").exists()
411
+ pins_file_exists = (legacy_history_session_dir / "memory" / "pins.json").exists()
412
+ if isinstance(tool_output_session_dir, Path):
413
+ offloaded_tool_outputs_count = len(
414
+ list((tool_output_session_dir / "tool_outputs").glob("*.json"))
415
+ )
416
+
417
+ pins_count = 0
418
+ if compactor is not None and hasattr(compactor, "state"):
419
+ history_chunks_count = max(
420
+ history_chunks_count,
421
+ int(getattr(compactor.state, "history_chunk_index", 0) or 0),
422
+ )
423
+ pins = getattr(compactor.state, "pins", [])
424
+ if isinstance(pins, list):
425
+ pins_count = len(pins)
426
+
427
+ compaction_table = _Table(title="Compaction Artifacts")
428
+ compaction_table.add_column("field")
429
+ compaction_table.add_column("value")
430
+ compaction_table.add_row("compaction_enabled", "yes" if compactor is not None else "no")
431
+ compaction_table.add_row(
432
+ "compaction_profile",
433
+ str(getattr(compactor, "profile_name", "-")) if compactor is not None else "-",
434
+ )
435
+ compaction_table.add_row(
436
+ "compactor_model",
437
+ str(getattr(getattr(compactor, "compactor_client", None), "model", "-")),
438
+ )
439
+ compaction_table.add_row("history_chunks_count", str(history_chunks_count))
440
+ compaction_table.add_row("pins_count", str(pins_count))
441
+ compaction_table.add_row("offloaded_tool_outputs_count", str(offloaded_tool_outputs_count))
442
+ compaction_table.add_row("summary_file_exists", "yes" if summary_exists else "no")
443
+ compaction_table.add_row("pins_file_exists", "yes" if pins_file_exists else "no")
444
+ console.print(compaction_table)
@@ -0,0 +1,124 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any
5
+
6
+ from ...config import AppConfig
7
+ from ...forge import RunPaths
8
+ from ...plan_repair import ClarificationLoopTracker
9
+
10
+
11
+ @dataclass
12
+ class _ForgePlannerSessionState:
13
+ transcript: list[dict[str, str]] = field(default_factory=list)
14
+ cfg: AppConfig | None = None
15
+ workspace_context: dict[str, Any] | None = None
16
+ awaiting_clarification: bool = False
17
+ pending_questions: list[str] = field(default_factory=list)
18
+ # Consecutive clarification-only planner turns for the current goal. Past the
19
+ # policy cap the planner is made to draft a plan instead of asking again.
20
+ clarification_loop: ClarificationLoopTracker = field(default_factory=ClarificationLoopTracker)
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class _ForgeEnterCommand:
25
+ entry_mode: str
26
+ initial_goal: str = ""
27
+ usage_error: str | None = None
28
+ # Explicit plan-assistant choice carried by the ``/forge --planner`` /
29
+ # ``/forge --no-planner`` forms (emitted by the TUI intro popup's native
30
+ # "Use the planner?" prompt, and usable directly). ``None`` means "no
31
+ # explicit choice" — the caller falls back to its own default.
32
+ planner_assistant_default: bool | None = None
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class _ForgeChatEntrySelection:
37
+ paths: RunPaths
38
+ plan: dict[str, Any]
39
+ entry_kind: str
40
+
41
+
42
+ @dataclass
43
+ class _ForgeChatState:
44
+ ui_mode: str = "chat"
45
+ paths: RunPaths | None = None
46
+ plan: dict[str, Any] | None = None
47
+ assistant_enabled: bool = False
48
+ entry_request_mode: str = "plain"
49
+ entry_goal: str = ""
50
+ planner_session: _ForgePlannerSessionState = field(default_factory=_ForgePlannerSessionState)
51
+ # Swarm knobs chosen in the TUI launch gate (``/execute plan``), read by the
52
+ # ``/execute`` command handler when present. Empty dict → the handler's own
53
+ # defaults apply (so the classic, non-TUI path is unchanged). Recognised keys:
54
+ # ``parallel`` (int), ``scope_mode`` ("strict"|"warn"), ``verify_mode``
55
+ # ("warn"|"strict"|"off"), ``review`` (bool).
56
+ swarm_knobs: dict[str, Any] = field(default_factory=dict)
57
+ # True once the current ``/execute plan`` invocation actually reached
58
+ # ``run_swarm`` (reset at the top of each invocation). Lets the TUI worker
59
+ # distinguish "the handler rejected the execute before anything ran" from
60
+ # "a swarm ran" when finalizing the live forge view.
61
+ swarm_run_attempted: bool = False
62
+
63
+ @property
64
+ def planner_transcript(self) -> list[dict[str, str]]:
65
+ return self.planner_session.transcript
66
+
67
+ @planner_transcript.setter
68
+ def planner_transcript(self, value: list[dict[str, str]]) -> None:
69
+ self.planner_session.transcript = list(value)
70
+
71
+ @property
72
+ def planner_cfg(self) -> AppConfig | None:
73
+ return self.planner_session.cfg
74
+
75
+ @planner_cfg.setter
76
+ def planner_cfg(self, value: AppConfig | None) -> None:
77
+ self.planner_session.cfg = value
78
+
79
+ @property
80
+ def workspace_context(self) -> dict[str, Any] | None:
81
+ return self.planner_session.workspace_context
82
+
83
+ @workspace_context.setter
84
+ def workspace_context(self, value: dict[str, Any] | None) -> None:
85
+ self.planner_session.workspace_context = value
86
+
87
+ @property
88
+ def planner_awaiting_clarification(self) -> bool:
89
+ return bool(self.planner_session.awaiting_clarification)
90
+
91
+ @planner_awaiting_clarification.setter
92
+ def planner_awaiting_clarification(self, value: bool) -> None:
93
+ self.planner_session.awaiting_clarification = bool(value)
94
+
95
+ @property
96
+ def planner_pending_questions(self) -> list[str]:
97
+ return self.planner_session.pending_questions
98
+
99
+ @planner_pending_questions.setter
100
+ def planner_pending_questions(self, value: list[str]) -> None:
101
+ self.planner_session.pending_questions = list(value)
102
+
103
+
104
+ @dataclass
105
+ class _ChatPlanModeState:
106
+ enabled: bool = False
107
+ restore_mode: str | None = None
108
+ latest_task: str | None = None
109
+ latest_draft: str | None = None
110
+
111
+
112
+ @dataclass(frozen=True)
113
+ class _ChatExecutionRequest:
114
+ instruction: str
115
+ routing_mode_override: str | None = None
116
+ ephemeral_system_messages: tuple[str, ...] = ()
117
+ ephemeral_user_messages: tuple[str, ...] = ()
118
+ mode_override: str | None = None
119
+ restore_mode_after: str | None = None
120
+ plan_mode_capture_task: str | None = None
121
+ chat_only: bool = False
122
+
123
+
124
+ __all__ = [name for name in globals() if not name.startswith("__") or name == "__version__"]