clawed 2.1.0__tar.gz → 2.3.0__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 (232) hide show
  1. {clawed-2.1.0 → clawed-2.3.0}/PKG-INFO +8 -6
  2. {clawed-2.1.0 → clawed-2.3.0}/README.md +7 -5
  3. {clawed-2.1.0 → clawed-2.3.0}/clawed/__init__.py +1 -1
  4. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/core.py +20 -1
  5. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/episodes.py +50 -0
  6. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/loader.py +23 -0
  7. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/generate_lesson_bundle.py +93 -61
  8. clawed-2.3.0/clawed/agent_core/tools/search_my_materials.py +149 -0
  9. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/generate.py +7 -1
  10. {clawed-2.1.0 → clawed-2.3.0}/clawed/export_docx.py +209 -0
  11. clawed-2.3.0/clawed/export_handout.py +368 -0
  12. {clawed-2.1.0 → clawed-2.3.0}/clawed/ingestor.py +249 -1
  13. {clawed-2.1.0 → clawed-2.3.0}/clawed/llm.py +78 -0
  14. {clawed-2.1.0 → clawed-2.3.0}/clawed/memory_engine.py +180 -0
  15. {clawed-2.1.0 → clawed-2.3.0}/clawed/models.py +117 -0
  16. clawed-2.3.0/clawed/prompts/admin_lesson_plan.txt +69 -0
  17. clawed-2.3.0/clawed/prompts/student_packet.txt +87 -0
  18. {clawed-2.1.0 → clawed-2.3.0}/clawed/slide_images.py +54 -4
  19. {clawed-2.1.0 → clawed-2.3.0}/clawed/workspace.py +1 -0
  20. {clawed-2.1.0 → clawed-2.3.0}/pyproject.toml +1 -1
  21. clawed-2.1.0/clawed/agent_core/tools/search_my_materials.py +0 -97
  22. clawed-2.1.0/clawed/export_handout.py +0 -229
  23. {clawed-2.1.0 → clawed-2.3.0}/.gitignore +0 -0
  24. {clawed-2.1.0 → clawed-2.3.0}/LICENSE +0 -0
  25. {clawed-2.1.0 → clawed-2.3.0}/clawed/__main__.py +0 -0
  26. {clawed-2.1.0 → clawed-2.3.0}/clawed/_legacy_gateway.py +0 -0
  27. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent.py +0 -0
  28. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/__init__.py +0 -0
  29. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/approvals.py +0 -0
  30. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/autonomy.py +0 -0
  31. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/context.py +0 -0
  32. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/custom_tools.py +0 -0
  33. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/drive/__init__.py +0 -0
  34. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/drive/auth.py +0 -0
  35. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/drive/client.py +0 -0
  36. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/fake_llm.py +0 -0
  37. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/loop.py +0 -0
  38. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/__init__.py +0 -0
  39. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/curriculum.py +0 -0
  40. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/curriculum_kb.py +0 -0
  41. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/embeddings.py +0 -0
  42. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/identity.py +0 -0
  43. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/preferences.py +0 -0
  44. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/planner.py +0 -0
  45. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/prompt.py +0 -0
  46. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/scheduler.py +0 -0
  47. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/__init__.py +0 -0
  48. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/base.py +0 -0
  49. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/configure_profile.py +0 -0
  50. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/curriculum_map.py +0 -0
  51. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/drive_create_doc.py +0 -0
  52. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/drive_create_slides.py +0 -0
  53. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/drive_list.py +0 -0
  54. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/drive_organize.py +0 -0
  55. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/drive_read.py +0 -0
  56. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/drive_upload.py +0 -0
  57. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/export_document.py +0 -0
  58. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/gap_analysis.py +0 -0
  59. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/generate_assessment.py +0 -0
  60. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/generate_lesson.py +0 -0
  61. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/generate_materials.py +0 -0
  62. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/generate_unit.py +0 -0
  63. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/ingest_materials.py +0 -0
  64. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/parent_comm.py +0 -0
  65. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/read_heartbeat.py +0 -0
  66. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/read_workspace.py +0 -0
  67. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/request_approval.py +0 -0
  68. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/schedule_task.py +0 -0
  69. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/search_lessons.py +0 -0
  70. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/search_standards.py +0 -0
  71. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/student_insights.py +0 -0
  72. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/sub_packet.py +0 -0
  73. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/switch_model.py +0 -0
  74. {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/update_soul.py +0 -0
  75. {clawed-2.1.0 → clawed-2.3.0}/clawed/analytics.py +0 -0
  76. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/__init__.py +0 -0
  77. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/deps.py +0 -0
  78. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/__init__.py +0 -0
  79. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/chat.py +0 -0
  80. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/export.py +0 -0
  81. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/feedback.py +0 -0
  82. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/gateway_chat.py +0 -0
  83. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/generate.py +0 -0
  84. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/ingest.py +0 -0
  85. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/lessons.py +0 -0
  86. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/school.py +0 -0
  87. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/settings.py +0 -0
  88. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/tools.py +0 -0
  89. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/server.py +0 -0
  90. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/static/app.js +0 -0
  91. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/static/style.css +0 -0
  92. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/static/widget.js +0 -0
  93. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/analytics.html +0 -0
  94. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/base.html +0 -0
  95. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/dashboard.html +0 -0
  96. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/generate.html +0 -0
  97. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/index.html +0 -0
  98. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/lesson.html +0 -0
  99. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/profile.html +0 -0
  100. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/settings.html +0 -0
  101. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/stats.html +0 -0
  102. {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/students.html +0 -0
  103. {clawed-2.1.0 → clawed-2.3.0}/clawed/assessment.py +0 -0
  104. {clawed-2.1.0 → clawed-2.3.0}/clawed/asset_registry.py +0 -0
  105. {clawed-2.1.0 → clawed-2.3.0}/clawed/auth/__init__.py +0 -0
  106. {clawed-2.1.0 → clawed-2.3.0}/clawed/auth/google_auth.py +0 -0
  107. {clawed-2.1.0 → clawed-2.3.0}/clawed/bot_state.py +0 -0
  108. {clawed-2.1.0 → clawed-2.3.0}/clawed/chat.py +0 -0
  109. {clawed-2.1.0 → clawed-2.3.0}/clawed/cli.py +0 -0
  110. {clawed-2.1.0 → clawed-2.3.0}/clawed/cli_chat.py +0 -0
  111. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/__init__.py +0 -0
  112. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/_helpers.py +0 -0
  113. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/bot.py +0 -0
  114. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/config.py +0 -0
  115. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/config_llm.py +0 -0
  116. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/config_profile.py +0 -0
  117. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/export.py +0 -0
  118. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/generate_assessment.py +0 -0
  119. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/generate_unit.py +0 -0
  120. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/queue.py +0 -0
  121. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/schedule_cmd.py +0 -0
  122. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/sub.py +0 -0
  123. {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/workspace_cmd.py +0 -0
  124. {clawed-2.1.0 → clawed-2.3.0}/clawed/config.py +0 -0
  125. {clawed-2.1.0 → clawed-2.3.0}/clawed/corpus.py +0 -0
  126. {clawed-2.1.0 → clawed-2.3.0}/clawed/curriculum_map.py +0 -0
  127. {clawed-2.1.0 → clawed-2.3.0}/clawed/database.py +0 -0
  128. {clawed-2.1.0 → clawed-2.3.0}/clawed/demo/__init__.py +0 -0
  129. {clawed-2.1.0 → clawed-2.3.0}/clawed/demo/demo_assessment.json +0 -0
  130. {clawed-2.1.0 → clawed-2.3.0}/clawed/demo/demo_lesson_science_g6.json +0 -0
  131. {clawed-2.1.0 → clawed-2.3.0}/clawed/demo/demo_lesson_social_studies_g8.json +0 -0
  132. {clawed-2.1.0 → clawed-2.3.0}/clawed/demo/demo_unit_plan.json +0 -0
  133. {clawed-2.1.0 → clawed-2.3.0}/clawed/differentiation.py +0 -0
  134. {clawed-2.1.0 → clawed-2.3.0}/clawed/doc_export.py +0 -0
  135. {clawed-2.1.0 → clawed-2.3.0}/clawed/drive.py +0 -0
  136. {clawed-2.1.0 → clawed-2.3.0}/clawed/evaluation.py +0 -0
  137. {clawed-2.1.0 → clawed-2.3.0}/clawed/export_markdown.py +0 -0
  138. {clawed-2.1.0 → clawed-2.3.0}/clawed/export_pdf.py +0 -0
  139. {clawed-2.1.0 → clawed-2.3.0}/clawed/export_pptx.py +0 -0
  140. {clawed-2.1.0 → clawed-2.3.0}/clawed/export_templates.py +0 -0
  141. {clawed-2.1.0 → clawed-2.3.0}/clawed/export_theme.py +0 -0
  142. {clawed-2.1.0 → clawed-2.3.0}/clawed/exporter.py +0 -0
  143. {clawed-2.1.0 → clawed-2.3.0}/clawed/feedback.py +0 -0
  144. {clawed-2.1.0 → clawed-2.3.0}/clawed/formats/__init__.py +0 -0
  145. {clawed-2.1.0 → clawed-2.3.0}/clawed/formats/flipchart.py +0 -0
  146. {clawed-2.1.0 → clawed-2.3.0}/clawed/formats/notebook.py +0 -0
  147. {clawed-2.1.0 → clawed-2.3.0}/clawed/formats/xbk.py +0 -0
  148. {clawed-2.1.0 → clawed-2.3.0}/clawed/gateway.py +0 -0
  149. {clawed-2.1.0 → clawed-2.3.0}/clawed/gateway_response.py +0 -0
  150. {clawed-2.1.0 → clawed-2.3.0}/clawed/generation.py +0 -0
  151. {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/__init__.py +0 -0
  152. {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/export.py +0 -0
  153. {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/feedback.py +0 -0
  154. {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/gaps.py +0 -0
  155. {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/generate.py +0 -0
  156. {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/ingest.py +0 -0
  157. {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/misc.py +0 -0
  158. {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/onboard.py +0 -0
  159. {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/schedule.py +0 -0
  160. {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/standards.py +0 -0
  161. {clawed-2.1.0 → clawed-2.3.0}/clawed/improver.py +0 -0
  162. {clawed-2.1.0 → clawed-2.3.0}/clawed/io.py +0 -0
  163. {clawed-2.1.0 → clawed-2.3.0}/clawed/lesson.py +0 -0
  164. {clawed-2.1.0 → clawed-2.3.0}/clawed/materials.py +0 -0
  165. {clawed-2.1.0 → clawed-2.3.0}/clawed/mcp_server.py +0 -0
  166. {clawed-2.1.0 → clawed-2.3.0}/clawed/model_router.py +0 -0
  167. {clawed-2.1.0 → clawed-2.3.0}/clawed/onboarding.py +0 -0
  168. {clawed-2.1.0 → clawed-2.3.0}/clawed/openclaw_plugin.py +0 -0
  169. {clawed-2.1.0 → clawed-2.3.0}/clawed/parent_comm.py +0 -0
  170. {clawed-2.1.0 → clawed-2.3.0}/clawed/persona.py +0 -0
  171. {clawed-2.1.0 → clawed-2.3.0}/clawed/planner.py +0 -0
  172. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/504_accommodations.txt +0 -0
  173. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/assessment.txt +0 -0
  174. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/curriculum_gaps.txt +0 -0
  175. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/dbq_assessment.txt +0 -0
  176. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/differentiation.txt +0 -0
  177. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/formative_assessment.txt +0 -0
  178. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/iep_modification.txt +0 -0
  179. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/lesson_plan.txt +0 -0
  180. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/pacing_guide.txt +0 -0
  181. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/parent_note.txt +0 -0
  182. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/persona_extract.txt +0 -0
  183. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/quiz.txt +0 -0
  184. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/rubric.txt +0 -0
  185. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/sub_packet.txt +0 -0
  186. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/summative_assessment.txt +0 -0
  187. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/tiered_assignments.txt +0 -0
  188. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/unit_plan.txt +0 -0
  189. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/worksheet.txt +0 -0
  190. {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/year_map.txt +0 -0
  191. {clawed-2.1.0 → clawed-2.3.0}/clawed/quality.py +0 -0
  192. {clawed-2.1.0 → clawed-2.3.0}/clawed/reading_report.py +0 -0
  193. {clawed-2.1.0 → clawed-2.3.0}/clawed/router.py +0 -0
  194. {clawed-2.1.0 → clawed-2.3.0}/clawed/sanitize.py +0 -0
  195. {clawed-2.1.0 → clawed-2.3.0}/clawed/scheduler.py +0 -0
  196. {clawed-2.1.0 → clawed-2.3.0}/clawed/school.py +0 -0
  197. {clawed-2.1.0 → clawed-2.3.0}/clawed/search.py +0 -0
  198. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/__init__.py +0 -0
  199. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/art.py +0 -0
  200. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/base.py +0 -0
  201. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/computer_science.py +0 -0
  202. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/ela.py +0 -0
  203. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/foreign_language.py +0 -0
  204. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/history.py +0 -0
  205. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/library.py +0 -0
  206. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/math.py +0 -0
  207. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/music.py +0 -0
  208. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/physical_education.py +0 -0
  209. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/science.py +0 -0
  210. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/social_studies.py +0 -0
  211. {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/special_education.py +0 -0
  212. {clawed-2.1.0 → clawed-2.3.0}/clawed/standards.py +0 -0
  213. {clawed-2.1.0 → clawed-2.3.0}/clawed/state.py +0 -0
  214. {clawed-2.1.0 → clawed-2.3.0}/clawed/state_standards.py +0 -0
  215. {clawed-2.1.0 → clawed-2.3.0}/clawed/student_bot.py +0 -0
  216. {clawed-2.1.0 → clawed-2.3.0}/clawed/student_cli.py +0 -0
  217. {clawed-2.1.0 → clawed-2.3.0}/clawed/student_telegram_bot.py +0 -0
  218. {clawed-2.1.0 → clawed-2.3.0}/clawed/sub_packet.py +0 -0
  219. {clawed-2.1.0 → clawed-2.3.0}/clawed/task_queue.py +0 -0
  220. {clawed-2.1.0 → clawed-2.3.0}/clawed/templates_lib.py +0 -0
  221. {clawed-2.1.0 → clawed-2.3.0}/clawed/tools.py +0 -0
  222. {clawed-2.1.0 → clawed-2.3.0}/clawed/transports/__init__.py +0 -0
  223. {clawed-2.1.0 → clawed-2.3.0}/clawed/transports/cli.py +0 -0
  224. {clawed-2.1.0 → clawed-2.3.0}/clawed/transports/openclaw.py +0 -0
  225. {clawed-2.1.0 → clawed-2.3.0}/clawed/transports/student_telegram.py +0 -0
  226. {clawed-2.1.0 → clawed-2.3.0}/clawed/transports/telegram.py +0 -0
  227. {clawed-2.1.0 → clawed-2.3.0}/clawed/transports/web.py +0 -0
  228. {clawed-2.1.0 → clawed-2.3.0}/clawed/tui.py +0 -0
  229. {clawed-2.1.0 → clawed-2.3.0}/clawed/tui_chat.py +0 -0
  230. {clawed-2.1.0 → clawed-2.3.0}/clawed/voice.py +0 -0
  231. {clawed-2.1.0 → clawed-2.3.0}/eduagent/__init__.py +0 -0
  232. {clawed-2.1.0 → clawed-2.3.0}/eduagent/_compat.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clawed
3
- Version: 2.1.0
3
+ Version: 2.3.0
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
@@ -79,15 +79,17 @@ Built on the OpenClaw agent framework. Open source. MIT license.
79
79
 
80
80
  ---
81
81
 
82
- ## What's new in v2.0
82
+ ## What's new in v2.3
83
83
 
84
- **Clean output.** Zero XML tags, zero markdown artifacts, zero `<teacher prompt>` gibberish in your printed documents. Every text field in every export (DOCX, PPTX, handout) passes through a rewritten sanitization pipeline that strips LLM formatting artifacts before they reach the page.
84
+ **Three documents, not one.** Every lesson now generates three professional files in parallel:
85
85
 
86
- **Your voice, for real.** The lesson generation LLM now receives your full persona, your SOUL.md, and up to 2000 characters of your voice sample in the system prompt. Lessons actually sound like you wrote them -- not a generic "expert lesson plan writer."
86
+ 1. **Student Packet** (4-6 page DOCX workbook) Fill-in-the-blank guided notes, station sections with full primary source text and analysis questions, graphic organizer tables, exit ticket with sentence starters. This is what students hold in their hands.
87
+ 2. **Admin Lesson Plan** (observation-ready DOCX) — Multi-column table with per-section teacher actions (scripted language), student actions, observer look-fors, and differentiation. Anticipated student responses and misconceptions with teacher corrections. Teacher content knowledge appendix.
88
+ 3. **Slideshow** (PPTX) — Subject-themed slides with academic images from your own files first, then Library of Congress and Wikimedia. Vocabulary, source quotes, and section dividers on dedicated slides.
87
89
 
88
- **Your files matter.** When you say "make me a lesson on absolutism," the agent searches your knowledge base, finds your existing materials, and injects them as structured context into the generation prompt. The LLM sees your prior work and builds on it instead of starting from scratch.
90
+ **Your files are first-class.** Ingestion extracts images from your PPTX/DOCX files, catalogues YouTube links, and classifies every file by type. The agent tells you what you already have before generating.
89
91
 
90
- **Images on by default.** Slideshows now include academic images (Library of Congress, Wikimedia) without needing `--images`. Logging added to diagnose fetch failures.
92
+ **Pedagogical fingerprint.** "Teacher voice" means how you teach, not just how you sound. The persona captures source types, activity patterns, scaffolding moves, Do Now style, exit ticket format, and signature moves.
91
93
 
92
94
  ---
93
95
 
@@ -13,15 +13,17 @@ Built on the OpenClaw agent framework. Open source. MIT license.
13
13
 
14
14
  ---
15
15
 
16
- ## What's new in v2.0
16
+ ## What's new in v2.3
17
17
 
18
- **Clean output.** Zero XML tags, zero markdown artifacts, zero `<teacher prompt>` gibberish in your printed documents. Every text field in every export (DOCX, PPTX, handout) passes through a rewritten sanitization pipeline that strips LLM formatting artifacts before they reach the page.
18
+ **Three documents, not one.** Every lesson now generates three professional files in parallel:
19
19
 
20
- **Your voice, for real.** The lesson generation LLM now receives your full persona, your SOUL.md, and up to 2000 characters of your voice sample in the system prompt. Lessons actually sound like you wrote them -- not a generic "expert lesson plan writer."
20
+ 1. **Student Packet** (4-6 page DOCX workbook) Fill-in-the-blank guided notes, station sections with full primary source text and analysis questions, graphic organizer tables, exit ticket with sentence starters. This is what students hold in their hands.
21
+ 2. **Admin Lesson Plan** (observation-ready DOCX) — Multi-column table with per-section teacher actions (scripted language), student actions, observer look-fors, and differentiation. Anticipated student responses and misconceptions with teacher corrections. Teacher content knowledge appendix.
22
+ 3. **Slideshow** (PPTX) — Subject-themed slides with academic images from your own files first, then Library of Congress and Wikimedia. Vocabulary, source quotes, and section dividers on dedicated slides.
21
23
 
22
- **Your files matter.** When you say "make me a lesson on absolutism," the agent searches your knowledge base, finds your existing materials, and injects them as structured context into the generation prompt. The LLM sees your prior work and builds on it instead of starting from scratch.
24
+ **Your files are first-class.** Ingestion extracts images from your PPTX/DOCX files, catalogues YouTube links, and classifies every file by type. The agent tells you what you already have before generating.
23
25
 
24
- **Images on by default.** Slideshows now include academic images (Library of Congress, Wikimedia) without needing `--images`. Logging added to diagnose fetch failures.
26
+ **Pedagogical fingerprint.** "Teacher voice" means how you teach, not just how you sound. The persona captures source types, activity patterns, scaffolding moves, Do Now style, exit ticket format, and signature moves.
25
27
 
26
28
  ---
27
29
 
@@ -17,7 +17,7 @@ if hasattr(sys.stderr, "reconfigure"):
17
17
  except Exception:
18
18
  pass
19
19
 
20
- __version__ = "2.1.0"
20
+ __version__ = "2.3.0"
21
21
  __author__ = "Jon Maccarello & Claw-ED contributors"
22
22
  __description__ = "Personal AI teaching agent. Learns your voice, works while you sleep."
23
23
 
@@ -361,7 +361,18 @@ class Gateway:
361
361
  soul_context=soul_context,
362
362
  )
363
363
 
364
- # 2c. Enhance prompt for multi-step planning requests
364
+ # 2c. Cross-session context threading greet with continuity
365
+ last_session = memory_ctx.get("last_session_summary", "")
366
+ if last_session and not session_history:
367
+ system += (
368
+ "\n\n=== Last Session Context ===\n"
369
+ f"The teacher's last interaction was about: {last_session}\n"
370
+ "If this is a new conversation, greet them with continuity — e.g. "
371
+ '"Last time we worked on [topic]. Want to continue or start something new?"\n'
372
+ "=== End Last Session Context ===\n"
373
+ )
374
+
375
+ # 2d. Enhance prompt for multi-step planning requests
365
376
  from clawed.agent_core.planner import build_planning_prompt, is_planning_request
366
377
 
367
378
  if is_planning_request(message):
@@ -415,6 +426,14 @@ class Gateway:
415
426
  except Exception:
416
427
  pass
417
428
 
429
+ # 8. Maybe compress old episodes (runs every COMPRESSION_THRESHOLD episodes)
430
+ try:
431
+ from clawed.memory_engine import maybe_compress_episodes
432
+
433
+ maybe_compress_episodes(teacher_id)
434
+ except Exception:
435
+ pass
436
+
418
437
  return result
419
438
 
420
439
  # ------------------------------------------------------------------
@@ -96,3 +96,53 @@ class EpisodicMemory:
96
96
 
97
97
  scored.sort(key=lambda x: x["similarity"], reverse=True)
98
98
  return scored[:top_k]
99
+
100
+ def get_latest_episode(self, teacher_id: str) -> dict[str, Any] | None:
101
+ """Return the most recent episode for a teacher, or None."""
102
+ with sqlite3.connect(self._db_path) as conn:
103
+ conn.row_factory = sqlite3.Row
104
+ row = conn.execute(
105
+ "SELECT text, metadata, created_at FROM episodes "
106
+ "WHERE teacher_id = ? ORDER BY created_at DESC LIMIT 1",
107
+ (teacher_id,),
108
+ ).fetchone()
109
+ if not row:
110
+ return None
111
+ return {
112
+ "text": row["text"],
113
+ "metadata": json.loads(row["metadata"]),
114
+ "created_at": row["created_at"],
115
+ }
116
+
117
+ def count_episodes(self, teacher_id: str) -> int:
118
+ """Return the total number of episodes stored for a teacher."""
119
+ with sqlite3.connect(self._db_path) as conn:
120
+ row = conn.execute(
121
+ "SELECT COUNT(*) FROM episodes WHERE teacher_id = ?",
122
+ (teacher_id,),
123
+ ).fetchone()
124
+ return row[0] if row else 0
125
+
126
+ def get_all_episodes(
127
+ self,
128
+ teacher_id: str,
129
+ limit: int = 500,
130
+ offset: int = 0,
131
+ ) -> list[dict[str, Any]]:
132
+ """Return episodes in chronological order (oldest first)."""
133
+ with sqlite3.connect(self._db_path) as conn:
134
+ conn.row_factory = sqlite3.Row
135
+ rows = conn.execute(
136
+ "SELECT text, metadata, created_at FROM episodes "
137
+ "WHERE teacher_id = ? ORDER BY created_at ASC "
138
+ "LIMIT ? OFFSET ?",
139
+ (teacher_id, limit, offset),
140
+ ).fetchall()
141
+ return [
142
+ {
143
+ "text": row["text"],
144
+ "metadata": json.loads(row["metadata"]),
145
+ "created_at": row["created_at"],
146
+ }
147
+ for row in rows
148
+ ]
@@ -96,6 +96,28 @@ def load_memory_context(teacher_id: str, current_message: str) -> dict[str, Any]
96
96
  except Exception as e:
97
97
  logger.debug("Curriculum KB search failed: %s", e)
98
98
 
99
+ # Layer 6: Last session summary (cross-session context threading)
100
+ last_session_summary = ""
101
+ try:
102
+ from clawed.agent_core.memory.episodes import EpisodicMemory
103
+
104
+ mem = EpisodicMemory()
105
+ latest = mem.get_latest_episode(teacher_id)
106
+ if latest:
107
+ text = latest["text"]
108
+ # Extract teacher's message (first line is usually "Teacher: <msg>")
109
+ first_line = text.split("\n")[0].strip()
110
+ if first_line.startswith("Teacher: "):
111
+ topic = first_line[len("Teacher: "):]
112
+ else:
113
+ topic = first_line
114
+ # Truncate to a concise summary
115
+ if len(topic) > 150:
116
+ topic = topic[:147] + "..."
117
+ last_session_summary = topic
118
+ except Exception as e:
119
+ logger.debug("Last session summary load failed: %s", e)
120
+
99
121
  return {
100
122
  "identity_summary": identity_summary,
101
123
  "curriculum_summary": curriculum_summary,
@@ -104,4 +126,5 @@ def load_memory_context(teacher_id: str, current_message: str) -> dict[str, Any]
104
126
  "preferences_summary": preferences_summary,
105
127
  "autonomy_summary": autonomy_summary,
106
128
  "curriculum_kb_context": curriculum_kb_context,
129
+ "last_session_summary": last_session_summary,
107
130
  }
@@ -104,18 +104,32 @@ class GenerateLessonBundleTool:
104
104
  topic=topic,
105
105
  )
106
106
 
107
- # ── Search curriculum KB for relevant prior work ───────────────
107
+ # ── Search for teacher's existing materials (assets + KB) ─────
108
108
  kb_context = ""
109
109
  kb_prompt_section = ""
110
+
111
+ # Asset-level search (complete files, YouTube links)
112
+ try:
113
+ from clawed.asset_registry import AssetRegistry
114
+ registry = AssetRegistry()
115
+ assets = registry.search_assets(context.teacher_id, topic, top_k=5)
116
+ yt_links = registry.get_youtube_links(context.teacher_id, topic, top_k=3)
117
+ if assets or yt_links:
118
+ kb_prompt_section = registry.format_asset_summary(assets, yt_links)
119
+ logger.info(
120
+ "Asset search found %d files, %d YouTube links for '%s'",
121
+ len(assets), len(yt_links), topic,
122
+ )
123
+ except Exception as e:
124
+ logger.debug("Asset search failed: %s", e)
125
+
126
+ # KB chunk-level search (text excerpts)
110
127
  try:
111
128
  from clawed.agent_core.memory.curriculum_kb import CurriculumKB
112
129
  kb = CurriculumKB()
113
130
  kb_results = kb.search(context.teacher_id, topic, top_k=3)
114
131
  if kb_results:
115
- kb_parts = []
116
- for r in kb_results:
117
- if r.get("similarity", 0) > 0.1:
118
- kb_parts.append(r)
132
+ kb_parts = [r for r in kb_results if r.get("similarity", 0) > 0.1]
119
133
  if kb_parts:
120
134
  kb_context = (
121
135
  "\n\nRelevant materials from the teacher's files:\n"
@@ -124,20 +138,22 @@ class GenerateLessonBundleTool:
124
138
  for r in kb_parts
125
139
  )
126
140
  )
127
- # Build structured prompt section for the LLM
128
- kb_prompt_section = (
129
- "Teacher's Existing Materials on This Topic\n"
130
- "The teacher has created content on this topic before. "
131
- "Reference and build on their existing work:\n\n"
132
- + "\n\n".join(
133
- f"From \"{r['doc_title']}\":\n{r['chunk_text'][:500]}"
134
- for r in kb_parts
135
- )
136
- + "\n\nUse these materials as a foundation. Reference the teacher's existing "
137
- "lessons, reuse their graphic organizer formats, build on their approach. "
138
- "If the teacher has taught this topic before, extend their work — don't "
139
- "start from scratch."
141
+ chunk_section = "\n\n".join(
142
+ f"From \"{r['doc_title']}\":\n{r['chunk_text'][:500]}"
143
+ for r in kb_parts
140
144
  )
145
+ if kb_prompt_section:
146
+ kb_prompt_section += "\n\n" + chunk_section
147
+ else:
148
+ kb_prompt_section = (
149
+ "Teacher's Existing Materials on This Topic\n"
150
+ "The teacher has created content on this topic before. "
151
+ "Reference and build on their existing work:\n\n"
152
+ + chunk_section
153
+ + "\n\nUse these materials as a foundation. "
154
+ "Reference the teacher's existing lessons, reuse their "
155
+ "graphic organizer formats, build on their approach."
156
+ )
141
157
  logger.info("KB search found %d relevant chunks for '%s'", len(kb_parts), topic)
142
158
  except Exception as e:
143
159
  logger.debug("KB search failed: %s", e)
@@ -216,6 +232,44 @@ class GenerateLessonBundleTool:
216
232
  except Exception:
217
233
  pass # Review is best-effort, don't block on failure
218
234
 
235
+ # ── Generate student packet + admin plan in parallel ──────────
236
+ import asyncio
237
+
238
+ from clawed.llm import LLMClient
239
+
240
+ llm_client = LLMClient(config)
241
+ persona_ctx = persona.to_prompt_context()
242
+ student_packet = None
243
+ admin_plan = None
244
+
245
+ async def _gen_packet():
246
+ return await llm_client.generate_student_packet(
247
+ lesson_json_str, persona_context=persona_ctx,
248
+ )
249
+
250
+ async def _gen_admin():
251
+ return await llm_client.generate_admin_plan(
252
+ lesson_json_str, persona_context=persona_ctx,
253
+ )
254
+
255
+ try:
256
+ results = await asyncio.gather(
257
+ _gen_packet(), _gen_admin(), return_exceptions=True,
258
+ )
259
+ if not isinstance(results[0], Exception):
260
+ student_packet = results[0]
261
+ logger.info("Student packet generated: %d stations, %d guided notes",
262
+ len(student_packet.stations), len(student_packet.guided_notes))
263
+ else:
264
+ logger.warning("Student packet generation failed: %s", results[0])
265
+ if not isinstance(results[1], Exception):
266
+ admin_plan = results[1]
267
+ logger.info("Admin lesson plan generated: %d sections", len(admin_plan.sections))
268
+ else:
269
+ logger.warning("Admin plan generation failed: %s", results[1])
270
+ except Exception as e:
271
+ logger.warning("Parallel generation failed: %s", e)
272
+
219
273
  # ── Export all three files ────────────────────────────────────
220
274
  output_dir = Path("clawed_output").resolve()
221
275
  output_dir.mkdir(parents=True, exist_ok=True)
@@ -224,61 +278,39 @@ class GenerateLessonBundleTool:
224
278
  side_effects: list[str] = []
225
279
  errors: list[str] = []
226
280
 
227
- # 1. Lesson plan DOCX
281
+ # 1. Admin lesson plan DOCX (or fallback to basic lesson plan)
228
282
  try:
229
283
  from clawed.export_docx import export_lesson_docx
230
284
 
231
- docx_path = export_lesson_docx(lesson, persona, output_dir)
285
+ docx_path = export_lesson_docx(
286
+ lesson, persona, output_dir, admin_plan=admin_plan,
287
+ )
232
288
  generated_files.append(docx_path)
233
- side_effects.append(f"Lesson plan DOCX: {docx_path.name}")
289
+ label = "Admin lesson plan" if admin_plan else "Lesson plan"
290
+ side_effects.append(f"{label} DOCX: {docx_path.name}")
234
291
  except Exception as e:
235
292
  logger.error("Lesson DOCX export failed: %s", e)
236
293
  errors.append(f"Lesson plan DOCX failed: {e}")
237
294
 
238
- # 2. Generate student handout via LLM (first-class output, not regex extraction)
295
+ # 2. Student packet DOCX (structured) or fallback to old handout
239
296
  try:
240
- import json_repair
297
+ from clawed.export_handout import export_student_packet_docx
241
298
 
242
- from clawed.llm import LLMClient
243
-
244
- llm_client = LLMClient(config)
245
- handout_raw = await llm_client.generate_student_handout(
246
- lesson_json_str,
247
- persona_context=persona.to_prompt_context(),
248
- subject=subject,
249
- grade=grade,
250
- )
251
- # Parse handout JSON
252
- handout_cleaned = handout_raw.strip()
253
- if handout_cleaned.startswith("```"):
254
- lines = handout_cleaned.split("\n")
255
- lines = lines[1:]
256
- if lines and lines[-1].strip() == "```":
257
- lines = lines[:-1]
258
- handout_cleaned = "\n".join(lines)
259
- try:
260
- handout_data = json.loads(handout_cleaned)
261
- except json.JSONDecodeError:
262
- handout_data = json_repair.loads(handout_cleaned)
263
-
264
- from clawed.export_handout import export_handout_docx
265
-
266
- handout_path = export_handout_docx(handout_data, subject=subject)
267
- if handout_path:
268
- generated_files.append(Path(handout_path))
269
- side_effects.append(f"Student handout DOCX: {Path(handout_path).name}")
270
- except Exception as handout_err:
271
- logger.warning("LLM handout generation failed, falling back: %s", handout_err)
272
- # Fallback to regex-based handout
273
- try:
299
+ if student_packet:
300
+ packet_path = export_student_packet_docx(
301
+ student_packet, subject=subject, output_dir=output_dir,
302
+ )
303
+ else:
304
+ # Fallback: build minimal packet from lesson data
274
305
  from clawed.export_docx import export_student_handout
306
+ packet_path = export_student_handout(lesson, persona, output_dir)
275
307
 
276
- handout_path = export_student_handout(lesson, persona, output_dir)
277
- if handout_path:
278
- generated_files.append(Path(handout_path))
279
- side_effects.append(f"Student handout DOCX: {Path(handout_path).name}")
280
- except Exception:
281
- pass
308
+ if packet_path:
309
+ generated_files.append(Path(packet_path))
310
+ side_effects.append(f"Student packet DOCX: {Path(packet_path).name}")
311
+ except Exception as e:
312
+ logger.warning("Student packet export failed: %s", e)
313
+ errors.append(f"Student packet failed: {e}")
282
314
 
283
315
  # 3. Slideshow PPTX
284
316
  try:
@@ -0,0 +1,149 @@
1
+ """Tool: search_my_materials — search the teacher's uploaded curriculum files.
2
+
3
+ This is the key tool that makes Claw-ED curriculum-aware. The agent calls
4
+ this before generating to find relevant prior work in the teacher's own
5
+ uploaded materials. Now includes asset-level awareness (slideshows, handouts,
6
+ YouTube links) alongside text chunk search.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from clawed.agent_core.context import AgentContext, ToolResult
15
+
16
+
17
+ class SearchMyMaterialsTool:
18
+ """Search the teacher's curriculum knowledge base for relevant content."""
19
+
20
+ def schema(self) -> dict[str, Any]:
21
+ return {
22
+ "type": "function",
23
+ "function": {
24
+ "name": "search_my_materials",
25
+ "description": (
26
+ "Search the teacher's uploaded curriculum files for relevant "
27
+ "content. Use this BEFORE generating lessons, units, or materials "
28
+ "to ground your output in the teacher's own prior work. Returns "
29
+ "matching files (slideshows, handouts, assessments), YouTube links, "
30
+ "and text excerpts with source file attribution."
31
+ ),
32
+ "parameters": {
33
+ "type": "object",
34
+ "properties": {
35
+ "query": {
36
+ "type": "string",
37
+ "description": (
38
+ "What to search for — a topic, concept, or question. "
39
+ "Example: 'Civil War causes', 'photosynthesis lab', "
40
+ "'fractions worksheet'"
41
+ ),
42
+ },
43
+ "top_k": {
44
+ "type": "integer",
45
+ "description": "Maximum results to return (default 5)",
46
+ "default": 5,
47
+ },
48
+ },
49
+ "required": ["query"],
50
+ },
51
+ },
52
+ }
53
+
54
+ async def execute(
55
+ self, params: dict[str, Any], context: AgentContext
56
+ ) -> ToolResult:
57
+ query = params["query"]
58
+ top_k = params.get("top_k", 5)
59
+ teacher_id = context.teacher_id
60
+
61
+ lines: list[str] = []
62
+
63
+ # ── Asset-level search (files, YouTube links) ──────────────
64
+ try:
65
+ from clawed.asset_registry import AssetRegistry
66
+ registry = AssetRegistry()
67
+ assets = registry.search_assets(teacher_id, query, top_k=top_k)
68
+ yt_links = registry.get_youtube_links(teacher_id, query, top_k=3)
69
+
70
+ if assets:
71
+ lines.append("EXISTING MATERIALS:\n")
72
+ for i, a in enumerate(assets, 1):
73
+ type_label = a["material_type"].replace("_", " ").title()
74
+ extras: list[str] = []
75
+ if a.get("slide_count"):
76
+ extras.append(f"{a['slide_count']} slides")
77
+ if a.get("image_count"):
78
+ extras.append(f"{a['image_count']} images")
79
+ yt_raw = a.get("youtube_urls", [])
80
+ yt_list = json.loads(yt_raw) if isinstance(yt_raw, str) else yt_raw
81
+ yt_count = len(yt_list)
82
+ if yt_count:
83
+ extras.append(f"{yt_count} YouTube links")
84
+ extra_str = f" ({', '.join(extras)})" if extras else ""
85
+ lines.append(
86
+ f" {i}. [{type_label}] \"{a['title']}\"{extra_str}\n"
87
+ f" File: {a['filename']}\n"
88
+ )
89
+
90
+ if yt_links:
91
+ lines.append("YOUTUBE LINKS IN YOUR FILES:\n")
92
+ for link in yt_links:
93
+ lines.append(f" - {link['url']} (from \"{link['from_file']}\")\n")
94
+
95
+ except Exception:
96
+ pass
97
+
98
+ # ── Chunk-level search (text excerpts) ─────────────────────
99
+ try:
100
+ from clawed.agent_core.memory.curriculum_kb import CurriculumKB
101
+
102
+ kb = CurriculumKB()
103
+ results = kb.search(teacher_id, query, top_k=top_k)
104
+
105
+ if not results and not lines:
106
+ stats = kb.stats(teacher_id)
107
+ if stats["doc_count"] == 0:
108
+ return ToolResult(
109
+ text="No curriculum files uploaded yet. Ask the teacher "
110
+ "to share their lesson plans, handouts, or other "
111
+ "teaching materials so you can reference them."
112
+ )
113
+ return ToolResult(
114
+ text=f"No matches found for '{query}' in "
115
+ f"{stats['doc_count']} uploaded documents."
116
+ )
117
+
118
+ if results:
119
+ lines.append("RELEVANT EXCERPTS:\n")
120
+ for i, r in enumerate(results, 1):
121
+ source = r["doc_title"]
122
+ if r.get("source_path"):
123
+ fname = Path(r["source_path"]).name
124
+ source = f"{r['doc_title']} ({fname})"
125
+ sim_pct = int(r["similarity"] * 100)
126
+ text_preview = r["chunk_text"][:300]
127
+ if len(r["chunk_text"]) > 300:
128
+ text_preview += "..."
129
+ lines.append(
130
+ f" {i}. From '{source}' ({sim_pct}% match):\n"
131
+ f" {text_preview}\n"
132
+ )
133
+
134
+ except Exception as e:
135
+ if not lines:
136
+ return ToolResult(text=f"Failed to search curriculum files: {e}")
137
+
138
+ if lines:
139
+ header = f"Found materials related to \"{query}\":\n\n"
140
+ lines.append(
141
+ "\nWould you like me to use these existing materials, "
142
+ "enhance them, or create something new?"
143
+ )
144
+ return ToolResult(
145
+ text=header + "\n".join(lines),
146
+ data={"query": query},
147
+ )
148
+
149
+ return ToolResult(text=f"No materials found for '{query}'.")
@@ -155,20 +155,26 @@ def ingest(
155
155
  except Exception:
156
156
  pass
157
157
 
158
- # Register assets (file-level metadata, images, YouTube links)
158
+ # Register assets with rich extraction (images, YouTube links, metadata)
159
159
  asset_msg = ""
160
160
  try:
161
161
  from clawed.asset_registry import AssetRegistry
162
+ from clawed.ingestor import extract_rich
162
163
  registry = AssetRegistry()
163
164
  asset_count = 0
164
165
  for doc in documents:
165
166
  doc_type_val = doc.doc_type.value if hasattr(doc.doc_type, "value") else str(doc.doc_type)
167
+ # Try rich extraction for images/URLs from original file
168
+ extraction = None
169
+ if doc.source_path:
170
+ extraction = extract_rich(Path(doc.source_path))
166
171
  asset_id = registry.register_asset(
167
172
  teacher_id="default",
168
173
  source_path=doc.source_path or "",
169
174
  title=doc.title,
170
175
  doc_type=doc_type_val,
171
176
  text=doc.content,
177
+ extraction=extraction,
172
178
  )
173
179
  if asset_id:
174
180
  asset_count += 1