tsugite-cli 0.22.0__tar.gz → 0.22.1__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.22.0 → tsugite_cli-0.22.1}/PKG-INFO +1 -1
  2. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/pyproject.toml +1 -1
  3. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/agent_preparation.py +3 -2
  4. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/agent_runner/history_integration.py +7 -1
  5. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/attachments/base.py +2 -4
  6. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_agents/default.md +2 -2
  7. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/run.py +17 -2
  8. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/core/proxy.py +8 -0
  9. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/md_agents.py +2 -2
  10. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/agents.py +26 -12
  11. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/jobs.py +5 -3
  12. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/.gitignore +0 -0
  13. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/AGENTS.md +0 -0
  14. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/CONTRIBUTING.md +0 -0
  15. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/LICENSE +0 -0
  16. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/README.md +0 -0
  17. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/__init__.py +0 -0
  18. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/agent_inheritance.py +0 -0
  19. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/agent_runner/__init__.py +0 -0
  20. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/agent_runner/exec_directives.py +0 -0
  21. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/agent_runner/exec_runner.py +0 -0
  22. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/agent_runner/helpers.py +0 -0
  23. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/agent_runner/metrics.py +0 -0
  24. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/agent_runner/models.py +0 -0
  25. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/agent_runner/runner.py +0 -0
  26. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/agent_runner/steps.py +0 -0
  27. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/agent_runner/validation.py +0 -0
  28. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/agent_utils.py +0 -0
  29. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/approval.py +0 -0
  30. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/attachments/__init__.py +0 -0
  31. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/attachments/agent_config.py +0 -0
  32. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/attachments/auto_context.py +0 -0
  33. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/attachments/delegation.py +0 -0
  34. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/attachments/file.py +0 -0
  35. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/attachments/inline.py +0 -0
  36. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/attachments/storage.py +0 -0
  37. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/attachments/url.py +0 -0
  38. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_agents/.gitkeep +0 -0
  39. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_agents/code_searcher.md +0 -0
  40. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_agents/file_searcher.md +0 -0
  41. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_agents/job_verifier.md +0 -0
  42. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_agents/job_worker.md +0 -0
  43. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_agents/onboard.md +0 -0
  44. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_context.py +0 -0
  45. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_skills/.gitkeep +0 -0
  46. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_skills/codebase-exploration/SKILL.md +0 -0
  47. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_skills/external-agent-bridge/SKILL.md +0 -0
  48. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_skills/python-math/SKILL.md +0 -0
  49. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_skills/response-patterns/SKILL.md +0 -0
  50. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_skills/scheduling/SKILL.md +0 -0
  51. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_skills/skill-authoring/SKILL.md +0 -0
  52. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_skills/tsugite-agent-basics/SKILL.md +0 -0
  53. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_skills/tsugite-jinja-reference/SKILL.md +0 -0
  54. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/builtin_skills/tsugite-skill-basics/SKILL.md +0 -0
  55. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cache.py +0 -0
  56. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cancellation.py +0 -0
  57. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/__init__.py +0 -0
  58. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/agents.py +0 -0
  59. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/attachments.py +0 -0
  60. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/cache.py +0 -0
  61. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/chat.py +0 -0
  62. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/config.py +0 -0
  63. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/daemon.py +0 -0
  64. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/exec.py +0 -0
  65. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/helpers.py +0 -0
  66. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/history.py +0 -0
  67. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/init.py +0 -0
  68. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/models.py +0 -0
  69. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/plugins.py +0 -0
  70. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/render.py +0 -0
  71. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/secrets.py +0 -0
  72. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/skills.py +0 -0
  73. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/tools.py +0 -0
  74. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/usage.py +0 -0
  75. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/validate.py +0 -0
  76. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/cli/workspace.py +0 -0
  77. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/config.py +0 -0
  78. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/console.py +0 -0
  79. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/constants.py +0 -0
  80. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/context.py +0 -0
  81. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/context_block.py +0 -0
  82. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/core/__init__.py +0 -0
  83. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/core/agent.py +0 -0
  84. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/core/content_blocks.py +0 -0
  85. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/core/executor.py +0 -0
  86. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/core/executor_registry.py +0 -0
  87. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/core/memory.py +0 -0
  88. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/core/record_store.py +0 -0
  89. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/core/sandbox.py +0 -0
  90. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/core/state.py +0 -0
  91. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/core/subprocess_executor.py +0 -0
  92. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/core/tools.py +0 -0
  93. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/events/__init__.py +0 -0
  94. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/events/base.py +0 -0
  95. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/events/bus.py +0 -0
  96. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/events/events.py +0 -0
  97. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/events/helpers.py +0 -0
  98. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/exceptions.py +0 -0
  99. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/history/__init__.py +0 -0
  100. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/history/base.py +0 -0
  101. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/history/models.py +0 -0
  102. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/history/reconstruction.py +0 -0
  103. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/history/registry.py +0 -0
  104. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/history/sqlite_backend.py +0 -0
  105. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/history/sqlite_conn.py +0 -0
  106. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/history/sqlite_schema.py +0 -0
  107. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/history/ui_events.py +0 -0
  108. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/hooks.py +0 -0
  109. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/interaction.py +0 -0
  110. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/models.py +0 -0
  111. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/options.py +0 -0
  112. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/permissions.py +0 -0
  113. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/plugins.py +0 -0
  114. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/prompt_xml.py +0 -0
  115. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/providers/__init__.py +0 -0
  116. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/providers/anthropic.py +0 -0
  117. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/providers/base.py +0 -0
  118. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/providers/model_cache.py +0 -0
  119. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/providers/model_registry.py +0 -0
  120. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/providers/ollama.py +0 -0
  121. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/providers/openai_compat.py +0 -0
  122. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/providers/openrouter.py +0 -0
  123. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/py.typed +0 -0
  124. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/renderer.py +0 -0
  125. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/schemas/__init__.py +0 -0
  126. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/schemas/agent.schema.json +0 -0
  127. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/secrets/__init__.py +0 -0
  128. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/secrets/backend.py +0 -0
  129. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/secrets/env.py +0 -0
  130. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/secrets/exec.py +0 -0
  131. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/secrets/file.py +0 -0
  132. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/secrets/masking.py +0 -0
  133. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/secrets/redaction.py +0 -0
  134. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/secrets/registry.py +0 -0
  135. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/secrets/sqlite.py +0 -0
  136. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/shell_tool_config.py +0 -0
  137. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/skill_discovery.py +0 -0
  138. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/templates/AGENTS.md +0 -0
  139. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/templates/IDENTITY.md +0 -0
  140. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/templates/MEMORY.md +0 -0
  141. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/templates/USER.md +0 -0
  142. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/templates/personas/casual-technical.md +0 -0
  143. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/templates/personas/marvin.md +0 -0
  144. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/templates/personas/minimal.md +0 -0
  145. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/__init__.py +0 -0
  146. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/artifacts.py +0 -0
  147. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/daemon_control.py +0 -0
  148. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/fs.py +0 -0
  149. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/history.py +0 -0
  150. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/http.py +0 -0
  151. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/interactive.py +0 -0
  152. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/notify.py +0 -0
  153. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/schedule.py +0 -0
  154. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/secrets.py +0 -0
  155. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/sessions.py +0 -0
  156. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/shell.py +0 -0
  157. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/shell_tools.py +0 -0
  158. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/skills.py +0 -0
  159. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tools/time.py +0 -0
  160. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/tsugite.py +0 -0
  161. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/ui/__init__.py +0 -0
  162. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/ui/base.py +0 -0
  163. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/ui/chat.py +0 -0
  164. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/ui/dispatch.py +0 -0
  165. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/ui/helpers.py +0 -0
  166. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/ui/jsonl.py +0 -0
  167. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/ui/plain.py +0 -0
  168. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/ui/repl_chat.py +0 -0
  169. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/ui/repl_commands.py +0 -0
  170. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/ui/repl_completer.py +0 -0
  171. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/ui/repl_handler.py +0 -0
  172. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/ui_context.py +0 -0
  173. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/ui_surfaces.py +0 -0
  174. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/usage/__init__.py +0 -0
  175. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/usage/quota.py +0 -0
  176. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/usage/store.py +0 -0
  177. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/user_agent.py +0 -0
  178. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/utils.py +0 -0
  179. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/workspace/__init__.py +0 -0
  180. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/workspace/models.py +0 -0
  181. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/workspace/session.py +0 -0
  182. {tsugite_cli-0.22.0 → tsugite_cli-0.22.1}/tsugite/workspace/templates.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tsugite-cli
3
- Version: 0.22.0
3
+ Version: 0.22.1
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.22.0"
3
+ version = "0.22.1"
4
4
  description = "Micro-agent runner for task automation using markdown definitions"
5
5
  authors = [{ name = "Justyn Shull" }]
6
6
  requires-python = ">=3.11"
@@ -119,8 +119,9 @@ class AgentPreparer:
119
119
  placeholders: Dict[str, str] = {}
120
120
  modified = content
121
121
  for d in directives:
122
- if d.assign_var:
123
- placeholders[d.assign_var] = f"[{kind} directive: {d.name}(...) - not executed in render mode]"
122
+ for var in (d.assign_var, getattr(d, "stdout_assign", None)):
123
+ if var:
124
+ placeholders[var] = f"[{kind} directive: {d.name}(...) - not executed in render mode]"
124
125
  if rewrite_to is not None:
125
126
  modified = modified.replace(d.raw_match, rewrite_to(d))
126
127
  return modified, placeholders
@@ -231,11 +231,14 @@ def save_run_to_history(
231
231
  # agent ran to completion against this storage.
232
232
  existing = list(storage.iter_events())
233
233
  last_session_start = -1
234
+ last_user_input = -1
234
235
  last_model_response = -1
235
236
  last_session_end = -1
236
237
  for i, e in enumerate(existing):
237
238
  if e.type == "session_start":
238
239
  last_session_start = i
240
+ elif e.type == "user_input":
241
+ last_user_input = i
239
242
  elif e.type == "model_response":
240
243
  last_model_response = i
241
244
  elif e.type == "session_end":
@@ -244,7 +247,10 @@ def save_run_to_history(
244
247
  agent_already_recorded = last_model_response > last_session_start
245
248
 
246
249
  if agent_already_recorded:
247
- if last_session_end <= last_session_start:
250
+ # A continued conversation pins session_start at index 0, so the current
251
+ # turn starts at its own user_input.
252
+ turn_start = max(last_session_start, last_user_input)
253
+ if last_session_end < turn_start:
248
254
  record_session_end(storage, status=status, error_message=error_message)
249
255
  return storage.session_id
250
256
 
@@ -68,8 +68,7 @@ class Attachment:
68
68
 
69
69
  ``key`` is the stable id used for dedupe and the UI chip; ``label`` is the
70
70
  human name the model and UI show; ``value`` is the text. Fills the text
71
- content-type so context producers don't repeat it. This is the shape the
72
- old ``ContextItem`` carried, now just an Attachment.
71
+ content-type so context producers don't repeat it.
73
72
  """
74
73
  return cls(
75
74
  name=label,
@@ -88,8 +87,7 @@ class Attachment:
88
87
 
89
88
  @property
90
89
  def value(self) -> str:
91
- """The text content (an alias of ``content`` when it is text, else "").
92
- Lets context code read ``.value`` the way the old ``ContextItem`` did."""
90
+ """The text content (an alias of ``content`` when it is text, else "")."""
93
91
  return self.content if isinstance(self.content, str) else ""
94
92
 
95
93
  def to_metadata(self) -> dict:
@@ -157,12 +157,12 @@ You are managing a shared channel. When a user asks for something that would ben
157
157
  - **Inline** - single edit, one-shot read, small calculation. Don't spawn anything.
158
158
  - **`spawn_job`** - background work: long-running tasks, parallel work, or delegating to a different agent/model. Fire-and-forget by default; add `acceptance_criteria=[...]` when "done" has a checkable shape (tests pass, copy satisfies criteria, refactor preserves behavior) and the verification loop becomes the value.
159
159
 
160
- **Acceptance criteria**: pass each criterion as a plain string. The verifier reads them verbatim and returns per-criterion pass/fail in `result.ac_results`:
160
+ **Acceptance criteria**: pass each criterion as a plain string. A criterion starting with `cmd:<command>` (passes on exit 0), `exit_code:<command>:<n>` (passes on exit code `n`, default 0) or `file_exists:<path>` is checked in the Job's working directory with no verifier model. Write one whenever the check is mechanical. The verifier reads the rest verbatim and returns per-criterion pass/fail in `result.ac_results`:
161
161
  ```python-exec
162
162
  spawn_job(
163
163
  prompt="Refactor the agent loop to handle nested tool calls",
164
164
  acceptance_criteria=[
165
- "All existing tests still pass",
165
+ "cmd:uv run pytest -q",
166
166
  "Adds at least 2 new tests for nested calls",
167
167
  "No new dependencies",
168
168
  ],
@@ -1,6 +1,7 @@
1
1
  """CLI run command - execute agents."""
2
2
 
3
3
  from pathlib import Path
4
+ from types import SimpleNamespace
4
5
  from typing import Any, Dict, List, Optional, Tuple
5
6
 
6
7
  import typer
@@ -137,11 +138,18 @@ def _execute_agent_with_ui(
137
138
  """Execute agent with appropriate UI mode.
138
139
 
139
140
  Dispatch order:
140
- 1. --headless / --final-only -> custom_agent_ui with silent progress
141
- 2. fall-through default -> plain logger (covers no flags, --plain, piped/NO_COLOR)
141
+ 1. --log-json -> JSONL event stream on stdout
142
+ 2. --headless / --final-only -> custom_agent_ui with silent progress
143
+ 3. fall-through default -> plain logger (covers no flags, --plain, piped/NO_COLOR)
142
144
  """
143
145
  from tsugite.ui import create_plain_logger, custom_agent_ui
144
146
 
147
+ if ui_opts.log_json:
148
+ from tsugite.ui.jsonl import JSONLUIHandler
149
+
150
+ executor_kwargs["custom_logger"] = SimpleNamespace(ui_handler=JSONLUIHandler())
151
+ return executor(**executor_kwargs)
152
+
145
153
  if ui_opts.headless or ui_opts.final_only:
146
154
  stderr_console = get_error_console(True, console)
147
155
  show_progress_items = ui_opts.verbose and not ui_opts.final_only
@@ -188,6 +196,9 @@ def _display_result(result_str: str, ui_opts: UIOptions, stderr_console: Console
188
196
 
189
197
  from tsugite.console import get_stdout_console
190
198
 
199
+ if ui_opts.log_json:
200
+ return
201
+
191
202
  if not (ui_opts.headless or ui_opts.final_only):
192
203
  stderr_console.print()
193
204
  stderr_console.rule("[bold green]Agent Execution Complete[/bold green]")
@@ -358,6 +369,10 @@ def run(
358
369
 
359
370
  if history_opts.storage_dir:
360
371
  history_opts.storage_dir.mkdir(parents=True, exist_ok=True)
372
+ from tsugite.history import set_history_backend
373
+ from tsugite.history.sqlite_backend import SqliteHistoryBackend
374
+
375
+ set_history_backend(SqliteHistoryBackend(history_opts.storage_dir / "history.db"))
361
376
 
362
377
  if ui_opts.no_color:
363
378
  console.no_color = True
@@ -8,6 +8,7 @@ import asyncio
8
8
  import fnmatch
9
9
  import ipaddress
10
10
  import logging
11
+ import socket
11
12
  from pathlib import Path
12
13
  from typing import Optional
13
14
 
@@ -46,6 +47,13 @@ def _is_ip_address(host: str) -> bool:
46
47
  ipaddress.ip_address(host)
47
48
  return True
48
49
  except ValueError:
50
+ pass
51
+ # inet_aton accepts the integer, octal and short IPv4 forms that
52
+ # ip_address rejects and getaddrinfo resolves, and rejects hostnames.
53
+ try:
54
+ socket.inet_aton(host)
55
+ return True
56
+ except OSError:
49
57
  return False
50
58
 
51
59
 
@@ -305,7 +305,7 @@ def extract_tool_directives(content: str) -> List[ToolDirective]:
305
305
  {'url': 'http://example.com'}
306
306
  """
307
307
  directives = []
308
- pattern = r"<!--\s*tsu:tool\s+([^>]+?)\s*-->"
308
+ pattern = r"<!--\s*tsu:tool\s+(.+?)\s*-->"
309
309
 
310
310
  for match in re.finditer(pattern, content):
311
311
  raw_args = match.group(1).strip()
@@ -478,7 +478,7 @@ def extract_step_directives(content: str, include_preamble: bool = True) -> tupl
478
478
  True
479
479
  """
480
480
  steps = []
481
- pattern = r"<!--\s*tsu:step\s+([^>]+?)\s*-->"
481
+ pattern = r"<!--\s*tsu:step\s+(.+?)\s*-->"
482
482
  matches = list(re.finditer(pattern, content))
483
483
 
484
484
  # Extract preamble (content before first step)
@@ -1,5 +1,7 @@
1
1
  """Agent orchestration tools for spawning and managing sub-agents."""
2
2
 
3
+ import os
4
+ import signal
3
5
  from pathlib import Path
4
6
  from typing import Any, Dict, List, Optional
5
7
 
@@ -31,6 +33,13 @@ def _build_subagent_cmd(agent_file: Path, model_override: Optional[str], sandbox
31
33
  return cmd
32
34
 
33
35
 
36
+ def _kill_process_tree(proc) -> None:
37
+ try:
38
+ os.killpg(proc.pid, signal.SIGKILL)
39
+ except ProcessLookupError:
40
+ pass
41
+
42
+
34
43
  def resolve_agent_path(agent_path: str) -> Optional[Path]:
35
44
  """Resolve an agent reference (path or name) to a file. Returns None if missing.
36
45
 
@@ -187,7 +196,6 @@ def spawn_agent(
187
196
  emit_info_event(f"🚀 Spawning subagent: [cyan]{agent_name}[/cyan]...")
188
197
 
189
198
  try:
190
- # Spawn subprocess with line buffering
191
199
  proc = subprocess.Popen(
192
200
  cmd,
193
201
  stdin=subprocess.PIPE,
@@ -196,18 +204,16 @@ def spawn_agent(
196
204
  text=True,
197
205
  bufsize=1, # Line buffered for real-time output
198
206
  cwd=str(_effective_cwd()),
207
+ start_new_session=True,
199
208
  )
200
209
 
201
- # Write context to stdin then close it
202
210
  proc.stdin.write(context_json)
203
211
  proc.stdin.close()
204
212
 
205
- # Queue for passing lines from reader thread to main thread
206
213
  line_queue = queue.Queue()
207
214
  reader_exception = None
208
215
 
209
216
  def read_stdout():
210
- """Read stdout lines in separate thread and put in queue."""
211
217
  nonlocal reader_exception
212
218
  try:
213
219
  for line in proc.stdout:
@@ -217,11 +223,19 @@ def spawn_agent(
217
223
  reader_exception = e
218
224
  line_queue.put(None)
219
225
 
220
- # Start reader thread
221
226
  reader_thread = threading.Thread(target=read_stdout, daemon=True)
222
227
  reader_thread.start()
223
228
 
224
- # Read JSONL stream and collect events
229
+ # A child that fills the stderr pipe blocks on write(2).
230
+ stderr_text = ""
231
+
232
+ def read_stderr():
233
+ nonlocal stderr_text
234
+ stderr_text = proc.stderr.read()
235
+
236
+ stderr_thread = threading.Thread(target=read_stderr, daemon=True)
237
+ stderr_thread.start()
238
+
225
239
  final_result = None
226
240
  errors = []
227
241
  # The child signals EOF by closing stdout, so one that hangs without
@@ -231,7 +245,7 @@ def spawn_agent(
231
245
 
232
246
  while True:
233
247
  if time.monotonic() >= deadline:
234
- proc.kill()
248
+ _kill_process_tree(proc)
235
249
  raise RuntimeError(f"Subagent timed out after {timeout}s")
236
250
  # Try to get line from queue with timeout for periodic updates
237
251
  try:
@@ -280,17 +294,17 @@ def spawn_agent(
280
294
 
281
295
  # Wait for completion
282
296
  try:
283
- return_code = proc.wait(timeout=timeout)
297
+ return_code = proc.wait(timeout=max(0.0, deadline - time.monotonic()))
284
298
  except subprocess.TimeoutExpired:
285
- proc.kill()
299
+ _kill_process_tree(proc)
286
300
  raise RuntimeError(f"Subagent timed out after {timeout}s")
287
301
 
288
302
  # Check for failures
289
303
  if return_code != 0:
290
- stderr = proc.stderr.read()
304
+ stderr_thread.join(timeout=1.0)
291
305
  error_msg = f"Subagent failed with exit code {return_code}"
292
- if stderr:
293
- error_msg += f": {stderr}"
306
+ if stderr_text:
307
+ error_msg += f": {stderr_text}"
294
308
  if errors:
295
309
  error_msg += f"\nErrors: {[e['error'] for e in errors]}"
296
310
  raise RuntimeError(error_msg)
@@ -57,9 +57,11 @@ def spawn_job(
57
57
 
58
58
  Args:
59
59
  prompt: Task instruction for the spawned Job.
60
- acceptance_criteria: List of criterion strings the verifier grades
61
- against. Empty list short-circuits verification (Job goes straight
62
- to done).
60
+ acceptance_criteria: List of criterion strings. A criterion starting
61
+ with cmd:<command>, exit_code:<command>:<n> or file_exists:<path>
62
+ is checked by exit status in the Job's working directory and the
63
+ verifier grades the rest. Empty list short-circuits verification
64
+ (Job goes straight to done).
63
65
  repo: Repo path (workspace-relative or absolute). The worker runs in a
64
66
  fresh git worktree provisioned under `<repo>/.tsugite-jobs/<job_id>`,
65
67
  isolated from the parent checkout; it's pruned on done/cancelled and
File without changes
File without changes
File without changes
File without changes