Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
e9cfc27
feat(studio): the window works on the sidecar's committed chats, and …
AojdevStudio Nov 7, 2026
0c941f9
fix(studio): the update pass follows real work, and the window keeps …
AojdevStudio Nov 7, 2026
c635ad9
chore(formal): the update models still describe the sidecar after the…
AojdevStudio Nov 7, 2026
aed4f0c
fix(sidecar): the update pass stays held between a tool's result and …
AojdevStudio Nov 7, 2026
a397de6
fix(studio): work goes on only with the update pass, and drafts and r…
AojdevStudio Nov 7, 2026
eab0a5a
fix(studio): a stop or a quit ends the wait for the pass, and a draft…
AojdevStudio Nov 7, 2026
77d3a53
fix(sidecar): a turn waiting on Astra's sign-in gives the pass back w…
AojdevStudio Nov 7, 2026
e0e39ec
test(studio): the draft test reads its last save without Array.at, wh…
AojdevStudio Nov 7, 2026
4075bb8
fix(studio): a sent message never comes back as the start screen's draft
AojdevStudio Nov 7, 2026
52d2f28
fix(studio): the window's engine status listens before it asks, as th…
AojdevStudio Nov 7, 2026
613b27b
fix(sidecar): a projects path that is not a folder holds no old chats…
AojdevStudio Nov 7, 2026
af51508
chore(formal): the update models still describe the sidecar after #20…
AojdevStudio Nov 7, 2026
4421df6
fix(sidecar): a turn that goes on with a newer login takes the update…
AojdevStudio Nov 7, 2026
922604a
chore(formal): the update models still describe the sidecar after the…
AojdevStudio Nov 7, 2026
9dd6507
fix(sidecar): a turn a sign-in lets go waits out an installing update…
AojdevStudio Nov 7, 2026
f0cd331
fix(sidecar): a slice that outlives its sidecar holds the update off …
AojdevStudio Nov 7, 2026
817f6dd
fix(sidecar): a sign-in lets go only the wait it was for, and never h…
AojdevStudio Nov 7, 2026
1a35f90
fix(sidecar): the import keeps projects with no chat, shows cut-off s…
AojdevStudio Nov 7, 2026
2129457
fix(studio): a stopped step shows as stopped, and an older chat chang…
AojdevStudio Nov 7, 2026
1418897
fix(sidecar): a login shell's PATH comes at once, and a slice refused…
AojdevStudio Nov 7, 2026
d7f92e5
chore(formal): the update models still describe the sidecar after Cod…
AojdevStudio Nov 7, 2026
8809d02
fix(studio): an odd old line never drops a whole old chat, and the wi…
AojdevStudio Nov 7, 2026
5bd71cd
fix(sidecar): a chat's read order keeps growing when the sidecar star…
AojdevStudio Nov 7, 2026
6bd0ff4
chore(formal): the update models still describe the sidecar after the…
AojdevStudio Nov 7, 2026
38140ad
test: the unreadable-folder test skips under root, and the new login …
AojdevStudio Nov 7, 2026
baf85d1
chore(formal): record sidecar.rs's hash after its test became Unix only
AojdevStudio Nov 7, 2026
cdfbaa8
fix(sidecar): a chat change takes its read order as soon as it reads …
AojdevStudio Nov 7, 2026
fcbeec0
chore(formal): record chat.ts's hash after the read-order change
AojdevStudio Nov 7, 2026
23e36e6
fix(sidecar): the login PATH lookup stops reading once it is done, an…
AojdevStudio Nov 7, 2026
5024e48
chore(formal): record sidecar.rs's hash after the login PATH change
AojdevStudio Nov 7, 2026
b6e65f4
fix(studio): the open chat is read again after the engine restarts, a…
AojdevStudio Nov 7, 2026
5e390f3
chore(formal): record chat.ts's hash after the reconciled chat state
AojdevStudio Nov 7, 2026
4cb51ec
fix(studio): Send waits for a ready agent, drafts keep their pictures…
AojdevStudio Nov 7, 2026
5a61107
chore(formal): record app.ts's hash after the import retry
AojdevStudio Nov 7, 2026
5baa2be
chore(tauri): regenerate the ACL schemas for studio_flushed and studi…
AojdevStudio Nov 7, 2026
82e2478
fix(studio): only the newest chat load replaces the view, and a sent …
AojdevStudio Nov 7, 2026
7fba96b
chore(formal): record sidecar.rs's hash after a test change
AojdevStudio Nov 7, 2026
ab3907d
fix(studio): Send waits on the check through a restart, drafts let go…
AojdevStudio Nov 7, 2026
7e9a7bd
chore(formal): record app.ts's hash after the picture cleanup
AojdevStudio Nov 7, 2026
6a4c9a6
fix(studio): a send touches only its own drafts, keeps newer ones, an…
AojdevStudio Nov 7, 2026
7d6dee9
chore(formal): record app.ts's hash after the picture age check
AojdevStudio Nov 7, 2026
1ac2991
fix(studio): drafts carry a revision, and a send consumes only the dr…
AojdevStudio Nov 7, 2026
1ab5809
chore(formal): record chat.ts's hash after draft revisions
AojdevStudio Nov 7, 2026
5f3b790
fix(studio): a send names the draft save it went out with, and edits …
AojdevStudio Nov 7, 2026
47918da
chore(formal): record app.ts's hash after the old-draft revisions
AojdevStudio Nov 7, 2026
0aa8402
test(sidecar): start the test sidecar with the bun running the tests
AojdevStudio Nov 7, 2026
27f2c04
fix(studio): a retry matches by picture path, a send always names its…
AojdevStudio Nov 7, 2026
9168046
fix(studio): an unsent message is named by its draft revision
AojdevStudio Nov 7, 2026
488f33d
fix(sidecar): the toolchain check never holds up the sidecar, and a s…
AojdevStudio Nov 7, 2026
a6c2c80
fix(studio): an unsent message is named by its screen's edit count, a…
AojdevStudio Nov 7, 2026
b35114b
fix(studio): a message is its draft's revision, known only while the …
AojdevStudio Nov 7, 2026
cb45257
fix(studio): the sidecar owns the draft and the send, and a send name…
AojdevStudio Nov 7, 2026
b4c9bc2
chore(studio): drop the window's picture import call, which no send u…
AojdevStudio Nov 7, 2026
964383e
fix(studio): a cut-off first send goes on, a late saved state never b…
AojdevStudio Nov 7, 2026
361b3c5
fix(studio): the cleanup keeps a cut-off first send's pictures, and a…
AojdevStudio Nov 8, 2026
05144bd
fix(studio): a send takes what was clicked, a quit loses no draft, an…
AojdevStudio Nov 8, 2026
4798ac3
fix(studio): a quit never goes on with a draft unsaved
AojdevStudio Nov 8, 2026
176fde9
fix(studio): each quit try waits for the sends on their way before it…
AojdevStudio Nov 8, 2026
cca2035
fix(studio): a quit is done only when no screen sends and every scree…
AojdevStudio Nov 8, 2026
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: 5 additions & 5 deletions .agents/skills/formal-verify-materialize-3d/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Model-check, with TLC, what the in-app update's restart can cut off

Two TLA+ models describe code where a bad order of events harms a user and tests rarely reach that order. TLC (the TLA+ model checker) tries every order of steps that the models allow.

- `models/UpdateRestart.tla`. **Update now** waits for builds, closes the build gate, backs up, installs, and restarts. Builds, STL slices, and Studio turns race against it, and so do print queue writes.
- `models/UpdateRestart.tla`. **Update now** waits for builds, closes the build gate, backs up, installs, and restarts. Builds, STL slices, and the sidecar's chat work race against it, and so do print queue writes.
- `models/AutoSend.tla`. Two auto-send runs race to send the first Pending job in the print queue to the printer, across reconnects and a printer switch.

## Checks
Expand All @@ -16,7 +16,7 @@ Two TLA+ models describe code where a bad order of events harms a user and tests

| Check | Property | Harm if it breaks | Recorded |
|---|---|---|---|
| `no-build-cut-off` | The restart never kills a build, a slice, or a Studio turn, nor cuts a turn's last transcript write. | A half-made build or 3MF, a turn's agent killed mid-job, or a chat that never records how its turn ended. | holds |
| `no-build-cut-off` | The restart never kills a build, a slice, or the sidecar's chat work, nor cuts its last commit. | A half-made build or 3MF, or a turn's agent killed mid-job. | holds |
| `gate-held-until-restart` | From the idle check to the restart, the gate is closed and no build runs. | A build starts that the restart then kills. | holds |
| `failed-update-reopens` | A failed update lets builds start again. | The app refuses every build until it is quit. | holds |
| `update-wakes` | When builds stop starting, a waiting update goes ahead. | **Update now** hangs with no build running. | holds |
Expand Down Expand Up @@ -55,8 +55,8 @@ Exit 0 means every verdict matches the recorded one. Exit 1 means at least one d
| `split-close` | `close_when_idle` reads `running`, then closes the gate in a second send. | `gate-held-until-restart` |
| `late-subscribe` | `borrow_and_update` moves after the idle check, so a build that ends in between is never heard. | `update-wakes` |
| `ungated-slice` | `slice_model` no longer calls `builds.enter()`. | `no-build-cut-off` |
| `ungated-turn` | `studio_send` no longer calls `take_pass`. | `no-build-cut-off` |
| `early-release` | A Studio turn drops its pass before its last transcript write. | `no-build-cut-off` |
| `ungated-turn` | A send no longer asks for the pass (`Busy.during` in `sidecar/src/busy.ts`). | `no-build-cut-off` |
| `early-release` | The sidecar gives its pass back while a task is still live. | `no-build-cut-off` |
| `guard-dropped` | `update_install` binds the `Closed` guard to `_`, which drops it before the restart. | `no-build-cut-off` |
| `guard-leaked` | An error path keeps the `Closed` guard alive. | `failed-update-reopens` |
| `unguarded` | The claim in `auto_send_pending_job` no longer checks `queue_held`. | `one-run-at-a-time` |
Expand All @@ -78,7 +78,7 @@ Then read each covered function that changed. Also look for new code that reache

| Model action | Code |
|---|---|
| `Enter`, `Leave` | `BuildGate::enter` and `Drop for Running` in `build_gate.rs`. Callers: `build_as` in `pipeline.rs`, `slice_model` in `commands.rs`. For a Studio turn, `BuildGate::enter_owned` through `take_pass` in `studio_send`, and `Drop for OwnedRunning` at the end of the turn's task in `studio/turn.rs`, after its last transcript write. |
| `Enter`, `Leave` | `BuildGate::enter` and `Drop for Running` in `build_gate.rs`. Callers: `build_as` in `pipeline.rs`, `slice_model` in `commands.rs`. For the sidecar's chat work, `BuildGate::enter_owned` through `take_pass` in `set_busy` (`sidecar.rs`), which `sidecar/src/busy.ts` asks for before a send commits (`Busy.during` in `sidecar/src/chat.ts`) and before work a crash cut off resumes (`busy.watch` in `sidecar/src/app.ts`), and `Drop for OwnedRunning` when it gives the pass back once no task is pending or running, after its last commit. A task that waits, sleeps, or holds a sign-in wait (`Busy.parked`) is not work: it resumes after a restart. `set_busy` refuses a request from a sidecar run that ended. A slice the sidecar asks for takes a pass of its own (`with_own_pass` in `sidecar.rs`), since it can outlive the sidecar run that asked for it. A turn a sign-in lets go waits for the pass while an update installs (`Busy.duringWhenAllowed`). |
| `Downloaded`, `Top`, `CheckAndClose`, `CheckBusy`, `Wake` | `BuildGate::close_when_idle`. Its `subscribe`, `borrow_and_update`, one `send_if_modified`, and `changed().await`. |
| `Reopen` | `Drop for Closed`, on each error return of `install_when_idle`. |
| `Click`, `DownloadFails`, `BackupOk`, `BackupFails`, `InstallOk`, `InstallFails`, `Exit` | `update_install` and `install_when_idle` in `updater.rs`, then Tauri's `AppHandle::restart`, which ends in `process::exit(0)`. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
(* install_when_idle *)
(* src-tauri/src/fabrication/pipeline.rs build_as takes a pass *)
(* src-tauri/src/commands.rs slice_model takes a pass *)
(* src-tauri/src/studio/turn.rs a Studio turn takes a pass *)
(* in studio_send, before its *)
(* first transcript line, and *)
(* drops it after its last *)
(* src-tauri/src/sidecar.rs the sidecar's work takes a *)
(* sidecar/src/busy.ts pass (set_busy) before a *)
(* sidecar/src/chat.ts send's first commit and *)
(* sidecar/src/app.ts before work a crash cut off *)
(* resumes, and drops it once *)
(* no task is pending or *)
(* running, after its last *)
(* commit *)
(* src-tauri/src/print_queue.rs persist_queue *)
(* Tauri's AppHandle::restart from an async command asks the event loop to *)
(* exit, then calls std::process::exit(0), which stops every other thread *)
Expand All @@ -22,7 +26,7 @@ EXTENDS Naturals
CONSTANTS
Builds, \* tasks that each hold at most one build pass at a time
Slice, \* the member of Builds that is commands.rs slice_model
Turn, \* the member of Builds that is a Studio turn (studio/turn.rs)
Turn, \* the member of Builds that is the sidecar's work (busy.ts)
MaxStarts, \* starts per build task. 0 means no limit.
MaxUpdates, \* how many times update_install can run
Mutant \* "none" is the code. The others are broken variants.
Expand All @@ -36,7 +40,8 @@ VARIABLES
closed, \* Builds.closed in the gate's watch channel
unseen, \* the updater's watch::Receiver holds a change it has not seen
build, \* build task -> "idle", "running", or "writing" (mutant
\* early-release: a turn still writing after its pass is gone)
\* early-release: the sidecar still committing after its pass
\* is gone)
starts, \* build task -> starts so far, counted only when MaxStarts > 0
upd, \* where update_install is
updates, \* update_install runs so far
Expand All @@ -62,15 +67,19 @@ Init ==

----------------------------------------------------------------------------
(* Builds. build_as and slice_model each call BuildGate::enter first and *)
(* hold the Running pass until they return. A Studio turn calls *)
(* BuildGate::enter_owned in studio_send and drops the pass at the end of *)
(* its task, after its last transcript write. *)
(* hold the Running pass until they return. The sidecar asks for a pass *)
(* (BuildGate::enter_owned in set_busy) before a send commits and before *)
(* work a crash cut off resumes, and gives it back once no task is pending *)
(* or running, after its last commit. Turn is that work. A task that *)
(* waits, sleeps, or holds a sign-in wait does none until something wakes *)
(* it, and it resumes after a restart, so the model has no step for it. A *)
(* request from a sidecar run that ended takes no pass. *)

Gated(b) ==
/\ ~(Mutant = "ungated-slice" /\ b = Slice)
/\ ~(Mutant = "ungated-turn" /\ b = Turn)

\* Mutant early-release: a turn drops its pass before its last transcript write.
\* Mutant early-release: the sidecar drops its pass before its last commit.
EarlyRelease(b) == Mutant = "early-release" /\ b = Turn

CanStart(b) ==
Expand Down Expand Up @@ -105,8 +114,8 @@ Leave(b) ==
ELSE UNCHANGED <<running, unseen>>
/\ UNCHANGED <<closed, starts, upd, updates, qlock, queueFile>>

\* Mutant early-release: the pass drops while the turn still has its last
\* transcript lines to write.
\* Mutant early-release: the pass drops while the sidecar still has its last
\* commit to make.
ReleaseEarly(b) ==
/\ Alive /\ build[b] = "running" /\ EarlyRelease(b)
/\ build' = [build EXCEPT ![b] = "writing"]
Expand Down Expand Up @@ -256,8 +265,8 @@ Spec == Init /\ [][Next]_vars /\ Fairness
----------------------------------------------------------------------------
(* Properties. Each is one check in scripts/check.sh. *)

\* The restart never kills a build, a slice, or a Studio turn, nor cuts a
\* turn's last transcript write.
\* The restart never kills a build, a slice, or the sidecar's work, nor cuts
\* its last commit.
NoBuildCutOff == upd = "exited" => \A b \in Builds : build[b] = "idle"

\* From the idle check to the restart, the gate is closed and no build runs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ bfdae9bc19239959afa97b28ed24be9289b35a988fb59ee657e5086babce1665 src-tauri/src/
581b0e828a547528a87785cd366c6e57252dea85823cc1402fcaf7b113a2255c src-tauri/src/printer/mod.rs
dc6f6f11328e5f422b59be488099f9203bcfe352a1fadcc3f523debca4a7bbd8 src-tauri/src/commands.rs
dc1709c75444259b23f19d9210b730ba014074dcffc42453537d1f3a16cd5430 src-tauri/src/fabrication/pipeline.rs
10eff3df2dbaf0b024c18e97cb60b8961bdafbe8130d63f374a099606a687b0a src-tauri/src/studio/turn.rs
b6a70183e6dc2ae4abe76391342f4ce2c17536ea07f8976252c78eb6c46b17ad src-tauri/src/sidecar.rs
672150b06f7a26b26c4af16eaf9d878dab762c6bf37a406c87cf118f9e71f439 sidecar/src/busy.ts
d49a7e7b10a56f7fbe4e079b07a84c26e6728a1ab3fa38697c95a4e0b3c7558c sidecar/src/chat.ts
6f89db18e99fbe649f2b10a7476c4e548a401aac196d60053d16c5ebb46b9fec sidecar/src/app.ts
32 changes: 18 additions & 14 deletions .agents/skills/verify-materialize-3d/features/studio.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# Studio

The studio is the whole app. Each chat is a project folder in `~/Documents/Materialize 3D/<date>-<slug>/` (an `e2e` build uses `<M3D_E2E_DATA_DIR>/projects/`). A turn runs the local `claude` CLI with Opus 5.5 in that folder on the person's own login. The right panel shows whatever the agent writes: HTML in a sandboxed frame, STL, 3MF, and GLB in an orbit viewer, and pictures. The newest such file opens on its own. The Files tab lists every file, with Reveal in Finder and, for a 3MF, Open in Bambu Studio. The other recipes in this map describe screens the studio hides.
The studio is the whole app. Each chat is a project folder in `~/Documents/Materialize 3D/<date>-<slug>/` (an `e2e` build uses `<M3D_E2E_DATA_DIR>/projects/`), and a conversation in the sidecar's store, `<app data>/state/store.sqlite` (an `e2e` build: `<M3D_E2E_DATA_DIR>/state/`). The sidecar, `Contents/MacOS/materialize-sidecar`, runs the agent through Pi Durable on the person's own inference login, with the app's own tools: `read`, `write`, and `edit` inside the project folder, `run` (uv), `slice` (the validated Bambu Studio, through Rust), and `astra` (GPT 6 Astra as a child task). The right panel shows whatever the agent writes: HTML in a sandboxed frame, STL, 3MF, and GLB in an orbit viewer, and pictures. The newest such file opens on its own. The Files tab lists every file, with Reveal in Finder and, for a 3MF, Open in Bambu Studio. The other recipes in this map describe screens the studio hides.

## Sub-features

- `start-screen` holds "What are we making?", the `prompt` box, `Add picture`, `send`, and three suggestion chips that fill the box.
- `prompt` takes text, pasted pictures, and links. A picture dropped on the window or chosen with `Add picture` shows in `attachments` and is copied to the project's `refs/` on send. It reaches the model as an image block.
- `messages` shows each tool step on one line (`.tool`, with `.tool-running` while it runs), agent text, `file-cards` after a turn that wrote previewable files or a 3MF, and `.msg-error` for a failed or stopped turn. `working` shows while a turn runs, and `stop` replaces `send`.
- `stop` ends the agent and every tool it started (its process group).
- The agent runs lean: the person's inference login only (`ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, `API_TIMEOUT_MS`, and `apiKeyHelper` from `~/.claude/settings.json`), `--setting-sources project`, no MCP servers, no CLAUDE.md or auto-memory, and the app's own `materialize-studio` plugin from the bundle's `studio-plugin` resource. Astra runs with `CODEX_HOME` set to `<app data>/astra`, which holds only the cliproxy provider and turns off the skills under `~/.agents/skills`. A turn's `system` `init` line in the transcript lists its tools, MCP servers, plugins, and skills.
- A turn cut off by quitting the app ends when the project reopens, with "This turn stopped when the app closed."
- `prompt` takes text, pasted pictures, and links. The text is saved as the project's draft a moment after typing stops, and comes back when the project opens. A picture dropped on the window or chosen with `Add picture` shows in `attachments` and is copied to the project's `refs/` on send. It reaches the model as an image block.
- `messages` shows committed chat lines only: each tool step on one line (`.tool`, with `.tool-running` until its result is committed), agent text, and `file-cards` after a turn that wrote previewable files or a 3MF. `working` shows while a turn runs, and `stop` replaces `send`.
- `chat-model` names the model the current turn runs with. A settings change applies from the next message.
- `waiting` shows when a turn stopped before its answer: the fix, and the control for it. A provider with no login shows `Sign in with Claude` or `Sign in with ChatGPT`, and for Anthropic and OpenCode Go a key form (`API key or token`, and for Anthropic `Proxy URL`). Once the login is saved, the turn goes on by itself. Any other failure shows its error and `Try again`.
- A turn cut off by quitting or a crash goes on from its last committed step when the app opens again.
- `engine` in the sidebar shows the sidecar's version, or why it stopped.
- `files-tab` opens `files-list`, with "Print files" (3MF) above "All files".
- `html-preview` is the iframe. `model-view` holds the orbit canvas.

Expand All @@ -20,28 +21,31 @@ The studio is the whole app. Each chat is a project folder in `~/Documents/Mater

## Driving it with wd.ts

On macOS, drive it with `mac.ts` from an `e2e` build. Answer `Add picture` with `mac.ts dialog <png>` before the click. `PROJECT` is the one folder under `<run>/data/projects/`.
On macOS, drive it with `mac.ts` from an `e2e` build. Answer `Add picture` with `mac.ts dialog <png>` before the click. A fresh data folder has no login, so the first turn waits for one. `ANTHROPIC_TOKEN` and `ANTHROPIC_PROXY` are the host's proxy token and URL.

```bash
bun $S/mac.ts wait '[data-testid=start-screen]' 30000
bun $S/mac.ts dialog "$HOME/fixtures/sketch.png"
bun $S/mac.ts click 'text=Add picture'
bun $S/mac.ts type '[data-testid=prompt]' 'Make a cable clip like in this sketch, then slice it to a 3MF'
bun $S/mac.ts click '[data-testid=send]'
bun $S/mac.ts wait '[data-testid=waiting]' 20000
bun $S/mac.ts type '[aria-label="API key or token"]' "$ANTHROPIC_TOKEN"
bun $S/mac.ts type '[aria-label="Proxy URL"]' "$ANTHROPIC_PROXY"
bun $S/mac.ts click 'text=Save'
bun $S/mac.ts wait '[data-testid=working]' 20000
# A full job takes minutes. Wait for its end line in the transcript, then for the file card.
until grep -q m3d_turn_end "$PROJECT/.m3d/transcript.jsonl"; do sleep 10; done
bun $S/mac.ts wait '[data-testid=file-cards]' 30000
# A full job takes minutes. The file cards appear when the turn's last step is committed. One mac.ts wait is one
# HTTP request, which Bun ends after about five minutes, so a long wait goes in rounds.
for _ in 1 2 3 4; do bun $S/mac.ts wait '[data-testid=file-cards]' 240000 && break; done
bun $S/mac.ts click '[data-testid=files-tab]'
bun $S/mac.ts click '[data-testid=files-list] button.primary' # Open in Bambu Studio
```

The transcript is `<project>/.m3d/transcript.jsonl`. A finished turn ends with an `m3d_turn_end` line. Wait on that line, not on a clock.

## Gotchas

- A turn needs `claude` signed in on the host. Without it the turn ends at once with the CLI's error in `.msg-error`.
- Claude Code refuses a bare `sleep`. To test Stop, ask for `python3 -c 'import time; time.sleep(300)'` in the foreground **(observed 2026-11-06)**.
- The sidecar's stderr is in the app log, as `sidecar: <line>`. Its first line lists the agent's tools.
- The sidecar reads none of the host's agent setup or environment. A login made with `claude` or `codex` on the host does not reach it.
- `mac.ts` selectors are CSS or `text=`. It has no XPath.
- A `mac.ts wait` longer than about five minutes fails with "The operation timed out", because Bun's fetch ends the one request it makes **(observed 2026-11-07)**. Wait in rounds of four minutes.
- The WebKit snapshot from `mac.ts shot` includes the iframe and the WebGL canvas **(observed 2026-11-06)**.
- Bambu Studio's own window is outside the webview. Check its process with `pgrep -x BambuStudio` after Open in Bambu Studio.
5 changes: 4 additions & 1 deletion .github/workflows/formal-verification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ on:
- src-tauri/src/printer/mod.rs
- src-tauri/src/commands.rs
- src-tauri/src/fabrication/pipeline.rs
- src-tauri/src/studio/turn.rs
- src-tauri/src/sidecar.rs
- sidecar/src/busy.ts
- sidecar/src/chat.ts
- sidecar/src/app.ts
- .agents/skills/formal-verify-materialize-3d/**
- .github/workflows/formal-verification.yml
push:
Expand Down
6 changes: 2 additions & 4 deletions scripts/release/check-app-bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ newline=$'\n'
odd="$(cd "$app" && find . -name "*$newline*" -print -quit)"
[[ -z "$odd" ]] || die "a path in the app bundle contains a newline: $(printf '%q' "$odd")"

# The app binary, the sidecar, the CAD helper and its runtime, the agent's studio plugin, the icons (Assets.car for
# macOS 26, icon.icns as the CFBundleIconFile), the license notices, the signature, and the stapled ticket
# The app binary, the sidecar, the CAD helper and its runtime, the icons (Assets.car for macOS 26,
# icon.icns as the CFBundleIconFile), the license notices, the signature, and the stapled ticket
# (Contents/CodeResources). Every entry that is not a directory counts, symlinks included.
expected="$(printf '%s\n' \
Contents/CodeResources Contents/Info.plist \
Expand All @@ -28,8 +28,6 @@ expected="$(printf '%s\n' \
Contents/Resources/THIRD_PARTY_NOTICES.md Contents/Resources/icon.icns \
Contents/Resources/cad-runtime/Image Contents/Resources/cad-runtime/job.img \
Contents/Resources/cad-runtime/rootfs.img \
Contents/Resources/studio-plugin/.claude-plugin/plugin.json \
Contents/Resources/studio-plugin/skills/fabricate/SKILL.md \
Contents/_CodeSignature/CodeResources | sort)"
actual="$(cd "$app" && find . ! -type d | sed 's|^\./||' | sort)"
[[ "$actual" == "$expected" ]] || die "unexpected files in the app bundle:
Expand Down
Loading
Loading