Skip to content

Commit dd28dfd

Browse files
author
Yogthos
committed
release: 0.21.19
1 parent 3f38dc0 commit dd28dfd

3 files changed

Lines changed: 99 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,103 @@ All notable changes to dirge are documented here. The format follows
44
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
55
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [0.21.19] - 2026-08-14
8+
9+
### Fixed
10+
- A panic anywhere in a run hung the TUI permanently. `panic = "abort"` is not
11+
set, so a panic unwinds out of the agent task, tokio catches it, and the
12+
event channel closes — and the UI's select arm reads a closed channel as
13+
"nothing more to poll" and silently disables itself. `is_running` is cleared
14+
only by events that can no longer arrive, so the run sat at "running" with
15+
nothing left that could change its mind: no error, no timeout, and nothing on
16+
screen to tell it apart from thinking. Tools are awaited inline in that task,
17+
so any tool's `unwrap`, a slice index off a char boundary, or a debug-build
18+
overflow went this way. A run's terminal event is now a property of the
19+
task's lifetime rather than of the path it happened to take, so a crash
20+
arrives as the error it always should have been — naming the panic and its
21+
source location — and the prompt comes back. `--print` and ACP drain the same
22+
channel and get it too.
23+
- A caught panic reset the terminal of a process that kept running. The panic
24+
hook decided whether to restore the terminal by asking whether the panicking
25+
thread owned it, which is true for every agent-task panic on a
26+
single-threaded runtime — so a survivable panic cleared the screen, left raw
27+
mode, and latched a flag that made the real teardown skip its own reset
28+
later. The hook now only records what happened; whoever survives the panic
29+
reports it, and the terminal teardown prints an unclaimed record after
30+
restoring the screen. A panic some `catch_unwind` swallowed now gets one line
31+
at exit instead of living only in a log.
32+
- A tool call a model wrote as text was printed to the user as the turn's
33+
answer. The command ran and the reply was its source code. Two further
34+
defects came out of the same gap: the call never reached the assistant
35+
message, so the next request carried a `tool` result with no preceding
36+
`tool_calls` (a hard 400 on OpenAI and Anthropic), and those calls carried an
37+
empty id, so two in one turn were indistinguishable to result matching, the
38+
storm signature and the publish guard. Such a region is now hidden exactly
39+
when it dispatches — so a call that names a tool this run does not have stays
40+
on screen, and a ```` ```json ```` block a model is deliberately showing you
41+
never vanishes.
42+
- Renewing an expired provider token froze the whole program. Three transports
43+
resolved their bearer synchronously on the per-request path, and the
44+
refresher spawns an OS thread and joins it around a blocking HTTP exchange.
45+
On a single-threaded runtime that is the only thread: nothing painted, no
46+
keystroke was read, and no timer could fire — including any timeout meant to
47+
bound whatever was stuck. Kimi access tokens live 15 minutes, so it recurred
48+
through a long session. Resolution is off-thread now, with no thread hop at
49+
all on the common path where nothing needs renewing.
50+
- Ctrl+C did not escape the `question` modal. It cancels in every other modal;
51+
here it did nothing in the option list, and in the custom-answer field it
52+
typed a literal `c` into the answer.
53+
- Nearest-name suggestions for an unknown tool pointed at unrelated tools —
54+
`exec``spec`, `shell``skill`, `ask``task` — in a message with no
55+
hedge, steering a model that wanted a shell toward a spec-management tool.
56+
Of eleven plausible guesses, six were wrong; now one. Names that differ from
57+
a real tool only in case or separators resolve directly.
58+
- Automatic compaction had none of the prompt-injection fencing `/compact` has
59+
had all along, and it is the path that runs unattended. It also could not see
60+
tool CALLS — only their results — so a fold recorded outcomes with no record
61+
of what produced them; measured at 0/6 facts preserved that lived only in
62+
call arguments, now 6/6. Both paths are one implementation.
63+
- A `/compact` summary entered the loop without the marker an automatic fold
64+
writes, so the next fold could not find it and stacked a second summary
65+
behind it.
66+
- A headless failure printed its error to stderr twice, verbatim.
67+
- A model-supplied `bash` timeout ignored the configured ceiling: a model
68+
asking for 3600 got it however low `timeouts.bash_secs` was set. It is
69+
clamped by `timeouts.bash_max_secs` (default 3600), and the schema the model
70+
reads is rendered from the resolved config.
71+
72+
### Added
73+
- `timeouts.tool_call_secs` (default 600) — a ceiling on one tool dispatch.
74+
Every tool keeps its own tighter bound; this exists so a tool that forgets to
75+
bound itself, or one on a path that skips its own client, cannot stall a run
76+
silently. Tools whose own bound is legitimately larger declare it, and the
77+
budget does not run while a call is waiting on you: the permission prompt,
78+
the `question` tool and `/plan` approval all wait for a person from inside
79+
the window being bounded, and killing a call somebody is halfway through
80+
approving is worse than the stall this catches. It bounds waits, not blocked
81+
threads — a tool that blocks the runtime never lets the timer be polled.
82+
- A per-turn `<turn_envelope>` carrying the volatile session facts (cwd, mode,
83+
model, todos, modified files), rebuilt each turn and replacing the previous
84+
one instead of stacking behind it. The frozen prefix keeps only what does not
85+
change, so it stays cacheable.
86+
- The prompt's tool section is rendered from the live registry rather than a
87+
literal, so prompt-level `deny_tools`, umbrella deny rules and dynamic tool
88+
search can no longer make the prompt describe a capability boundary the loop
89+
does not have. Both of these default on: 2 of 6 control runs blew up against
90+
0 of 6 under treatment.
91+
- Tool failures are classified (misuse / missing-info / transient / fatal).
92+
Transient failures retry with backoff on reads only, and a missing-info error
93+
weighs more when estimating whether a model is coping.
94+
- `prompt_leak_detect`, off by default, for a model reciting its own system
95+
prompt back. Advisory mode exists so it can be measured before it is trusted.
96+
97+
### Changed
98+
- An interrupted mutating tool call no longer tells the model to retry it. What
99+
a call landed — committed, unknown, or nothing — is classified and handed to
100+
the next turn instead.
101+
- A truncated assistant turn is marked incomplete where the model can see it,
102+
not only in the UI.
103+
7104
## [0.21.18] - 2026-08-12
8105

9106
### Security

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# taken on crates.io. The installed BINARY is still `dirge` (see [[bin]]),
44
# so users run: `cargo install dirge-agent` then the `dirge` command.
55
name = "dirge-agent"
6-
version = "0.21.18"
6+
version = "0.21.19"
77
edition = "2024"
88
# MSRV: edition 2024 needs 1.85, but the dependency tree (time, serde_with,
99
# darling, instability) requires 1.88 — pin there so older toolchains fail

0 commit comments

Comments
 (0)