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,304 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import Any, Literal
6
+
7
+ from ..frontmatter_utils import parse_frontmatter_yaml, split_frontmatter
8
+ from .prompting import (
9
+ EXPLICIT_SKILL_CONTEXT_TOTAL_MAX_CHARS,
10
+ EXPLICIT_SKILL_DESCRIPTION_DISPLAY_MAX_CHARS,
11
+ EXPLICIT_SKILL_ENTRYPOINT_MAX_CHARS,
12
+ EXPLICIT_SKILL_NAME_DISPLAY_MAX_CHARS,
13
+ )
14
+
15
+ SKILL_ENTRYPOINT_WARNING_CHARS = EXPLICIT_SKILL_ENTRYPOINT_MAX_CHARS
16
+ SKILL_NAME_WARNING_CHARS = EXPLICIT_SKILL_NAME_DISPLAY_MAX_CHARS
17
+ SKILL_DESCRIPTION_WARNING_CHARS = EXPLICIT_SKILL_DESCRIPTION_DISPLAY_MAX_CHARS
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class SkillValidationIssue:
22
+ severity: Literal["error", "warning"]
23
+ message: str
24
+ path: Path
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class SkillValidationResult:
29
+ bundle_path: Path
30
+ entry_path: Path
31
+ valid: bool
32
+ name: str = ""
33
+ description: str = ""
34
+ instructions: str = ""
35
+ metadata: dict[str, Any] = field(default_factory=dict)
36
+ issues: tuple[SkillValidationIssue, ...] = ()
37
+
38
+ @property
39
+ def errors(self) -> tuple[SkillValidationIssue, ...]:
40
+ return tuple(issue for issue in self.issues if issue.severity == "error")
41
+
42
+ @property
43
+ def warnings(self) -> tuple[SkillValidationIssue, ...]:
44
+ return tuple(issue for issue in self.issues if issue.severity == "warning")
45
+
46
+
47
+ def validate_skill_bundle(bundle_path: Path) -> SkillValidationResult:
48
+ """Validate a skill bundle without allowing filesystem faults to escape.
49
+
50
+ Skill directories are untrusted workspace input. On Windows in particular,
51
+ even metadata probes such as ``is_symlink()`` can raise ``PermissionError``
52
+ for a deny-ACL directory. Discovery must fail that one bundle closed instead
53
+ of making session creation fail.
54
+ """
55
+
56
+ try:
57
+ return _validate_skill_bundle(bundle_path)
58
+ except OSError as exc:
59
+ try:
60
+ reported_bundle = bundle_path.expanduser().absolute()
61
+ except OSError:
62
+ reported_bundle = bundle_path
63
+ entry_path = reported_bundle / "SKILL.md"
64
+ issue = SkillValidationIssue(
65
+ severity="error",
66
+ message=f"Failed to inspect skill bundle: {exc}",
67
+ path=reported_bundle,
68
+ )
69
+ return SkillValidationResult(
70
+ bundle_path=reported_bundle,
71
+ entry_path=entry_path,
72
+ valid=False,
73
+ issues=(issue,),
74
+ )
75
+
76
+
77
+ def _validate_skill_bundle(bundle_path: Path) -> SkillValidationResult:
78
+ resolved_bundle = bundle_path.expanduser().resolve()
79
+ entry_path = resolved_bundle / "SKILL.md"
80
+ issues: list[SkillValidationIssue] = []
81
+ if not resolved_bundle.exists() or not resolved_bundle.is_dir():
82
+ issues.append(
83
+ SkillValidationIssue(
84
+ severity="error",
85
+ message="Skill bundle path is not a directory.",
86
+ path=resolved_bundle,
87
+ )
88
+ )
89
+ return SkillValidationResult(
90
+ bundle_path=resolved_bundle,
91
+ entry_path=entry_path,
92
+ valid=False,
93
+ issues=tuple(issues),
94
+ )
95
+ if entry_path.is_symlink():
96
+ issues.append(
97
+ SkillValidationIssue(
98
+ severity="error",
99
+ message="SKILL.md may not be a symlink.",
100
+ path=entry_path,
101
+ )
102
+ )
103
+ if not entry_path.exists() or not entry_path.is_file():
104
+ issues.append(
105
+ SkillValidationIssue(
106
+ severity="error",
107
+ message="SKILL.md is missing.",
108
+ path=entry_path,
109
+ )
110
+ )
111
+ return SkillValidationResult(
112
+ bundle_path=resolved_bundle,
113
+ entry_path=entry_path,
114
+ valid=False,
115
+ issues=tuple(issues),
116
+ )
117
+ try:
118
+ raw_text = entry_path.read_text(encoding="utf-8")
119
+ except UnicodeDecodeError:
120
+ issues.append(
121
+ SkillValidationIssue(
122
+ severity="error",
123
+ message="SKILL.md is not valid UTF-8 text.",
124
+ path=entry_path,
125
+ )
126
+ )
127
+ return SkillValidationResult(
128
+ bundle_path=resolved_bundle,
129
+ entry_path=entry_path,
130
+ valid=False,
131
+ issues=tuple(issues),
132
+ )
133
+ except OSError as exc:
134
+ issues.append(
135
+ SkillValidationIssue(
136
+ severity="error",
137
+ message=f"Failed to read SKILL.md: {exc}",
138
+ path=entry_path,
139
+ )
140
+ )
141
+ return SkillValidationResult(
142
+ bundle_path=resolved_bundle,
143
+ entry_path=entry_path,
144
+ valid=False,
145
+ issues=tuple(issues),
146
+ )
147
+
148
+ frontmatter, body = split_frontmatter(raw_text)
149
+ if frontmatter is None:
150
+ issues.append(
151
+ SkillValidationIssue(
152
+ severity="error",
153
+ message="missing YAML-style frontmatter in SKILL.md",
154
+ path=entry_path,
155
+ )
156
+ )
157
+ return SkillValidationResult(
158
+ bundle_path=resolved_bundle,
159
+ entry_path=entry_path,
160
+ valid=False,
161
+ issues=tuple(issues),
162
+ )
163
+
164
+ meta = parse_frontmatter_yaml(
165
+ frontmatter,
166
+ allowed_keys={"name", "description", "aliases"},
167
+ list_fields={"aliases"},
168
+ string_fields={"name", "description"},
169
+ )
170
+ name = str(meta.get("name") or "").strip()
171
+ description = str(meta.get("description") or "").strip()
172
+ instructions = body.strip()
173
+ if not name:
174
+ issues.append(
175
+ SkillValidationIssue(
176
+ severity="error",
177
+ message="Missing required frontmatter field: name.",
178
+ path=entry_path,
179
+ )
180
+ )
181
+ if not description:
182
+ issues.append(
183
+ SkillValidationIssue(
184
+ severity="error",
185
+ message="Missing required frontmatter field: description.",
186
+ path=entry_path,
187
+ )
188
+ )
189
+ if not instructions:
190
+ issues.append(
191
+ SkillValidationIssue(
192
+ severity="error",
193
+ message="SKILL.md body/instructions are empty.",
194
+ path=entry_path,
195
+ )
196
+ )
197
+ if name and len(name) > SKILL_NAME_WARNING_CHARS:
198
+ issues.append(
199
+ SkillValidationIssue(
200
+ severity="warning",
201
+ message=(
202
+ "Skill name is unusually large "
203
+ f"(>{SKILL_NAME_WARNING_CHARS:,} chars); explicit /skill metadata display is "
204
+ "bounded and may truncate the attached name preview."
205
+ ),
206
+ path=entry_path,
207
+ )
208
+ )
209
+ if description and len(description) > SKILL_DESCRIPTION_WARNING_CHARS:
210
+ issues.append(
211
+ SkillValidationIssue(
212
+ severity="warning",
213
+ message=(
214
+ "Skill description is unusually large "
215
+ f"(>{SKILL_DESCRIPTION_WARNING_CHARS:,} chars); explicit /skill metadata display "
216
+ "is bounded and may truncate the attached description preview."
217
+ ),
218
+ path=entry_path,
219
+ )
220
+ )
221
+ if len(raw_text) > SKILL_ENTRYPOINT_WARNING_CHARS:
222
+ issues.append(
223
+ SkillValidationIssue(
224
+ severity="warning",
225
+ message=(
226
+ "SKILL.md entrypoint is unusually large "
227
+ f"(>{SKILL_ENTRYPOINT_WARNING_CHARS:,} chars); explicit /skill context is "
228
+ f"bounded to a {EXPLICIT_SKILL_CONTEXT_TOTAL_MAX_CHARS:,}-char wrapper and may "
229
+ "truncate the attached preview."
230
+ ),
231
+ path=entry_path,
232
+ )
233
+ )
234
+ for optional_name in ("references", "scripts", "assets"):
235
+ optional_path = resolved_bundle / optional_name
236
+ if optional_path.is_symlink():
237
+ issues.append(
238
+ SkillValidationIssue(
239
+ severity="error",
240
+ message=f"{optional_name}/ may not be a symlink.",
241
+ path=optional_path,
242
+ )
243
+ )
244
+ elif optional_path.exists() and not optional_path.is_dir():
245
+ issues.append(
246
+ SkillValidationIssue(
247
+ severity="error",
248
+ message=f"{optional_name}/ must be a directory when present.",
249
+ path=optional_path,
250
+ )
251
+ )
252
+ for candidate in resolved_bundle.rglob("*"):
253
+ if candidate == entry_path:
254
+ continue
255
+ if candidate.is_symlink():
256
+ issues.append(
257
+ SkillValidationIssue(
258
+ severity="error",
259
+ message="Skill bundles may not contain symlinks.",
260
+ path=candidate,
261
+ )
262
+ )
263
+ break
264
+ total_bytes = 0
265
+ file_count = 0
266
+ for candidate in resolved_bundle.rglob("*"):
267
+ if not candidate.is_file():
268
+ continue
269
+ file_count += 1
270
+ try:
271
+ total_bytes += candidate.stat().st_size
272
+ except OSError:
273
+ continue
274
+ if total_bytes > 5 * 1024 * 1024:
275
+ issues.append(
276
+ SkillValidationIssue(
277
+ severity="warning",
278
+ message="Skill bundle is unusually large (>5 MiB).",
279
+ path=resolved_bundle,
280
+ )
281
+ )
282
+ if file_count > 256:
283
+ issues.append(
284
+ SkillValidationIssue(
285
+ severity="warning",
286
+ message="Skill bundle contains an unusually high file count (>256 files).",
287
+ path=resolved_bundle,
288
+ )
289
+ )
290
+
291
+ return SkillValidationResult(
292
+ bundle_path=resolved_bundle,
293
+ entry_path=entry_path,
294
+ valid=not any(issue.severity == "error" for issue in issues),
295
+ name=name,
296
+ description=description,
297
+ instructions=instructions,
298
+ metadata={
299
+ key: value
300
+ for key, value in meta.items()
301
+ if key in {"name", "description", "aliases"} and value
302
+ },
303
+ issues=tuple(issues),
304
+ )
@@ -0,0 +1,238 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ # These remain the default limits for the opt-in ``limited`` policy and for
7
+ # backwards-compatible configuration files. The default ``autonomous`` policy
8
+ # does not consume them.
9
+ DEFAULT_CHAT_MAX_STEPS = 80
10
+ DEFAULT_TASK_MAX_STEPS = 160
11
+ DEFAULT_SUBAGENT_MAX_STEPS = 28
12
+
13
+ AUTONOMOUS_STEP_BUDGET_POLICY = "autonomous"
14
+ LIMITED_STEP_BUDGET_POLICY = "limited"
15
+
16
+ # ``adaptive`` and ``fixed`` are legacy spellings. Keeping them loadable avoids
17
+ # breaking existing installations while giving them the new, explicit contract:
18
+ # adaptive -> autonomous, fixed -> limited.
19
+ _STEP_BUDGET_POLICY_ALIASES = {
20
+ "adaptive": AUTONOMOUS_STEP_BUDGET_POLICY,
21
+ "fixed": LIMITED_STEP_BUDGET_POLICY,
22
+ }
23
+ VALID_STEP_BUDGET_POLICIES = {
24
+ AUTONOMOUS_STEP_BUDGET_POLICY,
25
+ LIMITED_STEP_BUDGET_POLICY,
26
+ *_STEP_BUDGET_POLICY_ALIASES,
27
+ }
28
+
29
+
30
+ def _positive_int(value: Any, *, default: int) -> int:
31
+ try:
32
+ parsed = int(value)
33
+ except (TypeError, ValueError):
34
+ return max(1, int(default))
35
+ return max(1, parsed)
36
+
37
+
38
+ def normalize_step_budget_policy(raw_value: Any) -> str:
39
+ normalized = str(raw_value or "").strip().lower()
40
+ normalized = _STEP_BUDGET_POLICY_ALIASES.get(normalized, normalized)
41
+ if normalized in {AUTONOMOUS_STEP_BUDGET_POLICY, LIMITED_STEP_BUDGET_POLICY}:
42
+ return normalized
43
+ return AUTONOMOUS_STEP_BUDGET_POLICY
44
+
45
+
46
+ def step_budget_is_autonomous(raw_value: Any) -> bool:
47
+ return normalize_step_budget_policy(raw_value) == AUTONOMOUS_STEP_BUDGET_POLICY
48
+
49
+
50
+ def resolve_subagent_step_profile(subagent_name: Any) -> str | None:
51
+ normalized = str(subagent_name or "").strip().lower()
52
+ if not normalized:
53
+ return None
54
+ if normalized in {
55
+ "explorer",
56
+ "implementer",
57
+ "frontend-engineer",
58
+ "debugger",
59
+ "verifier",
60
+ "code-reviewer",
61
+ "visual-designer",
62
+ }:
63
+ return normalized
64
+ return "default"
65
+
66
+
67
+ @dataclass(frozen=True)
68
+ class StepBudgetRequest:
69
+ kind: str
70
+ policy: str
71
+ hard_cap: int | None
72
+ fixed_override: int | None = None
73
+ mode: str | None = None
74
+ route: str | None = None
75
+ one_shot_execution: bool = False
76
+ one_shot_turn_intent: str | None = None
77
+ verification_enabled: bool = False
78
+ subagents_enabled: bool = False
79
+ subagent_name: str | None = None
80
+ parent_turn_budget: int | None = None
81
+ attempt_count: int = 1
82
+ image_count: int = 0
83
+ explicit_path_count: int = 0
84
+ acceptance_criteria_count: int = 0
85
+ estimated_files_count: int = 0
86
+ write_scope_count: int = 0
87
+ dependency_count: int = 0
88
+ asset_count: int = 0
89
+ conflict_file_count: int = 0
90
+
91
+
92
+ @dataclass(frozen=True)
93
+ class StepBudgetResolution:
94
+ kind: str
95
+ policy: str
96
+ hard_cap: int | None
97
+ resolved_max_steps: int | None
98
+ override_applied: bool
99
+ reason: str
100
+ signals_used: dict[str, Any]
101
+ base_steps: int | None
102
+ profile: str | None = None
103
+
104
+ @property
105
+ def unlimited(self) -> bool:
106
+ return self.resolved_max_steps is None
107
+
108
+ def to_payload(self) -> dict[str, Any]:
109
+ return {
110
+ "kind": self.kind,
111
+ "policy": self.policy,
112
+ "hard_cap": self.hard_cap,
113
+ "resolved_max_steps": self.resolved_max_steps,
114
+ "unlimited": self.unlimited,
115
+ "override_applied": self.override_applied,
116
+ "reason": self.reason,
117
+ "signals_used": dict(self.signals_used),
118
+ "base_steps": self.base_steps,
119
+ "profile": self.profile,
120
+ }
121
+
122
+
123
+ @dataclass
124
+ class StepBudgetRuntime:
125
+ active_turn_budget: int | None = None
126
+ last_resolution: StepBudgetResolution | None = None
127
+
128
+
129
+ def _default_cap(kind: str) -> int:
130
+ return {
131
+ "chat_turn": DEFAULT_CHAT_MAX_STEPS,
132
+ "managed_task": DEFAULT_TASK_MAX_STEPS,
133
+ "conflict_resolution": DEFAULT_TASK_MAX_STEPS,
134
+ "subagent": DEFAULT_SUBAGENT_MAX_STEPS,
135
+ }.get(kind, DEFAULT_CHAT_MAX_STEPS)
136
+
137
+
138
+ def _resolve_limited_cap(request: StepBudgetRequest, *, kind: str) -> int:
139
+ hard_cap = _positive_int(request.hard_cap, default=_default_cap(kind))
140
+ if kind == "subagent" and request.parent_turn_budget is not None:
141
+ hard_cap = min(
142
+ hard_cap,
143
+ _positive_int(request.parent_turn_budget, default=hard_cap),
144
+ )
145
+ return max(1, hard_cap)
146
+
147
+
148
+ def _request_signals(request: StepBudgetRequest) -> dict[str, Any]:
149
+ """Retain useful task-shape telemetry without using it to terminate work."""
150
+
151
+ return {
152
+ "mode": request.mode,
153
+ "route": request.route,
154
+ "one_shot_execution": bool(request.one_shot_execution),
155
+ "one_shot_turn_intent": request.one_shot_turn_intent,
156
+ "verification_enabled": bool(request.verification_enabled),
157
+ "subagents_enabled": bool(request.subagents_enabled),
158
+ "subagent_name": request.subagent_name,
159
+ "parent_turn_budget": request.parent_turn_budget,
160
+ "attempt_count": request.attempt_count,
161
+ "image_count": request.image_count,
162
+ "explicit_path_count": request.explicit_path_count,
163
+ "acceptance_criteria_count": request.acceptance_criteria_count,
164
+ "estimated_files_count": request.estimated_files_count,
165
+ "write_scope_count": request.write_scope_count,
166
+ "dependency_count": request.dependency_count,
167
+ "asset_count": request.asset_count,
168
+ "conflict_file_count": request.conflict_file_count,
169
+ }
170
+
171
+
172
+ def resolve_step_budget(request: StepBudgetRequest) -> StepBudgetResolution:
173
+ """Resolve an optional safety ceiling for one autonomous agent loop.
174
+
175
+ Autonomous execution is intentionally unbounded. A finite ceiling exists
176
+ only when the caller supplies an explicit override or selects the opt-in
177
+ limited policy. Completion, cancellation, blockers, fatal errors, and an
178
+ optional wall-clock deadline remain independent termination conditions.
179
+ """
180
+
181
+ kind = str(request.kind or "").strip().lower() or "chat_turn"
182
+ policy = normalize_step_budget_policy(request.policy)
183
+ profile = resolve_subagent_step_profile(request.subagent_name) if kind == "subagent" else None
184
+
185
+ if request.fixed_override is not None:
186
+ explicit_limit = _positive_int(
187
+ request.fixed_override,
188
+ default=_default_cap(kind),
189
+ )
190
+ return StepBudgetResolution(
191
+ kind=kind,
192
+ policy=policy,
193
+ hard_cap=explicit_limit,
194
+ resolved_max_steps=explicit_limit,
195
+ override_applied=True,
196
+ reason="explicit_limit",
197
+ signals_used={
198
+ **_request_signals(request),
199
+ "policy": policy,
200
+ "explicit_limit": explicit_limit,
201
+ },
202
+ base_steps=explicit_limit,
203
+ profile=profile,
204
+ )
205
+
206
+ if policy == LIMITED_STEP_BUDGET_POLICY:
207
+ hard_cap = _resolve_limited_cap(request, kind=kind)
208
+ return StepBudgetResolution(
209
+ kind=kind,
210
+ policy=policy,
211
+ hard_cap=hard_cap,
212
+ resolved_max_steps=hard_cap,
213
+ override_applied=False,
214
+ reason="limited_policy",
215
+ signals_used={
216
+ **_request_signals(request),
217
+ "policy": policy,
218
+ "hard_cap": hard_cap,
219
+ },
220
+ base_steps=hard_cap,
221
+ profile=profile,
222
+ )
223
+
224
+ return StepBudgetResolution(
225
+ kind=kind,
226
+ policy=AUTONOMOUS_STEP_BUDGET_POLICY,
227
+ hard_cap=None,
228
+ resolved_max_steps=None,
229
+ override_applied=False,
230
+ reason="autonomous_unbounded",
231
+ signals_used={
232
+ **_request_signals(request),
233
+ "policy": AUTONOMOUS_STEP_BUDGET_POLICY,
234
+ "configured_legacy_cap": request.hard_cap,
235
+ },
236
+ base_steps=None,
237
+ profile=profile,
238
+ )
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+
5
+ from .safety.subagent_report import sanitize_subagent_report, subagent_report_evidence_text
6
+
7
+ SUBAGENT_TASK_LABEL_MAX_CHARS = 24
8
+
9
+ _LABEL_TOKEN_RE = re.compile(r"[\w.-]+", flags=re.UNICODE)
10
+
11
+
12
+ def subagent_task_label(task: object, *, requested_run_id: object = "") -> str:
13
+ """Return a stable, display-safe child label without exposing the generated id."""
14
+ caller_label = str(requested_run_id or "").strip()
15
+ if caller_label:
16
+ return caller_label
17
+
18
+ screened = sanitize_subagent_report(str(task or "")).text
19
+ evidence = subagent_report_evidence_text(screened)
20
+ words = [
21
+ token
22
+ for token in _LABEL_TOKEN_RE.findall(evidence)
23
+ if any(character.isalnum() for character in token)
24
+ ]
25
+ selected: list[str] = []
26
+ for word in words:
27
+ candidate = " ".join((*selected, word))
28
+ if len(candidate) > SUBAGENT_TASK_LABEL_MAX_CHARS:
29
+ break
30
+ selected.append(word)
31
+ if selected:
32
+ return " ".join(selected)
33
+ if words:
34
+ return words[0][:SUBAGENT_TASK_LABEL_MAX_CHARS]
35
+ return "task"
36
+
37
+
38
+ def subagent_identity(name: object, label: object) -> str:
39
+ """Combine role and stable task identity for user-facing surfaces."""
40
+ role = str(name or "subagent").strip() or "subagent"
41
+ task_label = str(label or "").strip()
42
+ return f"{role} \u00b7 {task_label}" if task_label else role
43
+
44
+
45
+ __all__ = [
46
+ "SUBAGENT_TASK_LABEL_MAX_CHARS",
47
+ "subagent_identity",
48
+ "subagent_task_label",
49
+ ]