toolang 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. {toolang-0.2.1 → toolang-0.2.2}/PKG-INFO +1 -1
  2. {toolang-0.2.1 → toolang-0.2.2}/pyproject.toml +1 -1
  3. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/progress.py +7 -0
  4. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/toolang/app.py +43 -18
  5. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/utils.py +26 -17
  6. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/config/log.py +21 -4
  7. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/up.py +11 -1
  8. {toolang-0.2.1 → toolang-0.2.2}/tests/test_cli.py +103 -7
  9. {toolang-0.2.1 → toolang-0.2.2}/tests/test_log_config.py +36 -0
  10. {toolang-0.2.1 → toolang-0.2.2}/tests/test_run_queue_runner.py +46 -0
  11. {toolang-0.2.1 → toolang-0.2.2}/uv.lock +1 -1
  12. {toolang-0.2.1 → toolang-0.2.2}/.github/workflows/publish-pypi.yml +0 -0
  13. {toolang-0.2.1 → toolang-0.2.2}/.gitignore +0 -0
  14. {toolang-0.2.1 → toolang-0.2.2}/AGENTS.md +0 -0
  15. {toolang-0.2.1 → toolang-0.2.2}/LICENSE +0 -0
  16. {toolang-0.2.1 → toolang-0.2.2}/README.md +0 -0
  17. {toolang-0.2.1 → toolang-0.2.2}/docs/api.md +0 -0
  18. {toolang-0.2.1 → toolang-0.2.2}/docs/caps.md +0 -0
  19. {toolang-0.2.1 → toolang-0.2.2}/docs/chat.md +0 -0
  20. {toolang-0.2.1 → toolang-0.2.2}/docs/concepts.md +0 -0
  21. {toolang-0.2.1 → toolang-0.2.2}/docs/execution.md +0 -0
  22. {toolang-0.2.1 → toolang-0.2.2}/docs/ids.md +0 -0
  23. {toolang-0.2.1 → toolang-0.2.2}/docs/index.md +0 -0
  24. {toolang-0.2.1 → toolang-0.2.2}/docs/layout.md +0 -0
  25. {toolang-0.2.1 → toolang-0.2.2}/docs/models.md +0 -0
  26. {toolang-0.2.1 → toolang-0.2.2}/docs/plugins.md +0 -0
  27. {toolang-0.2.1 → toolang-0.2.2}/docs/prepared-lock.md +0 -0
  28. {toolang-0.2.1 → toolang-0.2.2}/docs/program.md +0 -0
  29. {toolang-0.2.1 → toolang-0.2.2}/docs/selectors.md +0 -0
  30. {toolang-0.2.1 → toolang-0.2.2}/docs/tasks.md +0 -0
  31. {toolang-0.2.1 → toolang-0.2.2}/docs/tools.md +0 -0
  32. {toolang-0.2.1 → toolang-0.2.2}/docs/webui-jobs.md +0 -0
  33. {toolang-0.2.1 → toolang-0.2.2}/examples/alice.too +0 -0
  34. {toolang-0.2.1 → toolang-0.2.2}/examples/invoke-playground.too +0 -0
  35. {toolang-0.2.1 → toolang-0.2.2}/examples/invoke.fixed-model.too +0 -0
  36. {toolang-0.2.1 → toolang-0.2.2}/examples/invoke.openrouter-smoke.too +0 -0
  37. {toolang-0.2.1 → toolang-0.2.2}/examples/invoke.priority.too +0 -0
  38. {toolang-0.2.1 → toolang-0.2.2}/examples/invoke.simulated-history.memory.too +0 -0
  39. {toolang-0.2.1 → toolang-0.2.2}/examples/invoke.simulated-history.none.too +0 -0
  40. {toolang-0.2.1 → toolang-0.2.2}/reference/README.md +0 -0
  41. {toolang-0.2.1 → toolang-0.2.2}/reference/build.py +0 -0
  42. {toolang-0.2.1 → toolang-0.2.2}/reference/config.toml +0 -0
  43. {toolang-0.2.1 → toolang-0.2.2}/reference/templates/index.html.jinja2 +0 -0
  44. {toolang-0.2.1 → toolang-0.2.2}/reference/templates/module.html.jinja2 +0 -0
  45. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/__init__.py +0 -0
  46. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/agents.py +0 -0
  47. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/__init__.py +0 -0
  48. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/error.py +0 -0
  49. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/examples/__init__.py +0 -0
  50. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/examples/tools.py +0 -0
  51. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/protocols/__init__.py +0 -0
  52. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/protocols/channel.py +0 -0
  53. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/protocols/loop.py +0 -0
  54. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/protocols/model.py +0 -0
  55. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/protocols/sandbox.py +0 -0
  56. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/protocols/tool.py +0 -0
  57. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/types/__init__.py +0 -0
  58. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/types/channel.py +0 -0
  59. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/types/message.py +0 -0
  60. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/types/model.py +0 -0
  61. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/types/run.py +0 -0
  62. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/types/sandbox.py +0 -0
  63. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/types/tool.py +0 -0
  64. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/utils/__init__.py +0 -0
  65. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/utils/channels.py +0 -0
  66. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/utils/function_tools.py +0 -0
  67. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/utils/tools.py +0 -0
  68. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/base/utils/typer_tools.py +0 -0
  69. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/caps.py +0 -0
  70. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/channels/__init__.py +0 -0
  71. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/channels/telegram.py +0 -0
  72. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/__init__.py +0 -0
  73. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/__main__.py +0 -0
  74. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/caps/__init__.py +0 -0
  75. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/caps/__main__.py +0 -0
  76. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/caps/app.py +0 -0
  77. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/caps/commands.py +0 -0
  78. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/invoke.py +0 -0
  79. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/logo.py +0 -0
  80. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/toolang/__init__.py +0 -0
  81. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/toolang/__main__.py +0 -0
  82. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/toolang/caps.py +0 -0
  83. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/cli/toolang/fmt.py +0 -0
  84. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/common/__init__.py +0 -0
  85. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/common/ids.py +0 -0
  86. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/common/progress.py +0 -0
  87. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/common/template.py +0 -0
  88. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/__init__.py +0 -0
  89. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/registry.py +0 -0
  90. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/__init__.py +0 -0
  91. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/_streaming.py +0 -0
  92. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/chat.py +0 -0
  93. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/inspect/__init__.py +0 -0
  94. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/inspect/_shared.py +0 -0
  95. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/inspect/agent.py +0 -0
  96. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/inspect/caps.py +0 -0
  97. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/inspect/execution.py +0 -0
  98. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/inspect/jobs.py +0 -0
  99. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/inspect/router.py +0 -0
  100. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/manage/__init__.py +0 -0
  101. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/manage/caps.py +0 -0
  102. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/manage/jobs.py +0 -0
  103. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/router/manage/router.py +0 -0
  104. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/runner/__init__.py +0 -0
  105. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/trigger/__init__.py +0 -0
  106. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/trigger/poll.py +0 -0
  107. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/trigger/pulse.py +0 -0
  108. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/components/trigger/watch.py +0 -0
  109. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/config/__init__.py +0 -0
  110. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/config/env.py +0 -0
  111. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/config/log_spec.py +0 -0
  112. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/config/plugins.py +0 -0
  113. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/config/web.py +0 -0
  114. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/__init__.py +0 -0
  115. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/assembly.py +0 -0
  116. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/binding.py +0 -0
  117. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/context.py +0 -0
  118. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/db.py +0 -0
  119. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/detail.py +0 -0
  120. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/effective.py +0 -0
  121. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/events.py +0 -0
  122. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/execute.py +0 -0
  123. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/input.py +0 -0
  124. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/model.py +0 -0
  125. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/model_call.py +0 -0
  126. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/prompts/__init__.py +0 -0
  127. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/prompts/context.default.md +0 -0
  128. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/prompts/instruct.default.md +0 -0
  129. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/records.py +0 -0
  130. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/response.py +0 -0
  131. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/runner.py +0 -0
  132. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/execution/stream.py +0 -0
  133. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/loops/__init__.py +0 -0
  134. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/loops/basic.py +0 -0
  135. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/__init__.py +0 -0
  136. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/adapters/__init__.py +0 -0
  137. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/adapters/chat_completions.py +0 -0
  138. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/adapters/responses.py +0 -0
  139. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/config.py +0 -0
  140. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/discovery.py +0 -0
  141. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/errors.py +0 -0
  142. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/providers/__init__.py +0 -0
  143. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/providers/custom.py +0 -0
  144. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/providers/deepseek.py +0 -0
  145. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/providers/google.py +0 -0
  146. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/providers/ollama.py +0 -0
  147. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/providers/openai.py +0 -0
  148. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/providers/openrouter.py +0 -0
  149. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/resolution.py +0 -0
  150. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/models/views.py +0 -0
  151. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/plugin.py +0 -0
  152. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/program.py +0 -0
  153. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/program_format.py +0 -0
  154. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/sandboxes/__init__.py +0 -0
  155. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/sandboxes/docker.py +0 -0
  156. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/sandboxes/none.py +0 -0
  157. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/selectors.py +0 -0
  158. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/state/__init__.py +0 -0
  159. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/state/durable.py +0 -0
  160. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/state/live.py +0 -0
  161. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/state/prepared.py +0 -0
  162. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/state/program.py +0 -0
  163. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/state/pulse.py +0 -0
  164. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/templates/__init__.py +0 -0
  165. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/templates/agent.default.too +0 -0
  166. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/templates/chore.default.md +0 -0
  167. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/templates/prompt.default.md +0 -0
  168. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/templates/psyche.default.md +0 -0
  169. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/templates/service.default.md +0 -0
  170. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/templates/service.stdio.md +0 -0
  171. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/templates/skill.default.md +0 -0
  172. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/templates/task.default.md +0 -0
  173. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/tools/__init__.py +0 -0
  174. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/tools/agent_chat.py +0 -0
  175. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/tools/agent_state.py +0 -0
  176. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/tools/filesystem.py +0 -0
  177. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/tools/registry.py +0 -0
  178. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/tools/service_use.py +0 -0
  179. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/tools/shell.py +0 -0
  180. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/tools/views.py +0 -0
  181. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/tools/web_search.py +0 -0
  182. {toolang-0.2.1 → toolang-0.2.2}/src/toolang/work.py +0 -0
  183. {toolang-0.2.1 → toolang-0.2.2}/syntax.too +0 -0
  184. {toolang-0.2.1 → toolang-0.2.2}/tests/fixtures/remote-prompt/rewrite.md +0 -0
  185. {toolang-0.2.1 → toolang-0.2.2}/tests/fixtures/remote-psyche/reviewer.md +0 -0
  186. {toolang-0.2.1 → toolang-0.2.2}/tests/fixtures/remote-service/github.md +0 -0
  187. {toolang-0.2.1 → toolang-0.2.2}/tests/fixtures/remote-skill/pdf-processing/SKILL.md +0 -0
  188. {toolang-0.2.1 → toolang-0.2.2}/tests/fixtures/remote-skill/pdf-processing/assets/template.md +0 -0
  189. {toolang-0.2.1 → toolang-0.2.2}/tests/fixtures/sample.too +0 -0
  190. {toolang-0.2.1 → toolang-0.2.2}/tests/fixtures/source_only.too +0 -0
  191. {toolang-0.2.1 → toolang-0.2.2}/tests/test_agent_state_tool.py +0 -0
  192. {toolang-0.2.1 → toolang-0.2.2}/tests/test_channels.py +0 -0
  193. {toolang-0.2.1 → toolang-0.2.2}/tests/test_execution_template.py +0 -0
  194. {toolang-0.2.1 → toolang-0.2.2}/tests/test_ids.py +0 -0
  195. {toolang-0.2.1 → toolang-0.2.2}/tests/test_models.py +0 -0
  196. {toolang-0.2.1 → toolang-0.2.2}/tests/test_plugin_tools.py +0 -0
  197. {toolang-0.2.1 → toolang-0.2.2}/tests/test_prepared.py +0 -0
  198. {toolang-0.2.1 → toolang-0.2.2}/tests/test_program.py +0 -0
  199. {toolang-0.2.1 → toolang-0.2.2}/tests/test_program_format.py +0 -0
  200. {toolang-0.2.1 → toolang-0.2.2}/tests/test_sandboxes.py +0 -0
  201. {toolang-0.2.1 → toolang-0.2.2}/tests/test_selectors.py +0 -0
  202. {toolang-0.2.1 → toolang-0.2.2}/tests/test_tools.py +0 -0
  203. {toolang-0.2.1 → toolang-0.2.2}/tests/test_typer_tools.py +0 -0
  204. {toolang-0.2.1 → toolang-0.2.2}/tests/test_work.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: toolang
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: A programming language and runtime for agents
5
5
  Project-URL: Homepage, https://toolang.ai/
6
6
  Project-URL: Documentation, https://toolang.ai/docs
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "toolang"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "A programming language and runtime for agents"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -83,6 +83,7 @@ class CliProgress:
83
83
  for line in self._lines():
84
84
  print(line, file=self._stream)
85
85
  self._print_summary()
86
+ self._reset_terminal_style()
86
87
 
87
88
  def interrupt(self) -> None:
88
89
  with self._lock:
@@ -290,6 +291,12 @@ class CliProgress:
290
291
  for summary in summaries:
291
292
  self._console.print(Text(summary, style="dim"))
292
293
 
294
+ def _reset_terminal_style(self) -> None:
295
+ if not self._console.is_terminal:
296
+ return
297
+ self._stream.write("\x1b[0m")
298
+ self._stream.flush()
299
+
293
300
  def _live_text(self) -> Text:
294
301
  text = Text()
295
302
  agent_items = [item for item in self._items.values() if item.kind == "agent"]
@@ -38,7 +38,6 @@ from ..utils import (
38
38
  _required_prefix_agent,
39
39
  _required_runtime_agent,
40
40
  _runtime_environ_for_agent,
41
- _runtime_components,
42
41
  _runtime_value,
43
42
  _toolang_root,
44
43
  _ui_base_url,
@@ -401,6 +400,7 @@ def info_agent(
401
400
  ("Home", str(agents.agent_home(root, agent_name))),
402
401
  ("Caps", _info_caps_summary(root, agent_name)),
403
402
  ("Jobs", _info_jobs_summary(root, agent_name)),
403
+ ("Tools", _info_tools_summary(root, agent_name)),
404
404
  ("Models", _info_models_summary(root, agent_name, runtime_state=runtime_state, running=status.status != "stopped")),
405
405
  ("Status", status_value),
406
406
  ]
@@ -410,16 +410,12 @@ def info_agent(
410
410
  return
411
411
  if status.sandbox:
412
412
  rows.append(("Sandbox", status.sandbox))
413
- if status.status == "running":
414
- loops_text = _runtime_components(runtime_state)
415
- if loops_text is not None:
416
- rows.append(("Components", loops_text))
417
413
  message = _runtime_value(runtime_state.get("message"))
418
414
  pid_text = agents.runtime_pid_label(runtime_state)
419
415
  if pid_text is not None and status.status != "stopped":
420
416
  rows.append(("PID", pid_text))
421
- if status.api_url:
422
- rows.append(("API", status.api_url))
417
+ if status.endpoint:
418
+ rows.append(("API", status.endpoint))
423
419
  if status.webui_url:
424
420
  rows.append(("WebUI", status.webui_url))
425
421
  if status.status == "running" and started_at != "-":
@@ -875,19 +871,46 @@ def _info_models_summary(
875
871
  if running:
876
872
  raw_models = runtime_state.get("models")
877
873
  if isinstance(raw_models, list):
878
- runtime_models = tuple(
874
+ selectors = tuple(
879
875
  str(item).strip()
880
876
  for item in raw_models
881
877
  if isinstance(item, str) and item.strip()
882
878
  )
883
- if runtime_models:
884
- return ", ".join(runtime_models)
885
- configured = agent_up.load_default_models(toolang_root, agent_name)
886
- if configured:
887
- return ", ".join(configured)
888
- from ...models.resolution import DEFAULT_MODEL_SELECTOR
879
+ if selectors:
880
+ return _model_count_summary(
881
+ toolang_root,
882
+ agent_name,
883
+ environ=dict(os.environ),
884
+ selectors=selectors,
885
+ )
886
+ selectors = agent_up.load_default_models(toolang_root, agent_name)
887
+ return _model_count_summary(
888
+ toolang_root,
889
+ agent_name,
890
+ environ=dict(os.environ),
891
+ selectors=selectors,
892
+ )
893
+
889
894
 
890
- return DEFAULT_MODEL_SELECTOR
895
+ def _info_tools_summary(toolang_root: Path, agent_name: str) -> str:
896
+ rows = _tool_rows(toolang_root, dict(os.environ), agent_name=agent_name)
897
+ set_count = len({namespace for namespace, _tool, _description in rows})
898
+ return f"{len(rows)} {'tool' if len(rows) == 1 else 'tools'}, {set_count} {'set' if set_count == 1 else 'sets'}"
899
+
900
+
901
+ def _model_count_summary(
902
+ toolang_root: Path,
903
+ agent_name: str,
904
+ *,
905
+ environ: dict[str, str],
906
+ selectors: Sequence[str] = (),
907
+ ) -> str:
908
+ rows = _model_rows(toolang_root, environ, agent_name=agent_name, model_selectors=selectors)
909
+ provider_count = len({provider for _model, provider, _details in rows})
910
+ return (
911
+ f"{len(rows)} {'model' if len(rows) == 1 else 'models'}, "
912
+ f"{provider_count} {'provider' if provider_count == 1 else 'providers'}"
913
+ )
891
914
 
892
915
 
893
916
  model_app = typer.Typer(
@@ -1032,13 +1055,14 @@ def _model_rows(
1032
1055
  root: Path,
1033
1056
  environ: dict[str, str],
1034
1057
  *,
1058
+ agent_name: str = "",
1035
1059
  model_selectors: Sequence[str] = (),
1036
1060
  ) -> list[tuple[str, str, str]]:
1037
1061
  from ...models.config import load_model_aliases
1038
1062
  from ...models.views import model_list_rows
1039
1063
 
1040
- providers = agent_up.load_model_providers(root, "")
1041
- aliases = load_model_aliases(root, "")
1064
+ providers = agent_up.load_model_providers(root, agent_name)
1065
+ aliases = load_model_aliases(root, agent_name)
1042
1066
  return model_list_rows(
1043
1067
  providers=providers,
1044
1068
  aliases=aliases,
@@ -1066,12 +1090,13 @@ def _tool_rows(
1066
1090
  root: Path,
1067
1091
  environ: dict[str, str],
1068
1092
  *,
1093
+ agent_name: str = "",
1069
1094
  tool_selectors: Sequence[str] = (),
1070
1095
  ) -> list[tuple[str, str, str]]:
1071
1096
  from ...config.plugins import load_tool_plugin_config
1072
1097
  from ...tools.views import tool_list_rows
1073
1098
 
1074
- config = load_tool_plugin_config(root, "", environ=environ)
1099
+ config = load_tool_plugin_config(root, agent_name, environ=environ)
1075
1100
  tools = agent_up.load_tool_plugins(config=config)
1076
1101
  sources = _plugin_source_by_name("toolang.tool")
1077
1102
  return tool_list_rows(
@@ -30,6 +30,7 @@ if TYPE_CHECKING:
30
30
  # weight so usage notes remain easy to read in terminal help output.
31
31
  setattr(typer_rich_utils, "STYLE_HELPTEXT", "")
32
32
  _TABLE_CONSOLE = Console(highlight=False, width=4096)
33
+ _INFO_CONSOLE = Console(highlight=False)
33
34
  TableJustify = Literal["default", "left", "center", "right", "full"]
34
35
  _AGENT_AVATAR_CACHE: str | None = None
35
36
  _PALETTE_STYLES_CACHE: tuple[tuple[str, ...], tuple[str, ...]] | None = None
@@ -412,31 +413,39 @@ def _echo_pairs_table(
412
413
  collapse_padding=True,
413
414
  )
414
415
  table.add_column("FIELD", no_wrap=True, style="bold bright_cyan")
415
- table.add_column("VALUE", no_wrap=False, style="white")
416
+ table.add_column("VALUE", no_wrap=False, style="white", overflow="fold")
416
417
  for key, value in rows:
417
418
  table.add_row(Text(key), _styled_info_value(key, value))
418
419
  typer.echo()
419
420
  if avatar is None:
420
421
  if title is None:
421
- _TABLE_CONSOLE.print(table)
422
+ _INFO_CONSOLE.print(table)
422
423
  else:
423
- _TABLE_CONSOLE.print(_info_title_block(title))
424
- _TABLE_CONSOLE.print(table)
424
+ _INFO_CONSOLE.print(_info_title_block(title))
425
+ _INFO_CONSOLE.print(table)
425
426
  else:
426
- layout = Table.grid(padding=(0, 4))
427
- layout.add_column(no_wrap=True, ratio=0)
428
- layout.add_column(no_wrap=False, ratio=1)
429
- right = Table.grid(padding=(0, 0))
430
- right.add_column(no_wrap=False)
431
427
  avatar_text = _rainbow_avatar_text(avatar)
432
- if title is not None:
433
- right.add_row(_info_title_block(title))
434
- avatar_text = _rainbow_avatar_text("\n" + avatar)
435
- right.add_row(table)
436
- right.add_row(Text(""))
437
- right.add_row(_palette_block())
438
- layout.add_row(avatar_text, right)
439
- _TABLE_CONSOLE.print(layout)
428
+ if _INFO_CONSOLE.width < 100:
429
+ _INFO_CONSOLE.print(avatar_text)
430
+ if title is not None:
431
+ _INFO_CONSOLE.print(_info_title_block(title))
432
+ _INFO_CONSOLE.print(table)
433
+ _INFO_CONSOLE.print(Text(""))
434
+ _INFO_CONSOLE.print(_palette_block())
435
+ else:
436
+ layout = Table.grid(padding=(0, 4))
437
+ layout.add_column(no_wrap=True, ratio=0)
438
+ layout.add_column(no_wrap=False, ratio=1)
439
+ right = Table.grid(padding=(0, 0))
440
+ right.add_column(no_wrap=False)
441
+ if title is not None:
442
+ right.add_row(_info_title_block(title))
443
+ avatar_text = _rainbow_avatar_text("\n" + avatar)
444
+ right.add_row(table)
445
+ right.add_row(Text(""))
446
+ right.add_row(_palette_block())
447
+ layout.add_row(avatar_text, right)
448
+ _INFO_CONSOLE.print(layout)
440
449
  typer.echo()
441
450
 
442
451
 
@@ -8,6 +8,7 @@ import logging
8
8
  import logging.config
9
9
  from pathlib import Path
10
10
  import re
11
+ import sys
11
12
  from typing import Any, Literal, cast
12
13
 
13
14
  from .log_spec import (
@@ -72,13 +73,21 @@ class MessageRegexFilter(logging.Filter):
72
73
  return bool(self._pattern.search(record.getMessage()))
73
74
 
74
75
 
75
- def build_uvicorn_log_config(*, spec: LogSpec | None = None, level: str = DEFAULT_LOG_LEVEL) -> dict[str, object]:
76
+ def build_uvicorn_log_config(
77
+ *,
78
+ spec: LogSpec | None = None,
79
+ level: str = DEFAULT_LOG_LEVEL,
80
+ default_use_colors: bool | None = None,
81
+ access_use_colors: bool | None = None,
82
+ ) -> dict[str, object]:
76
83
  """Build one logging config shared by Uvicorn and runtime loggers."""
77
84
 
78
85
  ensure_custom_levels()
79
86
  log_spec = spec or resolve_log_spec(cli_value=level, environ={}, default=DEFAULT_LOG_LEVEL)
80
87
  root_level = level_name(log_spec.root_level)
81
88
  handler_level = level_name(log_spec.handler_level)
89
+ default_colors = _stream_uses_colors(sys.stderr) if default_use_colors is None else default_use_colors
90
+ access_colors = _stream_uses_colors(sys.stdout) if access_use_colors is None else access_use_colors
82
91
  return {
83
92
  "version": 1,
84
93
  "disable_existing_loggers": False,
@@ -87,13 +96,13 @@ def build_uvicorn_log_config(*, spec: LogSpec | None = None, level: str = DEFAUL
87
96
  "()": "uvicorn.logging.DefaultFormatter",
88
97
  "fmt": DEFAULT_LOG_FORMAT,
89
98
  "datefmt": DEFAULT_LOG_DATE_FORMAT,
90
- "use_colors": None,
99
+ "use_colors": default_colors,
91
100
  },
92
101
  "access": {
93
102
  "()": "uvicorn.logging.AccessFormatter",
94
103
  "fmt": DEFAULT_ACCESS_LOG_FORMAT,
95
104
  "datefmt": DEFAULT_LOG_DATE_FORMAT,
96
- "use_colors": None,
105
+ "use_colors": access_colors,
97
106
  },
98
107
  },
99
108
  "filters": {
@@ -169,7 +178,11 @@ def configure_logging(
169
178
  environ={} if environ is None else environ,
170
179
  default=DEFAULT_LOG_LEVEL,
171
180
  )
172
- config = build_uvicorn_log_config(spec=log_spec)
181
+ config = build_uvicorn_log_config(
182
+ spec=log_spec,
183
+ default_use_colors=False if log_path is not None else None,
184
+ access_use_colors=False if log_path is not None else None,
185
+ )
173
186
  if log_path is not None:
174
187
  log_path.parent.mkdir(parents=True, exist_ok=True)
175
188
  handlers = cast(dict[str, object], config.get("handlers", {}))
@@ -238,6 +251,10 @@ def _logger_level_name(target: str, spec: LogSpec, *, default: int) -> str:
238
251
  return level_name(default if level is None else level)
239
252
 
240
253
 
254
+ def _stream_uses_colors(stream: object) -> bool:
255
+ return bool(getattr(stream, "isatty", lambda: False)())
256
+
257
+
241
258
  def _install_message_filter(filter_obj: MessageRegexFilter) -> None:
242
259
  seen: set[int] = set()
243
260
  root = logging.getLogger()
@@ -46,6 +46,7 @@ from toolang.tools.registry import (
46
46
  from .config.log import (
47
47
  DEFAULT_LOG_LEVEL,
48
48
  build_uvicorn_log_config,
49
+ configure_logging,
49
50
  configure_logging_plan,
50
51
  resolve_agent_logging,
51
52
  )
@@ -777,6 +778,8 @@ def _up_local(
777
778
  progress: ProgressSink | None = None,
778
779
  prepared_state: PreparedState | None = None,
779
780
  ) -> int:
781
+ runtime_log_spec = _runtime_log_spec_value(log_spec, environ)
782
+ configure_logging(spec=runtime_log_spec, environ=environ)
780
783
  loop_intervals_ms = dict(DEFAULT_TRIGGER_INTERVAL_MS)
781
784
  for component_name in component_group(TRIGGER_COMPONENTS, "trigger"):
782
785
  if component_name in loop_intervals_ms and loop_intervals_ms[component_name] <= 0:
@@ -884,7 +887,7 @@ def _up_local(
884
887
  app,
885
888
  host=host,
886
889
  port=port,
887
- log_config=build_uvicorn_log_config(level=log_spec or DEFAULT_LOG_LEVEL),
890
+ log_config=build_uvicorn_log_config(level=runtime_log_spec or DEFAULT_LOG_LEVEL),
888
891
  shutdown_signal=shutdown_signal,
889
892
  on_starting=lambda: logger.info(
890
893
  "Agent starting root=%s trigger=%s runner=%s router=%s",
@@ -916,6 +919,13 @@ def _up_local(
916
919
  return 0
917
920
 
918
921
 
922
+ def _runtime_log_spec_value(log_spec: str | None, environ: Mapping[str, str]) -> str | None:
923
+ if isinstance(log_spec, str) and log_spec.strip():
924
+ return log_spec.strip()
925
+ env_spec = environ.get(PY_LOG_ENV_VAR, "").strip()
926
+ return env_spec or None
927
+
928
+
919
929
  def _log_state_loaded(context: UptimeContext) -> None:
920
930
  state_logger.info(
921
931
  "Agent loaded state=%s models=%s tools=%s psyches=%s skills=%s services=%s",
@@ -20,6 +20,7 @@ from toolang.base.types.tool import ToolContext, ToolDefinition
20
20
  import toolang.cli.toolang.app as cli
21
21
  import toolang.cli.caps.app as caps_cli
22
22
  import toolang.cli.caps.commands as caps_commands
23
+ import toolang.cli.utils as cli_utils
23
24
  from toolang.cli.progress import CliProgress
24
25
  from toolang.components.trigger import watch
25
26
  from toolang.config.log import DEFAULT_AGENT_LOG_SPEC
@@ -2623,6 +2624,57 @@ def test_cli_info_shows_agent_details(tmp_path: Path, monkeypatch) -> None:
2623
2624
  "_utc_now",
2624
2625
  lambda: datetime(2026, 4, 8, 12, 0, 0, tzinfo=timezone.utc),
2625
2626
  )
2627
+ monkeypatch.setattr(
2628
+ cli.agent_up,
2629
+ "load_model_providers",
2630
+ lambda *_args: {
2631
+ "openai": _FakeModelProvider(
2632
+ name="openai",
2633
+ models=(
2634
+ ModelInfo(
2635
+ ref="openai/o3",
2636
+ provider="openai",
2637
+ name="o3",
2638
+ model="o3",
2639
+ selectors=("o3", "openai/o3"),
2640
+ adapter="responses",
2641
+ ),
2642
+ ModelInfo(
2643
+ ref="openai/gpt-5",
2644
+ provider="openai",
2645
+ name="gpt-5",
2646
+ model="gpt-5",
2647
+ selectors=("gpt-5", "openai/gpt-5"),
2648
+ adapter="responses",
2649
+ ),
2650
+ ),
2651
+ ),
2652
+ },
2653
+ )
2654
+ monkeypatch.setattr(
2655
+ cli.agent_up,
2656
+ "load_tool_plugins",
2657
+ lambda *, config=None: {
2658
+ "filesystem__read_text": _FakeLoadedTool(
2659
+ plugin_name="filesystem",
2660
+ leaf_name="read_text",
2661
+ description="Read one text file.",
2662
+ ),
2663
+ "shell__execute": _FakeLoadedTool(
2664
+ plugin_name="shell",
2665
+ leaf_name="execute",
2666
+ description="Run one shell command.",
2667
+ ),
2668
+ },
2669
+ )
2670
+ monkeypatch.setattr(
2671
+ cli.agent_up,
2672
+ "list_plugin_infos",
2673
+ lambda *, group: [
2674
+ cli.agent_up.PluginInfo(name="filesystem", source="built-in"),
2675
+ cli.agent_up.PluginInfo(name="shell", source="external"),
2676
+ ],
2677
+ )
2626
2678
  caps.put_local_entry_text(
2627
2679
  toolang_root,
2628
2680
  "alice",
@@ -2677,7 +2729,8 @@ def test_cli_info_shows_agent_details(tmp_path: Path, monkeypatch) -> None:
2677
2729
  assert "alice" in result.stdout
2678
2730
  assert "-----" in result.stdout
2679
2731
  assert "Home" in result.stdout
2680
- assert str(toolang_root / "agents" / "alice") in result.stdout
2732
+ assert result.stdout.index("▄▄▄▄▄▄▄▄▄") < result.stdout.index("ALICE") < result.stdout.index("Home")
2733
+ assert str(toolang_root / "agents" / "alice") in "".join(result.stdout.split())
2681
2734
  assert "ROOM" not in result.stdout
2682
2735
  assert "PROGRAM" not in result.stdout
2683
2736
  assert "RUNTIME" not in result.stdout
@@ -2692,13 +2745,16 @@ def test_cli_info_shows_agent_details(tmp_path: Path, monkeypatch) -> None:
2692
2745
  assert "1 chore" in result.stdout
2693
2746
  assert "1 task" in result.stdout
2694
2747
  assert "Models" in result.stdout
2695
- assert "o3, gpt-5" in result.stdout
2748
+ assert "2 models, 1 provider" in result.stdout
2749
+ assert "Tools" in result.stdout
2750
+ assert "2 tools, 2 sets" in result.stdout
2751
+ assert result.stdout.index("Tools") < result.stdout.index("Models")
2696
2752
  assert "Status" in result.stdout
2697
2753
  assert "running (up a day)" in result.stdout
2698
2754
  assert "Sandbox" in result.stdout
2699
2755
  assert "none" in result.stdout
2700
- assert "Components" in result.stdout
2701
- assert "router.chat, runner.chat, trigger.pulse" in result.stdout
2756
+ assert "Components" not in result.stdout
2757
+ assert "router.chat, runner.chat, trigger.pulse" not in result.stdout
2702
2758
  assert "PID" in result.stdout
2703
2759
  assert str(os.getpid()) in result.stdout
2704
2760
  assert "Started" in result.stdout
@@ -2707,7 +2763,8 @@ def test_cli_info_shows_agent_details(tmp_path: Path, monkeypatch) -> None:
2707
2763
  assert "ONLINE" not in result.stdout
2708
2764
  assert "ENDPOINT" not in result.stdout
2709
2765
  assert "API" in result.stdout
2710
- assert "http://127.0.0.1:8765/docs" in result.stdout
2766
+ assert "http://127.0.0.1:8765" in result.stdout
2767
+ assert "http://127.0.0.1:8765/docs" not in result.stdout
2711
2768
  assert "WebUI" in result.stdout
2712
2769
  assert "https://too.run/8765" in result.stdout
2713
2770
  assert "Updated" not in result.stdout
@@ -2716,6 +2773,12 @@ def test_cli_info_shows_agent_details(tmp_path: Path, monkeypatch) -> None:
2716
2773
  assert result.stdout.index("Started") < result.stdout.index("Created")
2717
2774
 
2718
2775
 
2776
+ def test_cli_info_console_uses_terminal_width(monkeypatch) -> None:
2777
+ monkeypatch.setenv("COLUMNS", "72")
2778
+
2779
+ assert cli_utils._INFO_CONSOLE.width == 72
2780
+
2781
+
2719
2782
  def test_cli_info_reads_cap_counts_from_prepared_locks(tmp_path: Path, monkeypatch) -> None:
2720
2783
  from toolang.state.prepared import write_prepared_lock
2721
2784
 
@@ -2878,7 +2941,7 @@ def test_cli_info_for_running_docker_sandbox_shows_container_pid(tmp_path: Path,
2878
2941
  assert result.stdout.index("PID") < result.stdout.index("API")
2879
2942
 
2880
2943
 
2881
- def test_cli_info_prefers_runtime_models_for_active_agent(tmp_path: Path) -> None:
2944
+ def test_cli_info_prefers_runtime_models_for_active_agent(tmp_path: Path, monkeypatch) -> None:
2882
2945
  toolang_root = tmp_path / "toolang"
2883
2946
  agents.create_agent(toolang_root, "alice")
2884
2947
  (toolang_root / "agents" / "alice" / "config.toml").write_text(
@@ -2886,6 +2949,39 @@ def test_cli_info_prefers_runtime_models_for_active_agent(tmp_path: Path) -> Non
2886
2949
  'default = ["o3", "gpt-5"]\n',
2887
2950
  encoding="utf-8",
2888
2951
  )
2952
+ monkeypatch.setattr(
2953
+ cli.agent_up,
2954
+ "load_model_providers",
2955
+ lambda *_args: {
2956
+ "anthropic": _FakeModelProvider(
2957
+ name="anthropic",
2958
+ models=(
2959
+ ModelInfo(
2960
+ ref="anthropic/claude",
2961
+ provider="anthropic",
2962
+ name="claude",
2963
+ model="claude",
2964
+ selectors=("claude", "anthropic/claude"),
2965
+ adapter="responses",
2966
+ ),
2967
+ ),
2968
+ ),
2969
+ "openai": _FakeModelProvider(
2970
+ name="openai",
2971
+ models=(
2972
+ ModelInfo(
2973
+ ref="openai/gpt-5",
2974
+ provider="openai",
2975
+ name="gpt-5",
2976
+ model="gpt-5",
2977
+ selectors=("gpt-5", "openai/gpt-5"),
2978
+ adapter="responses",
2979
+ ),
2980
+ ),
2981
+ ),
2982
+ },
2983
+ )
2984
+ monkeypatch.setattr(cli.agent_up, "load_tool_plugins", lambda *, config=None: {})
2889
2985
  agents.write_runtime_state(
2890
2986
  toolang_root,
2891
2987
  "alice",
@@ -2905,7 +3001,7 @@ def test_cli_info_prefers_runtime_models_for_active_agent(tmp_path: Path) -> Non
2905
3001
 
2906
3002
  assert result.exit_code == 0
2907
3003
  assert "Models" in result.stdout
2908
- assert "claude, gpt-5" in result.stdout
3004
+ assert "2 models, 2 providers" in result.stdout
2909
3005
 
2910
3006
 
2911
3007
  def test_cli_channel_list_shows_installed_channels(monkeypatch) -> None:
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  import logging.config
5
5
  import re
6
+ import sys
6
7
  from typing import cast
7
8
 
8
9
  from toolang.config.log import (
@@ -70,6 +71,25 @@ def test_build_uvicorn_log_config_keeps_http_debug_off_when_toolang_debug_is_ena
70
71
  assert loggers["httpcore"]["level"] == "OFF"
71
72
 
72
73
 
74
+ def test_build_uvicorn_log_config_uses_formatter_stream_colors(monkeypatch) -> None:
75
+ class Tty:
76
+ def isatty(self) -> bool:
77
+ return True
78
+
79
+ class Plain:
80
+ def isatty(self) -> bool:
81
+ return False
82
+
83
+ monkeypatch.setattr(sys, "stderr", Tty())
84
+ monkeypatch.setattr(sys, "stdout", Plain())
85
+
86
+ config = build_uvicorn_log_config()
87
+
88
+ formatters = cast(dict[str, dict[str, object]], config["formatters"])
89
+ assert formatters["default"]["use_colors"] is True
90
+ assert formatters["access"]["use_colors"] is False
91
+
92
+
73
93
  def test_parse_log_level_accepts_warn_alias() -> None:
74
94
  assert parse_log_level("warn") == logging.WARNING
75
95
 
@@ -148,6 +168,22 @@ def test_configure_logging_supports_off_level(monkeypatch) -> None:
148
168
  assert loggers["httpx"]["level"] == "OFF"
149
169
 
150
170
 
171
+ def test_configure_logging_disables_colors_for_file_logs(monkeypatch, tmp_path) -> None:
172
+ captured: dict[str, object] = {}
173
+
174
+ def fake_dict_config(config: dict[str, object]) -> None:
175
+ captured["config"] = config
176
+
177
+ monkeypatch.setattr(logging.config, "dictConfig", fake_dict_config)
178
+
179
+ configure_logging(spec="toolang.run=debug", environ={}, log_path=tmp_path / "agent.log")
180
+
181
+ config = cast(dict[str, object], captured["config"])
182
+ formatters = cast(dict[str, dict[str, object]], config["formatters"])
183
+ assert formatters["default"]["use_colors"] is False
184
+ assert formatters["access"]["use_colors"] is False
185
+
186
+
151
187
  def test_resolve_agent_logging_defaults_run_and_start_to_agent_spec(tmp_path) -> None:
152
188
  agent_log_path = tmp_path / "agent.log"
153
189
 
@@ -84,6 +84,7 @@ from toolang.caps import (
84
84
  remove_remote_entry,
85
85
  )
86
86
  from toolang.config.plugins import ChannelBinding
87
+ from toolang.config.log_spec import PY_LOG_ENV_VAR
87
88
  from toolang.execution import execute as run_execute_module
88
89
  from toolang.execution.input import RunInput, bind_run_request
89
90
  from toolang.execution.runner import QueueRunner, RunOutcome, RunRequest, RunSubmission
@@ -2301,6 +2302,7 @@ def test_up_logs_runtime_urls_after_start_and_stop(tmp_path: Path, monkeypatch,
2301
2302
  on_stopped()
2302
2303
 
2303
2304
  monkeypatch.setattr("toolang.up._run_uvicorn_app", fake_run_uvicorn_app)
2305
+ monkeypatch.setattr("toolang.up.configure_logging", lambda **_kwargs: None)
2304
2306
  caplog.set_level(logging.INFO, logger="toolang.runtime")
2305
2307
 
2306
2308
  result = run_experiments_up(
@@ -2330,6 +2332,50 @@ def test_up_logs_runtime_urls_after_start_and_stop(tmp_path: Path, monkeypatch,
2330
2332
  assert color_messages[1] == "Agent started webui=\x1b[1m%s\x1b[0m"
2331
2333
 
2332
2334
 
2335
+ def test_local_runtime_configures_logging_before_state_loaded(tmp_path: Path, monkeypatch) -> None:
2336
+ toolang_root = tmp_path / "toolang"
2337
+ agents.create_agent(toolang_root, "alice")
2338
+ order: list[str] = []
2339
+ captured: dict[str, object] = {}
2340
+
2341
+ def fake_configure_logging(*, spec: str | None, environ: dict[str, str], log_path=None) -> None:
2342
+ del log_path
2343
+ order.append("configure")
2344
+ captured["spec"] = spec
2345
+ captured["environ"] = environ
2346
+
2347
+ def fake_log_state_loaded(context: UptimeContext) -> None:
2348
+ del context
2349
+ order.append("state")
2350
+
2351
+ def fake_run_uvicorn_app(*args, **kwargs) -> None:
2352
+ del args, kwargs
2353
+ order.append("run")
2354
+
2355
+ monkeypatch.setattr(up_module, "configure_logging", fake_configure_logging)
2356
+ monkeypatch.setattr(up_module, "_log_state_loaded", fake_log_state_loaded)
2357
+ monkeypatch.setattr(up_module, "_run_uvicorn_app", fake_run_uvicorn_app)
2358
+
2359
+ result = up_module._up_local(
2360
+ toolang_root=toolang_root,
2361
+ agent_name="alice",
2362
+ host="127.0.0.1",
2363
+ endpoint_host="localhost",
2364
+ port=8765,
2365
+ enabled_components=("router.inspect",),
2366
+ environ={PY_LOG_ENV_VAR: "toolang.state=info"},
2367
+ sandbox_child=False,
2368
+ model_selectors=(),
2369
+ tool_selectors=None,
2370
+ cap_selectors=(),
2371
+ log_spec=None,
2372
+ )
2373
+
2374
+ assert result == 0
2375
+ assert order[:2] == ["configure", "state"]
2376
+ assert captured["spec"] == "toolang.state=info"
2377
+
2378
+
2333
2379
  def test_state_loaded_log_counts_selectable_models(tmp_path: Path, caplog) -> None:
2334
2380
  toolang_root = tmp_path / "toolang"
2335
2381
  agents.create_agent(toolang_root, "alice")
@@ -1729,7 +1729,7 @@ wheels = [
1729
1729
 
1730
1730
  [[package]]
1731
1731
  name = "toolang"
1732
- version = "0.2.1"
1732
+ version = "0.2.2"
1733
1733
  source = { editable = "." }
1734
1734
  dependencies = [
1735
1735
  { name = "dataclasses-json" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes