Skip to content

Commit 3c84e76

Browse files
authored
Merge pull request #5313 from Hmbown/fix/v096-shell-wait-progress
chore(release): prepare v0.9.6
2 parents 7aa5c6b + a11c6f3 commit 3c84e76

103 files changed

Lines changed: 4590 additions & 6140 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/scripts/release-workflows.test.js

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ function valuesForKey(source, key) {
2121
return [...source.matchAll(expression)].map((match) => match[1]);
2222
}
2323

24+
function namedStep(source, name) {
25+
const marker = ` - name: ${name}\n`;
26+
const start = source.indexOf(marker);
27+
assert.notEqual(start, -1, `missing workflow step: ${name}`);
28+
const next = source.indexOf("\n - ", start + marker.length);
29+
return source.slice(start, next === -1 ? source.length : next);
30+
}
31+
2432
const ci = read(".github/workflows/ci.yml");
2533
const nightly = read(".github/workflows/nightly.yml");
2634
const candidate = read(".github/workflows/release-candidate.yml");
@@ -51,7 +59,7 @@ assert.doesNotMatch(ci, /--test qa_pty\b/, "CI must not name the removed qa_pty
5159

5260
const expectedNightlyTargets = [
5361
"x86_64-unknown-linux-gnu",
54-
"aarch64-unknown-linux-gnu",
62+
"aarch64-unknown-linux-musl",
5563
"x86_64-apple-darwin",
5664
"aarch64-apple-darwin",
5765
"x86_64-pc-windows-msvc",
@@ -84,6 +92,21 @@ assert.match(
8492
);
8593
assert.match(nightly, /startsWith\(matrix\.target, 'x86_64-'\).*runner\.arch == 'X64'/s);
8694
assert.match(nightly, /startsWith\(matrix\.target, 'aarch64-'\).*runner\.arch == 'ARM64'/s);
95+
const nightlyArmMuslSetup = namedStep(nightly, "Install Linux ARM64 musl toolchain");
96+
assert.match(nightlyArmMuslSetup, /matrix\.target == 'aarch64-unknown-linux-musl'/);
97+
assert.match(nightlyArmMuslSetup, /apt-get install -y binutils musl-tools/);
98+
assert.match(nightlyArmMuslSetup, /rustup target add --toolchain stable aarch64-unknown-linux-musl/);
99+
const nightlyArmStaticSmoke = namedStep(
100+
nightly,
101+
"Verify static Linux ARM64 binary and launch",
102+
);
103+
assert.match(
104+
nightlyArmStaticSmoke,
105+
/matrix\.target == 'aarch64-unknown-linux-musl' && runner\.arch == 'ARM64'/,
106+
);
107+
assert.match(nightlyArmStaticSmoke, /readelf -l "\$\{bin_path\}"/);
108+
assert.match(nightlyArmStaticSmoke, /grep -Fq 'INTERP'/);
109+
assert.match(nightlyArmStaticSmoke, /"\$\{bin_path\}" --version/);
87110
assert.doesNotMatch(nightly, /codewhale-tui/);
88111
assert.doesNotMatch(nightly, /target\/[^\n]*\/codew(?:\.exe)?/);
89112
assert.match(nightly, /cp "\$\{bin_path\}" "\$\{dir\}\/\$\{artifact\}"/);
@@ -153,7 +176,7 @@ assert.match(artifacts, /^ workflow_call:/m);
153176
assert.match(artifacts, /^permissions:\n contents: read$/m);
154177
const expectedTargets = [
155178
"x86_64-unknown-linux-musl",
156-
"aarch64-unknown-linux-gnu",
179+
"aarch64-unknown-linux-musl",
157180
"aarch64-linux-android",
158181
"x86_64-apple-darwin",
159182
"aarch64-apple-darwin",
@@ -162,6 +185,27 @@ const expectedTargets = [
162185
].sort();
163186
assert.deepEqual([...new Set(valuesForKey(artifacts, "target"))].sort(), expectedTargets);
164187

188+
const releaseMuslBuild = namedStep(artifacts, "Build static Linux binaries (musl)");
189+
assert.match(releaseMuslBuild, /endsWith\(matrix\.target, '-unknown-linux-musl'\)/);
190+
assert.match(releaseMuslBuild, /apt-get install -y binutils musl-tools/);
191+
assert.match(releaseMuslBuild, /rustup target add --toolchain stable \$\{\{ matrix\.target \}\}/);
192+
assert.match(
193+
releaseMuslBuild,
194+
/cargo build --profile dist --locked --target \$\{\{ matrix\.target \}\} -p codewhale-cli/,
195+
);
196+
const releaseStaticSmoke = namedStep(
197+
artifacts,
198+
"Verify static Linux binaries and launch on matching native runners",
199+
);
200+
assert.match(releaseStaticSmoke, /endsWith\(matrix\.target, '-unknown-linux-musl'\)/);
201+
assert.match(
202+
releaseStaticSmoke,
203+
/startsWith\(matrix\.target, 'aarch64-'\) && runner\.arch == 'ARM64'/,
204+
);
205+
assert.match(releaseStaticSmoke, /readelf -l "\$\{bin_path\}"/);
206+
assert.match(releaseStaticSmoke, /grep -Fq 'INTERP'/);
207+
assert.match(releaseStaticSmoke, /"\$\{bin_path\}" --version/);
208+
165209
const builtAssetNames = [
166210
...valuesForKey(artifacts, "cli_artifact"),
167211
...valuesForKey(artifacts, "shim_artifact"),

.github/workflows/nightly.yml

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
primary_artifact: codewhale-linux-x64
3838
alias_artifact: codew-linux-x64
3939
- os: ubuntu-24.04-arm
40-
target: aarch64-unknown-linux-gnu
40+
target: aarch64-unknown-linux-musl
4141
platform: linux-arm64
4242
binary: codewhale
4343
primary_artifact: codewhale-linux-arm64
@@ -88,15 +88,22 @@ jobs:
8888
- uses: Swatinem/rust-cache@v2
8989
with:
9090
cache-bin: false
91-
- name: Install Linux system dependencies
92-
if: runner.os == 'Linux'
91+
- name: Install Linux GNU system dependencies
92+
if: matrix.target == 'x86_64-unknown-linux-gnu'
9393
run: |
9494
for i in 1 2 3 4 5; do
9595
sudo apt-get update && break
9696
echo "apt-get update failed (attempt $i); retrying in 15s"
9797
sleep 15
9898
done
9999
sudo apt-get install -y libdbus-1-dev pkg-config
100+
- name: Install Linux ARM64 musl toolchain
101+
if: matrix.target == 'aarch64-unknown-linux-musl'
102+
shell: bash
103+
run: |
104+
sudo apt-get update
105+
sudo apt-get install -y binutils musl-tools
106+
rustup target add --toolchain stable aarch64-unknown-linux-musl
100107
- name: Build
101108
shell: bash
102109
# Nightly artifacts are disposable smoke binaries (14-day retention),
@@ -117,6 +124,17 @@ jobs:
117124
done
118125
echo "Build failed after 3 attempts" >&2
119126
exit 1
127+
- name: Verify static Linux ARM64 binary and launch
128+
if: matrix.target == 'aarch64-unknown-linux-musl' && runner.arch == 'ARM64'
129+
shell: bash
130+
run: |
131+
set -euo pipefail
132+
bin_path="target/${{ matrix.target }}/release/${{ matrix.binary }}"
133+
if readelf -l "${bin_path}" | grep -Fq 'INTERP'; then
134+
echo "Expected a static musl binary, but ${bin_path} has an ELF interpreter" >&2
135+
exit 1
136+
fi
137+
"${bin_path}" --version
120138
- name: Smoke binary on matching native runners
121139
if: >-
122140
(startsWith(matrix.target, 'x86_64-') && runner.arch == 'X64') ||

.github/workflows/release-artifacts.yml

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
shim_artifact: codew-linux-x64
4646
tui_artifact: codewhale-tui-linux-x64
4747
- os: ubuntu-24.04-arm
48-
target: aarch64-unknown-linux-gnu
48+
target: aarch64-unknown-linux-musl
4949
platform: linux-arm64
5050
cli_binary: codewhale
5151
shim_binary: codew
@@ -116,23 +116,14 @@ jobs:
116116
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
117117
with:
118118
cache-bin: false
119-
- name: Install Linux ARM64 system dependencies
120-
if: matrix.target == 'aarch64-unknown-linux-gnu'
121-
run: |
122-
for i in 1 2 3 4 5; do
123-
sudo apt-get update && break
124-
echo "apt-get update failed (attempt $i); retrying in 15s"
125-
sleep 15
126-
done
127-
sudo apt-get install -y libdbus-1-dev pkg-config
128-
- name: Build static Linux x64 binaries (musl)
129-
if: matrix.target == 'x86_64-unknown-linux-musl'
119+
- name: Build static Linux binaries (musl)
120+
if: endsWith(matrix.target, '-unknown-linux-musl')
130121
shell: bash
131122
run: |
132123
sudo apt-get update
133-
sudo apt-get install -y musl-tools
134-
rustup target add --toolchain stable x86_64-unknown-linux-musl
135-
cargo build --profile dist --locked --target x86_64-unknown-linux-musl -p codewhale-cli
124+
sudo apt-get install -y binutils musl-tools
125+
rustup target add --toolchain stable ${{ matrix.target }}
126+
cargo build --profile dist --locked --target ${{ matrix.target }} -p codewhale-cli
136127
- name: Configure Android NDK linker
137128
if: matrix.target == 'aarch64-linux-android' && runner.os == 'Linux'
138129
shell: bash
@@ -180,7 +171,7 @@ jobs:
180171
echo "BINDGEN_EXTRA_CLANG_ARGS_aarch64_linux_android=--target=aarch64-linux-android24 --sysroot=${ndk}/toolchains/llvm/prebuilt/linux-x86_64/sysroot"
181172
} >> "${GITHUB_ENV}"
182173
- name: Build
183-
if: matrix.target != 'x86_64-unknown-linux-musl'
174+
if: ${{ !endsWith(matrix.target, '-unknown-linux-musl') }}
184175
shell: bash
185176
run: cargo build --profile dist --locked --target ${{ matrix.target }} -p codewhale-cli
186177
- name: Materialize codew command alias
@@ -189,6 +180,23 @@ jobs:
189180
bin_dir="target/${{ matrix.target }}/dist"
190181
cp "${bin_dir}/${{ matrix.cli_binary }}" "${bin_dir}/${{ matrix.shim_binary }}"
191182
cmp "${bin_dir}/${{ matrix.cli_binary }}" "${bin_dir}/${{ matrix.shim_binary }}"
183+
- name: Verify static Linux binaries and launch on matching native runners
184+
if: >-
185+
endsWith(matrix.target, '-unknown-linux-musl') &&
186+
((startsWith(matrix.target, 'x86_64-') && runner.arch == 'X64') ||
187+
(startsWith(matrix.target, 'aarch64-') && runner.arch == 'ARM64'))
188+
shell: bash
189+
run: |
190+
set -euo pipefail
191+
bin_dir="target/${{ matrix.target }}/dist"
192+
for binary in "${{ matrix.cli_binary }}" "${{ matrix.shim_binary }}"; do
193+
bin_path="${bin_dir}/${binary}"
194+
if readelf -l "${bin_path}" | grep -Fq 'INTERP'; then
195+
echo "Expected a static musl binary, but ${bin_path} has an ELF interpreter" >&2
196+
exit 1
197+
fi
198+
"${bin_path}" --version
199+
done
192200
- name: Smoke binaries on matching native runners
193201
if: >-
194202
matrix.target != 'aarch64-linux-android' &&

CHANGELOG.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,134 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535
compatible gateways keep the generic Chat contract. FIM
3636
(`/v1/fim/completions`) is not wired.
3737

38+
## [0.9.6] - 2026-08-09
39+
40+
Codewhale v0.9.6 is a subtractive release. The runtime stopped supervising the
41+
model and started getting out of its way: the guards that interrupted live work
42+
are gone, mode-specific prompt doctrine is gone, compaction was rebuilt on a
43+
much smaller design, and a truncated provider response can no longer be
44+
recorded as a finished answer.
45+
46+
Most of these were found by running the v0.9.5 binary against Terminal-Bench
47+
2.1 beside Pi 0.8.41 on the same model, effort, endpoint, and task digests, and
48+
then reading the trials Codewhale lost.
49+
50+
### Added
51+
52+
- Mistral AI (la Plateforme) is now a first-class OpenAI-compatible provider
53+
route. Select it with `provider = "mistral"`, `CODEWHALE_PROVIDER=mistral`,
54+
or `codewhale --provider mistral`. Aliases `mistral-ai`, `mistralai`, and
55+
`la-plateforme` resolve to the same route. Authenticate with
56+
`MISTRAL_API_KEY` (get one at <https://console.mistral.ai/api-keys>), or
57+
via `[providers.mistral].api_key` / `codewhale auth set --provider mistral`.
58+
Endpoint defaults to `https://api.mistral.ai/v1`; model defaults to
59+
`mistral-code-latest` (Codestral coding model, 256K context; the historical
60+
`codestral-latest` slug is accepted as an alias). The current static picker
61+
ships `mistral-code-latest`, `mistral-medium-latest`,
62+
`mistral-small-latest`, and `mistral-large-latest` with 256K-class windows.
63+
Adjustable reasoning is wired end-to-end for `mistral-medium-latest` and
64+
`mistral-small-latest`: Codewhale sends `reasoning_effort` (`none` or `high`
65+
only — intermediate tiers get HTTP 400 code 3051), parses the polymorphic
66+
`content: [{type: thinking, thinking: [{type: text, text: ...}], closed:
67+
bool}, {type: text, text: ...}]` shape emitted during reasoning, and
68+
replays the thinking trace back into multi-turn history per the
69+
[official reasoning guide](https://docs.mistral.ai/studio-api/conversations/reasoning).
70+
Deprecated native Magistral IDs remain accepted
71+
when configured explicitly, always replay thinking, and never receive the
72+
adjustable effort field. Non-reasoning models (`mistral-code-latest`,
73+
`mistral-large-latest`) never receive it. Mistral-specific reasoning wire
74+
behavior is limited to the documented first-party HTTPS `/v1` hosts; custom
75+
compatible gateways keep the generic Chat contract. FIM
76+
(`/v1/fim/completions`) is not wired.
77+
- **Persistent background services for headless exec.** `Bash` accepts
78+
`persist: true` (Unix, real `codewhale exec`, explicit
79+
`--sandbox danger-full-access`, `background: true` only). The service starts
80+
with null stdio in its own process group; a successful exec transfers
81+
ownership and emits a release receipt naming the pid. Failure, cancellation,
82+
a terminating signal, or engine-channel EOF kills it and exits nonzero.
83+
Ordinary background jobs keep their existing kill-on-drop lifetime. Before
84+
this, a server the model started and verified died when the exec that
85+
started it succeeded, and the external verifier got connection refused.
86+
- **Static Linux ARM64 binaries.** Release and nightly now build
87+
`aarch64-unknown-linux-musl` on the native ARM runner, with a static check
88+
(no ELF interpreter) and a launch smoke on the matching runner. The previous
89+
GNU build inherited the builder's `GLIBC_2.39` floor and would not start on
90+
Ubuntu 22.04 ARM64 and similar images.
91+
92+
### Changed
93+
94+
- **`Bash action="wait"` blocks by default.** It previously computed blocking
95+
from a separate `wait` boolean defaulting to false, so
96+
`{"action":"wait","task_id":...,"timeout_ms":600000}` returned immediately
97+
and ignored the timeout. Pass `wait: false` for a nonblocking snapshot.
98+
`task_shell_wait` keeps its documented nonblocking default, including when
99+
`wait`/`block` arrive as null.
100+
- **Compaction rebuilt on the Codex design.** One summary request that is the
101+
live conversation plus a final handoff-summary message, so the provider's
102+
prefix cache covers everything already sent; a committed summary; and a
103+
replacement history of the recent user messages within a fixed token budget.
104+
On context-window overflow it drops the oldest history item and retries.
105+
Sessions saved under the previous format still restore their committed
106+
summary. Manual compaction remains nonblocking and serialized, both
107+
lifecycle labels persist for the real lifecycle, and the successor request
108+
carries the committed summary.
109+
- **One prompt for every mode.** Plan, Agent, and Operate previously shipped
110+
separate doctrine prompts that were prepended to the constitution, so a mode
111+
change rewrote the stable prefix. Modes differ in permissions and available
112+
tools, which runtime policy and the live tool catalog already express per
113+
turn. Headless hosts get a compact constitution; interactive hosts keep the
114+
full base; explicit embedder and base-prompt overrides still win.
115+
- **Goals are no longer bounded from inside the runtime.** The three-per-turn
116+
continuation cap, the auto-pause after three identical verifier gap sets, and
117+
the instruction to stop at any unanswered question are gone. `max_steps`,
118+
the opt-in `[goal] max_continuations` circuit breaker, and terminal
119+
complete/blocked status remain the ways a goal run ends.
120+
- `todo_write` is documented as an optional progress surface. Its description
121+
no longer instructs the model to keep the list live or never batch
122+
completions.
123+
124+
### Fixed
125+
126+
- **A truncated response is a failure, not an answer.** The turn loop read
127+
usage from the message delta and discarded its stop reason; trials that spent
128+
their whole output allowance on reasoning and emitted no answer were recorded
129+
`status=completed`, `termination_reason=resolved`. The stop reason is now
130+
retained end to end. On an incomplete stop the runtime charges the billed
131+
usage, keeps the visible fragment as interrupted rather than as a completed
132+
assistant message, closes every opened tool lifecycle without executing the
133+
call, and fails the turn with the provider's own reason. The Responses
134+
adapter preserves `incomplete_details.reason` instead of flattening it, so an
135+
unknown future reason cannot be mistaken for a finished answer.
136+
- The same rule now covers every remaining direct model consumer: compaction,
137+
the `review` and `verify` tools, MCP thread handling, purge, the advisor, the
138+
auto-route classifier, the fleet router, both setup drafts, and
139+
`codewhale review`.
140+
- **Step-budget exhaustion is a typed failure.** Reaching `max_steps` before
141+
completion is `Failed` / `BudgetExhausted` and cannot release a pending
142+
persistent service. A goal continuation injected on the final step no longer
143+
relabels an already delivered answer as a step-budget failure.
144+
- Cancellation arriving after the provider reported terminal usage still
145+
charges the turn.
146+
147+
### Removed
148+
149+
- **The no-progress stuck guard.** It fingerprinted steps by tool name and
150+
arguments with no result digest, so polling a live background job looked
151+
identical every time. It stopped `filter-js-from-html` while the task it was
152+
waiting on went on to pass, and stopped `llm-inference-batching-scheduler`
153+
and `mcmc-sampling-stan` mid-optimizer and mid-compile.
154+
- **The read-repeat guard.** It coalesced same-batch duplicate reads onto one
155+
execution and, past a threshold, replaced results with a receipt pointing at
156+
a prior tool-use id. A model that asks to read a file twice now reads it
157+
twice.
158+
- **Tool-error strategy coaching.** Errors were rewritten to append fallback
159+
advice, and a degradation hint fired after two consecutive error steps.
160+
Errors now return as the tool produced them.
161+
162+
### Contributors
163+
164+
- Xavier Pestel (@xavierpestel-ai) — Mistral AI provider route (#5295).
165+
38166
## [0.9.5] - 2026-08-08
39167

40168
Codewhale v0.9.5 consolidates the terminal application into one compiled
@@ -5262,6 +5390,7 @@ overflow report and `/theme` picker edge-wrapping patch in #1814.
52625390
Older releases (v0.8.39 and earlier) are archived in [docs/CHANGELOG_ARCHIVE.md](docs/CHANGELOG_ARCHIVE.md).
52635391

52645392
[Unreleased]: https://github.com/Hmbown/CodeWhale/compare/v0.9.5...HEAD
5393+
[0.9.6]: https://github.com/Hmbown/CodeWhale/compare/v0.9.5...v0.9.6
52655394
[0.9.5]: https://github.com/Hmbown/CodeWhale/compare/v0.9.4...v0.9.5
52665395
[0.9.4]: https://github.com/Hmbown/CodeWhale/compare/v0.9.3...v0.9.4
52675396
[0.9.3]: https://github.com/Hmbown/CodeWhale/compare/v0.9.2...v0.9.3

0 commit comments

Comments
 (0)