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,755 @@
1
+ """PDF extraction mixin for FileSystem tool.
2
+
3
+ This module contains all PDF-related extraction logic extracted from filesystem.py.
4
+ The PdfExtractionMixin is composed into FileSystem via multiple-inheritance.
5
+
6
+ IMPORTANT: Do NOT add `from __future__ import annotations` to this file.
7
+ The registry inspects first-param annotations on @action closures via issubclass()
8
+ and stringized annotations break that routing.
9
+ """
10
+
11
+ import logging
12
+ import re
13
+ import os
14
+ import tempfile
15
+ from io import BytesIO
16
+ from typing import Dict, Any
17
+
18
+ import pypdf # type: ignore
19
+ from pypdf.errors import PdfReadError # type: ignore
20
+
21
+ from core.exceptions import ServiceError
22
+ from utils.time_utils import get_current_timestamp
23
+
24
+
25
+ class PdfExtractionMixin:
26
+ """Mixin providing PDF extraction capabilities for FileSystem."""
27
+
28
+ async def _process_pdf(self, content) -> Dict[str, Any]:
29
+ """Process PDF content with enhanced extraction capabilities and validation."""
30
+ from io import BytesIO
31
+ import pypdf #type: ignore
32
+ from pypdf.errors import PdfReadError #type: ignore
33
+ import re
34
+
35
+ # Ensure content is in bytes
36
+ if isinstance(content, str):
37
+ try:
38
+ content = content.encode('utf-8')
39
+ except UnicodeEncodeError as e:
40
+ raise ServiceError(f"Failed to encode PDF content: {str(e)}")
41
+
42
+ try:
43
+ # Extract metadata early before any other processing
44
+ metadata = {}
45
+ title = "Untitled PDF Document"
46
+
47
+ try:
48
+ # Create a new dedicated BytesIO for metadata extraction to avoid issues
49
+ with BytesIO(content) as metadata_file:
50
+ try:
51
+ # Create a PDF reader specifically for metadata extraction
52
+ metadata_reader = pypdf.PdfReader(metadata_file, strict=False)
53
+
54
+ # Immediately extract and copy metadata to avoid closed file issues
55
+ if hasattr(metadata_reader, 'metadata') and metadata_reader.metadata:
56
+ # Get a shallow copy of the metadata dictionary
57
+ metadata_dict = dict(metadata_reader.metadata or {})
58
+
59
+ # Copy values to a new dictionary to avoid references to PDF objects
60
+ for key, value in metadata_dict.items():
61
+ if isinstance(key, str):
62
+ clean_key = key.lstrip('/')
63
+ metadata[clean_key] = str(value) if value is not None else ""
64
+
65
+ # Look for title specifically
66
+ if clean_key.lower() == 'title' and value:
67
+ title = str(value)
68
+ except Exception as meta_err:
69
+ self.logger.warning(f"Failed initial metadata extraction: {str(meta_err)}")
70
+ except Exception as meta_outer_err:
71
+ self.logger.warning(f"Error during dedicated metadata extraction: {str(meta_outer_err)}")
72
+
73
+ # More lenient PDF header check
74
+ if not (content.startswith(b'%PDF-') or b'%PDF-' in content[:1024]):
75
+ self.logger.warning("PDF header not found in expected location, attempting recovery...")
76
+
77
+ # Try different PDF recovery methods
78
+ recovery_methods = [
79
+ # Method 1: Standard reading
80
+ lambda f: pypdf.PdfReader(f, strict=False),
81
+
82
+ # Method 2: Try with stream parsing and error recovery
83
+ lambda f: self._read_pdf_with_recovery(f),
84
+
85
+ # Method 3: Most permissive reading
86
+ lambda f: self._read_pdf_permissive(f),
87
+
88
+ # Method 4: New advanced PDF recovery
89
+ lambda f: self._read_pdf_advanced_recovery(f)
90
+ ]
91
+
92
+ last_error = None
93
+ pdf_reader = None
94
+
95
+ # Try each recovery method
96
+ for method in recovery_methods:
97
+ try:
98
+ with BytesIO(content) as pdf_file:
99
+ pdf_file.seek(0)
100
+ pdf_reader = method(pdf_file)
101
+
102
+ # Test page access more thoroughly
103
+ valid_pages = 0
104
+ for i in range(min(3, len(pdf_reader.pages))):
105
+ try:
106
+ page = pdf_reader.pages[i]
107
+ # If we can get any text, consider it valid
108
+ text = self._extract_page_text_with_fallbacks(page)
109
+ if text and text.strip():
110
+ valid_pages += 1
111
+ except Exception as e:
112
+ self.logger.debug(f"Error accessing page {i}: {str(e)}")
113
+ continue
114
+
115
+ if valid_pages > 0:
116
+ break # Found a working method
117
+
118
+ except Exception as e:
119
+ last_error = e
120
+ self.logger.debug(f"PDF recovery method failed: {str(e)}")
121
+ continue
122
+
123
+ if pdf_reader is None:
124
+ # Try the raw extraction method as last resort
125
+ raw_text = self._extract_raw_pdf_text(content)
126
+ if raw_text and len(raw_text) > 100 and self._is_readable_text(raw_text):
127
+ return {
128
+ 'type': 'pdf',
129
+ 'content': raw_text,
130
+ 'metadata': {
131
+ 'title': 'Extracted PDF Content',
132
+ 'pages': 1,
133
+ 'extraction_method': 'raw_bytes',
134
+ 'extraction_quality': 'low'
135
+ }
136
+ }
137
+
138
+ # If we still don't have content, try OCR if available
139
+ try:
140
+ if self.container and self.container.has_service('ocr_service'):
141
+ self.logger.info("Trying OCR service for image-based PDF")
142
+ ocr_service = self.container.get_service('ocr_service')
143
+
144
+ # Save content to temporary file for OCR processing
145
+ temp_path = None
146
+ try:
147
+ with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as temp_file:
148
+ temp_path = temp_file.name
149
+ temp_file.write(content)
150
+
151
+ # Process with OCR
152
+ ocr_result = await ocr_service.extract_text_from_pdf(temp_path)
153
+
154
+ # If OCR was successful, return its result
155
+ if ocr_result and ocr_result.get('content'):
156
+ self.logger.info("Successfully extracted text using OCR service")
157
+ return ocr_result
158
+ except Exception as ocr_error:
159
+ self.logger.warning(f"OCR processing failed: {str(ocr_error)}")
160
+ finally:
161
+ # Ensure temp file is removed
162
+ if temp_path and os.path.exists(temp_path):
163
+ try:
164
+ os.unlink(temp_path)
165
+ except Exception as cleanup_error:
166
+ self.logger.warning(f"Failed to clean up temp file: {str(cleanup_error)}")
167
+ except Exception as ocr_setup_error:
168
+ self.logger.warning(f"Failed to setup OCR processing: {str(ocr_setup_error)}")
169
+
170
+ raise ServiceError(f"Failed to read PDF after all recovery attempts: {str(last_error)}")
171
+
172
+ # Extract text with multiple fallback methods
173
+ pages = []
174
+ full_text = []
175
+ total_text_length = 0
176
+ successful_pages = 0
177
+ # We already have metadata from the beginning of the method
178
+
179
+ # Get page count with fallback methods
180
+ try:
181
+ page_count = len(pdf_reader.pages)
182
+ except Exception as page_count_error:
183
+ try:
184
+ # Try direct access to pages array
185
+ page_count = len(pdf_reader._get_num_pages())
186
+ except Exception:
187
+ try:
188
+ # Try to access pages dict
189
+ page_count = len(pdf_reader._pages_cache)
190
+ except Exception:
191
+ # Last resort: binary search for valid pages
192
+ page_count = self._find_max_valid_page(pdf_reader)
193
+
194
+ if page_count == 0:
195
+ self.logger.warning(f"Could not determine PDF page count: {str(page_count_error)}")
196
+ page_count = 1 # Assume at least one page
197
+
198
+ # Process each page with enhanced extraction
199
+ for i in range(page_count):
200
+ try:
201
+ page_text = ""
202
+ try:
203
+ page = pdf_reader.pages[i]
204
+ page_text = self._extract_page_text_with_fallbacks(page)
205
+ except Exception as page_error:
206
+ self.logger.debug(f"Error extracting text from page {i+1}: {str(page_error)}")
207
+ page_text = f"[Error extracting page {i+1}]"
208
+
209
+ # Apply post-processing to improve text quality
210
+ page_text = self._post_process_pdf_text(page_text)
211
+
212
+ # Skip pages with no meaningful content
213
+ if not self._is_meaningful_content(page_text):
214
+ continue
215
+
216
+ # Skip pages that appear to contain binary data
217
+ if self._contains_binary_data(page_text):
218
+ self.logger.warning(f"Page {i+1} appears to contain binary data, skipping")
219
+ continue
220
+
221
+ # Add to page collection
222
+ pages.append({
223
+ 'number': i + 1,
224
+ 'content': page_text
225
+ })
226
+
227
+ full_text.append(page_text)
228
+ total_text_length += len(page_text)
229
+ successful_pages += 1
230
+
231
+ except Exception as e:
232
+ self.logger.warning(f"Error processing page {i+1}: {str(e)}")
233
+
234
+ # Check if we have any usable content
235
+ combined_text = "\n\n".join(full_text)
236
+
237
+ if not combined_text or len(combined_text.strip()) < 50:
238
+ # Try fallback methods if regular extraction failed
239
+ raw_text = self._extract_raw_pdf_text(content)
240
+ if raw_text and len(raw_text) > 100:
241
+ combined_text = raw_text
242
+ successful_pages = 1
243
+ else:
244
+ # If we still don't have good content, try OCR if available
245
+ try:
246
+ if self.container and self.container.has_service('ocr_service'):
247
+ self.logger.info("Trying OCR service for image-based PDF")
248
+ ocr_service = self.container.get_service('ocr_service')
249
+
250
+ # Save content to temporary file for OCR processing
251
+ temp_path = None
252
+ try:
253
+ with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as temp_file:
254
+ temp_path = temp_file.name
255
+ temp_file.write(content)
256
+
257
+ # Process with OCR
258
+ ocr_result = await ocr_service.extract_text_from_pdf(temp_path)
259
+
260
+ # If OCR was successful, return its result
261
+ if ocr_result and ocr_result.get('content'):
262
+ self.logger.info("Successfully extracted text using OCR service")
263
+ return ocr_result
264
+ except Exception as ocr_error:
265
+ self.logger.warning(f"OCR processing failed: {str(ocr_error)}")
266
+ finally:
267
+ # Ensure temp file is removed
268
+ if temp_path and os.path.exists(temp_path):
269
+ try:
270
+ os.unlink(temp_path)
271
+ except Exception as cleanup_error:
272
+ self.logger.warning(f"Failed to clean up temp file: {str(cleanup_error)}")
273
+ except Exception as ocr_setup_error:
274
+ self.logger.warning(f"Failed to setup OCR processing: {str(ocr_setup_error)}")
275
+
276
+ # If OCR failed or is not available, use a placeholder
277
+ combined_text = "[PDF CONTENT REQUIRES OCR PROCESSING - TEXT EXTRACTION FAILED]"
278
+
279
+ # Validate extracted content
280
+ if self._contains_binary_data(combined_text):
281
+ # If it looks like binary, try more aggressive cleaning
282
+ combined_text = self._aggressive_binary_cleanup(combined_text)
283
+
284
+ # Final validation
285
+ if not self._is_readable_text(combined_text):
286
+ self.logger.warning("Extracted PDF content does not appear to be readable text")
287
+ # Add indicator for clients to know content may need further processing
288
+ combined_text = "[WARNING: PDF CONTENT MAY BE IMAGE-BASED OR CORRUPTED]\n\n" + combined_text
289
+
290
+ return {
291
+ 'type': 'pdf',
292
+ 'content': combined_text,
293
+ 'title': title,
294
+ 'structure': {
295
+ 'pages': pages,
296
+ 'total_pages': page_count
297
+ },
298
+ 'metadata': {
299
+ **metadata,
300
+ 'processor_version': '2.0',
301
+ 'processing_time': get_current_timestamp(),
302
+ 'original_size': len(content),
303
+ 'processed_size': total_text_length,
304
+ 'successful_pages': successful_pages,
305
+ 'total_pages': page_count,
306
+ 'extraction_quality': 'high' if successful_pages == page_count else 'medium' if successful_pages > 0 else 'low'
307
+ }
308
+ }
309
+
310
+ except ServiceError:
311
+ raise
312
+ except Exception as e:
313
+ self.logger.error(f"Error processing PDF: {str(e)}", exc_info=True)
314
+ raise ServiceError(f"Failed to process PDF: {str(e)}")
315
+
316
+ def _read_pdf_with_recovery(self, file_obj) -> pypdf.PdfReader:
317
+ """Read PDF with additional recovery options."""
318
+ try:
319
+ # Create a copy of the file content to avoid "I/O operation on closed file" errors
320
+ file_obj.seek(0)
321
+ content = file_obj.read()
322
+ file_copy = BytesIO(content)
323
+
324
+ try:
325
+ reader = pypdf.PdfReader(file_copy, strict=False)
326
+ # Force load document info to catch early failures
327
+ _ = reader.metadata
328
+ return reader
329
+ except Exception as e:
330
+ # Try to repair common issues
331
+ repair_copy = BytesIO(content)
332
+
333
+ # Try to fix common PDF corruptions
334
+ if b'startxref' not in content:
335
+ # Add missing startxref
336
+ repaired_content = content + b'\nstartxref\n0\n%%EOF'
337
+ return pypdf.PdfReader(BytesIO(repaired_content), strict=False)
338
+
339
+ return pypdf.PdfReader(repair_copy, strict=False)
340
+ except Exception as e:
341
+ self.logger.warning(f"Recovery failed: {str(e)}")
342
+ raise e
343
+
344
+ def _read_pdf_permissive(self, file_obj) -> pypdf.PdfReader:
345
+ """Most permissive PDF reading attempt."""
346
+ try:
347
+ # Create a copy of the file content
348
+ file_obj.seek(0)
349
+ content = file_obj.read()
350
+ file_copy = BytesIO(content)
351
+
352
+ try:
353
+ # Try to read with minimal validation
354
+ reader = pypdf.PdfReader(file_copy)
355
+ reader.strict = False
356
+ return reader
357
+ except Exception as e:
358
+ # Last resort: try to extract any readable content
359
+ if b'obj' in content and b'endobj' in content:
360
+ # Create minimal PDF structure
361
+ pdf_content = b'%PDF-1.4\n' + content + b'\nstartxref\n0\n%%EOF'
362
+ return pypdf.PdfReader(BytesIO(pdf_content), strict=False)
363
+ raise ServiceError("Could not recover PDF structure")
364
+ except ServiceError:
365
+ raise
366
+ except Exception as e:
367
+ self.logger.warning(f"Permissive reading failed: {str(e)}")
368
+ raise e
369
+
370
+ def _read_pdf_advanced_recovery(self, file_obj) -> pypdf.PdfReader:
371
+ """Advanced PDF recovery for damaged files."""
372
+ try:
373
+ # Create a copy of the file content
374
+ file_obj.seek(0)
375
+ content = file_obj.read()
376
+
377
+ # First try normal reading with error handling
378
+ try:
379
+ file_copy = BytesIO(content)
380
+ return pypdf.PdfReader(file_copy, strict=False)
381
+ except Exception as normal_error:
382
+ self.logger.debug(f"Normal reading failed: {str(normal_error)}")
383
+ pass
384
+
385
+ # Try to repair the file
386
+ # Add PDF header if missing
387
+ if not content.startswith(b'%PDF-'):
388
+ content = b'%PDF-1.4\n' + content
389
+
390
+ # Add EOF marker if missing
391
+ if not content.endswith(b'%%EOF'):
392
+ content = content + b'\n%%EOF'
393
+
394
+ # Check for and fix xref table
395
+ if b'xref' not in content:
396
+ content = content + b'\nxref\n0 1\n0000000000 65535 f\ntrailer\n<<>>\nstartxref\n0\n%%EOF'
397
+
398
+ # Try to create reader from repaired content
399
+ repair_copy = BytesIO(content)
400
+ return pypdf.PdfReader(repair_copy, strict=False)
401
+
402
+ except Exception as e:
403
+ self.logger.warning(f"Advanced PDF recovery failed: {str(e)}")
404
+ raise ServiceError(f"Advanced PDF recovery failed: {str(e)}")
405
+
406
+ def _extract_page_text_with_fallbacks(self, page) -> str:
407
+ """Extract text using multiple fallback methods with enhanced capabilities."""
408
+ all_extracted_text = []
409
+ methods = [
410
+ # Standard extraction
411
+ lambda p: p.extract_text(),
412
+
413
+ # Try getting text using textual objects
414
+ lambda p: self._extract_textual_objects(p),
415
+
416
+ # Raw extraction
417
+ lambda p: p.get_contents() if hasattr(p, 'get_contents') else b'',
418
+
419
+ # Extract from raw page object
420
+ lambda p: self._extract_from_raw_page(p),
421
+
422
+ # OCR-like extraction (if available)
423
+ lambda p: self._extract_text_from_images(p)
424
+ ]
425
+
426
+ for method in methods:
427
+ try:
428
+ result = method(page)
429
+ if isinstance(result, bytes):
430
+ result = result.decode('utf-8', errors='ignore')
431
+
432
+ if result and result.strip():
433
+ all_extracted_text.append(result)
434
+ except Exception as e:
435
+ continue
436
+
437
+ # If we have multiple extraction results, choose the best one
438
+ if len(all_extracted_text) > 1:
439
+ # Find the extraction with the most alphanumeric characters
440
+ best_text = ""
441
+ max_alphanumeric = 0
442
+
443
+ for text in all_extracted_text:
444
+ alphanumeric_count = sum(c.isalnum() for c in text)
445
+ if alphanumeric_count > max_alphanumeric:
446
+ max_alphanumeric = alphanumeric_count
447
+ best_text = text
448
+
449
+ if best_text:
450
+ return best_text
451
+
452
+ # Fall back to first non-empty result or empty string
453
+ return next((text for text in all_extracted_text if text), '')
454
+
455
+ def _extract_textual_objects(self, page) -> str:
456
+ """Extract text from PDF page text objects."""
457
+ try:
458
+ if not hasattr(page, '/Contents'):
459
+ return ""
460
+
461
+ raw_content = ""
462
+ if hasattr(page, 'get_contents'):
463
+ raw_content = page.get_contents()
464
+ elif hasattr(page, '_contents'):
465
+ raw_content = page._contents
466
+
467
+ if not raw_content:
468
+ return ""
469
+
470
+ # Convert to string if needed
471
+ if isinstance(raw_content, bytes):
472
+ raw_content = raw_content.decode('utf-8', errors='ignore')
473
+
474
+ # Extract text between BT and ET markers (text objects)
475
+ text_objects = []
476
+ start = 0
477
+ while True:
478
+ bt_pos = raw_content.find('BT', start)
479
+ if bt_pos == -1:
480
+ break
481
+
482
+ et_pos = raw_content.find('ET', bt_pos + 2)
483
+ if et_pos == -1:
484
+ break
485
+
486
+ text_objects.append(raw_content[bt_pos:et_pos+2])
487
+ start = et_pos + 2
488
+
489
+ # Extract text from text objects
490
+ extracted_text = []
491
+ for obj in text_objects:
492
+ # Look for text in parentheses
493
+ in_text = False
494
+ text_buffer = []
495
+ current_text = ""
496
+
497
+ for i, char in enumerate(obj):
498
+ if char == '(' and (i == 0 or obj[i-1] != '\\'):
499
+ in_text = True
500
+ current_text = ""
501
+ elif char == ')' and (i == 0 or obj[i-1] != '\\') and in_text:
502
+ in_text = False
503
+ text_buffer.append(current_text)
504
+ elif in_text:
505
+ current_text += char
506
+
507
+ if text_buffer:
508
+ extracted_text.append(" ".join(text_buffer))
509
+
510
+ return "\n".join(extracted_text)
511
+ except Exception as e:
512
+ return ""
513
+
514
+ def _clean_pdf_text(self, text: str) -> str:
515
+ """Enhanced cleaning of extracted PDF text."""
516
+ if not text:
517
+ return ''
518
+
519
+ # Remove control characters
520
+ text = ''.join(char for char in text if ord(char) >= 32 or char in '\n\t')
521
+
522
+ # Fix common PDF extraction issues
523
+ text = re.sub(r'([a-z])([A-Z])', r'\1 \2', text) # Add space between words
524
+ text = re.sub(r'(\w)([\(\)\[\]\{\}])', r'\1 \2', text) # Add space before brackets
525
+ text = re.sub(r'([\(\)\[\]\{\}])(\w)', r'\1 \2', text) # Add space after brackets
526
+
527
+ # Fix spacing issues
528
+ text = re.sub(r'\s+', ' ', text) # Normalize whitespace
529
+ text = re.sub(r'[^\S\n]+', ' ', text) # Normalize spaces but keep newlines
530
+ text = re.sub(r'\n{3,}', '\n\n', text) # Limit consecutive newlines
531
+
532
+ # Fix hyphenated words at line breaks
533
+ text = re.sub(r'(\w)-\s*\n\s*(\w)', r'\1\2', text)
534
+
535
+ # Remove isolated punctuation
536
+ text = re.sub(r'\s+([,.;:])\s+', r'\1 ', text)
537
+
538
+ return text.strip()
539
+
540
+ def _post_process_pdf_text(self, text: str) -> str:
541
+ """Apply post-processing to improve PDF text quality."""
542
+ if not text:
543
+ return ''
544
+
545
+ # First apply standard cleaning
546
+ text = self._clean_pdf_text(text)
547
+
548
+ # Enhanced processing
549
+ # Fix unicode normalization issues
550
+ import unicodedata
551
+ text = unicodedata.normalize('NFKC', text)
552
+
553
+ # Fix common PDF-specific issues
554
+ # 1. Remove form field placeholders
555
+ text = re.sub(r'\[+\s*\]+', '', text)
556
+
557
+ # 2. Fix mis-encoded characters
558
+ text = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f]', '', text)
559
+
560
+ # 3. Fix duplicated punctuation
561
+ text = re.sub(r'([,.!?;:])\1+', r'\1', text)
562
+
563
+ # 4. Remove redundant spaces around newlines
564
+ text = re.sub(r'\s*\n\s*', '\n', text)
565
+
566
+ # 5. Try to detect and replace common character encoding issues
567
+ replacements = {
568
+ 'â': "'", '’': "'", '“': '"', 'â€': '"',
569
+ 'â€"': '-', 'â€"': '--', '…': '...',
570
+ 'é': 'é', 'è': 'è', 'Ã': 'à',
571
+ 'Â': '', '°': '°'
572
+ }
573
+
574
+ for wrong, right in replacements.items():
575
+ text = text.replace(wrong, right)
576
+
577
+ return text.strip()
578
+
579
+ def _is_meaningful_content(self, text: str) -> bool:
580
+ """Check if text contains meaningful content."""
581
+ if not text or len(text.strip()) < 10:
582
+ return False
583
+
584
+ # Check if the text contains enough alphanumeric characters
585
+ alphanumeric_ratio = sum(c.isalnum() for c in text) / max(1, len(text))
586
+ if alphanumeric_ratio < 0.1: # Less than 10% alphanumeric characters
587
+ return False
588
+
589
+ # Check for common placeholder patterns
590
+ placeholders = ['[Empty]', '[blank]', '[N/A]', '[?]', '[...]']
591
+ if any(p in text for p in placeholders):
592
+ return False
593
+
594
+ return True
595
+
596
+ def _contains_binary_data(self, text: str) -> bool:
597
+ """Check if text appears to contain binary data."""
598
+ if not text:
599
+ return False
600
+
601
+ # Check for high concentration of unusual characters
602
+ unusual_char_count = sum(1 for c in text if ord(c) > 127 or ord(c) < 32)
603
+ unusual_ratio = unusual_char_count / max(1, len(text))
604
+
605
+ # Binary data typically has many unusual characters - use more lenient threshold for PDFs
606
+ if unusual_ratio > 0.4: # Increased from 0.3 to 0.4 to be more lenient
607
+ return True
608
+
609
+ # Check for PDF binary stream markers
610
+ binary_markers = ['stream', 'endstream', 'obj', 'endobj', '/Filter', '/Length']
611
+ marker_count = sum(text.count(marker) for marker in binary_markers)
612
+
613
+ # PDF binary content often contains these markers - increased threshold
614
+ if marker_count > 10 and unusual_ratio > 0.2: # Increased from 5 to 10 and 0.1 to 0.2
615
+ return True
616
+
617
+ # Look for long sequences of random-looking characters
618
+ random_sequences = re.findall(r'[A-Za-z0-9+/=]{20,}', text)
619
+ if random_sequences and sum(len(seq) for seq in random_sequences) / len(text) > 0.3: # Increased from 0.2 to 0.3
620
+ return True
621
+
622
+ return False
623
+
624
+ def _is_readable_text(self, text: str) -> bool:
625
+ """Check if text appears to be human-readable."""
626
+ if not text or len(text) < 30: # Reduced from 50 to 30 characters minimum
627
+ return False
628
+
629
+ # Check for reasonable distribution of characters
630
+ char_counts = {}
631
+ for char in text:
632
+ char_counts[char] = char_counts.get(char, 0) + 1
633
+
634
+ # Human text typically uses a variety of characters - reduced minimum
635
+ if len(char_counts) < 15: # Reduced from 20 to 15
636
+ return False
637
+
638
+ # Check for reasonable word formation
639
+ words = re.findall(r'\b[a-zA-Z]{1,20}\b', text)
640
+
641
+ # Human text has a reasonable number of words - reduced minimum
642
+ if len(words) < 8: # Reduced from 10 to 8
643
+ return False
644
+
645
+ # Check distribution of word lengths
646
+ word_lengths = [len(word) for word in words]
647
+ if word_lengths:
648
+ avg_word_length = sum(word_lengths) / len(word_lengths)
649
+ # Extremely long average word length suggests non-text content
650
+ if avg_word_length > 15:
651
+ return False
652
+
653
+ # Check for reasonable whitespace - modified ratio
654
+ whitespace_ratio = text.count(' ') / max(1, len(text))
655
+ if whitespace_ratio < 0.03 or whitespace_ratio > 0.6: # Reduced from 0.05 to 0.03, increased from 0.5 to 0.6
656
+ return False
657
+
658
+ # If we have a reasonable number of words or paragraphs, consider it readable
659
+ # even if it doesn't strictly meet our other criteria (helps with image-based PDFs)
660
+ if len(words) > 30 or len(text.split('\n\n')) > 5:
661
+ return True
662
+
663
+ return True # More permissive - assume text is readable unless proven otherwise
664
+
665
+ def _aggressive_binary_cleanup(self, text: str) -> str:
666
+ """Aggressively clean binary-looking content to extract readable text."""
667
+ if not text:
668
+ return ""
669
+
670
+ # First pass: remove all non-printable characters
671
+ text = re.sub(r'[^\x20-\x7E\n\t]', '', text)
672
+
673
+ # Second pass: remove PDF binary markers and their content
674
+ binary_sections = [
675
+ (r'stream.*?endstream', ''),
676
+ (r'obj.*?endobj', ''),
677
+ (r'/Filter.*?/Length \d+', ''),
678
+ (r'<<.*?>>', ''),
679
+ (r'[0-9]+ [0-9]+ obj.*?endobj', ''),
680
+ (r'xref.*?trailer', ''),
681
+ (r'startxref.*?%%EOF', '')
682
+ ]
683
+
684
+ for pattern, replacement in binary_sections:
685
+ text = re.sub(pattern, replacement, text, flags=re.DOTALL)
686
+
687
+ # Third pass: clean up remaining issues
688
+ text = re.sub(r'\s+', ' ', text) # Normalize whitespace
689
+ text = re.sub(r'(\w)\1{5,}', r'\1\1\1', text) # Remove character repetitions
690
+ text = re.sub(r'[^a-zA-Z0-9\s,.;:!?"\'()-]', '', text) # Remove remaining non-text chars
691
+
692
+ # Final pass: segment into paragraphs
693
+ lines = text.split('\n')
694
+ paragraphs = []
695
+ current_paragraph = []
696
+
697
+ for line in lines:
698
+ if not line.strip():
699
+ if current_paragraph:
700
+ paragraphs.append(' '.join(current_paragraph))
701
+ current_paragraph = []
702
+ else:
703
+ current_paragraph.append(line.strip())
704
+
705
+ if current_paragraph:
706
+ paragraphs.append(' '.join(current_paragraph))
707
+
708
+ return '\n\n'.join(paragraphs)
709
+
710
+ def _extract_raw_pdf_text(self, content: bytes) -> str:
711
+ """Extract any text-like content from raw PDF bytes with enhanced capabilities."""
712
+ try:
713
+ # Convert to string, ignoring errors
714
+ text = content.decode('utf-8', errors='ignore')
715
+
716
+ # Find text between PDF markers
717
+ text_markers = [
718
+ (b'(', b')'), # Text in parentheses
719
+ (b'<', b'>'), # Hex encoded text
720
+ (b'BT', b'ET') # Text blocks
721
+ ]
722
+
723
+ extracted = []
724
+ for start_marker, end_marker in text_markers:
725
+ pos = 0
726
+ while True:
727
+ start = content.find(start_marker, pos)
728
+ if start == -1:
729
+ break
730
+ end = content.find(end_marker, start + len(start_marker))
731
+ if end == -1:
732
+ break
733
+
734
+ text_chunk = content[start+len(start_marker):end]
735
+ try:
736
+ # Try to decode as text
737
+ decoded = text_chunk.decode('utf-8', errors='ignore')
738
+ if decoded.strip():
739
+ extracted.append(decoded)
740
+ except Exception:
741
+ # Silently skip non-decodable chunks
742
+ pass
743
+
744
+ pos = end + len(end_marker)
745
+
746
+ # Clean up extracted text
747
+ if extracted:
748
+ text = '\n'.join(extracted)
749
+ return self._clean_pdf_text(text)
750
+
751
+ return ''
752
+
753
+ except Exception as e:
754
+ self.logger.warning(f"Failed to extract raw text: {str(e)}")
755
+ return ''