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