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
10 changes: 10 additions & 0 deletions web/src/fixtures/panes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1858,6 +1858,11 @@ signature.
| `muse--approval-ls-147.txt` | Muse Code 1.4.7 `ls` approval, `⟩` on option 1 (#424) | `blocked` |
| `muse--approval-date-147.txt` | Muse Code 1.4.7 `date` approval, `⟩` on option 2 (#424) | `blocked` |
| `muse--trust-prompt-147.txt` | Muse Code 1.4.7 workspace trust prompt (#424) | `blocked` |
| `muse--palette-hint.txt` | Muse Code 1.4.8, `/model` typed: the box reads `❯ /model [<model-id>]`, the hint in the bottom rule's colour, over one `/model` suggestion. The read is `/model` | `idle` |
| `muse--palette-hint-skills.txt` | `/resume` typed: its hint, and three suggestions, the bold `/resume` first and two built-in `resume-*` skills under it. The read is `/resume`, since Enter runs the bold row | `idle` |
| `muse--palette-hint-moved.txt` | Same, after Down: the bold has moved to `/resume-claude`, so the read must not verify `/resume` | `idle` |
| `muse--palette-typed-arg.txt` | `/model x` typed: the hint is gone and `x` wears the typed-text colour. The read is `/model x` | `idle` |
| `muse--palette-skill.txt` | `/plan` typed: its one suggestion is a built-in skill row, ` /plan · built-in · default skill On an explicit…`, tags between the command and the gap. The read is `/plan`. Visible-screen read 2026-10-15, cwd swapped for the sandbox path and model label renamed `mini` | `idle` |

**Nothing was approved blindly.** The one approved command was `ls -la` on the
empty sandbox (output verified); the trust prompt covered a throwaway `/tmp`
Expand All @@ -1866,6 +1871,11 @@ left open at the end were dismissed with `Escape`. A second session
(2026-09-26, tasks popup, same throwaway sandbox) approved `sleep 120/100/110/150`
(backgrounded, no output) and `ls -la /tmp` (listing verified).

The four `muse--palette-*` rows of 2026-10-15 are visible-screen captures of Muse Code 1.4.8 in a
throwaway Herdr session (`herdr pane read --source visible --format ansi`). The statusline's
shortened cwd was swapped for `/private/tmp/collie-muse-sandbox`, the same 32 characters, and its
model label renamed `mini`. Nothing was submitted while capturing.


## opencode corpus (captured 2026-09-26, opencode 1.18.32, herdr 0.9.0, private Herdr session)

Expand Down
10 changes: 10 additions & 0 deletions web/src/fixtures/panes/muse--palette-hint-moved.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

 Muse Code 1.4.8

── Voice input (⌥ + v to start) ───────────────────────────────────────────────────────────────────────────────────────
❯ /resume [--last|<id-or-name>]
 /resume Resume an earlier Muse Code session
 /resume-claude · built-in · default skill Continue a Claude Code session here
 /resume-codex · built-in · default skill Continue a Codex session here
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 mini · high · /private/tmp/collie-muse-sandbox · Auto-review
10 changes: 10 additions & 0 deletions web/src/fixtures/panes/muse--palette-hint-skills.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

 Muse Code 1.4.8

── Voice input (⌥ + v to start) ───────────────────────────────────────────────────────────────────────────────────────
❯ /resume [--last|<id-or-name>]
 /resume Resume an earlier Muse Code session
 /resume-claude · built-in · default skill Continue a Claude Code session here
 /resume-codex · built-in · default skill Continue a Codex session here
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 mini · high · /private/tmp/collie-muse-sandbox · Auto-review
8 changes: 8 additions & 0 deletions web/src/fixtures/panes/muse--palette-hint.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

 Muse Code 1.4.8

── Voice input (⌥ + v to start) ───────────────────────────────────────────────────────────────────────────────────────
❯ /model [<model-id>]
 /model Choose model
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 mini · high · /private/tmp/collie-muse-sandbox · Auto-review
8 changes: 8 additions & 0 deletions web/src/fixtures/panes/muse--palette-skill.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

 Muse Code 1.4.8

── Voice input (⌥ + v to start) ───────────────────────────────────────────────────────────────────────────────────────
❯ /plan
 /plan · built-in · default skill On an explicit planning request, always call `read_skill` for this skill before an…
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 mini · high · /private/tmp/collie-muse-sandbox · Auto-review
7 changes: 7 additions & 0 deletions web/src/fixtures/panes/muse--palette-typed-arg.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

 Muse Code 1.4.8

── Voice input (⌥ + v to start) ───────────────────────────────────────────────────────────────────────────────────────
❯ /model x
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 mini · high · /private/tmp/collie-muse-sandbox · Auto-review
5 changes: 5 additions & 0 deletions web/src/lib/harness/muse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ const PINNED = [
"muse--draft-wrapped.txt",
"muse--fresh-idle.txt",
"muse--palette-exact.txt",
"muse--palette-hint-moved.txt",
"muse--palette-hint-skills.txt",
"muse--palette-hint.txt",
"muse--palette-partial.txt",
"muse--palette-skill.txt",
"muse--palette-typed-arg.txt",
"muse--quoted-dialogs-bare.txt",
"muse--tasks-popup-approval.txt",
"muse--tasks-popup-draft.txt",
Expand Down
24 changes: 24 additions & 0 deletions web/src/lib/harness/muse/chrome.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,30 @@ describe("extractInputDraft — the verify half of type-then-verify", () => {
);
});

it("drops Muse 1.4.8's argument hint, painted in the rule's colour, after an exact command", () => {
// `❯ /model [<model-id>]` over one `/model` suggestion: the hint is not typed text.
expect(extractInputDraft(lines("muse--palette-hint.txt"))).toBe("/model");
});

it("reads the command alone when the highlighted first suggestion of several names it", () => {
// `/resume` lists two built-in skills under it; the bold first row is the one Enter runs.
expect(extractInputDraft(lines("muse--palette-hint-skills.txt"))).toBe("/resume");
});

it("stalls safe once the highlight has moved off the typed command", () => {
// After Down, Enter would run `/resume-claude`, so the typed text must not verify.
expect(extractInputDraft(lines("muse--palette-hint-moved.txt"))).not.toBe("/resume");
});

it("reads a command whose one suggestion is a built-in skill row, tags and all", () => {
// ` /plan · built-in · default skill On an explicit planning request…` (Muse 1.4.8).
expect(extractInputDraft(lines("muse--palette-skill.txt"))).toBe("/plan");
});

it("keeps a typed argument, which arrives in the typed-text colour once the hint is gone", () => {
expect(extractInputDraft(lines("muse--palette-typed-arg.txt"))).toBe("/model x");
});

it("reads attach tokens verbatim (the attach grammar verifies them, not this) (#278)", () => {
expect(extractInputDraft(lines("muse--draft-image-chip.txt"))).toBe("[Image #1]");
expect(extractInputDraft(lines("muse--draft-quoted-path.txt"))).toBe(
Expand Down
38 changes: 32 additions & 6 deletions web/src/lib/harness/muse/chrome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,20 @@ export function extractInputDraft(lines: StyledLine[]): string | null {
// keeps the legacy read and stalls safe: Enter on partial input accepts the highlighted
// suggestion (probed: `/us` ran `/usage`), so verifying the typed text there would bless a
// command the operator did not type.
//
// Muse 1.4.8 adds two things (live, 2026-10-15): an argument hint after a complete command
// (`❯ /model [<model-id>]`), and skills listed under a command they prefix (`/resume` over
// `/resume-claude` and `/resume-codex`). Several rows qualify only when the first one is the
// highlighted one, bold, since Enter runs the highlighted row (probed: Down moved the bold to
// `/resume-claude`).
const trimmed = prompt.trim();
if (trimmed.startsWith("/")) {
const run: string[] = [];
for (let i = tail.prompt + 1; i < tail.rule; i++) run.push(texts[i]!);
if (run.length === 1) {
const suggestion = parsePaletteRow(run[0]!);
if (suggestion !== null && suggestion.command === trimmed) return trimmed;
}
const typed = withoutArgumentHint(lines[tail.prompt]!, lines[tail.rule]!, trimmed);
const run: number[] = [];
for (let i = tail.prompt + 1; i < tail.rule; i++) run.push(i);
const head = run.length === 0 ? null : parsePaletteRow(texts[run[0]!]!);
const several = run.length > 1 && run.every((i) => texts[i]!.startsWith(" /")) && rowIsHighlighted(lines[run[0]!]!, typed);
if (head !== null && head.command === typed && (run.length === 1 || several)) return typed;
}
const parts = [prompt.trim()];
for (let i = tail.prompt + 1; i < tail.rule; i++) {
Expand All @@ -166,6 +172,26 @@ export function extractInputDraft(lines: StyledLine[]): string | null {
return draft;
}

/**
* The typed slash command with Muse's argument hint taken off. The hint (` [<model-id>]`) follows a
* complete command in Muse's muted chrome colour, the colour of the box's own bottom rule, and goes
* the moment an argument is typed, which arrives in the typed-text colour. So a trailing ` […]` run
* is the hint only when it wears the rule's colour; a literally typed `[x]` is kept.
*/
function withoutArgumentHint(row: StyledLine, rule: StyledLine, typed: string): string {
const last = row.segments.findLast((seg) => seg.text.trim() !== "");
const hint = last?.text.trim() ?? "";
if (last === undefined || !/^\[[^\]]*\]$/.test(hint) || !last.text.startsWith(" ")) return typed;
const ruleFg = rule.segments.find((seg) => seg.text.trim() !== "")?.fg;
if (ruleFg === undefined || last.fg !== ruleFg || !typed.endsWith(hint)) return typed;
return typed.slice(0, -hint.length).trimEnd();
}

/** Whether a palette row's command is painted bold, Muse's mark for the highlighted suggestion. */
function rowIsHighlighted(row: StyledLine, command: string): boolean {
return row.segments.some((seg) => seg.bold === true && seg.text.trim() === command);
}

/** Whether Muse's free-text composer is on screen at the tail (the weaker, purely geometric claim). */
export function hasComposer(lines: StyledLine[]): boolean {
const tail = locateTail(lines);
Expand Down
5 changes: 4 additions & 1 deletion web/src/lib/harness/muse/markers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,10 @@ export function parseSubmitRow(text: string): { n: number; checked: number } | n
// trimmed: the indent is structural. Only ever read under a slash-led prompt row (chrome.ts): the
// palette opens for slash input alone, so that gate keeps a prose continuation starting with `/`
// (a path) from parsing as a suggestion.
const PALETTE_ROW = /^ (\/\S+)\s{2,}(\S.*)$/;
//
// Muse 1.4.8 lists skills in the same palette with their tags between the command and the gap
// (` /plan · built-in · default skill On an explicit…`), so ` · tag` runs may follow the command.
const PALETTE_ROW = /^ (\/\S+)(?: · [^·]+?)*\s{2,}(\S.*)$/;

/** Parse a palette suggestion row into its command, or null. */
export function parsePaletteRow(text: string): { command: string } | null {
Expand Down
Loading