taskuary 0.3.7.2__tar.gz → 0.3.7.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (572) hide show
  1. {taskuary-0.3.7.2/taskuary.egg-info → taskuary-0.3.7.3}/PKG-INFO +3 -3
  2. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/README.md +2 -2
  3. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/pyproject.toml +1 -1
  4. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/aidefaults.py +1 -1
  5. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/assistant.py +4 -6
  6. taskuary-0.3.7.3/taskuary/background.py +125 -0
  7. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/channels.py +3 -0
  8. taskuary-0.3.7.3/taskuary/chatgptauth.py +268 -0
  9. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/concierge.py +55 -8
  10. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/connectorcatalog.json +14 -0
  11. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/funnel.py +1 -1
  12. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/general.py +8 -2
  13. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/hooks.py +70 -8
  14. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/ingest.py +24 -35
  15. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/llm.py +94 -6
  16. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/problems.py +17 -4
  17. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_reads.py +9 -2
  18. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/remote_assistant.py +8 -3
  19. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/server.py +57 -2
  20. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/settings_defaults.json +0 -2
  21. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/settings_schema.json +0 -14
  22. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/store.py +9 -6
  23. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/terminal.py +44 -12
  24. taskuary-0.3.7.2/taskuary/web/assets/AssistantGame-BZJj3XwN.js → taskuary-0.3.7.3/taskuary/web/assets/AssistantGame-CCx1jM1n.js +3 -3
  25. taskuary-0.3.7.2/taskuary/web/assets/GameScene-ATV2SyYO.js → taskuary-0.3.7.3/taskuary/web/assets/GameScene-DEtfroOB.js +1 -1
  26. taskuary-0.3.7.2/taskuary/web/assets/HubView-DtpvsbeL.js → taskuary-0.3.7.3/taskuary/web/assets/HubView-DE_KGqZM.js +1 -1
  27. taskuary-0.3.7.2/taskuary/web/assets/StudioScene-D4IlNRXi.js → taskuary-0.3.7.3/taskuary/web/assets/StudioScene-JyIZfG3s.js +1 -1
  28. taskuary-0.3.7.2/taskuary/web/assets/TasksView-B7m8zBYu.js → taskuary-0.3.7.3/taskuary/web/assets/TasksView-CYGOZob-.js +1 -1
  29. taskuary-0.3.7.3/taskuary/web/assets/index-BUNEVOG_.css +32 -0
  30. taskuary-0.3.7.2/taskuary/web/assets/index-BKrB4ObA.js → taskuary-0.3.7.3/taskuary/web/assets/index-i4LFqglY.js +152 -152
  31. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/index.html +2 -2
  32. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/workerstate.py +18 -4
  33. {taskuary-0.3.7.2 → taskuary-0.3.7.3/taskuary.egg-info}/PKG-INFO +3 -3
  34. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary.egg-info/SOURCES.txt +12 -7
  35. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_advisor_idea_words.py +2 -2
  36. taskuary-0.3.7.3/tests/test_api_web.py +131 -0
  37. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_ideas.py +5 -3
  38. taskuary-0.3.7.3/tests/test_background_work.py +193 -0
  39. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_is_not_one_task.py +12 -42
  40. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_retention.py +1 -1
  41. taskuary-0.3.7.3/tests/test_chatgptauth.py +155 -0
  42. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_concierge.py +38 -4
  43. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_context.py +2 -1
  44. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_continue_session.py +32 -0
  45. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_core.py +2 -2
  46. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_funnel.py +20 -1
  47. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_hooks_events.py +19 -0
  48. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_one_pty_one_geometry.py +159 -108
  49. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_phone_browse_pass.py +69 -0
  50. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_problems.py +19 -0
  51. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_problems_dismiss.py +9 -4
  52. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_remote_sections.py +5 -3
  53. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_terminal.py +37 -0
  54. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_joins.py +15 -0
  55. taskuary-0.3.7.2/taskuary/web/assets/index-DUxLnhhx.css +0 -32
  56. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/LICENSE +0 -0
  57. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/setup.cfg +0 -0
  58. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/__init__.py +0 -0
  59. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/acp.py +0 -0
  60. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/agents.py +0 -0
  61. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/aisetup.py +0 -0
  62. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/alchemy.py +0 -0
  63. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/appfacts.py +0 -0
  64. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/artifacts.py +0 -0
  65. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/assistantblocks.py +0 -0
  66. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/autoreply.py +0 -0
  67. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/aws.py +0 -0
  68. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/azure.py +0 -0
  69. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/blackboard.py +0 -0
  70. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/brief.py +0 -0
  71. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/browserview.py +0 -0
  72. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/calendar.py +0 -0
  73. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/categories.py +0 -0
  74. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/chains.py +0 -0
  75. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/chatformat.py +0 -0
  76. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/chatservers.py +0 -0
  77. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/ci.py +0 -0
  78. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/cli.py +0 -0
  79. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/cli_connections.py +0 -0
  80. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/cli_install_terminal.py +0 -0
  81. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/cliinstall.py +0 -0
  82. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/climodels.py +0 -0
  83. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/clipool.py +0 -0
  84. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/clis.py +0 -0
  85. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/clisetup.py +0 -0
  86. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/coder.py +0 -0
  87. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/compose.py +0 -0
  88. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/config.py +0 -0
  89. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/connectorcatalog.py +0 -0
  90. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/context.py +0 -0
  91. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/continuity.py +0 -0
  92. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/counsel.py +0 -0
  93. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/databases.py +0 -0
  94. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/db.py +0 -0
  95. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/demo.py +0 -0
  96. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/deps.py +0 -0
  97. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/desktop.py +0 -0
  98. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/devtools.py +0 -0
  99. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/digest.py +0 -0
  100. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/docsync.py +0 -0
  101. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/doorway_browse.py +0 -0
  102. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/evalset.py +0 -0
  103. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/evening.py +0 -0
  104. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/files.py +0 -0
  105. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/funnel_presentation.py +0 -0
  106. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/funnel_selection.py +0 -0
  107. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/ghcloseout.py +0 -0
  108. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/github.py +0 -0
  109. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/guard.py +0 -0
  110. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/handbook.py +0 -0
  111. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/handraise.py +0 -0
  112. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/histgen.py +0 -0
  113. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/hub.py +0 -0
  114. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/images.py +0 -0
  115. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/imapmail.py +0 -0
  116. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/imessage.py +0 -0
  117. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/intacct.py +0 -0
  118. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/interview.py +0 -0
  119. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/invoice_workflow.py +0 -0
  120. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/jev.py +0 -0
  121. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/knowledge.py +0 -0
  122. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/lanes.json +0 -0
  123. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/learn.py +0 -0
  124. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/learnedgraph.py +0 -0
  125. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/linkedin.py +0 -0
  126. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/live.py +0 -0
  127. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/logs.py +0 -0
  128. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/lookups.py +0 -0
  129. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/markets.py +0 -0
  130. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/mcp.py +0 -0
  131. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/messengers.py +0 -0
  132. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/msauth.py +0 -0
  133. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/mssql.py +0 -0
  134. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/operations.py +0 -0
  135. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/outbound.py +0 -0
  136. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/outbox.py +0 -0
  137. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/ownwork.py +0 -0
  138. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/playbooks.py +0 -0
  139. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/pm.py +0 -0
  140. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/policy.py +0 -0
  141. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing.py +0 -0
  142. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_all.py +0 -0
  143. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_inventory.py +0 -0
  144. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_membership.py +0 -0
  145. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_navigation.py +0 -0
  146. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_order.py +0 -0
  147. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_projection.py +0 -0
  148. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_rail.py +0 -0
  149. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_startup.py +0 -0
  150. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_unread.py +0 -0
  151. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/projects.py +0 -0
  152. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/promptmap.py +0 -0
  153. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/proof.py +0 -0
  154. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/proposals.py +0 -0
  155. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/quickbooks.py +0 -0
  156. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/rank.py +0 -0
  157. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/redact.py +0 -0
  158. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/remind.py +0 -0
  159. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/reports.py +0 -0
  160. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/research.py +0 -0
  161. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/reshape.py +0 -0
  162. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/responder.py +0 -0
  163. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/retention.py +0 -0
  164. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/robinhood.py +0 -0
  165. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/routing.py +0 -0
  166. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/routingmemory.py +0 -0
  167. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/scopes.py +0 -0
  168. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/selfclose.py +0 -0
  169. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/semantic.py +0 -0
  170. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/senders.py +0 -0
  171. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/session_artifacts.py +0 -0
  172. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/sessionfiles.py +0 -0
  173. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/settings_schema.py +0 -0
  174. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/setup.py +0 -0
  175. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/sharepoint.py +0 -0
  176. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/sheets.py +0 -0
  177. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/simplefin.py +0 -0
  178. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/skillimport.py +0 -0
  179. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/skills/soul-interview/SKILL.md +0 -0
  180. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/skills/taskuary-setup/SKILL.md +0 -0
  181. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/social.py +0 -0
  182. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/spawn.py +0 -0
  183. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/taskstate.py +0 -0
  184. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/teller.py +0 -0
  185. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/agent.md +0 -0
  186. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/analyst.md +0 -0
  187. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/coder.md +0 -0
  188. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/coordinator.md +0 -0
  189. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/counsel.md +0 -0
  190. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/digest.md +0 -0
  191. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/history/counsel-0.3.3.5.md +0 -0
  192. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/history/counsel-0.3.6.8.md +0 -0
  193. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/history/counsel-0.3.6.9-examples.md +0 -0
  194. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/history/counsel-0.3.6.9-remind.md +0 -0
  195. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/history/counsel-0.3.6.9.md +0 -0
  196. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/how-it-works.md +0 -0
  197. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/learned.md +0 -0
  198. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/marketer.md +0 -0
  199. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/playbook.md +0 -0
  200. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/profile.md +0 -0
  201. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/researcher.md +0 -0
  202. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/soul.md +0 -0
  203. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/style.md +0 -0
  204. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/trader.md +0 -0
  205. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/triage.md +0 -0
  206. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/testing.py +0 -0
  207. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/toil.py +0 -0
  208. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/toolcatalog.py +0 -0
  209. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/treg.py +0 -0
  210. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/triage.py +0 -0
  211. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/triage_kind_rules.json +0 -0
  212. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/update.py +0 -0
  213. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/verdicts.py +0 -0
  214. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/voice.py +0 -0
  215. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/wabridge.py +0 -0
  216. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/waitroom.py +0 -0
  217. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/walk.py +0 -0
  218. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/assets/RoundedBoxGeometry-BE4rf3sD.js +0 -0
  219. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/favicon.ico +0 -0
  220. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/favicon.png +0 -0
  221. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/assistant.png +0 -0
  222. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/board.png +0 -0
  223. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/connections.png +0 -0
  224. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/docs.png +0 -0
  225. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/hub.png +0 -0
  226. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/reports.png +0 -0
  227. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/settings.png +0 -0
  228. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/tasks.png +0 -0
  229. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/webguard.py +0 -0
  230. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/whatsapp/bridge.mjs +0 -0
  231. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/whatsapp/package.json +0 -0
  232. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/whatsapp/policy.mjs +0 -0
  233. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/whatsapp/poll.mjs +0 -0
  234. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/whatsapp/roster.mjs +0 -0
  235. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/whoami.py +0 -0
  236. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/witness.py +0 -0
  237. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/workflows.py +0 -0
  238. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/zoho.py +0 -0
  239. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary.egg-info/dependency_links.txt +0 -0
  240. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary.egg-info/entry_points.txt +0 -0
  241. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary.egg-info/requires.txt +0 -0
  242. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary.egg-info/top_level.txt +0 -0
  243. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_acp.py +0 -0
  244. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_answer_route.py +0 -0
  245. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_answers_and_reminders.py +0 -0
  246. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_asking.py +0 -0
  247. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_environment.py +0 -0
  248. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_identity.py +0 -0
  249. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_profiles.py +0 -0
  250. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_reply.py +0 -0
  251. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_sentence.py +0 -0
  252. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_source.py +0 -0
  253. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_wall.py +0 -0
  254. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_ai_defaults.py +0 -0
  255. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_aisetup.py +0 -0
  256. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_alchemy.py +0 -0
  257. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_an_inbox_is_not_a_destination.py +0 -0
  258. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_answered_yourself.py +0 -0
  259. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_answers.py +0 -0
  260. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_api.py +0 -0
  261. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_api_worker_signals.py +0 -0
  262. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_app_uptime.py +0 -0
  263. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_appfacts.py +0 -0
  264. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_approval_interrupt.py +0 -0
  265. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant.py +0 -0
  266. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_acts.py +0 -0
  267. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_blocks.py +0 -0
  268. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_can_look_things_up.py +0 -0
  269. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_doorways.py +0 -0
  270. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_notifications.py +0 -0
  271. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_presentation.py +0 -0
  272. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_reactions.py +0 -0
  273. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_verdicts.py +0 -0
  274. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_async_triage.py +0 -0
  275. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_audit_chain.py +0 -0
  276. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_audit_fixes.py +0 -0
  277. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_auto_start.py +0 -0
  278. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_autoreply.py +0 -0
  279. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_aws_region.py +0 -0
  280. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_azure_discovery.py +0 -0
  281. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_banner_is_one_rule.py +0 -0
  282. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_blackboard.py +0 -0
  283. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_brain_layer.py +0 -0
  284. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_brain_reaches_the_session.py +0 -0
  285. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_browser_session.py +0 -0
  286. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_browser_workflow.py +0 -0
  287. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_browserview.py +0 -0
  288. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_button_promises.py +0 -0
  289. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_calendar.py +0 -0
  290. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_calendar_today.py +0 -0
  291. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_canvas_relay.py +0 -0
  292. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_canvas_sections.py +0 -0
  293. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_card_story_phone.py +0 -0
  294. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_categories.py +0 -0
  295. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cc.py +0 -0
  296. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cc_triage.py +0 -0
  297. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_doors.py +0 -0
  298. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_echo_mail.py +0 -0
  299. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_freshness.py +0 -0
  300. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_proposals.py +0 -0
  301. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_queue.py +0 -0
  302. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_relationship.py +0 -0
  303. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_setup.py +0 -0
  304. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chatformat.py +0 -0
  305. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chatservers.py +0 -0
  306. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chinese_clis.py +0 -0
  307. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_clean_context.py +0 -0
  308. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_connections.py +0 -0
  309. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_failure.py +0 -0
  310. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_help.py +0 -0
  311. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_install.py +0 -0
  312. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_install_terminal.py +0 -0
  313. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_on_fresh_path.py +0 -0
  314. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_setup.py +0 -0
  315. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_update.py +0 -0
  316. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_climodels.py +0 -0
  317. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_clipool.py +0 -0
  318. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_clis.py +0 -0
  319. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_closeout.py +0 -0
  320. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cloud.py +0 -0
  321. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cloud_sources.py +0 -0
  322. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_coder_session_continuity.py +0 -0
  323. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_completion_freshness.py +0 -0
  324. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_compose.py +0 -0
  325. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_compose_sources.py +0 -0
  326. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_compose_workflows.py +0 -0
  327. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_concierge_counsel.py +0 -0
  328. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_config.py +0 -0
  329. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_context_refresh_freshness.py +0 -0
  330. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_coordination.py +0 -0
  331. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_correction_doors.py +0 -0
  332. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_counsel.py +0 -0
  333. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_counsel_consumers.py +0 -0
  334. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_counsel_migration.py +0 -0
  335. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_counsel_sections.py +0 -0
  336. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_current_restore.py +0 -0
  337. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cwlogs_paging.py +0 -0
  338. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_databases.py +0 -0
  339. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_demo.py +0 -0
  340. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_deps.py +0 -0
  341. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_desktop.py +0 -0
  342. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_devin.py +0 -0
  343. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_devtools.py +0 -0
  344. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_diff_review.py +0 -0
  345. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_digest_brief.py +0 -0
  346. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_dispatch_retries.py +0 -0
  347. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_docs_and_llm.py +0 -0
  348. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_docs_flow.py +0 -0
  349. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_duplicate_connectors.py +0 -0
  350. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_email_chains.py +0 -0
  351. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_email_identity_routing.py +0 -0
  352. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_ending_a_run_writes_the_answer.py +0 -0
  353. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_evening_brief.py +0 -0
  354. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_every_card_is_reachable.py +0 -0
  355. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_every_cli_startable.py +0 -0
  356. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_explicit_completion.py +0 -0
  357. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_factory.py +0 -0
  358. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_failed_acts.py +0 -0
  359. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_feed_working.py +0 -0
  360. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_files.py +0 -0
  361. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_final_answer_capture.py +0 -0
  362. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_finished_chat_stays_finished.py +0 -0
  363. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_floating_assistant.py +0 -0
  364. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_follow_up_verdict.py +0 -0
  365. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_fresh_evaluation.py +0 -0
  366. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_freshness.py +0 -0
  367. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_freshness_walk.py +0 -0
  368. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_general.py +0 -0
  369. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_github_auto.py +0 -0
  370. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_github_images.py +0 -0
  371. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_guard.py +0 -0
  372. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_handbook.py +0 -0
  373. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_handbook_topics.py +0 -0
  374. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_handoff_channels.py +0 -0
  375. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_handraise.py +0 -0
  376. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_harvest.py +0 -0
  377. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_histgen.py +0 -0
  378. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_home_is_never_yours.py +0 -0
  379. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_hooks_version.py +0 -0
  380. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_ideas_triage.py +0 -0
  381. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_ignore_sender.py +0 -0
  382. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_ignore_sender_quiets_its_work.py +0 -0
  383. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_images.py +0 -0
  384. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_imap_catchup.py +0 -0
  385. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_imap_history.py +0 -0
  386. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_imapmail.py +0 -0
  387. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_imessage.py +0 -0
  388. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_inline_pictures.py +0 -0
  389. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_intacct.py +0 -0
  390. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_interrupted_work.py +0 -0
  391. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_invite_is_not_work.py +0 -0
  392. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_invoice_workflow.py +0 -0
  393. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_jev.py +0 -0
  394. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_judge_reads_the_conclusion.py +0 -0
  395. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_kind_dispatch.py +0 -0
  396. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_knowledge.py +0 -0
  397. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_lane_vocabulary.py +0 -0
  398. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_learnedgraph.py +0 -0
  399. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_lifecycle.py +0 -0
  400. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_lifespan_loops_are_guarded.py +0 -0
  401. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_linkedin.py +0 -0
  402. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_linkedin_signin.py +0 -0
  403. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_live.py +0 -0
  404. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_local_file.py +0 -0
  405. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_lookups.py +0 -0
  406. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_mail_catchup.py +0 -0
  407. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_mail_fetch_cost.py +0 -0
  408. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_mail_folders.py +0 -0
  409. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_mark_read.py +0 -0
  410. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_markets.py +0 -0
  411. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_mcp.py +0 -0
  412. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_meeting_prep.py +0 -0
  413. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_message_thread.py +0 -0
  414. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_messengers.py +0 -0
  415. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_morning_line.py +0 -0
  416. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_msauth.py +0 -0
  417. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_mssql.py +0 -0
  418. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_multi_question.py +0 -0
  419. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_muse.py +0 -0
  420. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_no_pydantic_v1.py +0 -0
  421. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_noop_ending.py +0 -0
  422. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_not_coding.py +0 -0
  423. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_note_retrieval.py +0 -0
  424. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_onboarding_walk.py +0 -0
  425. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_one_app_one_reason.py +0 -0
  426. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_only_your_own_thread.py +0 -0
  427. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_openai_compatible_brains.py +0 -0
  428. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_operations.py +0 -0
  429. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_outbound_reports.py +0 -0
  430. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_outbox.py +0 -0
  431. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_own_rows_no_reply.py +0 -0
  432. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_person_name.py +0 -0
  433. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_phone_picks.py +0 -0
  434. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_phone_walk_ux.py +0 -0
  435. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_playbook_setup.py +0 -0
  436. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_playbooks.py +0 -0
  437. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_pm.py +0 -0
  438. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_poll_lanes.py +0 -0
  439. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_poll_overlap.py +0 -0
  440. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_poll_parallel.py +0 -0
  441. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_poll_sources.py +0 -0
  442. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_pr_comments.py +0 -0
  443. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_procedure_selection.py +0 -0
  444. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_processing_demo_replay.py +0 -0
  445. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_processing_ledger.py +0 -0
  446. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_profile_is_a_role.py +0 -0
  447. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_projects.py +0 -0
  448. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_prompt_images.py +0 -0
  449. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_prompt_sources.py +0 -0
  450. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_promptmap_and_catalog.py +0 -0
  451. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_proof_ci_proposals.py +0 -0
  452. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_pypi_downloads.py +0 -0
  453. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_quickbooks.py +0 -0
  454. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_qwen.py +0 -0
  455. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_rail_decisions.py +0 -0
  456. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_rank.py +0 -0
  457. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_rate_limit.py +0 -0
  458. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_read_text.py +0 -0
  459. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_recent_closures.py +0 -0
  460. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reclassify.py +0 -0
  461. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_redact.py +0 -0
  462. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_redact_doors.py +0 -0
  463. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_remind.py +0 -0
  464. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_remote_assistant.py +0 -0
  465. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_always_drafts.py +0 -0
  466. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_attachments.py +0 -0
  467. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_channels.py +0 -0
  468. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_envelope.py +0 -0
  469. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_needs_a_correspondent.py +0 -0
  470. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_sources.py +0 -0
  471. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_voice.py +0 -0
  472. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_repo_choice_triage.py +0 -0
  473. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_alerts.py +0 -0
  474. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_deliver.py +0 -0
  475. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_headline.py +0 -0
  476. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_one_road.py +0 -0
  477. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_order_and_research.py +0 -0
  478. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_prompt_isolation.py +0 -0
  479. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_refusal.py +0 -0
  480. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_routing.py +0 -0
  481. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_triage.py +0 -0
  482. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_research.py +0 -0
  483. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_research_more.py +0 -0
  484. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_review_task.py +0 -0
  485. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reviews.py +0 -0
  486. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_robinhood.py +0 -0
  487. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_routing_memory.py +0 -0
  488. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_s3_poll_paging.py +0 -0
  489. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_scopes.py +0 -0
  490. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_seed_fits_a_tty.py +0 -0
  491. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_seeded_reports.py +0 -0
  492. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_selector_vocabulary.py +0 -0
  493. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_semantic.py +0 -0
  494. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_send_outcomes.py +0 -0
  495. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_send_targets.py +0 -0
  496. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_sender_trust.py +0 -0
  497. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_sender_words.py +0 -0
  498. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_session_artifacts.py +0 -0
  499. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_session_continuity.py +0 -0
  500. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_session_endings.py +0 -0
  501. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_session_files.py +0 -0
  502. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_settings_defaults_json.py +0 -0
  503. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_settings_schema.py +0 -0
  504. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_setup.py +0 -0
  505. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_setup_skill.py +0 -0
  506. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_setup_walk.py +0 -0
  507. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_sheets_sharepoint.py +0 -0
  508. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_simplefin.py +0 -0
  509. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_skillimport.py +0 -0
  510. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_social.py +0 -0
  511. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_someone_else_replied.py +0 -0
  512. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_soul_interview.py +0 -0
  513. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_startup_contract.py +0 -0
  514. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_stay_open.py +0 -0
  515. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_stay_open_doors.py +0 -0
  516. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_store_app_cli.py +0 -0
  517. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_store_hotpaths.py +0 -0
  518. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_store_pokes.py +0 -0
  519. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_sync_clock.py +0 -0
  520. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_task_ask.py +0 -0
  521. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_task_checklist.py +0 -0
  522. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_task_controls_operations.py +0 -0
  523. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_task_identity.py +0 -0
  524. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_task_refs.py +0 -0
  525. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_task_tools.py +0 -0
  526. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_taskstate.py +0 -0
  527. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_telegram_buttons.py +0 -0
  528. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_teller.py +0 -0
  529. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_the_exe_ships_what_the_wheel_ships.py +0 -0
  530. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_the_split_holds.py +0 -0
  531. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_toil.py +0 -0
  532. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_tracker_revisions.py +0 -0
  533. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_treg.py +0 -0
  534. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_cleanup.py +0 -0
  535. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_dataset.py +0 -0
  536. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_errors.py +0 -0
  537. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_images.py +0 -0
  538. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_pr_rule.py +0 -0
  539. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_retry_sweep.py +0 -0
  540. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_urgent.py +0 -0
  541. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_update.py +0 -0
  542. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_update_refused.py +0 -0
  543. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_urgent_and_handoff.py +0 -0
  544. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_verdict_paths.py +0 -0
  545. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_verdict_shape.py +0 -0
  546. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_verdict_sticks.py +0 -0
  547. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_vision.py +0 -0
  548. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_voice.py +0 -0
  549. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_wabridge.py +0 -0
  550. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_wabridge_log_trim.py +0 -0
  551. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_waitroom.py +0 -0
  552. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_walk_order.py +0 -0
  553. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_watermark.py +0 -0
  554. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_web_entrypoint.py +0 -0
  555. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_webguard.py +0 -0
  556. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_whatsapp_documents.py +0 -0
  557. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_whatsapp_images.py +0 -0
  558. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_whoami.py +0 -0
  559. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_why_not_started.py +0 -0
  560. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_withdrawn.py +0 -0
  561. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_witness.py +0 -0
  562. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_worker_brief.py +0 -0
  563. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_worker_events.py +0 -0
  564. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_worker_resumes.py +0 -0
  565. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_worker_scenarios.py +0 -0
  566. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_worker_surfaces.py +0 -0
  567. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_workerstate_said.py +0 -0
  568. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_workflows.py +0 -0
  569. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_zz_chipverbs.py +0 -0
  570. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_zz_lastword.py +0 -0
  571. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_zz_setupchat.py +0 -0
  572. {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_zz_toolcatalog.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taskuary
3
- Version: 0.3.7.2
3
+ Version: 0.3.7.3
4
4
  Summary: Automate your job - the local-first operating system for your work: AI triage in, coding agents out.
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/ldbumble/taskuary
@@ -69,7 +69,7 @@ Dynamic: license-file
69
69
 
70
70
  [![CI](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml/badge.svg)](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml)
71
71
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/ldbumble/taskuary/badge)](https://scorecard.dev/viewer/?uri=github.com/ldbumble/taskuary)
72
- [![PyPI](https://img.shields.io/pypi/v/taskuary.svg?cacheSeconds=300&release=0.3.7.2&asof=2026-09-28T1810)](https://pypi.org/project/taskuary/)
72
+ [![PyPI](https://img.shields.io/pypi/v/taskuary.svg?cacheSeconds=300&release=0.3.7.3&asof=2026-09-28T1810)](https://pypi.org/project/taskuary/)
73
73
  [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-3776ab.svg)](https://github.com/ldbumble/taskuary)
74
74
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
75
75
  [![Stars](https://img.shields.io/github/stars/ldbumble/taskuary?style=flat&color=d4a72c&label=%E2%98%85%20stars)](https://github.com/ldbumble/taskuary/stargazers)
@@ -83,7 +83,7 @@ your agents, and brings decisions back to you. Nothing sends or ships without yo
83
83
 
84
84
  ![The Taskuary Studio assembling as work arrives and AI agents take their seats.](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/hero.gif?v=workspace)
85
85
 
86
- Taskuary is early—currently **v0.3.7.2**—so breaking changes are still possible before 1.0.
86
+ Taskuary is early—currently **v0.3.7.3**—so breaking changes are still possible before 1.0.
87
87
 
88
88
  <p align="center">
89
89
  <a href="https://taskuary.com/demo/"><img
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![CI](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml/badge.svg)](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml)
6
6
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/ldbumble/taskuary/badge)](https://scorecard.dev/viewer/?uri=github.com/ldbumble/taskuary)
7
- [![PyPI](https://img.shields.io/pypi/v/taskuary.svg?cacheSeconds=300&release=0.3.7.2&asof=2026-09-28T1810)](https://pypi.org/project/taskuary/)
7
+ [![PyPI](https://img.shields.io/pypi/v/taskuary.svg?cacheSeconds=300&release=0.3.7.3&asof=2026-09-28T1810)](https://pypi.org/project/taskuary/)
8
8
  [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-3776ab.svg)](https://github.com/ldbumble/taskuary)
9
9
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
10
10
  [![Stars](https://img.shields.io/github/stars/ldbumble/taskuary?style=flat&color=d4a72c&label=%E2%98%85%20stars)](https://github.com/ldbumble/taskuary/stargazers)
@@ -18,7 +18,7 @@ your agents, and brings decisions back to you. Nothing sends or ships without yo
18
18
 
19
19
  ![The Taskuary Studio assembling as work arrives and AI agents take their seats.](https://raw.githubusercontent.com/ldbumble/taskuary/master/docs/hero.gif?v=workspace)
20
20
 
21
- Taskuary is early—currently **v0.3.7.2**—so breaking changes are still possible before 1.0.
21
+ Taskuary is early—currently **v0.3.7.3**—so breaking changes are still possible before 1.0.
22
22
 
23
23
  <p align="center">
24
24
  <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.7.2"
7
+ version = "0.3.7.3"
8
8
  description = "Automate your job - the local-first operating system for your work: AI triage in, coding agents out."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -211,7 +211,7 @@ def _resolved(cat: dict, model: str) -> str:
211
211
  # say it rather than showing an empty field that looks unconfigured.
212
212
  _CONN_DEFAULT = {'anthropic': 'claude-opus-5', 'openai': 'gpt-4o-mini', 'openrouter': 'openrouter/auto',
213
213
  'meta': 'muse-spark-1.2', 'ollama': '(required - name the model you pulled)',
214
- 'azure_openai': '(the deployment name on the card)'}
214
+ 'azure_openai': '(the deployment name on the card)', 'chatgpt': '(the first model your plan lists)'}
215
215
  _CONN_MODELS = {'anthropic': ['claude-opus-5', 'claude-sonnet-5', 'claude-haiku-4-5'],
216
216
  'openai': ['gpt-4o-mini', 'gpt-4o'],
217
217
  'openrouter': ['openrouter/auto', 'meta-llama/llama-3.3-70b-instruct'],
@@ -417,12 +417,10 @@ def health_ideas(store, now: datetime = None) -> list:
417
417
  'text': f"{r['title']} is a workflow that has never run - its clock is {r['schedule'] or 'not set'}.",
418
418
  'action': {'type': 'health', 'tab': 'Reports', 'hash': f"report={r['source_id']}", 'source_id': r['source_id'],
419
419
  'why': 'a workflow that never ran is either mis-clocked or waiting on a sign-in'}})
420
- for c in appfacts.connections(store):
421
- if c['active'] and c['last_error']:
422
- out.append({'key': f"health:connection:{c['connector_id']}", 'kind': 'health', 'sig': re.sub(r'\d+', '#', _short(c['last_error'], 60)),
423
- 'text': f"{c['name']} is erroring - {_short(c['last_error'], 80)}.",
424
- 'action': {'type': 'health', 'tab': 'Connections', 'hash': f"connector={c['type']}", 'connector_id': c['connector_id'],
425
- 'why': 'a connection that errors reads nothing until somebody looks'}})
420
+ # An erroring CONNECTION is not raised here: the rail already carries it as its own "stopped answering" row
421
+ # (funnel.broken_connections), and said twice it became an idea, then a task, then a coding agent investigating
422
+ # a connection the owner was looking at (the owner, 2026-09-30: "don't need both ... if it fails we don't need
423
+ # advisor to tell us").
426
424
  try:
427
425
  waiting = len(store.pending_triage(limit=50))
428
426
  brain = str(store.get_setting('triage_ai') or '').strip()
@@ -0,0 +1,125 @@
1
+ """What a Claude run left RUNNING when its turn ended - background shells, monitors, subagents. Claude's own word
2
+ first: its Stop payload lists them (`from_hook`); a Claude that does not send that list is read off its own
3
+ transcript instead (`pending` - the Stop hook names it: `transcript_path`).
4
+
5
+ Stop is the end of a RESPONSE. A coder that starts a long pull in the background, arms a monitor on it and says
6
+ "re-pulling Jan 2024 now" ends its turn there, and the pane sits at a bare prompt with "1 shell, 1 monitor still
7
+ running" under it - so every surface read "coder is waiting on you" while the work went on (the owner, 2026-10-01:
8
+ "since there is a sub agent doing work the main chat might wait but the process is still going"). When a job
9
+ ends Claude is woken by its notification, takes a turn, and Stops again, so each Stop re-reads this.
10
+
11
+ The transcript says when a job STARTS (the tool result carries `backgroundTaskId`, a monitor's `taskId` +
12
+ `timeoutMs`, a subagent's `isAsync` + `agentId`) and when it ENDS (a `<task-notification>` with a `<status>`,
13
+ a monitor's "[Monitor expired" event, a TaskStop). Claude itself says some stops leave no marker - a shell killed
14
+ from its UI - so every job also carries its own deadline, and one past it is not counted.
15
+ """
16
+ import json, re, time
17
+ from datetime import datetime
18
+
19
+ SHELL_MS = 1_800_000 # Bash run_in_background's default timeout
20
+ _TID = re.compile(r'<task-id>([^<]+)</task-id>')
21
+ _ENDED = re.compile(r'<status>|\[Monitor expired')
22
+ # cheap substring gate: a transcript runs to tens of MB and only these lines can start or end a job
23
+ _MARKS = ('backgroundTaskId', '"timeoutMs"', 'async_launched', '<task-notification>', '"run_in_background":true',
24
+ '"name":"Monitor"', '"name":"TaskStop"', '"name":"Agent"', '"name":"Task"')
25
+
26
+
27
+ def _epoch(ts) -> float:
28
+ try: return datetime.fromisoformat(str(ts).replace('Z', '+00:00')).timestamp()
29
+ except ValueError: return time.time()
30
+
31
+
32
+ def _started(r: dict, inp: dict, at: float):
33
+ "(id, job) for a tool result that put work in the background, else None"
34
+ if r.get('backgroundTaskId'):
35
+ return r['backgroundTaskId'], {'kind': 'shell', 'what': inp.get('description') or str(inp.get('command') or '')[:120],
36
+ 'until': at + (inp.get('timeout') or SHELL_MS) / 1000}
37
+ if r.get('taskId') and 'timeoutMs' in r:
38
+ return r['taskId'], {'kind': 'monitor', 'what': inp.get('description') or '',
39
+ 'until': None if r.get('persistent') else at + (r.get('timeoutMs') or 0) / 1000}
40
+ if r.get('isAsync') and r.get('agentId'):
41
+ return r['agentId'], {'kind': 'agent', 'what': r.get('description') or inp.get('description') or '', 'until': None}
42
+
43
+
44
+ def pending(path, now: float = None) -> list:
45
+ """The jobs still running as of `now`: [{id, kind: shell|monitor|agent, what, until (epoch s, None = no deadline)}]."""
46
+ now, uses, jobs = now or time.time(), {}, {}
47
+ try: f = open(path, encoding='utf-8', errors='replace')
48
+ except (OSError, TypeError): return []
49
+ with f:
50
+ for l in f:
51
+ if not any(m in l for m in _MARKS): continue
52
+ try: o = json.loads(l)
53
+ except ValueError: continue
54
+ c = o.get('content') if o.get('type') == 'queue-operation' else (o.get('message') or {}).get('content')
55
+ if isinstance(c, str):
56
+ # the queued copy counts as well as the delivered one: the job has ended even before Claude reads it
57
+ if '<task-notification>' in c and _ENDED.search(c):
58
+ for i in _TID.findall(c): jobs.pop(i, None)
59
+ continue
60
+ for b in c if isinstance(c, list) else []:
61
+ if not isinstance(b, dict): continue
62
+ if b.get('type') == 'tool_use':
63
+ uses[b.get('id')] = b
64
+ if b.get('name') == 'TaskStop':
65
+ inp = b.get('input') or {}
66
+ jobs.pop(str(inp.get('task_id') or inp.get('shell_id') or ''), None)
67
+ elif b.get('type') == 'tool_result' and isinstance(o.get('toolUseResult'), dict):
68
+ got = _started(o['toolUseResult'], (uses.get(b.get('tool_use_id')) or {}).get('input') or {}, _epoch(o.get('timestamp')))
69
+ if got: jobs[got[0]] = got[1]
70
+ return [dict(id=k, **v) for k, v in jobs.items() if v['until'] is None or v['until'] > now]
71
+
72
+
73
+ def from_hook(p: dict):
74
+ """The jobs Claude itself says are running, off its Stop payload - None when this Claude does not send the list.
75
+
76
+ Claude 2.1.286 carries `background_tasks` on Stop and SubagentStop (measured 2026-10-01; the hooks reference does
77
+ not list it): [{id, type: shell|subagent, status, description, command|agent_type}], shrinking as each job ends.
78
+ A monitor is a `shell` there. The CLI's own word, so it needs no deadline; the transcript read is for a Claude
79
+ without it. `ambient` is the SDK's mark for housekeeping a host should not count as activity."""
80
+ got = p.get('background_tasks')
81
+ if not isinstance(got, list): return None
82
+ return [{'id': str(t.get('id') or ''), 'kind': 'agent' if t.get('type') == 'subagent' else str(t.get('type') or 'task'),
83
+ 'what': str(t.get('description') or t.get('command') or '')[:120], 'until': None}
84
+ for t in got if isinstance(t, dict) and t.get('status', 'running') == 'running' and not t.get('ambient')]
85
+
86
+
87
+ def is_wake(prompt) -> bool:
88
+ "a turn the CLI took because background work reported - a UserPromptSubmit nobody typed (Claude's, Copilot's)"
89
+ return str(prompt or '').lstrip().startswith(('<task-notification>', '<system_notification>'))
90
+
91
+
92
+ # COPILOT (1.0.86, measured 2026-10-01) sends no list on Stop, but says both ends in its hooks: a shell started with
93
+ # `mode: async` answers "<command started in background with shellId: 0>", and its end is a Notification
94
+ # `shell_completed` naming "(shellId: 0)" - then Copilot wakes and takes a turn on it. `detach` is the other way: a
95
+ # server left to outlive the session, which nobody wakes for, so it is not counted. No timeout is said, so a cap.
96
+ _STARTED = re.compile(r'started in background with shellId:\s*([\w-]+)')
97
+ _SHELL_ID = re.compile(r'shellId:\s*([\w-]+)')
98
+ CAP_S = 7200
99
+
100
+
101
+ def track(jobs: dict, p: dict) -> None:
102
+ "keep a session's own running jobs from its hook events (`jobs` lives on the pane: a pane and its jobs die together)"
103
+ ev = p.get('hook_event_name')
104
+ if ev == 'PostToolUse':
105
+ inp, res = p.get('tool_input') or {}, p.get('tool_result') or p.get('tool_response') or {}
106
+ m = _STARTED.search(str(res.get('text_result_for_llm') if isinstance(res, dict) else res))
107
+ if m and isinstance(inp, dict) and not inp.get('detach'):
108
+ jobs[m.group(1)] = {'id': m.group(1), 'kind': 'shell', 'what': str(inp.get('description') or inp.get('command') or '')[:120],
109
+ 'until': time.time() + CAP_S}
110
+ elif ev == 'Notification' and str(p.get('notification_type') or '').startswith('shell_'):
111
+ m = _SHELL_ID.search(str(p.get('message') or ''))
112
+ if m: jobs.pop(m.group(1), None)
113
+
114
+
115
+ def live(jobs, now: float = None) -> list:
116
+ "the ones of a recorded list still inside their deadline"
117
+ now = now or time.time()
118
+ return [j for j in jobs or [] if isinstance(j, dict) and (j.get('until') is None or j['until'] > now)]
119
+
120
+
121
+ def summary(jobs) -> str:
122
+ "'1 shell, 1 monitor still running' - the words Claude's own footer uses"
123
+ n = {}
124
+ for j in jobs: n[j['kind']] = n.get(j['kind'], 0) + 1
125
+ return ', '.join(f"{v} {k}{'' if v == 1 else 's'}" for k, v in n.items()) + ' still running' if n else ''
@@ -769,6 +769,9 @@ def retire_draft_answered_elsewhere(store, tid: int | None, sent: dict) -> list:
769
769
  where = f'TQ-{tid:04d}'
770
770
  state = ' The task is still open because it has other work in progress.' \
771
771
  if task.get('Status') not in ('done', 'dropped') else ' I marked the task done.'
772
+ # ...said in the Assistant's conversation - and never as a dead end: the page offers Next under the newest line that has
773
+ # nothing to press (the owner, 2026-09-30: "it can write that and close task ... but then should move to next or at least
774
+ # have buttons to navigate")
772
775
  from . import concierge, funnel, general
773
776
  concierge.record(store, general.dock_task(store)[0]['TaskId'], 'assistant',
774
777
  f'You replied {place} on {where}{excerpt}. '
@@ -0,0 +1,268 @@
1
+ """Sign in with ChatGPT - the owner's ChatGPT plan as a brain, no API key.
2
+
3
+ OpenAI's plan sharing for open-source, locally hosted apps (DevDay 2026, developers.openai.com/siwc):
4
+ the owner signs in once in their own browser, OpenAI registers a client for THIS install on the fly
5
+ (dynamic registration - there is no Taskuary app id to ship), and model calls are billed to their
6
+ ChatGPT allowance under a weekly cap they set per app. The Codex CLI already rode the plan for coding;
7
+ this is the same plan for the one-message jobs - triage, the Assistant, reports - which only ever
8
+ spoke to API keys.
9
+
10
+ The shape is msauth's: the refresh token is the card's secret, access tokens are minted from it and
11
+ cached, a rotated refresh token is handed to on_rotate so it survives a restart. What differs is the
12
+ sign-in itself: a browser redirect to a loopback listener on 127.0.0.1 (the only redirect OpenAI
13
+ takes for desktop apps), with PKCE, state and nonce fresh per attempt.
14
+
15
+ Limits the card states (developers.openai.com/siwc/.../preview-limitations): images, files, function tools and web
16
+ search where the model accepts them; no image generation, file search, code interpreter, computer use or hosted
17
+ connectors. Streamed and never stored on OpenAI's side; a cap reached stops the brain until the owner raises it.
18
+ """
19
+ import base64, hashlib, json, re, secrets, threading, time, uuid
20
+ from http.server import BaseHTTPRequestHandler, HTTPServer
21
+ from urllib.parse import parse_qs, urlencode, urlparse
22
+ import requests
23
+ from loguru import logger
24
+
25
+ AUTH = 'https://auth.openai.com'
26
+ API = 'https://api.openai.com/v1'
27
+ SCOPES = 'openid profile email offline_access resource.invoke chatgpt.tokens.use.direct'
28
+ PLAN_SCOPE = 'chatgpt.tokens.use.direct' # without it the sign-in worked and no call will
29
+ PORT = 1455 # OpenAI's example; anything on loopback is taken, so a busy port is not fatal
30
+ AGENT_NAME = 'Taskuary'
31
+
32
+ _CACHE, _LOCK = {}, threading.Lock() # refresh token -> (access, expires_at, current refresh token)
33
+ _FLOWS = {} # flow id -> one sign-in in progress
34
+ on_rotate = None # set by the server: (connector id, new refresh token) -> None
35
+
36
+
37
+ def host_id(cfg: dict) -> str:
38
+ """This install's stable, opaque id - usage is counted per host. Minted once, kept on the card."""
39
+ return (cfg or {}).get('host_id') or f'urn:uuid:{uuid.uuid4()}'
40
+
41
+
42
+ def _b64(b: bytes) -> str: return base64.urlsafe_b64encode(b).rstrip(b'=').decode()
43
+
44
+
45
+ class _Callback(BaseHTTPRequestHandler):
46
+ """One GET, the browser coming back from auth.openai.com. Everything else is a 404."""
47
+ def do_GET(self):
48
+ u = urlparse(self.path)
49
+ f = self.server.flow
50
+ if u.path != '/auth/callback':
51
+ self.send_response(404); self.end_headers(); return
52
+ q = {k: v[0] for k, v in parse_qs(u.query).items()}
53
+ if q.get('state') != f['state']: f['error'] = 'the sign-in came back for a different attempt - start it again'
54
+ elif q.get('error'): f['error'] = q.get('error_description') or q['error']
55
+ else: f['code'], f['client_id'], f['scope'] = q.get('code'), q.get('client_id') or f['client_id'], q.get('scope', '')
56
+ ok = not f.get('error')
57
+ body = (f"<!doctype html><meta charset=utf-8><title>Taskuary</title><body style='font:15px system-ui;margin:3em'>"
58
+ f"<h2>{'Signed in to ChatGPT' if ok else 'The sign-in did not finish'}</h2>"
59
+ f"<p>{'You can close this tab - Taskuary is finishing on its own.' if ok else f.get('error', '')}</p>").encode()
60
+ self.send_response(200); self.send_header('Content-Type', 'text/html; charset=utf-8'); self.end_headers()
61
+ self.wfile.write(body)
62
+ threading.Thread(target=self.server.shutdown, daemon=True).start()
63
+ def log_message(self, *a): pass # the request line would carry the code into the log
64
+
65
+
66
+ class _Server(HTTPServer):
67
+ # HTTPServer sets SO_REUSEADDR, and on Windows that lets a SECOND socket bind a port another one is listening on -
68
+ # a Codex login or an earlier sign-in holding 1455 would then share it, and the browser could come back to the
69
+ # wrong listener. Refused instead, so the next line moves to a free port.
70
+ allow_reuse_address = False
71
+
72
+
73
+ def _listen() -> HTTPServer:
74
+ for port in (PORT, 0):
75
+ try: return _Server(('127.0.0.1', port), _Callback)
76
+ except OSError: continue # a Codex login holding 1455 is not a reason to fail
77
+ raise RuntimeError('could not open a local port for the sign-in to return to')
78
+
79
+
80
+ def start(cfg: dict) -> dict:
81
+ """Begin a sign-in: the URL to open in the browser, and a flow id to poll with."""
82
+ for k, v in list(_FLOWS.items()):
83
+ if time.time() - v['at'] > 900: _stop(k)
84
+ verifier = _b64(secrets.token_bytes(32))
85
+ srv = _listen()
86
+ redirect = f'http://127.0.0.1:{srv.server_address[1]}/auth/callback'
87
+ flow = {'state': secrets.token_urlsafe(24), 'nonce': secrets.token_urlsafe(24), 'verifier': verifier, 'redirect': redirect,
88
+ 'host_id': host_id(cfg), 'client_id': (cfg or {}).get('client_id') or 'dynamic_agent_client', 'at': time.time(), 'srv': srv}
89
+ srv.flow = flow
90
+ threading.Thread(target=srv.serve_forever, daemon=True).start()
91
+ q = {'client_id': flow['client_id'], 'response_type': 'code', 'redirect_uri': redirect, 'scope': SCOPES,
92
+ 'resource': API, 'state': flow['state'], 'nonce': flow['nonce'], 'code_challenge_method': 'S256',
93
+ 'code_challenge': _b64(hashlib.sha256(verifier.encode()).digest())}
94
+ # a first sign-in registers this install; a card that already holds a client id signs back in to it
95
+ if q['client_id'] == 'dynamic_agent_client': q.update(agent_name_hint=AGENT_NAME, ext_agent_host_id=flow['host_id'])
96
+ fid = secrets.token_urlsafe(12)
97
+ _FLOWS[fid] = flow
98
+ return {'flow': fid, 'url': f'{AUTH}/api/accounts/authorize?{urlencode(q)}'}
99
+
100
+
101
+ def _stop(fid):
102
+ f = _FLOWS.pop(fid, None)
103
+ if f:
104
+ try: f['srv'].shutdown(); f['srv'].server_close()
105
+ except Exception: pass
106
+
107
+
108
+ def poll(fid: str) -> dict:
109
+ """{'pending': True} until the browser comes back; then the tokens and who signed in. Raises on refusal."""
110
+ f = _FLOWS.get(fid)
111
+ if not f: raise RuntimeError('no such sign-in in progress - start it again')
112
+ if f.get('error'): _stop(fid); raise RuntimeError(f['error'])
113
+ if not f.get('code'):
114
+ if time.time() - f['at'] > 900: _stop(fid); raise RuntimeError('the sign-in took too long - start it again')
115
+ return {'pending': True}
116
+ _stop(fid)
117
+ r = requests.post(f'{AUTH}/api/accounts/oauth/token', timeout=20, data={
118
+ 'grant_type': 'authorization_code', 'client_id': f['client_id'], 'code': f['code'],
119
+ 'code_verifier': f['verifier'], 'redirect_uri': f['redirect'], 'resource': API})
120
+ if r.status_code != 200: raise RuntimeError(f'OpenAI refused the sign-in ({r.status_code}): {_err(r)}')
121
+ t = r.json()
122
+ granted = str(t.get('scope') or f.get('scope') or '')
123
+ if PLAN_SCOPE not in granted.split():
124
+ raise RuntimeError('signed in, but ChatGPT plan usage was not granted - sign in again and allow it')
125
+ if not t.get('refresh_token'): raise RuntimeError('OpenAI returned no refresh token - offline access was not granted')
126
+ who = _identity(t.get('id_token'), f['nonce'])
127
+ return {'client_id': f['client_id'], 'host_id': f['host_id'], 'refresh_token': t['refresh_token'],
128
+ 'access_token': t['access_token'], 'expires_in': t.get('expires_in'), 'scope': granted, **who}
129
+
130
+
131
+ def _identity(id_token: str, nonce: str) -> dict:
132
+ """Who signed in, from the ID token. It came straight from the token endpoint over TLS, which OpenID Connect
133
+ accepts in place of a signature check (Core 3.1.3.7); issuer, expiry and our nonce are still checked."""
134
+ if not id_token: return {'sub': '', 'email': '', 'name': ''}
135
+ try:
136
+ part = id_token.split('.')[1]
137
+ c = json.loads(base64.urlsafe_b64decode(part + '=' * (-len(part) % 4)))
138
+ except (IndexError, ValueError): raise RuntimeError('OpenAI sent an ID token that does not parse')
139
+ if c.get('nonce') != nonce: raise RuntimeError('the sign-in answer does not belong to this attempt (nonce) - start it again')
140
+ if not str(c.get('iss') or '').startswith(AUTH): raise RuntimeError(f"unexpected token issuer {c.get('iss')!r}")
141
+ if c.get('exp') and float(c['exp']) < time.time(): raise RuntimeError('the sign-in answer had already expired - start it again')
142
+ return {'sub': c.get('sub', ''), 'email': c.get('email', ''), 'name': c.get('name', '')}
143
+
144
+
145
+ _DEAD = {'invalid_grant', 'invalid_refresh_token', 'token_expired', 'refresh_token_expired', 'refresh_token_invalidated', 'refresh_token_reused'}
146
+
147
+
148
+ def refresh(cfg: dict, refresh_token: str) -> dict:
149
+ r = requests.post(f'{AUTH}/api/accounts/oauth/token', timeout=20, data={
150
+ 'grant_type': 'refresh_token', 'client_id': cfg.get('client_id') or '', 'refresh_token': refresh_token, 'resource': API})
151
+ if r.status_code != 200:
152
+ code = _code(r)
153
+ if code in _DEAD: raise RuntimeError('the ChatGPT sign-in has lapsed - sign in again on the ChatGPT card')
154
+ raise RuntimeError(f'OpenAI would not renew the ChatGPT sign-in ({r.status_code}): {_err(r)}')
155
+ return r.json()
156
+
157
+
158
+ def access_token(cfg: dict, refresh_token: str) -> str:
159
+ """A live access token - cached until a minute before expiry; the mint is inside the lock so two callers never rotate
160
+ the refresh token out from under each other (a reused one is refused: refresh_token_reused)."""
161
+ if not refresh_token: raise RuntimeError('not signed in - click "Sign in with ChatGPT" on the ChatGPT card')
162
+ with _LOCK:
163
+ hit = _CACHE.get(refresh_token)
164
+ if hit and hit[1] > time.time() + 60: return hit[0]
165
+ rt_now = hit[2] if hit else refresh_token
166
+ t = refresh(cfg, rt_now)
167
+ new_rt = t.get('refresh_token') or rt_now
168
+ _CACHE[refresh_token] = (t['access_token'], time.time() + int(t.get('expires_in') or 3600), new_rt)
169
+ if new_rt != refresh_token and on_rotate and cfg.get('_cid'):
170
+ try: on_rotate(cfg['_cid'], new_rt)
171
+ except Exception as e: logger.warning(f'could not persist the rotated ChatGPT refresh token: {e}')
172
+ return t['access_token']
173
+
174
+
175
+ def revoke(cfg: dict, refresh_token: str) -> None:
176
+ """Sign out: end the renewable session at OpenAI, not just forget it here."""
177
+ try:
178
+ conf = requests.get(f'{AUTH}/.well-known/openid-configuration', timeout=15).json()
179
+ requests.post(conf['revocation_endpoint'], timeout=15, data={
180
+ 'token': refresh_token, 'token_type_hint': 'refresh_token', 'client_id': cfg.get('client_id') or ''})
181
+ except Exception as e: logger.warning(f'could not revoke the ChatGPT session at OpenAI: {e}')
182
+ with _LOCK: _CACHE.pop(refresh_token, None)
183
+
184
+
185
+ def models(token: str) -> list:
186
+ """[(slug, display name)] the plan offers - the ones OpenAI marks for listing."""
187
+ r = requests.get(f'{API}/models', headers={'Authorization': f'Bearer {token}'}, timeout=20)
188
+ if r.status_code != 200: raise RuntimeError(f'OpenAI would not list the plan models ({r.status_code}): {_err(r)}')
189
+ # the docs show `data`; the plan answers with `models` (slug / display_name / visibility), the shape Codex caches
190
+ # in ~/.codex/models_cache.json - read either, and say what came back rather than "no models" when neither is there
191
+ j = r.json()
192
+ rows = j.get('models') or j.get('data') or []
193
+ if not rows: raise RuntimeError(f'OpenAI listed no plan models (the answer carried: {sorted(j) or "nothing"})')
194
+ return [(m.get('slug') or m.get('id'), m.get('display_name') or m.get('slug') or m.get('id'))
195
+ for m in rows if m.get('visibility', 'list') == 'list' and (m.get('slug') or m.get('id'))]
196
+
197
+
198
+ # what each refusal means, and whether trying again can help (errors-and-recovery)
199
+ _CALL_ERRORS = {
200
+ 'subscription_sharing_usage_limit_exceeded': 'the weekly cap for Taskuary on your ChatGPT plan is used up - raise it in ChatGPT → Settings, or wait for it to reset',
201
+ 'subscription_sharing_user_not_eligible': 'this ChatGPT account is not eligible to share its plan with apps',
202
+ 'subscription_sharing_unsupported_capability': 'the plan does not serve part of this request (an image or a tool this model does not take)',
203
+ 'subscription_sharing_invalid_user': 'the ChatGPT sign-in is no longer valid - sign in again on the ChatGPT card',
204
+ }
205
+
206
+
207
+ class Unsupported(RuntimeError):
208
+ """The plan cannot serve something in this request (an image, a tool) - worth asking again without it."""
209
+
210
+
211
+ _OPTIONAL = ('text', 'max_output_tokens', 'instructions')
212
+
213
+
214
+ def complete(token: str, model: str, system: str, user: str, max_tokens: int, want: dict = None, timeout: int = 120,
215
+ images=None, web: bool = False) -> str:
216
+ """One answer, streamed (the plan serves nothing else) and never stored. Only `response.completed` is success:
217
+ a stream that stops early is a failure, not a short answer."""
218
+ content = [{'type': 'input_image', 'image_url': f'data:{ct};base64,{b64}'} for ct, b64 in (images or [])]
219
+ # no max_output_tokens: the plan's endpoint refuses it ("Unsupported parameter", measured 2026-09-30)
220
+ body = {'model': model, 'instructions': system, 'store': False, 'stream': True,
221
+ 'input': [{'role': 'user', 'content': content + [{'type': 'input_text', 'text': user}]}]}
222
+ if web: body['tools'] = [{'type': 'web_search'}]
223
+ if want: body['text'] = {'format': {'type': 'json_schema', 'name': want.get('name') or 'answer',
224
+ 'schema': want.get('schema') or want, 'strict': True}}
225
+ # An OPTIONAL field the plan names as unsupported is taken out and the question asked again - the schema then
226
+ # lives in the prompt, as it does for every brain that cannot carry one. `tools` is the caller's to drop.
227
+ for _ in range(3):
228
+ r = requests.post(f'{API}/responses', json=body, stream=True, timeout=timeout,
229
+ headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'})
230
+ if r.status_code == 200: break
231
+ field = (re.search(r'Unsupported parameter: ([\w]+)', _err(r)) or [None, None])[1]
232
+ r.close()
233
+ if field == 'tools': raise Unsupported(f'the plan does not take tools on this model: {_err(r)}')
234
+ if field in _OPTIONAL and field in body: body.pop(field); continue
235
+ code = _code(r)
236
+ if code == 'subscription_sharing_unsupported_capability': raise Unsupported(_CALL_ERRORS[code])
237
+ raise RuntimeError(_CALL_ERRORS.get(code) or f'ChatGPT plan call failed ({r.status_code}): {_err(r)}')
238
+ with r:
239
+ if r.status_code != 200: raise RuntimeError(f'ChatGPT plan call failed ({r.status_code}): {_err(r)}')
240
+ out, done = [], False
241
+ for line in r.iter_lines(decode_unicode=True):
242
+ if not line or not line.startswith('data:'): continue
243
+ data = line[5:].strip()
244
+ if data == '[DONE]': break
245
+ try: ev = json.loads(data)
246
+ except ValueError: continue
247
+ kind = ev.get('type')
248
+ if kind == 'response.output_text.delta': out.append(ev.get('delta') or '')
249
+ elif kind == 'response.completed': done = True; break
250
+ elif kind in ('response.failed', 'error'):
251
+ err = (ev.get('response') or {}).get('error') or ev.get('error') or {}
252
+ raise RuntimeError(_CALL_ERRORS.get(err.get('code')) or f"ChatGPT plan call failed: {err.get('message') or err.get('code') or kind}")
253
+ if not done: raise RuntimeError('the ChatGPT plan answer stopped before it finished - try again')
254
+ return ''.join(out)
255
+
256
+
257
+ def _code(r) -> str:
258
+ try: j = r.json()
259
+ except ValueError: return ''
260
+ e = j.get('error')
261
+ return (e.get('code') if isinstance(e, dict) else e) or ''
262
+
263
+
264
+ def _err(r) -> str:
265
+ try:
266
+ j = r.json(); e = j.get('error')
267
+ return (e.get('message') if isinstance(e, dict) else j.get('error_description') or e or j.get('detail')) or r.text[:300]
268
+ except ValueError: return r.text[:300]
@@ -151,8 +151,8 @@ OPENING = (
151
151
  RECEIPTS = {'reply': "I'll draft that - it lands below for your yes.", 'approve': 'Sending it as drafted. Moving on.',
152
152
  'not_ours': "Not ours, then - filed. Moving on.", 'not_ours_remember': "Filed, and remembered: this kind goes straight past you from now on.",
153
153
  'not_ours_sender': "Noted: that sender is noise - everything from them files itself from now on. Moving on.",
154
- 'remember': "Remembered. Moving on.", 'coder': "Sent off to the coding agent - watch it on the Board if you like; I'll bring its findings back here when it's done. Meanwhile, the next thing.",
155
- 'regular_agent': "Sent off to the regular agent - I'll bring its answer back here when it is done. Meanwhile, the next thing.",
154
+ 'remember': "Remembered. Moving on.", 'coder': "Sent to the coding agent - it is starting on it now, and its findings come back here when it is done.",
155
+ 'regular_agent': "Sent to an agent - it is starting on it now, and its answer comes back here when it is done.",
156
156
  'clear': 'Cleared. Moving on.',
157
157
  'mine': "On your list. Moving on.", 'close': 'Closing the task. Moving on.', 'rerun': "Queued the rerun - it lands back in the pipe when it's done. Moving on.",
158
158
  'setup': "I'll walk you through it - opening it as a conversation with the assistant, no code, nothing built. "
@@ -599,6 +599,31 @@ def cannot(item: dict | None, verb: str, store=None) -> str:
599
599
  return ''
600
600
 
601
601
 
602
+ def brain_trouble(error) -> str:
603
+ """What went wrong with the Assistant's AI, and what to do about it - in words. A failed turn used to be logged and
604
+ answered with the pipe's facts, which read as the Assistant ignoring the question or being stuck (the owner, 2026-09-30:
605
+ "if assistant brain runs into ran out of tokens or api errors it should say so and tell user to restart chat if broken or
606
+ wait until error clears")."""
607
+ import re as _re
608
+ text = ' '.join(str(error or '').split())
609
+ low = text.lower()
610
+ detail = _re.sub(r'\s*\{.*$', '', text)[:160].rstrip(' :.')
611
+ if _re.search(r'context (length|window)|maximum context|too many tokens|token limit|prompt is too long|max_tokens|too long', low):
612
+ return ("My AI could not take this conversation any more - it has grown longer than it can read. Start a new chat "
613
+ "(the pencil at the top right) and ask again; your work and the pipe are untouched.")
614
+ if _re.search(r'rate.?limit|429|quota|insufficient_quota|usage limit|out of (credits|sessions|tokens)|overloaded|529|capacity', low):
615
+ return (f"My AI is out of room for the moment ({detail}). It clears on its own - wait a few minutes and try again, "
616
+ "or go on with Next.")
617
+ if _re.search(r'401|403|unauthori[sz]ed|invalid.*(key|token)|signed out|not logged in|login|expired', low):
618
+ return (f"My AI turned the request away - its sign-in or key needs attention ({detail}). Settings → Triage & agents "
619
+ "names which AI I use; Connections is where it is signed in.")
620
+ if _re.search(r'timed out|timeout|connection|network|unreachable|502|503|504|500|server error', low):
621
+ return (f"My AI did not answer - the service had a problem ({detail}). Try again in a moment; if it keeps failing, "
622
+ "start a new chat (the pencil at the top right).")
623
+ return (f"My AI ran into an error and gave me no answer ({detail or 'no reason given'}). Try again - if it keeps "
624
+ "happening, start a new chat (the pencil at the top right).")
625
+
626
+
602
627
  def walk_chips(left) -> list:
603
628
  """With nothing on the table there is nothing to DECIDE about - only the walk. Offered on the opening
604
629
  line and on every "nothing here" answer while the pipe still holds something, so Next is always
@@ -735,6 +760,14 @@ def funnel_age(item: dict) -> str:
735
760
 
736
761
 
737
762
 
763
+ def _back_words(at) -> str:
764
+ """'back in 2h' - the rail's own countdown for a row put away (funnelPile.railBack), so the line and the rail agree."""
765
+ try: m = (datetime.fromisoformat(str(at).replace('T', ' ')[:19]) - datetime.now()).total_seconds() / 60
766
+ except ValueError: return 'back in a while'
767
+ if m <= 0: return 'back now'
768
+ return 'back in ' + ('30m' if m < 30 else '1h' if m < 60 else f'{int(m // 60)}h' if m < 1440 else f'{int(m // 1440)}d')
769
+
770
+
738
771
  def _title_cut(s: str, n: int = 120) -> str:
739
772
  """A title at most n long, cut at a word and marked as cut - "...check the schedule config, a" read as a typo."""
740
773
  s = str(s or '').strip()
@@ -1867,7 +1900,10 @@ def surface(store, key: str = None, llm=None, actor: str = 'owner', only: str =
1867
1900
  if not item:
1868
1901
  opens = []
1869
1902
  left = [i for i in p['items'] if not i.get('settling')]
1870
- waiting = [i for i in left if i.get('surfaced') and i['lane'] != 'working']
1903
+ # everything the rail holds For later: shown and put down for a while, AND put away to a day (Remind me) - the line
1904
+ # counted only the first, and said "1 thing" over a rail of two (the owner, 2026-10-01: "why does it say one thing
1905
+ # when there are 2?")
1906
+ waiting = [i for i in left if i['lane'] != 'working' and (i.get('surfaced') or i.get('back_at'))]
1871
1907
  if key: say = "I can't find that one - it may be older than what I keep, or it went out under another subject."
1872
1908
  elif not only and waiting:
1873
1909
  # Shown in this walk and still on the rail: a task waiting to start, one an agent left, or a
@@ -1875,8 +1911,10 @@ def surface(store, key: str = None, llm=None, actor: str = 'owner', only: str =
1875
1911
  # them yet, so the line must not promise it does - it did ("Say next and I'll take them"), and
1876
1912
  # Next only said it again (the owner, 2026-09-18: "hitting next just confuses it").
1877
1913
  n = len(waiting)
1878
- say = (f"{n} thing{'s' if n != 1 else ''} you've already seen still wait{'s' if n == 1 else ''} in Work. "
1879
- f"I'll bring {'it' if n == 1 else 'them'} round again in a while - or open {'it' if n == 1 else 'one'} now.")
1914
+ when = ', '.join(f"{i.get('ref') or _title_cut(' '.join(str(i.get('title') or 'one').split()), 40)} {_back_words(i.get('back_at'))}"
1915
+ for i in sorted(waiting, key=lambda i: str(i.get('back_at') or ''))[:4])
1916
+ say = (f"Nothing new right now. {n} thing{'s' if n != 1 else ''} wait{'s' if n == 1 else ''} for later - {when}"
1917
+ f"{' and more' if n > 4 else ''}. Open {'it' if n == 1 else 'one'} now if you want it sooner.")
1880
1918
  # ...and the way to them NOW, by name: a Next under this line only said it again (2026-09-23, six days running).
1881
1919
  # What waits on the owner first - a close-out, an agent's question - then the rest.
1882
1920
  # ...each saying WHAT it is, not a bare number (the owner, 2026-09-30: "have to write what they are about a little")
@@ -1902,7 +1940,9 @@ def surface(store, key: str = None, llm=None, actor: str = 'owner', only: str =
1902
1940
  # an agent has this one now (it started after the pile was built, or the owner just sent it): there is
1903
1941
  # nothing for the owner to do until it stops, so say so, let it go, and take the next one. It comes
1904
1942
  # back by itself - as the agent's question, its draft, or its finished job.
1905
- if item['lane'] == 'working' or (item.get('working') and item['kind'] not in ('agent', 'review', 'action')):
1943
+ # ...only on Next. A row the owner PICKED is one they want to see - the agent at work, its pane - not a line saying there is
1944
+ # nothing for them (the owner, 2026-09-30: "task should pull up if you want?"); a named key goes on the table like any other
1945
+ if not key and (item['lane'] == 'working' or (item.get('working') and item['kind'] not in ('agent', 'review', 'action'))):
1906
1946
  # it stays in the pipe, at the top, in hand - and comes to the front by itself when the agent stops
1907
1947
  who = item.get('working') or next((t.get('agent') or t.get('label') for t in _live(store) if t.get('taskId') == item['tid']), None) or 'the agent'
1908
1948
  say = f"{item.get('ref') or item['title']} is with {who} right now - nothing for you until it stops or asks. I'll bring it down then."
@@ -2764,7 +2804,7 @@ def say(store, text: str, key: str = None, llm=None, actor: str = 'owner', trace
2764
2804
  say_ = f"{fallback(item, False, p['items'])} {NO_BRAIN}".strip()
2765
2805
  rec('assistant', say_)
2766
2806
  return {'say': say_, 'options': [], 'chips': chips_for(store, item) or walk_chips(len(p['items'])), 'decision': None}
2767
- reply, options, decision, call, did_read = '', [], None, None, False
2807
+ reply, options, decision, call, did_read, failed = '', [], None, None, False, None
2768
2808
  try:
2769
2809
  # NO HUB NOTES PER TURN. Every turn carried whatever notes shared the owner's words - asked to research a
2770
2810
  # project, the model read internal notes on CLI flags and timeouts, and answered in that register (the
@@ -2816,7 +2856,8 @@ def say(store, text: str, key: str = None, llm=None, actor: str = 'owner', trace
2816
2856
  logger.info('concierge: the voice broke character - answering with the facts instead')
2817
2857
  reply, options = '', []
2818
2858
  _remember_sid(store, tid, llm)
2819
- except Exception as e: logger.warning(f'concierge: the model pass failed - {e}')
2859
+ except Exception as e:
2860
+ logger.warning(f'concierge: the model pass failed - {e}'); failed = e
2820
2861
  if call:
2821
2862
  # A MISS IS THE MODEL'S TO FIX, not the owner's to read. "No setting by that name - settings.list <group> names
2822
2863
  # them" is written for the model, and it reached the owner word for word (the 2026-09-24 audit). The model gets
@@ -2933,6 +2974,12 @@ def say(store, text: str, key: str = None, llm=None, actor: str = 'owner', trace
2933
2974
  rec('assistant', prop['say'], {'kind': 'proposal', 'key': prop.get('key'), 'title': prop['label'], 'op': prop['id'],
2934
2975
  'tid': prop.get('tid'), 'ref': prop.get('ref'), 'lane': (target_item or {}).get('lane')})
2935
2976
  return {'say': prop['say'], 'options': [], 'decision': None, 'proposal': prop}
2977
+ # THE AI FAILED: say so and what to do, with Try again (the owner's own words, sent again) and the way on
2978
+ if not reply and failed is not None and not decision:
2979
+ say_ = brain_trouble(failed)
2980
+ chips = [{'label': 'Try again', 'ask': text}, *(chips_for(store, item) or walk_chips(len(p['items'])))]
2981
+ rec('assistant', say_)
2982
+ return {'say': say_, 'options': [], 'chips': chips, 'decision': None, 'error': True}
2936
2983
  # a brain that answered with nothing is not "no AI": say what happened, never the missing-connector line
2937
2984
  if not reply: reply = fallback(item, False, p['items'], brain=True)
2938
2985
  chips = chips_for(store, item) or walk_chips(len(p['items']))