clawed 2.3.5__tar.gz → 2.3.7__tar.gz

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 (248) hide show
  1. {clawed-2.3.5 → clawed-2.3.7}/PKG-INFO +16 -14
  2. {clawed-2.3.5 → clawed-2.3.7}/README.md +11 -11
  3. {clawed-2.3.5 → clawed-2.3.7}/clawed/__init__.py +1 -1
  4. {clawed-2.3.5 → clawed-2.3.7}/clawed/_legacy_gateway.py +11 -9
  5. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/context.py +13 -1
  6. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/core.py +22 -24
  7. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/memory/curriculum_kb.py +45 -1
  8. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/memory/embeddings.py +13 -4
  9. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/prompt.py +3 -0
  10. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/generate_lesson_bundle.py +6 -0
  11. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/ingest_materials.py +10 -1
  12. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/read_workspace.py +3 -1
  13. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/search_my_materials.py +15 -2
  14. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/server.py +32 -18
  15. {clawed-2.3.5 → clawed-2.3.7}/clawed/asset_registry.py +57 -8
  16. clawed-2.3.7/clawed/handlers/ingest.py +283 -0
  17. {clawed-2.3.5 → clawed-2.3.7}/clawed/image_pipeline.py +7 -5
  18. {clawed-2.3.5 → clawed-2.3.7}/clawed/ingestor.py +291 -1
  19. {clawed-2.3.5 → clawed-2.3.7}/clawed/lesson.py +2 -2
  20. {clawed-2.3.5 → clawed-2.3.7}/clawed/llm.py +10 -4
  21. {clawed-2.3.5 → clawed-2.3.7}/clawed/master_content.py +18 -0
  22. {clawed-2.3.5 → clawed-2.3.7}/clawed/model_router.py +4 -3
  23. {clawed-2.3.5 → clawed-2.3.7}/clawed/models.py +1 -0
  24. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/master_content.txt +43 -4
  25. {clawed-2.3.5 → clawed-2.3.7}/clawed/slide_images.py +239 -81
  26. {clawed-2.3.5 → clawed-2.3.7}/clawed/tools.py +3 -2
  27. {clawed-2.3.5 → clawed-2.3.7}/clawed/transports/telegram.py +14 -1
  28. {clawed-2.3.5 → clawed-2.3.7}/pyproject.toml +4 -3
  29. clawed-2.3.5/clawed/handlers/ingest.py +0 -153
  30. {clawed-2.3.5 → clawed-2.3.7}/.gitignore +0 -0
  31. {clawed-2.3.5 → clawed-2.3.7}/LICENSE +0 -0
  32. {clawed-2.3.5 → clawed-2.3.7}/clawed/__main__.py +0 -0
  33. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent.py +0 -0
  34. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/__init__.py +0 -0
  35. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/approvals.py +0 -0
  36. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/autonomy.py +0 -0
  37. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/custom_tools.py +0 -0
  38. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/drive/__init__.py +0 -0
  39. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/drive/auth.py +0 -0
  40. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/drive/client.py +0 -0
  41. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/fake_llm.py +0 -0
  42. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/loop.py +0 -0
  43. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/memory/__init__.py +0 -0
  44. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/memory/curriculum.py +0 -0
  45. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/memory/episodes.py +0 -0
  46. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/memory/identity.py +0 -0
  47. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/memory/loader.py +0 -0
  48. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/memory/preferences.py +0 -0
  49. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/planner.py +0 -0
  50. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/scheduler.py +0 -0
  51. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/__init__.py +0 -0
  52. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/base.py +0 -0
  53. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/configure_profile.py +0 -0
  54. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/curriculum_map.py +0 -0
  55. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/drive_create_doc.py +0 -0
  56. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/drive_create_slides.py +0 -0
  57. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/drive_list.py +0 -0
  58. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/drive_organize.py +0 -0
  59. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/drive_read.py +0 -0
  60. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/drive_upload.py +0 -0
  61. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/export_document.py +0 -0
  62. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/gap_analysis.py +0 -0
  63. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/generate_assessment.py +0 -0
  64. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/generate_lesson.py +0 -0
  65. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/generate_materials.py +0 -0
  66. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/generate_unit.py +0 -0
  67. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/parent_comm.py +0 -0
  68. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/read_heartbeat.py +0 -0
  69. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/request_approval.py +0 -0
  70. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/schedule_task.py +0 -0
  71. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/search_lessons.py +0 -0
  72. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/search_standards.py +0 -0
  73. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/student_insights.py +0 -0
  74. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/sub_packet.py +0 -0
  75. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/switch_model.py +0 -0
  76. {clawed-2.3.5 → clawed-2.3.7}/clawed/agent_core/tools/update_soul.py +0 -0
  77. {clawed-2.3.5 → clawed-2.3.7}/clawed/analytics.py +0 -0
  78. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/__init__.py +0 -0
  79. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/deps.py +0 -0
  80. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/routes/__init__.py +0 -0
  81. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/routes/chat.py +0 -0
  82. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/routes/export.py +0 -0
  83. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/routes/feedback.py +0 -0
  84. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/routes/gateway_chat.py +0 -0
  85. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/routes/generate.py +0 -0
  86. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/routes/ingest.py +0 -0
  87. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/routes/lessons.py +0 -0
  88. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/routes/school.py +0 -0
  89. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/routes/settings.py +0 -0
  90. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/routes/tools.py +0 -0
  91. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/static/app.js +0 -0
  92. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/static/style.css +0 -0
  93. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/static/widget.js +0 -0
  94. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/templates/analytics.html +0 -0
  95. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/templates/base.html +0 -0
  96. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/templates/dashboard.html +0 -0
  97. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/templates/generate.html +0 -0
  98. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/templates/index.html +0 -0
  99. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/templates/lesson.html +0 -0
  100. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/templates/profile.html +0 -0
  101. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/templates/settings.html +0 -0
  102. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/templates/stats.html +0 -0
  103. {clawed-2.3.5 → clawed-2.3.7}/clawed/api/templates/students.html +0 -0
  104. {clawed-2.3.5 → clawed-2.3.7}/clawed/assessment.py +0 -0
  105. {clawed-2.3.5 → clawed-2.3.7}/clawed/auth/__init__.py +0 -0
  106. {clawed-2.3.5 → clawed-2.3.7}/clawed/auth/google_auth.py +0 -0
  107. {clawed-2.3.5 → clawed-2.3.7}/clawed/bot_state.py +0 -0
  108. {clawed-2.3.5 → clawed-2.3.7}/clawed/chat.py +0 -0
  109. {clawed-2.3.5 → clawed-2.3.7}/clawed/cli.py +0 -0
  110. {clawed-2.3.5 → clawed-2.3.7}/clawed/cli_chat.py +0 -0
  111. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/__init__.py +0 -0
  112. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/_helpers.py +0 -0
  113. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/bot.py +0 -0
  114. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/config.py +0 -0
  115. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/config_llm.py +0 -0
  116. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/config_profile.py +0 -0
  117. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/export.py +0 -0
  118. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/generate.py +0 -0
  119. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/generate_assessment.py +0 -0
  120. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/generate_unit.py +0 -0
  121. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/queue.py +0 -0
  122. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/schedule_cmd.py +0 -0
  123. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/sub.py +0 -0
  124. {clawed-2.3.5 → clawed-2.3.7}/clawed/commands/workspace_cmd.py +0 -0
  125. {clawed-2.3.5 → clawed-2.3.7}/clawed/compile_slides.py +0 -0
  126. {clawed-2.3.5 → clawed-2.3.7}/clawed/compile_student.py +0 -0
  127. {clawed-2.3.5 → clawed-2.3.7}/clawed/compile_teacher.py +0 -0
  128. {clawed-2.3.5 → clawed-2.3.7}/clawed/config.py +0 -0
  129. {clawed-2.3.5 → clawed-2.3.7}/clawed/corpus.py +0 -0
  130. {clawed-2.3.5 → clawed-2.3.7}/clawed/curriculum_map.py +0 -0
  131. {clawed-2.3.5 → clawed-2.3.7}/clawed/database.py +0 -0
  132. {clawed-2.3.5 → clawed-2.3.7}/clawed/demo/__init__.py +0 -0
  133. {clawed-2.3.5 → clawed-2.3.7}/clawed/demo/demo_assessment.json +0 -0
  134. {clawed-2.3.5 → clawed-2.3.7}/clawed/demo/demo_formative_assessment.json +0 -0
  135. {clawed-2.3.5 → clawed-2.3.7}/clawed/demo/demo_lesson_materials.json +0 -0
  136. {clawed-2.3.5 → clawed-2.3.7}/clawed/demo/demo_lesson_science_g6.json +0 -0
  137. {clawed-2.3.5 → clawed-2.3.7}/clawed/demo/demo_lesson_social_studies_g8.json +0 -0
  138. {clawed-2.3.5 → clawed-2.3.7}/clawed/demo/demo_master_content.json +0 -0
  139. {clawed-2.3.5 → clawed-2.3.7}/clawed/demo/demo_pacing_guide.json +0 -0
  140. {clawed-2.3.5 → clawed-2.3.7}/clawed/demo/demo_quiz.json +0 -0
  141. {clawed-2.3.5 → clawed-2.3.7}/clawed/demo/demo_rubric.json +0 -0
  142. {clawed-2.3.5 → clawed-2.3.7}/clawed/demo/demo_unit_plan.json +0 -0
  143. {clawed-2.3.5 → clawed-2.3.7}/clawed/demo/demo_year_map.json +0 -0
  144. {clawed-2.3.5 → clawed-2.3.7}/clawed/differentiation.py +0 -0
  145. {clawed-2.3.5 → clawed-2.3.7}/clawed/doc_export.py +0 -0
  146. {clawed-2.3.5 → clawed-2.3.7}/clawed/drive.py +0 -0
  147. {clawed-2.3.5 → clawed-2.3.7}/clawed/evaluation.py +0 -0
  148. {clawed-2.3.5 → clawed-2.3.7}/clawed/export_docx.py +0 -0
  149. {clawed-2.3.5 → clawed-2.3.7}/clawed/export_handout.py +0 -0
  150. {clawed-2.3.5 → clawed-2.3.7}/clawed/export_markdown.py +0 -0
  151. {clawed-2.3.5 → clawed-2.3.7}/clawed/export_pdf.py +0 -0
  152. {clawed-2.3.5 → clawed-2.3.7}/clawed/export_pptx.py +0 -0
  153. {clawed-2.3.5 → clawed-2.3.7}/clawed/export_templates.py +0 -0
  154. {clawed-2.3.5 → clawed-2.3.7}/clawed/export_theme.py +0 -0
  155. {clawed-2.3.5 → clawed-2.3.7}/clawed/exporter.py +0 -0
  156. {clawed-2.3.5 → clawed-2.3.7}/clawed/feedback.py +0 -0
  157. {clawed-2.3.5 → clawed-2.3.7}/clawed/formats/__init__.py +0 -0
  158. {clawed-2.3.5 → clawed-2.3.7}/clawed/formats/flipchart.py +0 -0
  159. {clawed-2.3.5 → clawed-2.3.7}/clawed/formats/notebook.py +0 -0
  160. {clawed-2.3.5 → clawed-2.3.7}/clawed/formats/xbk.py +0 -0
  161. {clawed-2.3.5 → clawed-2.3.7}/clawed/gateway.py +0 -0
  162. {clawed-2.3.5 → clawed-2.3.7}/clawed/gateway_response.py +0 -0
  163. {clawed-2.3.5 → clawed-2.3.7}/clawed/generation.py +0 -0
  164. {clawed-2.3.5 → clawed-2.3.7}/clawed/generation_report.py +0 -0
  165. {clawed-2.3.5 → clawed-2.3.7}/clawed/handlers/__init__.py +0 -0
  166. {clawed-2.3.5 → clawed-2.3.7}/clawed/handlers/export.py +0 -0
  167. {clawed-2.3.5 → clawed-2.3.7}/clawed/handlers/feedback.py +0 -0
  168. {clawed-2.3.5 → clawed-2.3.7}/clawed/handlers/gaps.py +0 -0
  169. {clawed-2.3.5 → clawed-2.3.7}/clawed/handlers/generate.py +0 -0
  170. {clawed-2.3.5 → clawed-2.3.7}/clawed/handlers/misc.py +0 -0
  171. {clawed-2.3.5 → clawed-2.3.7}/clawed/handlers/onboard.py +0 -0
  172. {clawed-2.3.5 → clawed-2.3.7}/clawed/handlers/schedule.py +0 -0
  173. {clawed-2.3.5 → clawed-2.3.7}/clawed/handlers/standards.py +0 -0
  174. {clawed-2.3.5 → clawed-2.3.7}/clawed/improver.py +0 -0
  175. {clawed-2.3.5 → clawed-2.3.7}/clawed/io.py +0 -0
  176. {clawed-2.3.5 → clawed-2.3.7}/clawed/materials.py +0 -0
  177. {clawed-2.3.5 → clawed-2.3.7}/clawed/mcp_server.py +0 -0
  178. {clawed-2.3.5 → clawed-2.3.7}/clawed/memory_engine.py +0 -0
  179. {clawed-2.3.5 → clawed-2.3.7}/clawed/onboarding.py +0 -0
  180. {clawed-2.3.5 → clawed-2.3.7}/clawed/openclaw_plugin.py +0 -0
  181. {clawed-2.3.5 → clawed-2.3.7}/clawed/parent_comm.py +0 -0
  182. {clawed-2.3.5 → clawed-2.3.7}/clawed/persona.py +0 -0
  183. {clawed-2.3.5 → clawed-2.3.7}/clawed/persona_evolution.py +0 -0
  184. {clawed-2.3.5 → clawed-2.3.7}/clawed/planner.py +0 -0
  185. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/504_accommodations.txt +0 -0
  186. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/admin_lesson_plan.txt +0 -0
  187. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/assessment.txt +0 -0
  188. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/curriculum_gaps.txt +0 -0
  189. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/dbq_assessment.txt +0 -0
  190. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/differentiation.txt +0 -0
  191. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/formative_assessment.txt +0 -0
  192. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/iep_modification.txt +0 -0
  193. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/lesson_plan.txt +0 -0
  194. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/pacing_guide.txt +0 -0
  195. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/parent_note.txt +0 -0
  196. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/persona_extract.txt +0 -0
  197. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/quiz.txt +0 -0
  198. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/rubric.txt +0 -0
  199. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/student_packet.txt +0 -0
  200. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/sub_packet.txt +0 -0
  201. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/summative_assessment.txt +0 -0
  202. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/tiered_assignments.txt +0 -0
  203. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/unit_plan.txt +0 -0
  204. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/worksheet.txt +0 -0
  205. {clawed-2.3.5 → clawed-2.3.7}/clawed/prompts/year_map.txt +0 -0
  206. {clawed-2.3.5 → clawed-2.3.7}/clawed/quality.py +0 -0
  207. {clawed-2.3.5 → clawed-2.3.7}/clawed/reading_report.py +0 -0
  208. {clawed-2.3.5 → clawed-2.3.7}/clawed/router.py +0 -0
  209. {clawed-2.3.5 → clawed-2.3.7}/clawed/sanitize.py +0 -0
  210. {clawed-2.3.5 → clawed-2.3.7}/clawed/scheduler.py +0 -0
  211. {clawed-2.3.5 → clawed-2.3.7}/clawed/school.py +0 -0
  212. {clawed-2.3.5 → clawed-2.3.7}/clawed/search.py +0 -0
  213. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/__init__.py +0 -0
  214. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/art.py +0 -0
  215. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/base.py +0 -0
  216. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/computer_science.py +0 -0
  217. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/ela.py +0 -0
  218. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/foreign_language.py +0 -0
  219. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/history.py +0 -0
  220. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/library.py +0 -0
  221. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/math.py +0 -0
  222. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/music.py +0 -0
  223. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/physical_education.py +0 -0
  224. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/science.py +0 -0
  225. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/social_studies.py +0 -0
  226. {clawed-2.3.5 → clawed-2.3.7}/clawed/skills/special_education.py +0 -0
  227. {clawed-2.3.5 → clawed-2.3.7}/clawed/standards.py +0 -0
  228. {clawed-2.3.5 → clawed-2.3.7}/clawed/state.py +0 -0
  229. {clawed-2.3.5 → clawed-2.3.7}/clawed/state_standards.py +0 -0
  230. {clawed-2.3.5 → clawed-2.3.7}/clawed/student_bot.py +0 -0
  231. {clawed-2.3.5 → clawed-2.3.7}/clawed/student_cli.py +0 -0
  232. {clawed-2.3.5 → clawed-2.3.7}/clawed/student_telegram_bot.py +0 -0
  233. {clawed-2.3.5 → clawed-2.3.7}/clawed/sub_packet.py +0 -0
  234. {clawed-2.3.5 → clawed-2.3.7}/clawed/task_queue.py +0 -0
  235. {clawed-2.3.5 → clawed-2.3.7}/clawed/templates_lib.py +0 -0
  236. {clawed-2.3.5 → clawed-2.3.7}/clawed/transports/__init__.py +0 -0
  237. {clawed-2.3.5 → clawed-2.3.7}/clawed/transports/cli.py +0 -0
  238. {clawed-2.3.5 → clawed-2.3.7}/clawed/transports/openclaw.py +0 -0
  239. {clawed-2.3.5 → clawed-2.3.7}/clawed/transports/student_telegram.py +0 -0
  240. {clawed-2.3.5 → clawed-2.3.7}/clawed/transports/web.py +0 -0
  241. {clawed-2.3.5 → clawed-2.3.7}/clawed/tui.py +0 -0
  242. {clawed-2.3.5 → clawed-2.3.7}/clawed/tui_chat.py +0 -0
  243. {clawed-2.3.5 → clawed-2.3.7}/clawed/validation.py +0 -0
  244. {clawed-2.3.5 → clawed-2.3.7}/clawed/voice.py +0 -0
  245. {clawed-2.3.5 → clawed-2.3.7}/clawed/voice_check.py +0 -0
  246. {clawed-2.3.5 → clawed-2.3.7}/clawed/workspace.py +0 -0
  247. {clawed-2.3.5 → clawed-2.3.7}/eduagent/__init__.py +0 -0
  248. {clawed-2.3.5 → clawed-2.3.7}/eduagent/_compat.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clawed
3
- Version: 2.3.5
3
+ Version: 2.3.7
4
4
  Summary: Claw-ED — personal AI teaching agent. Learns your voice, works while you sleep.
5
5
  Project-URL: Homepage, https://github.com/SirhanMacx/Claw-ED
6
6
  Project-URL: Documentation, https://github.com/SirhanMacx/Claw-ED#readme
@@ -18,14 +18,13 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Topic :: Education
20
20
  Requires-Python: >=3.10
21
- Requires-Dist: anthropic<1.0,>=0.40.0
22
21
  Requires-Dist: apscheduler<4.0,>=3.10.0
23
22
  Requires-Dist: fastapi<1.0,>=0.110.0
24
23
  Requires-Dist: httpx<1.0,>=0.25.0
25
24
  Requires-Dist: jinja2>=3.1.0
26
25
  Requires-Dist: json-repair>=0.30.0
26
+ Requires-Dist: lxml>=4.9.0
27
27
  Requires-Dist: mcp>=1.0.0
28
- Requires-Dist: openai>=1.0.0
29
28
  Requires-Dist: pydantic<3.0,>=2.0.0
30
29
  Requires-Dist: pymupdf>=1.23.0
31
30
  Requires-Dist: python-docx>=1.0.0
@@ -41,6 +40,7 @@ Provides-Extra: all
41
40
  Requires-Dist: faster-whisper>=0.10.0; extra == 'all'
42
41
  Requires-Dist: keyring>=24.0.0; extra == 'all'
43
42
  Requires-Dist: onnxruntime>=1.16.0; extra == 'all'
43
+ Requires-Dist: qrcode[pil]>=7.0; extra == 'all'
44
44
  Requires-Dist: textual>=0.56.0; extra == 'all'
45
45
  Requires-Dist: uvicorn[standard]>=0.27.0; extra == 'all'
46
46
  Provides-Extra: dev
@@ -59,6 +59,8 @@ Provides-Extra: memory
59
59
  Requires-Dist: onnxruntime>=1.16.0; extra == 'memory'
60
60
  Provides-Extra: pdf
61
61
  Requires-Dist: weasyprint>=60.0; extra == 'pdf'
62
+ Provides-Extra: qr
63
+ Requires-Dist: qrcode[pil]>=7.0; extra == 'qr'
62
64
  Provides-Extra: tui
63
65
  Requires-Dist: textual>=0.56.0; extra == 'tui'
64
66
  Provides-Extra: voice
@@ -80,23 +82,23 @@ Built on the OpenClaw agent framework. Open source. MIT license.
80
82
 
81
83
  ---
82
84
 
83
- ## What's new in v2.3.5
85
+ ## What's new in v2.3.7
84
86
 
85
- **Master Content Track.** One LLM call generates a single `MasterContent` object. Three output documents are compiled mechanically from the same source of truth -- no content drift between what the teacher sees, what students hold, and what's on screen:
87
+ **Real images in every lesson.** Image specs are now required for every primary source and instruction section across all subjects. The LLM generates specific search queries ("Thomas Nast Boss Tweed political cartoon 1871") instead of leaving the field blank. Teacher images are found first using a three-stage progressive search (full query, individual keywords, subject fallback) with filename-weighted scoring across up to 150 candidates. External sources (Library of Congress, Wikimedia Commons, Unsplash) fill in the rest with subject-aware routing.
86
88
 
87
- 1. **Teacher DOCX** Full answer keys, scripted teacher language, guided notes with answers filled in, station answer keys, differentiation notes. Observation-ready.
88
- 2. **Student DOCX** — Same structure, blanks instead of answers. Guided notes with fill-in lines, station directions without answer keys, exit ticket without expected responses. Print and hand out.
89
- 3. **Slideshow PPTX** — Widescreen academic slides: title, vocabulary cards, instruction sections with images, primary source analysis, station overview, stimulus-based exit ticket.
89
+ **12 new file formats.** Your old `.doc`, `.ppt`, `.xls`, `.xlsx`, `.csv`, `.rtf`, `.html`, `.odt`, and `.odp` files are now parsed and indexed. Previously only 8 formats were supported -- teachers' archives spanning decades of file formats were 93% invisible to search. Now they're searchable.
90
90
 
91
- **Zero silent failures.** All 11 generators use `safe_generate_json()` with automatic retry on validation errors. Post-generation validators catch empty outputs, topic drift, and delegation phrases. Quality review fails closed. CLI shows warnings, not raw tracebacks.
91
+ **Search actually works.** Three fixes to the search pipeline: cross-transport teacher ID fallback (files ingested via CLI now appear in Telegram searches), asset search errors are logged instead of silently swallowed, and the agent is explicitly instructed to surface results to you. Topic tags are auto-extracted from filenames and content for better matching.
92
92
 
93
- **Stimulus-based assessment.** Every question -- Do Now, guided notes, stations, exit ticket -- must be anchored to a stimulus (source text, data, diagram, scenario). Bare recall questions are banned at the prompt level.
93
+ **Background file ingestion.** Send your files and keep chatting. The bot acknowledges immediately, processes everything in a background thread, sends progress updates ("Indexed 50/200 documents..."), and a summary when done. Max 3 concurrent ingestions, individual file failures don't abort the batch.
94
94
 
95
- **Identity protection.** Onboarding only triggers on explicit `/setup`. Profile fields are validated and truncated. SOUL.md writes are audit-logged and capped at 500 chars.
95
+ **DEEP-tier model for lesson generation.** MasterContent routes to the DEEP tier. With a capable model (Claude Sonnet 4.6, GPT-4o), lesson quality improves dramatically.
96
96
 
97
- **Your files are first-class.** Teacher materials are searched (AssetRegistry + CurriculumKB) before every generation -- not just in the bundle tool, but in standalone lesson and unit generation too.
97
+ **Security hardened.** Path traversal protection on all file-reading tools. XSS escaping on the web dashboard. Thread-safe tool definitions. ZIP bomb protection. Debug info no longer leaked to users. Ingest paths restricted to home directory.
98
98
 
99
- **Parallel image pipeline.** All image specs from the MasterContent are fetched in parallel with configurable timeout and local caching.
99
+ **50 MB lighter.** Removed unused `anthropic` and `openai` SDK dependencies. API key resolution unified across all code paths (env var + keyring + secrets file).
100
+
101
+ **Everything from v2.3.5 still applies:** Master Content Track, stimulus-based assessment, zero silent failures, parallel image pipeline, identity protection.
100
102
 
101
103
  ---
102
104
 
@@ -113,7 +115,7 @@ Everything runs on your own computer. Your files never leave your machine unless
113
115
  ## How it works
114
116
 
115
117
  ```
116
- Your files (PDFs, DOCX, PPTX, TXT)
118
+ Your files (PDF, DOCX, PPTX, DOC, PPT, XLS, XLSX, CSV, RTF, HTML, ODT, TXT, and more)
117
119
  |
118
120
  v
119
121
  Claw-ED learns your teaching style
@@ -13,23 +13,23 @@ Built on the OpenClaw agent framework. Open source. MIT license.
13
13
 
14
14
  ---
15
15
 
16
- ## What's new in v2.3.5
16
+ ## What's new in v2.3.7
17
17
 
18
- **Master Content Track.** One LLM call generates a single `MasterContent` object. Three output documents are compiled mechanically from the same source of truth -- no content drift between what the teacher sees, what students hold, and what's on screen:
18
+ **Real images in every lesson.** Image specs are now required for every primary source and instruction section across all subjects. The LLM generates specific search queries ("Thomas Nast Boss Tweed political cartoon 1871") instead of leaving the field blank. Teacher images are found first using a three-stage progressive search (full query, individual keywords, subject fallback) with filename-weighted scoring across up to 150 candidates. External sources (Library of Congress, Wikimedia Commons, Unsplash) fill in the rest with subject-aware routing.
19
19
 
20
- 1. **Teacher DOCX** Full answer keys, scripted teacher language, guided notes with answers filled in, station answer keys, differentiation notes. Observation-ready.
21
- 2. **Student DOCX** — Same structure, blanks instead of answers. Guided notes with fill-in lines, station directions without answer keys, exit ticket without expected responses. Print and hand out.
22
- 3. **Slideshow PPTX** — Widescreen academic slides: title, vocabulary cards, instruction sections with images, primary source analysis, station overview, stimulus-based exit ticket.
20
+ **12 new file formats.** Your old `.doc`, `.ppt`, `.xls`, `.xlsx`, `.csv`, `.rtf`, `.html`, `.odt`, and `.odp` files are now parsed and indexed. Previously only 8 formats were supported -- teachers' archives spanning decades of file formats were 93% invisible to search. Now they're searchable.
23
21
 
24
- **Zero silent failures.** All 11 generators use `safe_generate_json()` with automatic retry on validation errors. Post-generation validators catch empty outputs, topic drift, and delegation phrases. Quality review fails closed. CLI shows warnings, not raw tracebacks.
22
+ **Search actually works.** Three fixes to the search pipeline: cross-transport teacher ID fallback (files ingested via CLI now appear in Telegram searches), asset search errors are logged instead of silently swallowed, and the agent is explicitly instructed to surface results to you. Topic tags are auto-extracted from filenames and content for better matching.
25
23
 
26
- **Stimulus-based assessment.** Every question -- Do Now, guided notes, stations, exit ticket -- must be anchored to a stimulus (source text, data, diagram, scenario). Bare recall questions are banned at the prompt level.
24
+ **Background file ingestion.** Send your files and keep chatting. The bot acknowledges immediately, processes everything in a background thread, sends progress updates ("Indexed 50/200 documents..."), and a summary when done. Max 3 concurrent ingestions, individual file failures don't abort the batch.
27
25
 
28
- **Identity protection.** Onboarding only triggers on explicit `/setup`. Profile fields are validated and truncated. SOUL.md writes are audit-logged and capped at 500 chars.
26
+ **DEEP-tier model for lesson generation.** MasterContent routes to the DEEP tier. With a capable model (Claude Sonnet 4.6, GPT-4o), lesson quality improves dramatically.
29
27
 
30
- **Your files are first-class.** Teacher materials are searched (AssetRegistry + CurriculumKB) before every generation -- not just in the bundle tool, but in standalone lesson and unit generation too.
28
+ **Security hardened.** Path traversal protection on all file-reading tools. XSS escaping on the web dashboard. Thread-safe tool definitions. ZIP bomb protection. Debug info no longer leaked to users. Ingest paths restricted to home directory.
31
29
 
32
- **Parallel image pipeline.** All image specs from the MasterContent are fetched in parallel with configurable timeout and local caching.
30
+ **50 MB lighter.** Removed unused `anthropic` and `openai` SDK dependencies. API key resolution unified across all code paths (env var + keyring + secrets file).
31
+
32
+ **Everything from v2.3.5 still applies:** Master Content Track, stimulus-based assessment, zero silent failures, parallel image pipeline, identity protection.
33
33
 
34
34
  ---
35
35
 
@@ -46,7 +46,7 @@ Everything runs on your own computer. Your files never leave your machine unless
46
46
  ## How it works
47
47
 
48
48
  ```
49
- Your files (PDFs, DOCX, PPTX, TXT)
49
+ Your files (PDF, DOCX, PPTX, DOC, PPT, XLS, XLSX, CSV, RTF, HTML, ODT, TXT, and more)
50
50
  |
51
51
  v
52
52
  Claw-ED learns your teaching style
@@ -17,7 +17,7 @@ if hasattr(sys.stderr, "reconfigure"):
17
17
  except Exception:
18
18
  pass
19
19
 
20
- __version__ = "2.3.5"
20
+ __version__ = "2.3.7"
21
21
  __author__ = "Jon Maccarello & Claw-ED contributors"
22
22
  __description__ = "Personal AI teaching agent. Learns your voice, works while you sleep."
23
23
 
@@ -94,7 +94,8 @@ class Gateway:
94
94
  self._model_switch = ModelSwitchHandler()
95
95
 
96
96
  async def handle(self, message: str, teacher_id: str,
97
- files: list[Path] | None = None) -> GatewayResponse:
97
+ files: list[Path] | None = None,
98
+ progress_callback=None) -> GatewayResponse:
98
99
  """Process any message from any transport."""
99
100
  self._stats.messages_today += 1
100
101
  self.active_sessions[teacher_id] = {
@@ -112,31 +113,29 @@ class Gateway:
112
113
  if not has_config():
113
114
  return await self._onboard.step(teacher_id, message)
114
115
 
115
- return await self._dispatch(message, teacher_id, files)
116
+ return await self._dispatch(message, teacher_id, files,
117
+ progress_callback=progress_callback)
116
118
 
117
119
  except Exception as e:
118
120
  logger.warning("Gateway error: %s", e)
121
+ logger.debug("Gateway error detail: %s: %s", type(e).__name__, e)
119
122
  self._stats.errors_today += 1
120
123
  await self.emit("error", {"teacher_id": teacher_id, "message": str(e)})
121
124
 
122
125
  err = str(e).lower()
123
- debug_hint = f"\n\n[Debug: {type(e).__name__}: {str(e)[:200]}]"
124
126
  if "401" in err or "unauthorized" in err or "api key" in err:
125
127
  return GatewayResponse(
126
128
  text="Your AI provider key doesn't seem to be working. "
127
129
  "Run `clawed setup --reset` to reconfigure it."
128
- + debug_hint
129
130
  )
130
131
  if "connection" in err or "connect" in err or "timeout" in err:
131
132
  return GatewayResponse(
132
133
  text="Can't connect to your AI provider right now. "
133
134
  "Check your internet connection and try again."
134
- + debug_hint
135
135
  )
136
136
  return GatewayResponse(
137
137
  text="Something went wrong. Try again, or run "
138
138
  "`clawed setup --reset` to reconfigure."
139
- + debug_hint
140
139
  )
141
140
 
142
141
  async def handle_callback(self, callback_data: str, teacher_id: str) -> GatewayResponse:
@@ -177,13 +176,16 @@ class Gateway:
177
176
  }
178
177
 
179
178
  async def _dispatch(self, message: str, teacher_id: str,
180
- files: list[Path] | None = None) -> GatewayResponse:
179
+ files: list[Path] | None = None,
180
+ progress_callback=None) -> GatewayResponse:
181
181
  """Route a message to the appropriate handler based on intent."""
182
182
  if files:
183
- return await self._ingest.handle(teacher_id, files)
183
+ return await self._ingest.handle(teacher_id, files,
184
+ progress_callback=progress_callback)
184
185
 
185
186
  if self._looks_like_path(message):
186
- return await self._ingest.handle(teacher_id, path=message.strip())
187
+ return await self._ingest.handle(teacher_id, path=message.strip(),
188
+ progress_callback=progress_callback)
187
189
 
188
190
  # NOTE: parse_intent() is keyword/regex-based (zero cost).
189
191
  # When upgraded to LLM-based detection, use:
@@ -1,12 +1,15 @@
1
1
  """Core data types for the agent system."""
2
2
  from __future__ import annotations
3
3
 
4
+ import logging
4
5
  from dataclasses import dataclass, field
5
6
  from pathlib import Path
6
- from typing import Any
7
+ from typing import Any, Callable, Optional
7
8
 
8
9
  from clawed.models import AppConfig
9
10
 
11
+ logger = logging.getLogger(__name__)
12
+
10
13
 
11
14
  @dataclass
12
15
  class AgentContext:
@@ -19,6 +22,15 @@ class AgentContext:
19
22
  session_history: list[dict[str, Any]]
20
23
  improvement_context: str
21
24
  agent_name: str = "Claw-ED"
25
+ progress_callback: Optional[Callable[[str], None]] = None
26
+
27
+ def notify_progress(self, message: str) -> None:
28
+ """Send a progress update to the user if a callback is registered."""
29
+ if self.progress_callback:
30
+ try:
31
+ self.progress_callback(message)
32
+ except Exception as e:
33
+ logger.debug("Progress notification failed: %s", e)
22
34
 
23
35
 
24
36
  @dataclass
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
  import asyncio
11
11
  import json
12
12
  import logging
13
+ import threading
13
14
  import time
14
15
  from datetime import datetime
15
16
  from pathlib import Path
@@ -28,6 +29,9 @@ from clawed.models import AppConfig
28
29
  logger = logging.getLogger(__name__)
29
30
 
30
31
 
32
+ _tool_lock = threading.Lock()
33
+
34
+
31
35
  class _LLMClientAdapter:
32
36
  """Adapts the existing clawed.agent module's LLM calling to LLMInterface.
33
37
 
@@ -45,28 +49,24 @@ class _LLMClientAdapter:
45
49
  tools: list[dict[str, Any]] | None = None,
46
50
  system: str = "",
47
51
  ) -> dict[str, Any]:
48
- # WARNING: This monkey-patches a module-level variable, which is NOT
49
- # safe under concurrent requests. For v1.0 (hosted/multi-teacher),
50
- # refactor the legacy agent functions to accept tool definitions as
51
- # a parameter instead of reading from the module global.
52
- #
53
52
  # The legacy agent functions operate on the global TOOL_DEFINITIONS.
54
- # We temporarily monkey-patch them so the registry schemas are used
55
- # instead. Since these functions read TOOL_DEFINITIONS at call time,
56
- # we swap the module-level list.
53
+ # We temporarily swap them under a lock so concurrent requests don't
54
+ # clobber each other's tool definitions.
57
55
  import clawed.agent as _agent_mod
58
56
  from clawed.agent import _call_with_native_tools, _call_with_ollama_tools
59
57
  from clawed.models import LLMProvider
60
58
 
61
- original_defs = _agent_mod.TOOL_DEFINITIONS
62
- _agent_mod.TOOL_DEFINITIONS = tools or []
59
+ with _tool_lock:
60
+ original_defs = _agent_mod.TOOL_DEFINITIONS
61
+ _agent_mod.TOOL_DEFINITIONS = tools or []
63
62
  try:
64
63
  if self._config.provider in (LLMProvider.ANTHROPIC, LLMProvider.OPENAI):
65
64
  return await _call_with_native_tools(messages, system, self._config)
66
65
  else:
67
66
  return await _call_with_ollama_tools(messages, system, self._config)
68
67
  finally:
69
- _agent_mod.TOOL_DEFINITIONS = original_defs
68
+ with _tool_lock:
69
+ _agent_mod.TOOL_DEFINITIONS = original_defs
70
70
 
71
71
 
72
72
  class Gateway:
@@ -116,6 +116,7 @@ class Gateway:
116
116
  message: str,
117
117
  teacher_id: str,
118
118
  files: list[Path] | None = None,
119
+ progress_callback: Any = None,
119
120
  ) -> GatewayResponse:
120
121
  """Process any message from any transport."""
121
122
  self._stats.messages_today += 1
@@ -128,9 +129,11 @@ class Gateway:
128
129
  })
129
130
 
130
131
  try:
131
- # 1. Files → ingest (deterministic, no LLM)
132
+ # 1. Files → ingest (deterministic, no LLM, runs in background)
132
133
  if files:
133
- return await self._ingest.handle(teacher_id, files)
134
+ return await self._ingest.handle(
135
+ teacher_id, files, progress_callback=progress_callback
136
+ )
134
137
 
135
138
  # 2. Onboarding state machine (deterministic, no LLM)
136
139
  if self._onboard.is_onboarding(teacher_id):
@@ -147,38 +150,32 @@ class Gateway:
147
150
  )
148
151
 
149
152
  # 4. Natural-language → agent loop
150
- return await self._agent_loop(message, teacher_id)
153
+ return await self._agent_loop(message, teacher_id, progress_callback=progress_callback)
151
154
 
152
155
  except Exception as e:
153
- logger.debug("Gateway error: %s", e)
156
+ logger.error("Agent error for teacher %s: %s", teacher_id, e, exc_info=True)
154
157
  self._stats.errors_today += 1
155
158
  await self.emit("error", {"teacher_id": teacher_id, "message": str(e)})
156
159
 
157
- # Teacher-friendly error messages (include debug info for troubleshooting)
160
+ # Teacher-friendly error messages (no internal details exposed)
158
161
  err = str(e).lower()
159
- debug_hint = f"\n\n[Debug: {type(e).__name__}: {str(e)[:200]}]"
160
162
  if "401" in err or "unauthorized" in err or "api key" in err:
161
163
  return GatewayResponse(
162
164
  text="Your AI provider key doesn't seem to be working. "
163
165
  "Run `clawed setup --reset` to reconfigure it."
164
- + debug_hint
165
166
  )
166
167
  if "connection" in err or "connect" in err or "timeout" in err:
167
168
  return GatewayResponse(
168
169
  text="Can't connect to your AI provider right now. "
169
170
  "Check your internet connection and try again."
170
- + debug_hint
171
171
  )
172
172
  if "rate limit" in err or "429" in err:
173
173
  return GatewayResponse(
174
174
  text="Your AI provider is temporarily overloaded. "
175
175
  "Wait a minute and try again."
176
- + debug_hint
177
176
  )
178
177
  return GatewayResponse(
179
- text="Something went wrong. Try again, or run "
180
- "`clawed setup --reset` to reconfigure."
181
- + debug_hint
178
+ text="Something went wrong. Please try again."
182
179
  )
183
180
 
184
181
  async def handle_callback(self, callback_data: str, teacher_id: str) -> GatewayResponse:
@@ -299,7 +296,7 @@ class Gateway:
299
296
  # Agent loop — the core reasoning path
300
297
  # ------------------------------------------------------------------
301
298
 
302
- async def _agent_loop(self, message: str, teacher_id: str) -> GatewayResponse:
299
+ async def _agent_loop(self, message: str, teacher_id: str, progress_callback: Any = None) -> GatewayResponse:
303
300
  """Load context, build prompt, and run the agent tool-use loop."""
304
301
  # 1. Load teacher context from canonical sources
305
302
  teacher_profile = self._load_teacher_profile()
@@ -393,6 +390,7 @@ class Gateway:
393
390
  session_history=session_history,
394
391
  improvement_context=memory_ctx["improvement_context"],
395
392
  agent_name=agent_name,
393
+ progress_callback=progress_callback,
396
394
  )
397
395
 
398
396
  # 4. Get or create LLM adapter
@@ -138,10 +138,13 @@ class CurriculumKB:
138
138
 
139
139
  with sqlite3.connect(self._db_path) as conn:
140
140
  conn.row_factory = sqlite3.Row
141
+ # Fetch up to 5000 chunks for scoring. This trades higher memory
142
+ # for better recall — teachers with large file collections may have
143
+ # thousands of chunks, and a 2000 cap was silently dropping results.
141
144
  rows = conn.execute(
142
145
  "SELECT doc_title, source_path, chunk_text, embedding, metadata, created_at "
143
146
  "FROM chunks WHERE teacher_id = ? "
144
- "LIMIT 2000",
147
+ "LIMIT 5000",
145
148
  (teacher_id,),
146
149
  ).fetchall()
147
150
 
@@ -166,6 +169,47 @@ class CurriculumKB:
166
169
  scored.sort(key=lambda x: x["similarity"], reverse=True)
167
170
  return scored[:top_k]
168
171
 
172
+ def search_all_teachers(
173
+ self,
174
+ query: str,
175
+ top_k: int = 10,
176
+ ) -> list[dict[str, Any]]:
177
+ """Fallback search across ALL teachers when teacher_id doesn't match.
178
+
179
+ This handles cross-transport mismatches (e.g. files ingested via
180
+ Telegram numeric ID, searched via CLI 'local-teacher').
181
+ """
182
+ query_embedding = self._embedder.embed(query)
183
+
184
+ with sqlite3.connect(self._db_path) as conn:
185
+ conn.row_factory = sqlite3.Row
186
+ # Search all chunks regardless of teacher_id — capped for safety
187
+ rows = conn.execute(
188
+ "SELECT doc_title, source_path, chunk_text, embedding, metadata, created_at "
189
+ "FROM chunks LIMIT 5000",
190
+ ).fetchall()
191
+
192
+ if not rows:
193
+ return []
194
+
195
+ scored = []
196
+ for row in rows:
197
+ stored_embedding = json.loads(row["embedding"])
198
+ sim = self._embedder.cosine_similarity(query_embedding, stored_embedding)
199
+ scored.append({
200
+ "doc_title": row["doc_title"],
201
+ "source_path": row["source_path"],
202
+ "chunk_text": row["chunk_text"],
203
+ "metadata": json.loads(row["metadata"]),
204
+ "created_at": row["created_at"],
205
+ "similarity": sim,
206
+ })
207
+
208
+ scored = [s for s in scored if s["similarity"] > 0.05]
209
+ logger.debug("KB fallback search '%s': %d chunks scored, %d above threshold", query, len(rows), len(scored))
210
+ scored.sort(key=lambda x: x["similarity"], reverse=True)
211
+ return scored[:top_k]
212
+
169
213
  def stats(self, teacher_id: str) -> dict[str, Any]:
170
214
  """Return stats about the teacher's curriculum knowledge base."""
171
215
  with sqlite3.connect(self._db_path) as conn:
@@ -60,7 +60,13 @@ class OllamaEmbedder:
60
60
 
61
61
 
62
62
  class TFIDFEmbedder:
63
- """TF-IDF with bigrams — no dependencies, always available."""
63
+ """TF-IDF with bigrams — no dependencies, always available.
64
+
65
+ Vocabulary is capped at MAX_VOCAB tokens to bound vector dimensionality
66
+ and prevent unbounded memory growth during large ingestion runs.
67
+ """
68
+
69
+ MAX_VOCAB = 10_000
64
70
 
65
71
  def __init__(self) -> None:
66
72
  self._vocab: dict[str, int] = {}
@@ -78,12 +84,15 @@ class TFIDFEmbedder:
78
84
  def embed(self, text: str) -> list[float]:
79
85
  tokens = self._tokenize(text)
80
86
  for t in tokens:
81
- if t not in self._vocab:
87
+ if t not in self._vocab and self._next_idx < self.MAX_VOCAB:
82
88
  self._vocab[t] = self._next_idx
83
89
  self._next_idx += 1
84
- vec = [0.0] * len(self._vocab)
90
+ dim = min(len(self._vocab), self.MAX_VOCAB)
91
+ vec = [0.0] * dim
85
92
  for t in tokens:
86
- vec[self._vocab[t]] += 1.0
93
+ idx = self._vocab.get(t)
94
+ if idx is not None and idx < dim:
95
+ vec[idx] += 1.0
87
96
  norm = math.sqrt(sum(x * x for x in vec)) or 1.0
88
97
  return [x / norm for x in vec]
89
98
 
@@ -96,6 +96,9 @@ def build_system_prompt(
96
96
  "The teacher has uploaded materials — if you skip this step, you will "
97
97
  "generate generic content instead of building on their prior work. "
98
98
  "Tell the teacher what you found before generating.\n"
99
+ " IMPORTANT: If search_my_materials returns results, you MUST list them "
100
+ "for the teacher. NEVER say 'I didn't find anything' if the tool returned "
101
+ "materials. Always surface what was found, even if it's not an exact match.\n"
99
102
  "3. Generate complete packages (lesson plan + student handout + slideshow) "
100
103
  "using generate_lesson_bundle\n"
101
104
  "4. Never ask 'want me to create materials?' -- just create them\n"
@@ -83,6 +83,12 @@ class GenerateLessonBundleTool:
83
83
  activity_type = params.get("activity_type", "general")
84
84
  include_images = params.get("include_images", True)
85
85
 
86
+ # ── Notify user we're starting ────────────────────────────────
87
+ context.notify_progress(
88
+ f"Working on your lesson materials for \"{topic}\" now — "
89
+ f"this usually takes 2-4 minutes. I'll send everything when it's ready!"
90
+ )
91
+
86
92
  # ── Load config & persona from context ───────────────────────
87
93
  config = context.config
88
94
  persona = TeacherPersona()
@@ -38,6 +38,8 @@ class IngestMaterialsTool:
38
38
  },
39
39
  }
40
40
 
41
+ MAX_INGEST_FILES = 500
42
+
41
43
  async def execute(
42
44
  self, params: dict[str, Any], context: AgentContext
43
45
  ) -> ToolResult:
@@ -46,13 +48,20 @@ class IngestMaterialsTool:
46
48
  raw_path = params["path"]
47
49
  resolved = Path(raw_path).expanduser().resolve()
48
50
 
51
+ # Security: only allow ingesting files from the user's home directory
52
+ home = Path.home().resolve()
53
+ if not str(resolved).startswith(str(home)):
54
+ return ToolResult(
55
+ text="Access denied: can only ingest files from your home directory."
56
+ )
57
+
49
58
  if not resolved.exists():
50
59
  return ToolResult(
51
60
  text=f"Path not found: {raw_path}. Check the path and try again."
52
61
  )
53
62
 
54
63
  try:
55
- docs = ingest_path(resolved)
64
+ docs = ingest_path(resolved, max_files=self.MAX_INGEST_FILES)
56
65
  if not docs:
57
66
  return ToolResult(
58
67
  text=f"No supported files found in {raw_path}. "
@@ -41,7 +41,9 @@ class ReadWorkspaceTool:
41
41
  ) -> ToolResult:
42
42
  workspace = Path.home() / ".eduagent" / "workspace"
43
43
  filename = params["filename"]
44
- target = workspace / filename
44
+ target = (workspace / filename).resolve()
45
+ if not str(target).startswith(str(workspace.resolve())):
46
+ return ToolResult(text="Access denied: path is outside the workspace.")
45
47
 
46
48
  if not target.exists():
47
49
  # List what IS in the workspace so the agent knows what's available
@@ -8,11 +8,14 @@ YouTube links) alongside text chunk search.
8
8
  from __future__ import annotations
9
9
 
10
10
  import json
11
+ import logging
11
12
  from pathlib import Path
12
13
  from typing import Any
13
14
 
14
15
  from clawed.agent_core.context import AgentContext, ToolResult
15
16
 
17
+ logger = logging.getLogger(__name__)
18
+
16
19
 
17
20
  class SearchMyMaterialsTool:
18
21
  """Search the teacher's curriculum knowledge base for relevant content."""
@@ -65,7 +68,14 @@ class SearchMyMaterialsTool:
65
68
  from clawed.asset_registry import AssetRegistry
66
69
  registry = AssetRegistry()
67
70
  assets = registry.search_assets(teacher_id, query, top_k=top_k)
71
+ # Fallback: if no results with this teacher_id, try without
72
+ # teacher_id filter. This handles cross-transport mismatches
73
+ # (e.g. files ingested via Telegram ID, searched via CLI "local-teacher").
74
+ if not assets:
75
+ assets = registry.search_assets("", query, top_k=top_k)
68
76
  yt_links = registry.get_youtube_links(teacher_id, query, top_k=3)
77
+ if not yt_links:
78
+ yt_links = registry.get_youtube_links("", query, top_k=3)
69
79
 
70
80
  if assets:
71
81
  lines.append("EXISTING MATERIALS:\n")
@@ -92,8 +102,8 @@ class SearchMyMaterialsTool:
92
102
  for link in yt_links:
93
103
  lines.append(f" - {link['url']} (from \"{link['from_file']}\")\n")
94
104
 
95
- except Exception:
96
- pass
105
+ except Exception as e:
106
+ logger.warning("Asset search failed: %s", e)
97
107
 
98
108
  # ── Chunk-level search (text excerpts) ─────────────────────
99
109
  try:
@@ -101,6 +111,9 @@ class SearchMyMaterialsTool:
101
111
 
102
112
  kb = CurriculumKB()
103
113
  results = kb.search(teacher_id, query, top_k=top_k)
114
+ # Fallback: cross-transport teacher_id mismatch
115
+ if not results:
116
+ results = kb.search_all_teachers(query, top_k=top_k)
104
117
 
105
118
  if not results and not lines:
106
119
  stats = kb.stats(teacher_id)