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.
- {taskuary-0.3.3.2/taskuary.egg-info → taskuary-0.3.3.4}/PKG-INFO +29 -18
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/README.md +28 -17
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/pyproject.toml +1 -1
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/agents.py +30 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/assistant.py +51 -5
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/blackboard.py +16 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/channels.py +59 -2
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/clis.py +11 -1
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/coder.py +11 -3
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/concierge.py +252 -76
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/funnel.py +213 -69
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/general.py +50 -9
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/github.py +43 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/histgen.py +15 -3
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/interview.py +37 -4
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/messengers.py +43 -5
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/outbound.py +40 -11
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/outbox.py +49 -13
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/remote_assistant.py +22 -6
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/reports.py +64 -10
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/responder.py +8 -2
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/server.py +424 -62
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/session_artifacts.py +3 -3
- taskuary-0.3.3.4/taskuary/skills/soul-interview/SKILL.md +92 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/store.py +169 -13
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/terminal.py +178 -19
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/verdicts.py +5 -4
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/wabridge.py +24 -5
- taskuary-0.3.3.2/taskuary/web/assets/GeneralWorkspace-C75zMBzq.js → taskuary-0.3.3.4/taskuary/web/assets/GeneralWorkspace-CugBH3Pj.js +2 -2
- taskuary-0.3.3.2/taskuary/web/assets/HubView-BtNf0bG5.js → taskuary-0.3.3.4/taskuary/web/assets/HubView-CKPusAgu.js +1 -1
- taskuary-0.3.3.4/taskuary/web/assets/index-Dw2KclKc.css +32 -0
- taskuary-0.3.3.4/taskuary/web/assets/index-HgTGxZl1.js +1140 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/web/index.html +2 -2
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/whatsapp/bridge.mjs +118 -13
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/whatsapp/package.json +1 -1
- taskuary-0.3.3.4/taskuary/whatsapp/policy.mjs +51 -0
- taskuary-0.3.3.4/taskuary/whatsapp/policy.test.mjs +37 -0
- taskuary-0.3.3.4/taskuary/whatsapp/roster.mjs +83 -0
- taskuary-0.3.3.4/taskuary/whatsapp/roster.test.mjs +43 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4/taskuary.egg-info}/PKG-INFO +29 -18
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary.egg-info/SOURCES.txt +15 -4
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_agent_environment.py +28 -0
- taskuary-0.3.3.4/tests/test_agent_identity.py +70 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_agent_source.py +10 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_agent_wall.py +14 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_answered_yourself.py +47 -4
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_api.py +7 -2
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_assistant.py +31 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_assistant_reactions.py +43 -11
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_chat_doors.py +69 -0
- taskuary-0.3.3.4/tests/test_chat_freshness.py +85 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_concierge.py +96 -15
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_core.py +15 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_floating_assistant.py +19 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_follow_up_verdict.py +11 -3
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_funnel.py +241 -11
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_general.py +37 -0
- taskuary-0.3.3.4/tests/test_github_images.py +117 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_histgen.py +7 -0
- taskuary-0.3.3.4/tests/test_ignore_sender.py +90 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_lifecycle.py +20 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_live.py +29 -10
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_messengers.py +43 -0
- taskuary-0.3.3.4/tests/test_outbox.py +77 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_promptmap_and_catalog.py +12 -6
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_remote_assistant.py +5 -3
- taskuary-0.3.3.4/tests/test_report_order_and_research.py +110 -0
- taskuary-0.3.3.4/tests/test_report_refusal.py +137 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_send_targets.py +57 -5
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_session_artifacts.py +6 -4
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_soul_interview.py +51 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_terminal.py +72 -11
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_wabridge.py +17 -4
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_waitroom.py +26 -0
- taskuary-0.3.3.2/taskuary/skills/soul-interview/SKILL.md +0 -57
- taskuary-0.3.3.2/taskuary/web/assets/index-CE8to-uH.js +0 -1140
- taskuary-0.3.3.2/taskuary/web/assets/index-C_xWyjgP.css +0 -32
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/LICENSE +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/setup.cfg +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/__init__.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/aisetup.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/artifacts.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/aws.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/azure.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/browserview.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/calendar.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/categories.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/ci.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/cli.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/climodels.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/compose.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/config.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/context.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/counsel.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/db.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/demo.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/deps.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/desktop.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/devtools.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/digest.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/docsync.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/evalset.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/evening.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/guard.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/handbook.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/handraise.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/hooks.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/hub.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/imapmail.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/imessage.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/ingest.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/intacct.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/invoice_workflow.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/knowledge.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/learn.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/learnedgraph.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/live.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/llm.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/logs.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/mcp.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/msauth.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/mssql.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/ownwork.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/phone.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/playbooks.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/pm.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/policy.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/problems.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/projects.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/promptmap.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/proof.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/proposals.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/quickbooks.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/rank.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/research.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/reshape.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/routing.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/scopes.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/selfclose.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/semantic.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/senders.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/setup.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/sharepoint.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/sheets.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/spawn.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/teller.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/coder.md +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/counsel.md +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/digest.md +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/how-it-works.md +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/learned.md +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/playbook.md +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/soul.md +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/style.md +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/templates/triage.md +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/testing.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/toil.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/triage.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/update.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/voice.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/waitroom.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/web/assets/GeneralWorkspace--IqTbJGE.css +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/web/favicon.ico +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/web/favicon.png +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/webguard.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/whoami.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/witness.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary/zoho.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary.egg-info/dependency_links.txt +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary.egg-info/entry_points.txt +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary.egg-info/requires.txt +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/taskuary.egg-info/top_level.txt +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_aisetup.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_answers.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_assistant_opens.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_assistant_verdicts.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_async_triage.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_audit_chain.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_audit_fixes.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_aws_region.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_blackboard.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_browser_session.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_browserview.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_calendar.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_calendar_today.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_categories.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_cc.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_cc_triage.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_chat_is_not_one_task.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_chat_queue.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_climodels.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_clis.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_cloud.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_cloud_sources.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_compose.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_compose_sources.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_compose_workflows.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_config.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_context.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_counsel.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_cwlogs_paging.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_demo.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_deps.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_desktop.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_devtools.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_diff_review.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_digest_brief.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_docs_and_llm.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_docs_flow.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_duplicate_connectors.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_evening_brief.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_factory.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_feed_working.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_finished_chat_stays_finished.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_github_auto.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_guard.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_handbook.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_handbook_topics.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_handoff_channels.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_handraise.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_harvest.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_home_is_never_yours.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_imap_history.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_imapmail.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_imessage.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_intacct.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_invoice_workflow.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_kind_dispatch.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_knowledge.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_learnedgraph.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_local_file.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_mail_folders.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_mark_read.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_mcp.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_meeting_prep.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_message_thread.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_msauth.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_mssql.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_noop_ending.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_not_coding.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_note_retrieval.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_outbound_reports.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_own_rows_no_reply.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_playbooks.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_pm.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_poll_overlap.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_poll_parallel.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_poll_sources.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_pr_comments.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_problems.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_problems_dismiss.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_projects.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_proof_ci_proposals.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_pypi_downloads.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_quickbooks.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_rank.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_rate_limit.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_reclassify.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_reply_channels.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_reply_voice.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_report_alerts.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_report_triage.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_research.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_reviews.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_scopes.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_seed_fits_a_tty.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_seeded_reports.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_semantic.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_setup.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_sheets_sharepoint.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_someone_else_replied.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_stay_open.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_stay_open_doors.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_store_app_cli.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_store_hotpaths.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_sync_clock.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_task_identity.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_teller.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_toil.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_triage_dataset.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_triage_images.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_update.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_update_refused.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_urgent_and_handoff.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_verdict_paths.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_verdict_sticks.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_vision.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_voice.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_watermark.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_web_entrypoint.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_webguard.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_whatsapp_documents.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_whatsapp_images.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_whoami.py +0 -0
- {taskuary-0.3.3.2 → taskuary-0.3.3.4}/tests/test_withdrawn.py +0 -0
- {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.
|
|
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
|
[](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml)
|
|
65
|
-
[](https://pypi.org/project/taskuary/)
|
|
66
66
|
[](https://github.com/ldbumble/taskuary)
|
|
67
67
|
[](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
|
-

|
|
75
75
|
|
|
76
|
-
Taskuary is early—currently **v0.3.3.
|
|
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
|
-
**
|
|
88
|
+
**Connect every system. Keep control.**
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-

|
|
95
|
+

|
|
94
96
|
|
|
95
|
-
**
|
|
97
|
+
**Turn incoming work into tasks automatically**
|
|
96
98
|
|
|
97
|
-
|
|
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
|
-
**
|
|
119
|
+
**Describe a check. Let it run itself.**
|
|
109
120
|
|
|
110
|
-
|
|
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
|
-

|
|
137
|
+

|
|
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
|
-

|
|
139
150
|
|
|
140
151
|
## Install
|
|
141
152
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Taskuary
|
|
2
2
|
|
|
3
3
|
[](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml)
|
|
4
|
-
[](https://pypi.org/project/taskuary/)
|
|
5
5
|
[](https://github.com/ldbumble/taskuary)
|
|
6
6
|
[](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
|
-

|
|
14
14
|
|
|
15
|
-
Taskuary is early—currently **v0.3.3.
|
|
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
|
-
**
|
|
27
|
+
**Connect every system. Keep control.**
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-

|
|
34
|
+

|
|
33
35
|
|
|
34
|
-
**
|
|
36
|
+
**Turn incoming work into tasks automatically**
|
|
35
37
|
|
|
36
|
-
|
|
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
|
-
**
|
|
58
|
+
**Describe a check. Let it run itself.**
|
|
48
59
|
|
|
49
|
-
|
|
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
|
-

|
|
76
|
+

|
|
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
|
-

|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 '
|
|
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',
|
|
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
|
|
228
|
-
#
|
|
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:
|
|
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':
|