Skip to content

Commit fd75f41

Browse files
committed
rc.6
1 parent c1803b7 commit fd75f41

12 files changed

Lines changed: 160 additions & 43 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@ If the proxy dies unexpectedly, a watchdog restarts it; after repeated failures
6666
| Tool | What it does | Default |
6767
|------|-------------|---------|
6868
| [headroom](https://pypi.org/project/headroom-ai/) | Prompt optimization pipeline (Python) | Required |
69-
| [rtk](https://github.com/gglucass/rtk) | Rewrites Claude Code bash commands to strip noise before it reaches the context window | Auto-enabled |
70-
| vitals | Project health scanner — flags stale deps, large files, drift | Included |
69+
| [rtk](https://github.com/gglucass/rtk) | Rewrites Claude Code bash commands to strip noise before it reaches the context window | Opt-in add-on |
70+
| [markitdown](https://github.com/microsoft/markitdown) | Converts PDFs and Office documents to clean Markdown before the agent reads them | Opt-in add-on |
71+
| ponytail | Nudges the agent toward leaner, less over-engineered code | Opt-in add-on |
7172

7273
**Tool inclusion policy:** only tools that run entirely locally, inside Headroom-managed storage, with a stable CLI surface make it in. No cloud dependencies, no host profile mutations. See [`research/tool-compatibility-matrix.md`](research/tool-compatibility-matrix.md).
7374

docs/beta-smoke-test.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ Expect: mtime within the last minute. `lastTransformation` inside the file is a
2828

2929
### 3. RTK is on PATH and reports savings (Claude Code only — RTK does not rewrite Codex)
3030
```bash
31-
rtk --version && rtk gain | head -5
31+
zsh -lc 'rtk --version && rtk gain | head -5'
3232
```
33-
Expect: a version line and a gain summary, no "command not found".
33+
Expect: a version line and a gain summary, no "command not found". The `zsh -lc` wrapper is required: `rtk` is added to PATH by the `headroom:managed_rtk` block in `~/.zprofile`, which only a login shell sources. Claude Code's Bash tool (and Codex's shell tool) spawn a non-login, non-interactive shell that does *not* source it, so a bare `rtk` here reports `command not found` on a perfectly healthy install. A login shell exercises the same PATH wiring a real terminal gets, so this confirms both that the managed block is intact and that the binary runs.
3434

3535
### 4. MCP retrieve tool is available (Claude Code only; only if memory tools are enabled)
3636
First check whether the proxy was started with memory tools:
@@ -164,6 +164,12 @@ When inspecting the running proxy by hand (e.g. checking `/stats`), wrap `curl`
164164
rtk proxy curl -s http://127.0.0.1:6767/stats | jq .summary
165165
```
166166

167+
Every `rtk` invocation in this doc (checks 3, 7, C2, and above) has the same PATH caveat as check 3: when Claude Code or Codex runs them through their shell tool, `rtk` is not on PATH because the non-login shell never sources `~/.zprofile`. Either wrap the command in `zsh -lc '...'`, or call the binary by its managed path:
168+
169+
```bash
170+
"$HOME/Library/Application Support/Headroom/headroom/bin/rtk" proxy curl -s http://127.0.0.1:6767/stats | jq .summary
171+
```
172+
167173
## When something fails
168174

169175
- Proxy log silent → check `~/Library/Application Support/Headroom/headroom/logs/` for a newer log file or a crash file.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "headroom-desktop",
3-
"version": "0.4.6-rc.5",
3+
"version": "0.4.6-rc.6",
44
"private": true,
55
"type": "module",
66
"scripts": {

src-tauri/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.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "headroom-desktop"
3-
version = "0.4.6-rc.5"
3+
version = "0.4.6-rc.6"
44
description = "Headroom v1 local-first LLM optimization tray app"
55
authors = ["Codex"]
66
license = "MIT"

src-tauri/src/client_adapters.rs

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,16 +319,19 @@ pub fn verify_client_setup(client_id: &str) -> Result<ClientSetupVerification> {
319319
.into(),
320320
);
321321
}
322-
// RTK is a separate, user-toggleable integration (`set_rtk_enabled`
323-
// tears it down without touching ANTHROPIC_BASE_URL routing). When
324-
// the user has deliberately disabled RTK, its absence must not fail
325-
// Claude Code verification — routing is what "connected" means here.
326-
if !state.rtk_disabled && !rtk_path_ok {
322+
// RTK is a separate, opt-in integration (`set_rtk_enabled` tears it
323+
// down without touching ANTHROPIC_BASE_URL routing). Its wiring is
324+
// only ever added when the managed binary exists on disk (see
325+
// `ensure_rtk_integrations_for_targets`), so its absence must not
326+
// fail Claude Code verification when RTK isn't installed or the user
327+
// disabled it — routing is what "connected" means here.
328+
let rtk_required = !state.rtk_disabled && default_headroom_rtk_path().exists();
329+
if rtk_required && !rtk_path_ok {
327330
failures.push(
328331
"Headroom-managed RTK PATH export was not found in shell profiles.".into(),
329332
);
330333
}
331-
if !state.rtk_disabled && !rtk_hook_ok {
334+
if rtk_required && !rtk_hook_ok {
332335
failures.push(
333336
"Headroom-managed RTK Claude hook was not found in ~/.claude/settings.json."
334337
.into(),
@@ -4069,6 +4072,46 @@ export ANTHROPIC_BASE_URL=http://127.0.0.1:6767
40694072
);
40704073
}
40714074

4075+
#[test]
4076+
#[serial_test::serial]
4077+
fn verify_claude_code_passes_when_rtk_not_installed() {
4078+
let home = TestHome::new();
4079+
fs::write(home.path().join(".zshrc"), "# user zshrc\n").unwrap();
4080+
fs::write(home.path().join(".zshenv"), "# user zshenv\n").unwrap();
4081+
fs::create_dir_all(home.path().join(".claude")).unwrap();
4082+
fs::write(
4083+
home.path().join(".claude").join("settings.json"),
4084+
r#"{"hooks": {}}"#,
4085+
)
4086+
.unwrap();
4087+
4088+
// Clean install with RTK auto-install removed: routing is configured but
4089+
// the managed RTK binary was never dropped on disk and the user never
4090+
// toggled RTK off (rtk_disabled stays false). Claude Code must still
4091+
// verify green on routing alone.
4092+
super::apply_client_setup("claude_code").expect("apply_client_setup succeeds");
4093+
4094+
assert!(
4095+
!super::default_headroom_rtk_path().exists(),
4096+
"RTK binary must be absent for this test"
4097+
);
4098+
let state = super::load_setup_state();
4099+
assert!(!state.rtk_disabled, "rtk_disabled stays false when untoggled");
4100+
4101+
let verification =
4102+
super::verify_client_setup("claude_code").expect("verify_client_setup succeeds");
4103+
assert!(
4104+
verification.verified,
4105+
"claude_code verifies on routing alone when RTK isn't installed, failures: {:?}",
4106+
verification.failures
4107+
);
4108+
assert!(
4109+
verification.failures.iter().all(|f| !f.contains("RTK")),
4110+
"no RTK failures reported when RTK isn't installed, got: {:?}",
4111+
verification.failures
4112+
);
4113+
}
4114+
40724115
#[test]
40734116
#[serial_test::serial]
40744117
fn ensure_rtk_integrations_writes_codex_nudge_and_disable_removes_it() {

src-tauri/src/lib.rs

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ fn show_notification_impl(
627627
}
628628

629629
#[tauri::command]
630-
fn install_addon(state: State<'_, AppState>, id: String) -> Result<DashboardState, String> {
630+
async fn install_addon(state: State<'_, AppState>, id: String) -> Result<DashboardState, String> {
631631
match id.as_str() {
632632
"markitdown" => {
633633
state
@@ -667,7 +667,7 @@ fn install_addon(state: State<'_, AppState>, id: String) -> Result<DashboardStat
667667
}
668668

669669
#[tauri::command]
670-
fn set_addon_enabled(
670+
async fn set_addon_enabled(
671671
state: State<'_, AppState>,
672672
id: String,
673673
enabled: bool,
@@ -705,7 +705,7 @@ fn set_addon_enabled(
705705
}
706706

707707
#[tauri::command]
708-
fn uninstall_addon(state: State<'_, AppState>, id: String) -> Result<DashboardState, String> {
708+
async fn uninstall_addon(state: State<'_, AppState>, id: String) -> Result<DashboardState, String> {
709709
match id.as_str() {
710710
"markitdown" => {
711711
let _ = client_adapters::disable_markitdown_integration(
@@ -1951,6 +1951,10 @@ fn get_headroom_request_count() -> Option<u64> {
19511951
}
19521952

19531953
fn fetch_proxy_request_count_stats() -> Option<u64> {
1954+
parse_request_count_from_stats_body(&fetch_proxy_stats_body()?)
1955+
}
1956+
1957+
fn fetch_proxy_stats_body() -> Option<String> {
19541958
let client = reqwest::blocking::Client::builder()
19551959
.timeout(std::time::Duration::from_millis(500))
19561960
.build()
@@ -1963,14 +1967,43 @@ fn fetch_proxy_request_count_stats() -> Option<u64> {
19631967
if !response.status().is_success() {
19641968
continue;
19651969
}
1966-
let Ok(body) = response.text() else { continue };
1967-
if let Some(count) = parse_request_count_from_stats_body(&body) {
1968-
return Some(count);
1970+
if let Ok(body) = response.text() {
1971+
return Some(body);
19691972
}
19701973
}
19711974
None
19721975
}
19731976

1977+
/// Per-agent request counts from `/stats` `agent_usage.agents[]`, keyed by the
1978+
/// proxy's agent id (`claude-code`, `codex`, ...). Used by setup verification
1979+
/// so a prompt sent to one client only flips that client's row, not all rows.
1980+
#[tauri::command]
1981+
fn get_headroom_request_counts_by_agent() -> Option<std::collections::HashMap<String, u64>> {
1982+
parse_request_counts_by_agent(&fetch_proxy_stats_body()?)
1983+
}
1984+
1985+
pub(crate) fn parse_request_counts_by_agent(
1986+
body: &str,
1987+
) -> Option<std::collections::HashMap<String, u64>> {
1988+
let root = serde_json::from_str::<serde_json::Value>(body).ok()?;
1989+
let mut counts = std::collections::HashMap::new();
1990+
if let Some(agents) = root
1991+
.get("agent_usage")
1992+
.and_then(|v| v.get("agents"))
1993+
.and_then(|v| v.as_array())
1994+
{
1995+
for agent in agents {
1996+
if let (Some(key), Some(requests)) = (
1997+
agent.get("agent").and_then(|v| v.as_str()),
1998+
agent.get("requests").and_then(|v| v.as_u64()),
1999+
) {
2000+
counts.insert(key.to_string(), requests);
2001+
}
2002+
}
2003+
}
2004+
Some(counts)
2005+
}
2006+
19742007
/// Pull `requests.total` (or any of the legacy spellings) out of a /stats
19752008
/// JSON body. Mirrors the lookup in `state::parse_headroom_stats_from_json`
19762009
/// but trimmed to just the counter we need for verification.
@@ -3278,6 +3311,7 @@ pub fn run() {
32783311
get_runtime_status,
32793312
get_headroom_logs,
32803313
get_headroom_request_count,
3314+
get_headroom_request_counts_by_agent,
32813315
get_rtk_activity,
32823316
get_tool_logs,
32833317
get_claude_code_projects,
@@ -5170,7 +5204,8 @@ mod tests {
51705204
is_disk_full_signal, is_endpoint_protection_signal, is_network_download_signal,
51715205
is_port_conflict_failure, is_prerelease_version, lifetime_token_milestone_kind,
51725206
noop_app_update_progress_emitter, parse_live_learnings,
5173-
parse_request_count_from_stats_body, parse_updater_endpoint_list, pattern_matches_project,
5207+
parse_request_count_from_stats_body, parse_request_counts_by_agent,
5208+
parse_updater_endpoint_list, pattern_matches_project,
51745209
physical_rect_from_rect, read_applied_patterns_for_project, readyz_failed_checks_csv,
51755210
readyz_failure_has_core_unhealthy, readyz_failure_is_upstream_only,
51765211
resolve_release_updater_config, select_updater_endpoints, store_checked_update,
@@ -6534,6 +6569,29 @@ Some unrelated content.
65346569
assert_eq!(parse_request_count_from_stats_body("not json"), None);
65356570
}
65366571

6572+
#[test]
6573+
fn parse_request_counts_by_agent_keys_by_agent_id() {
6574+
let body = json!({
6575+
"agent_usage": {
6576+
"agents": [
6577+
{ "agent": "claude-code", "requests": 5 },
6578+
{ "agent": "codex", "requests": 2 }
6579+
]
6580+
}
6581+
})
6582+
.to_string();
6583+
let counts = parse_request_counts_by_agent(&body).unwrap();
6584+
assert_eq!(counts.get("claude-code"), Some(&5));
6585+
assert_eq!(counts.get("codex"), Some(&2));
6586+
6587+
// Proxy up, no traffic yet: empty map, not None.
6588+
let empty = json!({ "agent_usage": { "agents": [] } }).to_string();
6589+
assert!(parse_request_counts_by_agent(&empty).unwrap().is_empty());
6590+
6591+
// Unparseable body is None so the poller treats it as unreachable.
6592+
assert!(parse_request_counts_by_agent("not json").is_none());
6593+
}
6594+
65376595
#[test]
65386596
fn build_watchdog_give_up_report_uses_exit_status_when_present() {
65396597
let report = build_watchdog_give_up_report(

src-tauri/src/tool_manager.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3508,6 +3508,7 @@ impl ToolManager {
35083508
/// one host (Claude Code or Codex) still has the plugin registered, so a
35093509
/// user who removes it via `/plugin` doesn't leave the card stuck on
35103510
/// "Enabled".
3511+
#[cfg(test)]
35113512
pub fn ponytail_installed(&self) -> bool {
35123513
self.runtime.tools_dir.join("ponytail.json").exists()
35133514
&& PluginHost::ALL.iter().any(|host| host.plugin_present())

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "Headroom",
4-
"version": "0.4.6-rc.5",
4+
"version": "0.4.6-rc.6",
55
"identifier": "com.extraheadroom.headroom",
66
"build": {
77
"beforeDevCommand": "npm run dev",

0 commit comments

Comments
 (0)