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
14 changes: 5 additions & 9 deletions .github/workflows/agent-app-cross-platform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,9 @@ jobs:
- name: Linux build tools
if: matrix.os == 'linux'
working-directory: .
# The runner's apt mirror list (Azure, then *.ubuntu.com) can stop resolving while other hosts work: try the
# kernel.org Ubuntu mirror first, keep Ubuntu's own archive as the fallback, and retry.
run: |
printf 'http://mirrors.edge.kernel.org/ubuntu/\tpriority:1\nhttp://archive.ubuntu.com/ubuntu/\tpriority:2\n' | sudo tee /etc/apt/apt-mirrors.txt >/dev/null
sudo apt-get -o Acquire::Retries=5 update && sudo apt-get -o Acquire::Retries=5 install -y --no-install-recommends build-essential python3 xvfb libgtk-3-0 libnss3 libxss1 libgbm1 libasound2t64 rpm fakeroot desktop-file-utils xdg-utils
# The runner's apt mirror can stop resolving, miss a package or stall: the script tries the kernel.org, Azure and
# Ubuntu mirrors in turn, twice, with a time limit per attempt (#876).
run: bash packages/agent-app/scripts/ci-apt-install.sh build-essential python3 xvfb libgtk-3-0 libnss3 libxss1 libgbm1 libasound2t64 rpm fakeroot desktop-file-utils xdg-utils

- id: electron
# Download caches only (never node_modules): keyed on OS, CPU and the locked Electron version.
Expand Down Expand Up @@ -227,10 +225,8 @@ jobs:
path: packages/agent-app/release-dist
- name: Install the deb with apt
run: |
# *.ubuntu.com can stop resolving on the runners while other hosts work: use the kernel.org Ubuntu mirror.
sed -i -E 's#http://(archive|security)\.ubuntu\.com/ubuntu/?#http://mirrors.edge.kernel.org/ubuntu/#' /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list 2>/dev/null || true
apt-get -o Acquire::Retries=5 update
DEBIAN_FRONTEND=noninteractive apt-get -o Acquire::Retries=5 install -y --no-install-recommends xvfb xauth desktop-file-utils ./release-dist/Muster-Agent-*-linux-amd64.deb
# A mirror can stop resolving, miss a package or stall inside the container too: the script moves to another (#876).
bash scripts/ci-apt-install.sh xvfb xauth desktop-file-utils ./release-dist/Muster-Agent-*-linux-amd64.deb
! dpkg -s libfuse2t64 >/dev/null 2>&1 && ! dpkg -s libfuse2 >/dev/null 2>&1 && echo "libfuse2 is not installed"
bash scripts/test-linux-install.sh
- name: Launch the installed app (as a regular user)
Expand Down
28 changes: 26 additions & 2 deletions docs/spec/clauses/03-providers-models-and-accounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ Boundaries:
| [MUS-SPEC-03-688N](03-providers-models-and-accounts.md#mus-spec-03-688n) | The saved conversation starts with a user message that holds the person's own text, stays within its limits and holds no image data. | [`saved Claude history never starts with an assistant message`](../../../packages/agent-app/tests/claude-messages-driver.test.ts) |
| [MUS-SPEC-03-688O](03-providers-models-and-accounts.md#mus-spec-03-688o) | An error event before any content is retried once and the turn completes; an error after text has streamed fails the turn without resending; every request asks for high effort. | [`an error event before any content is sent once more; after content it fails the turn`](../../../packages/agent-app/tests/claude-messages-driver.test.ts) |
| [MUS-SPEC-03-688P](03-providers-models-and-accounts.md#mus-spec-03-688p) | The turn runs over the Messages API with a native ask_user tool offered; the call returns the default at once, a non-blocking question card is posted, and the turn completes on the Messages lane. | [`a Full access Claude turn still runs over the Messages API when the service adds the ask_user server, and ask_user returns the default`](../../../packages/agent-app/tests/claude-messages-lane.test.ts) |
| [MUS-SPEC-03-866A](03-providers-models-and-accounts.md#mus-spec-03-866a) | With the device tool server added, the turn still runs over the Messages API (the Codex client never runs), completes on the Messages lane, and the request carries no device tool and no device note. | [`a Full access Claude turn with device tools on still runs over the Messages API, without the device server or its note`](../../../packages/agent-app/tests/claude-messages-lane.test.ts) |
| [MUS-SPEC-03-866B](03-providers-models-and-accounts.md#mus-spec-03-866b) | With the device tool server added, the turn through Claude Code loads `muster_device` in its MCP config next to the user's server. | [`with device tools on, a router Claude turn through Claude Code keeps the device server`](../../../packages/agent-app/tests/router-claude-code-730.test.ts) |
| [MUS-SPEC-03-716A](03-providers-models-and-accounts.md#mus-spec-03-716a) | When the copy of the thread is refused too, one fresh thread starts with a summary of the earlier messages; the chat ends completed on it, with one notice for the copy and one for the summary. | [`when the copy is refused too, one fresh conversation starts with a summary`](../../../packages/agent-app/tests/replay-expired-716.test.ts) |
| [MUS-SPEC-03-716B](03-providers-models-and-accounts.md#mus-spec-03-716b) | If the copy and the fresh conversation are both refused there is no fourth attempt and the error is shown through the usual recovery notice. | [`when the copy and the fresh conversation are refused with the same 409 there is no fourth attempt and the error is shown`](../../../packages/agent-app/tests/replay-expired-716.test.ts) |
| [MUS-SPEC-03-767A](03-providers-models-and-accounts.md#mus-spec-03-767a) | Through the real coding client, a router that refuses the thread's session gets the turn from a new session whose request carries every earlier user message and reply as history, with no digest; one notice says the chat continues in a copy; later turns keep working. | [`a router that refuses a thread's session continues the turn with the whole earlier conversation`](../../../packages/agent-app/tests/router-refused-session-767.test.ts) |
Expand Down Expand Up @@ -457,6 +459,28 @@ Boundaries:
- Known gaps: The `muster_ask` MCP server itself is dropped on this lane (it has no MCP client); the native tool takes its place.
- Evidence: UNVERIFIED

<a id="mus-spec-03-866a"></a>
#### MUS-SPEC-03-866A: Device tools do not move a Claude turn off the Messages API

- Precondition: A Full access Agent chat on a router gateway route, model `cc/claude-opus-5-5`, Claude Code not installed. "Let agents use devices" is on, so the service adds the `muster_device` server and the device note to the turn, next to its usual per-chat servers.
- Action: Send a message.
- Expected result: The turn runs over the Messages API and the Codex client never starts. The request offers no device tool and its system text has no device note. The turn completes on the Messages lane.
- Test: `packages/agent-app/tests/claude-messages-lane.test.ts::a Full access Claude turn with device tools on still runs over the Messages API, without the device server or its note`
- Environment: Node test runner; the real service, computer domain and provider adapter with a fake router over fetch and a fake Codex client; no network. Every tool launcher the service reads from the environment is set or cleared by the test, so a launcher in the caller's environment (as inside Muster) does not change the result.
- Known gaps: The Messages lane has no device tools, so the agent cannot drive a device on this route; the device server itself is not started.
- Evidence: UNVERIFIED

<a id="mus-spec-03-866b"></a>
#### MUS-SPEC-03-866B: Claude Code on a router keeps the device tools

- Precondition: A Workspace Agent chat on a router gateway route, model `cc/claude-opus-5-5`, with Claude Code installed (a fake `claude` executable). "Let agents use devices" is on and the chat has a user tool server.
- Action: Send a message.
- Expected result: The turn runs through Claude Code, and the MCP config Claude Code starts with lists `muster_device` as well as the user's server.
- Test: `packages/agent-app/tests/router-claude-code-730.test.ts::with device tools on, a router Claude turn through Claude Code keeps the device server`
- Environment: Node test runner; the real service, provider adapter and Claude Code adapter with a fake `claude` executable, a fake router and a fake Codex client; no network.
- Known gaps: A guard for the route that keeps the device server: it also passes before the #866 change, which only touches the Messages lane.
- Evidence: UNVERIFIED

<a id="mus-spec-03-767a"></a>
#### MUS-SPEC-03-767A: A refused router thread continues with the whole conversation

Expand Down Expand Up @@ -826,7 +850,7 @@ Any agent that speaks the Agent Client Protocol (JSON-RPC over stdio) can be add
- Action: In Auto, send an edit of a file inside the folder, then "RUN npm install left-pad" and decline its card. In Full access, send "RUN npm install left-pad".
- Expected result: In Auto the edit is approved without a card and its row records the Auto rule (inside the chat's folder); the install still shows a command card with its command. In Full access the command runs with no card.
- Test: `packages/agent-app/tests/acp-route-160.test.ts::ACP in Auto: an edit inside the folder settles itself, an install still asks; Full access never asks`
- Environment: As 160B.
- Environment: As 160B. The fake agent ends each prompt 150 ms after its last words, and each follow-up is sent only once the previous turn has settled (#877).
- Known gaps: Auto's rules themselves are clause 05 (#841); this checks that ACP requests reach them.
- Evidence: UNVERIFIED

Expand All @@ -837,7 +861,7 @@ Any agent that speaks the Agent Client Protocol (JSON-RPC over stdio) can be add
- Action: Ask the agent to write a file outside the folder and inside it, and to read a line range; in Read only, to write and to edit; in Full access, to write outside the folder.
- Expected result: Workspace refuses the outside write (the agent gets an error naming the working folder and no file appears), writes the inside file, and returns line 2 of a file for `line: 2, limit: 1`. Read only refuses the write (error says read-only), rejects the edit's permission request without a card, leaves the file unchanged and switches the agent to its plan mode. Full access writes outside the folder.
- Test: `packages/agent-app/tests/acp-route-160.test.ts::ACP file access follows the access level: Workspace writes stay in the folder, Read only writes nothing and refuses edits`
- Environment: As 160B.
- Environment: As 160B. The fake agent ends each prompt 150 ms after its last words, and each follow-up is sent only once the previous turn has settled (#877).
- Known gaps: Links inside the folder are refused by the same check as the HTTP tools (MUS-SPEC-03-384E) and are not exercised again here.
- Evidence: UNVERIFIED

Expand Down
14 changes: 14 additions & 0 deletions docs/spec/clauses/20-performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Boundaries:
| [MUS-SPEC-20-553B](20-performance.md#mus-spec-20-553b) | After 20 streamed reply tokens, links in the transcript have re-rendered at most 2 times. | [`streaming 20 tokens does not re-render resource links`](../../../packages/agent-app/tests/chat-render-bounds-dom.tsx) |
| [MUS-SPEC-20-553C](20-performance.md#mus-spec-20-553c) | After 20 streamed reply tokens, the Changes view has re-rendered at most 2 times. | [`streaming 20 tokens does not re-render the changes view`](../../../packages/agent-app/tests/chat-render-bounds-dom.tsx) |
| [MUS-SPEC-20-813A](20-performance.md#mus-spec-20-813a) | A panel that opens while the window is inactive ends its fade-in at full opacity, and no animation keeps ticking while inactive; a focused window still fades in and spins. | [`a panel that opens while the window is inactive is visible, and nothing keeps animating`](../../../packages/agent-app/tests/idle-animations-813.test.ts) |
| [MUS-SPEC-20-770A](20-performance.md#mus-spec-20-770a) | The first pass over 2,000 rows parses each timestamp once; 100 more passes over the same rows parse none, and the dividers stay the same. | [`cheap enough to run on every streamed update of a long chat`](../../../packages/agent-app/tests/day-dividers.test.ts) |

### Startup size

Expand Down Expand Up @@ -78,3 +79,16 @@ Boundaries:
- Environment: Headless Chrome over CDP with the real styles.css, app-shell.css and message-body.css; skipped when Chrome is not installed.
- Known gaps: windowActivity.ts, which sets the attribute, is not driven here; the test sets it directly.
- Evidence: UNVERIFIED

### Day dividers on a long chat (#770)

<a id="mus-spec-20-770a"></a>
#### MUS-SPEC-20-770A: Day dividers do not re-parse timestamps on each streamed update

- Precondition: A chat with 2,000 rows, alternating user and assistant messages one minute apart.
- Action: Work out the day dividers once, then 100 more times over the same rows, as the chat does on every streamed update.
- Expected result: The first pass parses each row's timestamp exactly once. The 100 later passes parse no timestamp at all and return the same dividers. As a backstop for a gross slowdown, the later passes average under 5 ms each.
- Test: `packages/agent-app/tests/day-dividers.test.ts::cheap enough to run on every streamed update of a long chat`
- Environment: Node test runner; the real `dividerRows` with `Date.parse` counted for the duration of the test. The main check counts work, so a busy CI runner does not change it; the time ceiling is ten times the cost seen on a loaded runner.
- Known gaps: Only timestamp parsing is counted. Other per-row work (the day boundary of each user message) is bounded only by the time ceiling.
- Evidence: UNVERIFIED
26 changes: 26 additions & 0 deletions docs/spec/clauses/22-engineering-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Boundaries:
| [MUS-SPEC-22-482A](22-engineering-standards.md#mus-spec-22-482a) | With no dist folder, the Hindsight service test file loads and the tests that use a fake core pass; only the tests that drive the real bundle are skipped. | [`unconfigured status is truthful and never includes a key`](../../../packages/agent-app/tests/hindsight-service.test.ts) |
| [MUS-SPEC-22-5A](22-engineering-standards.md#mus-spec-22-5a) | Every `tests/*.tsx` file in the repository is either a suite run by `npm run test:renderer` or excluded with a stated reason, and every listed name has a file. | [`every tests/*.tsx file is a renderer suite or is excluded with a reason`](../../../packages/agent-app/tests/renderer-suites.test.ts) |
| [MUS-SPEC-22-5B](22-engineering-standards.md#mus-spec-22-5b) | The guard names a stray DOM suite, a stale list entry, a name in both lists and a duplicate. | [`the guard names a stray suite, a stale entry, a name in both lists and a duplicate`](../../../packages/agent-app/tests/renderer-suites.test.ts) |
| [MUS-SPEC-22-876A](22-engineering-standards.md#mus-spec-22-876a) | With the first mirror refusing the package list and the second missing a package, the script installs from the third mirror, rewriting the sources each time; when every mirror fails it exits non-zero after trying each one. | [`the apt install moves to the next mirror when one cannot find a package, and fails only after every mirror`](../../../packages/agent-app/tests/ci-apt-install.test.ts) |
| [MUS-SPEC-22-876B](22-engineering-standards.md#mus-spec-22-876b) | The Linux build job and the Ubuntu 24.04 container job both call the script for their apt packages and no longer run apt-get install themselves. | [`both Linux jobs install their apt packages through the retrying script`](../../../packages/agent-app/tests/ci-apt-install.test.ts) |

### Case format

Expand Down Expand Up @@ -243,3 +245,27 @@ Boundaries:
- Environment: Node test runner; pure function over made-up lists.
- Known gaps: None known.
- Evidence: UNVERIFIED

### Linux CI survives an apt mirror outage (#876)

<a id="mus-spec-22-876a"></a>
#### MUS-SPEC-22-876A: The apt step falls back to another mirror

- Precondition: A Ubuntu apt setup (a deb822 `ubuntu.sources` and a classic `sources.list`). The first mirror fails `apt-get update`; on the second, update works but the install cannot find a package; the third works.
- Action: Run `scripts/ci-apt-install.sh` with the packages to install.
- Expected result: The script points the sources at each mirror in turn, refreshes the package lists and installs from the first mirror that works (the third here). When every mirror fails, it exits non-zero after trying each one, so the job fails with apt's own error.
- Test: `packages/agent-app/tests/ci-apt-install.test.ts::the apt install moves to the next mirror when one cannot find a package, and fails only after every mirror`
- Environment: Node test runner; the real script under bash with a stand-in `apt-get` on PATH and the apt folders in a temp directory; no network and no root.
- Known gaps: Real mirrors are not contacted; a mirror that stalls is covered by apt's own per-request timeout and the script's time limit per attempt, which the test does not exercise.
- Evidence: UNVERIFIED

<a id="mus-spec-22-876b"></a>
#### MUS-SPEC-22-876B: Both Linux jobs use the retrying apt step

- Precondition: The cross-platform workflow, with the Linux build job on the runner and the Ubuntu 24.04 job inside a container.
- Action: Read the apt steps of both jobs.
- Expected result: Each job installs its apt packages through `scripts/ci-apt-install.sh` and does not call `apt-get install` directly.
- Test: `packages/agent-app/tests/ci-apt-install.test.ts::both Linux jobs install their apt packages through the retrying script`
- Environment: Node test runner; reads the real workflow file.
- Known gaps: Checks the wiring, not a real CI run; the PR's own CI run is the end-to-end proof.
- Evidence: UNVERIFIED
1 change: 1 addition & 0 deletions packages/agent-app/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ All notable changes to Muster Agent. Each `## <version>` section becomes the not
- A suggested follow-up after a turn, as ghost text in the empty message field: Tab or click puts it there, Cmd/Ctrl-click sends it. Off by default (Settings › Chat › Suggest a follow-up) since it is one small extra request per turn: Claude Code chats use Claude Code's own suggestion, API and gateway chats ask the same model once; Codex chats offer none. The transcript also marks where the model or effort changed between turns: "Switched to GPT-5.5 · high effort" (#401).
- Commits in the History graph have a menu (right-click, or Shift+F10 on a row): copy the SHA or message, check the commit out (detached), create a branch there, cherry-pick it onto the current branch or revert it. A picker shows another branch’s commits to pick from. Each change confirms first, saying what will happen; it is refused while tracked files have uncommitted changes, warns when a chat is running in the folder, and a conflicting pick or revert opens the conflict view. History and Changes refresh afterwards (#409).
- Automations can run once at a set date and time: pick **Once at a date and time** under Repeat. The time is in the automation's time zone and is shown with it. After it runs the automation stays in the list as Done with its run linked; a time that has already passed is refused when you save. If Muster was closed or asleep at that time, it runs once at the next start and its history says it ran late (or records it as missed when catch-up is off). Before it runs you can move it to another chat with Edit (#845).
- **Device tools no longer push router Claude chats back to the Codex client.** With "Let agents use devices" on, a Full access Claude chat on a router runs over the router's Messages API again (without the device tools, which that route has no client for); through Claude Code it keeps them. Also steadier CI: the Linux jobs move to another apt mirror when one fails, and two timing-sensitive tests no longer flake.

## 0.3.18

Expand Down
Loading
Loading