hx-cli 0.2.11__tar.gz → 0.2.14__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 (158) hide show
  1. {hx_cli-0.2.11 → hx_cli-0.2.14}/CHANGELOG.md +107 -1
  2. {hx_cli-0.2.11 → hx_cli-0.2.14}/PKG-INFO +80 -15
  3. {hx_cli-0.2.11 → hx_cli-0.2.14}/README.md +78 -14
  4. {hx_cli-0.2.11 → hx_cli-0.2.14}/pyproject.toml +3 -0
  5. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/__init__.py +1 -1
  6. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/agents/subagent.py +8 -1
  7. hx_cli-0.2.14/src/hx/auth/claude_code.py +369 -0
  8. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/auth/resolve.py +45 -1
  9. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/cli.py +53 -31
  10. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/core/loop.py +70 -18
  11. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/core/session.py +18 -5
  12. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/core/title.py +14 -1
  13. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/net.py +62 -6
  14. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/providers/base.py +5 -0
  15. hx_cli-0.2.14/src/hx/providers/claude_code.py +972 -0
  16. hx_cli-0.2.14/src/hx/providers/claude_code_catalogue.py +276 -0
  17. hx_cli-0.2.14/src/hx/providers/claude_code_cli.py +414 -0
  18. hx_cli-0.2.14/src/hx/providers/claude_code_transcript.py +433 -0
  19. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/providers/codex.py +1 -1
  20. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/providers/models.py +298 -33
  21. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/providers/registry.py +41 -0
  22. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/glob.py +25 -6
  23. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/app.py +4 -0
  24. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/commands.py +102 -37
  25. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/runtime.py +64 -11
  26. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/views/pickers.py +101 -14
  27. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/views/status.py +17 -6
  28. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/e2e/conftest.py +117 -1
  29. hx_cli-0.2.14/tests/e2e/fake_claude.py +585 -0
  30. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/e2e/stub.py +58 -3
  31. hx_cli-0.2.14/tests/e2e/test_claude_code.py +1299 -0
  32. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/e2e/test_commands.py +305 -2
  33. hx_cli-0.2.14/tests/e2e/test_queue.py +156 -0
  34. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/e2e/test_tools.py +27 -0
  35. hx_cli-0.2.14/tests/test_live_claude_code.py +350 -0
  36. hx_cli-0.2.14/tests/tls_proxy.py +222 -0
  37. {hx_cli-0.2.11 → hx_cli-0.2.14}/uv.lock +191 -0
  38. {hx_cli-0.2.11 → hx_cli-0.2.14}/.github/workflows/ci.yml +0 -0
  39. {hx_cli-0.2.11 → hx_cli-0.2.14}/.gitignore +0 -0
  40. {hx_cli-0.2.11 → hx_cli-0.2.14}/examples/hooks/check.sh +0 -0
  41. {hx_cli-0.2.11 → hx_cli-0.2.14}/install.sh +0 -0
  42. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/agents/__init__.py +0 -0
  43. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/agents/definitions.py +0 -0
  44. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/auth/__init__.py +0 -0
  45. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/auth/oauth/__init__.py +0 -0
  46. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/auth/oauth/browser.py +0 -0
  47. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/auth/oauth/callback.py +0 -0
  48. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/auth/oauth/codex.py +0 -0
  49. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/auth/oauth/devin.py +0 -0
  50. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/auth/oauth/pkce.py +0 -0
  51. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/auth/store.py +0 -0
  52. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/config.py +0 -0
  53. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/core/__init__.py +0 -0
  54. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/core/checkpoints.py +0 -0
  55. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/core/compaction.py +0 -0
  56. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/core/context.py +0 -0
  57. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/core/events.py +0 -0
  58. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/core/images.py +0 -0
  59. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/core/lateinject.py +0 -0
  60. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/core/messages.py +0 -0
  61. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/core/usage.py +0 -0
  62. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/docs.py +0 -0
  63. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/frontmatter.py +0 -0
  64. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/git.py +0 -0
  65. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/hooks/__init__.py +0 -0
  66. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/hooks/engine.py +0 -0
  67. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/hooks/spec.py +0 -0
  68. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/keys.py +0 -0
  69. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/mcp/__init__.py +0 -0
  70. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/mcp/client.py +0 -0
  71. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/mcp/manager.py +0 -0
  72. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/mcp/oauth.py +0 -0
  73. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/paths.py +0 -0
  74. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/permissions/__init__.py +0 -0
  75. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/permissions/engine.py +0 -0
  76. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/permissions/parser.py +0 -0
  77. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/permissions/sandbox.py +0 -0
  78. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/providers/__init__.py +0 -0
  79. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/providers/codex_catalogue.py +0 -0
  80. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/providers/devin.py +0 -0
  81. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/providers/devin_catalogue.py +0 -0
  82. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/providers/devin_wire.py +0 -0
  83. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/providers/openrouter.py +0 -0
  84. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/providers/protowire.py +0 -0
  85. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/providers/responses_codec.py +0 -0
  86. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/skills/__init__.py +0 -0
  87. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/skills/loader.py +0 -0
  88. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/skills/runtime.py +0 -0
  89. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/__init__.py +0 -0
  90. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/ansi.py +0 -0
  91. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/buffer.py +0 -0
  92. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/component.py +0 -0
  93. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/demo.py +0 -0
  94. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/editor.py +0 -0
  95. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/keydecode.py +0 -0
  96. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/loop.py +0 -0
  97. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/markdown.py +0 -0
  98. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/modifiers.py +0 -0
  99. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/primitives.py +0 -0
  100. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/sanitize.py +0 -0
  101. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/screen.py +0 -0
  102. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/syntax.py +0 -0
  103. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/terminal.py +0 -0
  104. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/undo.py +0 -0
  105. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/width.py +0 -0
  106. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/term/word_nav.py +0 -0
  107. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/__init__.py +0 -0
  108. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/anchors.py +0 -0
  109. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/base.py +0 -0
  110. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/bash.py +0 -0
  111. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/edit.py +0 -0
  112. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/grep.py +0 -0
  113. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/output.py +0 -0
  114. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/read.py +0 -0
  115. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/registry.py +0 -0
  116. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/symbols.py +0 -0
  117. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/task.py +0 -0
  118. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/todo.py +0 -0
  119. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/websearch.py +0 -0
  120. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tools/write.py +0 -0
  121. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/trace.py +0 -0
  122. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/__init__.py +0 -0
  123. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/clipboard.py +0 -0
  124. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/format.py +0 -0
  125. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/fuzzy.py +0 -0
  126. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/glyphs.py +0 -0
  127. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/killring.py +0 -0
  128. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/limits.py +0 -0
  129. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/paint.py +0 -0
  130. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/renderers.py +0 -0
  131. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/roles.py +0 -0
  132. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/theme.py +0 -0
  133. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/theme_json.py +0 -0
  134. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/themes/ansi.json +0 -0
  135. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/themes/dark.json +0 -0
  136. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/themes/light.json +0 -0
  137. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/views/__init__.py +0 -0
  138. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/views/blocks.py +0 -0
  139. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/views/dialog.py +0 -0
  140. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/views/login.py +0 -0
  141. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/views/permission.py +0 -0
  142. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/views/prompt.py +0 -0
  143. {hx_cli-0.2.11 → hx_cli-0.2.14}/src/hx/tui/views/transcript.py +0 -0
  144. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/__init__.py +0 -0
  145. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/conftest.py +0 -0
  146. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/e2e/__init__.py +0 -0
  147. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/e2e/fixtures/echo_server.py +0 -0
  148. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/e2e/report.py +0 -0
  149. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/e2e/terminal.py +0 -0
  150. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/e2e/test_chat.py +0 -0
  151. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/e2e/test_cli.py +0 -0
  152. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/e2e/test_extend.py +0 -0
  153. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/e2e/test_input.py +0 -0
  154. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/e2e/test_print.py +0 -0
  155. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/test_live.py +0 -0
  156. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/test_live_codex.py +0 -0
  157. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/test_live_devin.py +0 -0
  158. {hx_cli-0.2.11 → hx_cli-0.2.14}/tests/test_live_tavily.py +0 -0
@@ -28,6 +28,109 @@ the project follows [semantic versioning](https://semver.org/).
28
28
 
29
29
  ---
30
30
 
31
+ ## [0.2.14] - 2026-10-04
32
+
33
+ ### Added
34
+
35
+ - When the installed Claude Code is behind its current release - and so lists older Claude
36
+ models than your subscription can run - `/model` says so under the list, with the command
37
+ that updates it (`brew upgrade --cask claude-code` for a Homebrew install, otherwise
38
+ `claude update`). After updating, the next `/model` lists the new models; no
39
+ refresh needed.
40
+
41
+ ### Changed
42
+
43
+ - The model catalogue is refreshed from inside the `/model` picker: press `ctrl+r`. The
44
+ list updates in place and keeps your filter and selection; the outcome, or why the refresh
45
+ failed, shows under the list. With no models at all, `/model` now opens anyway and says
46
+ so, so `ctrl+r` is there to fill it.
47
+
48
+ ### Removed
49
+
50
+ - `/models refresh`. It sat one letter away from `/model` and did nothing without its one
51
+ argument; use `ctrl+r` in the `/model` picker.
52
+
53
+ ### Fixed
54
+
55
+ - On the `claude-code` route, the context window is the one Claude Code reports, not a
56
+ 200k guess: Opus 5.5 runs with 1M. Claude Code lists its models without their sizes, and
57
+ what it serves depends on the plan, so a model starts at the window it was measured
58
+ serving on every plan - 1M for Opus 5.5, Sonnet 5.5, Sonnet 5 and Opus 4.7, 200k
59
+ otherwise - until its first turn reports the real window and maximum output for your
60
+ account. From then on the context gauge, the compaction threshold, the `/model` picker
61
+ and later sessions all use it, and a catalogue refresh keeps it.
62
+ - A model Claude Code lists at its long context - Fable 5.1 as `claude-fable-5-1[1m]` -
63
+ runs at it: `/model` offers `claude-code/claude-fable-5-1[1m]` at 1M, where HX used to
64
+ drop the `[1m]` and run the model at its standard context.
65
+ - A turn refused because the model, or its 1M context, needs usage credits your Claude plan
66
+ has off no longer reads "usage limit is reached. It resets at ...": HX says what needs the
67
+ credits, where to turn them on, and which model runs without them. Once they are on, the
68
+ next turn runs.
69
+ - After a catalogue refresh (`ctrl+r` in `/model`, or signing in), the session in progress
70
+ measures its context against the refreshed window, not the one it started with.
71
+ - `/model anthropic` (or typing `anthropic` in the picker) lists the Claude models your Claude
72
+ Code subscription runs, first, ahead of the per-token OpenRouter ones; `openai` does the same
73
+ for Codex. Several words match in any order: `/model anthropic sonnet`.
74
+
75
+ ---
76
+
77
+ ## [0.2.13] - 2026-10-04
78
+
79
+ ### Added
80
+
81
+ - Claude Pro/Max route: `hx auth login claude-code`, then
82
+ `hx --model claude-code/claude-sonnet-5`. Runs Claude models on your Claude
83
+ subscription through the official Claude Code CLI, with HX's own tools,
84
+ approvals and transcript. `/model` lists the account's models and `/effort`
85
+ their levels.
86
+ - Behind a TLS-inspecting proxy the `claude-code` route takes the same
87
+ settings as every other route: `HX_CA_BUNDLE` reaches Claude Code as
88
+ `NODE_EXTRA_CA_CERTS` and `HX_SSL_NO_VERIFY` as
89
+ `NODE_TLS_REJECT_UNAUTHORIZED=0`, Claude Code's own `CLAUDE_CODE_CERT_STORE`
90
+ and `CLAUDE_CODE_CLIENT_CERT` / `_KEY` / `_KEY_PASSPHRASE` (for a network
91
+ that requires a client certificate) pass through, and a certificate failure
92
+ it reports names the fix.
93
+ - On the `claude-code` route, a turn Anthropic bills as Extra Usage while
94
+ your plan still has room says so, and names HX's part of the system prompt,
95
+ instead of claiming the plan's usage limit is reached. A turn whose prompt
96
+ carries HX's own system prompt text (a copied `system-prompt.md`, an
97
+ AGENTS.md, an agent definition) is refused before it is sent, naming where
98
+ the text is; `hx prompt` shows the sources.
99
+
100
+ ### Fixed
101
+
102
+ - A subagent that fails - a provider error, a refused prompt, the turn limit -
103
+ reports why to the parent model, instead of "(the subagent returned no
104
+ text)".
105
+ - `Glob` with a pattern of `.` or `./` returns an error saying to use `*` or
106
+ `**/*`, and an absolute pattern one naming the `path` and relative pattern
107
+ to send instead, on every Python version, instead of a Python exception.
108
+ - With `tui.enterWhileBusy` set to `"steer"`, `alt+enter` queues a message
109
+ behind the running turn, as the prompt says. It used to steer like `enter`,
110
+ leaving no way to queue in that mode.
111
+ - The status bar's `⧗N queued` count stays visible in a project with a long
112
+ path or branch name; the path is shortened instead of the count being cut off.
113
+
114
+ ---
115
+
116
+ ## [0.2.12] - 2026-10-01
117
+
118
+ ### Fixed
119
+
120
+ - `/exit` and `ctrl+d` return the shell at once. They used to wait up to 10
121
+ seconds on a model call renaming the session; sessions are no longer renamed
122
+ on the way out, in the TUI or with `hx -p`.
123
+ - A name set with `/title` is no longer replaced by a generated one, including
124
+ one that was still being generated when you set it.
125
+ - The session name is generated from the opening and the latest stretch of the
126
+ conversation, not only its first 2000 characters.
127
+ - A session left before its first name came back is named from your first
128
+ message, instead of being listed by `/resume` with no name.
129
+ - A session left with `/resume` into another directory is renamed for what it
130
+ became, by the HX that starts there, in the background.
131
+
132
+ ---
133
+
31
134
  ## [0.2.11] - 2026-10-01
32
135
 
33
136
  ### Fixed
@@ -776,7 +879,10 @@ First release, published to PyPI as [`hx-cli`](https://pypi.org/project/hx-cli/)
776
879
  - `/configure` and `hx auth` for the OpenRouter key, `hx upgrade` for
777
880
  self-update, and `install.sh` bootstrapping uv with a pinned Python.
778
881
 
779
- [Unreleased]: https://github.com/aletisunil/hx/compare/v0.2.11...HEAD
882
+ [Unreleased]: https://github.com/aletisunil/hx/compare/v0.2.14...HEAD
883
+ [0.2.14]: https://github.com/aletisunil/hx/compare/v0.2.13...v0.2.14
884
+ [0.2.13]: https://github.com/aletisunil/hx/compare/v0.2.12...v0.2.13
885
+ [0.2.12]: https://github.com/aletisunil/hx/compare/v0.2.11...v0.2.12
780
886
  [0.2.11]: https://github.com/aletisunil/hx/compare/v0.2.10...v0.2.11
781
887
  [0.2.10]: https://github.com/aletisunil/hx/compare/v0.2.9...v0.2.10
782
888
  [0.2.9]: https://github.com/aletisunil/hx/compare/v0.2.8...v0.2.9
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hx-cli
3
- Version: 0.2.11
3
+ Version: 0.2.14
4
4
  Summary: HX - an agent harness for the terminal
5
5
  Project-URL: Homepage, https://hx.sunilaleti.dev
6
6
  Project-URL: Documentation, https://hx.sunilaleti.dev
@@ -31,6 +31,7 @@ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
31
31
  Requires-Dist: pytest-cov>=5.0; extra == 'dev'
32
32
  Requires-Dist: pytest>=8.2; extra == 'dev'
33
33
  Requires-Dist: ruff>=0.6; extra == 'dev'
34
+ Requires-Dist: trustme>=1.2; extra == 'dev'
34
35
  Requires-Dist: types-pygments; extra == 'dev'
35
36
  Requires-Dist: types-pyyaml; extra == 'dev'
36
37
  Provides-Extra: symbols
@@ -90,7 +91,7 @@ Requires Python 3.11+. macOS and Linux. Update with `hx upgrade`.
90
91
 
91
92
  ### Credentials
92
93
 
93
- HX reaches models over three routes. Which one serves a turn is decided by the
94
+ HX reaches models over four routes. Which one serves a turn is decided by the
94
95
  model id alone, so "what paid for that" is always answerable by reading it:
95
96
 
96
97
  | Model id | Route | Billing |
@@ -98,8 +99,10 @@ model id alone, so "what paid for that" is always answerable by reading it:
98
99
  | `anthropic/claude-sonnet-4.5`, `openai/gpt-5`, … | OpenRouter | per token, API key |
99
100
  | `openai-codex/gpt-5.6-terra` | ChatGPT Plus/Pro | your subscription |
100
101
  | `devin/swe-1-6`, `devin/claude-opus-5`, … | Devin | your subscription |
102
+ | `claude-code/claude-sonnet-5`, `claude-code/claude-opus-4-8`, … | Claude Pro/Max, via Claude Code | your subscription |
101
103
 
102
- Credentials live in `~/.hx/auth.json`, mode 0600, one entry per route.
104
+ Credentials live in `~/.hx/auth.json`, mode 0600, one entry per route - except
105
+ Claude Code's, which stays with Claude Code.
103
106
 
104
107
  **OpenRouter.** On first run HX asks for a key. Get one at
105
108
  <https://openrouter.ai/keys>. Change it later with `/configure` in the TUI, or:
@@ -153,7 +156,7 @@ reach it at all.
153
156
 
154
157
  **Which Codex models you get** is decided by your ChatGPT account, not by HX.
155
158
  Signing in fetches the account's own list from the Codex backend, and `/model`
156
- offers exactly that; `/models refresh` asks again. Entitlement is per model —
159
+ offers exactly that; ctrl+r in `/model` asks again. Entitlement is per model -
157
160
  the same `plus` account can run `gpt-5.6-sol` and be refused `gpt-5.3-codex` —
158
161
  so a model the picker does not list is one the subscription does not cover.
159
162
 
@@ -215,7 +218,7 @@ you to sign in again. On a machine with no browser at all, a session token in
215
218
  `HX_DEVIN_API_KEY` or `DEVIN_API_KEY` is used when nothing is saved.
216
219
 
217
220
  Signing in fetches the account's own model list, and `/model` offers exactly
218
- that; `/models refresh` asks again. Devin publishes each reasoning depth as a
221
+ that; ctrl+r in `/model` asks again. Devin publishes each reasoning depth as a
219
222
  model of its own - Claude Opus 5 High, Claude Opus 5 Max - and HX folds each
220
223
  family into one model, so `/effort` picks the depth on Devin exactly as it does
221
224
  on Codex. A fast lane or a 1M-context lane stays a separate model, since it
@@ -223,6 +226,54 @@ changes what a turn costs or how much fits. Router models such as
223
226
  `devin/adaptive` are resolved to a concrete model on every turn by Devin itself.
224
227
  If the list cannot be fetched, `devin/swe-1-6` and `devin/swe-1-6-fast` stand in.
225
228
 
229
+ **Claude Pro/Max.** HX runs Claude models on your Claude subscription through
230
+ the official [Claude Code](https://claude.com/claude-code) CLI, the way the
231
+ Claude Agent SDK does. Install Claude Code, then:
232
+
233
+ ```sh
234
+ hx auth login claude-code # Claude Code's own sign-in; or /login inside HX
235
+ hx --model claude-code/claude-sonnet-5
236
+ ```
237
+
238
+ HX holds no Claude token. The login is Claude Code's, kept and refreshed where
239
+ Claude Code keeps it, so `hx auth logout claude-code` only tells you to run
240
+ `claude auth logout` - which signs out every Claude Code on the machine. A
241
+ headless machine can use a token from `claude setup-token` in
242
+ `CLAUDE_CODE_OAUTH_TOKEN`. A Console login (`claude auth login --console`) bills
243
+ per token and is refused: this route is the subscription.
244
+
245
+ How a turn works: HX starts `claude -p` with every one of Claude Code's own
246
+ tools switched off and serves HX's tools to it as an MCP server. The model's
247
+ tool calls are run, permission-checked and drawn by HX exactly as on every
248
+ other route; Claude Code holds each call open until HX answers it, however long
249
+ an approval takes. The system prompt is Claude Code's, with your AGENTS.md
250
+ files, skills index and `--append-system-prompt` text added. One Claude Code
251
+ process serves a conversation for as long as it can, so the prompt cache
252
+ carries from turn to turn; after an interrupt, a steer while a tool runs, a
253
+ compaction, `/model`, `/effort` or a resumed session, HX starts a new one on a
254
+ session rebuilt from its own transcript. That costs a second or so and a
255
+ partly cold cache, not the conversation.
256
+
257
+ Claude Code is started isolated from your Claude Code setup: no CLAUDE.md, no
258
+ hooks, no plugins or MCP servers from `~/.claude`, no auto-memory, no
259
+ auto-compaction (HX compacts its own transcript), and nothing saved to Claude
260
+ Code's session list. Variables that would move it off the subscription -
261
+ `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`, `CLAUDE_CODE_USE_BEDROCK` and the
262
+ rest - are removed from its environment; its TLS settings are kept (see
263
+ *Behind a TLS-inspecting proxy* below). `HX_CLAUDE_CODE_PATH` points HX at a
264
+ `claude` other than the one on `PATH`.
265
+
266
+ `/model` offers the models Claude Code lists for the account, each with the
267
+ efforts it accepts for `/effort`; ctrl+r in `/model` asks again. A model Claude
268
+ Code runs at its long context keeps `[1m]` in its id
269
+ (`claude-code/claude-fable-5-1[1m]`), exactly as Claude Code's own `/model` runs
270
+ it. Claude Code lists no context sizes, so a model starts at the window it was
271
+ measured serving on every plan - 1M for Opus 5.5, Sonnet 5.5, Sonnet 5 and Opus
272
+ 4.7, otherwise 200k - and from its first turn uses the window Claude Code
273
+ reports for your account. When a usage limit is reached, HX says which one and
274
+ when it resets; when a model or its 1M context needs usage credits your plan
275
+ has off, HX says that instead, and what to pick without them.
276
+
226
277
  **Web search.** Optional, and the one credential that is not a model route.
227
278
  It powers two tools:
228
279
 
@@ -232,7 +283,7 @@ It powers two tools:
232
283
  | `WebFetch` | read up to 5 URLs, returned as markdown |
233
284
 
234
285
  [Tavily](https://app.tavily.com) bills per search rather than per token, so it
235
- works identically on both routes — including the ChatGPT subscription one,
286
+ works identically on every route - including the subscription ones,
236
287
  where a provider-native search tool is not on offer.
237
288
 
238
289
  Get a key at <https://app.tavily.com> — the free tier is 1000 searches a month
@@ -286,9 +337,18 @@ startup every session, because it is not a fix. A TLS failure is reported in
286
337
  one line - `/model` and the startup notice both name the certificate problem
287
338
  rather than reporting an empty catalogue with no cause.
288
339
 
289
- There is no Claude Pro/Max route. Anthropic rejects OAuth tokens unless the
290
- request impersonates Claude Code, which HX will not do. Claude models stay
291
- available through OpenRouter.
340
+ The same settings cover the `claude-code` route, whose requests Claude Code
341
+ makes with its own TLS stack: HX passes `HX_CA_BUNDLE` on to it as
342
+ `NODE_EXTRA_CA_CERTS` (unless you already set that yourself), and
343
+ `HX_SSL_NO_VERIFY` as `NODE_TLS_REJECT_UNAUTHORIZED=0`. Claude Code reads the
344
+ OS trust store on its own, and its own TLS settings reach it untouched:
345
+ `CLAUDE_CODE_CERT_STORE`, and `CLAUDE_CODE_CLIENT_CERT`,
346
+ `CLAUDE_CODE_CLIENT_KEY` and `CLAUDE_CODE_CLIENT_KEY_PASSPHRASE` for a network
347
+ that requires a client certificate.
348
+
349
+ HX never sends a Claude subscription token to the API itself - Anthropic
350
+ accepts those only from Claude Code, and HX will not impersonate it. The
351
+ `claude-code` route runs the real Claude Code instead.
292
352
 
293
353
  ---
294
354
 
@@ -314,7 +374,7 @@ hx mcp list|add|remove # manage MCP servers
314
374
  hx mcp login <server> # sign in to a remote MCP server in the browser
315
375
  hx mcp logout <server> # forget a remote MCP server's sign-in
316
376
  hx auth [set|clear] # manage the OpenRouter key
317
- hx auth login [provider] # sign in (openrouter, openai-codex, devin)
377
+ hx auth login [provider] # sign in (openrouter, openai-codex, devin, claude-code)
318
378
  hx auth logout <provider> # forget a stored credential
319
379
  hx docs [section] # the manual; no argument lists its sections
320
380
  hx changelog [version] # what shipped in each version
@@ -368,7 +428,7 @@ be rebound (see [Keybindings](#keybindings)).
368
428
  | `ctrl+d` | exit, when the prompt is empty |
369
429
  | `shift+tab` | cycle permission mode |
370
430
  | `ctrl+p` | command palette |
371
- | `ctrl+l` | model picker |
431
+ | `ctrl+l` | model picker; inside it, `ctrl+r` fetches the catalogue again |
372
432
  | `ctrl+o` | expand tool output (also `ctrl+r`) |
373
433
  | `ctrl+t` | show the current plan |
374
434
  | `ctrl+x` | copy the selected message to the clipboard |
@@ -474,8 +534,7 @@ silently resolved.
474
534
 
475
535
  | Command | Does |
476
536
  |---|---|
477
- | `/model [query]` | pick a model; shows context window, price per Mtok, cache support |
478
- | `/models refresh` | re-fetch the catalogue |
537
+ | `/model [query]` | pick a model; shows context window, price per Mtok, cache support. `ctrl+r` in the picker re-fetches the catalogue |
479
538
  | `/effort [level]` | how hard a reasoning model thinks, from the levels it offers |
480
539
  | `/configure` | session settings and the API key |
481
540
  | `/login [provider]` | sign in to a model route |
@@ -709,8 +768,14 @@ After the first exchange, HX asks the model for a short name for the session and
709
768
  writes it to `~/.hx/sessions/<id>/meta.json`, so `/resume` lists work rather
710
769
  than timestamps. It is one small call — cap 32 output tokens, `models.title_model`
711
770
  if you want a cheaper model for it — and it is counted in `/cost` like any other.
712
- If the call fails the session is still named, from your first message. `/title
713
- <text>` renames it.
771
+ If the call fails, or you leave before it is back, the session is still named,
772
+ from your first message.
773
+
774
+ The live session is never renamed while it runs, and nothing is renamed on
775
+ `/exit`, which returns your shell at once. A session you leave with `/clear` or
776
+ `/resume` - including a `/resume` into another directory - is renamed in the
777
+ background for what it became. `/title <text>` renames it yourself, and a name
778
+ you chose is never replaced.
714
779
 
715
780
  `/resume` lists every session except the one you are in: this directory's
716
781
  first, then other directories', each row naming where it lives. A session from
@@ -50,7 +50,7 @@ Requires Python 3.11+. macOS and Linux. Update with `hx upgrade`.
50
50
 
51
51
  ### Credentials
52
52
 
53
- HX reaches models over three routes. Which one serves a turn is decided by the
53
+ HX reaches models over four routes. Which one serves a turn is decided by the
54
54
  model id alone, so "what paid for that" is always answerable by reading it:
55
55
 
56
56
  | Model id | Route | Billing |
@@ -58,8 +58,10 @@ model id alone, so "what paid for that" is always answerable by reading it:
58
58
  | `anthropic/claude-sonnet-4.5`, `openai/gpt-5`, … | OpenRouter | per token, API key |
59
59
  | `openai-codex/gpt-5.6-terra` | ChatGPT Plus/Pro | your subscription |
60
60
  | `devin/swe-1-6`, `devin/claude-opus-5`, … | Devin | your subscription |
61
+ | `claude-code/claude-sonnet-5`, `claude-code/claude-opus-4-8`, … | Claude Pro/Max, via Claude Code | your subscription |
61
62
 
62
- Credentials live in `~/.hx/auth.json`, mode 0600, one entry per route.
63
+ Credentials live in `~/.hx/auth.json`, mode 0600, one entry per route - except
64
+ Claude Code's, which stays with Claude Code.
63
65
 
64
66
  **OpenRouter.** On first run HX asks for a key. Get one at
65
67
  <https://openrouter.ai/keys>. Change it later with `/configure` in the TUI, or:
@@ -113,7 +115,7 @@ reach it at all.
113
115
 
114
116
  **Which Codex models you get** is decided by your ChatGPT account, not by HX.
115
117
  Signing in fetches the account's own list from the Codex backend, and `/model`
116
- offers exactly that; `/models refresh` asks again. Entitlement is per model —
118
+ offers exactly that; ctrl+r in `/model` asks again. Entitlement is per model -
117
119
  the same `plus` account can run `gpt-5.6-sol` and be refused `gpt-5.3-codex` —
118
120
  so a model the picker does not list is one the subscription does not cover.
119
121
 
@@ -175,7 +177,7 @@ you to sign in again. On a machine with no browser at all, a session token in
175
177
  `HX_DEVIN_API_KEY` or `DEVIN_API_KEY` is used when nothing is saved.
176
178
 
177
179
  Signing in fetches the account's own model list, and `/model` offers exactly
178
- that; `/models refresh` asks again. Devin publishes each reasoning depth as a
180
+ that; ctrl+r in `/model` asks again. Devin publishes each reasoning depth as a
179
181
  model of its own - Claude Opus 5 High, Claude Opus 5 Max - and HX folds each
180
182
  family into one model, so `/effort` picks the depth on Devin exactly as it does
181
183
  on Codex. A fast lane or a 1M-context lane stays a separate model, since it
@@ -183,6 +185,54 @@ changes what a turn costs or how much fits. Router models such as
183
185
  `devin/adaptive` are resolved to a concrete model on every turn by Devin itself.
184
186
  If the list cannot be fetched, `devin/swe-1-6` and `devin/swe-1-6-fast` stand in.
185
187
 
188
+ **Claude Pro/Max.** HX runs Claude models on your Claude subscription through
189
+ the official [Claude Code](https://claude.com/claude-code) CLI, the way the
190
+ Claude Agent SDK does. Install Claude Code, then:
191
+
192
+ ```sh
193
+ hx auth login claude-code # Claude Code's own sign-in; or /login inside HX
194
+ hx --model claude-code/claude-sonnet-5
195
+ ```
196
+
197
+ HX holds no Claude token. The login is Claude Code's, kept and refreshed where
198
+ Claude Code keeps it, so `hx auth logout claude-code` only tells you to run
199
+ `claude auth logout` - which signs out every Claude Code on the machine. A
200
+ headless machine can use a token from `claude setup-token` in
201
+ `CLAUDE_CODE_OAUTH_TOKEN`. A Console login (`claude auth login --console`) bills
202
+ per token and is refused: this route is the subscription.
203
+
204
+ How a turn works: HX starts `claude -p` with every one of Claude Code's own
205
+ tools switched off and serves HX's tools to it as an MCP server. The model's
206
+ tool calls are run, permission-checked and drawn by HX exactly as on every
207
+ other route; Claude Code holds each call open until HX answers it, however long
208
+ an approval takes. The system prompt is Claude Code's, with your AGENTS.md
209
+ files, skills index and `--append-system-prompt` text added. One Claude Code
210
+ process serves a conversation for as long as it can, so the prompt cache
211
+ carries from turn to turn; after an interrupt, a steer while a tool runs, a
212
+ compaction, `/model`, `/effort` or a resumed session, HX starts a new one on a
213
+ session rebuilt from its own transcript. That costs a second or so and a
214
+ partly cold cache, not the conversation.
215
+
216
+ Claude Code is started isolated from your Claude Code setup: no CLAUDE.md, no
217
+ hooks, no plugins or MCP servers from `~/.claude`, no auto-memory, no
218
+ auto-compaction (HX compacts its own transcript), and nothing saved to Claude
219
+ Code's session list. Variables that would move it off the subscription -
220
+ `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`, `CLAUDE_CODE_USE_BEDROCK` and the
221
+ rest - are removed from its environment; its TLS settings are kept (see
222
+ *Behind a TLS-inspecting proxy* below). `HX_CLAUDE_CODE_PATH` points HX at a
223
+ `claude` other than the one on `PATH`.
224
+
225
+ `/model` offers the models Claude Code lists for the account, each with the
226
+ efforts it accepts for `/effort`; ctrl+r in `/model` asks again. A model Claude
227
+ Code runs at its long context keeps `[1m]` in its id
228
+ (`claude-code/claude-fable-5-1[1m]`), exactly as Claude Code's own `/model` runs
229
+ it. Claude Code lists no context sizes, so a model starts at the window it was
230
+ measured serving on every plan - 1M for Opus 5.5, Sonnet 5.5, Sonnet 5 and Opus
231
+ 4.7, otherwise 200k - and from its first turn uses the window Claude Code
232
+ reports for your account. When a usage limit is reached, HX says which one and
233
+ when it resets; when a model or its 1M context needs usage credits your plan
234
+ has off, HX says that instead, and what to pick without them.
235
+
186
236
  **Web search.** Optional, and the one credential that is not a model route.
187
237
  It powers two tools:
188
238
 
@@ -192,7 +242,7 @@ It powers two tools:
192
242
  | `WebFetch` | read up to 5 URLs, returned as markdown |
193
243
 
194
244
  [Tavily](https://app.tavily.com) bills per search rather than per token, so it
195
- works identically on both routes — including the ChatGPT subscription one,
245
+ works identically on every route - including the subscription ones,
196
246
  where a provider-native search tool is not on offer.
197
247
 
198
248
  Get a key at <https://app.tavily.com> — the free tier is 1000 searches a month
@@ -246,9 +296,18 @@ startup every session, because it is not a fix. A TLS failure is reported in
246
296
  one line - `/model` and the startup notice both name the certificate problem
247
297
  rather than reporting an empty catalogue with no cause.
248
298
 
249
- There is no Claude Pro/Max route. Anthropic rejects OAuth tokens unless the
250
- request impersonates Claude Code, which HX will not do. Claude models stay
251
- available through OpenRouter.
299
+ The same settings cover the `claude-code` route, whose requests Claude Code
300
+ makes with its own TLS stack: HX passes `HX_CA_BUNDLE` on to it as
301
+ `NODE_EXTRA_CA_CERTS` (unless you already set that yourself), and
302
+ `HX_SSL_NO_VERIFY` as `NODE_TLS_REJECT_UNAUTHORIZED=0`. Claude Code reads the
303
+ OS trust store on its own, and its own TLS settings reach it untouched:
304
+ `CLAUDE_CODE_CERT_STORE`, and `CLAUDE_CODE_CLIENT_CERT`,
305
+ `CLAUDE_CODE_CLIENT_KEY` and `CLAUDE_CODE_CLIENT_KEY_PASSPHRASE` for a network
306
+ that requires a client certificate.
307
+
308
+ HX never sends a Claude subscription token to the API itself - Anthropic
309
+ accepts those only from Claude Code, and HX will not impersonate it. The
310
+ `claude-code` route runs the real Claude Code instead.
252
311
 
253
312
  ---
254
313
 
@@ -274,7 +333,7 @@ hx mcp list|add|remove # manage MCP servers
274
333
  hx mcp login <server> # sign in to a remote MCP server in the browser
275
334
  hx mcp logout <server> # forget a remote MCP server's sign-in
276
335
  hx auth [set|clear] # manage the OpenRouter key
277
- hx auth login [provider] # sign in (openrouter, openai-codex, devin)
336
+ hx auth login [provider] # sign in (openrouter, openai-codex, devin, claude-code)
278
337
  hx auth logout <provider> # forget a stored credential
279
338
  hx docs [section] # the manual; no argument lists its sections
280
339
  hx changelog [version] # what shipped in each version
@@ -328,7 +387,7 @@ be rebound (see [Keybindings](#keybindings)).
328
387
  | `ctrl+d` | exit, when the prompt is empty |
329
388
  | `shift+tab` | cycle permission mode |
330
389
  | `ctrl+p` | command palette |
331
- | `ctrl+l` | model picker |
390
+ | `ctrl+l` | model picker; inside it, `ctrl+r` fetches the catalogue again |
332
391
  | `ctrl+o` | expand tool output (also `ctrl+r`) |
333
392
  | `ctrl+t` | show the current plan |
334
393
  | `ctrl+x` | copy the selected message to the clipboard |
@@ -434,8 +493,7 @@ silently resolved.
434
493
 
435
494
  | Command | Does |
436
495
  |---|---|
437
- | `/model [query]` | pick a model; shows context window, price per Mtok, cache support |
438
- | `/models refresh` | re-fetch the catalogue |
496
+ | `/model [query]` | pick a model; shows context window, price per Mtok, cache support. `ctrl+r` in the picker re-fetches the catalogue |
439
497
  | `/effort [level]` | how hard a reasoning model thinks, from the levels it offers |
440
498
  | `/configure` | session settings and the API key |
441
499
  | `/login [provider]` | sign in to a model route |
@@ -669,8 +727,14 @@ After the first exchange, HX asks the model for a short name for the session and
669
727
  writes it to `~/.hx/sessions/<id>/meta.json`, so `/resume` lists work rather
670
728
  than timestamps. It is one small call — cap 32 output tokens, `models.title_model`
671
729
  if you want a cheaper model for it — and it is counted in `/cost` like any other.
672
- If the call fails the session is still named, from your first message. `/title
673
- <text>` renames it.
730
+ If the call fails, or you leave before it is back, the session is still named,
731
+ from your first message.
732
+
733
+ The live session is never renamed while it runs, and nothing is renamed on
734
+ `/exit`, which returns your shell at once. A session you leave with `/clear` or
735
+ `/resume` - including a `/resume` into another directory - is renamed in the
736
+ background for what it became. `/title <text>` renames it yourself, and a name
737
+ you chose is never replaced.
674
738
 
675
739
  `/resume` lists every session except the one you are in: this directory's
676
740
  first, then other directories', each row naming where it lives. A session from
@@ -55,6 +55,9 @@ dev = [
55
55
  # A VT emulator, so tests can assert what the differ actually paints rather
56
56
  # than what it claims to have written.
57
57
  "pyte>=0.8",
58
+ # A throwaway CA, so the TLS tests can stand up the intercepting proxy a
59
+ # corporate network puts between HX and the API.
60
+ "trustme>=1.2",
58
61
  ]
59
62
 
60
63
  [project.scripts]
@@ -1,5 +1,5 @@
1
1
  """HX - an agent harness for the terminal."""
2
2
 
3
- __version__ = "0.2.11"
3
+ __version__ = "0.2.14"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -107,7 +107,13 @@ class SubagentRunner:
107
107
  try:
108
108
  loop = self._build_loop(definition, subagent_id)
109
109
  result = await loop.run(prompt)
110
- report = _final_text(result) or "(the subagent returned no text)"
110
+ report = _final_text(result)
111
+ if result.error is not None:
112
+ # The parent model only sees the report: without the reason it
113
+ # cannot tell a refused or broken run from one with nothing to say.
114
+ failure = f"The subagent failed: {result.error}"
115
+ report = f"{report}\n\n{failure}" if report else failure
116
+ report = report or "(the subagent returned no text)"
111
117
  cost = loop.session.usage.total_cost_usd
112
118
  self._roll_up_cost(loop)
113
119
  return SubagentResult(
@@ -167,6 +173,7 @@ class SubagentRunner:
167
173
  model_info=self.models.get_or_default(model) if self.models else None,
168
174
  project_context=self.project_context,
169
175
  hooks=self.hooks,
176
+ models=self.models,
170
177
  )
171
178
  loop.origin = f"{definition.name} subagent"
172
179
  return loop