taskuary 0.3.3.2__tar.gz → 0.3.3.3__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 (288) hide show
  1. {taskuary-0.3.3.2/taskuary.egg-info → taskuary-0.3.3.3}/PKG-INFO +29 -18
  2. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/README.md +28 -17
  3. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/pyproject.toml +1 -1
  4. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/assistant.py +2 -2
  5. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/clis.py +11 -1
  6. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/concierge.py +46 -4
  7. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/funnel.py +15 -0
  8. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/general.py +19 -0
  9. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/interview.py +37 -4
  10. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/messengers.py +16 -1
  11. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/reports.py +39 -7
  12. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/server.py +100 -5
  13. taskuary-0.3.3.3/taskuary/skills/soul-interview/SKILL.md +92 -0
  14. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/store.py +13 -2
  15. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/terminal.py +78 -7
  16. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/wabridge.py +22 -5
  17. taskuary-0.3.3.2/taskuary/web/assets/GeneralWorkspace-C75zMBzq.js → taskuary-0.3.3.3/taskuary/web/assets/GeneralWorkspace-BpOeJv4L.js +1 -1
  18. taskuary-0.3.3.2/taskuary/web/assets/HubView-BtNf0bG5.js → taskuary-0.3.3.3/taskuary/web/assets/HubView-BhP6xVxh.js +1 -1
  19. taskuary-0.3.3.2/taskuary/web/assets/index-CE8to-uH.js → taskuary-0.3.3.3/taskuary/web/assets/index-B0RiuVsJ.js +113 -113
  20. taskuary-0.3.3.2/taskuary/web/assets/index-C_xWyjgP.css → taskuary-0.3.3.3/taskuary/web/assets/index-BVYz6QiM.css +1 -1
  21. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/web/index.html +2 -2
  22. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/whatsapp/bridge.mjs +73 -13
  23. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/whatsapp/package.json +1 -1
  24. taskuary-0.3.3.3/taskuary/whatsapp/policy.mjs +51 -0
  25. taskuary-0.3.3.3/taskuary/whatsapp/policy.test.mjs +37 -0
  26. {taskuary-0.3.3.2 → taskuary-0.3.3.3/taskuary.egg-info}/PKG-INFO +29 -18
  27. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary.egg-info/SOURCES.txt +9 -4
  28. taskuary-0.3.3.3/tests/test_agent_identity.py +70 -0
  29. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_agent_source.py +10 -0
  30. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_assistant.py +14 -0
  31. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_floating_assistant.py +19 -0
  32. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_follow_up_verdict.py +11 -3
  33. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_funnel.py +35 -0
  34. taskuary-0.3.3.3/tests/test_ignore_sender.py +90 -0
  35. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_live.py +22 -9
  36. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_messengers.py +27 -0
  37. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_promptmap_and_catalog.py +12 -6
  38. taskuary-0.3.3.3/tests/test_report_order_and_research.py +109 -0
  39. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_soul_interview.py +51 -0
  40. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_terminal.py +12 -0
  41. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_wabridge.py +16 -4
  42. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_waitroom.py +26 -0
  43. taskuary-0.3.3.2/taskuary/skills/soul-interview/SKILL.md +0 -57
  44. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/LICENSE +0 -0
  45. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/setup.cfg +0 -0
  46. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/__init__.py +0 -0
  47. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/agents.py +0 -0
  48. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/aisetup.py +0 -0
  49. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/artifacts.py +0 -0
  50. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/aws.py +0 -0
  51. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/azure.py +0 -0
  52. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/blackboard.py +0 -0
  53. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/browserview.py +0 -0
  54. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/calendar.py +0 -0
  55. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/categories.py +0 -0
  56. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/channels.py +0 -0
  57. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/ci.py +0 -0
  58. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/cli.py +0 -0
  59. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/climodels.py +0 -0
  60. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/coder.py +0 -0
  61. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/compose.py +0 -0
  62. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/config.py +0 -0
  63. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/context.py +0 -0
  64. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/counsel.py +0 -0
  65. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/db.py +0 -0
  66. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/demo.py +0 -0
  67. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/deps.py +0 -0
  68. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/desktop.py +0 -0
  69. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/devtools.py +0 -0
  70. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/digest.py +0 -0
  71. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/docsync.py +0 -0
  72. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/evalset.py +0 -0
  73. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/evening.py +0 -0
  74. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/github.py +0 -0
  75. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/guard.py +0 -0
  76. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/handbook.py +0 -0
  77. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/handraise.py +0 -0
  78. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/histgen.py +0 -0
  79. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/hooks.py +0 -0
  80. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/hub.py +0 -0
  81. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/imapmail.py +0 -0
  82. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/imessage.py +0 -0
  83. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/ingest.py +0 -0
  84. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/intacct.py +0 -0
  85. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/invoice_workflow.py +0 -0
  86. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/knowledge.py +0 -0
  87. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/learn.py +0 -0
  88. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/learnedgraph.py +0 -0
  89. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/live.py +0 -0
  90. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/llm.py +0 -0
  91. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/logs.py +0 -0
  92. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/mcp.py +0 -0
  93. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/msauth.py +0 -0
  94. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/mssql.py +0 -0
  95. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/outbound.py +0 -0
  96. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/outbox.py +0 -0
  97. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/ownwork.py +0 -0
  98. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/phone.py +0 -0
  99. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/playbooks.py +0 -0
  100. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/pm.py +0 -0
  101. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/policy.py +0 -0
  102. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/problems.py +0 -0
  103. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/projects.py +0 -0
  104. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/promptmap.py +0 -0
  105. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/proof.py +0 -0
  106. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/proposals.py +0 -0
  107. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/quickbooks.py +0 -0
  108. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/rank.py +0 -0
  109. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/remote_assistant.py +0 -0
  110. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/research.py +0 -0
  111. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/reshape.py +0 -0
  112. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/responder.py +0 -0
  113. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/routing.py +0 -0
  114. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/scopes.py +0 -0
  115. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/selfclose.py +0 -0
  116. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/semantic.py +0 -0
  117. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/senders.py +0 -0
  118. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/session_artifacts.py +0 -0
  119. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/setup.py +0 -0
  120. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/sharepoint.py +0 -0
  121. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/sheets.py +0 -0
  122. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/spawn.py +0 -0
  123. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/teller.py +0 -0
  124. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/templates/coder.md +0 -0
  125. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/templates/counsel.md +0 -0
  126. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/templates/digest.md +0 -0
  127. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/templates/how-it-works.md +0 -0
  128. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/templates/learned.md +0 -0
  129. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/templates/playbook.md +0 -0
  130. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/templates/soul.md +0 -0
  131. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/templates/style.md +0 -0
  132. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/templates/triage.md +0 -0
  133. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/testing.py +0 -0
  134. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/toil.py +0 -0
  135. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/triage.py +0 -0
  136. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/update.py +0 -0
  137. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/verdicts.py +0 -0
  138. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/voice.py +0 -0
  139. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/waitroom.py +0 -0
  140. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/web/assets/GeneralWorkspace--IqTbJGE.css +0 -0
  141. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/web/favicon.ico +0 -0
  142. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/web/favicon.png +0 -0
  143. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/webguard.py +0 -0
  144. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/whoami.py +0 -0
  145. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/witness.py +0 -0
  146. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary/zoho.py +0 -0
  147. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary.egg-info/dependency_links.txt +0 -0
  148. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary.egg-info/entry_points.txt +0 -0
  149. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary.egg-info/requires.txt +0 -0
  150. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/taskuary.egg-info/top_level.txt +0 -0
  151. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_agent_environment.py +0 -0
  152. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_agent_wall.py +0 -0
  153. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_aisetup.py +0 -0
  154. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_answered_yourself.py +0 -0
  155. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_answers.py +0 -0
  156. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_api.py +0 -0
  157. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_assistant_opens.py +0 -0
  158. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_assistant_reactions.py +0 -0
  159. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_assistant_verdicts.py +0 -0
  160. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_async_triage.py +0 -0
  161. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_audit_chain.py +0 -0
  162. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_audit_fixes.py +0 -0
  163. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_aws_region.py +0 -0
  164. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_blackboard.py +0 -0
  165. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_browser_session.py +0 -0
  166. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_browserview.py +0 -0
  167. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_calendar.py +0 -0
  168. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_calendar_today.py +0 -0
  169. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_categories.py +0 -0
  170. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_cc.py +0 -0
  171. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_cc_triage.py +0 -0
  172. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_chat_doors.py +0 -0
  173. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_chat_is_not_one_task.py +0 -0
  174. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_chat_queue.py +0 -0
  175. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_climodels.py +0 -0
  176. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_clis.py +0 -0
  177. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_cloud.py +0 -0
  178. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_cloud_sources.py +0 -0
  179. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_compose.py +0 -0
  180. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_compose_sources.py +0 -0
  181. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_compose_workflows.py +0 -0
  182. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_concierge.py +0 -0
  183. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_config.py +0 -0
  184. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_context.py +0 -0
  185. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_core.py +0 -0
  186. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_counsel.py +0 -0
  187. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_cwlogs_paging.py +0 -0
  188. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_demo.py +0 -0
  189. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_deps.py +0 -0
  190. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_desktop.py +0 -0
  191. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_devtools.py +0 -0
  192. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_diff_review.py +0 -0
  193. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_digest_brief.py +0 -0
  194. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_docs_and_llm.py +0 -0
  195. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_docs_flow.py +0 -0
  196. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_duplicate_connectors.py +0 -0
  197. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_evening_brief.py +0 -0
  198. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_factory.py +0 -0
  199. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_feed_working.py +0 -0
  200. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_finished_chat_stays_finished.py +0 -0
  201. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_general.py +0 -0
  202. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_github_auto.py +0 -0
  203. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_guard.py +0 -0
  204. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_handbook.py +0 -0
  205. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_handbook_topics.py +0 -0
  206. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_handoff_channels.py +0 -0
  207. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_handraise.py +0 -0
  208. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_harvest.py +0 -0
  209. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_histgen.py +0 -0
  210. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_home_is_never_yours.py +0 -0
  211. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_imap_history.py +0 -0
  212. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_imapmail.py +0 -0
  213. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_imessage.py +0 -0
  214. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_intacct.py +0 -0
  215. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_invoice_workflow.py +0 -0
  216. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_kind_dispatch.py +0 -0
  217. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_knowledge.py +0 -0
  218. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_learnedgraph.py +0 -0
  219. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_lifecycle.py +0 -0
  220. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_local_file.py +0 -0
  221. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_mail_folders.py +0 -0
  222. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_mark_read.py +0 -0
  223. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_mcp.py +0 -0
  224. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_meeting_prep.py +0 -0
  225. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_message_thread.py +0 -0
  226. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_msauth.py +0 -0
  227. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_mssql.py +0 -0
  228. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_noop_ending.py +0 -0
  229. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_not_coding.py +0 -0
  230. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_note_retrieval.py +0 -0
  231. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_outbound_reports.py +0 -0
  232. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_own_rows_no_reply.py +0 -0
  233. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_playbooks.py +0 -0
  234. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_pm.py +0 -0
  235. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_poll_overlap.py +0 -0
  236. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_poll_parallel.py +0 -0
  237. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_poll_sources.py +0 -0
  238. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_pr_comments.py +0 -0
  239. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_problems.py +0 -0
  240. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_problems_dismiss.py +0 -0
  241. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_projects.py +0 -0
  242. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_proof_ci_proposals.py +0 -0
  243. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_pypi_downloads.py +0 -0
  244. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_quickbooks.py +0 -0
  245. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_rank.py +0 -0
  246. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_rate_limit.py +0 -0
  247. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_reclassify.py +0 -0
  248. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_remote_assistant.py +0 -0
  249. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_reply_channels.py +0 -0
  250. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_reply_voice.py +0 -0
  251. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_report_alerts.py +0 -0
  252. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_report_triage.py +0 -0
  253. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_research.py +0 -0
  254. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_reviews.py +0 -0
  255. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_scopes.py +0 -0
  256. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_seed_fits_a_tty.py +0 -0
  257. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_seeded_reports.py +0 -0
  258. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_semantic.py +0 -0
  259. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_send_targets.py +0 -0
  260. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_session_artifacts.py +0 -0
  261. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_setup.py +0 -0
  262. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_sheets_sharepoint.py +0 -0
  263. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_someone_else_replied.py +0 -0
  264. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_stay_open.py +0 -0
  265. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_stay_open_doors.py +0 -0
  266. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_store_app_cli.py +0 -0
  267. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_store_hotpaths.py +0 -0
  268. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_sync_clock.py +0 -0
  269. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_task_identity.py +0 -0
  270. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_teller.py +0 -0
  271. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_toil.py +0 -0
  272. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_triage_dataset.py +0 -0
  273. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_triage_images.py +0 -0
  274. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_update.py +0 -0
  275. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_update_refused.py +0 -0
  276. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_urgent_and_handoff.py +0 -0
  277. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_verdict_paths.py +0 -0
  278. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_verdict_sticks.py +0 -0
  279. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_vision.py +0 -0
  280. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_voice.py +0 -0
  281. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_watermark.py +0 -0
  282. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_web_entrypoint.py +0 -0
  283. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_webguard.py +0 -0
  284. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_whatsapp_documents.py +0 -0
  285. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_whatsapp_images.py +0 -0
  286. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_whoami.py +0 -0
  287. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_withdrawn.py +0 -0
  288. {taskuary-0.3.3.2 → taskuary-0.3.3.3}/tests/test_witness.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taskuary
3
- Version: 0.3.3.2
3
+ Version: 0.3.3.3
4
4
  Summary: Automate your job - the local-first operating system for your work: AI triage in, coding agents out.
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/ldbumble/taskuary
@@ -62,7 +62,7 @@ Dynamic: license-file
62
62
  # Taskuary
63
63
 
64
64
  [![CI](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml/badge.svg)](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml)
65
- [![PyPI](https://img.shields.io/pypi/v/taskuary.svg?cacheSeconds=300&release=0.3.3.2)](https://pypi.org/project/taskuary/)
65
+ [![PyPI](https://img.shields.io/pypi/v/taskuary.svg?cacheSeconds=300&release=0.3.3.3)](https://pypi.org/project/taskuary/)
66
66
  [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-3776ab.svg)](https://github.com/ldbumble/taskuary)
67
67
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
68
68
 
@@ -71,9 +71,9 @@ Dynamic: license-file
71
71
  Taskuary turns incoming messages into organized work. It sorts what matters, hands tasks to
72
72
  your agents, and brings decisions back to you. Nothing sends or ships without your approval.
73
73
 
74
- ![Taskuary: the pipe ranked by triage beside the assistant chat - a mail pulled in with its drafted reply, the Timeline in task view, the Reports and the Board.](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/hero.gif)
74
+ ![The Taskuary Studio: work arrives, triage decides what needs action, and agents take seats to work it.](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/hero.gif?v=studio)
75
75
 
76
- Taskuary is early—currently **v0.3.3.2**—so breaking changes are still possible before 1.0.
76
+ Taskuary is early—currently **v0.3.3.3**—so breaking changes are still possible before 1.0.
77
77
 
78
78
  <p align="center">
79
79
  <a href="https://taskuary.com/demo/"><img
@@ -85,16 +85,27 @@ Taskuary is early—currently **v0.3.3.2**—so breaking changes are still possi
85
85
 
86
86
  ## What Taskuary can do
87
87
 
88
- **One Timeline and Assistant**
88
+ **Connect every system. Keep control.**
89
89
 
90
- Mail, chats, issues, alerts, and reports appear on one Timeline and in one Assistant chat.
91
- Open any item to see its context, status, and available actions.
90
+ Bring your mail, chats, issue trackers, alerts, reports, and AI agents into one control center
91
+ that runs on your machine. Every input lands on a single Timeline with its context, status, and
92
+ available actions intact. Taskuary gives your tools one place to work together while you decide
93
+ what can run, what can send, and what needs your attention.
92
94
 
93
- ![Taskuary's unified work pipe and chat, populated only with fictional demo data.](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/screenshot-chat.png)
95
+ ![Taskuary's unified work pipe and chat, populated only with fictional demo data.](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/screenshot-chat.png?v=0.3.3.2)
94
96
 
95
- **AI triage**
97
+ **Turn incoming work into tasks automatically**
96
98
 
97
- Triage files routine updates, turns real requests into tasks, and puts urgent decisions first.
99
+ AI triage reads every incoming item, understands what it means, and turns actionable requests
100
+ into ready-to-run tasks. Routine updates are filed, questions get draft replies, and urgent
101
+ decisions move to the front. Every choice comes with a reason, and you can change it at any time.
102
+
103
+ **Watch the work. Approve the outcome.**
104
+
105
+ Taskuary keeps the system moving and brings you in when your judgment matters. The Assistant
106
+ speaks up when a reply is waiting, a task has gone quiet, a report has failed, or a meeting is
107
+ about to start. Each update arrives once, with the evidence and next actions attached, so you can
108
+ review the work, make the call, and approve what happens next.
98
109
 
99
110
  **Agents in your own repos**
100
111
 
@@ -105,25 +116,25 @@ Send coding work to Claude Code, Codex, Gemini, Cursor, Copilot, or another CLI.
105
116
  Watch the terminal, answer questions, review changes, and use the built-in browser without
106
117
  losing the session.
107
118
 
108
- **Scheduled checks**
119
+ **Describe a check. Let it run itself.**
109
120
 
110
- Watch databases, files, APIs, cloud logs, reports, or agent skills. Quiet checks stay silent.
121
+ Say what you want watched in plain words and Taskuary builds it: read a folder of CSVs every
122
+ morning, total the AP bills due in the next 30 days, count helpdesk tickets by day. Reports read
123
+ and summarize, on a schedule or on demand, and can come back with an AI summary. Workflows write
124
+ data and keep state. Quiet checks stay quiet, so the only one you hear about is the one that
125
+ failed.
111
126
 
112
127
  **You stay in control**
113
128
 
114
129
  Replies and proposed actions wait in Review. You decide what sends, runs, closes, or gets dismissed.
115
130
 
116
- **Catches what slipped**
117
-
118
- Surface unanswered replies, upcoming meetings, quiet tasks, failed reports, and important changes.
119
-
120
131
  ## How work flows
121
132
 
122
133
  ### 1. Work arrives and gets sorted
123
134
 
124
135
  Taskuary reads connected inboxes, separates tasks from noise, and shows the result on one Timeline.
125
136
 
126
- ![The Timeline in task view: every message with its verdict, and the drafted reply waiting on you](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/screenshot-timeline-crop.png)
137
+ ![The Timeline in task view: every message with its verdict, and the drafted reply waiting on you](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/screenshot-timeline-crop.png?v=0.3.3.2)
127
138
 
128
139
  ### 2. An agent works on it
129
140
 
@@ -135,7 +146,7 @@ Tasks move from **Queued** to **Agent working**. The Board shows live progress a
135
146
 
136
147
  Finished work, draft replies, questions, and loose ends return for review. You decide what to send, change, snooze, or dismiss.
137
148
 
138
- ![Taskuary assistant showing loose ends and suggested actions](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/screenshot-assistant.png)
149
+ ![The assistant walking the pipe: the item on the table, its drafted reply, and Approve & send waiting on you](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/screenshot-assistant.png?v=0.3.3.2)
139
150
 
140
151
  ## Install
141
152
 
@@ -1,7 +1,7 @@
1
1
  # Taskuary
2
2
 
3
3
  [![CI](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml/badge.svg)](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml)
4
- [![PyPI](https://img.shields.io/pypi/v/taskuary.svg?cacheSeconds=300&release=0.3.3.2)](https://pypi.org/project/taskuary/)
4
+ [![PyPI](https://img.shields.io/pypi/v/taskuary.svg?cacheSeconds=300&release=0.3.3.3)](https://pypi.org/project/taskuary/)
5
5
  [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-3776ab.svg)](https://github.com/ldbumble/taskuary)
6
6
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
7
 
@@ -10,9 +10,9 @@
10
10
  Taskuary turns incoming messages into organized work. It sorts what matters, hands tasks to
11
11
  your agents, and brings decisions back to you. Nothing sends or ships without your approval.
12
12
 
13
- ![Taskuary: the pipe ranked by triage beside the assistant chat - a mail pulled in with its drafted reply, the Timeline in task view, the Reports and the Board.](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/hero.gif)
13
+ ![The Taskuary Studio: work arrives, triage decides what needs action, and agents take seats to work it.](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/hero.gif?v=studio)
14
14
 
15
- Taskuary is early—currently **v0.3.3.2**—so breaking changes are still possible before 1.0.
15
+ Taskuary is early—currently **v0.3.3.3**—so breaking changes are still possible before 1.0.
16
16
 
17
17
  <p align="center">
18
18
  <a href="https://taskuary.com/demo/"><img
@@ -24,16 +24,27 @@ Taskuary is early—currently **v0.3.3.2**—so breaking changes are still possi
24
24
 
25
25
  ## What Taskuary can do
26
26
 
27
- **One Timeline and Assistant**
27
+ **Connect every system. Keep control.**
28
28
 
29
- Mail, chats, issues, alerts, and reports appear on one Timeline and in one Assistant chat.
30
- Open any item to see its context, status, and available actions.
29
+ Bring your mail, chats, issue trackers, alerts, reports, and AI agents into one control center
30
+ that runs on your machine. Every input lands on a single Timeline with its context, status, and
31
+ available actions intact. Taskuary gives your tools one place to work together while you decide
32
+ what can run, what can send, and what needs your attention.
31
33
 
32
- ![Taskuary's unified work pipe and chat, populated only with fictional demo data.](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/screenshot-chat.png)
34
+ ![Taskuary's unified work pipe and chat, populated only with fictional demo data.](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/screenshot-chat.png?v=0.3.3.2)
33
35
 
34
- **AI triage**
36
+ **Turn incoming work into tasks automatically**
35
37
 
36
- Triage files routine updates, turns real requests into tasks, and puts urgent decisions first.
38
+ AI triage reads every incoming item, understands what it means, and turns actionable requests
39
+ into ready-to-run tasks. Routine updates are filed, questions get draft replies, and urgent
40
+ decisions move to the front. Every choice comes with a reason, and you can change it at any time.
41
+
42
+ **Watch the work. Approve the outcome.**
43
+
44
+ Taskuary keeps the system moving and brings you in when your judgment matters. The Assistant
45
+ speaks up when a reply is waiting, a task has gone quiet, a report has failed, or a meeting is
46
+ about to start. Each update arrives once, with the evidence and next actions attached, so you can
47
+ review the work, make the call, and approve what happens next.
37
48
 
38
49
  **Agents in your own repos**
39
50
 
@@ -44,25 +55,25 @@ Send coding work to Claude Code, Codex, Gemini, Cursor, Copilot, or another CLI.
44
55
  Watch the terminal, answer questions, review changes, and use the built-in browser without
45
56
  losing the session.
46
57
 
47
- **Scheduled checks**
58
+ **Describe a check. Let it run itself.**
48
59
 
49
- Watch databases, files, APIs, cloud logs, reports, or agent skills. Quiet checks stay silent.
60
+ Say what you want watched in plain words and Taskuary builds it: read a folder of CSVs every
61
+ morning, total the AP bills due in the next 30 days, count helpdesk tickets by day. Reports read
62
+ and summarize, on a schedule or on demand, and can come back with an AI summary. Workflows write
63
+ data and keep state. Quiet checks stay quiet, so the only one you hear about is the one that
64
+ failed.
50
65
 
51
66
  **You stay in control**
52
67
 
53
68
  Replies and proposed actions wait in Review. You decide what sends, runs, closes, or gets dismissed.
54
69
 
55
- **Catches what slipped**
56
-
57
- Surface unanswered replies, upcoming meetings, quiet tasks, failed reports, and important changes.
58
-
59
70
  ## How work flows
60
71
 
61
72
  ### 1. Work arrives and gets sorted
62
73
 
63
74
  Taskuary reads connected inboxes, separates tasks from noise, and shows the result on one Timeline.
64
75
 
65
- ![The Timeline in task view: every message with its verdict, and the drafted reply waiting on you](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/screenshot-timeline-crop.png)
76
+ ![The Timeline in task view: every message with its verdict, and the drafted reply waiting on you](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/screenshot-timeline-crop.png?v=0.3.3.2)
66
77
 
67
78
  ### 2. An agent works on it
68
79
 
@@ -74,7 +85,7 @@ Tasks move from **Queued** to **Agent working**. The Board shows live progress a
74
85
 
75
86
  Finished work, draft replies, questions, and loose ends return for review. You decide what to send, change, snooze, or dismiss.
76
87
 
77
- ![Taskuary assistant showing loose ends and suggested actions](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/screenshot-assistant.png)
88
+ ![The assistant walking the pipe: the item on the table, its drafted reply, and Approve & send waiting on you](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/screenshot-assistant.png?v=0.3.3.2)
78
89
 
79
90
  ## Install
80
91
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "taskuary"
7
- version = "0.3.3.2"
7
+ version = "0.3.3.3"
8
8
  description = "Automate your job - the local-first operating system for your work: AI triage in, coding agents out."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -537,7 +537,7 @@ def raised(store, days: float = 2) -> str:
537
537
  return '\n'.join(out) or '(the assistant raised nothing in this window)'
538
538
 
539
539
 
540
- def parse(text: str, cands: list, max_lines: int = MAX_LINES) -> list:
540
+ def parse(store, text: str, cands: list, max_lines: int = MAX_LINES) -> list:
541
541
  """The model's list, kept honest: a key it invents must be idea:*, a candidate key keeps its
542
542
  kind and its buttons, and the text is the model's when it gave one. Every line keeps its WHY -
543
543
  the hub's facts for a candidate (plus the model's read on them), the model's own for an idea -
@@ -767,7 +767,7 @@ def think(store, cands: list, llm, instruction: str = None, max_lines: int = MAX
767
767
  from .llm import readable_images
768
768
  images = readable_images(store, _people_context(store)[1])
769
769
  text = llm(system, user, max_tokens=POST_TOKENS, **({'images': images} if images else {}))
770
- return parse(text, cands, max_lines), _notes(text), user
770
+ return parse(store, text, cands, max_lines), _notes(text), user
771
771
 
772
772
 
773
773
  def facts(store, watch_source_ids=None, watch_sources=None, systems_only: bool = False) -> str:
@@ -54,9 +54,19 @@ READONLY = {'claude': (('--dangerously-skip-permissions',), ('--tools', '')),
54
54
  # classifier above, which gets no tools at all because the text it classifies is untrusted input.
55
55
  # Claude's built-ins are named explicitly, and every MCP tool is denied because MCP permissions
56
56
  # are separate from --tools and a connector may expose writes beside reads.
57
+ REPORT_TOOLS = 'Read,Glob,Grep,WebFetch,WebSearch'
58
+
59
+ # --tools and --allowedTools answer different questions and a report needs BOTH: --tools says which
60
+ # built-ins EXIST in this run, --allowedTools says which may be used without asking a human. With
61
+ # the bypass flag dropped and only --tools given, WebFetch and WebSearch existed but still prompted,
62
+ # and a headless report has nobody to click: every fetch came back "Claude requested permissions to
63
+ # use WebFetch, but you haven't granted it yet" and the run narrated the refusal instead of the
64
+ # trending page. Read/Glob/Grep never prompt, which is why the gap stayed invisible for two months
65
+ # (the GitHub Trending report, 2026-09-04). Granting the same five is not a widening - the set the
66
+ # run may use is still exactly the set that exists.
57
67
  REPORT_READ = {
58
68
  'claude': (('--dangerously-skip-permissions',),
59
- ('--tools', 'Read,Glob,Grep,WebFetch,WebSearch', '--disallowedTools', 'mcp__*')),
69
+ ('--tools', REPORT_TOOLS, '--allowedTools', REPORT_TOOLS, '--disallowedTools', 'mcp__*')),
60
70
  'codex': READONLY['codex'],
61
71
  'gemini': READONLY['gemini'],
62
72
  }
@@ -81,8 +81,13 @@ _SAYS = (
81
81
  (re.compile(r"\b(not (my|our) (issue|problem|job|task|thing)|not (ours|mine)|nothing to do with (me|us)|let them (handle|deal|sort|do)"
82
82
  r"|ignore (it|this)|leave it (alone|be)|drop it|no need to (respond|reply|answer))\b", re.I), 'not_ours'),
83
83
  (re.compile(r"\b(never|always) (again|file|ignore)|remember (this|that)|from now on\b", re.I), 'not_ours_remember'),
84
+ # "research" came out of this list: the other verbs here read as DIAGNOSING something that
85
+ # exists ("look into that server", "find out why the export drops rows"), but research is the
86
+ # word for reading about the WORLD - "can you research the Factor Elara gravel bike" opened a
87
+ # coding agent on a checkout (the owner, 2026-09-04). Left off the list it reaches the brain,
88
+ # which has the sentence and can tell a product from a production database.
84
89
  (re.compile(r"\b(send (it |this |that )?to (the )?(coding agent|coder|codex|claude|gemini|agent)|have the (coder|agent)|let the (coder|agent)|start the (coder|agent)|code it|(coder|codex|claude) (should|can) (review|look|fix|handle)"
85
- r"|look into|investigate|dig into|find out|figure out|research|check (on |out )?(that|the|this|what|why|if)|ask (the |an )?(assistant|agent|coder) to)\b", re.I), 'coder'),
90
+ r"|look into|investigate|dig into|find out|figure out|check (on |out )?(that|the|this|what|why|if)|ask (the |an )?(assistant|agent|coder) to)\b", re.I), 'coder'),
86
91
  (re.compile(r"\b(i'?ll (do|take|handle) (it|this|that)|i will (do|take|handle) (it|this|that)|mine|leave it (to|with) me|(just )?make it a task|create a task|(add|put) (it|this) (on|to) my list)\b", re.I), 'mine'),
87
92
  # ENDING AN AGENT is not closing a task: "close the agent working" closed the item on the table
88
93
  # instead - a task the owner had not even asked about (2026-09-03). It comes first, so the words
@@ -137,6 +142,7 @@ SYSTEM = (
137
142
  "(hand it to the coding agent - put EVERYTHING the owner wants done after a colon, in their words: DECIDE: coder: find out "
138
143
  "why the bulk-approve fix from before did not stick, and create an admin login for X), mine (they will do it themselves), "
139
144
  "approve (send the drafted reply as it stands), not_ours_sender (the SENDER is noise - file everything from them from now on), "
145
+ "setup (reading, thinking or research with NO system to type at - Taskuary opens it as a conversation with the assistant: nothing is built, no repository is touched, and it can be handed to the coding agent later if it turns out something has to be), "
140
146
  "split (this arrival is TWO jobs - Taskuary breaks it in two, each with its own ref), "
141
147
  "setting (what they want is a SWITCH - Taskuary puts it in front of them to approve and never changes it itself), "
142
148
  "stop_agent (end the AGENT that is running - stopping a session is not closing a task, and never guess which: "
@@ -149,6 +155,28 @@ SYSTEM = (
149
155
  "have it wrong is the one thing you must not shrug off.\n"
150
156
  "A question or a remark is not a decision: answer it and write no DECIDE line. A polite request is "
151
157
  "not a question: \"can you look into that server\" is a hand-off, so decide it.\n"
158
+ # "can you research the Factor Elara gravel bike" opened a coding agent on a checkout (the
159
+ # owner, 2026-09-04), and before that a set-up did the same ("it doesn't need coding agent
160
+ # just a regular agent that will walk me through it"). The example above primes coder; this
161
+ # is the line that stops it generalising to everything phrased as a favour.
162
+ '...but coder and setup are not the same road, and the test is whether there is a SYSTEM to '
163
+ 'type at. A repository, a server, a database, a query, a file, an error, a failing report: '
164
+ 'coder. Reading about the world, comparing products, weighing an option, working out what to '
165
+ 'ask, anything whose answer is a judgement rather than a change: setup. Never send reading '
166
+ 'work to the coding agent because the sentence was polite - "can you research X" is a '
167
+ 'walk-through, not a hand-off.\n'
168
+ # "ignore" names the act and leaves out the part that lasts. Filing one mail and silencing a
169
+ # sender for ever are different acts, and the owner asked to be consulted on which (2026-09-04:
170
+ # "even without the button when you say ignore unless you say specific just for today it
171
+ # should ask what it should do with it in terms of memory").
172
+ '"Ignore it" and "not ours" name the act but not its SCOPE, and scope is the part that lasts. '
173
+ 'Unless they already said which - "just this once", "just for today", "never again", "always", '
174
+ '"from this sender" - do not pick one: say in one line that you can file this one, remember the '
175
+ 'kind, or silence the sender, and end with exactly OPTIONS: just this once | this kind from now '
176
+ 'on | everything from this sender. This is the one ambiguity worth a question, because the wide '
177
+ 'answers are the ones that quietly hide mail later. When they HAVE said which, decide it and '
178
+ 'write no options line: this once is not_ours, the kind is not_ours_remember, the sender is '
179
+ 'not_ours_sender.\n'
152
180
  "The thread you are given is the whole thread, the owner's own sent mail included. When it shows they "
153
181
  "already answered, say so as a fact. Only when the thread has no answer from them may you say the mail "
154
182
  "has not been read back yet - and then name the Sync button, never blame yourself for not seeing it.")
@@ -617,6 +645,18 @@ def parse_options(text: str) -> tuple[str, list]:
617
645
  return (text[:m.start()].strip(), opts if len(opts) >= 2 else [])
618
646
 
619
647
 
648
+ def _verdict_why(item: dict) -> str:
649
+ """Triage's own reason, with the verdict word it opens with trimmed off - "triage filed it as
650
+ fyi - triage: fyi - an automated notification" says it twice (the owner, 2026-09-04: "ask we
651
+ processed as fyi because of x")."""
652
+ w = (item.get('why') or '').strip()
653
+ for pre in ('triage: fyi -', 'triage: fyi', 'triage:'):
654
+ if w.lower().startswith(pre):
655
+ w = w[len(pre):].lstrip(' -·')
656
+ break
657
+ return f' - {w}' if w else ''
658
+
659
+
620
660
  def fallback(item: dict | None, opening: bool, pile_items: list = None) -> str:
621
661
  """No model: the facts in the same three beats - where from, what was done, what you need to do."""
622
662
  if not item:
@@ -632,9 +672,11 @@ def fallback(item: dict | None, opening: bool, pile_items: list = None) -> str:
632
672
  frm = f"{item.get('who') or 'Someone'} wrote on {item.get('channel') or 'email'}" + (f" ({funnel_age(item)})" if funnel_age(item) else '') + f": \"{item['title']}\""
633
673
  done = (f"the agent {item['summary']}" if item.get('summary') else
634
674
  'triage judged it a reply to write' if item['kind'] == 'review' else 'an agent proposed an action' if item['kind'] == 'action' else
635
- 'a coding task with no agent on it yet' if item.get('coding') else 'nothing has been done with it yet' if item['kind'] in ('asked', 'todo') else 'triage filed it as fyi')
675
+ 'a coding task with no agent on it yet' if item.get('coding') else 'nothing has been done with it yet' if item['kind'] in ('asked', 'todo')
676
+ else f'triage filed it as fyi{_verdict_why(item)}')
636
677
  need = ('approve the draft below, or redraft it' if item['kind'] == 'review' else 'say whether it may run' if item['kind'] == 'action' else
637
- 'reply, hand it to the coding agent, or say it is not ours' if item['kind'] in ('asked', 'todo') else 'nothing - read it if you like')
678
+ 'reply, hand it to the coding agent, or say it is not ours' if item['kind'] in ('asked', 'todo')
679
+ else 'nothing has to happen - make it a task, tell me to ignore this sender, or move on')
638
680
  return f"{frm}. Since then: {done}. From you: {need}."
639
681
  if item['kind'] == 'agent':
640
682
  return f"{item.get('agent') or 'An agent'} on {item.get('ref') or item['title']} " + (f"asked: {item['tail'][-1]}" if item.get('asking') and item.get('tail') else 'stopped at its prompt') + ' - answer it below.'
@@ -644,7 +686,7 @@ def fallback(item: dict | None, opening: bool, pile_items: list = None) -> str:
644
686
  return f"{item['title']}" + (f" ({item.get('who')})" if item.get('who') else '') + f" - {item.get('why') or 'the assistant raised this'}. Draft the follow-up, make it a task, or let it go."
645
687
  if item['kind'] == 'meeting': return f"{item['title']} is {item.get('why')}. Prep me, or move on."
646
688
  if item['kind'] == 'report':
647
- return f"{item['title']} landed {funnel_age(item)}" + (' and FAILED - the cause is in it.' if item.get('bad') else '.') + ' Read it with the button, or move on.'
689
+ return f"{item['title']} landed {funnel_age(item)}" + (' and FAILED - the cause is in it.' if item.get('bad') else '.') + ' It is open below - run it again, or move on.'
648
690
  if item['kind'] == 'agentdone': return f"{item.get('who') or 'The agent'} finished {item.get('ref') or item['title']}" + (f": {item['summary']}" if item.get('summary') else '.') + ' Want to see the final report?'
649
691
  lead = {'agent': f"{item.get('agent') or 'An agent'} is waiting on you on {item.get('ref') or item['title']}.",
650
692
  'meeting': f"{item['title']} is {item.get('why')}.",
@@ -634,6 +634,20 @@ def came_in(i: dict) -> bool:
634
634
  FYI_BATCH = 4 # fyi comes as a handful, not one at a time - there is nothing to do with each
635
635
  BLOCKED_AGAIN_MIN = 30 # an agent still waiting comes out again this long after it was shown
636
636
 
637
+ def _not_yet(i: dict) -> bool:
638
+ """On the timeline, but nothing to say about it YET - the walk skips it and comes back.
639
+
640
+ A meeting enters the pipe two hours out (SOON_MIN) so the day is visible, and its lane is
641
+ 'time', which puts it at the very front of the walk. So the assistant opened with a meeting
642
+ that was still an hour and a half away, ahead of mail that wanted answering now. It is the same
643
+ shape as an agent that is mid-run: real, on the board, and not yours to do anything about until
644
+ it stops (the owner, 2026-09-04: "meetings should not go down into the chat until 15 minutes
645
+ before like a agent in middel of working"). alerts() already drew this line at ALERT_MIN; the
646
+ walk did not.
647
+ """
648
+ return i['kind'] == 'meeting' and i.get('mins') is not None and i['mins'] > ALERT_MIN
649
+
650
+
637
651
  def next_item(store, key: str = None, only: str = None) -> dict | None:
638
652
  """What comes out of the mouth: the named item (read or not - the chat may return to it), or
639
653
  the first unread one - of the mail alone when `only` is 'mail'. Something still being triaged is
@@ -642,6 +656,7 @@ def next_item(store, key: str = None, only: str = None) -> dict | None:
642
656
  if key: return next((i for i in build(store, keep_surfaced=True)['items'] if i['key'] == key), None) or batch_item(store, key)
643
657
  again = (datetime.now() - timedelta(minutes=BLOCKED_AGAIN_MIN)).strftime('%Y-%m-%d %H:%M:%S')
644
658
  ready = [i for i in pile(store, force=True)['items'] if not i.get('settling') and i['lane'] != 'working'
659
+ and not _not_yet(i)
645
660
  and (not i.get('surfaced') or _ts(i.get('surfaced_at')) <= again)]
646
661
  if only == 'mail': ready = [i for i in ready if came_in(i) or i['kind'] in INTERRUPTS]
647
662
  return ready[0] if ready else None
@@ -172,6 +172,25 @@ def dock_task(store, actor='owner') -> tuple[dict, bool]:
172
172
  return store.get_task(tid), True
173
173
 
174
174
 
175
+ def retire_dock(store, actor='owner'):
176
+ """Close the current dock conversation so the next caller is handed a fresh one - what the
177
+ 'New chat' button does, minus the live session there is none of in a just-started process.
178
+
179
+ A relaunch should open a NEW chat, not resume yesterday's (the owner, 2026-09-04: "after you
180
+ restart a new chat should be there not same chat"): the dock task id lives in settings, so
181
+ without this the same conversation - and the same model session - came back forever. A dock
182
+ nobody said anything in is left alone, or every launch would archive an empty guide task."""
183
+ raw = store.get_settings().get('assistant_dock_task_id')
184
+ task = store.get_task(int(raw)) if str(raw or '').isdigit() else None
185
+ if not (task and is_dock(task) and task.get('Status') not in ('done', 'dropped')): return None
186
+ if not history(store, task['TaskId']): return None
187
+ from . import concierge
188
+ store.update_task(task['TaskId'], {'Status': 'done'}, actor)
189
+ store.audit('task', task['TaskId'], 'archive_assistant_dock', actor)
190
+ store.set_setting(f"{concierge.SID_KEY}:{task['TaskId']}", '', actor)
191
+ return task['TaskId']
192
+
193
+
175
194
  def dock_snapshot(store) -> str:
176
195
  """A compact, live map of the surfaces the hovering assistant is meant to explain.
177
196
 
@@ -17,13 +17,15 @@ SKILL_FILE = Path(__file__).parent / 'skills' / 'soul-interview' / 'SKILL.md'
17
17
  _LEGACY_LABELS = {
18
18
  'who': 'Who are you, and what is your work?',
19
19
  'work': 'What kind of work reaches you?',
20
- 'task': 'What may the assistant handle without asking?',
21
- 'never': 'What must never happen without you?',
20
+ 'task': 'What kind of work is worth turning into a task?',
21
+ 'never': 'Which ambiguities or sensitive situations need your judgment?',
22
22
  'people': 'Which people and relationships matter?',
23
23
  'systems': 'Which systems or sources of truth matter?',
24
24
  'voice': 'How should communication sound?',
25
25
  }
26
26
 
27
+ _MANAGED_HEADINGS = ('## Connected systems', '## Project relationships', '## Repository map')
28
+
27
29
 
28
30
  def skill_text() -> str:
29
31
  """The portable interview contract used for both questioning and writing."""
@@ -45,10 +47,35 @@ def _known(ctx: dict) -> str:
45
47
  bits = [f"Owner name on file: {ctx['owner'] or '(not set)'}",
46
48
  f"Connected sources: {', '.join(ctx['channels']) or 'none yet'}",
47
49
  f"Known repositories (mention only if relevant): {', '.join(ctx['repos']) or 'none'}",
48
- f"Frequent correspondents: {'; '.join(ctx['writes_most']) or 'nothing ingested yet'}"]
50
+ f"Frequent correspondents (activity only; not proof of importance or authority): "
51
+ f"{'; '.join(ctx['writes_most']) or 'nothing ingested yet'}"]
49
52
  return 'WHAT TASKUARY ALREADY KNOWS (facts; do not contradict them):\n' + '\n'.join(bits)
50
53
 
51
54
 
55
+ def _section(text: str, heading: str) -> str:
56
+ """One level-two section, through the next level-two heading or end of document."""
57
+ match = re.search(rf'(?ms)^{re.escape(heading)}[^\n]*\n.*?(?=^##\s|\Z)', str(text or ''))
58
+ return match.group(0).rstrip() if match else ''
59
+
60
+
61
+ def _without_section(text: str, heading: str) -> str:
62
+ return re.sub(rf'(?ms)^\s*{re.escape(heading)}[^\n]*\n.*?(?=^##\s|\Z)', '', str(text or '')).strip()
63
+
64
+
65
+ def _carry_managed_sections(current: str, drafted: str) -> str:
66
+ """Keep machine-maintained operational memory out of the model's authorship.
67
+
68
+ The interview rewrites the owner's prose. Connected systems and repository notes are updated
69
+ elsewhere, while project/person edges live in SQLite and are rendered after the save. A model
70
+ draft must therefore be unable to erase or fabricate any of those sections.
71
+ """
72
+ body = str(drafted or '')
73
+ for heading in _MANAGED_HEADINGS:
74
+ body = _without_section(body, heading)
75
+ carried = [_section(current, heading) for heading in ('## Connected systems', '## Repository map')]
76
+ return '\n\n'.join([body.rstrip()] + [block for block in carried if block]).strip()
77
+
78
+
52
79
  def _answers(value) -> list[dict]:
53
80
  """Normalize adaptive transcripts and the previous seven-field request shape."""
54
81
  if isinstance(value, list):
@@ -146,8 +173,14 @@ def draft(store, answers, llm=None) -> str:
146
173
 
147
174
  def write(store, answers, actor: str = 'owner', llm=None) -> str:
148
175
  """Draft, save, and audit an owner-controlled SOUL.md."""
149
- body = draft(store, answers, llm)
176
+ body = _carry_managed_sections(store.get_doc('soul') or '', draft(store, answers, llm))
150
177
  store.save_doc('soul', body, actor)
178
+ # Structured project/person memory is not interview prose. Re-render it from the database
179
+ # after every interview, and refresh the carried connection block from current configuration.
180
+ from .docsync import sync_connections, sync_projects
181
+ sync_connections(store, actor)
182
+ sync_projects(store, actor)
183
+ body = store.get_doc('soul') or body
151
184
  transcript = _answers(answers)
152
185
  store.audit('doc', 0, 'soul_interview', actor,
153
186
  detail={'answered': sum(bool(row['a']) for row in transcript),
@@ -201,7 +201,8 @@ def wa_status(c) -> dict:
201
201
  jid = str(st.get('jid') or '')
202
202
  out = {'connected': bool(st.get('connected')), 'me': st.get('me') or '', 'jid': jid,
203
203
  'phone': ('+' + jid.split('@')[0].split(':')[0]) if jid and jid.split(':')[0].split('@')[0].isdigit() else '', # the paired number, from the account jid
204
- 'pairing_code': st.get('pairingCode') or '', 'qr_svg': ''}
204
+ 'pairing_code': st.get('pairingCode') or '', 'qr_svg': '',
205
+ 'reconnect': st.get('reconnect') or {}, 'filter': st.get('filter') or {}}
205
206
  if st.get('qr') and not out['connected']:
206
207
  import segno
207
208
  out['qr_svg'] = segno.make(st['qr'], error='m').svg_data_uri(scale=5, border=2, dark='#1e1e2e', light='#ffffff')
@@ -222,6 +223,13 @@ def wa_chats(c) -> list:
222
223
  r['n'] += 1
223
224
  if not m.get('fromMe') and m.get('name'): r['name'] = m['name'] # the other side's push name, never ours
224
225
  if (m.get('ts') or 0) >= r['last']: r['last'], r['snippet'] = m.get('ts') or 0, (m.get('text') or '')[:80]
226
+ # The bridge sees the routing JID before deciding whether to decrypt. That lets the source
227
+ # picker show a newly active chat without retaining its sender, text, or media first.
228
+ for m in out.get('blockedChats', []):
229
+ jid = m.get('jid') or ''
230
+ if not jid or jid.endswith('@broadcast') or jid in by: continue
231
+ by[jid] = {'jid': jid, 'group': bool(m.get('group')), 'name': '', 'n': 0,
232
+ 'last': m.get('last') or 0, 'snippet': 'not opened - chat is not authorized'}
225
233
  rows = sorted(by.values(), key=lambda r: -r['last'])
226
234
  for r in rows: r['last'] = datetime.fromtimestamp(r['last']).strftime('%Y-%m-%d %H:%M') if r['last'] else ''
227
235
  return rows
@@ -254,6 +262,13 @@ def poll_whatsapp(store, c, sources: list, llm=None, file_only=False) -> int:
254
262
  srcs = [s for s in sources if s.get('Channel', 'whatsapp') == 'whatsapp' and s.get('Address')]
255
263
  star = any(s['Address'] == '*' for s in srcs)
256
264
  want = {s['Address'] for s in srcs if s['Address'] != '*'}
265
+ notify_chat = str(cfg.get('notify_chat') or '').strip()
266
+ bridge_want = want | ({notify_chat} if notify_chat else set()) # approvals/assistant may not be an inbound source
267
+ # Filtering here alone was too late: Baileys had already decrypted and downloaded media from
268
+ # every chat. Give the same policy to its pre-decryption hook before asking for messages.
269
+ try: _wa(c, '/filter', {'allDirect': star, 'jids': sorted(bridge_want)})
270
+ except RuntimeError as e:
271
+ if '(404)' not in str(e): raise # an older detached bridge: keep polling until its next restart
257
272
  out = _wa(c, f"/messages?after={int(cfg.get('wa_seq') or 0)}")
258
273
  n, took = 0, []
259
274
  from . import phone, remote_assistant
@@ -750,10 +750,16 @@ CONNECTION_OF = {'mssql': mssql_connection, 'winrm': winrm_connection, 'database
750
750
  'google_sheets': _sheets_connection}
751
751
 
752
752
 
753
+ # Report types whose data IS the store - they reach no further than the local database, so they
754
+ # can neither be slow nor unreachable. Everything else dials out: a SQL box, an API, a share.
755
+ # run_due_reports runs these FIRST for that reason, so keep the two uses of this list together.
756
+ STORE_BACKED = ('digest', 'evening_inbox', 'automate', 'assistant', 'agent', 'calendar', 'kb_search', 'kb_reindex',
757
+ 'metric', 'metric_check', 'handbook_search', 'handbook_write', 'handbook_vote',
758
+ 'hub_search', 'hub_write', 'hub_vote', 'hub_comment')
759
+
760
+
753
761
  def resolve_cfg(store, cfg: dict) -> dict:
754
- if cfg.get('type') in ('digest', 'evening_inbox', 'automate', 'assistant', 'agent', 'calendar', 'kb_search', 'kb_reindex',
755
- 'metric', 'metric_check', 'handbook_search', 'handbook_write', 'handbook_vote',
756
- 'hub_search', 'hub_write', 'hub_vote', 'hub_comment'):
762
+ if cfg.get('type') in STORE_BACKED:
757
763
  return {**cfg, 'store': store} # their data IS the store (the agent's: its profile; the calendar's: the cards; the knowledge base: its index)
758
764
  conn = CONNECTION_OF.get(cfg.get('type'))
759
765
  if conn:
@@ -1239,15 +1245,41 @@ def deliver_report(store, src: dict, cfg: dict, subject: str, body: str) -> dict
1239
1245
  return {'gate': 'auto', 'message_id': mid, 'sent': sent}
1240
1246
 
1241
1247
 
1248
+ def _own_data(src: dict) -> bool:
1249
+ """Does this report read nothing but the store? An unknown type counts as dialling out, which
1250
+ is the conservative half of the split - it waits behind the local ones rather than ahead."""
1251
+ try: return (json.loads(src.get('ConfigJson') or '{}').get('type') or 'rest') in STORE_BACKED
1252
+ except ValueError: return False
1253
+
1254
+
1242
1255
  def run_due_reports(store, startup: bool = False) -> int:
1256
+ """Every due report, own-data ones FIRST and each one walled off from the others.
1257
+
1258
+ On a startup catch-up these run one after another, so a source that is merely unreachable used
1259
+ to hold up everything behind it: a SQL Server that was not there spent 41 s in an ODBC login
1260
+ timeout, and the owner's Morning digest and Assistant post - which read nothing but Taskuary's
1261
+ own database - waited it out (the owner, 2026-09-04: the first item took ~3 minutes). Ordering
1262
+ the local ones first means what they actually read lands while the dial-out ones take their time.
1263
+
1264
+ And one report may not cost the others their run. A failing executor files a FAILED row and
1265
+ returns, but a runner that RAISES used to abort the whole loop, silently skipping every report
1266
+ after it and leaving them un-touched so the next poll started over.
1267
+ """
1243
1268
  from .llm import build_llm
1244
1269
  try: llm = build_llm(store)
1245
1270
  except Exception: llm = None
1271
+ due = [s for s in store.list_sources() if s['Channel'] == 'report'
1272
+ and is_due(json.loads(s.get('ConfigJson') or '{}'), s.get('LastPolledAt'), startup)]
1246
1273
  n = 0
1247
- for src in store.list_sources():
1248
- if src['Channel'] != 'report': continue
1249
- if is_due(json.loads(src.get('ConfigJson') or '{}'), src.get('LastPolledAt'), startup):
1274
+ for src in sorted(due, key=lambda s: not _own_data(s)):
1275
+ try:
1250
1276
  run_report_source(store, src, llm)
1251
- store.touch_source(src['SourceId'])
1252
1277
  n += 1
1278
+ except Exception as e:
1279
+ # touched anyway: a report that blew up must wait for its next slot like any other,
1280
+ # or a poll every few minutes re-runs the broken one for ever
1281
+ logger.warning(f"report {src.get('Address')} raised, skipping it and running the rest - {e}")
1282
+ finally:
1283
+ try: store.touch_source(src['SourceId'])
1284
+ except Exception as e: logger.warning(f"could not touch source {src['SourceId']}: {e}")
1253
1285
  return n