PraisonAI 3.0.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 (393) hide show
  1. praisonai/__init__.py +54 -0
  2. praisonai/__main__.py +15 -0
  3. praisonai/acp/__init__.py +54 -0
  4. praisonai/acp/config.py +159 -0
  5. praisonai/acp/server.py +587 -0
  6. praisonai/acp/session.py +219 -0
  7. praisonai/adapters/__init__.py +50 -0
  8. praisonai/adapters/readers.py +395 -0
  9. praisonai/adapters/rerankers.py +315 -0
  10. praisonai/adapters/retrievers.py +394 -0
  11. praisonai/adapters/vector_stores.py +409 -0
  12. praisonai/agent_scheduler.py +337 -0
  13. praisonai/agents_generator.py +903 -0
  14. praisonai/api/call.py +292 -0
  15. praisonai/auto.py +1197 -0
  16. praisonai/capabilities/__init__.py +275 -0
  17. praisonai/capabilities/a2a.py +140 -0
  18. praisonai/capabilities/assistants.py +283 -0
  19. praisonai/capabilities/audio.py +320 -0
  20. praisonai/capabilities/batches.py +469 -0
  21. praisonai/capabilities/completions.py +336 -0
  22. praisonai/capabilities/container_files.py +155 -0
  23. praisonai/capabilities/containers.py +93 -0
  24. praisonai/capabilities/embeddings.py +158 -0
  25. praisonai/capabilities/files.py +467 -0
  26. praisonai/capabilities/fine_tuning.py +293 -0
  27. praisonai/capabilities/guardrails.py +182 -0
  28. praisonai/capabilities/images.py +330 -0
  29. praisonai/capabilities/mcp.py +190 -0
  30. praisonai/capabilities/messages.py +270 -0
  31. praisonai/capabilities/moderations.py +154 -0
  32. praisonai/capabilities/ocr.py +217 -0
  33. praisonai/capabilities/passthrough.py +204 -0
  34. praisonai/capabilities/rag.py +207 -0
  35. praisonai/capabilities/realtime.py +160 -0
  36. praisonai/capabilities/rerank.py +165 -0
  37. praisonai/capabilities/responses.py +266 -0
  38. praisonai/capabilities/search.py +109 -0
  39. praisonai/capabilities/skills.py +133 -0
  40. praisonai/capabilities/vector_store_files.py +334 -0
  41. praisonai/capabilities/vector_stores.py +304 -0
  42. praisonai/capabilities/videos.py +141 -0
  43. praisonai/chainlit_ui.py +304 -0
  44. praisonai/chat/__init__.py +106 -0
  45. praisonai/chat/app.py +125 -0
  46. praisonai/cli/__init__.py +26 -0
  47. praisonai/cli/app.py +213 -0
  48. praisonai/cli/commands/__init__.py +75 -0
  49. praisonai/cli/commands/acp.py +70 -0
  50. praisonai/cli/commands/completion.py +333 -0
  51. praisonai/cli/commands/config.py +166 -0
  52. praisonai/cli/commands/debug.py +142 -0
  53. praisonai/cli/commands/diag.py +55 -0
  54. praisonai/cli/commands/doctor.py +166 -0
  55. praisonai/cli/commands/environment.py +179 -0
  56. praisonai/cli/commands/lsp.py +112 -0
  57. praisonai/cli/commands/mcp.py +210 -0
  58. praisonai/cli/commands/profile.py +457 -0
  59. praisonai/cli/commands/run.py +228 -0
  60. praisonai/cli/commands/schedule.py +150 -0
  61. praisonai/cli/commands/serve.py +97 -0
  62. praisonai/cli/commands/session.py +212 -0
  63. praisonai/cli/commands/traces.py +145 -0
  64. praisonai/cli/commands/version.py +101 -0
  65. praisonai/cli/configuration/__init__.py +18 -0
  66. praisonai/cli/configuration/loader.py +353 -0
  67. praisonai/cli/configuration/paths.py +114 -0
  68. praisonai/cli/configuration/schema.py +164 -0
  69. praisonai/cli/features/__init__.py +268 -0
  70. praisonai/cli/features/acp.py +236 -0
  71. praisonai/cli/features/action_orchestrator.py +546 -0
  72. praisonai/cli/features/agent_scheduler.py +773 -0
  73. praisonai/cli/features/agent_tools.py +474 -0
  74. praisonai/cli/features/agents.py +375 -0
  75. praisonai/cli/features/at_mentions.py +471 -0
  76. praisonai/cli/features/auto_memory.py +182 -0
  77. praisonai/cli/features/autonomy_mode.py +490 -0
  78. praisonai/cli/features/background.py +356 -0
  79. praisonai/cli/features/base.py +168 -0
  80. praisonai/cli/features/capabilities.py +1326 -0
  81. praisonai/cli/features/checkpoints.py +338 -0
  82. praisonai/cli/features/code_intelligence.py +652 -0
  83. praisonai/cli/features/compaction.py +294 -0
  84. praisonai/cli/features/compare.py +534 -0
  85. praisonai/cli/features/cost_tracker.py +514 -0
  86. praisonai/cli/features/debug.py +810 -0
  87. praisonai/cli/features/deploy.py +517 -0
  88. praisonai/cli/features/diag.py +289 -0
  89. praisonai/cli/features/doctor/__init__.py +63 -0
  90. praisonai/cli/features/doctor/checks/__init__.py +24 -0
  91. praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
  92. praisonai/cli/features/doctor/checks/config_checks.py +366 -0
  93. praisonai/cli/features/doctor/checks/db_checks.py +366 -0
  94. praisonai/cli/features/doctor/checks/env_checks.py +543 -0
  95. praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
  96. praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
  97. praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
  98. praisonai/cli/features/doctor/checks/network_checks.py +251 -0
  99. praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
  100. praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
  101. praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
  102. praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
  103. praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
  104. praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
  105. praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
  106. praisonai/cli/features/doctor/engine.py +266 -0
  107. praisonai/cli/features/doctor/formatters.py +310 -0
  108. praisonai/cli/features/doctor/handler.py +397 -0
  109. praisonai/cli/features/doctor/models.py +264 -0
  110. praisonai/cli/features/doctor/registry.py +239 -0
  111. praisonai/cli/features/endpoints.py +1019 -0
  112. praisonai/cli/features/eval.py +560 -0
  113. praisonai/cli/features/external_agents.py +231 -0
  114. praisonai/cli/features/fast_context.py +410 -0
  115. praisonai/cli/features/flow_display.py +566 -0
  116. praisonai/cli/features/git_integration.py +651 -0
  117. praisonai/cli/features/guardrail.py +171 -0
  118. praisonai/cli/features/handoff.py +185 -0
  119. praisonai/cli/features/hooks.py +583 -0
  120. praisonai/cli/features/image.py +384 -0
  121. praisonai/cli/features/interactive_runtime.py +585 -0
  122. praisonai/cli/features/interactive_tools.py +380 -0
  123. praisonai/cli/features/interactive_tui.py +603 -0
  124. praisonai/cli/features/jobs.py +632 -0
  125. praisonai/cli/features/knowledge.py +531 -0
  126. praisonai/cli/features/lite.py +244 -0
  127. praisonai/cli/features/lsp_cli.py +225 -0
  128. praisonai/cli/features/mcp.py +169 -0
  129. praisonai/cli/features/message_queue.py +587 -0
  130. praisonai/cli/features/metrics.py +211 -0
  131. praisonai/cli/features/n8n.py +673 -0
  132. praisonai/cli/features/observability.py +293 -0
  133. praisonai/cli/features/ollama.py +361 -0
  134. praisonai/cli/features/output_style.py +273 -0
  135. praisonai/cli/features/package.py +631 -0
  136. praisonai/cli/features/performance.py +308 -0
  137. praisonai/cli/features/persistence.py +636 -0
  138. praisonai/cli/features/profile.py +226 -0
  139. praisonai/cli/features/profiler/__init__.py +81 -0
  140. praisonai/cli/features/profiler/core.py +558 -0
  141. praisonai/cli/features/profiler/optimizations.py +652 -0
  142. praisonai/cli/features/profiler/suite.py +386 -0
  143. praisonai/cli/features/profiling.py +350 -0
  144. praisonai/cli/features/queue/__init__.py +73 -0
  145. praisonai/cli/features/queue/manager.py +395 -0
  146. praisonai/cli/features/queue/models.py +286 -0
  147. praisonai/cli/features/queue/persistence.py +564 -0
  148. praisonai/cli/features/queue/scheduler.py +484 -0
  149. praisonai/cli/features/queue/worker.py +372 -0
  150. praisonai/cli/features/recipe.py +1723 -0
  151. praisonai/cli/features/recipes.py +449 -0
  152. praisonai/cli/features/registry.py +229 -0
  153. praisonai/cli/features/repo_map.py +860 -0
  154. praisonai/cli/features/router.py +466 -0
  155. praisonai/cli/features/sandbox_executor.py +515 -0
  156. praisonai/cli/features/serve.py +829 -0
  157. praisonai/cli/features/session.py +222 -0
  158. praisonai/cli/features/skills.py +856 -0
  159. praisonai/cli/features/slash_commands.py +650 -0
  160. praisonai/cli/features/telemetry.py +179 -0
  161. praisonai/cli/features/templates.py +1384 -0
  162. praisonai/cli/features/thinking.py +305 -0
  163. praisonai/cli/features/todo.py +334 -0
  164. praisonai/cli/features/tools.py +680 -0
  165. praisonai/cli/features/tui/__init__.py +83 -0
  166. praisonai/cli/features/tui/app.py +580 -0
  167. praisonai/cli/features/tui/cli.py +566 -0
  168. praisonai/cli/features/tui/debug.py +511 -0
  169. praisonai/cli/features/tui/events.py +99 -0
  170. praisonai/cli/features/tui/mock_provider.py +328 -0
  171. praisonai/cli/features/tui/orchestrator.py +652 -0
  172. praisonai/cli/features/tui/screens/__init__.py +50 -0
  173. praisonai/cli/features/tui/screens/main.py +245 -0
  174. praisonai/cli/features/tui/screens/queue.py +174 -0
  175. praisonai/cli/features/tui/screens/session.py +124 -0
  176. praisonai/cli/features/tui/screens/settings.py +148 -0
  177. praisonai/cli/features/tui/widgets/__init__.py +56 -0
  178. praisonai/cli/features/tui/widgets/chat.py +261 -0
  179. praisonai/cli/features/tui/widgets/composer.py +224 -0
  180. praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
  181. praisonai/cli/features/tui/widgets/status.py +167 -0
  182. praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
  183. praisonai/cli/features/workflow.py +720 -0
  184. praisonai/cli/legacy.py +236 -0
  185. praisonai/cli/main.py +5559 -0
  186. praisonai/cli/schedule_cli.py +54 -0
  187. praisonai/cli/state/__init__.py +31 -0
  188. praisonai/cli/state/identifiers.py +161 -0
  189. praisonai/cli/state/sessions.py +313 -0
  190. praisonai/code/__init__.py +93 -0
  191. praisonai/code/agent_tools.py +344 -0
  192. praisonai/code/diff/__init__.py +21 -0
  193. praisonai/code/diff/diff_strategy.py +432 -0
  194. praisonai/code/tools/__init__.py +27 -0
  195. praisonai/code/tools/apply_diff.py +221 -0
  196. praisonai/code/tools/execute_command.py +275 -0
  197. praisonai/code/tools/list_files.py +274 -0
  198. praisonai/code/tools/read_file.py +206 -0
  199. praisonai/code/tools/search_replace.py +248 -0
  200. praisonai/code/tools/write_file.py +217 -0
  201. praisonai/code/utils/__init__.py +46 -0
  202. praisonai/code/utils/file_utils.py +307 -0
  203. praisonai/code/utils/ignore_utils.py +308 -0
  204. praisonai/code/utils/text_utils.py +276 -0
  205. praisonai/db/__init__.py +64 -0
  206. praisonai/db/adapter.py +531 -0
  207. praisonai/deploy/__init__.py +62 -0
  208. praisonai/deploy/api.py +231 -0
  209. praisonai/deploy/docker.py +454 -0
  210. praisonai/deploy/doctor.py +367 -0
  211. praisonai/deploy/main.py +327 -0
  212. praisonai/deploy/models.py +179 -0
  213. praisonai/deploy/providers/__init__.py +33 -0
  214. praisonai/deploy/providers/aws.py +331 -0
  215. praisonai/deploy/providers/azure.py +358 -0
  216. praisonai/deploy/providers/base.py +101 -0
  217. praisonai/deploy/providers/gcp.py +314 -0
  218. praisonai/deploy/schema.py +208 -0
  219. praisonai/deploy.py +185 -0
  220. praisonai/endpoints/__init__.py +53 -0
  221. praisonai/endpoints/a2u_server.py +410 -0
  222. praisonai/endpoints/discovery.py +165 -0
  223. praisonai/endpoints/providers/__init__.py +28 -0
  224. praisonai/endpoints/providers/a2a.py +253 -0
  225. praisonai/endpoints/providers/a2u.py +208 -0
  226. praisonai/endpoints/providers/agents_api.py +171 -0
  227. praisonai/endpoints/providers/base.py +231 -0
  228. praisonai/endpoints/providers/mcp.py +263 -0
  229. praisonai/endpoints/providers/recipe.py +206 -0
  230. praisonai/endpoints/providers/tools_mcp.py +150 -0
  231. praisonai/endpoints/registry.py +131 -0
  232. praisonai/endpoints/server.py +161 -0
  233. praisonai/inbuilt_tools/__init__.py +24 -0
  234. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  235. praisonai/inc/__init__.py +2 -0
  236. praisonai/inc/config.py +96 -0
  237. praisonai/inc/models.py +155 -0
  238. praisonai/integrations/__init__.py +56 -0
  239. praisonai/integrations/base.py +303 -0
  240. praisonai/integrations/claude_code.py +270 -0
  241. praisonai/integrations/codex_cli.py +255 -0
  242. praisonai/integrations/cursor_cli.py +195 -0
  243. praisonai/integrations/gemini_cli.py +222 -0
  244. praisonai/jobs/__init__.py +67 -0
  245. praisonai/jobs/executor.py +425 -0
  246. praisonai/jobs/models.py +230 -0
  247. praisonai/jobs/router.py +314 -0
  248. praisonai/jobs/server.py +186 -0
  249. praisonai/jobs/store.py +203 -0
  250. praisonai/llm/__init__.py +66 -0
  251. praisonai/llm/registry.py +382 -0
  252. praisonai/mcp_server/__init__.py +152 -0
  253. praisonai/mcp_server/adapters/__init__.py +74 -0
  254. praisonai/mcp_server/adapters/agents.py +128 -0
  255. praisonai/mcp_server/adapters/capabilities.py +168 -0
  256. praisonai/mcp_server/adapters/cli_tools.py +568 -0
  257. praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
  258. praisonai/mcp_server/adapters/knowledge.py +93 -0
  259. praisonai/mcp_server/adapters/memory.py +104 -0
  260. praisonai/mcp_server/adapters/prompts.py +306 -0
  261. praisonai/mcp_server/adapters/resources.py +124 -0
  262. praisonai/mcp_server/adapters/tools_bridge.py +280 -0
  263. praisonai/mcp_server/auth/__init__.py +48 -0
  264. praisonai/mcp_server/auth/api_key.py +291 -0
  265. praisonai/mcp_server/auth/oauth.py +460 -0
  266. praisonai/mcp_server/auth/oidc.py +289 -0
  267. praisonai/mcp_server/auth/scopes.py +260 -0
  268. praisonai/mcp_server/cli.py +852 -0
  269. praisonai/mcp_server/elicitation.py +445 -0
  270. praisonai/mcp_server/icons.py +302 -0
  271. praisonai/mcp_server/recipe_adapter.py +573 -0
  272. praisonai/mcp_server/recipe_cli.py +824 -0
  273. praisonai/mcp_server/registry.py +703 -0
  274. praisonai/mcp_server/sampling.py +422 -0
  275. praisonai/mcp_server/server.py +490 -0
  276. praisonai/mcp_server/tasks.py +443 -0
  277. praisonai/mcp_server/transports/__init__.py +18 -0
  278. praisonai/mcp_server/transports/http_stream.py +376 -0
  279. praisonai/mcp_server/transports/stdio.py +132 -0
  280. praisonai/persistence/__init__.py +84 -0
  281. praisonai/persistence/config.py +238 -0
  282. praisonai/persistence/conversation/__init__.py +25 -0
  283. praisonai/persistence/conversation/async_mysql.py +427 -0
  284. praisonai/persistence/conversation/async_postgres.py +410 -0
  285. praisonai/persistence/conversation/async_sqlite.py +371 -0
  286. praisonai/persistence/conversation/base.py +151 -0
  287. praisonai/persistence/conversation/json_store.py +250 -0
  288. praisonai/persistence/conversation/mysql.py +387 -0
  289. praisonai/persistence/conversation/postgres.py +401 -0
  290. praisonai/persistence/conversation/singlestore.py +240 -0
  291. praisonai/persistence/conversation/sqlite.py +341 -0
  292. praisonai/persistence/conversation/supabase.py +203 -0
  293. praisonai/persistence/conversation/surrealdb.py +287 -0
  294. praisonai/persistence/factory.py +301 -0
  295. praisonai/persistence/hooks/__init__.py +18 -0
  296. praisonai/persistence/hooks/agent_hooks.py +297 -0
  297. praisonai/persistence/knowledge/__init__.py +26 -0
  298. praisonai/persistence/knowledge/base.py +144 -0
  299. praisonai/persistence/knowledge/cassandra.py +232 -0
  300. praisonai/persistence/knowledge/chroma.py +295 -0
  301. praisonai/persistence/knowledge/clickhouse.py +242 -0
  302. praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
  303. praisonai/persistence/knowledge/couchbase.py +286 -0
  304. praisonai/persistence/knowledge/lancedb.py +216 -0
  305. praisonai/persistence/knowledge/langchain_adapter.py +291 -0
  306. praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
  307. praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
  308. praisonai/persistence/knowledge/milvus.py +277 -0
  309. praisonai/persistence/knowledge/mongodb_vector.py +306 -0
  310. praisonai/persistence/knowledge/pgvector.py +335 -0
  311. praisonai/persistence/knowledge/pinecone.py +253 -0
  312. praisonai/persistence/knowledge/qdrant.py +301 -0
  313. praisonai/persistence/knowledge/redis_vector.py +291 -0
  314. praisonai/persistence/knowledge/singlestore_vector.py +299 -0
  315. praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
  316. praisonai/persistence/knowledge/upstash_vector.py +266 -0
  317. praisonai/persistence/knowledge/weaviate.py +223 -0
  318. praisonai/persistence/migrations/__init__.py +10 -0
  319. praisonai/persistence/migrations/manager.py +251 -0
  320. praisonai/persistence/orchestrator.py +406 -0
  321. praisonai/persistence/state/__init__.py +21 -0
  322. praisonai/persistence/state/async_mongodb.py +200 -0
  323. praisonai/persistence/state/base.py +107 -0
  324. praisonai/persistence/state/dynamodb.py +226 -0
  325. praisonai/persistence/state/firestore.py +175 -0
  326. praisonai/persistence/state/gcs.py +155 -0
  327. praisonai/persistence/state/memory.py +245 -0
  328. praisonai/persistence/state/mongodb.py +158 -0
  329. praisonai/persistence/state/redis.py +190 -0
  330. praisonai/persistence/state/upstash.py +144 -0
  331. praisonai/persistence/tests/__init__.py +3 -0
  332. praisonai/persistence/tests/test_all_backends.py +633 -0
  333. praisonai/profiler.py +1214 -0
  334. praisonai/recipe/__init__.py +134 -0
  335. praisonai/recipe/bridge.py +278 -0
  336. praisonai/recipe/core.py +893 -0
  337. praisonai/recipe/exceptions.py +54 -0
  338. praisonai/recipe/history.py +402 -0
  339. praisonai/recipe/models.py +266 -0
  340. praisonai/recipe/operations.py +440 -0
  341. praisonai/recipe/policy.py +422 -0
  342. praisonai/recipe/registry.py +849 -0
  343. praisonai/recipe/runtime.py +214 -0
  344. praisonai/recipe/security.py +711 -0
  345. praisonai/recipe/serve.py +859 -0
  346. praisonai/recipe/server.py +613 -0
  347. praisonai/scheduler/__init__.py +45 -0
  348. praisonai/scheduler/agent_scheduler.py +552 -0
  349. praisonai/scheduler/base.py +124 -0
  350. praisonai/scheduler/daemon_manager.py +225 -0
  351. praisonai/scheduler/state_manager.py +155 -0
  352. praisonai/scheduler/yaml_loader.py +193 -0
  353. praisonai/scheduler.py +194 -0
  354. praisonai/setup/__init__.py +1 -0
  355. praisonai/setup/build.py +21 -0
  356. praisonai/setup/post_install.py +23 -0
  357. praisonai/setup/setup_conda_env.py +25 -0
  358. praisonai/setup.py +16 -0
  359. praisonai/templates/__init__.py +116 -0
  360. praisonai/templates/cache.py +364 -0
  361. praisonai/templates/dependency_checker.py +358 -0
  362. praisonai/templates/discovery.py +391 -0
  363. praisonai/templates/loader.py +564 -0
  364. praisonai/templates/registry.py +511 -0
  365. praisonai/templates/resolver.py +206 -0
  366. praisonai/templates/security.py +327 -0
  367. praisonai/templates/tool_override.py +498 -0
  368. praisonai/templates/tools_doctor.py +256 -0
  369. praisonai/test.py +105 -0
  370. praisonai/train.py +562 -0
  371. praisonai/train_vision.py +306 -0
  372. praisonai/ui/agents.py +824 -0
  373. praisonai/ui/callbacks.py +57 -0
  374. praisonai/ui/chainlit_compat.py +246 -0
  375. praisonai/ui/chat.py +532 -0
  376. praisonai/ui/code.py +717 -0
  377. praisonai/ui/colab.py +474 -0
  378. praisonai/ui/colab_chainlit.py +81 -0
  379. praisonai/ui/components/aicoder.py +284 -0
  380. praisonai/ui/context.py +283 -0
  381. praisonai/ui/database_config.py +56 -0
  382. praisonai/ui/db.py +294 -0
  383. praisonai/ui/realtime.py +488 -0
  384. praisonai/ui/realtimeclient/__init__.py +756 -0
  385. praisonai/ui/realtimeclient/tools.py +242 -0
  386. praisonai/ui/sql_alchemy.py +710 -0
  387. praisonai/upload_vision.py +140 -0
  388. praisonai/version.py +1 -0
  389. praisonai-3.0.0.dist-info/METADATA +3493 -0
  390. praisonai-3.0.0.dist-info/RECORD +393 -0
  391. praisonai-3.0.0.dist-info/WHEEL +5 -0
  392. praisonai-3.0.0.dist-info/entry_points.txt +4 -0
  393. praisonai-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,564 @@
1
+ """
2
+ SQLite persistence layer for the PraisonAI Queue System.
3
+
4
+ Provides crash recovery, session persistence, and run history.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import os
10
+ import sqlite3
11
+ import threading
12
+ import time
13
+ from contextlib import contextmanager
14
+ from pathlib import Path
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ from .models import QueuedRun, RunState, RunPriority, QueueStats
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ SCHEMA_VERSION = "1.0"
22
+
23
+ SCHEMA_SQL = """
24
+ -- Schema version tracking
25
+ CREATE TABLE IF NOT EXISTS schema_version (
26
+ version TEXT PRIMARY KEY,
27
+ applied_at REAL NOT NULL
28
+ );
29
+
30
+ -- Queue runs
31
+ CREATE TABLE IF NOT EXISTS runs (
32
+ run_id TEXT PRIMARY KEY,
33
+ agent_name TEXT NOT NULL,
34
+ input_content TEXT,
35
+ output_content TEXT,
36
+ state TEXT NOT NULL DEFAULT 'queued',
37
+ priority INTEGER NOT NULL DEFAULT 1,
38
+ session_id TEXT,
39
+ trace_id TEXT,
40
+ workspace TEXT,
41
+ user_id TEXT,
42
+ created_at REAL NOT NULL,
43
+ started_at REAL,
44
+ ended_at REAL,
45
+ error TEXT,
46
+ retry_count INTEGER DEFAULT 0,
47
+ max_retries INTEGER DEFAULT 3,
48
+ parent_run_id TEXT,
49
+ config TEXT,
50
+ metrics TEXT,
51
+ recovered INTEGER DEFAULT 0
52
+ );
53
+
54
+ CREATE INDEX IF NOT EXISTS idx_runs_state ON runs(state);
55
+ CREATE INDEX IF NOT EXISTS idx_runs_session ON runs(session_id);
56
+ CREATE INDEX IF NOT EXISTS idx_runs_priority_created ON runs(priority DESC, created_at ASC);
57
+ CREATE INDEX IF NOT EXISTS idx_runs_workspace ON runs(workspace);
58
+
59
+ -- Messages (chat history per run)
60
+ CREATE TABLE IF NOT EXISTS messages (
61
+ message_id TEXT PRIMARY KEY,
62
+ run_id TEXT REFERENCES runs(run_id),
63
+ role TEXT NOT NULL,
64
+ content TEXT NOT NULL,
65
+ timestamp REAL NOT NULL,
66
+ metadata TEXT
67
+ );
68
+
69
+ CREATE INDEX IF NOT EXISTS idx_messages_run ON messages(run_id);
70
+
71
+ -- Tool calls
72
+ CREATE TABLE IF NOT EXISTS tool_calls (
73
+ call_id TEXT PRIMARY KEY,
74
+ run_id TEXT REFERENCES runs(run_id),
75
+ tool_name TEXT NOT NULL,
76
+ args TEXT,
77
+ result TEXT,
78
+ started_at REAL NOT NULL,
79
+ ended_at REAL,
80
+ error TEXT
81
+ );
82
+
83
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_run ON tool_calls(run_id);
84
+
85
+ -- Sessions
86
+ CREATE TABLE IF NOT EXISTS sessions (
87
+ session_id TEXT PRIMARY KEY,
88
+ user_id TEXT,
89
+ created_at REAL NOT NULL,
90
+ updated_at REAL NOT NULL,
91
+ state TEXT,
92
+ config TEXT
93
+ );
94
+
95
+ CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
96
+ """
97
+
98
+
99
+ class QueuePersistence:
100
+ """SQLite-backed persistence for the queue system."""
101
+
102
+ def __init__(self, db_path: str = ".praison/queue.db"):
103
+ """
104
+ Initialize persistence layer.
105
+
106
+ Args:
107
+ db_path: Path to SQLite database file.
108
+ """
109
+ self.db_path = db_path
110
+ self._conn: Optional[sqlite3.Connection] = None
111
+ self._lock = threading.Lock()
112
+ self._initialized = False
113
+
114
+ def _ensure_dir(self) -> None:
115
+ """Ensure database directory exists."""
116
+ db_dir = os.path.dirname(self.db_path)
117
+ if db_dir:
118
+ os.makedirs(db_dir, exist_ok=True)
119
+
120
+ def _get_connection(self) -> sqlite3.Connection:
121
+ """Get or create database connection."""
122
+ if self._conn is None:
123
+ self._ensure_dir()
124
+ self._conn = sqlite3.connect(
125
+ self.db_path,
126
+ check_same_thread=False,
127
+ timeout=30.0
128
+ )
129
+ self._conn.row_factory = sqlite3.Row
130
+ # Enable WAL mode for better concurrency
131
+ self._conn.execute("PRAGMA journal_mode=WAL")
132
+ self._conn.execute("PRAGMA synchronous=NORMAL")
133
+ return self._conn
134
+
135
+ @contextmanager
136
+ def _transaction(self):
137
+ """Context manager for database transactions."""
138
+ conn = self._get_connection()
139
+ with self._lock:
140
+ try:
141
+ yield conn
142
+ conn.commit()
143
+ except Exception:
144
+ conn.rollback()
145
+ raise
146
+
147
+ def initialize(self) -> None:
148
+ """Initialize database schema."""
149
+ if self._initialized:
150
+ return
151
+
152
+ with self._transaction() as conn:
153
+ conn.executescript(SCHEMA_SQL)
154
+
155
+ # Check/set schema version
156
+ cursor = conn.execute(
157
+ "SELECT version FROM schema_version ORDER BY applied_at DESC LIMIT 1"
158
+ )
159
+ row = cursor.fetchone()
160
+
161
+ if row is None:
162
+ conn.execute(
163
+ "INSERT INTO schema_version (version, applied_at) VALUES (?, ?)",
164
+ (SCHEMA_VERSION, time.time())
165
+ )
166
+ elif row["version"] != SCHEMA_VERSION:
167
+ # Future: handle migrations
168
+ logger.warning(
169
+ f"Schema version mismatch: {row['version']} vs {SCHEMA_VERSION}"
170
+ )
171
+
172
+ self._initialized = True
173
+
174
+ def close(self) -> None:
175
+ """Close database connection."""
176
+ if self._conn is not None:
177
+ self._conn.close()
178
+ self._conn = None
179
+ self._initialized = False
180
+
181
+ # Run operations
182
+
183
+ def save_run(self, run: QueuedRun) -> None:
184
+ """Save or update a run."""
185
+ self.initialize()
186
+
187
+ with self._transaction() as conn:
188
+ conn.execute("""
189
+ INSERT OR REPLACE INTO runs (
190
+ run_id, agent_name, input_content, output_content,
191
+ state, priority, session_id, trace_id, workspace, user_id,
192
+ created_at, started_at, ended_at, error,
193
+ retry_count, max_retries, parent_run_id,
194
+ config, metrics
195
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
196
+ """, (
197
+ run.run_id,
198
+ run.agent_name,
199
+ run.input_content,
200
+ run.output_content,
201
+ run.state.value,
202
+ int(run.priority),
203
+ run.session_id,
204
+ run.trace_id,
205
+ run.workspace,
206
+ run.user_id,
207
+ run.created_at,
208
+ run.started_at,
209
+ run.ended_at,
210
+ run.error,
211
+ run.retry_count,
212
+ run.max_retries,
213
+ run.parent_run_id,
214
+ json.dumps(run.config) if run.config else None,
215
+ json.dumps(run.metrics) if run.metrics else None,
216
+ ))
217
+
218
+ def load_run(self, run_id: str) -> Optional[QueuedRun]:
219
+ """Load a run by ID."""
220
+ self.initialize()
221
+
222
+ with self._transaction() as conn:
223
+ cursor = conn.execute(
224
+ "SELECT * FROM runs WHERE run_id = ?",
225
+ (run_id,)
226
+ )
227
+ row = cursor.fetchone()
228
+
229
+ if row is None:
230
+ return None
231
+
232
+ return self._row_to_run(row)
233
+
234
+ def list_runs(
235
+ self,
236
+ state: Optional[RunState] = None,
237
+ session_id: Optional[str] = None,
238
+ workspace: Optional[str] = None,
239
+ limit: int = 100,
240
+ offset: int = 0,
241
+ ) -> List[QueuedRun]:
242
+ """List runs with optional filters."""
243
+ self.initialize()
244
+
245
+ query = "SELECT * FROM runs WHERE 1=1"
246
+ params: List[Any] = []
247
+
248
+ if state is not None:
249
+ query += " AND state = ?"
250
+ params.append(state.value)
251
+
252
+ if session_id is not None:
253
+ query += " AND session_id = ?"
254
+ params.append(session_id)
255
+
256
+ if workspace is not None:
257
+ query += " AND workspace = ?"
258
+ params.append(workspace)
259
+
260
+ query += " ORDER BY priority DESC, created_at ASC LIMIT ? OFFSET ?"
261
+ params.extend([limit, offset])
262
+
263
+ with self._transaction() as conn:
264
+ cursor = conn.execute(query, params)
265
+ return [self._row_to_run(row) for row in cursor.fetchall()]
266
+
267
+ def delete_run(self, run_id: str) -> bool:
268
+ """Delete a run and its related data."""
269
+ self.initialize()
270
+
271
+ with self._transaction() as conn:
272
+ # Delete related data first
273
+ conn.execute("DELETE FROM messages WHERE run_id = ?", (run_id,))
274
+ conn.execute("DELETE FROM tool_calls WHERE run_id = ?", (run_id,))
275
+
276
+ cursor = conn.execute("DELETE FROM runs WHERE run_id = ?", (run_id,))
277
+ return cursor.rowcount > 0
278
+
279
+ def update_run_state(
280
+ self,
281
+ run_id: str,
282
+ state: RunState,
283
+ error: Optional[str] = None,
284
+ output: Optional[str] = None,
285
+ ) -> bool:
286
+ """Update run state."""
287
+ self.initialize()
288
+
289
+ updates = ["state = ?"]
290
+ params: List[Any] = [state.value]
291
+
292
+ if state == RunState.RUNNING:
293
+ updates.append("started_at = ?")
294
+ params.append(time.time())
295
+ elif state.is_terminal():
296
+ updates.append("ended_at = ?")
297
+ params.append(time.time())
298
+
299
+ if error is not None:
300
+ updates.append("error = ?")
301
+ params.append(error)
302
+
303
+ if output is not None:
304
+ updates.append("output_content = ?")
305
+ params.append(output)
306
+
307
+ params.append(run_id)
308
+
309
+ with self._transaction() as conn:
310
+ cursor = conn.execute(
311
+ f"UPDATE runs SET {', '.join(updates)} WHERE run_id = ?",
312
+ params
313
+ )
314
+ return cursor.rowcount > 0
315
+
316
+ # Crash recovery
317
+
318
+ def load_pending_runs(self) -> List[QueuedRun]:
319
+ """Load runs that were QUEUED or RUNNING at crash."""
320
+ self.initialize()
321
+
322
+ with self._transaction() as conn:
323
+ cursor = conn.execute("""
324
+ SELECT * FROM runs
325
+ WHERE state IN ('queued', 'running')
326
+ ORDER BY priority DESC, created_at ASC
327
+ """)
328
+ return [self._row_to_run(row) for row in cursor.fetchall()]
329
+
330
+ def mark_recovered(self, run_id: str) -> None:
331
+ """Mark a run as recovered after restart."""
332
+ self.initialize()
333
+
334
+ with self._transaction() as conn:
335
+ conn.execute(
336
+ "UPDATE runs SET recovered = 1 WHERE run_id = ?",
337
+ (run_id,)
338
+ )
339
+
340
+ def mark_interrupted_as_failed(self) -> int:
341
+ """Mark all RUNNING runs as FAILED (for crash recovery)."""
342
+ self.initialize()
343
+
344
+ with self._transaction() as conn:
345
+ cursor = conn.execute("""
346
+ UPDATE runs
347
+ SET state = 'failed',
348
+ error = 'Interrupted by crash/restart',
349
+ ended_at = ?
350
+ WHERE state = 'running'
351
+ """, (time.time(),))
352
+ return cursor.rowcount
353
+
354
+ # Statistics
355
+
356
+ def get_stats(self, session_id: Optional[str] = None) -> QueueStats:
357
+ """Get queue statistics."""
358
+ self.initialize()
359
+
360
+ with self._transaction() as conn:
361
+ where = ""
362
+ params: List[Any] = []
363
+ if session_id:
364
+ where = "WHERE session_id = ?"
365
+ params = [session_id]
366
+
367
+ # Count by state
368
+ cursor = conn.execute(f"""
369
+ SELECT state, COUNT(*) as count
370
+ FROM runs {where}
371
+ GROUP BY state
372
+ """, params)
373
+
374
+ counts = {row["state"]: row["count"] for row in cursor.fetchall()}
375
+
376
+ # Average wait time
377
+ cursor = conn.execute(f"""
378
+ SELECT AVG(started_at - created_at) as avg_wait
379
+ FROM runs
380
+ {where + ' AND' if where else 'WHERE'} started_at IS NOT NULL
381
+ """, params)
382
+ row = cursor.fetchone()
383
+ avg_wait = row["avg_wait"] or 0.0
384
+
385
+ # Average duration
386
+ cursor = conn.execute(f"""
387
+ SELECT AVG(ended_at - started_at) as avg_duration
388
+ FROM runs
389
+ {where + ' AND' if where else 'WHERE'} ended_at IS NOT NULL AND started_at IS NOT NULL
390
+ """, params)
391
+ row = cursor.fetchone()
392
+ avg_duration = row["avg_duration"] or 0.0
393
+
394
+ return QueueStats(
395
+ queued_count=counts.get("queued", 0),
396
+ running_count=counts.get("running", 0),
397
+ succeeded_count=counts.get("succeeded", 0),
398
+ failed_count=counts.get("failed", 0),
399
+ cancelled_count=counts.get("cancelled", 0),
400
+ total_runs=sum(counts.values()),
401
+ avg_wait_seconds=avg_wait,
402
+ avg_duration_seconds=avg_duration,
403
+ )
404
+
405
+ # Session operations
406
+
407
+ def save_session(
408
+ self,
409
+ session_id: str,
410
+ user_id: Optional[str] = None,
411
+ state: Optional[Dict[str, Any]] = None,
412
+ config: Optional[Dict[str, Any]] = None,
413
+ ) -> None:
414
+ """Save or update a session."""
415
+ self.initialize()
416
+
417
+ now = time.time()
418
+
419
+ with self._transaction() as conn:
420
+ # Check if exists
421
+ cursor = conn.execute(
422
+ "SELECT session_id FROM sessions WHERE session_id = ?",
423
+ (session_id,)
424
+ )
425
+ exists = cursor.fetchone() is not None
426
+
427
+ if exists:
428
+ conn.execute("""
429
+ UPDATE sessions SET
430
+ user_id = COALESCE(?, user_id),
431
+ updated_at = ?,
432
+ state = COALESCE(?, state),
433
+ config = COALESCE(?, config)
434
+ WHERE session_id = ?
435
+ """, (
436
+ user_id,
437
+ now,
438
+ json.dumps(state) if state else None,
439
+ json.dumps(config) if config else None,
440
+ session_id,
441
+ ))
442
+ else:
443
+ conn.execute("""
444
+ INSERT INTO sessions (session_id, user_id, created_at, updated_at, state, config)
445
+ VALUES (?, ?, ?, ?, ?, ?)
446
+ """, (
447
+ session_id,
448
+ user_id,
449
+ now,
450
+ now,
451
+ json.dumps(state) if state else None,
452
+ json.dumps(config) if config else None,
453
+ ))
454
+
455
+ def load_session(self, session_id: str) -> Optional[Dict[str, Any]]:
456
+ """Load a session."""
457
+ self.initialize()
458
+
459
+ with self._transaction() as conn:
460
+ cursor = conn.execute(
461
+ "SELECT * FROM sessions WHERE session_id = ?",
462
+ (session_id,)
463
+ )
464
+ row = cursor.fetchone()
465
+
466
+ if row is None:
467
+ return None
468
+
469
+ return {
470
+ "session_id": row["session_id"],
471
+ "user_id": row["user_id"],
472
+ "created_at": row["created_at"],
473
+ "updated_at": row["updated_at"],
474
+ "state": json.loads(row["state"]) if row["state"] else None,
475
+ "config": json.loads(row["config"]) if row["config"] else None,
476
+ }
477
+
478
+ def list_sessions(self, limit: int = 50) -> List[Dict[str, Any]]:
479
+ """List recent sessions."""
480
+ self.initialize()
481
+
482
+ with self._transaction() as conn:
483
+ cursor = conn.execute("""
484
+ SELECT * FROM sessions
485
+ ORDER BY updated_at DESC
486
+ LIMIT ?
487
+ """, (limit,))
488
+
489
+ return [
490
+ {
491
+ "session_id": row["session_id"],
492
+ "user_id": row["user_id"],
493
+ "created_at": row["created_at"],
494
+ "updated_at": row["updated_at"],
495
+ "state": json.loads(row["state"]) if row["state"] else None,
496
+ "config": json.loads(row["config"]) if row["config"] else None,
497
+ }
498
+ for row in cursor.fetchall()
499
+ ]
500
+
501
+ # Helper methods
502
+
503
+ def _row_to_run(self, row: sqlite3.Row) -> QueuedRun:
504
+ """Convert database row to QueuedRun."""
505
+ return QueuedRun(
506
+ run_id=row["run_id"],
507
+ agent_name=row["agent_name"],
508
+ input_content=row["input_content"] or "",
509
+ output_content=row["output_content"],
510
+ state=RunState(row["state"]),
511
+ priority=RunPriority(row["priority"]),
512
+ session_id=row["session_id"],
513
+ trace_id=row["trace_id"],
514
+ workspace=row["workspace"],
515
+ user_id=row["user_id"],
516
+ created_at=row["created_at"],
517
+ started_at=row["started_at"],
518
+ ended_at=row["ended_at"],
519
+ error=row["error"],
520
+ retry_count=row["retry_count"] or 0,
521
+ max_retries=row["max_retries"] or 3,
522
+ parent_run_id=row["parent_run_id"],
523
+ config=json.loads(row["config"]) if row["config"] else {},
524
+ metrics=json.loads(row["metrics"]) if row["metrics"] else {},
525
+ )
526
+
527
+ # Cleanup
528
+
529
+ def cleanup_old_runs(self, days: int = 30) -> int:
530
+ """Delete runs older than specified days."""
531
+ self.initialize()
532
+
533
+ cutoff = time.time() - (days * 24 * 60 * 60)
534
+
535
+ with self._transaction() as conn:
536
+ # Get run IDs to delete
537
+ cursor = conn.execute("""
538
+ SELECT run_id FROM runs
539
+ WHERE created_at < ? AND state IN ('succeeded', 'failed', 'cancelled')
540
+ """, (cutoff,))
541
+ run_ids = [row["run_id"] for row in cursor.fetchall()]
542
+
543
+ if not run_ids:
544
+ return 0
545
+
546
+ placeholders = ",".join("?" * len(run_ids))
547
+
548
+ # Delete related data
549
+ conn.execute(
550
+ f"DELETE FROM messages WHERE run_id IN ({placeholders})",
551
+ run_ids
552
+ )
553
+ conn.execute(
554
+ f"DELETE FROM tool_calls WHERE run_id IN ({placeholders})",
555
+ run_ids
556
+ )
557
+
558
+ # Delete runs
559
+ cursor = conn.execute(
560
+ f"DELETE FROM runs WHERE run_id IN ({placeholders})",
561
+ run_ids
562
+ )
563
+
564
+ return cursor.rowcount