taskuary 0.3.3.2__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 (296) hide show
  1. {taskuary-0.3.3.2/taskuary.egg-info → taskuary-0.3.3.4}/PKG-INFO +29 -18
  2. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/README.md +28 -17
  3. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/pyproject.toml +1 -1
  4. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/agents.py +30 -0
  5. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/assistant.py +51 -5
  6. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/blackboard.py +16 -0
  7. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/channels.py +59 -2
  8. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/clis.py +11 -1
  9. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/coder.py +11 -3
  10. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/concierge.py +252 -76
  11. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/funnel.py +213 -69
  12. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/general.py +50 -9
  13. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/github.py +43 -0
  14. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/histgen.py +15 -3
  15. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/interview.py +37 -4
  16. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/messengers.py +43 -5
  17. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/outbound.py +40 -11
  18. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/outbox.py +49 -13
  19. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/remote_assistant.py +22 -6
  20. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/reports.py +64 -10
  21. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/responder.py +8 -2
  22. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/server.py +424 -62
  23. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/session_artifacts.py +3 -3
  24. taskuary-0.3.3.4/taskuary/skills/soul-interview/SKILL.md +92 -0
  25. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/store.py +169 -13
  26. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/terminal.py +178 -19
  27. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/verdicts.py +5 -4
  28. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/wabridge.py +24 -5
  29. taskuary-0.3.3.2/taskuary/web/assets/GeneralWorkspace-C75zMBzq.js → taskuary-0.3.3.4/taskuary/web/assets/GeneralWorkspace-CugBH3Pj.js +2 -2
  30. taskuary-0.3.3.2/taskuary/web/assets/HubView-BtNf0bG5.js → taskuary-0.3.3.4/taskuary/web/assets/HubView-CKPusAgu.js +1 -1
  31. taskuary-0.3.3.4/taskuary/web/assets/index-Dw2KclKc.css +32 -0
  32. taskuary-0.3.3.4/taskuary/web/assets/index-HgTGxZl1.js +1140 -0
  33. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/web/index.html +2 -2
  34. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/whatsapp/bridge.mjs +118 -13
  35. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/whatsapp/package.json +1 -1
  36. taskuary-0.3.3.4/taskuary/whatsapp/policy.mjs +51 -0
  37. taskuary-0.3.3.4/taskuary/whatsapp/policy.test.mjs +37 -0
  38. taskuary-0.3.3.4/taskuary/whatsapp/roster.mjs +83 -0
  39. taskuary-0.3.3.4/taskuary/whatsapp/roster.test.mjs +43 -0
  40. {taskuary-0.3.3.2 → taskuary-0.3.3.4/taskuary.egg-info}/PKG-INFO +29 -18
  41. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary.egg-info/SOURCES.txt +15 -4
  42. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_agent_environment.py +28 -0
  43. taskuary-0.3.3.4/tests/test_agent_identity.py +70 -0
  44. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_agent_source.py +10 -0
  45. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_agent_wall.py +14 -0
  46. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_answered_yourself.py +47 -4
  47. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_api.py +7 -2
  48. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_assistant.py +31 -0
  49. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_assistant_reactions.py +43 -11
  50. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_chat_doors.py +69 -0
  51. taskuary-0.3.3.4/tests/test_chat_freshness.py +85 -0
  52. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_concierge.py +96 -15
  53. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_core.py +15 -0
  54. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_floating_assistant.py +19 -0
  55. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_follow_up_verdict.py +11 -3
  56. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_funnel.py +241 -11
  57. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_general.py +37 -0
  58. taskuary-0.3.3.4/tests/test_github_images.py +117 -0
  59. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_histgen.py +7 -0
  60. taskuary-0.3.3.4/tests/test_ignore_sender.py +90 -0
  61. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_lifecycle.py +20 -0
  62. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_live.py +29 -10
  63. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_messengers.py +43 -0
  64. taskuary-0.3.3.4/tests/test_outbox.py +77 -0
  65. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_promptmap_and_catalog.py +12 -6
  66. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_remote_assistant.py +5 -3
  67. taskuary-0.3.3.4/tests/test_report_order_and_research.py +110 -0
  68. taskuary-0.3.3.4/tests/test_report_refusal.py +137 -0
  69. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_send_targets.py +57 -5
  70. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_session_artifacts.py +6 -4
  71. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_soul_interview.py +51 -0
  72. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_terminal.py +72 -11
  73. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_wabridge.py +17 -4
  74. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_waitroom.py +26 -0
  75. taskuary-0.3.3.2/taskuary/skills/soul-interview/SKILL.md +0 -57
  76. taskuary-0.3.3.2/taskuary/web/assets/index-CE8to-uH.js +0 -1140
  77. taskuary-0.3.3.2/taskuary/web/assets/index-C_xWyjgP.css +0 -32
  78. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/LICENSE +0 -0
  79. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/setup.cfg +0 -0
  80. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/__init__.py +0 -0
  81. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/aisetup.py +0 -0
  82. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/artifacts.py +0 -0
  83. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/aws.py +0 -0
  84. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/azure.py +0 -0
  85. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/browserview.py +0 -0
  86. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/calendar.py +0 -0
  87. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/categories.py +0 -0
  88. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/ci.py +0 -0
  89. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/cli.py +0 -0
  90. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/climodels.py +0 -0
  91. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/compose.py +0 -0
  92. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/config.py +0 -0
  93. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/context.py +0 -0
  94. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/counsel.py +0 -0
  95. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/db.py +0 -0
  96. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/demo.py +0 -0
  97. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/deps.py +0 -0
  98. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/desktop.py +0 -0
  99. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/devtools.py +0 -0
  100. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/digest.py +0 -0
  101. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/docsync.py +0 -0
  102. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/evalset.py +0 -0
  103. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/evening.py +0 -0
  104. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/guard.py +0 -0
  105. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/handbook.py +0 -0
  106. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/handraise.py +0 -0
  107. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/hooks.py +0 -0
  108. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/hub.py +0 -0
  109. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/imapmail.py +0 -0
  110. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/imessage.py +0 -0
  111. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/ingest.py +0 -0
  112. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/intacct.py +0 -0
  113. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/invoice_workflow.py +0 -0
  114. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/knowledge.py +0 -0
  115. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/learn.py +0 -0
  116. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/learnedgraph.py +0 -0
  117. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/live.py +0 -0
  118. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/llm.py +0 -0
  119. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/logs.py +0 -0
  120. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/mcp.py +0 -0
  121. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/msauth.py +0 -0
  122. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/mssql.py +0 -0
  123. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/ownwork.py +0 -0
  124. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/phone.py +0 -0
  125. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/playbooks.py +0 -0
  126. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/pm.py +0 -0
  127. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/policy.py +0 -0
  128. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/problems.py +0 -0
  129. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/projects.py +0 -0
  130. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/promptmap.py +0 -0
  131. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/proof.py +0 -0
  132. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/proposals.py +0 -0
  133. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/quickbooks.py +0 -0
  134. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/rank.py +0 -0
  135. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/research.py +0 -0
  136. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/reshape.py +0 -0
  137. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/routing.py +0 -0
  138. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/scopes.py +0 -0
  139. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/selfclose.py +0 -0
  140. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/semantic.py +0 -0
  141. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/senders.py +0 -0
  142. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/setup.py +0 -0
  143. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/sharepoint.py +0 -0
  144. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/sheets.py +0 -0
  145. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/spawn.py +0 -0
  146. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/teller.py +0 -0
  147. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/coder.md +0 -0
  148. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/counsel.md +0 -0
  149. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/digest.md +0 -0
  150. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/how-it-works.md +0 -0
  151. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/learned.md +0 -0
  152. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/playbook.md +0 -0
  153. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/soul.md +0 -0
  154. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/style.md +0 -0
  155. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/triage.md +0 -0
  156. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/testing.py +0 -0
  157. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/toil.py +0 -0
  158. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/triage.py +0 -0
  159. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/update.py +0 -0
  160. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/voice.py +0 -0
  161. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/waitroom.py +0 -0
  162. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/web/assets/GeneralWorkspace--IqTbJGE.css +0 -0
  163. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/web/favicon.ico +0 -0
  164. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/web/favicon.png +0 -0
  165. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/webguard.py +0 -0
  166. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/whoami.py +0 -0
  167. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/witness.py +0 -0
  168. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/zoho.py +0 -0
  169. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary.egg-info/dependency_links.txt +0 -0
  170. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary.egg-info/entry_points.txt +0 -0
  171. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary.egg-info/requires.txt +0 -0
  172. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary.egg-info/top_level.txt +0 -0
  173. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_aisetup.py +0 -0
  174. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_answers.py +0 -0
  175. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_assistant_opens.py +0 -0
  176. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_assistant_verdicts.py +0 -0
  177. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_async_triage.py +0 -0
  178. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_audit_chain.py +0 -0
  179. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_audit_fixes.py +0 -0
  180. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_aws_region.py +0 -0
  181. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_blackboard.py +0 -0
  182. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_browser_session.py +0 -0
  183. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_browserview.py +0 -0
  184. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_calendar.py +0 -0
  185. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_calendar_today.py +0 -0
  186. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_categories.py +0 -0
  187. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_cc.py +0 -0
  188. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_cc_triage.py +0 -0
  189. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_chat_is_not_one_task.py +0 -0
  190. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_chat_queue.py +0 -0
  191. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_climodels.py +0 -0
  192. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_clis.py +0 -0
  193. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_cloud.py +0 -0
  194. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_cloud_sources.py +0 -0
  195. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_compose.py +0 -0
  196. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_compose_sources.py +0 -0
  197. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_compose_workflows.py +0 -0
  198. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_config.py +0 -0
  199. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_context.py +0 -0
  200. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_counsel.py +0 -0
  201. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_cwlogs_paging.py +0 -0
  202. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_demo.py +0 -0
  203. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_deps.py +0 -0
  204. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_desktop.py +0 -0
  205. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_devtools.py +0 -0
  206. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_diff_review.py +0 -0
  207. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_digest_brief.py +0 -0
  208. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_docs_and_llm.py +0 -0
  209. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_docs_flow.py +0 -0
  210. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_duplicate_connectors.py +0 -0
  211. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_evening_brief.py +0 -0
  212. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_factory.py +0 -0
  213. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_feed_working.py +0 -0
  214. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_finished_chat_stays_finished.py +0 -0
  215. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_github_auto.py +0 -0
  216. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_guard.py +0 -0
  217. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_handbook.py +0 -0
  218. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_handbook_topics.py +0 -0
  219. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_handoff_channels.py +0 -0
  220. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_handraise.py +0 -0
  221. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_harvest.py +0 -0
  222. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_home_is_never_yours.py +0 -0
  223. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_imap_history.py +0 -0
  224. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_imapmail.py +0 -0
  225. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_imessage.py +0 -0
  226. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_intacct.py +0 -0
  227. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_invoice_workflow.py +0 -0
  228. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_kind_dispatch.py +0 -0
  229. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_knowledge.py +0 -0
  230. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_learnedgraph.py +0 -0
  231. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_local_file.py +0 -0
  232. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_mail_folders.py +0 -0
  233. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_mark_read.py +0 -0
  234. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_mcp.py +0 -0
  235. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_meeting_prep.py +0 -0
  236. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_message_thread.py +0 -0
  237. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_msauth.py +0 -0
  238. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_mssql.py +0 -0
  239. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_noop_ending.py +0 -0
  240. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_not_coding.py +0 -0
  241. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_note_retrieval.py +0 -0
  242. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_outbound_reports.py +0 -0
  243. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_own_rows_no_reply.py +0 -0
  244. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_playbooks.py +0 -0
  245. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_pm.py +0 -0
  246. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_poll_overlap.py +0 -0
  247. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_poll_parallel.py +0 -0
  248. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_poll_sources.py +0 -0
  249. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_pr_comments.py +0 -0
  250. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_problems.py +0 -0
  251. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_problems_dismiss.py +0 -0
  252. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_projects.py +0 -0
  253. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_proof_ci_proposals.py +0 -0
  254. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_pypi_downloads.py +0 -0
  255. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_quickbooks.py +0 -0
  256. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_rank.py +0 -0
  257. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_rate_limit.py +0 -0
  258. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_reclassify.py +0 -0
  259. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_reply_channels.py +0 -0
  260. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_reply_voice.py +0 -0
  261. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_report_alerts.py +0 -0
  262. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_report_triage.py +0 -0
  263. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_research.py +0 -0
  264. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_reviews.py +0 -0
  265. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_scopes.py +0 -0
  266. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_seed_fits_a_tty.py +0 -0
  267. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_seeded_reports.py +0 -0
  268. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_semantic.py +0 -0
  269. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_setup.py +0 -0
  270. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_sheets_sharepoint.py +0 -0
  271. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_someone_else_replied.py +0 -0
  272. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_stay_open.py +0 -0
  273. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_stay_open_doors.py +0 -0
  274. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_store_app_cli.py +0 -0
  275. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_store_hotpaths.py +0 -0
  276. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_sync_clock.py +0 -0
  277. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_task_identity.py +0 -0
  278. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_teller.py +0 -0
  279. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_toil.py +0 -0
  280. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_triage_dataset.py +0 -0
  281. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_triage_images.py +0 -0
  282. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_update.py +0 -0
  283. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_update_refused.py +0 -0
  284. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_urgent_and_handoff.py +0 -0
  285. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_verdict_paths.py +0 -0
  286. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_verdict_sticks.py +0 -0
  287. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_vision.py +0 -0
  288. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_voice.py +0 -0
  289. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_watermark.py +0 -0
  290. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_web_entrypoint.py +0 -0
  291. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_webguard.py +0 -0
  292. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_whatsapp_documents.py +0 -0
  293. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_whatsapp_images.py +0 -0
  294. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_whoami.py +0 -0
  295. {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_withdrawn.py +0 -0
  296. {taskuary-0.3.3.2 → 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.2
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.2)](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
 
@@ -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.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
@@ -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.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
 
@@ -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.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
@@ -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.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 '
@@ -537,7 +538,7 @@ def raised(store, days: float = 2) -> str:
537
538
  return '\n'.join(out) or '(the assistant raised nothing in this window)'
538
539
 
539
540
 
540
- def parse(text: str, cands: list, max_lines: int = MAX_LINES) -> list:
541
+ def parse(store, text: str, cands: list, max_lines: int = MAX_LINES) -> list:
541
542
  """The model's list, kept honest: a key it invents must be idea:*, a candidate key keeps its
542
543
  kind and its buttons, and the text is the model's when it gave one. Every line keeps its WHY -
543
544
  the hub's facts for a candidate (plus the model's read on them), the model's own for an idea -
@@ -558,6 +559,8 @@ def parse(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)
@@ -767,7 +770,7 @@ def think(store, cands: list, llm, instruction: str = None, max_lines: int = MAX
767
770
  from .llm import readable_images
768
771
  images = readable_images(store, _people_context(store)[1])
769
772
  text = llm(system, user, max_tokens=POST_TOKENS, **({'images': images} if images else {}))
770
- return parse(text, cands, max_lines), _notes(text), user
773
+ return parse(store, text, cands, max_lines), _notes(text), user
771
774
 
772
775
 
773
776
  def facts(store, watch_source_ids=None, watch_sources=None, systems_only: bool = False) -> str:
@@ -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
@@ -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
  }
@@ -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':