Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion asr-worker/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "asr-worker"
version = "1.17.1"
version = "1.17.2"
description = "Local ASR worker for english-trainer (Parakeet EN/JA routing)"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion asr-worker/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions doc/examples/skills/minimum-headroom-ops/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,20 @@ The detector ships with a small set of CLI-specific regex patterns. Coverage is

| Pattern id | regex | Trigger | Covers |
|---|---|---|---|
| `claude_approval` | `/Do you want to proceed\?/` | Tool / shell-command approval | **Claude Code**, **Antigravity** (same wording) |
| `claude_approval` | `/Do you want to proceed\?/` | Tool / shell-command / MCP approval | **Claude Code**, **Antigravity** (same wording, including the MCP tool modal) |
| `codex_approval` | `/Would you like to run the following command\?/` | Shell-command approval | **Codex** |
| `codex_picker` | `/Switch to (gpt\|claude\|gemini)-/` | Model picker | **Codex** |
| `codex_mcp_approval` | `/Allow the .+ MCP server to run tool/` | MCP tool-call approval (separate path from shell-command approval) | **Codex** |
| `agy_trust_folder` | `/Do you trust the contents of this project\?/` | First-run workspace trust prompt | **Antigravity** |
| `codex_picker` | `/Select Model and Effort/` | `/model` picker header | **Codex** |
| `codex_quota` | `/You've hit your usage limit/` | ChatGPT usage limit | **Codex** |
| `agy_survey` | `/How's the CLI experience/` | Post-session feedback survey | **Antigravity** |
| `generic_press_enter` | `/Press enter to confirm/` | Generic "press enter" prompt | any CLI |

Real pane snapshots used to verify these patterns (positive + negative) live under
`test/face-app/fixtures/stuck_detector/{codex,agy}/`. When you discover a new modal,
add the verbatim capture there and add a row to `FIXTURE_CASES` in
`test/face-app/helper_stuck_detector.test.mjs` before extending `DEFAULT_STUCK_PATTERNS`.

Things the detector does **not** catch yet:

- New / changed prompt wording from any CLI vendor (the pattern set is a snapshot in time).
Expand Down
22 changes: 21 additions & 1 deletion face-app/dist/helper_stuck_detector.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,30 @@ export const DEFAULT_STUCK_PATTERNS = [
regex: /Would you like to run the following command\?/,
summary: () => 'helper paused on approval prompt'
},
{
id: 'codex_mcp_approval',
category: 'approval',
// Codex's MCP tool-call approval modal is a separate path from the
// shell-command modal and reaches the pane as
// 'Allow the <server> MCP server to run tool "<name>"?'.
regex: /Allow the .+ MCP server to run tool/,
summary: () => 'helper paused on MCP tool approval prompt'
},
{
id: 'agy_trust_folder',
category: 'approval',
// Antigravity's first-run trust prompt blocks before any mission is
// injected. It cannot be matched by the generic approval phrases.
regex: /Do you trust the contents of this project\?/,
summary: () => 'helper paused on workspace trust prompt'
},
{
id: 'codex_picker',
category: 'picker',
regex: /Switch to (gpt|claude|gemini)-/,
// The /model picker header line. The previous "Switch to <model>"
// wording only appeared on a transient confirm screen and missed the
// primary picker that operators actually see.
regex: /Select Model and Effort/,
summary: () => 'helper paused on model picker'
},
{
Expand Down
2 changes: 1 addition & 1 deletion mcp-server/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
import { createFramedMessageParser, writeMessage } from './mcp_stdio.js';

const SERVER_NAME = 'minimum-headroom';
const SERVER_VERSION = '1.17.1';
const SERVER_VERSION = '1.17.2';
const PROTOCOL_VERSION = '2024-11-05';
const FACE_WS_URL = process.env.FACE_WS_URL ?? 'ws://127.0.0.1:8765/ws';
const FACE_AUTH_TOKEN = (() => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "minimum-headroom",
"version": "1.17.1",
"version": "1.17.2",
"private": true,
"type": "module",
"scripts": {
Expand Down
18 changes: 18 additions & 0 deletions test/face-app/fixtures/stuck_detector/agy/approval_mcp_tool.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
● Read(/home/amari1/.gemini/antigravity-cli/mcp/minimum_headroom/agent_report.json)
● minimum_headroom/agent_report(Agent Report Progress) (ctrl+o to expand)

MCP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────

agent_report (server: minimum_headroom)

Do you want to proceed?
> 1. Yes
2. Yes, and always allow 'minimum_headroom/agent_report' in this conversation
3. Yes, and always allow 'minimum_headroom/agent_report' (Persist to settings.json)
4. No
5. No, and always deny 'minimum_headroom/agent_report' in this conversation
6. No, and always deny 'minimum_headroom/agent_report' (Persist to settings.json)

↑/↓ Navigate · tab Amend · e edit command
esc to cancel Gemini 3.5 Flash (Medium)
11 changes: 11 additions & 0 deletions test/face-app/fixtures/stuck_detector/agy/idle_after_response.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
● minimum_headroom/agent_report(Agent Report Done)
● minimum_headroom/face_event(Face Event CMD Succeeded)
● minimum_headroom/face_say(Face Say Completion)
● minimum_headroom/face_event(Face Event Idle) (ctrl+o to expand)

hello

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
>
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
? for shortcuts Gemini 3.5 Flash (Medium)
10 changes: 10 additions & 0 deletions test/face-app/fixtures/stuck_detector/agy/idle_empty_prompt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
▄▀▀▄ Antigravity CLI 1.0.1
▀▀▀▀▀▀ akfsmti@gmail.com (Google AI Pro)
▀▀▀▀▀▀▀▀ Gemini 3.5 Flash (Medium)
▄▀▀ ▀▀▄ ~/github/minimum-headroom/.agent/worktrees/helper-1
▄▀▀ ▀▀▄

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
>
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
? for shortcuts Gemini 3.5 Flash (Medium)
12 changes: 12 additions & 0 deletions test/face-app/fixtures/stuck_detector/agy/running_loading.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
● Read(/home/amari1/.gemini/antigravity-cli/mcp/minimum_headroom/agent_report.json)
● minimum_headroom/agent_report(Agent Report Progress)
● Read(/home/amari1/.gemini/antigravity-cli/mcp/minimum_headroom/face_ping.json)
● Read(/home/amari1/.gemini/antigravity-cli/mcp/minimum_headroom/face_event.json)
● Read(/home/amari1/.gemini/antigravity-cli/mcp/minimum_headroom/face_say.json)
● minimum_headroom/face_ping(Face Ping)
● minimum_headroom/face_event(Face Event CMD Started) (ctrl+o to expand)
⣷ Loading...
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
>
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
esc to cancel Gemini 3.5 Flash (Medium)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
> /quit
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
> /exit (quit) Exit the CLI

↑/↓ Navigate · enter Select · tab Complete
esc to cancel Gemini 3.5 Flash (Medium)
13 changes: 13 additions & 0 deletions test/face-app/fixtures/stuck_detector/agy/trust_folder_prompt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Accessing workspace:

/home/amari1/github/minimum-headroom/.agent/worktrees/helper-1

Do you trust the contents of this project?

Antigravity CLI requires permission to read, edit, and execute files here.

> Yes, I trust this folder
No, exit

↑/↓ Navigate · enter Confirm
Gemini 3.5 Flash (Medium)
18 changes: 18 additions & 0 deletions test/face-app/fixtures/stuck_detector/codex/approval_mcp_tool.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
• Calling
└ minimum_headroom.agent_report({"stream_id":"repo:/home/amari1/github/minimum-headroom","mission_id":"codex-
fixture-1","owner_agent_id":"__operator__","from_agent_id":"helper-1","kind":"progress","summary":"Mission
accepted"})


Field 1/1
Allow the minimum_headroom MCP server to run tool "agent_report"?

from_agent_id: helper-1
kind: progress
mission_id: codex-fixture-1

› 1. Allow Run the tool and continue.
2. Allow for this session Run the tool and remember this choice for this session.
3. Always allow Run the tool and remember this choice for future tool calls.
4. Cancel Cancel this tool call
enter to submit | esc to cancel
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
• Running pwd


Would you like to run the following command?

Reason: command failed; retry without sandbox?

$ pwd

› 1. Yes, proceed (y)
2. Yes, and don't ask again for commands that start with `pwd` (p)
3. No, and tell Codex what to do differently (esc)

Press enter to confirm or esc to cancel
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
✗ You canceled the request to run pwd

• Ran pwd
└ (no output)

■ Conversation interrupted - tell the model what to do differently. Something went wrong? Hit `/feedback` to report
the issue.


› Explain this codebase

gpt-5.5 high · ~/github/minimum-headroom/.agent/worktrees/helper-1
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────

• hello

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────


› Explain this codebase

gpt-5.5 high · ~/github/minimum-headroom/.agent/worktrees/helper-1
27 changes: 27 additions & 0 deletions test/face-app/fixtures/stuck_detector/codex/idle_empty_prompt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export MH_FACE_AGENT_ID='helper-1' MH_FACE_AGENT_LABEL='helper-1' MH_HOOK_SUPPRESS_EVENTS='idle_after_response'
env MH_FACE_AGENT_ID='helper-1' MH_FACE_AGENT_LABEL='helper-1' MH_HOOK_SUPPRESS_EVENTS='idle_after_response' codex
-a untrusted
amari1@amari1-Z890i-Nova:~/github/minimum-headroom/.agent/worktrees/helper-1$ export MH_FACE_AGENT_ID='helper-1' MH
_FACE_AGENT_LABEL='helper-1' MH_HOOK_SUPPRESS_EVENTS='idle_after_response'
amari1@amari1-Z890i-Nova:~/github/minimum-headroom/.agent/worktrees/helper-1$ env MH_FACE_AGENT_ID='helper-1' MH_FA
CE_AGENT_LABEL='helper-1' MH_HOOK_SUPPRESS_EVENTS='idle_after_response' codex -a untrusted
⚠ Codex's Linux sandbox uses bubblewrap and needs access to create user namespaces.

╭─────────────────────────────────────────────────╮
│ >_ OpenAI Codex (v0.133.0) │
│ │
│ model: gpt-5.5 high /model to change │
│ directory: ~/github/…/.agent/worktrees/helper-1 │
╰─────────────────────────────────────────────────╯

Tip: Switch models or reasoning effort quickly with /model.


› Explain this codebase

gpt-5.5 high · ~/github/minimum-headroom/.agent/worktrees/helper-1





10 changes: 10 additions & 0 deletions test/face-app/fixtures/stuck_detector/codex/picker_model.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Select Model and Effort
Access legacy models by running codex -m <model_name> or in your config.toml

› 1. gpt-5.5 (current) Frontier model for complex coding, research, and real-world work.
2. gpt-5.4 Strong model for everyday coding.
3. gpt-5.4-mini Small, fast, and cost-efficient model for simpler coding tasks.
4. gpt-5.3-codex Coding-optimized model.
5. gpt-5.2 Optimized for professional work and long-running agents.

Press enter to confirm or esc to go back
10 changes: 10 additions & 0 deletions test/face-app/fixtures/stuck_detector/codex/running_thinking.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Mission body:
Please reply with exactly the single word: hello


• Working (9s • esc to interrupt)


› Explain this codebase

gpt-5.5 high · ~/github/minimum-headroom/.agent/worktrees/helper-1
67 changes: 67 additions & 0 deletions test/face-app/helper_stuck_detector.test.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import test from 'node:test';
import { createHelperStuckDetector, DEFAULT_STUCK_PATTERNS } from '../../face-app/dist/helper_stuck_detector.js';

const __dirname = dirname(fileURLToPath(import.meta.url));
const FIXTURE_ROOT = join(__dirname, 'fixtures', 'stuck_detector');

function loadFixture(cli, name) {
const path = join(FIXTURE_ROOT, cli, `${name}.txt`);
return readFileSync(path, 'utf8').replace(/\n$/, '').split('\n');
}

const quietLog = {
info() {},
warn() {},
Expand Down Expand Up @@ -197,8 +208,10 @@ test('DEFAULT_STUCK_PATTERNS exports the documented pattern ids', () => {
const ids = DEFAULT_STUCK_PATTERNS.map((p) => p.id).sort();
assert.deepEqual(ids, [
'agy_survey',
'agy_trust_folder',
'claude_approval',
'codex_approval',
'codex_mcp_approval',
'codex_picker',
'codex_quota',
'generic_press_enter'
Expand Down Expand Up @@ -236,6 +249,60 @@ test('codex_approval pattern matches the Codex shell-command approval modal', as
assert.ok(report.detail.includes('Would you like to run the following command?'));
});

// Fixture-driven coverage. Each fixture under test/face-app/fixtures/stuck_detector/<cli>/
// is a verbatim ANSI-stripped tmux pane snapshot collected from a real helper. Positive
// fixtures must surface exactly one matching pattern; negative fixtures must surface none.

const FIXTURE_CASES = [
// codex positives
{ cli: 'codex', name: 'approval_shell_command', expectPatternId: 'codex_approval' },
{ cli: 'codex', name: 'approval_mcp_tool', expectPatternId: 'codex_mcp_approval' },
{ cli: 'codex', name: 'picker_model', expectPatternId: 'codex_picker' },
// codex negatives — these are real running / idle states that must not fire
{ cli: 'codex', name: 'idle_empty_prompt', expectPatternId: null },
{ cli: 'codex', name: 'idle_after_response', expectPatternId: null },
{ cli: 'codex', name: 'idle_after_interrupted', expectPatternId: null },
{ cli: 'codex', name: 'running_thinking', expectPatternId: null },
// agy positives
{ cli: 'agy', name: 'trust_folder_prompt', expectPatternId: 'agy_trust_folder' },
{ cli: 'agy', name: 'approval_mcp_tool', expectPatternId: 'claude_approval' },
// agy negatives
{ cli: 'agy', name: 'idle_empty_prompt', expectPatternId: null },
{ cli: 'agy', name: 'idle_after_response', expectPatternId: null },
{ cli: 'agy', name: 'running_loading', expectPatternId: null },
{ cli: 'agy', name: 'slash_command_picker', expectPatternId: null }
];

for (const { cli, name, expectPatternId } of FIXTURE_CASES) {
const label = expectPatternId
? `fixture ${cli}/${name} triggers ${expectPatternId}`
: `fixture ${cli}/${name} does not trigger any pattern`;
test(label, async () => {
const agentId = `${cli}-fixture`;
const agents = [{ id: agentId, pane_id: '%99', stream_id: 'repo:/test', status: 'active' }];
const runtime = createFakeRuntime(agents, { [agentId]: loadFixture(cli, name) });
const inbox = createFakeInbox();
const detector = createHelperStuckDetector({ runtime, inboxStore: inbox, log: quietLog });

const result = await detector.tick();

if (expectPatternId === null) {
assert.equal(result.posted, 0, `expected no report, got ${inbox.reports.length}`);
assert.equal(inbox.reports.length, 0);
} else {
assert.equal(inbox.reports.length, 1, `expected exactly one report from ${expectPatternId}`);
assert.equal(result.posted, 1);
// Confirm the matched line actually belongs to the expected pattern by re-running
// its regex over the report detail (first line is the matched line).
const pattern = DEFAULT_STUCK_PATTERNS.find((p) => p.id === expectPatternId);
assert.ok(pattern, `pattern ${expectPatternId} not registered`);
const firstLine = inbox.reports[0].detail.split('\n', 1)[0];
assert.ok(pattern.regex.test(firstLine),
`expected ${expectPatternId} regex to match first detail line: ${firstLine}`);
}
});
}

test('claude_approval pattern also matches the Antigravity permission modal', async () => {
// Antigravity uses the same "Do you want to proceed?" phrase as Claude, so
// the existing claude_approval pattern covers it without a separate rule.
Expand Down
2 changes: 1 addition & 1 deletion tts-worker/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "minimum-headroom-tts-worker"
version = "1.17.1"
version = "1.17.2"
description = "Minimum Headroom TTS worker (Kokoro ONNX default, optional Qwen3-TTS)"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
2 changes: 1 addition & 1 deletion tts-worker/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading