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.
- {taskuary-0.3.7.2/taskuary.egg-info → taskuary-0.3.7.3}/PKG-INFO +3 -3
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/README.md +2 -2
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/pyproject.toml +1 -1
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/aidefaults.py +1 -1
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/assistant.py +4 -6
- taskuary-0.3.7.3/taskuary/background.py +125 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/channels.py +3 -0
- taskuary-0.3.7.3/taskuary/chatgptauth.py +268 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/concierge.py +55 -8
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/connectorcatalog.json +14 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/funnel.py +1 -1
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/general.py +8 -2
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/hooks.py +70 -8
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/ingest.py +24 -35
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/llm.py +94 -6
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/problems.py +17 -4
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_reads.py +9 -2
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/remote_assistant.py +8 -3
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/server.py +57 -2
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/settings_defaults.json +0 -2
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/settings_schema.json +0 -14
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/store.py +9 -6
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/terminal.py +44 -12
- taskuary-0.3.7.2/taskuary/web/assets/AssistantGame-BZJj3XwN.js → taskuary-0.3.7.3/taskuary/web/assets/AssistantGame-CCx1jM1n.js +3 -3
- taskuary-0.3.7.2/taskuary/web/assets/GameScene-ATV2SyYO.js → taskuary-0.3.7.3/taskuary/web/assets/GameScene-DEtfroOB.js +1 -1
- 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
- taskuary-0.3.7.2/taskuary/web/assets/StudioScene-D4IlNRXi.js → taskuary-0.3.7.3/taskuary/web/assets/StudioScene-JyIZfG3s.js +1 -1
- taskuary-0.3.7.2/taskuary/web/assets/TasksView-B7m8zBYu.js → taskuary-0.3.7.3/taskuary/web/assets/TasksView-CYGOZob-.js +1 -1
- taskuary-0.3.7.3/taskuary/web/assets/index-BUNEVOG_.css +32 -0
- taskuary-0.3.7.2/taskuary/web/assets/index-BKrB4ObA.js → taskuary-0.3.7.3/taskuary/web/assets/index-i4LFqglY.js +152 -152
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/index.html +2 -2
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/workerstate.py +18 -4
- {taskuary-0.3.7.2 → taskuary-0.3.7.3/taskuary.egg-info}/PKG-INFO +3 -3
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary.egg-info/SOURCES.txt +12 -7
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_advisor_idea_words.py +2 -2
- taskuary-0.3.7.3/tests/test_api_web.py +131 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_ideas.py +5 -3
- taskuary-0.3.7.3/tests/test_background_work.py +193 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_is_not_one_task.py +12 -42
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_retention.py +1 -1
- taskuary-0.3.7.3/tests/test_chatgptauth.py +155 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_concierge.py +38 -4
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_context.py +2 -1
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_continue_session.py +32 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_core.py +2 -2
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_funnel.py +20 -1
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_hooks_events.py +19 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_one_pty_one_geometry.py +159 -108
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_phone_browse_pass.py +69 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_problems.py +19 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_problems_dismiss.py +9 -4
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_remote_sections.py +5 -3
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_terminal.py +37 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_joins.py +15 -0
- taskuary-0.3.7.2/taskuary/web/assets/index-DUxLnhhx.css +0 -32
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/LICENSE +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/setup.cfg +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/__init__.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/acp.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/agents.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/aisetup.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/alchemy.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/appfacts.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/artifacts.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/assistantblocks.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/autoreply.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/aws.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/azure.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/blackboard.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/brief.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/browserview.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/calendar.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/categories.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/chains.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/chatformat.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/chatservers.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/ci.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/cli.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/cli_connections.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/cli_install_terminal.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/cliinstall.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/climodels.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/clipool.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/clis.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/clisetup.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/coder.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/compose.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/config.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/connectorcatalog.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/context.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/continuity.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/counsel.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/databases.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/db.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/demo.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/deps.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/desktop.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/devtools.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/digest.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/docsync.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/doorway_browse.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/evalset.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/evening.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/files.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/funnel_presentation.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/funnel_selection.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/ghcloseout.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/github.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/guard.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/handbook.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/handraise.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/histgen.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/hub.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/images.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/imapmail.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/imessage.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/intacct.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/interview.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/invoice_workflow.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/jev.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/knowledge.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/lanes.json +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/learn.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/learnedgraph.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/linkedin.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/live.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/logs.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/lookups.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/markets.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/mcp.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/messengers.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/msauth.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/mssql.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/operations.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/outbound.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/outbox.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/ownwork.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/playbooks.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/pm.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/policy.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_all.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_inventory.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_membership.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_navigation.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_order.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_projection.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_rail.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_startup.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/processing_unread.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/projects.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/promptmap.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/proof.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/proposals.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/quickbooks.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/rank.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/redact.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/remind.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/reports.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/research.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/reshape.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/responder.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/retention.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/robinhood.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/routing.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/routingmemory.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/scopes.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/selfclose.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/semantic.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/senders.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/session_artifacts.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/sessionfiles.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/settings_schema.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/setup.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/sharepoint.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/sheets.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/simplefin.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/skillimport.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/skills/soul-interview/SKILL.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/skills/taskuary-setup/SKILL.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/social.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/spawn.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/taskstate.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/teller.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/agent.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/analyst.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/coder.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/coordinator.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/counsel.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/digest.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/history/counsel-0.3.3.5.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/history/counsel-0.3.6.8.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/history/counsel-0.3.6.9-examples.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/history/counsel-0.3.6.9-remind.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/history/counsel-0.3.6.9.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/how-it-works.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/learned.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/marketer.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/playbook.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/profile.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/researcher.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/soul.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/style.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/trader.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/templates/triage.md +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/testing.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/toil.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/toolcatalog.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/treg.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/triage.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/triage_kind_rules.json +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/update.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/verdicts.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/voice.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/wabridge.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/waitroom.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/walk.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/assets/RoundedBoxGeometry-BE4rf3sD.js +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/favicon.ico +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/favicon.png +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/assistant.png +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/board.png +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/connections.png +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/docs.png +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/hub.png +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/reports.png +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/settings.png +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/web/walk/tasks.png +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/webguard.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/whatsapp/bridge.mjs +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/whatsapp/package.json +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/whatsapp/policy.mjs +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/whatsapp/poll.mjs +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/whatsapp/roster.mjs +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/whoami.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/witness.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/workflows.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary/zoho.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary.egg-info/dependency_links.txt +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary.egg-info/entry_points.txt +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary.egg-info/requires.txt +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/taskuary.egg-info/top_level.txt +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_acp.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_answer_route.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_answers_and_reminders.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_asking.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_environment.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_identity.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_profiles.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_reply.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_sentence.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_source.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_agent_wall.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_ai_defaults.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_aisetup.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_alchemy.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_an_inbox_is_not_a_destination.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_answered_yourself.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_answers.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_api.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_api_worker_signals.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_app_uptime.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_appfacts.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_approval_interrupt.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_acts.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_blocks.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_can_look_things_up.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_doorways.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_notifications.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_presentation.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_reactions.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_assistant_verdicts.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_async_triage.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_audit_chain.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_audit_fixes.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_auto_start.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_autoreply.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_aws_region.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_azure_discovery.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_banner_is_one_rule.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_blackboard.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_brain_layer.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_brain_reaches_the_session.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_browser_session.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_browser_workflow.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_browserview.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_button_promises.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_calendar.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_calendar_today.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_canvas_relay.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_canvas_sections.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_card_story_phone.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_categories.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cc.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cc_triage.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_doors.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_echo_mail.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_freshness.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_proposals.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_queue.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_relationship.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chat_setup.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chatformat.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chatservers.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_chinese_clis.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_clean_context.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_connections.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_failure.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_help.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_install.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_install_terminal.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_on_fresh_path.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_setup.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cli_update.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_climodels.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_clipool.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_clis.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_closeout.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cloud.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cloud_sources.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_coder_session_continuity.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_completion_freshness.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_compose.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_compose_sources.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_compose_workflows.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_concierge_counsel.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_config.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_context_refresh_freshness.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_coordination.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_correction_doors.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_counsel.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_counsel_consumers.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_counsel_migration.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_counsel_sections.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_current_restore.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_cwlogs_paging.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_databases.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_demo.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_deps.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_desktop.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_devin.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_devtools.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_diff_review.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_digest_brief.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_dispatch_retries.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_docs_and_llm.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_docs_flow.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_duplicate_connectors.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_email_chains.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_email_identity_routing.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_ending_a_run_writes_the_answer.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_evening_brief.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_every_card_is_reachable.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_every_cli_startable.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_explicit_completion.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_factory.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_failed_acts.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_feed_working.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_files.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_final_answer_capture.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_finished_chat_stays_finished.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_floating_assistant.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_follow_up_verdict.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_fresh_evaluation.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_freshness.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_freshness_walk.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_general.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_github_auto.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_github_images.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_guard.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_handbook.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_handbook_topics.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_handoff_channels.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_handraise.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_harvest.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_histgen.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_home_is_never_yours.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_hooks_version.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_ideas_triage.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_ignore_sender.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_ignore_sender_quiets_its_work.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_images.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_imap_catchup.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_imap_history.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_imapmail.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_imessage.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_inline_pictures.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_intacct.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_interrupted_work.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_invite_is_not_work.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_invoice_workflow.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_jev.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_judge_reads_the_conclusion.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_kind_dispatch.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_knowledge.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_lane_vocabulary.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_learnedgraph.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_lifecycle.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_lifespan_loops_are_guarded.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_linkedin.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_linkedin_signin.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_live.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_local_file.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_lookups.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_mail_catchup.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_mail_fetch_cost.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_mail_folders.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_mark_read.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_markets.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_mcp.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_meeting_prep.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_message_thread.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_messengers.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_morning_line.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_msauth.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_mssql.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_multi_question.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_muse.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_no_pydantic_v1.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_noop_ending.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_not_coding.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_note_retrieval.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_onboarding_walk.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_one_app_one_reason.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_only_your_own_thread.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_openai_compatible_brains.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_operations.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_outbound_reports.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_outbox.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_own_rows_no_reply.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_person_name.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_phone_picks.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_phone_walk_ux.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_playbook_setup.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_playbooks.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_pm.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_poll_lanes.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_poll_overlap.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_poll_parallel.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_poll_sources.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_pr_comments.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_procedure_selection.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_processing_demo_replay.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_processing_ledger.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_profile_is_a_role.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_projects.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_prompt_images.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_prompt_sources.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_promptmap_and_catalog.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_proof_ci_proposals.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_pypi_downloads.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_quickbooks.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_qwen.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_rail_decisions.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_rank.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_rate_limit.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_read_text.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_recent_closures.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reclassify.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_redact.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_redact_doors.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_remind.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_remote_assistant.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_always_drafts.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_attachments.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_channels.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_envelope.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_needs_a_correspondent.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_sources.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reply_voice.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_repo_choice_triage.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_alerts.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_deliver.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_headline.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_one_road.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_order_and_research.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_prompt_isolation.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_refusal.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_routing.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_report_triage.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_research.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_research_more.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_review_task.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_reviews.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_robinhood.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_routing_memory.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_s3_poll_paging.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_scopes.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_seed_fits_a_tty.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_seeded_reports.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_selector_vocabulary.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_semantic.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_send_outcomes.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_send_targets.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_sender_trust.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_sender_words.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_session_artifacts.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_session_continuity.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_session_endings.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_session_files.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_settings_defaults_json.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_settings_schema.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_setup.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_setup_skill.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_setup_walk.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_sheets_sharepoint.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_simplefin.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_skillimport.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_social.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_someone_else_replied.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_soul_interview.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_startup_contract.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_stay_open.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_stay_open_doors.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_store_app_cli.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_store_hotpaths.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_store_pokes.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_sync_clock.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_task_ask.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_task_checklist.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_task_controls_operations.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_task_identity.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_task_refs.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_task_tools.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_taskstate.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_telegram_buttons.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_teller.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_the_exe_ships_what_the_wheel_ships.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_the_split_holds.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_toil.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_tracker_revisions.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_treg.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_cleanup.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_dataset.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_errors.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_images.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_pr_rule.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_retry_sweep.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_triage_urgent.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_update.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_update_refused.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_urgent_and_handoff.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_verdict_paths.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_verdict_shape.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_verdict_sticks.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_vision.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_voice.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_wabridge.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_wabridge_log_trim.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_waitroom.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_walk_order.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_watermark.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_web_entrypoint.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_webguard.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_whatsapp_documents.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_whatsapp_images.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_whoami.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_why_not_started.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_withdrawn.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_witness.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_worker_brief.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_worker_events.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_worker_resumes.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_worker_scenarios.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_worker_surfaces.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_workerstate_said.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_workflows.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_zz_chipverbs.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_zz_lastword.py +0 -0
- {taskuary-0.3.7.2 → taskuary-0.3.7.3}/tests/test_zz_setupchat.py +0 -0
- {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.
|
|
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
|
[](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml)
|
|
71
71
|
[](https://scorecard.dev/viewer/?uri=github.com/ldbumble/taskuary)
|
|
72
|
-
[](https://pypi.org/project/taskuary/)
|
|
73
73
|
[](https://github.com/ldbumble/taskuary)
|
|
74
74
|
[](LICENSE)
|
|
75
75
|
[](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
|

|
|
85
85
|
|
|
86
|
-
Taskuary is early—currently **v0.3.7.
|
|
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
|
[](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml)
|
|
6
6
|
[](https://scorecard.dev/viewer/?uri=github.com/ldbumble/taskuary)
|
|
7
|
-
[](https://pypi.org/project/taskuary/)
|
|
8
8
|
[](https://github.com/ldbumble/taskuary)
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](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
|

|
|
20
20
|
|
|
21
|
-
Taskuary is early—currently **v0.3.7.
|
|
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.
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
|
155
|
-
'regular_agent': "Sent
|
|
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
|
-
|
|
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
|
-
|
|
1879
|
-
|
|
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
|
-
|
|
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:
|
|
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']))
|