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,113 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ from ...surface.base import Surface
7
+
8
+
9
+ def _legacy_message_tool_events_required(surface: Surface | object) -> bool:
10
+ """Whether a surface still needs the legacy half of dual event delivery."""
11
+ return not bool(getattr(surface, "canonical_message_tool_events", False))
12
+
13
+
14
+ def _event_preview(value: Any, *, max_chars: int = 500) -> str:
15
+ try:
16
+ text = json.dumps(value, ensure_ascii=True, default=str)
17
+ except (TypeError, ValueError):
18
+ text = repr(value)
19
+ if len(text) <= max_chars:
20
+ return text
21
+ return text[: max_chars - 3].rstrip() + "..."
22
+
23
+
24
+ def _emit_message_delta_event(
25
+ surface: Surface | object,
26
+ text: str,
27
+ *,
28
+ worker_id: str | None = None,
29
+ role: str | None = None,
30
+ ) -> None:
31
+ if not text:
32
+ return
33
+ handler = getattr(surface, "emit_message_delta", None)
34
+ if callable(handler):
35
+ handler(text, worker_id=worker_id, role=role)
36
+
37
+
38
+ def _emit_message_end_event(
39
+ surface: Surface | object,
40
+ text: str,
41
+ *,
42
+ worker_id: str | None = None,
43
+ role: str | None = None,
44
+ ) -> None:
45
+ handler = getattr(surface, "emit_message_end", None)
46
+ if callable(handler):
47
+ handler(text, worker_id=worker_id, role=role)
48
+
49
+
50
+ def _emit_assistant_message_events(
51
+ surface: Surface | object,
52
+ text: str,
53
+ *,
54
+ streamed_text_emitted: bool = False,
55
+ worker_id: str | None = None,
56
+ role: str | None = None,
57
+ ) -> None:
58
+ if not streamed_text_emitted:
59
+ _emit_message_delta_event(surface, text, worker_id=worker_id, role=role)
60
+ _emit_message_end_event(surface, text, worker_id=worker_id, role=role)
61
+
62
+
63
+ def _emit_tool_call_started_event(
64
+ surface: Surface | object,
65
+ *,
66
+ call_id: str,
67
+ name: str,
68
+ arguments: Any,
69
+ worker_id: str | None = None,
70
+ role: str | None = None,
71
+ ) -> None:
72
+ handler = getattr(surface, "emit_tool_call_started", None)
73
+ if callable(handler):
74
+ handler(
75
+ call_id,
76
+ name,
77
+ _event_preview(arguments),
78
+ worker_id=worker_id,
79
+ role=role,
80
+ )
81
+
82
+
83
+ def _emit_tool_call_progress_event(
84
+ surface: Surface | object,
85
+ *,
86
+ call_id: str,
87
+ text: str,
88
+ worker_id: str | None = None,
89
+ role: str | None = None,
90
+ ) -> None:
91
+ handler = getattr(surface, "emit_tool_call_progress", None)
92
+ if callable(handler):
93
+ handler(call_id, text, worker_id=worker_id, role=role)
94
+
95
+
96
+ def _emit_tool_call_completed_event(
97
+ surface: Surface | object,
98
+ *,
99
+ call_id: str,
100
+ success: bool,
101
+ result: Any,
102
+ worker_id: str | None = None,
103
+ role: str | None = None,
104
+ ) -> None:
105
+ handler = getattr(surface, "emit_tool_call_completed", None)
106
+ if callable(handler):
107
+ handler(
108
+ call_id,
109
+ success,
110
+ _event_preview(result),
111
+ worker_id=worker_id,
112
+ role=role,
113
+ )
@@ -0,0 +1,590 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ import shlex
6
+ from collections.abc import Collection
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from ...text_normalization import normalize_for_matching
11
+ from ...tools.registry import get_builtin_tool_metadata
12
+ from ...verification_command_analysis import (
13
+ VerificationCommandEvidentiaryCapability,
14
+ analyze_verification_command,
15
+ )
16
+ from ..prompt_context import (
17
+ MAX_POST_EXPLORE_ANCHOR_PATHS,
18
+ _extract_repo_relative_paths_from_text,
19
+ _normalize_repo_relative_hint_path,
20
+ )
21
+ from ..verification import _runtime_message
22
+
23
+ MAX_RECENT_EXPLORATION_PATHS = 12
24
+ _EXPLORATION_FALLBACK_TOOL_NAMES = {
25
+ "fs_read",
26
+ "fs_read_lines",
27
+ "fs_list",
28
+ "symbol_search",
29
+ "repo_map",
30
+ "search_rg",
31
+ "history_search",
32
+ "git_status",
33
+ "git_diff",
34
+ "git_history",
35
+ }
36
+ _ACTION_PROGRESS_FALLBACK_TOOL_NAMES = {
37
+ "fs_edit",
38
+ "fs_move",
39
+ "fs_copy",
40
+ "fs_delete",
41
+ "fs_mkdir",
42
+ "fs_write",
43
+ "git_apply_patch",
44
+ "verify_run",
45
+ }
46
+ _ACTION_PROGRESS_TOOL_CATEGORIES = {"write", "verify", "subagent"}
47
+ _EXPLORATION_TOOL_CATEGORIES = {"read", "search", "history"}
48
+ _SUBAGENT_READ_CONTROL_TOOL_NAMES = {
49
+ "subagent_cancel",
50
+ "subagent_send",
51
+ "subagent_status",
52
+ }
53
+ _SUBAGENT_EFFECT_CLASSIFIED_TOOL_NAMES = {
54
+ "subagent_run",
55
+ "subagent_spawn",
56
+ "subagent_wait",
57
+ }
58
+ _FAILED_EDIT_STAGNATION_TOOL_NAMES = {"fs_edit", "git_apply_patch", "fs_write"}
59
+ _SHELL_TOOL_NAMES = {
60
+ "shell_run",
61
+ "shell_background",
62
+ "shell_service_start",
63
+ "shell_service_status",
64
+ "workspace_preview_start",
65
+ }
66
+ _SHELL_SERVICE_PROGRESS_TOOL_NAMES = {
67
+ "shell_background",
68
+ "shell_service_start",
69
+ "workspace_preview_start",
70
+ }
71
+ _UNEXECUTED_TOOL_CALL_MARKUP_MARKERS = (
72
+ "<tool_call",
73
+ "</tool_call",
74
+ "tool_calls",
75
+ "<function_calls",
76
+ "</function_calls",
77
+ )
78
+
79
+
80
+ def _exploration_attempt_outcome(success_count: int, failed_count: int) -> str:
81
+ if success_count > 0 and failed_count > 0:
82
+ return "mixed"
83
+ if failed_count > 0:
84
+ return "failed"
85
+ if success_count > 0:
86
+ return "successful"
87
+ return "none"
88
+
89
+
90
+ def _one_shot_progress_fingerprint(text: str) -> str:
91
+ normalized = normalize_for_matching(text)
92
+ return re.sub(r"[^\w ]+", "", normalized, flags=re.UNICODE)
93
+
94
+
95
+ def _stagnation_detection_event_should_emit(detection_count: int, *, nudge_sent: bool) -> bool:
96
+ """Whether the Nth stagnation detection of an episode should be recorded.
97
+
98
+ Once stagnation locks in, the detector fires on every subsequent step, so a
99
+ long stagnating turn used to log one near-identical event per step (50+ per
100
+ session). Detections that actually send a nudge are always recorded;
101
+ otherwise emission is spaced at power-of-two counts (1, 2, 4, 8, ...), so a
102
+ stagnating turn logs O(log n) events with unchanged prompt behavior — the
103
+ suppressed occurrences are summarized on the next emitted event.
104
+ """
105
+
106
+ if nudge_sent:
107
+ return True
108
+ if detection_count <= 0:
109
+ return False
110
+ return detection_count & (detection_count - 1) == 0
111
+
112
+
113
+ def _tool_call_retry_key(name: str, arguments: dict[str, Any]) -> str:
114
+ try:
115
+ payload = json.dumps(arguments, ensure_ascii=True, sort_keys=True)
116
+ except TypeError:
117
+ payload = json.dumps(str(arguments), ensure_ascii=True)
118
+ return f"{name}:{payload}"
119
+
120
+
121
+ def _exploration_similarity_key(name: str, arguments: dict[str, Any]) -> str:
122
+ relevant_fields = (
123
+ "path",
124
+ "root_path",
125
+ "cmd",
126
+ "query",
127
+ "pattern",
128
+ "ref",
129
+ "commit",
130
+ )
131
+ parts = [name]
132
+ for key_name in relevant_fields:
133
+ value = arguments.get(key_name)
134
+ if value is None:
135
+ continue
136
+ parts.append(f"{key_name}={value}")
137
+ return "|".join(parts)
138
+
139
+
140
+ def _edit_similarity_key(name: str, arguments: dict[str, Any]) -> str:
141
+ path = str(arguments.get("path") or "").strip()
142
+ source_path = str(arguments.get("source_path") or "").strip()
143
+ destination_path = str(arguments.get("destination_path") or "").strip()
144
+ ops = arguments.get("edits")
145
+ op_names: list[str] = []
146
+ if isinstance(ops, list):
147
+ for raw in ops:
148
+ if isinstance(raw, dict):
149
+ op = raw.get("op")
150
+ if isinstance(op, str):
151
+ op_names.append(op.strip())
152
+ op_signature = ",".join(op_names[:5])
153
+ return "|".join(
154
+ [
155
+ name,
156
+ f"path={path}",
157
+ f"source={source_path}",
158
+ f"destination={destination_path}",
159
+ f"ops={op_signature}",
160
+ ]
161
+ )
162
+
163
+
164
+ def _append_recent_exploration_path(
165
+ *,
166
+ paths: list[str],
167
+ candidate: str | None,
168
+ max_items: int = MAX_RECENT_EXPLORATION_PATHS,
169
+ ) -> None:
170
+ if not candidate:
171
+ return
172
+ normalized = candidate.strip()
173
+ if not normalized or normalized == ".":
174
+ return
175
+ existing_idx = None
176
+ for idx, item in enumerate(paths):
177
+ if item.casefold() == normalized.casefold():
178
+ existing_idx = idx
179
+ break
180
+ if existing_idx is not None:
181
+ paths.pop(existing_idx)
182
+ paths.append(normalized)
183
+ if len(paths) > max_items:
184
+ del paths[0 : len(paths) - max_items]
185
+
186
+
187
+ def _looks_like_unexecuted_tool_call_markup(text: str) -> bool:
188
+ normalized = str(text or "").strip().lower()
189
+ if not normalized:
190
+ return False
191
+ if "dsml" in normalized and ("tool_calls" in normalized or "invoke" in normalized):
192
+ return True
193
+ return any(marker in normalized for marker in _UNEXECUTED_TOOL_CALL_MARKUP_MARKERS)
194
+
195
+
196
+ def _extract_successful_exploration_paths(
197
+ *,
198
+ root: Path,
199
+ tool_name: str,
200
+ arguments: dict[str, Any],
201
+ result: dict[str, Any],
202
+ max_items: int = MAX_POST_EXPLORE_ANCHOR_PATHS,
203
+ ) -> list[str]:
204
+ normalized_tool = tool_name.strip().lower()
205
+ out: list[str] = []
206
+
207
+ for key in ("path", "root_path"):
208
+ value = arguments.get(key)
209
+ if isinstance(value, str):
210
+ normalized = _normalize_repo_relative_hint_path(root=root, raw=value)
211
+ if normalized and not any(
212
+ existing.casefold() == normalized.casefold() for existing in out
213
+ ):
214
+ out.append(normalized)
215
+ if len(out) >= max_items:
216
+ return out
217
+
218
+ if normalized_tool == "fs_list":
219
+ result_root = _normalize_repo_relative_hint_path(
220
+ root=root, raw=str(result.get("root") or "")
221
+ )
222
+ entries = result.get("entries")
223
+ if isinstance(entries, list):
224
+ for entry in entries[: max_items * 3]:
225
+ if not isinstance(entry, dict):
226
+ continue
227
+ rel = str(entry.get("path") or "")
228
+ if not rel:
229
+ continue
230
+ if result_root and result_root != ".":
231
+ combined = f"{result_root.rstrip('/')}/{rel.lstrip('./')}"
232
+ else:
233
+ combined = rel
234
+ normalized = _normalize_repo_relative_hint_path(root=root, raw=combined)
235
+ if normalized and not any(
236
+ existing.casefold() == normalized.casefold() for existing in out
237
+ ):
238
+ out.append(normalized)
239
+ if len(out) >= max_items:
240
+ return out
241
+
242
+ if normalized_tool == "search_rg":
243
+ matches = result.get("matches")
244
+ if isinstance(matches, list):
245
+ for match in matches[: max_items * 3]:
246
+ if not isinstance(match, dict):
247
+ continue
248
+ normalized = _normalize_repo_relative_hint_path(
249
+ root=root,
250
+ raw=str(match.get("path") or ""),
251
+ )
252
+ if normalized and not any(
253
+ existing.casefold() == normalized.casefold() for existing in out
254
+ ):
255
+ out.append(normalized)
256
+ if len(out) >= max_items:
257
+ return out
258
+
259
+ if normalized_tool == "subagent_run":
260
+ subagent_result = str(result.get("result") or "")
261
+ for candidate in _extract_repo_relative_paths_from_text(
262
+ root=root,
263
+ text=subagent_result,
264
+ max_items=max_items,
265
+ ):
266
+ if any(existing.casefold() == candidate.casefold() for existing in out):
267
+ continue
268
+ out.append(candidate)
269
+ if len(out) >= max_items:
270
+ break
271
+
272
+ return out
273
+
274
+
275
+ def _is_successful_subagent_run(
276
+ *,
277
+ tool_name: str,
278
+ arguments: dict[str, Any],
279
+ status: str,
280
+ result: dict[str, Any],
281
+ ) -> bool:
282
+ if tool_name.strip().lower() != "subagent_run":
283
+ return False
284
+ if status == "failed":
285
+ return False
286
+ return bool(str(result.get("subagent") or arguments.get("name") or "").strip())
287
+
288
+
289
+ def _build_post_explore_bootstrap_nudge(
290
+ *,
291
+ anchor_paths: list[str],
292
+ language: str = "",
293
+ explicit_language_override: bool = False,
294
+ ) -> str:
295
+ message = _runtime_message(
296
+ "one_shot_post_explore_bootstrap_nudge",
297
+ language=language,
298
+ explicit_language_override=explicit_language_override,
299
+ )
300
+ if anchor_paths:
301
+ joined = ", ".join(anchor_paths[:MAX_POST_EXPLORE_ANCHOR_PATHS])
302
+ message += " " + _runtime_message(
303
+ "one_shot_post_explore_bootstrap_targets",
304
+ language=language,
305
+ explicit_language_override=explicit_language_override,
306
+ joined=joined,
307
+ )
308
+ return message
309
+
310
+
311
+ def _tool_categories(tool_name: str) -> set[str]:
312
+ metadata = get_builtin_tool_metadata(tool_name)
313
+ if metadata is None:
314
+ return set()
315
+ return {str(category).strip().lower() for category in metadata.categories}
316
+
317
+
318
+ def _normal_tool_name(tool_name: str) -> str:
319
+ return str(tool_name or "").strip().lower()
320
+
321
+
322
+ def _shell_cmd(arguments: dict[str, Any] | None) -> str:
323
+ if not isinstance(arguments, dict):
324
+ return ""
325
+ return str(arguments.get("cmd") or arguments.get("command") or "").strip()
326
+
327
+
328
+ def _shell_exit_code(result: dict[str, Any] | None) -> int | None:
329
+ if not isinstance(result, dict):
330
+ return None
331
+ value = result.get("exit_code")
332
+ return value if isinstance(value, int) else None
333
+
334
+
335
+ def _shell_touched_paths(
336
+ *,
337
+ result: dict[str, Any] | None,
338
+ touched_paths: Collection[str] | None,
339
+ ) -> tuple[str, ...]:
340
+ out: list[str] = []
341
+ for raw in touched_paths or ():
342
+ text = str(raw or "").strip()
343
+ if text:
344
+ out.append(text)
345
+ if isinstance(result, dict):
346
+ for key in (
347
+ "touched_repo_paths",
348
+ "material_touched_repo_paths",
349
+ "verification_relevant_touched_paths",
350
+ ):
351
+ value = result.get(key)
352
+ if isinstance(value, (list, tuple, set)):
353
+ for raw in value:
354
+ text = str(raw or "").strip()
355
+ if text:
356
+ out.append(text)
357
+ deduped: list[str] = []
358
+ for item in out:
359
+ if not any(existing.casefold() == item.casefold() for existing in deduped):
360
+ deduped.append(item)
361
+ return tuple(deduped)
362
+
363
+
364
+ def _shell_result_has_mutation_effect(
365
+ *,
366
+ result: dict[str, Any] | None,
367
+ touched_paths: Collection[str] | None,
368
+ ) -> bool:
369
+ return bool(_shell_touched_paths(result=result, touched_paths=touched_paths))
370
+
371
+
372
+ def _shell_command_is_assertive_verification(
373
+ *,
374
+ cmd: str,
375
+ result: dict[str, Any] | None,
376
+ ) -> bool:
377
+ if not cmd:
378
+ return False
379
+ if isinstance(result, dict) and (
380
+ bool(result.get("trusted_shell_verification"))
381
+ or bool(result.get("verification_evidence_allowed"))
382
+ ):
383
+ return True
384
+ try:
385
+ analysis = analyze_verification_command(cmd, trusted=False)
386
+ except Exception: # noqa: BLE001
387
+ return False
388
+ if analysis.rejection_reason:
389
+ return False
390
+ return analysis.evidentiary_capability == VerificationCommandEvidentiaryCapability.ASSERTIVE
391
+
392
+
393
+ def _shell_command_head(cmd: str) -> str:
394
+ try:
395
+ parts = shlex.split(cmd)
396
+ except ValueError:
397
+ return ""
398
+ if not parts:
399
+ return ""
400
+ head = parts[0].rsplit("/", 1)[-1].casefold()
401
+ if (
402
+ head in {"python", "python3", "node"}
403
+ and len(parts) >= 3
404
+ and parts[1]
405
+ in {
406
+ "-m",
407
+ "-c",
408
+ }
409
+ ):
410
+ return f"{head} {parts[1]} {parts[2].casefold()}"
411
+ return head
412
+
413
+
414
+ def _shell_command_is_low_value_exploration(cmd: str) -> bool:
415
+ head = _shell_command_head(cmd)
416
+ if not head:
417
+ return True
418
+ if head in {
419
+ "awk",
420
+ "cat",
421
+ "cut",
422
+ "env",
423
+ "file",
424
+ "find",
425
+ "grep",
426
+ "head",
427
+ "ls",
428
+ "pwd",
429
+ "rg",
430
+ "sed",
431
+ "stat",
432
+ "tail",
433
+ "type",
434
+ "wc",
435
+ "which",
436
+ }:
437
+ return True
438
+ return head.startswith(("python -c", "python3 -c", "node -c"))
439
+
440
+
441
+ def _shell_command_is_focused_progress(
442
+ *,
443
+ cmd: str,
444
+ focused: bool,
445
+ result: dict[str, Any] | None,
446
+ ) -> bool:
447
+ if not focused or not cmd:
448
+ return False
449
+ if _shell_command_is_low_value_exploration(cmd):
450
+ return False
451
+ exit_code = _shell_exit_code(result)
452
+ return exit_code is None or exit_code == 0
453
+
454
+
455
+ def _is_shell_action_progress_tool(
456
+ tool_name: str,
457
+ *,
458
+ arguments: dict[str, Any] | None = None,
459
+ result: dict[str, Any] | None = None,
460
+ touched_paths: Collection[str] | None = None,
461
+ focused: bool = False,
462
+ ) -> bool:
463
+ normalized = _normal_tool_name(tool_name)
464
+ if normalized in _SHELL_SERVICE_PROGRESS_TOOL_NAMES:
465
+ return True
466
+ if normalized == "shell_service_status":
467
+ return isinstance(result, dict) and bool(result.get("ready") or result.get("healthy"))
468
+ if normalized != "shell_run":
469
+ return False
470
+ if _shell_result_has_mutation_effect(result=result, touched_paths=touched_paths):
471
+ return True
472
+ cmd = _shell_cmd(arguments)
473
+ if _shell_command_is_assertive_verification(cmd=cmd, result=result):
474
+ return True
475
+ return _shell_command_is_focused_progress(cmd=cmd, focused=focused, result=result)
476
+
477
+
478
+ def _subagent_result_has_action_effect(
479
+ *,
480
+ result: dict[str, Any] | None,
481
+ touched_paths: Collection[str] | None,
482
+ ) -> bool:
483
+ if touched_paths:
484
+ return True
485
+ if not isinstance(result, dict):
486
+ return False
487
+ candidates = [result]
488
+ nested_results = result.get("results")
489
+ if isinstance(nested_results, dict):
490
+ candidates.extend(
491
+ candidate for candidate in nested_results.values() if isinstance(candidate, dict)
492
+ )
493
+ for candidate in candidates:
494
+ raw_effects = candidate.get("effects")
495
+ effect_names = (
496
+ {str(effect or "").strip().lower() for effect in raw_effects if str(effect or "")}
497
+ if isinstance(raw_effects, (list, tuple, set))
498
+ else set()
499
+ )
500
+ if effect_names & {"write_workspace", "run_commands", "external_write"}:
501
+ return True
502
+ if _shell_touched_paths(result=candidate, touched_paths=None):
503
+ return True
504
+ return False
505
+
506
+
507
+ def _is_subagent_action_progress_tool(
508
+ tool_name: str,
509
+ *,
510
+ arguments: dict[str, Any] | None,
511
+ result: dict[str, Any] | None,
512
+ touched_paths: Collection[str] | None,
513
+ ) -> bool:
514
+ normalized = _normal_tool_name(tool_name)
515
+ if normalized in _SUBAGENT_READ_CONTROL_TOOL_NAMES:
516
+ return False
517
+ if normalized == "subagent_spawn":
518
+ workspace_view = str((arguments or {}).get("workspace_view") or "shared")
519
+ return workspace_view.strip().lower() == "isolated"
520
+ return _subagent_result_has_action_effect(
521
+ result=result,
522
+ touched_paths=touched_paths,
523
+ )
524
+
525
+
526
+ def _is_action_progress_tool(
527
+ tool_name: str,
528
+ *,
529
+ arguments: dict[str, Any] | None = None,
530
+ result: dict[str, Any] | None = None,
531
+ touched_paths: Collection[str] | None = None,
532
+ focused: bool = False,
533
+ ) -> bool:
534
+ normalized = _normal_tool_name(tool_name)
535
+ if normalized in (_SUBAGENT_READ_CONTROL_TOOL_NAMES | _SUBAGENT_EFFECT_CLASSIFIED_TOOL_NAMES):
536
+ return _is_subagent_action_progress_tool(
537
+ normalized,
538
+ arguments=arguments,
539
+ result=result,
540
+ touched_paths=touched_paths,
541
+ )
542
+ if normalized in _SHELL_TOOL_NAMES:
543
+ return _is_shell_action_progress_tool(
544
+ normalized,
545
+ arguments=arguments,
546
+ result=result,
547
+ touched_paths=touched_paths,
548
+ focused=focused,
549
+ )
550
+ categories = _tool_categories(tool_name)
551
+ if categories:
552
+ return bool(categories & _ACTION_PROGRESS_TOOL_CATEGORIES)
553
+ return normalized in _ACTION_PROGRESS_FALLBACK_TOOL_NAMES
554
+
555
+
556
+ def _is_exploration_only_tool(
557
+ tool_name: str,
558
+ *,
559
+ arguments: dict[str, Any] | None = None,
560
+ result: dict[str, Any] | None = None,
561
+ touched_paths: Collection[str] | None = None,
562
+ focused: bool = False,
563
+ ) -> bool:
564
+ normalized = _normal_tool_name(tool_name)
565
+ if normalized in (_SUBAGENT_READ_CONTROL_TOOL_NAMES | _SUBAGENT_EFFECT_CLASSIFIED_TOOL_NAMES):
566
+ return not _is_action_progress_tool(
567
+ normalized,
568
+ arguments=arguments,
569
+ result=result,
570
+ touched_paths=touched_paths,
571
+ focused=focused,
572
+ )
573
+ if normalized in _SHELL_TOOL_NAMES:
574
+ return not _is_shell_action_progress_tool(
575
+ normalized,
576
+ arguments=arguments,
577
+ result=result,
578
+ touched_paths=touched_paths,
579
+ focused=focused,
580
+ )
581
+ categories = _tool_categories(tool_name)
582
+ if categories:
583
+ if categories & _ACTION_PROGRESS_TOOL_CATEGORIES:
584
+ return False
585
+ return bool(categories & _EXPLORATION_TOOL_CATEGORIES)
586
+ return normalized in _EXPLORATION_FALLBACK_TOOL_NAMES
587
+
588
+
589
+ def _is_failed_edit_stagnation_tool(tool_name: str) -> bool:
590
+ return tool_name.strip().lower() in _FAILED_EDIT_STAGNATION_TOOL_NAMES