tsugite-cli 0.23.1__tar.gz → 0.23.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/PKG-INFO +1 -1
  2. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/pyproject.toml +1 -1
  3. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/agent_preparation.py +4 -2
  4. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/agent_runner/history_integration.py +4 -7
  5. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/agent_runner/runner.py +2 -1
  6. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/agents.py +2 -1
  7. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/run.py +2 -1
  8. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/core/executor.py +9 -0
  9. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/providers/anthropic.py +1 -0
  10. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/providers/model_registry.py +8 -4
  11. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/providers/openai_compat.py +2 -0
  12. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/__init__.py +15 -0
  13. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/ui/jsonl.py +14 -1
  14. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/usage/quota.py +8 -10
  15. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/utils.py +1 -0
  16. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/.gitignore +0 -0
  17. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/AGENTS.md +0 -0
  18. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/CONTRIBUTING.md +0 -0
  19. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/LICENSE +0 -0
  20. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/README.md +0 -0
  21. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/__init__.py +0 -0
  22. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/agent_inheritance.py +0 -0
  23. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/agent_runner/__init__.py +0 -0
  24. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/agent_runner/exec_directives.py +0 -0
  25. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/agent_runner/exec_runner.py +0 -0
  26. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/agent_runner/helpers.py +0 -0
  27. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/agent_runner/metrics.py +0 -0
  28. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/agent_runner/models.py +0 -0
  29. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/agent_runner/steps.py +0 -0
  30. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/agent_runner/validation.py +0 -0
  31. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/agent_utils.py +0 -0
  32. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/approval.py +0 -0
  33. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/attachments/__init__.py +0 -0
  34. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/attachments/agent_config.py +0 -0
  35. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/attachments/auto_context.py +0 -0
  36. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/attachments/base.py +0 -0
  37. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/attachments/delegation.py +0 -0
  38. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/attachments/file.py +0 -0
  39. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/attachments/inline.py +0 -0
  40. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/attachments/storage.py +0 -0
  41. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/attachments/url.py +0 -0
  42. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_agents/.gitkeep +0 -0
  43. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_agents/code_searcher.md +0 -0
  44. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_agents/default.md +0 -0
  45. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_agents/file_searcher.md +0 -0
  46. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_agents/job_verifier.md +0 -0
  47. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_agents/job_worker.md +0 -0
  48. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_agents/onboard.md +0 -0
  49. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_context.py +0 -0
  50. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_skills/.gitkeep +0 -0
  51. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_skills/codebase-exploration/SKILL.md +0 -0
  52. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_skills/external-agent-bridge/SKILL.md +0 -0
  53. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_skills/python-math/SKILL.md +0 -0
  54. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_skills/response-patterns/SKILL.md +0 -0
  55. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_skills/scheduling/SKILL.md +0 -0
  56. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_skills/skill-authoring/SKILL.md +0 -0
  57. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_skills/tsugite-agent-basics/SKILL.md +0 -0
  58. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_skills/tsugite-jinja-reference/SKILL.md +0 -0
  59. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/builtin_skills/tsugite-skill-basics/SKILL.md +0 -0
  60. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cache.py +0 -0
  61. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cancellation.py +0 -0
  62. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/__init__.py +0 -0
  63. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/attachments.py +0 -0
  64. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/cache.py +0 -0
  65. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/chat.py +0 -0
  66. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/config.py +0 -0
  67. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/daemon.py +0 -0
  68. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/exec.py +0 -0
  69. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/helpers.py +0 -0
  70. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/history.py +0 -0
  71. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/init.py +0 -0
  72. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/models.py +0 -0
  73. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/plugins.py +0 -0
  74. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/render.py +0 -0
  75. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/secrets.py +0 -0
  76. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/skills.py +0 -0
  77. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/tools.py +0 -0
  78. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/usage.py +0 -0
  79. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/validate.py +0 -0
  80. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/cli/workspace.py +0 -0
  81. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/config.py +0 -0
  82. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/console.py +0 -0
  83. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/constants.py +0 -0
  84. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/context.py +0 -0
  85. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/context_block.py +0 -0
  86. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/core/__init__.py +0 -0
  87. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/core/agent.py +0 -0
  88. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/core/content_blocks.py +0 -0
  89. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/core/executor_registry.py +0 -0
  90. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/core/memory.py +0 -0
  91. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/core/proxy.py +0 -0
  92. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/core/record_store.py +0 -0
  93. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/core/sandbox.py +0 -0
  94. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/core/state.py +0 -0
  95. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/core/subprocess_executor.py +0 -0
  96. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/core/tools.py +0 -0
  97. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/events/__init__.py +0 -0
  98. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/events/base.py +0 -0
  99. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/events/bus.py +0 -0
  100. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/events/events.py +0 -0
  101. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/events/helpers.py +0 -0
  102. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/exceptions.py +0 -0
  103. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/history/__init__.py +0 -0
  104. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/history/base.py +0 -0
  105. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/history/models.py +0 -0
  106. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/history/reconstruction.py +0 -0
  107. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/history/registry.py +0 -0
  108. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/history/sqlite_backend.py +0 -0
  109. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/history/sqlite_conn.py +0 -0
  110. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/history/sqlite_schema.py +0 -0
  111. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/history/ui_events.py +0 -0
  112. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/hooks.py +0 -0
  113. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/interaction.py +0 -0
  114. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/md_agents.py +0 -0
  115. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/models.py +0 -0
  116. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/options.py +0 -0
  117. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/permissions.py +0 -0
  118. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/plugins.py +0 -0
  119. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/prompt_xml.py +0 -0
  120. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/providers/__init__.py +0 -0
  121. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/providers/base.py +0 -0
  122. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/providers/model_cache.py +0 -0
  123. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/providers/ollama.py +0 -0
  124. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/providers/openrouter.py +0 -0
  125. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/py.typed +0 -0
  126. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/renderer.py +0 -0
  127. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/schemas/__init__.py +0 -0
  128. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/schemas/agent.schema.json +0 -0
  129. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/secrets/__init__.py +0 -0
  130. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/secrets/backend.py +0 -0
  131. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/secrets/env.py +0 -0
  132. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/secrets/exec.py +0 -0
  133. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/secrets/file.py +0 -0
  134. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/secrets/masking.py +0 -0
  135. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/secrets/redaction.py +0 -0
  136. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/secrets/registry.py +0 -0
  137. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/secrets/sqlite.py +0 -0
  138. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/shell_tool_config.py +0 -0
  139. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/skill_discovery.py +0 -0
  140. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/templates/AGENTS.md +0 -0
  141. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/templates/IDENTITY.md +0 -0
  142. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/templates/MEMORY.md +0 -0
  143. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/templates/USER.md +0 -0
  144. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/templates/personas/casual-technical.md +0 -0
  145. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/templates/personas/marvin.md +0 -0
  146. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/templates/personas/minimal.md +0 -0
  147. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/agents.py +0 -0
  148. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/artifacts.py +0 -0
  149. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/daemon_control.py +0 -0
  150. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/fs.py +0 -0
  151. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/history.py +0 -0
  152. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/http.py +0 -0
  153. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/interactive.py +0 -0
  154. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/jobs.py +0 -0
  155. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/notify.py +0 -0
  156. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/schedule.py +0 -0
  157. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/secrets.py +0 -0
  158. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/sessions.py +0 -0
  159. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/shell.py +0 -0
  160. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/shell_tools.py +0 -0
  161. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/skills.py +0 -0
  162. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tools/time.py +0 -0
  163. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/tsugite.py +0 -0
  164. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/ui/__init__.py +0 -0
  165. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/ui/base.py +0 -0
  166. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/ui/chat.py +0 -0
  167. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/ui/dispatch.py +0 -0
  168. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/ui/helpers.py +0 -0
  169. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/ui/plain.py +0 -0
  170. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/ui/repl_chat.py +0 -0
  171. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/ui/repl_commands.py +0 -0
  172. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/ui/repl_completer.py +0 -0
  173. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/ui/repl_handler.py +0 -0
  174. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/ui_context.py +0 -0
  175. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/ui_surfaces.py +0 -0
  176. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/usage/__init__.py +0 -0
  177. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/usage/store.py +0 -0
  178. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/user_agent.py +0 -0
  179. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/workspace/__init__.py +0 -0
  180. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/workspace/models.py +0 -0
  181. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/workspace/session.py +0 -0
  182. {tsugite_cli-0.23.1 → tsugite_cli-0.23.2}/tsugite/workspace/templates.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tsugite-cli
3
- Version: 0.23.1
3
+ Version: 0.23.2
4
4
  Summary: Micro-agent runner for task automation using markdown definitions
5
5
  Author: Justyn Shull
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tsugite-cli"
3
- version = "0.23.1"
3
+ version = "0.23.2"
4
4
  description = "Micro-agent runner for task automation using markdown definitions"
5
5
  authors = [{ name = "Justyn Shull" }]
6
6
  requires-python = ">=3.11"
@@ -152,7 +152,8 @@ class AgentPreparer:
152
152
 
153
153
  A front-matter attachment defines its cache tier and replaces a same-named
154
154
  caller attachment. Legacy `-filename` string entries drop a same-named
155
- entry. Front-matter paths render with `paths` and resolve against its CWD.
155
+ entry. Front-matter paths render with `paths` and resolve against the
156
+ workspace, or the CWD when the run has no workspace.
156
157
  """
157
158
  all_attachments = list(attachments or [])
158
159
 
@@ -160,7 +161,8 @@ class AgentPreparer:
160
161
  if removals:
161
162
  all_attachments = [a for a in all_attachments if a.name not in removals]
162
163
 
163
- loaded, bindings = resolve_agent_config_attachments(keep_items, Path(paths["CWD"]), paths)
164
+ base = paths["WORKSPACE_DIR"] or paths["CWD"]
165
+ loaded, bindings = resolve_agent_config_attachments(keep_items, Path(base), paths)
164
166
  all_attachments = loaded + all_attachments
165
167
 
166
168
  seen_names: set[str] = set()
@@ -92,14 +92,11 @@ def _current_turn_already_has_user_input(storage: Session) -> bool:
92
92
  session_end after it; the post-hoc save_run_to_history on the error path
93
93
  would otherwise record a second, identical one (a duplicate user bubble).
94
94
  A genuinely repeated message opens a new turn *after* the prior session_end,
95
- so this stays False for it.
95
+ so this stays False for it. A final_result also ends a turn: compaction copies
96
+ retained turns into the successor without their session_end.
96
97
  """
97
- for event in reversed(list(storage.iter_events(types=("user_input", "session_end")))):
98
- if event.type == "session_end":
99
- return False
100
- if event.type == "user_input":
101
- return True
102
- return False
98
+ events = list(storage.iter_events(types=("user_input", "session_end", "final_result")))
99
+ return bool(events) and events[-1].type == "user_input"
103
100
 
104
101
 
105
102
  def record_user_input(
@@ -1281,6 +1281,7 @@ def preview_multistep_agent(
1281
1281
 
1282
1282
  # Extract steps
1283
1283
  from tsugite.md_agents import extract_step_directives, has_step_directives
1284
+ from tsugite.tools import format_tool_specs
1284
1285
 
1285
1286
  if not has_step_directives(agent.content):
1286
1287
  output("[yellow]This is a single-step agent (no step directives).[/yellow]", is_warning=True)
@@ -1302,7 +1303,7 @@ def preview_multistep_agent(
1302
1303
  output(f"Prompt: {prompt}")
1303
1304
  output(f"Steps: {len(steps)}")
1304
1305
  output(f"Model: {resolve_effective_model(agent_model=agent.config.model) or 'unknown'}")
1305
- output(f"Tools: {', '.join(agent.config.tools) if agent.config.tools else 'None'}")
1306
+ output(f"Tools: {format_tool_specs(agent.config.tools) if agent.config.tools else 'None'}")
1306
1307
  output("")
1307
1308
 
1308
1309
  # Show steps in table format
@@ -75,6 +75,7 @@ def agents_show(
75
75
  """
76
76
  from tsugite.agent_inheritance import find_agent_file
77
77
  from tsugite.md_agents import parse_agent_file
78
+ from tsugite.tools import format_tool_spec
78
79
 
79
80
  try:
80
81
  agent_file = Path(agent_path)
@@ -113,7 +114,7 @@ def agents_show(
113
114
  if config.tools:
114
115
  console.print(f"\n[bold]Tools ({len(config.tools)}):[/bold]")
115
116
  for tool in config.tools:
116
- console.print(f" • {tool}")
117
+ console.print(f" • {format_tool_spec(tool)}")
117
118
 
118
119
  if config.extends:
119
120
  console.print(f"\n[bold]Extends:[/bold] {config.extends}")
@@ -288,6 +288,7 @@ def run(
288
288
  from tsugite.console import get_stderr_console
289
289
  from tsugite.md_agents import validate_agent_execution
290
290
  from tsugite.secrets import init_cli as init_secrets
291
+ from tsugite.tools import format_tool_specs
291
292
  from tsugite.utils import should_use_plain_output
292
293
 
293
294
  from . import console
@@ -562,7 +563,7 @@ def run(
562
563
  "Directory": str(Path.cwd()),
563
564
  "Model": resolve_effective_model(exec_opts.model_override, agent_info.get("model_raw")) or "unknown",
564
565
  "Instructions": instruction_label,
565
- "Tools": ", ".join(agent_info.get("tools", [])),
566
+ "Tools": format_tool_specs(agent_info.get("tools", [])),
566
567
  }
567
568
 
568
569
  agent_attachments = agent_info.get("attachments")
@@ -98,6 +98,13 @@ def _jsonable_call_args(kwargs: Dict[str, Any]) -> Dict[str, Any]:
98
98
  return safe
99
99
 
100
100
 
101
+ NAME_ERROR_NOTE = (
102
+ " (each code block starts with a fresh namespace: names from earlier blocks and"
103
+ " <content> blocks do not carry over, so re-import or recompute them, or keep"
104
+ " values in state['key'])"
105
+ )
106
+
107
+
101
108
  def truncate_observation(text: str, max_output_kb: int = MAX_EXECUTION_OUTPUT_KB) -> tuple[str, bool]:
102
109
  """Clip text to the observation cap, returning (text, was_truncated).
103
110
 
@@ -134,6 +141,8 @@ def build_execution_result(
134
141
 
135
142
  children = [El("output", [output], inline=True)]
136
143
  if error:
144
+ if error.startswith("NameError:"):
145
+ error += NAME_ERROR_NOTE
137
146
  children.append(El("error", [error], inline=True))
138
147
  if traceback:
139
148
  children.append(El("traceback", [traceback], inline=True))
@@ -48,6 +48,7 @@ _ANTHROPIC_MODELS: dict[str, ModelInfo] = {
48
48
  "anthropic/claude-opus-4-7": ModelInfo(max_input_tokens=1_000_000, max_output_tokens=128_000, input_cost_per_million=5.0, output_cost_per_million=25.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high", "xhigh", "max"], thinking_style="adaptive"),
49
49
  "anthropic/claude-opus-4-8": ModelInfo(max_input_tokens=1_000_000, max_output_tokens=128_000, input_cost_per_million=5.0, output_cost_per_million=25.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high", "xhigh", "max"], thinking_style="adaptive"),
50
50
  "anthropic/claude-opus-5": ModelInfo(max_input_tokens=1_000_000, max_output_tokens=128_000, input_cost_per_million=5.0, output_cost_per_million=25.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high", "xhigh", "max"], thinking_style="adaptive"),
51
+ "anthropic/claude-opus-5-5": ModelInfo(max_input_tokens=1_000_000, max_output_tokens=128_000, input_cost_per_million=4.0, output_cost_per_million=20.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high", "xhigh", "max"], thinking_style="adaptive"),
51
52
  "anthropic/claude-sonnet-4-5": ModelInfo(max_input_tokens=1_000_000, max_output_tokens=64_000, input_cost_per_million=3.0, output_cost_per_million=15.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high", "max"], thinking_style="budget_tokens"),
52
53
  "anthropic/claude-sonnet-4-5-20250929": ModelInfo(max_input_tokens=1_000_000, max_output_tokens=64_000, input_cost_per_million=3.0, output_cost_per_million=15.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high", "max"], thinking_style="budget_tokens"),
53
54
  "anthropic/claude-sonnet-4-6": ModelInfo(max_input_tokens=1_000_000, max_output_tokens=128_000, input_cost_per_million=3.0, output_cost_per_million=15.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high", "max"], thinking_style="budget_tokens"),
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
6
+
5
7
  from .base import ModelInfo, Usage
6
8
 
7
9
  _REGISTRY: dict[str, ModelInfo] = {}
@@ -22,11 +24,13 @@ def get_model_info(provider: str, model: str) -> ModelInfo | None:
22
24
  for reg_key, info in _REGISTRY.items():
23
25
  if not key.startswith(reg_key) or len(reg_key) <= best_len:
24
26
  continue
25
- # Only treat the prefix as the same model when the remainder is a date/version
26
- # continuation (e.g. "-20250805"), not a distinct variant ("-mini", "-turbo")
27
- # — otherwise an unlisted variant silently inherits a sibling's pricing.
27
+ # Only treat the prefix as the same model when the remainder is a dated
28
+ # continuation (e.g. "-20250805", "-2024-05-13"), not a distinct variant
29
+ # ("-mini", "-turbo") or a point release ("-5"). An unlisted variant
30
+ # would otherwise silently inherit a sibling's pricing. A year is 4+
31
+ # digits, so require the digit run right after the dash to be that long.
28
32
  suffix = key[len(reg_key) :]
29
- if suffix and not (suffix.startswith("-") and len(suffix) > 1 and suffix[1].isdigit()):
33
+ if suffix and not re.match(r"^-\d{4}", suffix):
30
34
  continue
31
35
  best_match = info
32
36
  best_len = len(reg_key)
@@ -55,6 +55,8 @@ _OPENAI_MODELS: dict[str, ModelInfo] = {
55
55
  "openai/gpt-5.6-sol": ModelInfo(max_input_tokens=1_050_000, max_output_tokens=128_000, input_cost_per_million=4.0, output_cost_per_million=20.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["none", "low", "medium", "high", "xhigh", "max"]),
56
56
  "openai/gpt-5.6-terra": ModelInfo(max_input_tokens=1_050_000, max_output_tokens=128_000, input_cost_per_million=2.0, output_cost_per_million=12.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["none", "low", "medium", "high", "xhigh", "max"]),
57
57
  "openai/gpt-6-astra": ModelInfo(max_input_tokens=1_050_000, max_output_tokens=128_000, input_cost_per_million=10.0, output_cost_per_million=50.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high", "xhigh", "max"]),
58
+ "openai/gpt-6-luna": ModelInfo(max_input_tokens=1_050_000, max_output_tokens=128_000, input_cost_per_million=0.1, output_cost_per_million=0.5, supports_vision=True, supports_reasoning=True, supported_effort_levels=["none", "low", "medium", "high", "xhigh", "max"]),
59
+ "openai/gpt-6-sol": ModelInfo(max_input_tokens=1_050_000, max_output_tokens=128_000, input_cost_per_million=2.0, output_cost_per_million=10.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["none", "low", "medium", "high", "xhigh", "max"]),
58
60
  "openai/o1": ModelInfo(max_input_tokens=200_000, max_output_tokens=100_000, input_cost_per_million=15.0, output_cost_per_million=60.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high"]),
59
61
  "openai/o1-pro": ModelInfo(max_input_tokens=200_000, max_output_tokens=100_000, input_cost_per_million=150.0, output_cost_per_million=600.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high"]),
60
62
  "openai/o3": ModelInfo(max_input_tokens=200_000, max_output_tokens=100_000, input_cost_per_million=2.0, output_cost_per_million=8.0, supports_vision=True, supports_reasoning=True, supported_effort_levels=["low", "medium", "high"]),
@@ -376,6 +376,21 @@ def expand_tool_specs(tool_specs: List[str], strict: bool = False) -> List[str]:
376
376
  return result
377
377
 
378
378
 
379
+ def format_tool_spec(spec: str) -> str:
380
+ """Render one frontmatter tool spec for console output, with `@category` as `name (toolset)`.
381
+
382
+ An agent that shells out to tsugite reads this through its own tool layer, where `@name`
383
+ expands as a file mention if it matches a directory.
384
+ """
385
+ prefix, name = ("-", spec[1:]) if spec.startswith("-") else ("", spec)
386
+ return f"{prefix}{name[1:]} (toolset)" if name.startswith("@") else spec
387
+
388
+
389
+ def format_tool_specs(tool_specs: List[str]) -> str:
390
+ """Render a frontmatter tool list as one comma-separated line."""
391
+ return ", ".join(format_tool_spec(spec) for spec in tool_specs)
392
+
393
+
379
394
  def load_custom_shell_tools() -> None:
380
395
  """Register the shell tools defined in custom_tools.yaml.
381
396
 
@@ -1,7 +1,7 @@
1
1
  """JSONL UI handler for subprocess-based subagent communication."""
2
2
 
3
3
  import json
4
- from typing import Any, Dict
4
+ from typing import Any, Dict, Optional
5
5
 
6
6
  from tsugite.events import (
7
7
  CodeExecutionEvent,
@@ -32,6 +32,7 @@ from tsugite.events import (
32
32
  ToolResultEvent,
33
33
  WarningEvent,
34
34
  )
35
+ from tsugite.events.base import BaseEvent
35
36
  from tsugite.ui.dispatch import EventDispatchMixin, handles
36
37
 
37
38
  # Event types that share the same file-io payload shape
@@ -73,6 +74,17 @@ class JSONLUIHandler(EventDispatchMixin):
73
74
  - Failed tool: {"type": "tool_result", "tool": "read_file", "success": false, "error": "..."}
74
75
  """
75
76
 
77
+ final_seen = False
78
+ latest_prompt_messages: Optional[list] = None
79
+
80
+ def handle_event(self, event: BaseEvent) -> None:
81
+ if isinstance(event, PromptSnapshotEvent):
82
+ if event.messages:
83
+ self.latest_prompt_messages = event.messages
84
+ if not event.token_breakdown:
85
+ return
86
+ super().handle_event(event)
87
+
76
88
  @handles(TaskStartEvent)
77
89
  def _handle_task_start(self, event: TaskStartEvent) -> None:
78
90
  self._emit("init", {"agent": event.task, "model": event.model})
@@ -132,6 +144,7 @@ class JSONLUIHandler(EventDispatchMixin):
132
144
 
133
145
  @handles(FinalAnswerEvent)
134
146
  def _handle_final_answer(self, event: FinalAnswerEvent) -> None:
147
+ self.final_seen = True
135
148
  self._emit(
136
149
  "final_result",
137
150
  {
@@ -3,9 +3,8 @@ subscription or budget the user has consumed ("week 73%, resets Aug 19").
3
3
 
4
4
  A source is contributed by a plugin through the ``tsugite.usage_providers``
5
5
  entry point (a module-only entry point whose import registers the source) and
6
- fetches from whatever local state that provider keeps. The shape has no severity
7
- field: the UI derives its own warn threshold, and a second source of truth would
8
- let the two disagree.
6
+ fetches from that provider's local state or API. The shape has no severity
7
+ field because the UI derives its own warn threshold.
9
8
  """
10
9
 
11
10
  from __future__ import annotations
@@ -31,8 +30,8 @@ class QuotaWindow:
31
30
 
32
31
  @dataclass(frozen=True)
33
32
  class QuotaReport:
34
- """One provider's quota. ``error`` is set instead of windows when local
35
- state was unreadable."""
33
+ """One provider's quota. ``error`` is set instead of windows when the
34
+ source could not be read."""
36
35
 
37
36
  provider: str
38
37
  label: str
@@ -69,10 +68,9 @@ def reset_quota_sources() -> None:
69
68
 
70
69
 
71
70
  def ensure_loaded() -> None:
72
- """Import the ``tsugite.usage_providers`` entry-point modules once, so every
73
- ``register_quota_source`` call has run before the registry is read. The
74
- daemon collects from worker threads, so a reader racing the first load
75
- waits for it rather than seeing an empty registry."""
71
+ """Import the ``tsugite.usage_providers`` entry-point modules once, before
72
+ the registry is read. A daemon worker thread racing the first load waits
73
+ for it."""
76
74
  global _loaded
77
75
  if _loaded:
78
76
  return
@@ -102,7 +100,7 @@ def collect_quota_reports() -> list[dict]:
102
100
  logger.warning("Quota source '%s' failed: %s", source.provider, e)
103
101
  report = QuotaReport(provider=source.provider, label=source.label, error=str(e))
104
102
  row = asdict(report)
105
- # asdict keeps the dataclass's tuple; the wire shape is an array.
103
+ # asdict keeps windows as a tuple and the wire shape is an array.
106
104
  row["windows"] = list(row["windows"])
107
105
  rows.append(row)
108
106
  return rows
@@ -128,6 +128,7 @@ def execute_shell_command(
128
128
  shell=shell,
129
129
  capture_output=True,
130
130
  text=True,
131
+ errors="replace",
131
132
  timeout=timeout,
132
133
  check=False,
133
134
  cwd=resolved_cwd,
File without changes
File without changes
File without changes
File without changes