tsugite-cli 0.17.2__tar.gz → 0.18.0__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 (170) hide show
  1. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/AGENTS.md +2 -2
  2. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/PKG-INFO +1 -1
  3. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/pyproject.toml +3 -1
  4. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/history_integration.py +30 -0
  5. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/runner.py +10 -8
  6. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/code_searcher.md +1 -1
  7. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/default.md +29 -27
  8. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/file_searcher.md +11 -11
  9. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/job_verifier.md +2 -2
  10. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/codebase-exploration/SKILL.md +57 -57
  11. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/python-math/SKILL.md +10 -10
  12. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/response-patterns/SKILL.md +6 -6
  13. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/scheduling/SKILL.md +10 -10
  14. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/skill-authoring/SKILL.md +1 -1
  15. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/tsugite-agent-basics/SKILL.md +1 -1
  16. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/tsugite-skill-basics/SKILL.md +7 -7
  17. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/agent.py +105 -35
  18. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/subprocess_executor.py +66 -23
  19. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/reconstruction.py +30 -4
  20. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/hooks.py +20 -35
  21. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/anthropic.py +3 -13
  22. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/openai_compat.py +4 -0
  23. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/jobs.py +70 -1
  24. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/schedule.py +19 -6
  25. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/.gitignore +0 -0
  26. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/CONTRIBUTING.md +0 -0
  27. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/LICENSE +0 -0
  28. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/README.md +0 -0
  29. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/__init__.py +0 -0
  30. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_inheritance.py +0 -0
  31. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_preparation.py +0 -0
  32. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/__init__.py +0 -0
  33. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/exec_directives.py +0 -0
  34. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/exec_runner.py +0 -0
  35. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/helpers.py +0 -0
  36. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/metrics.py +0 -0
  37. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/models.py +0 -0
  38. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/validation.py +0 -0
  39. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_utils.py +0 -0
  40. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/__init__.py +0 -0
  41. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/auto_context.py +0 -0
  42. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/base.py +0 -0
  43. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/file.py +0 -0
  44. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/inline.py +0 -0
  45. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/storage.py +0 -0
  46. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/url.py +0 -0
  47. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/.gitkeep +0 -0
  48. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/job_worker.md +0 -0
  49. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/onboard.md +0 -0
  50. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/.gitkeep +0 -0
  51. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/external-agent-bridge/SKILL.md +0 -0
  52. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/tsugite-jinja-reference/SKILL.md +0 -0
  53. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cache.py +0 -0
  54. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cancellation.py +0 -0
  55. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/__init__.py +0 -0
  56. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/agents.py +0 -0
  57. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/attachments.py +0 -0
  58. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/cache.py +0 -0
  59. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/chat.py +0 -0
  60. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/config.py +0 -0
  61. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/daemon.py +0 -0
  62. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/exec.py +0 -0
  63. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/helpers.py +0 -0
  64. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/history.py +0 -0
  65. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/init.py +0 -0
  66. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/models.py +0 -0
  67. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/plugins.py +0 -0
  68. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/render.py +0 -0
  69. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/run.py +0 -0
  70. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/secrets.py +0 -0
  71. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/skills.py +0 -0
  72. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/tools.py +0 -0
  73. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/usage.py +0 -0
  74. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/validate.py +0 -0
  75. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/workspace.py +0 -0
  76. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/config.py +0 -0
  77. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/console.py +0 -0
  78. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/constants.py +0 -0
  79. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/__init__.py +0 -0
  80. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/content_blocks.py +0 -0
  81. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/executor.py +0 -0
  82. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/executor_registry.py +0 -0
  83. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/memory.py +0 -0
  84. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/proxy.py +0 -0
  85. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/record_store.py +0 -0
  86. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/sandbox.py +0 -0
  87. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/state.py +0 -0
  88. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/tools.py +0 -0
  89. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/events/__init__.py +0 -0
  90. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/events/base.py +0 -0
  91. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/events/bus.py +0 -0
  92. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/events/events.py +0 -0
  93. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/events/helpers.py +0 -0
  94. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/exceptions.py +0 -0
  95. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/__init__.py +0 -0
  96. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/base.py +0 -0
  97. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/models.py +0 -0
  98. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/registry.py +0 -0
  99. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/sqlite_backend.py +0 -0
  100. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/sqlite_conn.py +0 -0
  101. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/sqlite_schema.py +0 -0
  102. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/storage.py +0 -0
  103. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/ui_events.py +0 -0
  104. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/interaction.py +0 -0
  105. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/md_agents.py +0 -0
  106. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/models.py +0 -0
  107. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/options.py +0 -0
  108. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/plugins.py +0 -0
  109. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/__init__.py +0 -0
  110. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/base.py +0 -0
  111. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/model_cache.py +0 -0
  112. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/model_registry.py +0 -0
  113. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/ollama.py +0 -0
  114. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/openrouter.py +0 -0
  115. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/py.typed +0 -0
  116. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/renderer.py +0 -0
  117. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/schemas/__init__.py +0 -0
  118. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/schemas/agent.schema.json +0 -0
  119. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/__init__.py +0 -0
  120. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/backend.py +0 -0
  121. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/env.py +0 -0
  122. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/exec.py +0 -0
  123. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/file.py +0 -0
  124. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/masking.py +0 -0
  125. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/registry.py +0 -0
  126. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/sqlite.py +0 -0
  127. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/shell_tool_config.py +0 -0
  128. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/skill_discovery.py +0 -0
  129. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/AGENTS.md +0 -0
  130. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/IDENTITY.md +0 -0
  131. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/MEMORY.md +0 -0
  132. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/USER.md +0 -0
  133. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/personas/casual-technical.md +0 -0
  134. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/personas/marvin.md +0 -0
  135. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/personas/minimal.md +0 -0
  136. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/__init__.py +0 -0
  137. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/agents.py +0 -0
  138. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/fs.py +0 -0
  139. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/history.py +0 -0
  140. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/http.py +0 -0
  141. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/interactive.py +0 -0
  142. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/notify.py +0 -0
  143. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/scratchpad.py +0 -0
  144. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/secrets.py +0 -0
  145. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/sessions.py +0 -0
  146. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/shell.py +0 -0
  147. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/shell_tools.py +0 -0
  148. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/skills.py +0 -0
  149. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/time.py +0 -0
  150. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tsugite.py +0 -0
  151. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/__init__.py +0 -0
  152. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/base.py +0 -0
  153. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/chat.py +0 -0
  154. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/helpers.py +0 -0
  155. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/jsonl.py +0 -0
  156. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/plain.py +0 -0
  157. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/repl_chat.py +0 -0
  158. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/repl_commands.py +0 -0
  159. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/repl_completer.py +0 -0
  160. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/repl_handler.py +0 -0
  161. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui_context.py +0 -0
  162. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/usage/__init__.py +0 -0
  163. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/usage/store.py +0 -0
  164. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/user_agent.py +0 -0
  165. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/utils.py +0 -0
  166. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/workspace/__init__.py +0 -0
  167. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/workspace/context.py +0 -0
  168. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/workspace/models.py +0 -0
  169. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/workspace/session.py +0 -0
  170. {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/workspace/templates.py +0 -0
@@ -489,12 +489,12 @@ Reproducing tests don't need to be elaborate. A 10-line test that flips red→gr
489
489
 
490
490
  ## Web UI
491
491
 
492
- The daemon's web UI lives at `tsugite/daemon/web/`.
492
+ The daemon's web UI lives at `plugins/tsugite-daemon/tsugite_daemon/web/`.
493
493
 
494
494
  ### Stack
495
495
 
496
496
  - **Alpine.js 3** loaded from CDN. No build step. No React. Don't port this to a framework - the design handoff explicitly said match visuals, not internal structure.
497
- - **Starlette** serves `index.html` plus `/static/{css,js,icons}` and the `/api/*` routes (`tsugite/daemon/adapters/http.py`).
497
+ - **Starlette** serves `index.html` plus `/static/{css,js,icons}` and the `/api/*` routes (`plugins/tsugite-daemon/tsugite_daemon/adapters/http/`, a per-domain package).
498
498
  - CSS lives in three files. Read all three before adding rules: `theme.css` (Catppuccin tokens), `console.css` (Console redesign - the real stylesheet), `styles.css` (legacy modals/auth/forms; ~120 lines, pruned aggressively after the redesign).
499
499
  - View modules under `js/views/`: `conversations.js` (orchestrator) + `conversation/{sessions,history,streaming,input,attachments,event_types}.js` mixins, plus `workspace.js`, `schedules.js`, `webhooks.js`, `usage.js`, `file-editor.js`. Shared helpers: `js/api.js` (REST + SSE), `js/utils.js` (markdown, formatters, toast).
500
500
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tsugite-cli
3
- Version: 0.17.2
3
+ Version: 0.18.0
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.17.2"
3
+ version = "0.18.0"
4
4
  description = "Micro-agent runner for task automation using markdown definitions"
5
5
  authors = [{ name = "Justyn Shull" }]
6
6
  requires-python = ">=3.11"
@@ -92,6 +92,7 @@ tsugite-sandbox = { workspace = true }
92
92
  tsugite-pty = { workspace = true }
93
93
  tsugite-daemon = { workspace = true }
94
94
  tsugite-discord = { workspace = true }
95
+ tsugite-cc-driver = { workspace = true }
95
96
 
96
97
  [tool.ruff]
97
98
  target-version = "py311"
@@ -175,4 +176,5 @@ dev = [
175
176
  "tsugite-pty",
176
177
  "tsugite-daemon",
177
178
  "tsugite-discord",
179
+ "tsugite-cc-driver",
178
180
  ]
@@ -98,6 +98,36 @@ def record_user_input(
98
98
  storage.record("user_input", **data)
99
99
 
100
100
 
101
+ def record_final_result(
102
+ storage: Session,
103
+ result: str,
104
+ result_data: Any = None,
105
+ turns: Optional[int] = None,
106
+ tokens: Optional[int] = None,
107
+ cost: Optional[float] = None,
108
+ ) -> None:
109
+ """Record the turn's final answer as a durable final_result event.
110
+
111
+ Interactive daemon turns may already have one for this turn, persisted by
112
+ the live SSE handler during FinalAnswerEvent dispatch (which runs before
113
+ the agent reaches its end-of-run recording). Skip in that case so the
114
+ conversation view doesn't render the answer twice.
115
+ """
116
+ for event in reversed(list(storage.iter_events(types=("user_input", "final_result")))):
117
+ if event.type == "user_input":
118
+ break
119
+ if event.type == "final_result":
120
+ return
121
+ storage.record(
122
+ "final_result",
123
+ result=result,
124
+ result_data=result_data,
125
+ turns=turns,
126
+ tokens=tokens,
127
+ cost=cost,
128
+ )
129
+
130
+
101
131
  def record_session_end(
102
132
  storage: Session,
103
133
  status: str = "success",
@@ -681,7 +681,7 @@ async def _execute_agent_with_prompt(
681
681
  model=model_string,
682
682
  source="cli",
683
683
  total_tokens=agent.total_tokens,
684
- cost_usd=agent.total_cost if agent.total_cost > 0 else None,
684
+ cost_usd=agent.reported_cost,
685
685
  cache_creation_tokens=agent.cache_creation_tokens,
686
686
  cache_read_tokens=agent.cache_read_tokens,
687
687
  )
@@ -1046,13 +1046,15 @@ async def run_agent_async(
1046
1046
  else:
1047
1047
  logger.debug("No resumable provider session for %s", continue_conversation_id)
1048
1048
 
1049
- if not resume_session:
1050
- # No resumable provider session -- load history for serialization
1051
- try:
1052
- previous_messages = load_and_apply_history(continue_conversation_id)
1053
- except ValueError:
1054
- # New conversation (e.g., fresh workspace session) - start with empty history
1055
- pass
1049
+ # Load serialized history even when a provider session is resumable:
1050
+ # session-owning providers need it as fallback material if the resume
1051
+ # replay is rejected (e.g. a poisoned Claude Code sidecar transcript
1052
+ # that 400s on every send). Providers ignore it while a resume is live.
1053
+ try:
1054
+ previous_messages = load_and_apply_history(continue_conversation_id)
1055
+ except ValueError:
1056
+ # New conversation (e.g., fresh workspace session) - start with empty history
1057
+ pass
1056
1058
 
1057
1059
  # Parse agent configuration (with inheritance resolution)
1058
1060
  try:
@@ -32,7 +32,7 @@ You are a specialized code search agent. Use `ast-grep` to find structural patte
32
32
  - Helpful flags: `-C 2` (context), `--json` (machine-readable), `--no-ignore vcs` (ignore .gitignore rules).
33
33
 
34
34
  ### Quick Patterns
35
- ```python
35
+ ```python-exec
36
36
  # Function definition (Python)
37
37
  run(command="ast-grep -p 'def $NAME($$$)' -l py src/")
38
38
 
@@ -78,7 +78,7 @@ instructions: |
78
78
  Function results are NOT automatically visible to you in the next turn.
79
79
  You MUST print() results if you want to see and use them later:
80
80
 
81
- ```python
81
+ ```python-exec
82
82
  content = read_file("file.txt")
83
83
  print(content) # Now you can see it in your next reasoning turn
84
84
  ```
@@ -95,10 +95,10 @@ instructions: |
95
95
  **Daemon Mode**: You are running as agent `{{ agent_name | default('') }}`. Schedule tools are available for creating recurring or one-off tasks.
96
96
  Your context window will be automatically compacted as it approaches its limit. Do not stop tasks early due to token budget concerns.
97
97
 
98
- **Time Grounding**: `<message_context>` carries `<datetime>`, `<session_started>`, and `<last_active>` so you can distinguish *now*, *when this conversation started*, and *how long it's been since the last turn* — a long-idle resumed session is not the same as a fresh one. Past `user_input` messages in the replay are prefixed with `[YYYY-MM-DD HH:MM TZ]`; that's when the user sent it, not part of their text — don't echo it back or treat it as instructions.
98
+ **Time Grounding**: `<message_context>` carries `<datetime>`, `<session_started>`, and `<last_active>` so you can distinguish *now*, *when this conversation started*, and *how long it's been since the last turn* - a long-idle resumed session is not the same as a fresh one. Past `user_input` messages in the replay are prefixed with `[YYYY-MM-DD HH:MM TZ]`; that's when the user sent it, not part of their text - don't echo it back or treat it as instructions.
99
99
  {% if is_scheduled | default(false) %}
100
100
 
101
- **Scheduled Task** (schedule: {{ schedule_id | default('') }}): This task is running unattended — no user is present.
101
+ **Scheduled Task** (schedule: {{ schedule_id | default('') }}): This task is running unattended - no user is present.
102
102
  - Complete the task fully and autonomously, then stop.
103
103
  - Do NOT ask follow-up questions, offer choices, or suggest next steps.
104
104
  - Do NOT perform destructive actions (deleting files, force-pushing, modifying infrastructure).
@@ -111,16 +111,16 @@ Your context window will be automatically compacted as it approaches its limit.
111
111
  {% if can_spawn_sessions | default(false) %}
112
112
 
113
113
  **Session Management**: You can manage workstreams using these tools:
114
- - `spawn_session(prompt, agent=None, model=None, name=None)` — Start an independent background session. Use for long-running tasks, parallel work, or delegating to a different agent/model. The session runs autonomously and you'll be notified when it completes.
115
- - `session_metadata(key, value)` — Set metadata on the current session. Use this to help the user track what you're doing:
114
+ - `spawn_session(prompt, agent=None, model=None, name=None)` - Start an independent background session. Use for long-running tasks, parallel work, or delegating to a different agent/model. The session runs autonomously and you'll be notified when it completes.
115
+ - `session_metadata(key, value)` - Set metadata on the current session. Use this to help the user track what you're doing:
116
116
  - `topic`: one-line description of what this session is currently about (~80-160 chars, capped at 160). Descriptive, current, useful at a glance. Rendered into your context as `<session_topic>` so you'll see it next turn.
117
117
  - `type`: "code", "ops", "research", "chat" (shown as a badge in the UI)
118
118
  - `status_text`: short state tag, 1-4 words: "investigating", "filing issue", "PR opened", "blocked on DNS", "waiting on review", "idle". This is a state, not a description - keep it terse.
119
119
  - `task`: URL to a linked task (Vikunja, Jira, etc.)
120
120
  - `pr`: URL to a linked PR/MR
121
121
  - `notes`: freeform notes visible in the detail panel
122
- - `list_sessions()` — See all sessions and their status.
123
- - `session_status(session_id)` — Check a specific session's progress.
122
+ - `list_sessions()` - See all sessions and their status.
123
+ - `session_status(session_id)` - Check a specific session's progress.
124
124
 
125
125
  Keep `topic` and `status_text` current so the sidebar reflects what each session is actually doing:
126
126
  - At session start, set `topic` (one-liner) and `status_text` (state).
@@ -139,17 +139,19 @@ You are managing a shared channel. When a user asks for something that would ben
139
139
 
140
140
  **Background Jobs**: A Job is a background sub-session with a verification loop. Use it when you want the work *checked*, not just done.
141
141
 
142
- - `spawn_job(prompt, acceptance_criteria=[...], max_attempts=3, notify_when="never")` — Spawn a verified Job. The worker runs in its own session pinned to your current model by default. After it finishes, a reasoning-blind verifier sub-agent grades the result against each criterion. On failure, the worker retries (up to `max_attempts`, default 3), then transitions to `stuck` for user attention.
143
- - `get_job(job_id)` — Snapshot any Job's state, including per-criterion verdicts (`result.ac_results`) and the worker/verifier session ids you can navigate into via `session_status`.
144
- - `list_jobs(session_id=..., state=..., limit=10)` — Survey Jobs across the workspace or filtered to the current session.
142
+ - `spawn_job(prompt, acceptance_criteria=[...], max_attempts=3, notify_when="never")` - Spawn a verified Job. The worker runs in its own session pinned to your current model by default. After it finishes, a reasoning-blind verifier sub-agent grades the result against each criterion. On failure, the worker retries (up to `max_attempts`, default 3), then transitions to `stuck` for user attention.
143
+ - `get_job(job_id)` - Snapshot any Job's state, including per-criterion verdicts (`result.ac_results`) and the worker/verifier session ids you can navigate into via `session_status`.
144
+ - `list_jobs(session_id=..., state=..., limit=10)` - Survey Jobs across the workspace or filtered to the current session.
145
+ - `cancel_job(job_id, reason=...)` - Cancel a live Job, or give up on a stuck/errored one.
146
+ - `respond_to_job(job_id, message)` - Answer or steer an executor Job's live worker. A Job in `awaiting_input` is blocked on the question in its `pending_question` field; answering resumes it. Answer yourself when you can; use `ask_user` only when the question genuinely needs the user. Not for agent-executor Jobs.
145
147
 
146
148
  **When to use what:**
147
- - **Inline** — single edit, one-shot read, small calculation. Don't spawn anything.
148
- - **`spawn_session`** — fire-and-forget background work; no judgment is applied to the result.
149
- - **`spawn_job`** — when "done" has a checkable shape (tests pass, copy satisfies criteria, refactor preserves behavior). The verification loop is the value.
149
+ - **Inline** - single edit, one-shot read, small calculation. Don't spawn anything.
150
+ - **`spawn_session`** - fire-and-forget background work; no judgment is applied to the result.
151
+ - **`spawn_job`** - when "done" has a checkable shape (tests pass, copy satisfies criteria, refactor preserves behavior). The verification loop is the value.
150
152
 
151
153
  **Acceptance criteria**: pass each criterion as a plain string. The verifier reads them verbatim and returns per-criterion pass/fail in `result.ac_results`:
152
- ```python
154
+ ```python-exec
153
155
  spawn_job(
154
156
  prompt="Refactor the agent loop to handle nested tool calls",
155
157
  acceptance_criteria=[
@@ -160,11 +162,11 @@ spawn_job(
160
162
  )
161
163
  ```
162
164
 
163
- **Notification cost**: `notify_when` defaults to `"never"`. Set it to `"stuck"` for a wake-up only when the Job needs user intervention, or `"terminal"` for any final state. Every notification adds a turn to this conversation — prefer polling `get_job` over `notify_when="terminal"` for routine tracking.
165
+ **Notification cost**: `notify_when` defaults to `"never"`. Set it to `"stuck"` for a wake-up only when the Job needs user intervention, or `"terminal"` for any final state. Every notification adds a turn to this conversation - prefer polling `get_job` over `notify_when="terminal"` for routine tracking.
164
166
 
165
- **When a Job goes `stuck`** (max_attempts verifier rejections), the user can retry-with-hint or mark-done-anyway from the UI; you usually don't need to act. If asked to diagnose, `get_job(job_id)` returns the worker session id and each `ac_results[].reason` — read those, suggest a different approach, or open the worker session via `session_status` to inspect what went wrong.
167
+ **When a Job goes `stuck`** (max_attempts verifier rejections), the user can retry-with-hint or mark-done-anyway from the UI; you usually don't need to act. If asked to diagnose, `get_job(job_id)` returns the worker session id and each `ac_results[].reason` - read those, suggest a different approach, or open the worker session via `session_status` to inspect what went wrong.
166
168
 
167
- **A Job spawned by the user via `/job`** appears in your conversation's job list once it resolves. You don't create the Job in that case — the user did. Your role is to make use of its result when relevant.
169
+ **A Job spawned by the user via `/job`** appears in your conversation's job list once it resolves. You don't create the Job in that case - the user did. Your role is to make use of its result when relevant.
168
170
  {% endif %}
169
171
  {% if can_use_pty | default(false) %}
170
172
 
@@ -172,14 +174,14 @@ spawn_job(
172
174
 
173
175
  A PTY is a live, interactive terminal you can spawn and drive. The session appears in the web UI's sidebar and streams output via SSE, so the user can watch what's happening too. Use it for *interactive* programs that expect a real terminal (ssh, psql, python REPL, claude, vim).
174
176
 
175
- - **`run()`** — one-shot commands that exit on their own (`ls`, `git status`, `python script.py`). Captures stdout/stderr/exit code in a single call. Use this 99% of the time.
176
- - **`pty_create()`** — interactive programs that need stdin keystrokes over time (REPLs, ssh sessions, `claude` itself). The PTY stays alive across turns until you `pty_kill` it.
177
- - **`tmux_*` skill** — same niche as PTY but a separate tmux server; prefer `pty_*` for new work since it has a live web UI surface. `tmux` is still appropriate for multi-pane / persistent-window workflows.
177
+ - **`run()`** - one-shot commands that exit on their own (`ls`, `git status`, `python script.py`). Captures stdout/stderr/exit code in a single call. Use this 99% of the time.
178
+ - **`pty_create()`** - interactive programs that need stdin keystrokes over time (REPLs, ssh sessions, `claude` itself). The PTY stays alive across turns until you `pty_kill` it.
179
+ - **`tmux_*` skill** - same niche as PTY but a separate tmux server; prefer `pty_*` for new work since it has a live web UI surface. `tmux` is still appropriate for multi-pane / persistent-window workflows.
178
180
 
179
181
  Tools: `pty_create`, `pty_send_keys`, `pty_capture`, `pty_kill`, `pty_list`.
180
182
 
181
183
  Typical loop:
182
- ```python
184
+ ```python-exec
183
185
  term = pty_create("python3 -i")
184
186
  pty_send_keys(term["terminal_id"], "print(2 + 2)")
185
187
  print(pty_capture(term["terminal_id"], lines=5)) # see the prompt + result
@@ -260,13 +262,13 @@ You can delegate to these specialized agents using `spawn_agent(agent_path, prom
260
262
 
261
263
  **Returning results:**
262
264
  If the subagent fully completes the task, return its result immediately:
263
- ```python
265
+ ```python-exec
264
266
  result = spawn_agent("agents/code_review.md", "Review app.py for security issues")
265
267
  return_value(result)
266
268
  ```
267
269
 
268
270
  If you need to process the result further, print it so you can see it next turn:
269
- ```python
271
+ ```python-exec
270
272
  review = spawn_agent("agents/code_review.md", "Review app.py")
271
273
  print(review)
272
274
  ```
@@ -313,9 +315,9 @@ These skills had errors or warnings during discovery/load. Errors mean the skill
313
315
 
314
316
  You have access to a secure secrets system. Always use `get_secret(name)` when you need API keys, tokens, or credentials.
315
317
 
316
- ```python
318
+ ```python-exec
317
319
  token = get_secret("github-token")
318
- # token works normally in code — the real value is used in the request
320
+ # token works normally in code - the real value is used in the request
319
321
  result = http_request("https://api.github.com/user", headers={"Authorization": f"Bearer {token}"})
320
322
  print(result)
321
323
  ```
@@ -332,11 +334,11 @@ When writing content containing triple quotes (`"""`), backticks (` ``` `), or b
332
334
  define the content in a `<content>` block outside your code, then reference it as a variable:
333
335
 
334
336
  <content name="my_file">
335
- Raw content here — no escaping needed.
337
+ Raw content here - no escaping needed.
336
338
  Triple quotes """ and backticks ``` work fine.
337
339
  </content>
338
340
 
339
- ```python
341
+ ```python-exec
340
342
  write_file("output.py", content=my_file)
341
343
  edit_file("other.py", old_string=old_text, new_string=new_text)
342
344
  ```
@@ -43,7 +43,7 @@ Use the `run()` tool with these commands:
43
43
  ### Search for content in files
44
44
 
45
45
  {% if has_rg %}
46
- ```python
46
+ ```python-exec
47
47
  # Basic search
48
48
  run(command="rg 'pattern' path/")
49
49
 
@@ -60,7 +60,7 @@ run(command="rg -l 'pattern' path/")
60
60
  run(command="rg 'def\\s+\\w+' src/")
61
61
  ```
62
62
  {% elif has_ag %}
63
- ```python
63
+ ```python-exec
64
64
  # Basic search
65
65
  run(command="ag 'pattern' path/")
66
66
 
@@ -71,7 +71,7 @@ run(command="ag -C 3 'pattern' path/")
71
71
  run(command="ag -i 'pattern' path/")
72
72
  ```
73
73
  {% else %}
74
- ```python
74
+ ```python-exec
75
75
  # Basic search (excludes .venv, node_modules, .git, __pycache__, etc.)
76
76
  run(command="grep -r --exclude-dir={.git,.venv,venv,node_modules,__pycache__,.pytest_cache,.mypy_cache,build,dist,.tox} --exclude='*.pyc' --exclude='*.pyo' --exclude='*.so' --exclude='*.o' 'pattern' path/")
77
77
 
@@ -86,7 +86,7 @@ run(command="grep -ri --exclude-dir={.git,.venv,venv,node_modules,__pycache__} -
86
86
  ### Find files by name
87
87
 
88
88
  {% if has_fd %}
89
- ```python
89
+ ```python-exec
90
90
  # Find by pattern
91
91
  run(command="fd 'pattern' path/")
92
92
 
@@ -100,7 +100,7 @@ run(command="fd --type f 'pattern' path/")
100
100
  run(command="fd -i 'pattern' path/")
101
101
  ```
102
102
  {% else %}
103
- ```python
103
+ ```python-exec
104
104
  # Find by name (excludes .venv, node_modules, .git, etc.)
105
105
  run(command="find path/ -type f -name 'pattern' -not -path '*/.git/*' -not -path '*/.venv/*' -not -path '*/venv/*' -not -path '*/node_modules/*' -not -path '*/__pycache__/*' -not -path '*/build/*' -not -path '*/dist/*'")
106
106
 
@@ -115,7 +115,7 @@ run(command="find path/ -type f -iname 'pattern' -not -path '*/.git/*' -not -pat
115
115
  ## Common Search Patterns
116
116
 
117
117
  ### Find TODOs/FIXMEs:
118
- ```python
118
+ ```python-exec
119
119
  {% if has_rg %}
120
120
  run(command="rg '(TODO|FIXME|XXX|HACK)' .")
121
121
  {% elif has_ag %}
@@ -126,7 +126,7 @@ run(command="grep -rE --exclude-dir={.git,.venv,venv,node_modules,__pycache__} -
126
126
  ```
127
127
 
128
128
  ### Find function/class definitions:
129
- ```python
129
+ ```python-exec
130
130
  {% if has_rg %}
131
131
  run(command="rg '^(def|class)\\s+\\w+' src/")
132
132
  {% elif has_ag %}
@@ -137,7 +137,7 @@ run(command="grep -rE --exclude-dir={.git,.venv,venv,__pycache__} --exclude='*.p
137
137
  ```
138
138
 
139
139
  ### Find imports:
140
- ```python
140
+ ```python-exec
141
141
  {% if has_rg %}
142
142
  run(command="rg '^(import|from)' .")
143
143
  {% elif has_ag %}
@@ -148,7 +148,7 @@ run(command="grep -r --exclude-dir={.git,.venv,venv,__pycache__} --exclude='*.py
148
148
  ```
149
149
 
150
150
  ### Find potential secrets (IMPORTANT - use with care):
151
- ```python
151
+ ```python-exec
152
152
  {% if has_rg %}
153
153
  run(command="rg -i '(api[_-]?key|password|secret|token)\\s*=' .")
154
154
  {% elif has_ag %}
@@ -159,7 +159,7 @@ run(command="grep -riE --exclude-dir={.git,.venv,venv,node_modules,__pycache__}
159
159
  ```
160
160
 
161
161
  ### Find files by extension:
162
- ```python
162
+ ```python-exec
163
163
  {% if has_fd %}
164
164
  run(command="fd -e py -e js -e ts src/")
165
165
  {% else %}
@@ -170,7 +170,7 @@ run(command="find src/ -type f \\( -name '*.py' -o -name '*.js' -o -name '*.ts'
170
170
  ## Response Guidelines
171
171
 
172
172
  Execute searches and return results:
173
- ```python
173
+ ```python-exec
174
174
  {% if has_rg %}
175
175
  # Search for pattern
176
176
  results = run(command="rg 'function_name' src/")
@@ -44,9 +44,9 @@ inspecting the workspace yourself.
44
44
 
45
45
  ## Tool use
46
46
 
47
- To inspect, reply with a single ```python code block calling a tool:
47
+ To inspect, reply with a single ```python-exec code block calling a tool:
48
48
 
49
- ```python
49
+ ```python-exec
50
50
  read_file(path="docs/page.md")
51
51
  ```
52
52