taskuary 0.3.3.3__tar.gz → 0.3.3.4__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 (295) hide show
  1. {taskuary-0.3.3.3/taskuary.egg-info → taskuary-0.3.3.4}/PKG-INFO +3 -3
  2. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/README.md +2 -2
  3. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/pyproject.toml +1 -1
  4. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/agents.py +30 -0
  5. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/assistant.py +49 -3
  6. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/blackboard.py +16 -0
  7. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/channels.py +59 -2
  8. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/coder.py +11 -3
  9. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/concierge.py +208 -74
  10. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/funnel.py +199 -70
  11. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/general.py +31 -9
  12. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/github.py +43 -0
  13. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/histgen.py +15 -3
  14. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/messengers.py +28 -5
  15. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/outbound.py +40 -11
  16. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/outbox.py +49 -13
  17. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/remote_assistant.py +22 -6
  18. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/reports.py +25 -3
  19. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/responder.py +8 -2
  20. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/server.py +325 -58
  21. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/session_artifacts.py +3 -3
  22. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/store.py +156 -11
  23. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/terminal.py +101 -13
  24. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/verdicts.py +5 -4
  25. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/wabridge.py +2 -0
  26. taskuary-0.3.3.3/taskuary/web/assets/GeneralWorkspace-BpOeJv4L.js → taskuary-0.3.3.4/taskuary/web/assets/GeneralWorkspace-CugBH3Pj.js +2 -2
  27. taskuary-0.3.3.3/taskuary/web/assets/HubView-BhP6xVxh.js → taskuary-0.3.3.4/taskuary/web/assets/HubView-CKPusAgu.js +1 -1
  28. taskuary-0.3.3.4/taskuary/web/assets/index-Dw2KclKc.css +32 -0
  29. taskuary-0.3.3.4/taskuary/web/assets/index-HgTGxZl1.js +1140 -0
  30. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/web/index.html +2 -2
  31. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/whatsapp/bridge.mjs +45 -0
  32. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/whatsapp/package.json +1 -1
  33. taskuary-0.3.3.4/taskuary/whatsapp/roster.mjs +83 -0
  34. taskuary-0.3.3.4/taskuary/whatsapp/roster.test.mjs +43 -0
  35. {taskuary-0.3.3.3 → taskuary-0.3.3.4/taskuary.egg-info}/PKG-INFO +3 -3
  36. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary.egg-info/SOURCES.txt +10 -4
  37. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_agent_environment.py +28 -0
  38. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_agent_wall.py +14 -0
  39. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_answered_yourself.py +47 -4
  40. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_api.py +7 -2
  41. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_assistant.py +17 -0
  42. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_assistant_reactions.py +43 -11
  43. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_chat_doors.py +69 -0
  44. taskuary-0.3.3.4/tests/test_chat_freshness.py +85 -0
  45. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_concierge.py +96 -15
  46. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_core.py +15 -0
  47. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_funnel.py +206 -11
  48. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_general.py +37 -0
  49. taskuary-0.3.3.4/tests/test_github_images.py +117 -0
  50. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_histgen.py +7 -0
  51. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_lifecycle.py +20 -0
  52. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_live.py +7 -1
  53. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_messengers.py +18 -2
  54. taskuary-0.3.3.4/tests/test_outbox.py +77 -0
  55. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_remote_assistant.py +5 -3
  56. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_report_order_and_research.py +5 -4
  57. taskuary-0.3.3.4/tests/test_report_refusal.py +137 -0
  58. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_send_targets.py +57 -5
  59. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_session_artifacts.py +6 -4
  60. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_terminal.py +60 -11
  61. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_wabridge.py +3 -2
  62. taskuary-0.3.3.3/taskuary/web/assets/index-B0RiuVsJ.js +0 -1140
  63. taskuary-0.3.3.3/taskuary/web/assets/index-BVYz6QiM.css +0 -32
  64. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/LICENSE +0 -0
  65. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/setup.cfg +0 -0
  66. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/__init__.py +0 -0
  67. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/aisetup.py +0 -0
  68. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/artifacts.py +0 -0
  69. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/aws.py +0 -0
  70. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/azure.py +0 -0
  71. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/browserview.py +0 -0
  72. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/calendar.py +0 -0
  73. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/categories.py +0 -0
  74. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/ci.py +0 -0
  75. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/cli.py +0 -0
  76. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/climodels.py +0 -0
  77. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/clis.py +0 -0
  78. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/compose.py +0 -0
  79. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/config.py +0 -0
  80. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/context.py +0 -0
  81. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/counsel.py +0 -0
  82. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/db.py +0 -0
  83. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/demo.py +0 -0
  84. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/deps.py +0 -0
  85. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/desktop.py +0 -0
  86. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/devtools.py +0 -0
  87. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/digest.py +0 -0
  88. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/docsync.py +0 -0
  89. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/evalset.py +0 -0
  90. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/evening.py +0 -0
  91. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/guard.py +0 -0
  92. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/handbook.py +0 -0
  93. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/handraise.py +0 -0
  94. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/hooks.py +0 -0
  95. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/hub.py +0 -0
  96. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/imapmail.py +0 -0
  97. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/imessage.py +0 -0
  98. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/ingest.py +0 -0
  99. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/intacct.py +0 -0
  100. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/interview.py +0 -0
  101. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/invoice_workflow.py +0 -0
  102. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/knowledge.py +0 -0
  103. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/learn.py +0 -0
  104. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/learnedgraph.py +0 -0
  105. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/live.py +0 -0
  106. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/llm.py +0 -0
  107. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/logs.py +0 -0
  108. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/mcp.py +0 -0
  109. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/msauth.py +0 -0
  110. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/mssql.py +0 -0
  111. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/ownwork.py +0 -0
  112. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/phone.py +0 -0
  113. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/playbooks.py +0 -0
  114. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/pm.py +0 -0
  115. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/policy.py +0 -0
  116. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/problems.py +0 -0
  117. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/projects.py +0 -0
  118. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/promptmap.py +0 -0
  119. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/proof.py +0 -0
  120. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/proposals.py +0 -0
  121. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/quickbooks.py +0 -0
  122. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/rank.py +0 -0
  123. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/research.py +0 -0
  124. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/reshape.py +0 -0
  125. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/routing.py +0 -0
  126. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/scopes.py +0 -0
  127. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/selfclose.py +0 -0
  128. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/semantic.py +0 -0
  129. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/senders.py +0 -0
  130. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/setup.py +0 -0
  131. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/sharepoint.py +0 -0
  132. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/sheets.py +0 -0
  133. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/skills/soul-interview/SKILL.md +0 -0
  134. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/spawn.py +0 -0
  135. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/teller.py +0 -0
  136. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/templates/coder.md +0 -0
  137. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/templates/counsel.md +0 -0
  138. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/templates/digest.md +0 -0
  139. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/templates/how-it-works.md +0 -0
  140. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/templates/learned.md +0 -0
  141. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/templates/playbook.md +0 -0
  142. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/templates/soul.md +0 -0
  143. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/templates/style.md +0 -0
  144. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/templates/triage.md +0 -0
  145. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/testing.py +0 -0
  146. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/toil.py +0 -0
  147. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/triage.py +0 -0
  148. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/update.py +0 -0
  149. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/voice.py +0 -0
  150. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/waitroom.py +0 -0
  151. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/web/assets/GeneralWorkspace--IqTbJGE.css +0 -0
  152. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/web/favicon.ico +0 -0
  153. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/web/favicon.png +0 -0
  154. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/webguard.py +0 -0
  155. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/whatsapp/policy.mjs +0 -0
  156. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/whatsapp/policy.test.mjs +0 -0
  157. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/whoami.py +0 -0
  158. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/witness.py +0 -0
  159. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary/zoho.py +0 -0
  160. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary.egg-info/dependency_links.txt +0 -0
  161. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary.egg-info/entry_points.txt +0 -0
  162. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary.egg-info/requires.txt +0 -0
  163. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/taskuary.egg-info/top_level.txt +0 -0
  164. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_agent_identity.py +0 -0
  165. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_agent_source.py +0 -0
  166. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_aisetup.py +0 -0
  167. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_answers.py +0 -0
  168. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_assistant_opens.py +0 -0
  169. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_assistant_verdicts.py +0 -0
  170. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_async_triage.py +0 -0
  171. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_audit_chain.py +0 -0
  172. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_audit_fixes.py +0 -0
  173. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_aws_region.py +0 -0
  174. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_blackboard.py +0 -0
  175. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_browser_session.py +0 -0
  176. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_browserview.py +0 -0
  177. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_calendar.py +0 -0
  178. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_calendar_today.py +0 -0
  179. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_categories.py +0 -0
  180. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_cc.py +0 -0
  181. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_cc_triage.py +0 -0
  182. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_chat_is_not_one_task.py +0 -0
  183. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_chat_queue.py +0 -0
  184. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_climodels.py +0 -0
  185. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_clis.py +0 -0
  186. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_cloud.py +0 -0
  187. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_cloud_sources.py +0 -0
  188. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_compose.py +0 -0
  189. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_compose_sources.py +0 -0
  190. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_compose_workflows.py +0 -0
  191. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_config.py +0 -0
  192. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_context.py +0 -0
  193. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_counsel.py +0 -0
  194. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_cwlogs_paging.py +0 -0
  195. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_demo.py +0 -0
  196. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_deps.py +0 -0
  197. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_desktop.py +0 -0
  198. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_devtools.py +0 -0
  199. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_diff_review.py +0 -0
  200. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_digest_brief.py +0 -0
  201. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_docs_and_llm.py +0 -0
  202. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_docs_flow.py +0 -0
  203. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_duplicate_connectors.py +0 -0
  204. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_evening_brief.py +0 -0
  205. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_factory.py +0 -0
  206. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_feed_working.py +0 -0
  207. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_finished_chat_stays_finished.py +0 -0
  208. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_floating_assistant.py +0 -0
  209. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_follow_up_verdict.py +0 -0
  210. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_github_auto.py +0 -0
  211. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_guard.py +0 -0
  212. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_handbook.py +0 -0
  213. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_handbook_topics.py +0 -0
  214. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_handoff_channels.py +0 -0
  215. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_handraise.py +0 -0
  216. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_harvest.py +0 -0
  217. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_home_is_never_yours.py +0 -0
  218. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_ignore_sender.py +0 -0
  219. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_imap_history.py +0 -0
  220. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_imapmail.py +0 -0
  221. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_imessage.py +0 -0
  222. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_intacct.py +0 -0
  223. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_invoice_workflow.py +0 -0
  224. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_kind_dispatch.py +0 -0
  225. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_knowledge.py +0 -0
  226. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_learnedgraph.py +0 -0
  227. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_local_file.py +0 -0
  228. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_mail_folders.py +0 -0
  229. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_mark_read.py +0 -0
  230. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_mcp.py +0 -0
  231. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_meeting_prep.py +0 -0
  232. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_message_thread.py +0 -0
  233. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_msauth.py +0 -0
  234. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_mssql.py +0 -0
  235. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_noop_ending.py +0 -0
  236. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_not_coding.py +0 -0
  237. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_note_retrieval.py +0 -0
  238. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_outbound_reports.py +0 -0
  239. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_own_rows_no_reply.py +0 -0
  240. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_playbooks.py +0 -0
  241. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_pm.py +0 -0
  242. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_poll_overlap.py +0 -0
  243. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_poll_parallel.py +0 -0
  244. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_poll_sources.py +0 -0
  245. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_pr_comments.py +0 -0
  246. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_problems.py +0 -0
  247. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_problems_dismiss.py +0 -0
  248. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_projects.py +0 -0
  249. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_promptmap_and_catalog.py +0 -0
  250. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_proof_ci_proposals.py +0 -0
  251. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_pypi_downloads.py +0 -0
  252. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_quickbooks.py +0 -0
  253. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_rank.py +0 -0
  254. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_rate_limit.py +0 -0
  255. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_reclassify.py +0 -0
  256. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_reply_channels.py +0 -0
  257. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_reply_voice.py +0 -0
  258. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_report_alerts.py +0 -0
  259. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_report_triage.py +0 -0
  260. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_research.py +0 -0
  261. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_reviews.py +0 -0
  262. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_scopes.py +0 -0
  263. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_seed_fits_a_tty.py +0 -0
  264. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_seeded_reports.py +0 -0
  265. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_semantic.py +0 -0
  266. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_setup.py +0 -0
  267. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_sheets_sharepoint.py +0 -0
  268. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_someone_else_replied.py +0 -0
  269. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_soul_interview.py +0 -0
  270. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_stay_open.py +0 -0
  271. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_stay_open_doors.py +0 -0
  272. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_store_app_cli.py +0 -0
  273. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_store_hotpaths.py +0 -0
  274. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_sync_clock.py +0 -0
  275. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_task_identity.py +0 -0
  276. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_teller.py +0 -0
  277. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_toil.py +0 -0
  278. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_triage_dataset.py +0 -0
  279. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_triage_images.py +0 -0
  280. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_update.py +0 -0
  281. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_update_refused.py +0 -0
  282. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_urgent_and_handoff.py +0 -0
  283. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_verdict_paths.py +0 -0
  284. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_verdict_sticks.py +0 -0
  285. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_vision.py +0 -0
  286. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_voice.py +0 -0
  287. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_waitroom.py +0 -0
  288. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_watermark.py +0 -0
  289. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_web_entrypoint.py +0 -0
  290. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_webguard.py +0 -0
  291. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_whatsapp_documents.py +0 -0
  292. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_whatsapp_images.py +0 -0
  293. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_whoami.py +0 -0
  294. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/tests/test_withdrawn.py +0 -0
  295. {taskuary-0.3.3.3 → taskuary-0.3.3.4}/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.3
3
+ Version: 0.3.3.4
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.3)](https://pypi.org/project/taskuary/)
65
+ [![PyPI](https://img.shields.io/pypi/v/taskuary.svg?cacheSeconds=300&release=0.3.3.4)](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
 
@@ -73,7 +73,7 @@ your agents, and brings decisions back to you. Nothing sends or ships without yo
73
73
 
74
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.3**—so breaking changes are still possible before 1.0.
76
+ Taskuary is early—currently **v0.3.3.4**—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
@@ -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.3)](https://pypi.org/project/taskuary/)
4
+ [![PyPI](https://img.shields.io/pypi/v/taskuary.svg?cacheSeconds=300&release=0.3.3.4)](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
 
@@ -12,7 +12,7 @@ your agents, and brings decisions back to you. Nothing sends or ships without yo
12
12
 
13
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.3**—so breaking changes are still possible before 1.0.
15
+ Taskuary is early—currently **v0.3.3.4**—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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "taskuary"
7
- version = "0.3.3.3"
7
+ version = "0.3.3.4"
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" }
@@ -13,6 +13,27 @@ from . import spawn
13
13
  from .store import task_ref
14
14
  from .clis import preset_args
15
15
 
16
+ _CLI_CHILDREN = set()
17
+ _CLI_CHILDREN_LOCK = threading.Lock()
18
+
19
+
20
+ def shutdown_cli_children():
21
+ """Stop headless CLI calls that are still running when Taskuary shuts down.
22
+
23
+ Normal calls remove themselves when their one-shot process exits. Keeping the live Popen
24
+ objects here gives the application lifespan a deterministic cleanup road instead of leaving
25
+ an invisible Claude/Codex process behind after the server is gone.
26
+ """
27
+ with _CLI_CHILDREN_LOCK:
28
+ children = list(_CLI_CHILDREN)
29
+ _CLI_CHILDREN.clear()
30
+ for child in children:
31
+ try:
32
+ if child.poll() is None: child.kill()
33
+ except Exception:
34
+ pass
35
+ return len(children)
36
+
16
37
 
17
38
  def _git(cwd, *args):
18
39
  try:
@@ -371,6 +392,13 @@ def run_cli(profile: dict, prompt: str, trace, resume: str = None, cancel=None,
371
392
  # to the saved profile, which is also used to open the interactive terminal TUI).
372
393
  is_codex = _cli_name(name) == 'codex' and bool(args) and args[0] in ('exec', 'e')
373
394
  if is_codex and '--json' not in args: args.append('--json')
395
+ # Same reasoning for the trust check: codex refuses to start outside a git repo with "Not
396
+ # inside a trusted directory and --skip-git-repo-check was not specified", and half of what
397
+ # this app asks a CLI runs in ~/.taskuary/scratch on purpose - the classifier, the drafter,
398
+ # STYLE.md generation - none of which is a checkout. Headlessly there is nobody to answer the
399
+ # trust prompt, so every one of those died before the model was reached (issue #34). The flag
400
+ # only relaxes WHERE codex may run; the sandbox flags decide what it may do.
401
+ if is_codex and '--skip-git-repo-check' not in args: args.append('--skip-git-repo-check')
374
402
  cmd = _resolve_cmd(name) + args
375
403
  # which model works it: profile default, or a per-run override from the UI. The flag
376
404
  # name is configurable because every CLI spells it differently (claude/codex: --model).
@@ -393,6 +421,7 @@ def run_cli(profile: dict, prompt: str, trace, resume: str = None, cancel=None,
393
421
  p = spawn.popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
394
422
  text=True, encoding='utf-8', errors='replace', cwd=cwd, shell=False,
395
423
  env=env)
424
+ with _CLI_CHILDREN_LOCK: _CLI_CHILDREN.add(p)
396
425
  except PermissionError as e:
397
426
  # which() found something that cannot be executed from here. "Not installed" sent people
398
427
  # off to reinstall a CLI that was already there; the reason is in denied_msg.
@@ -466,6 +495,7 @@ def run_cli(profile: dict, prompt: str, trace, resume: str = None, cancel=None,
466
495
  p.wait()
467
496
  finally:
468
497
  killer.cancel()
498
+ with _CLI_CHILDREN_LOCK: _CLI_CHILDREN.discard(p)
469
499
  err_t.join(5) # the exit code can land before the stderr reader has appended - 'boom' read as 'no output' on a fast CI box
470
500
  if p.returncode != 0:
471
501
  if cancel is not None and cancel.is_set(): raise RuntimeError('cancelled')
@@ -327,7 +327,8 @@ CONTRACT = ('\n\nYou are writing your POST on the owner\'s Timeline - the short
327
327
  'already and yours is ignored for those; this is for idea:* lines>", '
328
328
  '"why": "<one line: what this rests on - the mail, the date, the silence, the pattern - named as it appears in what you '
329
329
  'were given (sender, subject, mid, TQ-ref), so the owner can check it>", "mid": <the message id it is '
330
- 'about, or null>, "task": "<idea:* only - a task title the owner could accept as-is, or null>"}], '
330
+ 'about, or null>, "task": "<idea:* only - a task title the owner could accept as-is, or null>", '
331
+ '"kind": "<when task is not null: coding if it needs a repository/system changed, otherwise general>"}], '
331
332
  '"notes": "<your note to the next check, under 120 words: FACTS AND TIMINGS ONLY - what you looked at and found nothing in, '
332
333
  'the date or silence length at which something becomes worth raising, a fact you settled so it need not be worked out again. '
333
334
  'Never a standing rule about what to ignore or what is noise: the instruction decides that, and a note that says '
@@ -558,6 +559,8 @@ def parse(store, text: str, cands: list, max_lines: int = MAX_LINES) -> list:
558
559
  mid = s.get('mid') if isinstance(s.get('mid'), int) else None
559
560
  title = _short(s.get('task'), 120) or None
560
561
  act = {'type': 'task', 'mid': mid, 'title': title} if title and mid else {'type': 'message', 'mid': mid} if mid else {'type': 'note'}
562
+ if act['type'] == 'task' and str(s.get('kind') or '').lower() in ('coding', 'general'):
563
+ act['kind'] = str(s['kind']).lower()
561
564
  # a line that NAMES a task belongs to it, whatever else it carries: without the tid the
562
565
  # pipe could not tell that an agent had the work, so "TQ-0329 July financials hasn't moved"
563
566
  # sat in 'slipped' while the task said in_progress (the owner, 2026-09-03)
@@ -1101,9 +1104,40 @@ def act(store, idea_id: int, verb: str, actor: str = 'owner', llm=None, days: in
1101
1104
  elif verb == 'task':
1102
1105
  if not a.get('mid'): raise ValueError('this idea is not about a message, so there is nothing to make a task from')
1103
1106
  from . import ingest
1104
- tid = ingest.task_from_message(store, a['mid'], actor, 'coding')
1107
+ message = store.get_message(a['mid']) or {}
1108
+ prior = store.get_task(message.get('TaskId')) if message.get('TaskId') else None
1109
+ # The task kind is the router. The old path always launched `_auto_code`, even when the
1110
+ # message already belonged to a GENERAL task; that is how TQ-0367 was silently reopened
1111
+ # in a repository after it had been completed. An explicit kind from this assistant's
1112
+ # one triage wins, then the existing task kind, with coding retained only as the legacy
1113
+ # default for older idea rows that carry neither.
1114
+ kind = str(a.get('kind') or (prior or {}).get('Kind') or 'coding').lower()
1115
+ if kind not in ('coding', 'general'): kind = 'coding'
1116
+ if prior and prior.get('Status') in ('done', 'dropped'):
1117
+ # This is NEW work noticed after an earlier task was completed. A message can point
1118
+ # at only one task, so do not steal it from history or rename/reopen the completed
1119
+ # task. Carry the evidence into a fresh assistant-owned task instead.
1120
+ title = str(a.get('title') or i.get('Text') or 'Assistant follow-up').strip()[:200]
1121
+ source_text = str(message.get('BodyText') or '').strip()
1122
+ summary = str(i.get('Text') or '').strip()
1123
+ if source_text and source_text not in summary: summary += f'\n\nSource message: {source_text}'
1124
+ tid = store.create_task({'Title': title, 'Summary': summary[:2000], 'Kind': kind,
1125
+ 'Source': 'assistant', 'SourceRef': f'assistant:idea:{idea_id}'}, actor)
1126
+ store.add_comment(tid, 'assistant', 'assistant_agent',
1127
+ f'Created from assistant idea {idea_id}; related completed task {task_ref(prior["TaskId"])}.')
1128
+ store.audit('task', tid, 'create_from_assistant_idea', actor,
1129
+ detail={'idea_id': idea_id, 'message_id': a['mid'], 'related_task_id': prior['TaskId']})
1130
+ else:
1131
+ tid = ingest.task_from_message(store, a['mid'], actor, kind)
1105
1132
  if a.get('title'): store.update_task(tid, {'Title': str(a['title'])[:200]}, actor)
1106
- if store.get_settings().get('coder_auto_enabled') == '1': ingest._spawn(ingest._auto_code, store, tid)
1133
+ task = store.get_task(tid) or {}
1134
+ brief = str(a.get('title') or i.get('Text') or task.get('Title') or '').strip()
1135
+ why = str(a.get('why') or '').strip()
1136
+ if why: brief += f'\n\nWhy the assistant raised it: {why}'
1137
+ if kind == 'general':
1138
+ ingest._spawn(_auto_general, store, tid, brief)
1139
+ elif store.get_settings().get('coder_auto_enabled') == '1':
1140
+ ingest._spawn(ingest._auto_code, store, tid)
1107
1141
  out |= {'taskId': tid, 'ref': task_ref(tid)}
1108
1142
  elif verb == 'snooze':
1109
1143
  until = (datetime.now() + timedelta(days=max(1, int(days or 1)))).strftime('%Y-%m-%d %H:%M:%S')
@@ -1119,3 +1153,15 @@ def act(store, idea_id: int, verb: str, actor: str = 'owner', llm=None, days: in
1119
1153
  else: learn.learn_from(store, ev)
1120
1154
  store.audit('idea', idea_id, verb, actor, detail={'kind': i.get('Kind')})
1121
1155
  return out
1156
+
1157
+
1158
+ def _auto_general(store, tid: int, brief: str) -> None:
1159
+ """The regular-agent counterpart to ingest._auto_code for an accepted assistant idea."""
1160
+ from . import general
1161
+ try:
1162
+ session = general.start_session(store, tid, actor='router')
1163
+ store.add_comment(tid, 'router', 'agent', 'sent to the regular agent from the assistant')
1164
+ session.send_prompt(brief)
1165
+ except Exception as e:
1166
+ logger.warning(f'regular-agent dispatch failed for task {tid}: {e}')
1167
+ store.add_comment(tid, 'router', 'agent', f'Regular-agent start failed: {str(e)[:200]}')
@@ -99,6 +99,22 @@ def wall(store, cwd: str = '', limit: int = 12) -> list:
99
99
  return store.notes(norm(cwd) if cwd else None, limit)
100
100
 
101
101
 
102
+ def live_wall(store, cwd: str = '', limit: int = 60) -> list:
103
+ """Task notes for sessions alive now, plus the shared house lane.
104
+
105
+ Board notes are durable, but the UI calls this surface Live handoff. Filtering only at the
106
+ daily roll-up left notes from closed sessions looking current for hours (or indefinitely).
107
+ """
108
+ from . import terminal as term
109
+ try:
110
+ live_tids = {int(s['taskId']) for s in term.live_sessions(tail=0, details=False)
111
+ if s.get('taskId')}
112
+ except Exception:
113
+ live_tids = set()
114
+ rows = store.notes(norm(cwd) if cwd else None, max(int(limit) * 5, 300))
115
+ return [n for n in rows if not n.get('TaskId') or int(n['TaskId']) in live_tids][:int(limit)]
116
+
117
+
102
118
  def house_wall(store, limit: int = 8) -> list:
103
119
  """The lane with no checkout in it: what the assistant chat and the owner leave for
104
120
  everyone. A chat has no repository, so this is the whole of its wall."""
@@ -466,6 +466,57 @@ def _same_words(a: str, b: str, n: int = 160) -> bool:
466
466
  return bool(x) and x == y
467
467
 
468
468
 
469
+ def retire_draft_answered_elsewhere(store, tid: int | None, sent: dict) -> list:
470
+ """Resolve reply drafts made obsolete by a reply the owner sent in the native app.
471
+
472
+ A provider sync can land well after triage wrote its draft. The draft belongs to the
473
+ inbound message it was written against, so an owner line later on that same conversation
474
+ is the missing verdict: the reply already went out. Action approvals are deliberately
475
+ excluded -- sending a chat line cannot approve an unrelated proposed action.
476
+ """
477
+ conv, sent_at = sent.get('ConversationId'), sent.get('SentAt')
478
+ if not conv or not sent_at:
479
+ return []
480
+ stale = [rv for rv in store.list_reviews('pending')
481
+ if rv.get('TaskId') == tid and rv.get('Kind') != 'action'
482
+ and rv.get('ConversationId') == conv
483
+ and (not rv.get('SentAt') or rv['SentAt'] <= sent_at)]
484
+ if not stale:
485
+ return []
486
+
487
+ place = SENT_FROM.get(sent.get('Channel'), 'outside Taskuary')
488
+ for rv in stale:
489
+ store.decide_review(rv['ReviewId'], 'superseded', None, 'you',
490
+ f'The owner replied {place}; this draft was not sent.')
491
+
492
+ # Treat this exactly like a successful send through Review. Reply-only work closes, while
493
+ # owner-controlled work and a task whose coding agent is still running keep their safeguards.
494
+ if tid:
495
+ from . import verdicts
496
+ verdicts._settle_task_after_sent_reply(store, stale[0], 'you', True)
497
+
498
+ task = store.get_task(tid) if tid else {}
499
+ body = re.sub(r'\s+', ' ', sent.get('BodyText') or '').strip()
500
+ excerpt = f': "{body[:180]}"' if body else ''
501
+ if not tid:
502
+ subject = (store.get_message(stale[0].get('MessageId')) or {}).get('Subject') or 'this thread'
503
+ where = subject
504
+ state = ''
505
+ else:
506
+ where = f'TQ-{tid:04d}'
507
+ state = ' The task is still open because it has other work in progress.' \
508
+ if task.get('Status') not in ('done', 'dropped') else ' I marked the task done.'
509
+ from . import concierge, funnel, general
510
+ concierge.record(store, general.dock_task(store)[0]['TaskId'], 'assistant',
511
+ f'You replied {place} on {where}{excerpt}. '
512
+ f'I removed the unused draft; the reply was taken care of.{state}')
513
+ funnel.invalidate()
514
+ # The review transition already wakes the UI, but this final wake happens after the durable
515
+ # Assistant line was written, avoiding a race where an open chat fetched one write too early.
516
+ store._poke('feed-changed', 'task-changed', task_id=tid)
517
+ return stale
518
+
519
+
469
520
  def ingest_own_message(store, msg: dict, why: str, keep_unmatched: bool = True) -> int:
470
521
  """Anything YOU sent - a mail reply, a line in a chat - never gets its own timeline row and
471
522
  never becomes work: it rides INSIDE the thread as a 'context' message, which is what the
@@ -486,6 +537,8 @@ def ingest_own_message(store, msg: dict, why: str, keep_unmatched: bool = True)
486
537
  'SentAt': msg.get('sent_at'), 'BodyText': msg.get('body'),
487
538
  'SourceLink': msg.get('source_link'), 'Status': 'context',
488
539
  'MailMetaJson': json.dumps(msg.get('mail_meta')) if msg.get('mail_meta') else None})
540
+ sent = store.get_message(mid) or {}
541
+ retire_draft_answered_elsewhere(store, tid, sent)
489
542
  if not tid: return 1
490
543
  store.add_route(mid, tid, 'attach', None, why, [], 'router')
491
544
  # ...unless Taskuary is reading back its OWN send. That reply already has its line on the
@@ -745,7 +798,7 @@ def ingest_github_issues(store, src: dict, tok: str, since, llm=None, file_only=
745
798
  issues_mode, prs_mode = gh_modes(src, file_only)
746
799
  if issues_mode == 'off' and prs_mode == 'off': return 0
747
800
  n = 0
748
- from .github import list_items
801
+ from .github import body_images, list_items
749
802
  for i in reversed(list_items(tok, repo, since=since.astimezone().isoformat())):
750
803
  if TQ_ISSUE.match(i.get('title') or ''): continue
751
804
  is_pr = 'pull_request' in i
@@ -761,6 +814,10 @@ def ingest_github_issues(store, src: dict, tok: str, since, llm=None, file_only=
761
814
  'from_name': who, 'from_email': f'{who}@users.noreply.github.com',
762
815
  'conversation_id': f"gh:{repo}#{i['number']}", 'sent_at': _local(i.get('updated_at') or ''),
763
816
  'source_link': i.get('html_url'), 'source_name': repo,
817
+ # the screenshot IS the report: read it before the row exists, or the classifier
818
+ # judges an issue template whose headings are empty (see images_for_triage above)
819
+ 'images': (body_images(tok, i.get('body') or '')
820
+ if str(store.get_settings().get('vision_enabled') or '1') == '1' else []),
764
821
  'no_auto': not gh_auto_ok(src, i.get('author_association'))},
765
822
  llm=llm, file_only=mode == 'feed')
766
823
  n += out['status'] != 'duplicate'
@@ -897,7 +954,7 @@ def _poll_jobs(store, only=None):
897
954
  # tool-only card, a Sync that pulled nothing, and no error anywhere).
898
955
  phone_guide = (c['Type'] == 'whatsapp'
899
956
  and store.get_settings().get('phone_assistant') == '1'
900
- and 'notify' in roles)
957
+ and bool(_cfg(c).get('assistant_chat') or _cfg(c).get('notify_chat')))
901
958
  if (not roles & {'trigger', 'feed'} and not phone_guide
902
959
  and not (c['Type'] == 'github' and _gh_explicit(store))
903
960
  and not (c['Type'] in CLOUD and _cloud_explicit(store, CH2SRC[c['Type']]))): continue
@@ -224,8 +224,8 @@ def wrap(store, tid: int, close: bool = True, actor: str = 'owner', sid: str = N
224
224
  report = resolution_text(rep)
225
225
  store.add_comment(tid, actor, 'human', 'Closed the session - wrapped up from what was on screen.')
226
226
  store.add_comment(tid, agent, 'agent', f'CODER REPORT\n{report}')
227
- # The compact result stays deliberately scannable. The transcript does not disappear behind
228
- # that compression: preserve it as a Markdown artifact and link it from the same Agent work card.
227
+ # The compact result and final answer become the readable Markdown artifact. terminal.py has
228
+ # already filed the raw PTY stream for recovery; duplicating it here made the reader unusable.
229
229
  artifact = None
230
230
  try:
231
231
  from . import session_artifacts
@@ -283,7 +283,15 @@ def raise_reply(store, task_id: int, mid: int, run_id: int, rep: dict,
283
283
  'Status': 'pending', 'Reason': 'coder finished the work - reply awaiting approval'})
284
284
  # The report is intentionally compact UI copy. Draft from the complete final response (or
285
285
  # transcript fallback), so every question the session answered remains available here.
286
- try: responder.write_draft(store, task_id, rid, complete_result or resolution_text(rep), 'coder')
286
+ try:
287
+ try: deliver = json.loads((held or {}).get('Deliver') or '{}') or {}
288
+ except (TypeError, ValueError): deliver = {}
289
+ if deliver.get('channel') and deliver.get('kind') != 'zoho_invoice':
290
+ from . import outbox
291
+ outbox.redraft_review(store, store.get_review(rid),
292
+ complete_result or resolution_text(rep))
293
+ else:
294
+ responder.write_draft(store, task_id, rid, complete_result or resolution_text(rep), 'coder')
287
295
  except Exception as e: logger.warning(f'reply draft failed for task {task_id}: {e}')
288
296
  # the ping that matters most: work FINISHED and its reply is sitting in Review on you
289
297
  if (store.get_settings().get('notify_level') or 'needs_me') != 'off':