tsugite-cli 0.14.2__tar.gz → 0.14.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/PKG-INFO +1 -1
  2. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/pyproject.toml +1 -1
  3. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/daemon.py +16 -11
  4. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/run.py +50 -46
  5. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/adapters/base.py +42 -15
  6. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/adapters/discord.py +1 -1
  7. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/adapters/http.py +22 -10
  8. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/compaction_scheduler.py +39 -12
  9. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/config.py +38 -2
  10. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/scheduler.py +2 -2
  11. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/session_runner.py +1 -1
  12. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/session_store.py +76 -22
  13. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/views/conversation/sessions.js +30 -0
  14. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/views/conversations.js +57 -24
  15. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/.gitignore +0 -0
  16. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/AGENTS.md +0 -0
  17. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/CONTRIBUTING.md +0 -0
  18. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/LICENSE +0 -0
  19. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/README.md +0 -0
  20. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/__init__.py +0 -0
  21. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/agent_inheritance.py +0 -0
  22. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/agent_preparation.py +0 -0
  23. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/agent_runner/__init__.py +0 -0
  24. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/agent_runner/exec_directives.py +0 -0
  25. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/agent_runner/exec_runner.py +0 -0
  26. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/agent_runner/helpers.py +0 -0
  27. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/agent_runner/history_integration.py +0 -0
  28. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/agent_runner/metrics.py +0 -0
  29. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/agent_runner/models.py +0 -0
  30. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/agent_runner/runner.py +0 -0
  31. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/agent_runner/validation.py +0 -0
  32. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/agent_utils.py +0 -0
  33. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/attachments/__init__.py +0 -0
  34. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/attachments/auto_context.py +0 -0
  35. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/attachments/base.py +0 -0
  36. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/attachments/file.py +0 -0
  37. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/attachments/inline.py +0 -0
  38. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/attachments/storage.py +0 -0
  39. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/attachments/url.py +0 -0
  40. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/attachments/youtube.py +0 -0
  41. {tsugite_cli-0.14.2/tsugite/builtin_skills → tsugite_cli-0.14.3/tsugite/builtin_agents}/.gitkeep +0 -0
  42. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/builtin_agents/code_searcher.md +0 -0
  43. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/builtin_agents/default.md +0 -0
  44. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/builtin_agents/file_searcher.md +0 -0
  45. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/builtin_agents/onboard.md +0 -0
  46. {tsugite_cli-0.14.2/tsugite/builtin_agents → tsugite_cli-0.14.3/tsugite/builtin_skills}/.gitkeep +0 -0
  47. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/builtin_skills/codebase-exploration/SKILL.md +0 -0
  48. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/builtin_skills/python-math/SKILL.md +0 -0
  49. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/builtin_skills/response-patterns/SKILL.md +0 -0
  50. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/builtin_skills/scheduling/SKILL.md +0 -0
  51. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/builtin_skills/skill-authoring/SKILL.md +0 -0
  52. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/builtin_skills/tsugite-agent-basics/SKILL.md +0 -0
  53. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/builtin_skills/tsugite-jinja-reference/SKILL.md +0 -0
  54. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/builtin_skills/tsugite-skill-basics/SKILL.md +0 -0
  55. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cache.py +0 -0
  56. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/__init__.py +0 -0
  57. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/agents.py +0 -0
  58. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/attachments.py +0 -0
  59. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/cache.py +0 -0
  60. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/chat.py +0 -0
  61. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/config.py +0 -0
  62. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/helpers.py +0 -0
  63. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/history.py +0 -0
  64. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/init.py +0 -0
  65. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/models.py +0 -0
  66. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/plugins.py +0 -0
  67. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/render.py +0 -0
  68. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/secrets.py +0 -0
  69. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/skills.py +0 -0
  70. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/tools.py +0 -0
  71. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/usage.py +0 -0
  72. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/validate.py +0 -0
  73. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/cli/workspace.py +0 -0
  74. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/config.py +0 -0
  75. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/console.py +0 -0
  76. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/constants.py +0 -0
  77. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/core/__init__.py +0 -0
  78. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/core/agent.py +0 -0
  79. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/core/claude_code.py +0 -0
  80. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/core/content_blocks.py +0 -0
  81. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/core/executor.py +0 -0
  82. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/core/memory.py +0 -0
  83. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/core/proxy.py +0 -0
  84. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/core/sandbox.py +0 -0
  85. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/core/state.py +0 -0
  86. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/core/subprocess_executor.py +0 -0
  87. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/core/tools.py +0 -0
  88. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/__init__.py +0 -0
  89. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/adapters/__init__.py +0 -0
  90. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/adapters/scheduler_adapter.py +0 -0
  91. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/auth.py +0 -0
  92. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/commands.py +0 -0
  93. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/gateway.py +0 -0
  94. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/memory.py +0 -0
  95. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/push.py +0 -0
  96. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/css/console.css +0 -0
  97. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/css/responsive.css +0 -0
  98. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/css/styles.css +0 -0
  99. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/css/theme.css +0 -0
  100. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/icons/icon-192.png +0 -0
  101. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/icons/icon-512-maskable.png +0 -0
  102. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/icons/icon-512.png +0 -0
  103. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/icons/screenshot-narrow.png +0 -0
  104. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/icons/screenshot-wide.png +0 -0
  105. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/index.html +0 -0
  106. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/api.js +0 -0
  107. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/app.js +0 -0
  108. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/utils.js +0 -0
  109. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/vendor/marked.LICENSE.md +0 -0
  110. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/vendor/marked.esm.min.js +0 -0
  111. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/views/conversation/attachments.js +0 -0
  112. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/views/conversation/event_types.js +0 -0
  113. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/views/conversation/history.js +0 -0
  114. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/views/conversation/input.js +0 -0
  115. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/views/conversation/streaming.js +0 -0
  116. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/views/file-editor.js +0 -0
  117. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/views/schedules.js +0 -0
  118. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/views/usage.js +0 -0
  119. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/views/webhooks.js +0 -0
  120. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/js/views/workspace.js +0 -0
  121. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/manifest.json +0 -0
  122. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/web/sw.js +0 -0
  123. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/daemon/webhook_store.py +0 -0
  124. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/events/__init__.py +0 -0
  125. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/events/base.py +0 -0
  126. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/events/bus.py +0 -0
  127. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/events/events.py +0 -0
  128. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/events/helpers.py +0 -0
  129. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/exceptions.py +0 -0
  130. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/history/__init__.py +0 -0
  131. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/history/models.py +0 -0
  132. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/history/reconstruction.py +0 -0
  133. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/history/storage.py +0 -0
  134. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/hooks.py +0 -0
  135. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/interaction.py +0 -0
  136. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/md_agents.py +0 -0
  137. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/models.py +0 -0
  138. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/options.py +0 -0
  139. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/plugins.py +0 -0
  140. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/providers/__init__.py +0 -0
  141. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/providers/anthropic.py +0 -0
  142. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/providers/base.py +0 -0
  143. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/providers/claude_code.py +0 -0
  144. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/providers/model_cache.py +0 -0
  145. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/providers/model_registry.py +0 -0
  146. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/providers/ollama.py +0 -0
  147. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/providers/openai_compat.py +0 -0
  148. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/providers/openrouter.py +0 -0
  149. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/renderer.py +0 -0
  150. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/schemas/__init__.py +0 -0
  151. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/schemas/agent.schema.json +0 -0
  152. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/secrets/__init__.py +0 -0
  153. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/secrets/backend.py +0 -0
  154. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/secrets/env.py +0 -0
  155. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/secrets/exec.py +0 -0
  156. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/secrets/file.py +0 -0
  157. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/secrets/masking.py +0 -0
  158. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/secrets/registry.py +0 -0
  159. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/secrets/sqlite.py +0 -0
  160. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/shell_tool_config.py +0 -0
  161. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/skill_discovery.py +0 -0
  162. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/templates/AGENTS.md +0 -0
  163. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/templates/IDENTITY.md +0 -0
  164. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/templates/MEMORY.md +0 -0
  165. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/templates/USER.md +0 -0
  166. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/templates/personas/casual-technical.md +0 -0
  167. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/templates/personas/marvin.md +0 -0
  168. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/templates/personas/minimal.md +0 -0
  169. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/__init__.py +0 -0
  170. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/agents.py +0 -0
  171. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/fs.py +0 -0
  172. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/history.py +0 -0
  173. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/http.py +0 -0
  174. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/interactive.py +0 -0
  175. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/notify.py +0 -0
  176. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/schedule.py +0 -0
  177. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/scratchpad.py +0 -0
  178. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/secrets.py +0 -0
  179. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/sessions.py +0 -0
  180. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/shell.py +0 -0
  181. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/shell_tools.py +0 -0
  182. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/skills.py +0 -0
  183. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tools/time.py +0 -0
  184. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/tsugite.py +0 -0
  185. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/ui/__init__.py +0 -0
  186. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/ui/base.py +0 -0
  187. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/ui/chat.py +0 -0
  188. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/ui/helpers.py +0 -0
  189. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/ui/jsonl.py +0 -0
  190. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/ui/live.py +0 -0
  191. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/ui/plain.py +0 -0
  192. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/ui/repl_chat.py +0 -0
  193. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/ui/repl_commands.py +0 -0
  194. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/ui/repl_completer.py +0 -0
  195. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/ui/repl_handler.py +0 -0
  196. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/ui_context.py +0 -0
  197. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/usage/__init__.py +0 -0
  198. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/usage/store.py +0 -0
  199. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/user_agent.py +0 -0
  200. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/utils.py +0 -0
  201. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/workspace/__init__.py +0 -0
  202. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/workspace/context.py +0 -0
  203. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/workspace/models.py +0 -0
  204. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/workspace/session.py +0 -0
  205. {tsugite_cli-0.14.2 → tsugite_cli-0.14.3}/tsugite/workspace/templates.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tsugite-cli
3
- Version: 0.14.2
3
+ Version: 0.14.3
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.14.2"
3
+ version = "0.14.3"
4
4
  description = "Micro-agent runner for task automation using markdown definitions"
5
5
  authors = [{ name = "Justyn Shull" }]
6
6
  requires-python = ">=3.11"
@@ -44,13 +44,14 @@ def _prompt_bot_name(style) -> str:
44
44
  ).ask()
45
45
 
46
46
 
47
- def _prompt_token_env_var(style) -> str:
48
- """Prompt for token environment variable name."""
47
+ def _prompt_token_file_path(style, bot_name: str) -> str:
48
+ """Prompt for the file path that holds the Discord token."""
49
49
  import questionary
50
50
 
51
+ default_path = f"~/.config/tsugite/discord-{bot_name}.token"
51
52
  return questionary.text(
52
- "Environment variable name for Discord token:",
53
- default="DISCORD_BOT_TOKEN",
53
+ "Path to file containing Discord token:",
54
+ default=default_path,
54
55
  style=style,
55
56
  ).ask()
56
57
 
@@ -200,7 +201,8 @@ def _config_to_dict(config) -> dict:
200
201
  "discord_bots": [
201
202
  {
202
203
  "name": bot.name,
203
- "token": bot.token,
204
+ **({"token_secret": bot.token_secret} if bot.token_secret else {}),
205
+ **({"token_file": str(bot.token_file)} if bot.token_file else {}),
204
206
  "agent": bot.agent,
205
207
  "command_prefix": bot.command_prefix,
206
208
  "dm_policy": bot.dm_policy,
@@ -220,14 +222,17 @@ def _show_generated_config(config_data: dict):
220
222
  console.print(Panel(config_yaml, border_style="cyan"))
221
223
 
222
224
 
223
- def _show_next_steps(token_env_var: str, bot_name: str):
225
+ def _show_next_steps(token_file: str, bot_name: str):
224
226
  """Display next steps for the user."""
225
227
  console.print("\n" + "=" * 60)
226
228
  console.print("[bold green]Setup Complete![/bold green]")
227
229
  console.print("=" * 60)
228
230
 
229
231
  console.print("\n[bold]Next Steps:[/bold]")
230
- console.print(f' 1. Set your Discord token: [cyan]export {token_env_var}="your-token"[/cyan]')
232
+ console.print(f" 1. Save your Discord token to: [cyan]{token_file}[/cyan]")
233
+ console.print(
234
+ f' [dim]mkdir -p $(dirname {token_file}) && echo "your-token" > {token_file} && chmod 600 {token_file}[/dim]'
235
+ )
231
236
  console.print(" 2. Start the daemon: [cyan]tsugite daemon[/cyan]")
232
237
  console.print(" 3. DM your bot with: [cyan]!hello[/cyan]")
233
238
 
@@ -295,8 +300,8 @@ def init_daemon(
295
300
  if not bot_name:
296
301
  raise typer.Exit(1)
297
302
 
298
- token_env_var = _prompt_token_env_var(style)
299
- if not token_env_var:
303
+ token_file = _prompt_token_file_path(style, bot_name)
304
+ if not token_file:
300
305
  raise typer.Exit(1)
301
306
 
302
307
  command_prefix = _prompt_command_prefix(style)
@@ -335,7 +340,7 @@ def init_daemon(
335
340
 
336
341
  bot_config = {
337
342
  "name": bot_name,
338
- "token": f"${{{token_env_var}}}",
343
+ "token_file": token_file,
339
344
  "agent": agent_config_name,
340
345
  "command_prefix": command_prefix,
341
346
  "dm_policy": dm_policy,
@@ -357,7 +362,7 @@ def init_daemon(
357
362
 
358
363
  console.print(f"[green]✓[/green] Configuration saved to: {config_path}")
359
364
 
360
- _show_next_steps(token_env_var, bot_name)
365
+ _show_next_steps(token_file, bot_name)
361
366
 
362
367
 
363
368
  def _daemon_request(method: str, host: str, port: int, path: str, token: Optional[str] = None, **kwargs):
@@ -87,19 +87,21 @@ def _check_and_run_onboarding(
87
87
  return workspace
88
88
 
89
89
 
90
- def _resolve_ui_mode(ui_mode: Optional[str], ui_opts: UIOptions, console: Console) -> UIOptions:
90
+ def _resolve_ui_mode(ui_mode: Optional[str], ui_opts: UIOptions, stderr_console: Console) -> UIOptions:
91
91
  """Resolve UI mode flag and return updated UIOptions."""
92
92
  if not ui_mode:
93
93
  return ui_opts
94
94
 
95
95
  if ui_opts.plain or ui_opts.headless:
96
- console.print("[red]Error: --ui cannot be used with --plain or --headless[/red]")
96
+ stderr_console.print("[red]Error: --ui cannot be used with --plain or --headless[/red]")
97
97
  raise typer.Exit(1)
98
98
 
99
99
  ui_modes = {"plain", "headless", "live"}
100
100
  ui_lower = ui_mode.lower()
101
101
  if ui_lower not in ui_modes:
102
- console.print(f"[red]Error: Invalid UI mode '{ui_mode}'. Choose from: {', '.join(sorted(ui_modes))}[/red]")
102
+ stderr_console.print(
103
+ f"[red]Error: Invalid UI mode '{ui_mode}'. Choose from: {', '.join(sorted(ui_modes))}[/red]"
104
+ )
103
105
  raise typer.Exit(1)
104
106
 
105
107
  if ui_lower == "plain":
@@ -142,7 +144,7 @@ def _build_executor_kwargs(
142
144
 
143
145
 
144
146
  def _resolve_conversation_continuation(
145
- continue_conversation: bool, conversation_id: Optional[str], console: Console
147
+ continue_conversation: bool, conversation_id: Optional[str], stderr_console: Console
146
148
  ) -> Optional[str]:
147
149
  """Resolve which conversation to continue."""
148
150
  if not continue_conversation:
@@ -151,15 +153,15 @@ def _resolve_conversation_continuation(
151
153
  from tsugite.agent_runner.history_integration import get_latest_conversation
152
154
 
153
155
  if conversation_id:
154
- console.print(f"[cyan]Continuing conversation: {conversation_id}[/cyan]")
156
+ stderr_console.print(f"[cyan]Continuing conversation: {conversation_id}[/cyan]")
155
157
  return conversation_id
156
158
 
157
159
  continue_conversation_id = get_latest_conversation()
158
160
  if not continue_conversation_id:
159
- console.print("[red]No conversations found to resume[/red]")
161
+ stderr_console.print("[red]No conversations found to resume[/red]")
160
162
  raise typer.Exit(1)
161
163
 
162
- console.print(f"[cyan]Continuing latest conversation: {continue_conversation_id}[/cyan]")
164
+ stderr_console.print(f"[cyan]Continuing latest conversation: {continue_conversation_id}[/cyan]")
163
165
  return continue_conversation_id
164
166
 
165
167
 
@@ -318,21 +320,24 @@ def run(
318
320
  tsu run --daemon odyn "follow up message" # Join daemon session for agent 'odyn'
319
321
  """
320
322
  from tsugite.agent_runner import get_agent_info, run_agent
323
+ from tsugite.console import get_stderr_console
321
324
  from tsugite.md_agents import validate_agent_execution
322
325
  from tsugite.secrets import init_cli as init_secrets
323
326
  from tsugite.utils import should_use_plain_output
324
327
 
325
328
  from . import console
326
329
 
330
+ stderr_console = get_stderr_console(no_color=no_color)
331
+
327
332
  init_secrets(no_secrets)
328
333
 
329
334
  # Validate flag combinations
330
335
  if new_session and continue_conversation:
331
- console.print("[red]Error: Cannot use --new-session with --continue[/red]")
336
+ stderr_console.print("[red]Error: Cannot use --new-session with --continue[/red]")
332
337
  raise typer.Exit(1)
333
338
 
334
339
  if workspace and no_workspace:
335
- console.print("[red]Error: Cannot use --workspace with --no-workspace[/red]")
340
+ stderr_console.print("[red]Error: Cannot use --workspace with --no-workspace[/red]")
336
341
  raise typer.Exit(1)
337
342
 
338
343
  ui_opts = UIOptions(
@@ -349,7 +354,7 @@ def run(
349
354
  workspace_to_use, resolved_workspace = _resolve_effective_workspace(workspace, no_workspace)
350
355
 
351
356
  if new_session and not workspace_to_use:
352
- console.print("[yellow]Warning: --new-session has no effect without a workspace[/yellow]")
357
+ stderr_console.print("[yellow]Warning: --new-session has no effect without a workspace[/yellow]")
353
358
  exec_opts = ExecutionOptions.from_cli(
354
359
  model=model,
355
360
  debug=debug,
@@ -367,7 +372,7 @@ def run(
367
372
  history_dir=history_dir,
368
373
  )
369
374
  if workspace_to_use and not resolved_workspace:
370
- console.print(f"[yellow]Warning: Workspace '{workspace_to_use}' not found[/yellow]")
375
+ stderr_console.print(f"[yellow]Warning: Workspace '{workspace_to_use}' not found[/yellow]")
371
376
 
372
377
  # Auto-continue workspace session unless explicitly overridden
373
378
  workspace_session_continued = False
@@ -407,14 +412,16 @@ def run(
407
412
  console.no_color = True
408
413
 
409
414
  # Resolve UI mode to update ui_opts
410
- ui_opts = _resolve_ui_mode(ui, ui_opts, console)
415
+ ui_opts = _resolve_ui_mode(ui, ui_opts, stderr_console)
411
416
 
412
417
  # Handle subagent mode and daemon mode (both need os)
413
418
  import os
414
419
 
415
420
  if subagent_mode:
416
421
  if ui_opts.plain or ui_opts.headless or ui_opts.live:
417
- console.print("[red]Error: --subagent-mode cannot be combined with --plain, --headless, or --ui live[/red]")
422
+ stderr_console.print(
423
+ "[red]Error: --subagent-mode cannot be combined with --plain, --headless, or --ui live[/red]"
424
+ )
418
425
  raise typer.Exit(1)
419
426
 
420
427
  ui_opts.non_interactive = True
@@ -430,14 +437,14 @@ def run(
430
437
 
431
438
  daemon_config = load_daemon_config()
432
439
  if daemon_agent not in daemon_config.agents:
433
- console.print(f"[red]Agent '{daemon_agent}' not found in daemon config[/red]")
440
+ stderr_console.print(f"[red]Agent '{daemon_agent}' not found in daemon config[/red]")
434
441
  raise typer.Exit(1)
435
442
 
436
443
  agent_config = daemon_config.agents[daemon_agent]
437
444
 
438
445
  except ValueError as e:
439
- console.print(f"[red]Daemon config not found: {e}[/red]")
440
- console.print("[dim]Run 'tsugite daemon' to start daemon first[/dim]")
446
+ stderr_console.print(f"[red]Daemon config not found: {e}[/red]")
447
+ stderr_console.print("[dim]Run 'tsugite daemon' to start daemon first[/dim]")
441
448
  raise typer.Exit(1)
442
449
 
443
450
  # Find latest session for this agent
@@ -455,11 +462,11 @@ def run(
455
462
  continue
456
463
 
457
464
  if latest_conv_id:
458
- console.print(f"[cyan]Joining daemon session: {latest_conv_id}[/cyan]")
465
+ stderr_console.print(f"[cyan]Joining daemon session: {latest_conv_id}[/cyan]")
459
466
  history_opts.continue_id = latest_conv_id
460
467
  else:
461
- console.print(f"[yellow]No active daemon session found for '{daemon_agent}'[/yellow]")
462
- console.print("[dim]Creating new daemon-managed session...[/dim]")
468
+ stderr_console.print(f"[yellow]No active daemon session found for '{daemon_agent}'[/yellow]")
469
+ stderr_console.print("[dim]Creating new daemon-managed session...[/dim]")
463
470
 
464
471
  # Override agent with daemon agent
465
472
  args = [f"+{daemon_agent}"] + args
@@ -489,9 +496,9 @@ def run(
489
496
 
490
497
  # Handle conversation continuation - check before parsing args
491
498
  if continue_conversation and not history_opts.continue_id:
492
- history_opts.continue_id = _resolve_conversation_continuation(True, None, console)
499
+ history_opts.continue_id = _resolve_conversation_continuation(True, None, stderr_console)
493
500
  elif history_opts.continue_id:
494
- console.print(f"[cyan]Continuing conversation: {history_opts.continue_id}[/cyan]")
501
+ stderr_console.print(f"[cyan]Continuing conversation: {history_opts.continue_id}[/cyan]")
495
502
 
496
503
  # Parse CLI arguments into agents and prompt (allow empty agents when continuing)
497
504
  try:
@@ -501,10 +508,10 @@ def run(
501
508
  args, allow_empty_agents=continue_conversation, check_stdin=not continue_conversation
502
509
  )
503
510
  except ValueError as e:
504
- console.print(f"[red]Error: {e}[/red]")
511
+ stderr_console.print(f"[red]Error: {e}[/red]")
505
512
  raise typer.Exit(1)
506
513
 
507
- with workspace_directory_context(resolved_workspace, root, console) as path_context:
514
+ with workspace_directory_context(resolved_workspace, root, stderr_console) as path_context:
508
515
  try:
509
516
  base_dir = Path.cwd()
510
517
 
@@ -518,15 +525,17 @@ def run(
518
525
  if not agent_name:
519
526
  raise ValueError("agent name missing from session_start")
520
527
  except Exception:
521
- console.print(f"[red]Could not load metadata for conversation: {history_opts.continue_id}[/red]")
528
+ stderr_console.print(
529
+ f"[red]Could not load metadata for conversation: {history_opts.continue_id}[/red]"
530
+ )
522
531
  raise typer.Exit(1)
523
532
 
524
- console.print(f"[cyan]Auto-detected agent from conversation: {agent_name}[/cyan]")
533
+ stderr_console.print(f"[cyan]Auto-detected agent from conversation: {agent_name}[/cyan]")
525
534
  agent_refs = [f"+{agent_name}"]
526
535
 
527
536
  # Handle multi-agent mode: first agent is primary, rest are allowed to spawn
528
537
  if not agent_refs:
529
- console.print("[red]Error: No agent specified[/red]")
538
+ stderr_console.print("[red]Error: No agent specified[/red]")
530
539
  raise typer.Exit(1)
531
540
 
532
541
  from tsugite.agent_runner.helpers import set_allowed_agents
@@ -537,27 +546,23 @@ def run(
537
546
  if len(agent_refs) > 1:
538
547
  allowed_agent_names = []
539
548
  for allowed_ref in agent_refs[1:]:
540
- _, allowed_file, _ = load_and_validate_agent(allowed_ref, console)
549
+ _, allowed_file, _ = load_and_validate_agent(allowed_ref, stderr_console)
541
550
  allowed_agent = parse_agent_file(allowed_file)
542
551
  allowed_agent_names.append(allowed_agent.config.name)
543
552
 
544
553
  set_allowed_agents(allowed_agent_names)
545
- console.print(f"[cyan]Allowed agents to spawn: {', '.join(allowed_agent_names)}[/cyan]")
554
+ stderr_console.print(f"[cyan]Allowed agents to spawn: {', '.join(allowed_agent_names)}[/cyan]")
546
555
  else:
547
556
  set_allowed_agents(None)
548
557
 
549
- _, agent_file, _ = load_and_validate_agent(primary_agent_ref, console)
558
+ _, agent_file, _ = load_and_validate_agent(primary_agent_ref, stderr_console)
550
559
 
551
560
  except ValueError as e:
552
- console.print(f"[red]Error: {e}[/red]")
561
+ stderr_console.print(f"[red]Error: {e}[/red]")
553
562
  raise typer.Exit(1)
554
563
 
555
564
  use_plain_output = ui_opts.plain or should_use_plain_output()
556
565
 
557
- from tsugite.console import get_stderr_console
558
-
559
- stderr_console = get_stderr_console(no_color=ui_opts.no_color)
560
-
561
566
  # Print deferred workspace status messages
562
567
  if not ui_opts.headless and not ui_opts.final_only:
563
568
  if workspace_to_use and not workspace:
@@ -591,7 +596,7 @@ def run(
591
596
  cli_attachments=attach_opts.sources,
592
597
  base_dir=base_dir,
593
598
  refresh_cache=attach_opts.refresh_cache,
594
- console=console,
599
+ console=stderr_console,
595
600
  stdin_attachment=stdin_attachment,
596
601
  )
597
602
 
@@ -624,7 +629,7 @@ def run(
624
629
 
625
630
  is_valid, error_msg = validate_agent_execution(agent_file)
626
631
  if not is_valid:
627
- get_error_console(ui_opts.headless, console).print(f"[red]Agent validation failed: {error_msg}[/red]")
632
+ stderr_console.print(f"[red]Agent validation failed: {error_msg}[/red]")
628
633
  raise typer.Exit(1)
629
634
 
630
635
  from tsugite.agent_runner import preview_multistep_agent, run_multistep_agent
@@ -638,11 +643,11 @@ def run(
638
643
  preview_multistep_agent(
639
644
  agent_path=agent_file,
640
645
  prompt=prompt,
641
- console=console,
646
+ console=stderr_console,
642
647
  )
643
648
  else:
644
- console.print("[yellow]Dry-run mode is for multi-step agents only.[/yellow]")
645
- console.print("[dim]This is a single-step agent. Use --debug to see the rendered prompt.[/dim]")
649
+ stderr_console.print("[yellow]Dry-run mode is for multi-step agents only.[/yellow]")
650
+ stderr_console.print("[dim]This is a single-step agent. Use --debug to see the rendered prompt.[/dim]")
646
651
  return
647
652
 
648
653
  executor = run_multistep_agent if is_multistep else run_agent
@@ -708,7 +713,7 @@ def run(
708
713
  executor_kwargs,
709
714
  ui_opts,
710
715
  use_plain_output,
711
- console,
716
+ stderr_console,
712
717
  )
713
718
 
714
719
  (
@@ -734,20 +739,19 @@ def run(
734
739
 
735
740
  except ValueError as e:
736
741
  _save_history(status="error", error_message=str(e))
737
- get_error_console(ui_opts.headless, console).print(f"[red]Configuration error: {e}[/red]")
742
+ stderr_console.print(f"[red]Configuration error: {e}[/red]")
738
743
  raise typer.Exit(1)
739
744
  except RuntimeError as e:
740
745
  _save_history(status="error", error_message=str(e))
741
- get_error_console(ui_opts.headless, console).print(f"[red]Execution error: {e}[/red]")
746
+ stderr_console.print(f"[red]Execution error: {e}[/red]")
742
747
  raise typer.Exit(1)
743
748
  except KeyboardInterrupt:
744
749
  _save_history(status="interrupted")
745
- get_error_console(ui_opts.headless, console).print("\n[yellow]Agent execution interrupted by user[/yellow]")
750
+ stderr_console.print("\n[yellow]Agent execution interrupted by user[/yellow]")
746
751
  raise typer.Exit(130)
747
752
  except Exception as e:
748
753
  _save_history(status="error", error_message=str(e))
749
- err_console = get_error_console(ui_opts.headless, console)
750
- err_console.print(f"[red]Unexpected error: {e}[/red]")
754
+ stderr_console.print(f"[red]Unexpected error: {e}[/red]")
751
755
  if not ui_opts.log_json:
752
- err_console.print("\n[dim]Use --log-json for machine-readable output[/dim]")
756
+ stderr_console.print("\n[dim]Use --log-json for machine-readable output[/dim]")
753
757
  raise typer.Exit(1)
@@ -431,6 +431,7 @@ class BaseAdapter(ABC):
431
431
  scheduled_xml = render_iso_element("scheduled_for", meta.get("scheduled_for"), tz, tz_label, now)
432
432
  actual_xml = render_iso_element("actual_fire_time", meta.get("actual_fire_time"), tz, tz_label, now)
433
433
  scheduler_timing_xml = scheduled_xml + actual_xml
434
+ context_limit_for_render = self.session_store.get_context_limit(self.agent_name)
434
435
  try:
435
436
  conv_id_override = (channel_context.metadata or {}).get("conv_id_override")
436
437
  if conv_id_override:
@@ -440,6 +441,7 @@ class BaseAdapter(ABC):
440
441
  if session is None:
441
442
  raise ValueError("no default session yet")
442
443
  tokens_used = session.cumulative_tokens
444
+ context_limit_for_render = self.session_store.get_session_context_limit(session.id)
443
445
  session_started_xml = render_iso_element("session_started", session.created_at, tz, tz_label, now)
444
446
  last_active_xml = render_iso_element("last_active", session.last_active, tz, tz_label, now)
445
447
  if session.metadata:
@@ -465,7 +467,7 @@ class BaseAdapter(ABC):
465
467
  <source>{channel_context.source}</source>
466
468
  <user_id>{channel_context.user_id}</user_id>
467
469
  <context_tokens_used>{tokens_used}</context_tokens_used>
468
- <context_limit>{self.agent_config.context_limit}</context_limit>{session_topic_xml}{session_meta_xml}{scratchpad_xml}
470
+ <context_limit>{context_limit_for_render}</context_limit>{session_topic_xml}{session_meta_xml}{scratchpad_xml}
469
471
  </message_context>
470
472
 
471
473
  {message}"""
@@ -684,8 +686,10 @@ class BaseAdapter(ABC):
684
686
 
685
687
  ps = getattr(result, "provider_state", None) or {}
686
688
  if ps.get("context_window"):
687
- self.session_store.update_context_limit(self.agent_name, ps["context_window"])
688
- self.agent_config.context_limit = ps["context_window"]
689
+ # Per-session storage: this turn's reported window applies to THIS
690
+ # session only. An agent-wide scalar would let any other turn (or a
691
+ # secondary model call) clobber the displayed limit.
692
+ self.session_store.update_session_context_limit(conv_id, ps["context_window"])
689
693
 
690
694
  last_input = getattr(result, "last_input_tokens", None)
691
695
  context_tokens = last_input if isinstance(last_input, int) and last_input > 0 else (result.token_count or 0)
@@ -866,7 +870,27 @@ class BaseAdapter(ABC):
866
870
  use the returned session for downstream id-keyed work; rediscovering
867
871
  via `find_default_session` is unreliable for non-default or
868
872
  non-interactive sessions.
873
+
874
+ Defensive snapshot/restore of the agent-wide context-limit fallback. The
875
+ primary per-session limit lives on `Session.context_limit` and isn't
876
+ touched here; this guard catches any future code path that mutates the
877
+ agent-wide default during the compaction flow.
869
878
  """
879
+ saved_session_store_limit = self.session_store.get_context_limit(self.agent_name)
880
+ saved_agent_config_limit = self.agent_config.context_limit
881
+ try:
882
+ return await self._compact_session_inner(session_id, instructions, reason, progress_callback)
883
+ finally:
884
+ self.session_store.update_context_limit(self.agent_name, saved_session_store_limit)
885
+ self.agent_config.context_limit = saved_agent_config_limit
886
+
887
+ async def _compact_session_inner(
888
+ self,
889
+ session_id: str,
890
+ instructions: str | None,
891
+ reason: str | None,
892
+ progress_callback: Optional[Callable[[Dict[str, Any]], None]],
893
+ ) -> Optional[Session]:
870
894
  if instructions is None:
871
895
  instructions = self._DEFAULT_COMPACT_INSTRUCTIONS
872
896
  from tsugite.daemon.memory import (
@@ -894,7 +918,12 @@ class BaseAdapter(ABC):
894
918
  None,
895
919
  )
896
920
 
897
- context_limit = get_context_limit(model, fallback=self.agent_config.context_limit)
921
+ # Fallback to the session's tracked window (set from the main model's
922
+ # last reported context_window) rather than the agent-wide scalar so
923
+ # sessions with different model overrides compute their own correct
924
+ # retention budget.
925
+ session_limit_fallback = self.session_store.get_session_context_limit(session_id)
926
+ context_limit = get_context_limit(model, fallback=session_limit_fallback)
898
927
  retention_budget = int(context_limit * RETENTION_BUDGET_RATIO)
899
928
 
900
929
  old_events, recent_events = split_events_for_compaction(all_events, model, retention_budget)
@@ -1002,25 +1031,16 @@ class BaseAdapter(ABC):
1002
1031
 
1003
1032
  old_messages = sanitize_for_summary(old_messages, model=model, attachment_basenames=attachment_basenames)
1004
1033
 
1005
- # Snapshot the agent's tracked context limit before summarization so
1006
- # that any mutation during the call (e.g. provider state leakage from
1007
- # a smaller compact model) doesn't corrupt the displayed value or
1008
- # the next compaction-threshold computation.
1009
- saved_session_store_limit = self.session_store.get_context_limit(self.agent_name)
1010
- saved_agent_config_limit = self.agent_config.context_limit
1011
1034
  try:
1012
1035
  summary = await summarize_session(
1013
1036
  old_messages,
1014
1037
  model=model,
1015
- max_context_tokens=self.agent_config.context_limit,
1038
+ max_context_tokens=session_limit_fallback,
1016
1039
  progress_callback=progress_callback,
1017
1040
  )
1018
1041
  except Exception:
1019
1042
  logger.exception("[%s] Compaction summarization failed", self.agent_name)
1020
1043
  raise
1021
- finally:
1022
- self.session_store.update_context_limit(self.agent_name, saved_session_store_limit)
1023
- self.agent_config.context_limit = saved_agent_config_limit
1024
1044
 
1025
1045
  new_session = self.session_store.compact_session(session_id)
1026
1046
  new_session_path = get_history_dir() / f"{new_session.id}.jsonl"
@@ -1044,7 +1064,14 @@ class BaseAdapter(ABC):
1044
1064
  source_session_id=old_conv_id,
1045
1065
  )
1046
1066
  for event in recent_events:
1047
- new_storage.record(event.type, **event.data)
1067
+ data = event.data
1068
+ if event.type == "model_response" and "state_delta" in data:
1069
+ # state_delta holds provider-specific runtime IDs (e.g. claude_code
1070
+ # session_id, compaction flags) tied to the pre-compaction session.
1071
+ # Carrying them forward causes the next turn to resume the old
1072
+ # Claude Code session and bypass compaction entirely.
1073
+ data = {k: v for k, v in data.items() if k != "state_delta"}
1074
+ new_storage.record(event.type, **data)
1048
1075
 
1049
1076
  post_compact_execs = await fire_compact_hooks(
1050
1077
  self.agent_config.workspace_dir,
@@ -892,7 +892,7 @@ class DiscordAdapter(BaseAdapter):
892
892
  backoff = 5
893
893
  for attempt in range(max_retries):
894
894
  try:
895
- await self.bot.start(self.bot_config.token)
895
+ await self.bot.start(self.bot_config.resolve_token())
896
896
  break
897
897
  except (discord.errors.DiscordServerError, OSError) as e:
898
898
  if attempt < max_retries - 1:
@@ -7,7 +7,7 @@ import mimetypes
7
7
  import re
8
8
  import shutil
9
9
  import threading
10
- from dataclasses import asdict, dataclass
10
+ from dataclasses import dataclass
11
11
  from dataclasses import fields as dataclass_fields
12
12
  from datetime import datetime, timezone
13
13
  from pathlib import Path
@@ -28,7 +28,7 @@ from tsugite.attachments.base import AttachmentContentType
28
28
  from tsugite.attachments.file import FileHandler
29
29
  from tsugite.daemon.adapters.base import _PERSIST_EVENT_TYPES, BaseAdapter, ChannelContext
30
30
  from tsugite.daemon.config import AgentConfig, HTTPConfig
31
- from tsugite.daemon.scheduler import ScheduleEntry
31
+ from tsugite.daemon.scheduler import ScheduleEntry, entry_to_dict
32
32
  from tsugite.daemon.webhook_store import WebhookStore
33
33
  from tsugite.events.base import BaseEvent
34
34
  from tsugite.history.storage import get_history_dir
@@ -836,8 +836,16 @@ class HTTPServer:
836
836
  "model": model,
837
837
  "resolved_model": resolved_model if resolved_model != model else None,
838
838
  "tokens": tokens,
839
- "context_limit": adapter.session_store.get_context_limit(adapter.agent_name),
840
- "threshold": adapter.session_store.get_compaction_threshold(adapter.agent_name),
839
+ "context_limit": (
840
+ adapter.session_store.get_session_context_limit(session.id)
841
+ if session
842
+ else adapter.session_store.get_context_limit(adapter.agent_name)
843
+ ),
844
+ "threshold": (
845
+ adapter.session_store.get_session_compaction_threshold(session.id)
846
+ if session
847
+ else adapter.session_store.get_compaction_threshold(adapter.agent_name)
848
+ ),
841
849
  "message_count": message_count,
842
850
  "compacting": adapter.session_store.is_compacting(
843
851
  user_id, adapter.agent_name, session_id=session.id if session else None
@@ -860,6 +868,10 @@ class HTTPServer:
860
868
 
861
869
  if "model" in body:
862
870
  new_model = body["model"].strip() if body["model"] else None
871
+ # Pin existing sessions to the current model before mutating the
872
+ # agent default so they don't silently switch on their next turn.
873
+ # The default change should only affect sessions created after it.
874
+ adapter.session_store.freeze_session_models_to_current(adapter.agent_name, agent_config.model)
863
875
  agent_config.model = new_model
864
876
 
865
877
  from tsugite.daemon.memory import DEFAULT_CONTEXT_LIMIT, get_context_limit
@@ -1469,8 +1481,8 @@ class HTTPServer:
1469
1481
  {
1470
1482
  "session_id": target_session_id,
1471
1483
  "tokens": refreshed.cumulative_tokens,
1472
- "context_limit": adapter.session_store.get_context_limit(adapter.agent_name),
1473
- "threshold": adapter.session_store.get_compaction_threshold(adapter.agent_name),
1484
+ "context_limit": adapter.session_store.get_session_context_limit(target_session_id),
1485
+ "threshold": adapter.session_store.get_session_compaction_threshold(target_session_id),
1474
1486
  "message_count": refreshed.message_count,
1475
1487
  "model": adapter.resolve_model(),
1476
1488
  "attachments": [a.name for a in adapter._get_all_attachments()],
@@ -1541,7 +1553,7 @@ class HTTPServer:
1541
1553
  async def _list_schedules(self, request: Request) -> JSONResponse:
1542
1554
  if err := self._require_auth_and_scheduler(request):
1543
1555
  return err
1544
- return JSONResponse({"schedules": [asdict(e) for e in self.scheduler.list()]})
1556
+ return JSONResponse({"schedules": [entry_to_dict(e) for e in self.scheduler.list()]})
1545
1557
 
1546
1558
  async def _create_schedule(self, request: Request) -> JSONResponse:
1547
1559
  if err := self._require_auth_and_scheduler(request):
@@ -1565,7 +1577,7 @@ class HTTPServer:
1565
1577
  return JSONResponse({"error": str(e)}, status_code=400)
1566
1578
 
1567
1579
  self.event_bus.emit("schedule_update", {"action": "created", "id": entry.id})
1568
- return JSONResponse(asdict(entry), status_code=201)
1580
+ return JSONResponse(entry_to_dict(entry), status_code=201)
1569
1581
 
1570
1582
  async def _get_schedule(self, request: Request) -> JSONResponse:
1571
1583
  if err := self._require_auth_and_scheduler(request):
@@ -1574,7 +1586,7 @@ class HTTPServer:
1574
1586
  entry = self.scheduler.get(request.path_params["schedule_id"])
1575
1587
  except ValueError as e:
1576
1588
  return JSONResponse({"error": str(e)}, status_code=404)
1577
- return JSONResponse(asdict(entry))
1589
+ return JSONResponse(entry_to_dict(entry))
1578
1590
 
1579
1591
  async def _update_schedule(self, request: Request) -> JSONResponse:
1580
1592
  if err := self._require_auth_and_scheduler(request):
@@ -1609,7 +1621,7 @@ class HTTPServer:
1609
1621
  except ValueError as e:
1610
1622
  return JSONResponse({"error": str(e)}, status_code=400)
1611
1623
  self.event_bus.emit("schedule_update", {"action": "updated", "id": schedule_id})
1612
- return JSONResponse(asdict(entry))
1624
+ return JSONResponse(entry_to_dict(entry))
1613
1625
 
1614
1626
  async def _delete_schedule(self, request: Request) -> JSONResponse:
1615
1627
  if err := self._require_auth_and_scheduler(request):