polyrob 0.5.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 (745) hide show
  1. agents/README.md +453 -0
  2. agents/__init__.py +164 -0
  3. agents/base_agent.py +743 -0
  4. agents/personality/character.py +119 -0
  5. agents/personality/character_manager.py +309 -0
  6. agents/personality/characters/rob.character.json +56 -0
  7. agents/personality/characters/trump.character.json +63 -0
  8. agents/personality/persona_render.py +78 -0
  9. agents/personality/persona_resolver.py +115 -0
  10. agents/prompt/__init__.py +58 -0
  11. agents/prompt/base_prompt.py +92 -0
  12. agents/prompt/system.py +417 -0
  13. agents/task/README_PATH_MANAGEMENT.md +141 -0
  14. agents/task/__init__.py +162 -0
  15. agents/task/agent/__init__.py +70 -0
  16. agents/task/agent/agent_state.py +408 -0
  17. agents/task/agent/async_delegation.py +274 -0
  18. agents/task/agent/autonomy_state.py +275 -0
  19. agents/task/agent/conversation.py +75 -0
  20. agents/task/agent/core/__init__.py +6 -0
  21. agents/task/agent/core/aux_metering.py +61 -0
  22. agents/task/agent/core/background_review.py +157 -0
  23. agents/task/agent/core/construction.py +1179 -0
  24. agents/task/agent/core/conversational_exit.py +49 -0
  25. agents/task/agent/core/correspondent_gate.py +227 -0
  26. agents/task/agent/core/curator.py +246 -0
  27. agents/task/agent/core/episodic_digest.py +103 -0
  28. agents/task/agent/core/error_recovery.py +480 -0
  29. agents/task/agent/core/history_io.py +257 -0
  30. agents/task/agent/core/llm_provisioning.py +287 -0
  31. agents/task/agent/core/llm_runner.py +547 -0
  32. agents/task/agent/core/logging_io.py +276 -0
  33. agents/task/agent/core/loop_detection.py +262 -0
  34. agents/task/agent/core/memory_prefetch.py +325 -0
  35. agents/task/agent/core/memory_writer.py +667 -0
  36. agents/task/agent/core/model_introspection.py +82 -0
  37. agents/task/agent/core/model_swap.py +177 -0
  38. agents/task/agent/core/next_action_internal.py +1331 -0
  39. agents/task/agent/core/output_validation.py +303 -0
  40. agents/task/agent/core/project_context.py +272 -0
  41. agents/task/agent/core/resources.py +93 -0
  42. agents/task/agent/core/result_processing.py +389 -0
  43. agents/task/agent/core/run_loop.py +661 -0
  44. agents/task/agent/core/safety_lifecycle.py +228 -0
  45. agents/task/agent/core/secret_guard.py +293 -0
  46. agents/task/agent/core/self_wake.py +280 -0
  47. agents/task/agent/core/session_metadata.py +104 -0
  48. agents/task/agent/core/step.py +781 -0
  49. agents/task/agent/core/step_execution.py +398 -0
  50. agents/task/agent/core/step_telemetry.py +222 -0
  51. agents/task/agent/core/test_aux_metering.py +50 -0
  52. agents/task/agent/core/test_billing_failover.py +68 -0
  53. agents/task/agent/core/test_fatal_error_classifier.py +38 -0
  54. agents/task/agent/core/test_judge_validation.py +275 -0
  55. agents/task/agent/core/test_model_swap.py +190 -0
  56. agents/task/agent/core/test_reflection_offload.py +54 -0
  57. agents/task/agent/core/turn_input.py +8 -0
  58. agents/task/agent/core/untrusted_wrap.py +103 -0
  59. agents/task/agent/core/user_ingress.py +228 -0
  60. agents/task/agent/hitl_manager.py +396 -0
  61. agents/task/agent/log_sanitize.py +36 -0
  62. agents/task/agent/message_manager/config.py +97 -0
  63. agents/task/agent/message_manager/service.py +758 -0
  64. agents/task/agent/message_manager/tests.py +305 -0
  65. agents/task/agent/message_manager/tool_call_builder.py +539 -0
  66. agents/task/agent/message_manager/tool_message_repair.py +364 -0
  67. agents/task/agent/message_manager/views.py +143 -0
  68. agents/task/agent/messages/__init__.py +8 -0
  69. agents/task/agent/messages/builders.py +309 -0
  70. agents/task/agent/messages/compactor.py +611 -0
  71. agents/task/agent/messages/context_references.py +271 -0
  72. agents/task/agent/messages/filters.py +333 -0
  73. agents/task/agent/messages/guidance.py +328 -0
  74. agents/task/agent/messages/persistence.py +448 -0
  75. agents/task/agent/messages/retrieval.py +506 -0
  76. agents/task/agent/messages/sqlite_persistence.py +85 -0
  77. agents/task/agent/messages/test_compaction_upgrade.py +349 -0
  78. agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
  79. agents/task/agent/messages/test_sqlite_persistence.py +54 -0
  80. agents/task/agent/messages/token_counter.py +532 -0
  81. agents/task/agent/orchestrator.py +776 -0
  82. agents/task/agent/profile_manager.py +105 -0
  83. agents/task/agent/profile_registry.py +173 -0
  84. agents/task/agent/prompts.py +1090 -0
  85. agents/task/agent/scenario_registry.py +226 -0
  86. agents/task/agent/service.py +509 -0
  87. agents/task/agent/session.py +984 -0
  88. agents/task/agent/skill_discovery.py +110 -0
  89. agents/task/agent/skill_frontmatter.py +94 -0
  90. agents/task/agent/skill_manager.py +1201 -0
  91. agents/task/agent/skill_store.py +627 -0
  92. agents/task/agent/skill_validation.py +56 -0
  93. agents/task/agent/skill_writer.py +492 -0
  94. agents/task/agent/sub_agent_manager.py +1137 -0
  95. agents/task/agent/test_session_disk_recovery.py +34 -0
  96. agents/task/agent/tests.py +513 -0
  97. agents/task/agent/tool_call_tracker.py +511 -0
  98. agents/task/agent/views.py +469 -0
  99. agents/task/config.py +432 -0
  100. agents/task/constants.py +1384 -0
  101. agents/task/flag_defaults.py +63 -0
  102. agents/task/goals/__init__.py +1 -0
  103. agents/task/goals/autonomy_marker.py +28 -0
  104. agents/task/goals/board.py +690 -0
  105. agents/task/goals/completion_judge.py +247 -0
  106. agents/task/goals/context.py +62 -0
  107. agents/task/goals/dispatcher.py +848 -0
  108. agents/task/goals/escalation.py +102 -0
  109. agents/task/goals/planner.py +114 -0
  110. agents/task/logging_config.py +155 -0
  111. agents/task/path.py +991 -0
  112. agents/task/robust_parse_config.py +400 -0
  113. agents/task/runtime/__init__.py +0 -0
  114. agents/task/runtime/run_as_session.py +130 -0
  115. agents/task/runtime_safety.py +308 -0
  116. agents/task/session/__init__.py +5 -0
  117. agents/task/session/browser_pool.py +130 -0
  118. agents/task/session/cleanup.py +612 -0
  119. agents/task/session/execution.py +397 -0
  120. agents/task/session/feed.py +200 -0
  121. agents/task/session/hitl_ingress.py +201 -0
  122. agents/task/session/hooks.py +83 -0
  123. agents/task/session/multi_agent.py +243 -0
  124. agents/task/session/workspace.py +91 -0
  125. agents/task/session_registry.py +91 -0
  126. agents/task/session_route.py +35 -0
  127. agents/task/sqlite_session_registry.py +261 -0
  128. agents/task/surface_config.py +185 -0
  129. agents/task/telemetry/__init__.py +58 -0
  130. agents/task/telemetry/event_log.py +180 -0
  131. agents/task/telemetry/formatters.py +642 -0
  132. agents/task/telemetry/manager.py +571 -0
  133. agents/task/telemetry/memory_events.py +66 -0
  134. agents/task/telemetry/self_events.py +53 -0
  135. agents/task/telemetry/sequence.py +160 -0
  136. agents/task/telemetry/service.py +1265 -0
  137. agents/task/telemetry/views.py +882 -0
  138. agents/task/templates.py +98 -0
  139. agents/task/test_aux_chain.py +151 -0
  140. agents/task/test_aux_router.py +41 -0
  141. agents/task/tests/test_parse_robustness.py +170 -0
  142. agents/task/tool_defaults.py +110 -0
  143. agents/task/utils.py +762 -0
  144. agents/task/utils_json.py +768 -0
  145. agents/task/utils_webview.py +86 -0
  146. agents/task/workspace_context.py +229 -0
  147. agents/task_agent_lite.py +2398 -0
  148. api/README.md +683 -0
  149. api/TASK_API_DOCS.md +151 -0
  150. api/__init__.py +0 -0
  151. api/a2a/__init__.py +37 -0
  152. api/a2a/agent_card.py +337 -0
  153. api/a2a/client.py +657 -0
  154. api/a2a/endpoints.py +375 -0
  155. api/a2a/models.py +268 -0
  156. api/a2a/streaming.py +419 -0
  157. api/a2a/task_handler.py +929 -0
  158. api/admin_endpoints.py +1584 -0
  159. api/app.py +923 -0
  160. api/auth_constants.py +28 -0
  161. api/auth_endpoints.py +402 -0
  162. api/auth_state.py +36 -0
  163. api/chat_via_task.py +42 -0
  164. api/conversation_manager.py +231 -0
  165. api/dependencies.py +186 -0
  166. api/eip8004_endpoints.py +452 -0
  167. api/hyperliquid_models.py +174 -0
  168. api/hyperliquid_routes.py +450 -0
  169. api/interfaces.py +207 -0
  170. api/jwt_middleware.py +83 -0
  171. api/kb/__init__.py +1 -0
  172. api/kb/endpoints.py +314 -0
  173. api/mcp_models.py +279 -0
  174. api/mcp_routes.py +438 -0
  175. api/middleware.py +526 -0
  176. api/models.py +176 -0
  177. api/openai_compat/__init__.py +0 -0
  178. api/openai_compat/model_map.py +25 -0
  179. api/openai_compat/models.py +59 -0
  180. api/openai_compat/router.py +140 -0
  181. api/payment_endpoints.py +444 -0
  182. api/payment_verification.py +138 -0
  183. api/polymarket_models.py +155 -0
  184. api/polymarket_routes.py +501 -0
  185. api/pricing_endpoints.py +89 -0
  186. api/session_routing.py +54 -0
  187. api/skill_endpoints.py +453 -0
  188. api/task_http_api.py +1856 -0
  189. api/webhooks.py +52 -0
  190. api/x402_endpoints.py +280 -0
  191. avatar/README.md +44 -0
  192. avatar/__init__.py +1 -0
  193. avatar/config/rob.json +7 -0
  194. avatar/mindprint.js +395 -0
  195. avatar/renders/rob.meta.json +20 -0
  196. avatar/renders/rob.png +0 -0
  197. avatar/studio.html +373 -0
  198. avatar/webview/avatar-live.js +44 -0
  199. cli/README.md +89 -0
  200. cli/__init__.py +0 -0
  201. cli/cli_surface.py +67 -0
  202. cli/commands/__init__.py +0 -0
  203. cli/commands/_bootstrap.py +49 -0
  204. cli/commands/_errors.py +53 -0
  205. cli/commands/chat.py +973 -0
  206. cli/commands/config.py +100 -0
  207. cli/commands/dashboard.py +103 -0
  208. cli/commands/doctor.py +222 -0
  209. cli/commands/email.py +174 -0
  210. cli/commands/gateway.py +346 -0
  211. cli/commands/goals.py +509 -0
  212. cli/commands/init.py +276 -0
  213. cli/commands/journey.py +30 -0
  214. cli/commands/kb.py +280 -0
  215. cli/commands/model.py +107 -0
  216. cli/commands/owner.py +431 -0
  217. cli/commands/pfp.py +232 -0
  218. cli/commands/run.py +414 -0
  219. cli/commands/serve.py +41 -0
  220. cli/commands/session.py +684 -0
  221. cli/commands/skill_install.py +786 -0
  222. cli/commands/skills.py +128 -0
  223. cli/commands/subagents.py +100 -0
  224. cli/commands/surface.py +81 -0
  225. cli/commands/telegram.py +185 -0
  226. cli/commands/todos.py +209 -0
  227. cli/commands/tools.py +102 -0
  228. cli/commands/update.py +316 -0
  229. cli/commands/whatsapp.py +175 -0
  230. cli/config_store.py +276 -0
  231. cli/gitignore.py +43 -0
  232. cli/inventory.py +46 -0
  233. cli/keys.py +101 -0
  234. cli/persona.py +17 -0
  235. cli/polyrob.py +137 -0
  236. cli/ui/__init__.py +64 -0
  237. cli/ui/activity.py +141 -0
  238. cli/ui/app.py +538 -0
  239. cli/ui/banner.py +233 -0
  240. cli/ui/blocks.py +534 -0
  241. cli/ui/bootstrap_notice.py +47 -0
  242. cli/ui/commands/__init__.py +41 -0
  243. cli/ui/commands/h_cron.py +64 -0
  244. cli/ui/commands/h_journey.py +164 -0
  245. cli/ui/commands/h_kb.py +121 -0
  246. cli/ui/commands/h_learn.py +100 -0
  247. cli/ui/commands/h_mcp.py +215 -0
  248. cli/ui/commands/h_self.py +113 -0
  249. cli/ui/commands/h_skills.py +314 -0
  250. cli/ui/commands/handlers.py +1672 -0
  251. cli/ui/commands/registry.py +426 -0
  252. cli/ui/dialog.py +425 -0
  253. cli/ui/event_registry.py +110 -0
  254. cli/ui/event_specs.py +45 -0
  255. cli/ui/events.py +433 -0
  256. cli/ui/identity.py +15 -0
  257. cli/ui/lifecycle.py +159 -0
  258. cli/ui/live_hooks.py +43 -0
  259. cli/ui/model_selector.py +532 -0
  260. cli/ui/persistent_loop.py +181 -0
  261. cli/ui/pick.py +113 -0
  262. cli/ui/plain_renderer.py +351 -0
  263. cli/ui/renderer.py +384 -0
  264. cli/ui/rich_renderer.py +533 -0
  265. cli/ui/secrets.py +115 -0
  266. cli/ui/state.py +376 -0
  267. cli/ui/statusbar.py +231 -0
  268. cli/ui/streaming.py +254 -0
  269. cli/ui/terminal_render.py +105 -0
  270. cli/ui/theme.py +132 -0
  271. cli/update/__init__.py +6 -0
  272. cli/update/context.py +121 -0
  273. cli/update/detect.py +176 -0
  274. cli/update/engine.py +95 -0
  275. cli/update/migrate_guarded.py +55 -0
  276. cli/update/process_guard.py +215 -0
  277. cli/update/runners.py +78 -0
  278. cli/update/snapshot.py +270 -0
  279. cli/update/versions.py +205 -0
  280. core/README.md +377 -0
  281. core/__init__.py +174 -0
  282. core/assets.py +87 -0
  283. core/async_bridge.py +76 -0
  284. core/autonomy_runtime.py +259 -0
  285. core/base_component.py +302 -0
  286. core/bootstrap.py +681 -0
  287. core/bot.py +310 -0
  288. core/config.py +833 -0
  289. core/constants.py +126 -0
  290. core/container.py +462 -0
  291. core/db_manifest.py +101 -0
  292. core/embedding.py +57 -0
  293. core/env.py +39 -0
  294. core/exceptions.py +287 -0
  295. core/flags.py +144 -0
  296. core/flags_catalog.py +345 -0
  297. core/home_migration.py +62 -0
  298. core/identity.py +118 -0
  299. core/initialization.py +1004 -0
  300. core/instance.py +537 -0
  301. core/interactive_gate.py +96 -0
  302. core/logging.py +436 -0
  303. core/owner_doc_writer.py +176 -0
  304. core/pairing.py +193 -0
  305. core/path_safety.py +20 -0
  306. core/paths.py +27 -0
  307. core/payment_config.py +21 -0
  308. core/permissions.py +322 -0
  309. core/runtime_config.py +96 -0
  310. core/runtime_paths.py +135 -0
  311. core/seams.py +35 -0
  312. core/secret_scan.py +25 -0
  313. core/secret_scrub.py +71 -0
  314. core/secrets.py +35 -0
  315. core/security_logging_filter.py +148 -0
  316. core/self_context_writer.py +333 -0
  317. core/self_evolution.py +294 -0
  318. core/session_context.py +47 -0
  319. core/sqlite_util.py +59 -0
  320. core/surfaces/__init__.py +16 -0
  321. core/surfaces/access.py +118 -0
  322. core/surfaces/binding.py +88 -0
  323. core/surfaces/bootstrap.py +78 -0
  324. core/surfaces/circuit.py +168 -0
  325. core/surfaces/continuity.py +77 -0
  326. core/surfaces/correspondents.py +228 -0
  327. core/surfaces/dispatcher.py +216 -0
  328. core/surfaces/envelopes.py +75 -0
  329. core/surfaces/gc.py +30 -0
  330. core/surfaces/idempotency.py +57 -0
  331. core/surfaces/inbound_webhook.py +124 -0
  332. core/surfaces/media.py +32 -0
  333. core/surfaces/message_router.py +91 -0
  334. core/surfaces/outbound_allowlist.py +80 -0
  335. core/surfaces/outbound_dispatcher.py +110 -0
  336. core/surfaces/outbound_mirror.py +37 -0
  337. core/surfaces/outbound_queue.py +107 -0
  338. core/surfaces/outbound_target.py +12 -0
  339. core/surfaces/owner_admin.py +29 -0
  340. core/surfaces/proactive.py +78 -0
  341. core/surfaces/progress.py +108 -0
  342. core/surfaces/rate_bucket.py +21 -0
  343. core/surfaces/registry.py +51 -0
  344. core/surfaces/rendering.py +53 -0
  345. core/surfaces/send_policy.py +14 -0
  346. core/surfaces/serialize.py +24 -0
  347. core/surfaces/session_chat_registry.py +112 -0
  348. core/surfaces/session_policy.py +60 -0
  349. core/surfaces/surface.py +288 -0
  350. core/surfaces/transcription.py +88 -0
  351. core/surfaces/voice_echo.py +26 -0
  352. core/surfaces/voice_guard.py +23 -0
  353. core/tickers.py +174 -0
  354. core/tool_catalog.py +192 -0
  355. core/version.py +75 -0
  356. core/wallet/__init__.py +5 -0
  357. core/wallet/agent_wallet.py +59 -0
  358. core/wallet/audit_sink.py +61 -0
  359. core/wallet/config.py +70 -0
  360. core/wallet/factory.py +87 -0
  361. core/wallet/policy.py +104 -0
  362. core/wallet/signer.py +51 -0
  363. cron/README.md +65 -0
  364. cron/__init__.py +0 -0
  365. cron/delivery.py +277 -0
  366. cron/digest.py +124 -0
  367. cron/jobs.py +207 -0
  368. cron/runner.py +297 -0
  369. cron/schedule.py +194 -0
  370. cron/scheduler.py +201 -0
  371. cron/service.py +57 -0
  372. cron/wake_gate.py +223 -0
  373. data/__init__.py +1 -0
  374. data/prompts/autov2_prompts.json +87 -0
  375. data/prompts/skills/browser-automation/SKILL.md +43 -0
  376. data/prompts/skills/coding-workflow/SKILL.md +36 -0
  377. data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
  378. data/prompts/skills/document-writing/SKILL.md +33 -0
  379. data/prompts/skills/email-comms/SKILL.md +33 -0
  380. data/prompts/skills/file-data-ops/SKILL.md +35 -0
  381. data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
  382. data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
  383. data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
  384. data/prompts/skills/lead-research/SKILL.md +86 -0
  385. data/prompts/skills/market-research-brief/SKILL.md +104 -0
  386. data/prompts/skills/person-analyzer/SKILL.md +124 -0
  387. data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
  388. data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
  389. data/prompts/skills/polymarket-trading/SKILL.md +55 -0
  390. data/prompts/skills/presentation-creator/SKILL.md +36 -0
  391. data/prompts/skills/project-analyzer/SKILL.md +169 -0
  392. data/prompts/skills/rules.json +268 -0
  393. data/prompts/skills/secret-handling/SKILL.md +20 -0
  394. data/prompts/skills/skill-authoring/SKILL.md +31 -0
  395. data/prompts/skills/skill-security-review/SKILL.md +27 -0
  396. data/prompts/skills/social-discovery/SKILL.md +57 -0
  397. data/prompts/skills/task-planning/SKILL.md +33 -0
  398. data/prompts/skills/web-research/SKILL.md +39 -0
  399. data/prompts/skills/web-scraping/SKILL.md +73 -0
  400. data/prompts/skills/x-engagement/SKILL.md +69 -0
  401. data/prompts/system_prompts.json +4 -0
  402. data/subscription_channels.json +5 -0
  403. modules/README.md +503 -0
  404. modules/__init__.py +124 -0
  405. modules/auth/__init__.py +13 -0
  406. modules/auth/api_key_manager.py +157 -0
  407. modules/auth/identity_mapper.py +380 -0
  408. modules/auth/siwe_auth.py +273 -0
  409. modules/auth/tier_manager.py +173 -0
  410. modules/base_module.py +190 -0
  411. modules/credits/__init__.py +29 -0
  412. modules/credits/balance_manager.py +237 -0
  413. modules/credits/cost_utils.py +129 -0
  414. modules/credits/pricing.py +139 -0
  415. modules/credits/unified_ledger.py +148 -0
  416. modules/credits/usage_meter.py +310 -0
  417. modules/credits/usage_tracker.py +674 -0
  418. modules/database/__init__.py +33 -0
  419. modules/database/audit_log.py +322 -0
  420. modules/database/auth_tables.py +276 -0
  421. modules/database/connection.py +644 -0
  422. modules/database/connection_pool.py +305 -0
  423. modules/database/conversation_contexts.py +446 -0
  424. modules/database/database_manager.py +237 -0
  425. modules/database/hyperliquid.py +375 -0
  426. modules/database/polymarket.py +574 -0
  427. modules/database/user_mcp_servers.py +728 -0
  428. modules/database/user_profiles.py +562 -0
  429. modules/database/utils.py +107 -0
  430. modules/database/x402_tables.py +92 -0
  431. modules/eip8004/README.md +1069 -0
  432. modules/eip8004/__init__.py +65 -0
  433. modules/eip8004/contracts.py +498 -0
  434. modules/eip8004/models.py +271 -0
  435. modules/eip8004/registration.py +148 -0
  436. modules/eip8004/reputation.py +367 -0
  437. modules/eip8004/validation.py +277 -0
  438. modules/llm/__init__.py +97 -0
  439. modules/llm/adapters.py +963 -0
  440. modules/llm/anthropic_client.py +831 -0
  441. modules/llm/available_models.py +106 -0
  442. modules/llm/brain_scrubber.py +137 -0
  443. modules/llm/cache_hints.py +162 -0
  444. modules/llm/deepseek_client.py +562 -0
  445. modules/llm/gemini_client.py +1367 -0
  446. modules/llm/llm_client.py +1139 -0
  447. modules/llm/llm_client_registry.py +188 -0
  448. modules/llm/llm_factory.py +237 -0
  449. modules/llm/llm_manager.py +1036 -0
  450. modules/llm/messages.py +374 -0
  451. modules/llm/model_registry.py +1935 -0
  452. modules/llm/nvidia_client.py +125 -0
  453. modules/llm/openai_client.py +800 -0
  454. modules/llm/openrouter_client.py +969 -0
  455. modules/llm/profiles.py +178 -0
  456. modules/llm/test_anthropic_cache_capture.py +35 -0
  457. modules/llm/test_cached_pricing_derivation.py +24 -0
  458. modules/llm/think_scrubber.py +444 -0
  459. modules/llm/token_counter.py +268 -0
  460. modules/memory/__init__.py +35 -0
  461. modules/memory/backend_factory.py +83 -0
  462. modules/memory/cache_manager.py +186 -0
  463. modules/memory/episodic.py +108 -0
  464. modules/memory/local_vector_memory_provider.py +552 -0
  465. modules/memory/memory_manager.py +225 -0
  466. modules/memory/models.py +312 -0
  467. modules/memory/provider.py +210 -0
  468. modules/memory/registry.py +205 -0
  469. modules/memory/sqlite_memory_provider.py +767 -0
  470. modules/memory/task/__init__.py +52 -0
  471. modules/memory/task/compaction_manager.py +157 -0
  472. modules/memory/task/context_retriever.py +809 -0
  473. modules/memory/task/hierarchical_memory.py +1002 -0
  474. modules/memory/task/lexical_retriever.py +71 -0
  475. modules/memory/task/null_context_manager.py +213 -0
  476. modules/memory/task/phase_manager.py +625 -0
  477. modules/memory/task/reflection_service.py +157 -0
  478. modules/memory/task/semantic_retriever.py +218 -0
  479. modules/memory/task/task_context_manager.py +1165 -0
  480. modules/memory/task/test_forgetting_engages.py +24 -0
  481. modules/memory/task/test_reflection_llm.py +39 -0
  482. modules/memory/task/test_threat_scan.py +53 -0
  483. modules/memory/task/threat_scan.py +90 -0
  484. modules/memory/test_sqlite_memory_provider.py +106 -0
  485. modules/memory/user_profile_manager.py +527 -0
  486. modules/payments/__init__.py +11 -0
  487. modules/payments/deposit_monitor.py +478 -0
  488. modules/payments/price_oracle.py +70 -0
  489. modules/payments/treasury_sweeper.py +401 -0
  490. modules/payments/wallet_generator.py +145 -0
  491. modules/pfp/__init__.py +1 -0
  492. modules/pfp/config.py +92 -0
  493. modules/pfp/mesh.py +565 -0
  494. modules/pfp/push.py +69 -0
  495. modules/pfp/renderer.py +85 -0
  496. modules/pfp/store.py +82 -0
  497. modules/skills/__init__.py +1 -0
  498. modules/skills/skill_usage.py +248 -0
  499. modules/transcription/__init__.py +24 -0
  500. modules/transcription/base.py +20 -0
  501. modules/transcription/faster_whisper_transcriber.py +55 -0
  502. modules/x402/README.md +249 -0
  503. modules/x402/__init__.py +22 -0
  504. modules/x402/invoicing.py +416 -0
  505. modules/x402/middleware.py +364 -0
  506. modules/x402/settlement_watcher.py +145 -0
  507. modules/x402/x402_integration.py +344 -0
  508. polyrob-0.5.0.dist-info/METADATA +482 -0
  509. polyrob-0.5.0.dist-info/RECORD +745 -0
  510. polyrob-0.5.0.dist-info/WHEEL +5 -0
  511. polyrob-0.5.0.dist-info/entry_points.txt +2 -0
  512. polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
  513. polyrob-0.5.0.dist-info/top_level.txt +12 -0
  514. surfaces/README.md +70 -0
  515. surfaces/__init__.py +0 -0
  516. surfaces/email/__init__.py +0 -0
  517. surfaces/email/dedup.py +44 -0
  518. surfaces/email/harness.py +187 -0
  519. surfaces/email/inbound.py +159 -0
  520. surfaces/email/seed.py +60 -0
  521. surfaces/email/surface.py +105 -0
  522. surfaces/telegram/__init__.py +0 -0
  523. surfaces/telegram/dedup.py +83 -0
  524. surfaces/telegram/harness.py +886 -0
  525. surfaces/telegram/inbound.py +145 -0
  526. surfaces/telegram/interactive_tools.py +49 -0
  527. surfaces/telegram/markdown.py +347 -0
  528. surfaces/telegram/rate_limit.py +167 -0
  529. surfaces/telegram/surface.py +187 -0
  530. surfaces/telegram/voice.py +52 -0
  531. surfaces/whatsapp/__init__.py +0 -0
  532. surfaces/whatsapp/client.py +51 -0
  533. surfaces/whatsapp/harness.py +62 -0
  534. surfaces/whatsapp/inbound.py +132 -0
  535. surfaces/whatsapp/surface.py +64 -0
  536. surfaces/whatsapp/window.py +34 -0
  537. tools/README.md +578 -0
  538. tools/__init__.py +424 -0
  539. tools/alchemy/__init__.py +5 -0
  540. tools/alchemy/alchemy_tool.py +389 -0
  541. tools/anysite/__init__.py +17 -0
  542. tools/anysite/client.py +88 -0
  543. tools/anysite/tool.py +89 -0
  544. tools/base_tool.py +468 -0
  545. tools/browser/__init__.py +22 -0
  546. tools/browser/actions.py +118 -0
  547. tools/browser/browser.py +1457 -0
  548. tools/browser/browser_manager.py +690 -0
  549. tools/browser/context.py +1748 -0
  550. tools/browser/playwright_utils.py +137 -0
  551. tools/browser/views.py +56 -0
  552. tools/code_exec/SANDBOX_SECURITY.md +144 -0
  553. tools/code_exec/__init__.py +178 -0
  554. tools/code_exec/backend.py +73 -0
  555. tools/code_exec/backends/__init__.py +1 -0
  556. tools/code_exec/backends/docker.py +779 -0
  557. tools/code_exec/backends/local_subprocess.py +154 -0
  558. tools/code_exec/env_policy.py +45 -0
  559. tools/code_exec/result.py +42 -0
  560. tools/code_exec/sandbox_guard.py +59 -0
  561. tools/code_exec/tool.py +227 -0
  562. tools/coding/__init__.py +50 -0
  563. tools/coding/edit.py +107 -0
  564. tools/coding/search.py +113 -0
  565. tools/coding/tool.py +353 -0
  566. tools/collabland/__init__.py +5 -0
  567. tools/collabland/collabland_tool.py +471 -0
  568. tools/controller/__init__.py +0 -0
  569. tools/controller/_helpers.py +165 -0
  570. tools/controller/action_registration.py +1765 -0
  571. tools/controller/approval.py +244 -0
  572. tools/controller/approval_interactive.py +90 -0
  573. tools/controller/delegation.py +195 -0
  574. tools/controller/execution.py +797 -0
  575. tools/controller/execution_context.py +141 -0
  576. tools/controller/hooks.py +135 -0
  577. tools/controller/introspection.py +344 -0
  578. tools/controller/mcp_registrar.py +190 -0
  579. tools/controller/message_send.py +31 -0
  580. tools/controller/registry/__init__.py +4 -0
  581. tools/controller/registry/schema_generators.py +395 -0
  582. tools/controller/registry/schema_sanitizer.py +406 -0
  583. tools/controller/registry/service.py +1266 -0
  584. tools/controller/registry/views.py +151 -0
  585. tools/controller/service.py +409 -0
  586. tools/controller/tool_management.py +334 -0
  587. tools/controller/types.py +28 -0
  588. tools/controller/views.py +450 -0
  589. tools/cronjob_tools.py +166 -0
  590. tools/crypto_trade_gate.py +59 -0
  591. tools/descriptors.py +452 -0
  592. tools/dom/__init__.py +0 -0
  593. tools/dom/history_tree_processor/service.py +244 -0
  594. tools/dom/history_tree_processor/view.py +99 -0
  595. tools/dom/service.py +471 -0
  596. tools/dom/views.py +346 -0
  597. tools/email_tool.py +403 -0
  598. tools/exceptions.py +169 -0
  599. tools/filesystem.py +1038 -0
  600. tools/filesystem_docproc.py +331 -0
  601. tools/filesystem_pdf.py +755 -0
  602. tools/git/__init__.py +42 -0
  603. tools/git/tool.py +258 -0
  604. tools/github/__init__.py +33 -0
  605. tools/github/client.py +94 -0
  606. tools/github/tool.py +237 -0
  607. tools/goal_tools.py +309 -0
  608. tools/hyperliquid/__init__.py +56 -0
  609. tools/hyperliquid/models.py +233 -0
  610. tools/hyperliquid/service.py +1680 -0
  611. tools/knowledge_ingest.py +950 -0
  612. tools/mcp/README.md +535 -0
  613. tools/mcp/__init__.py +52 -0
  614. tools/mcp/catalog.py +115 -0
  615. tools/mcp/config.py +337 -0
  616. tools/mcp/mcp_tool.py +1415 -0
  617. tools/mcp/param_coercion.py +289 -0
  618. tools/mcp/protocol.py +1520 -0
  619. tools/mcp/rate_limit.py +50 -0
  620. tools/mcp/security.py +424 -0
  621. tools/mcp/self_install.py +111 -0
  622. tools/mcp/server_manager.py +829 -0
  623. tools/mcp/subscriptions.py +72 -0
  624. tools/mcp/user_mcp_service.py +899 -0
  625. tools/mcp/validation_tracker.py +89 -0
  626. tools/mcp/views.py +346 -0
  627. tools/oauth/__init__.py +17 -0
  628. tools/oauth/manager.py +75 -0
  629. tools/oauth/provider.py +69 -0
  630. tools/oauth/providers/__init__.py +1 -0
  631. tools/oauth/providers/generic_oauth2.py +96 -0
  632. tools/perplexity_tool.py +280 -0
  633. tools/polymarket/__init__.py +41 -0
  634. tools/polymarket/clob_adapter.py +76 -0
  635. tools/polymarket/models.py +182 -0
  636. tools/polymarket/service.py +2082 -0
  637. tools/self_env/__init__.py +61 -0
  638. tools/self_env/tool.py +274 -0
  639. tools/shell/__init__.py +80 -0
  640. tools/shell/backend_pool.py +118 -0
  641. tools/shell/discipline.py +62 -0
  642. tools/shell/executor.py +157 -0
  643. tools/shell/loopback_allow.py +87 -0
  644. tools/shell/process_registry.py +113 -0
  645. tools/shell/process_tool.py +131 -0
  646. tools/shell/state.py +129 -0
  647. tools/shell/tool.py +151 -0
  648. tools/task_tool.py +637 -0
  649. tools/twitter_tool.py +2086 -0
  650. tools/user_directory.py +179 -0
  651. tools/web_fetch/__init__.py +3 -0
  652. tools/web_fetch/fetcher.py +144 -0
  653. tools/web_fetch/render.py +44 -0
  654. tools/web_fetch/tool.py +60 -0
  655. tools/x402/__init__.py +60 -0
  656. tools/x402/client.py +47 -0
  657. tools/x402/invoice_tool.py +143 -0
  658. tools/x402/real_client.py +250 -0
  659. tools/x402/service.py +131 -0
  660. utils/README.md +498 -0
  661. utils/__init__.py +46 -0
  662. utils/auth_utils.py +114 -0
  663. utils/bounded_collections.py +162 -0
  664. utils/circuit_breaker.py +389 -0
  665. utils/gif_utils.py +435 -0
  666. utils/markdown_utils.py +291 -0
  667. utils/message_utils.py +147 -0
  668. utils/metrics.py +36 -0
  669. utils/path_validator.py +227 -0
  670. utils/rate_limit_manager.py +399 -0
  671. utils/result_size.py +296 -0
  672. utils/time_utils.py +206 -0
  673. utils/user_utils.py +188 -0
  674. webview/README.md +111 -0
  675. webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
  676. webview/__init__.py +8 -0
  677. webview/activity.py +636 -0
  678. webview/owner_auth.py +108 -0
  679. webview/pages.py +448 -0
  680. webview/repair_sessions.py +399 -0
  681. webview/server.py +4323 -0
  682. webview/server_launcher.py +112 -0
  683. webview/static/css/activity.css +89 -0
  684. webview/static/css/chat.css +1772 -0
  685. webview/static/css/components.css +1190 -0
  686. webview/static/css/config-panel.css +651 -0
  687. webview/static/css/pages/admin.css +766 -0
  688. webview/static/css/pages/profile.css +231 -0
  689. webview/static/css/pages/settings.css +297 -0
  690. webview/static/css/pages/signin.css +316 -0
  691. webview/static/css/style.css +4250 -0
  692. webview/static/css/variables.css +147 -0
  693. webview/static/css/workspace-fullwidth-fix.css +109 -0
  694. webview/static/img/favicon.ico +0 -0
  695. webview/static/js/activity.js +394 -0
  696. webview/static/js/admin/activity.js +186 -0
  697. webview/static/js/admin/dashboard.js +163 -0
  698. webview/static/js/admin/user_detail.js +400 -0
  699. webview/static/js/admin/users.js +174 -0
  700. webview/static/js/admin/utils.js +214 -0
  701. webview/static/js/chat.js +3178 -0
  702. webview/static/js/config-panel.js +522 -0
  703. webview/static/js/constants.js +20 -0
  704. webview/static/js/error-handler.js +226 -0
  705. webview/static/js/ethers.min.js +1 -0
  706. webview/static/js/event-filter.js +137 -0
  707. webview/static/js/event-store.js +351 -0
  708. webview/static/js/file-attachments.js +374 -0
  709. webview/static/js/file-loader.js +163 -0
  710. webview/static/js/index.js +103 -0
  711. webview/static/js/performance-utils.js +196 -0
  712. webview/static/js/profile.js +396 -0
  713. webview/static/js/screenshot.js +386 -0
  714. webview/static/js/session.js +1527 -0
  715. webview/static/js/settings.js +1288 -0
  716. webview/static/js/sidebar-data.js +766 -0
  717. webview/static/js/sidebar-toggle.js +296 -0
  718. webview/static/js/socket.io.min.js +7 -0
  719. webview/static/js/stats.js +355 -0
  720. webview/static/js/ui-utils.js +420 -0
  721. webview/static/js/workspace.js +966 -0
  722. webview/stats_service.py +613 -0
  723. webview/templates/__init__.py +1 -0
  724. webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  725. webview/templates/activity.html +36 -0
  726. webview/templates/admin/activity.html +122 -0
  727. webview/templates/admin/dashboard.html +112 -0
  728. webview/templates/admin/user_detail.html +228 -0
  729. webview/templates/admin/users.html +105 -0
  730. webview/templates/autonomy.html +60 -0
  731. webview/templates/error.html +133 -0
  732. webview/templates/finance.html +90 -0
  733. webview/templates/identity.html +73 -0
  734. webview/templates/index.html +42 -0
  735. webview/templates/layout.html +125 -0
  736. webview/templates/memory.html +59 -0
  737. webview/templates/owner_login.html +15 -0
  738. webview/templates/profile.html +121 -0
  739. webview/templates/session.html +312 -0
  740. webview/templates/settings.html +534 -0
  741. webview/templates/sidebar.html +112 -0
  742. webview/templates/signin.html +213 -0
  743. webview/templates/status.html +14 -0
  744. webview/templates/system.html +45 -0
  745. webview/webgate.py +196 -0
@@ -0,0 +1,349 @@
1
+ """Tests for the Reference-parity compaction upgrade (Tiers A/B/C).
2
+
3
+ See docs/REFERENCE_VS_ROB_CONTEXT_SYSTEM_2026-06.md §7 (test plan) and §9 (patches).
4
+
5
+ These tests are deliberately light: a small harness composes CompactorMixin against
6
+ a real MessageHistory and a stub LLM, so the compaction logic is exercised with no
7
+ network and no browser state.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import logging
13
+
14
+ import pytest
15
+
16
+ from modules.llm.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage
17
+ from agents.task.agent.messages.compactor import CompactorMixin
18
+ from agents.task.agent.message_manager.views import (
19
+ ManagedMessage,
20
+ MessageHistory,
21
+ MessageMetadata,
22
+ )
23
+
24
+
25
+ # --------------------------------------------------------------------------- #
26
+ # Test harness
27
+ # --------------------------------------------------------------------------- #
28
+ class _StubLLM:
29
+ """Records calls; echoes a configurable reply (or raises)."""
30
+
31
+ def __init__(self, reply: str = "STUB SUMMARY", fail: bool = False):
32
+ self.reply = reply
33
+ self.fail = fail
34
+ self.calls: list = []
35
+
36
+ async def ainvoke(self, messages):
37
+ self.calls.append(messages)
38
+ if self.fail:
39
+ raise RuntimeError("aux llm down")
40
+ return AIMessage(content=self.reply)
41
+
42
+
43
+ class _Harness(CompactorMixin):
44
+ """Minimal MessageManager stand-in providing exactly what the mixin needs."""
45
+
46
+ def __init__(self, llm=None, aux_llm=None, max_input_tokens: int = 1000):
47
+ self.logger = logging.getLogger("test_compaction_upgrade")
48
+ self.history = MessageHistory()
49
+ self.llm = llm or _StubLLM()
50
+ if aux_llm is not None:
51
+ self.aux_llm = aux_llm
52
+ self.max_input_tokens = max_input_tokens
53
+ self._usage = 90.0
54
+
55
+ def _add_message_with_tokens(self, message, _internal: bool = False):
56
+ tokens = max(1, len(str(message.content)) // 4)
57
+ self.history.messages.append(
58
+ ManagedMessage(message=message, metadata=MessageMetadata(input_tokens=tokens))
59
+ )
60
+ self.history.total_tokens += tokens
61
+
62
+ def get_context_usage_percent(self) -> float:
63
+ return self._usage
64
+
65
+
66
+ def _fill(h: _Harness, n: int, size: int = 4000):
67
+ """Add n large messages so the token-budget tail (C3) floors at min_keep=10."""
68
+ for i in range(n):
69
+ h._add_message_with_tokens(HumanMessage(content=("x" * size) + f"-m{i}"))
70
+
71
+
72
+ # --------------------------------------------------------------------------- #
73
+ # Tier A — compaction payload fixes
74
+ # --------------------------------------------------------------------------- #
75
+ def test_a1_oldest_middle_messages_reach_the_summarizer():
76
+ """Defect A: the oldest messages must not be silently dropped (was [-50:])."""
77
+ h = _Harness()
78
+ msgs = [HumanMessage(content=f"MARK-{i}-zzz") for i in range(200)]
79
+ prompt = h._build_compaction_prompt(msgs)
80
+ assert "MARK-0-zzz" in prompt, "oldest message was dropped before summarization"
81
+ assert "MARK-199-zzz" in prompt
82
+
83
+
84
+ def test_a2_tool_results_are_serialized_not_a_literal():
85
+ """Defect B (part 1): tool output content must reach the summary input."""
86
+ h = _Harness()
87
+ payload = "TOOLDATA_NEEDLE_" + ("z" * 200)
88
+ prompt = h._build_compaction_prompt([ToolMessage(content=payload, tool_call_id="c1")])
89
+ assert "TOOLDATA_NEEDLE_" in prompt, "tool result reduced to a bare marker"
90
+
91
+
92
+ def test_a2_message_not_hard_truncated_at_500_chars():
93
+ """Defect B (part 2): content past 500 chars must survive."""
94
+ h = _Harness()
95
+ content = ("a" * 600) + "NEEDLE_PAST_500"
96
+ prompt = h._build_compaction_prompt([HumanMessage(content=content)])
97
+ assert "NEEDLE_PAST_500" in prompt
98
+
99
+
100
+ def test_a3_structured_template_has_sections():
101
+ h = _Harness()
102
+ prompt = h._build_compaction_prompt([HumanMessage(content="hi")] * 6)
103
+ for section in (
104
+ "## Active Task",
105
+ "## In Progress",
106
+ "## Resolved Questions",
107
+ "## Pending User Asks",
108
+ "## Remaining Work",
109
+ ):
110
+ assert section in prompt, f"missing template section {section!r}"
111
+
112
+
113
+ def test_a4_prior_summary_is_fed_back_for_update():
114
+ h = _Harness()
115
+ prompt = h._build_compaction_prompt(
116
+ [HumanMessage(content="hi")], prior_summary="PRIOR_SUMMARY_BODY_123"
117
+ )
118
+ assert "PRIOR_SUMMARY_BODY_123" in prompt
119
+ assert "PRIOR SUMMARY" in prompt
120
+
121
+
122
+ def test_a4_existing_compacted_block_detected_and_not_re_summarized():
123
+ """An existing [COMPACTED SESSION HISTORY] in the middle is fed back, not re-summarized."""
124
+ h = _Harness(llm=_StubLLM(reply="NEW SUMMARY"))
125
+ h._add_message_with_tokens(
126
+ HumanMessage(content="[COMPACTED SESSION HISTORY]\n\nOLD_SUMMARY_TEXT\n\n[END]")
127
+ )
128
+ _fill(h, 19)
129
+ asyncio.run(h.llm_compact_history())
130
+ sent_prompt = h.llm.calls[0][0].content
131
+ assert "OLD_SUMMARY_TEXT" in sent_prompt
132
+ assert "PRIOR SUMMARY" in sent_prompt
133
+
134
+
135
+ def test_a5_routes_to_aux_llm_when_present():
136
+ aux = _StubLLM(reply="AUX")
137
+ main = _StubLLM(reply="MAIN")
138
+ h = _Harness(llm=main, aux_llm=aux)
139
+ _fill(h, 20)
140
+ assert asyncio.run(h.llm_compact_history()) is True
141
+ assert len(aux.calls) == 1
142
+ assert len(main.calls) == 0
143
+
144
+
145
+ def test_a5_falls_back_to_main_llm_without_aux():
146
+ main = _StubLLM(reply="MAIN")
147
+ h = _Harness(llm=main)
148
+ _fill(h, 20)
149
+ assert asyncio.run(h.llm_compact_history()) is True
150
+ assert len(main.calls) == 1
151
+
152
+
153
+ def test_a6_static_fallback_used_when_llm_fails():
154
+ h = _Harness(llm=_StubLLM(fail=True))
155
+ emergency_called = {"v": False}
156
+ h.emergency_context_prune = lambda: emergency_called.__setitem__("v", True)
157
+ for i in range(20):
158
+ # large enough that the C3 token-budget tail floors at min_keep (so the
159
+ # middle is non-empty and compaction actually runs). Paired AIMessage →
160
+ # ToolMessage so the CX-H2 orphan-guard doesn't extend the tail past
161
+ # the whole (otherwise all-orphan) history and skip compaction.
162
+ body = ("padding " * 500) + f"ran tool /path/file{i}.py -> error: boom {i}"
163
+ h._add_message_with_tokens(
164
+ AIMessage(content="", tool_calls=[{"id": f"c{i}", "name": "run", "args": {}}])
165
+ )
166
+ h._add_message_with_tokens(ToolMessage(content=body, tool_call_id=f"c{i}"))
167
+ assert asyncio.run(h.llm_compact_history()) is True
168
+ assert emergency_called["v"] is False, "fell through to emergency prune instead of static fallback"
169
+ joined = " ".join(str(m.message.content) for m in h.history.messages)
170
+ assert "COMPACTED SESSION HISTORY" in joined
171
+
172
+
173
+ # --------------------------------------------------------------------------- #
174
+ # Tier B — cache breadth, dedup, media hygiene, anti-thrash
175
+ # --------------------------------------------------------------------------- #
176
+ def _has_cache_control(block_holder) -> bool:
177
+ content = block_holder.get("content")
178
+ if isinstance(content, list):
179
+ return any(isinstance(b, dict) and b.get("cache_control") for b in content)
180
+ return False
181
+
182
+
183
+ def test_b1_marks_system_plus_last_three_messages():
184
+ from modules.llm.anthropic_client import _apply_conversation_cache
185
+
186
+ msgs = [{"role": "user", "content": f"m{i}"} for i in range(6)]
187
+ out = _apply_conversation_cache(msgs, n=3)
188
+ marked = [m for m in out if _has_cache_control(m)]
189
+ assert len(marked) == 3
190
+ # the three marked must be the last three
191
+ assert all(_has_cache_control(m) for m in out[-3:])
192
+ assert not any(_has_cache_control(m) for m in out[:-3])
193
+
194
+
195
+ def test_b1_disabled_by_env(monkeypatch):
196
+ from modules.llm.anthropic_client import _apply_conversation_cache
197
+
198
+ monkeypatch.setenv("ANTHROPIC_PROMPT_CACHE", "0")
199
+ msgs = [{"role": "user", "content": f"m{i}"} for i in range(6)]
200
+ out = _apply_conversation_cache(msgs, n=3)
201
+ assert not any(_has_cache_control(m) for m in out)
202
+
203
+
204
+ def test_b2_identical_tool_results_deduped():
205
+ from agents.task.agent.messages.filters import dedup_tool_results
206
+
207
+ msgs = [
208
+ AIMessage(content="", tool_calls=[{"id": "c1", "name": "x", "args": {}}]),
209
+ ToolMessage(content="IDENTICAL_OUTPUT", tool_call_id="c1"),
210
+ AIMessage(content="", tool_calls=[{"id": "c2", "name": "x", "args": {}}]),
211
+ ToolMessage(content="IDENTICAL_OUTPUT", tool_call_id="c2"),
212
+ ]
213
+ out = dedup_tool_results(msgs)
214
+ tool_contents = [str(m.content) for m in out if isinstance(m, ToolMessage)]
215
+ assert tool_contents[0] == "IDENTICAL_OUTPUT"
216
+ assert tool_contents[1] != "IDENTICAL_OUTPUT"
217
+ assert "duplicate" in tool_contents[1].lower() or "identical to" in tool_contents[1].lower()
218
+
219
+
220
+ def test_b3_keeps_latest_image_strips_older():
221
+ from agents.task.agent.messages.filters import strip_historical_media
222
+
223
+ def img():
224
+ return {"type": "image_url", "image_url": {"url": "data:image/png;base64,QUJDRA=="}}
225
+
226
+ txt = {"type": "text", "text": "hi"}
227
+ msgs = [
228
+ HumanMessage(content=[txt, img()]), # older -> strip
229
+ HumanMessage(content="middle"),
230
+ HumanMessage(content=[txt, img()]), # latest -> keep
231
+ ]
232
+ out = strip_historical_media(msgs)
233
+
234
+ def has_b64(m):
235
+ c = m.content
236
+ if isinstance(c, list):
237
+ return any(
238
+ isinstance(b, dict)
239
+ and b.get("type") == "image_url"
240
+ and "base64" in str(b.get("image_url", {}).get("url", ""))
241
+ for b in c
242
+ )
243
+ return False
244
+
245
+ assert not has_b64(out[0]), "older image base64 should be stripped"
246
+ assert has_b64(out[2]), "latest image base64 should be preserved"
247
+
248
+
249
+ def test_b4_thrash_detector_needs_two_low_savings():
250
+ h = _Harness()
251
+ h._compaction_savings = [0.05, 0.04]
252
+ assert h._compaction_is_thrashing() is True
253
+ h._compaction_savings = [0.05, 0.50]
254
+ assert h._compaction_is_thrashing() is False
255
+ h._compaction_savings = [0.04]
256
+ assert h._compaction_is_thrashing() is False
257
+
258
+
259
+ def test_b4_skips_llm_call_when_thrashing():
260
+ main = _StubLLM(reply="s")
261
+ h = _Harness(llm=main)
262
+ h._compaction_savings = [0.05, 0.04]
263
+ _fill(h, 20)
264
+ assert asyncio.run(h.llm_compact_history()) is False
265
+ assert len(main.calls) == 0
266
+
267
+
268
+ def test_b4_records_savings_after_compaction():
269
+ h = _Harness(llm=_StubLLM(reply="tiny"))
270
+ _fill(h, 20)
271
+ asyncio.run(h.llm_compact_history())
272
+ assert len(h._compaction_savings) == 1
273
+ assert 0.0 <= h._compaction_savings[0] <= 1.0
274
+
275
+
276
+ def test_b4_emergency_prune_resets_thrash_signal():
277
+ """Recovery: a hard prune changes history, so the anti-thrash signal must clear
278
+ (otherwise LLM compaction stays permanently disabled for the session)."""
279
+ h = _Harness()
280
+ h._compaction_savings = [0.05, 0.04]
281
+ for i in range(20):
282
+ h._add_message_with_tokens(HumanMessage(content=f"m{i}"))
283
+ h.emergency_context_prune()
284
+ assert h._compaction_savings == []
285
+ assert h._compaction_is_thrashing() is False
286
+
287
+
288
+ def test_a1_summarizes_in_windows_when_middle_is_huge():
289
+ """A small aux model must not be overflowed: a huge middle is summarized in
290
+ iterative windows, and the oldest content still reaches the summarizer."""
291
+ h = _Harness(llm=_StubLLM(reply="WIN"), max_input_tokens=1000)
292
+ for i in range(250):
293
+ h._add_message_with_tokens(HumanMessage(content=f"MARK{i}-" + ("q" * 3500)))
294
+ assert asyncio.run(h.llm_compact_history()) is True
295
+ assert len(h.llm.calls) > 1, "huge middle should be windowed into multiple calls"
296
+ all_sent = " ".join(c[0].content for c in h.llm.calls)
297
+ assert "MARK0-" in all_sent, "oldest message must still reach the summarizer (no drop)"
298
+
299
+
300
+ # --------------------------------------------------------------------------- #
301
+ # Tier C — references, checkpoint, token-budget tail
302
+ # --------------------------------------------------------------------------- #
303
+ def test_c1_expands_file_reference(tmp_path):
304
+ from agents.task.agent.messages.context_references import preprocess_context_references
305
+
306
+ f = tmp_path / "note.txt"
307
+ f.write_text("HELLO_FROM_FILE_C1")
308
+ out = preprocess_context_references(f"see @file:{f} please", context_length=100_000)
309
+ assert "HELLO_FROM_FILE_C1" in out
310
+
311
+
312
+ def test_c1_hard_cap_blocks_oversized_injection(tmp_path):
313
+ from agents.task.agent.messages.context_references import preprocess_context_references
314
+
315
+ f = tmp_path / "big.txt"
316
+ f.write_text("Z" * 10_000)
317
+ # context_length tiny -> 50% hard cap is ~10 chars -> must NOT inline the 10k file
318
+ out = preprocess_context_references(f"@file:{f}", context_length=20)
319
+ assert "Z" * 10_000 not in out
320
+
321
+
322
+ def test_c2_writes_pre_compaction_checkpoint(tmp_path):
323
+ h = _Harness(llm=_StubLLM(reply="s"))
324
+ h._compaction_checkpoint_dir = str(tmp_path)
325
+ _fill(h, 20)
326
+ asyncio.run(h.llm_compact_history())
327
+ files = list(tmp_path.glob("compaction_*.json"))
328
+ assert len(files) == 1
329
+
330
+
331
+ def test_c3_keeps_more_than_floor_when_budget_generous():
332
+ h = _Harness(max_input_tokens=10_000)
333
+ msgs = [HumanMessage(content="x" * 40) for _ in range(60)] # ~10 tok each
334
+ assert h._compaction_keep_recent(msgs, min_keep=10) > 10
335
+
336
+
337
+ def test_c3_floors_at_min_keep_when_budget_tight():
338
+ h = _Harness(max_input_tokens=100)
339
+ msgs = [HumanMessage(content="x" * 4000) for _ in range(20)] # ~1000 tok each
340
+ assert h._compaction_keep_recent(msgs, min_keep=10) == 10
341
+
342
+
343
+ def test_real_message_manager_declares_compaction_slots():
344
+ """The real MessageManager is __slots__-based; the new attrs must be slotted
345
+ or construction.py / the mixin writes would raise AttributeError at runtime."""
346
+ from agents.task.agent.message_manager.service import MessageManager
347
+
348
+ for slot in ("aux_llm", "_compaction_savings", "_compaction_count", "_compaction_checkpoint_dir"):
349
+ assert slot in MessageManager.__slots__
@@ -0,0 +1,28 @@
1
+ """Regression: load_from_disk must call create_file_path with subdir_name= (not subdir=)."""
2
+ from pathlib import Path
3
+
4
+ from agents.task.agent.messages.persistence import PersistenceMixin
5
+
6
+
7
+ class _Host(PersistenceMixin):
8
+ def __init__(self):
9
+ import logging
10
+ self.logger = logging.getLogger("test_persistence_subdir_fix")
11
+ self.session_id = "sess_1"
12
+
13
+
14
+ def test_load_from_disk_uses_subdir_name_kwarg(monkeypatch):
15
+ captured = {}
16
+
17
+ class _PM:
18
+ def create_file_path(self, **kwargs):
19
+ captured.update(kwargs)
20
+ return Path("/nonexistent/message_history.json") # .exists() -> False, early return
21
+
22
+ monkeypatch.setattr("agents.task.path.pm", lambda: _PM())
23
+
24
+ host = _Host()
25
+ # Must NOT raise TypeError (the bug); returns False because the path doesn't exist.
26
+ assert host.load_from_disk("sess_1") is False
27
+ assert "subdir_name" in captured
28
+ assert "subdir" not in captured
@@ -0,0 +1,54 @@
1
+ """SqliteMessageStore: append + load round-trip, per-session, durable across instances."""
2
+ from agents.task.agent.messages.sqlite_persistence import SqliteMessageStore
3
+
4
+
5
+ def test_append_and_load_roundtrip(tmp_path):
6
+ store = SqliteMessageStore(str(tmp_path / "msgs.db"))
7
+ store.append("s1", {"type": "HumanMessage", "content": "hello"})
8
+ store.append("s1", {"type": "AIMessage", "content": "hi", "tool_calls": []})
9
+ rows = store.load("s1")
10
+ assert [r["type"] for r in rows] == ["HumanMessage", "AIMessage"]
11
+ assert rows[0]["content"] == "hello"
12
+
13
+
14
+ def test_isolation_by_session(tmp_path):
15
+ store = SqliteMessageStore(str(tmp_path / "msgs.db"))
16
+ store.append("s1", {"type": "HumanMessage", "content": "a"})
17
+ store.append("s2", {"type": "HumanMessage", "content": "b"})
18
+ assert len(store.load("s1")) == 1
19
+ assert store.load("s2")[0]["content"] == "b"
20
+
21
+
22
+ def test_durable_across_instances(tmp_path):
23
+ db = str(tmp_path / "msgs.db")
24
+ SqliteMessageStore(db).append("s1", {"type": "HumanMessage", "content": "persisted"})
25
+ rows = SqliteMessageStore(db).load("s1") # fresh instance, same file
26
+ assert rows[0]["content"] == "persisted"
27
+
28
+
29
+ def test_clear_session(tmp_path):
30
+ store = SqliteMessageStore(str(tmp_path / "msgs.db"))
31
+ store.append("s1", {"type": "HumanMessage", "content": "x"})
32
+ store.clear("s1")
33
+ assert store.load("s1") == []
34
+
35
+
36
+ def test_replace_all_atomic_swap_and_order(tmp_path):
37
+ # MED-4: replace_all does DELETE + executemany in one txn; seq from enumerate.
38
+ store = SqliteMessageStore(str(tmp_path / "m.db"))
39
+ store.replace_all("s1", [{"i": 0}, {"i": 1}, {"i": 2}])
40
+ assert store.load("s1") == [{"i": 0}, {"i": 1}, {"i": 2}]
41
+ # A second replace_all fully supersedes the first (no PK collision, no leftovers).
42
+ store.replace_all("s1", [{"i": 9}])
43
+ assert store.load("s1") == [{"i": 9}]
44
+ # Per-session isolation preserved.
45
+ store.replace_all("s2", [{"x": 1}])
46
+ assert store.load("s1") == [{"i": 9}]
47
+ assert store.load("s2") == [{"x": 1}]
48
+
49
+
50
+ def test_replace_all_empty_clears(tmp_path):
51
+ store = SqliteMessageStore(str(tmp_path / "m.db"))
52
+ store.replace_all("s1", [{"a": 1}])
53
+ store.replace_all("s1", [])
54
+ assert store.load("s1") == []