A page of its own in the user manual: install a model, pick the coding
agent per session (and install one that is missing), keep more than one
model on the Mac, and hand work to the local model from a cloud session
with the codeman skill, with the prompts to say and the limits to know.
Linked from the sidebar and the Home index, and from the two pages that
already mention the local model (Custom Model Endpoints, Codeman Skill).
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
`spawn_worker <name> local` started the one agent the model was
installed with. The server now lists every agent the model can be run
with, so the skill passes the choice on:
- `local_model` adds `agents` (each with its status, whether it is the
default, and the readiness mark the server measured) and `models`
(what is on the Mac; only the active one runs). A new reason,
`no-agent`: installed, but nothing that can run the model is there.
- `spawn_worker <name> local:<cli>` (`local:pi`, `local:deepseek`,
`local:opencode`, `local:claude`, also as `name:local:pi` in
`spawn_workers`) starts an agent whose status is `ready`. Anything
else fails the spawn before a session exists and names the agents
that are startable. No agent is ever installed from here: that stays
a person's click in the window. A Codeman that lists no agents knows
one, and only that one can be named.
- The readiness mark comes from the server when it names one, else from
what this skill measured (OpenCode `┃`, Pi `extended-keys`), and
`LOCAL_READY_MARK` overrides both.
- A Pi worker on the local model is started with
`piConfig.approveProjectTrust: false` (--no-approve). On a custom
model Pi keeps no home, so it asks to trust the folder in every
session with Trust preselected, and the task prompt's Enter would
answer that.
Pi's mark is the last line of its startup, a warning about tmux's
`extended-keys` option. Its composer rule is painted in the first frame,
before Pi takes Enter: a prompt sent at the rule stayed unsent, 2 of 2,
and one sent at the warning was submitted, 4 of 4 (pi 1.5.2, Linux). It
is a side effect, not a contract, so a Pi that stops printing it fails
the spawn and the worker is deleted, never handed back on a guess.
The preamble changed, so CODEMAN_PREAMBLE moves to 1.51.0 in the
preamble, the SKILL.md copy of it and every version check, and the
plugin mirror is synced.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
- docs/local-models.md: the four agents with their first request, whether
the window installs them and what was seen where (a real model, a
scripted endpoint, Linux, not the pinned runtime); what differs for Pi
and for DeepSeek Harness; installing one more agent beside a model.
- docs/api-reference.md: `POST /api/local-model/agent`, `install.harnesses`,
the `install-agent` action, and `install.cli` as the default agent.
- docs/architecture-invariants.md, CLAUDE.md, src/web/CLAUDE.md: the
rules: the agent is chosen per session, the agent install is not a
job, a launcher is never installable, a readiness mark is a measured
one, and the window's one builder for the agent chips.
- docs/custom-model-endpoints.md: Pi's context window.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The installed screen gets one more block, "Start a session with": a chip
per coding agent the model in use can be run with, as a radio group, the
server's default selected. A chip that is not on the Mac says so.
The main button follows the selected chip:
- an agent that can be started: "Start a session", with that agent;
- an agent that is not on the Mac: its literal install command above the
button, and "Install <agent>". The click is the consent; the server
runs its own stock command, and the window shows "Installing" on the
chip while the model stays ready and another agent can be started;
- an agent that needs a setup of its own (DeepSeek Harness without a
terminal profile): one line that says where, nothing to press.
A failed install shows the server's sentence under the chip with the
installer's last words folded away; the model's own headline is
untouched. On a stopped model Start stays the one main button. The
choice is remembered on the device. A server that lists no agents gets
the screen it had.
Rendered in Chromium at 1440, 390 and 320 px on a dark and a light
skin: the chips wrap, nothing leaves the window.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
An installed model knew one coding agent: the one it was installed with.
The launch check never asked about the agent, only about the endpoint
and the model, so nothing stopped a second one. What was missing is the
list, and a way to get an agent that is not on the Mac yet.
The install view gains `harnesses`: every catalogue harness the model in
use can be run with, each with how it stands (`ready`, `installable`,
`installing`, `setup-needed`), the literal install command, a readiness
mark for scripts, and the actions the server accepts for it. `cli` stays
the default agent, and names one that can be started whenever one can,
so a client that reads only `cli` never starts an agent that is gone.
`POST /api/local-model/agent { cli }` installs one more agent beside an
installed model. It is not a job and not an event of the reducer: the
state stays `installed` whatever the installer does, so a failed npm
install never blocks a session on a model that works. Same install slot
and same compiled stock command as the `agent` step, one at a time, the
outcome in memory only.
The catalogue:
- `pi` is no longer gated. Its two open questions were answered by a run
against a real model (the `name:tag` id reaches the runtime unchanged;
its first request is 2,096 tokens), and Codeman now passes it the
window. Its minimum is 65536 like the others: nothing below was run,
and no model row offers less, so no Mac's pick changes.
- `deepseek` is new. It is a launcher: its install command leaves it
with nothing to launch, so it is never reported installable (read off
`discovery.launcherProfile`, not the id) and a Mac is offered it only
where it was already set up. Its tool round trip was seen on a
scripted endpoint only; the header says how to gate it again.
- a row may carry `readyMark`; `opencode` has the measured one.
A switch no longer refuses for the agent a model was checked with when
another agent that can run it is on the Mac.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A pi session on a custom endpoint got a model list entry with only the
model id. pi then takes the model for a 128,000-token one (its footer
reads "/128k") and asks for 16,384 reply tokens, so on a 65,536 or
32,768 window it never compacts before the server's real window is full.
The entry now carries `contextWindow` (the full window) and `maxTokens`
(a quarter of it, 8,192 at most) whenever the endpoint's context length
is known: the same two figures the DeepSeek Harness patch already
carries, now from one helper. An unknown length writes neither, as
before.
Measured on pi 1.5.2: with both fields `pi --list-models` shows the
window, and the request carries `max_tokens: 8192`.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A job started from a model the person had stopped runs the runtime, and
the model it ends on is in use and running. The stored "stopped" flag
stayed set, so that model would not have started with the server again.
Both verbs now set it back once their job started, the way Start does.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The guide, the API reference, the invariants and the two CLAUDE files
describe the library with one model in use: the state file's list, the
three verbs and their refusals, the two cached listings of the view, and
the window's list with its row actions.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The local model window could hold one model. A second install was refused
(409 already-installed), and a model a Remove kept could never be freed
from the window again. The store can now hold several models, and exactly
one of them is the model in use: the one the endpoint record lists, the
runtime is started for and a session may be launched on.
Server
- LocalModelStateFile.library lists every model that passed the check
(id, label, context, digest, size, coding agent). The service writes
the entry when verify passes; the reducer carries it and remove drops
it. An installed file of an earlier build counts its one model.
- POST /api/local-model/install is allowed from installed: it adds a
model, and the earlier one stays. A replaced job no longer deletes a
model of the library, only an unchecked or partial download.
- POST /api/local-model/use switches to a checked model: a job of the
two steps verify and connect. The stored digest is compared before
anything is loaded (model-changed otherwise). Nothing is downloaded.
- DELETE /api/local-model/models deletes one model by its name, through
the runtime's own delete, never by a path, never the model in use.
- Adding and switching answer 409 sessions-open while a live session
uses the endpoint. Until such a job ends installed, a session start is
refused with the new code job-unfinished.
- The view carries library (the state's entries joined with what the
running runtime lists: ready, changed, missing, unverified) and store
(the size of the blob folder). Both are cached and refreshed in the
background, never read on the one-second poll.
- With nothing installed, remove is offered while the store still holds
models an earlier Remove kept.
Window
- The installed screen lists the models on this Mac with the context
each was checked with, its state and its size on disk, with Use and
Delete per row (Delete asks once, in the row) and Add a model.
- In the chooser a model that is on this Mac already says so and is
switched to without a download or a confirm screen.
- A paused or failed job that has a checked model beside it shows the
way back to that model.
Every route is admin only in multi-user mode, like the existing ones.
Not run on a Mac: the tests use fakes and the fake runtime, and the
listing, store size and delete were checked against a real Ollama 0.13.0
on Linux, not the pinned 0.40.15 build for macOS.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A 16 GB Mac got no model: the two 9B builds of the catalogue need 10.2 GB at
65,536 tokens, 0.987 of the estimated budget, just past the 0.98 a tight fit
may reach.
New row `ornith:9b-q4_k_m` (Ornith 1.0 9B, MIT), a fine-tune of Qwen3.5-9B for
coding agents whose GGUF build has no vision projector: 5.63 GB against 6.55 GB,
so 9.3 GB at 65,536 tokens, 0.898 of that budget. SWE-bench Verified 69.4,
SWE-bench Pro 42.9, Terminal-Bench 2.1 43.1 on Ornith's card. Its config.json
and its GGUF header are those of Qwen3.5-9B (no prediction layer), so it shares
the figures of the two other 9B rows. Never run on a Mac: `estimated`.
- What changes for a Mac: 16 GB goes from no model to `limited` with this row,
tight. That is the only pick that changes: the row is ranked under MiMo V2.6
9B, so every Mac that could already run a 9B keeps its pick and gains an
alternative. With the budget a 16 GB Mac mini measured (10.7 GB) the pick is
MiMo as before.
- Why under MiMo although its own card's numbers are higher: the two vendors'
tables put their common base 7 points apart, and runtime issue 16992 (open)
was filed on the 35B sibling, which has the same renderer and parser. The
35B stays set aside.
- The 16 GB rule itself is untouched: same budget estimate, same 0.98, same
eight context checkpoints.
- Ranks renumbered 1 to 10; the pins of test/local-model-recommend.test.ts and
the table of docs/local-models.md follow.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The "Run a local model" chooser showed the pick and four more models. It now
lists every catalogue model that fits the Mac, and the install accepts every
listed row.
- MAX_ALTERNATIVES goes from 4 to 11, above what the catalogue holds. It stays
a bound: test/local-model-catalog.test.ts fails for a catalogue that outgrows
it, so nothing drops off the list unseen.
- New row `muse-glimmer:30b-q4_k_m` (Meta, Apache-2.0): SWE-bench Verified 76.0,
SWE-bench Pro 51.2, Terminal-Bench 2.1 51.7 on Meta's card. Its K/V cache is
the smallest of the 27B class, so it is the first model of that class a 32 GB
Mac holds: 21.2 GB at 65,536 tokens, 0.970 of the estimated budget. Figures
computed from its config.json and its GGUF header by the llamacpp rule (13
full-attention layers, 39 sliding-window layers of 2,048 tokens, no
prediction layer); the sliding-window cache rule was read again in llama.cpp
b12111 (src/llama-kv-cache-iswa.cpp). Never run on a Mac: `estimated`.
- What changes for a Mac: 32 GB is no longer `limited`. On an M1 Max or M2 Max
the pick is Muse Glimmer; on a base or Pro chip the dense model is slow and
the pick stays MiMo V2.6 9B, now with the small-pick note. Every other pick
is unchanged.
- The "set aside" list was read again at its sources on 2026-11-10 and gained
ripple-preview:65b (every build requires runtime 0.40.16, one above the pin),
Ornith 1.0 (runtime issue 16992), nemotron-3.5-super:120b, qwen3.5:4b,
qwen3-coder-next and the MLX and DFlash tags of Muse Glimmer.
- The header and the guide now say that runtime issue 16992 has one diagnosed
cause in the tool parser every Qwen-family row shares (a tool argument that
quotes a whole tool call is cut off; the fix is runtime PR 17021, open).
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The README still said nobody had watched this app blink the logo under a
closed lid. The owner ran release candidate 6 of 1.50.1 on a MacBook Pro
and reported that it works. It also names what the LogoLight handover was
checked against.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
LogoLight drives the lid logo with the same DisplayLogicalState fades,
so Codeman used to leave the logo to it whenever it ran: the owner had
LogoLight open and the release candidate never breathed.
LogoLight steps aside instead since its 1.3.0 ("LogoLight steps aside
while Codeman shows its agents on the logo, and takes the logo back
afterwards"). This is Codeman's side of that contract, which lives in the
marker the blinker already keeps for crash recovery:
- it now holds the blinker's process ID;
- it goes down right before the first write rather than when the blink
starts, so a blink waiting for the display does not pause LogoLight for
nothing; under a closed lid the first write is telling the logo the lid
is open, so the marker and the wait come before that;
- with a LogoLight that says LogoLightStepsAsideForCodeman in its
Info.plist, the blinker waits two seconds after writing it, since
LogoLight looks every second. A blink that starts within a second of
the last one's end (work, then a permission) skips the wait: LogoLight
takes the logo back only after the marker has been gone for 1.5 s.
The key is read from the running LogoLight's bundle by process ID
(proc_pidpath, then the plist file, never NSBundle, which would keep a
stale copy across a LogoLight update). An older LogoLight is still left
the logo, and the warning when the switch is turned on now says to update
it. At launch, a logo a killed child left is put back as before, but with
any LogoLight running the lid state is left to it.
Tests: 229 pass on the Mac mini, and six deliberate breaks of the
handover each fail one. The Info.plist lookup was run against a stand-in
process carrying the Info.plist of the released LogoLight 1.3.1: it reads
"steps aside". Not seen with a real LogoLight on a real logo yet.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
LogoLight 1.3.1 lights the logo behind a closed lid on a real MacBook kept
awake with no display attached, and its research notes say why the earlier
tries here stayed dark and what must not be written:
- Kept awake like that, the built-in display stays online and
CGDisplayIsAsleep says 0, so the lid path was never taken ("not tried
yet"). The lid sensor decides now (in since the last commit).
- DisplayPowerStateEvent is gone. A client's DidSleep keeps the logo dark,
lid open too, until a DidWake is written; the last commit wrote DidSleep
when a blink ended, and at launch with a marker and the lid closed.
- After ClamshellUpdate 0, each display in CBDisplayList is told the lid
is closed again (setProperty:withKey:andDisplay:), as each keyboard is:
told it is open, True Tone tints an external display warm and dim.
- The logo is told once, when the daemon has handled the lid
(CBClamshellState, bounded to two seconds of waiting), not at every
cycle, which would toggle those display modules with every beat. It is
told again when it is found dark at the start of a beat, or when a rise
did not move it: three times in a row, then every half minute, with
"stayed dark" in the menu line.
- Unplugging under the lid tells the logo the lid is closed again.
- "The Mac went to sleep" is only said while the lid is closed.
The keep-awake side needs nothing new: pmset disablesleep holds the Mac at
full wake, which is the state LogoLight measured; an assertion against
system sleep alone gives a dark wake, where the logo stays dark.
221 tests pass on the Mac mini, and 13 deliberate breaks of the lid rules
each fail one. Not seen from this app on a real lid yet.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
On RC4 the owner closed the lid with LogoLight not running and the menu
line still said "not tried yet": the child never wrote anything. Three
ways that could happen left no trace: the display list still showing the
built-in display awake under the closed lid (the blink then never took
the lid path), no blink running because no agent was at work, and the
Mac going to sleep at the lid close.
The lid sensor now wins: a closed lid counts as closed whatever the
display list says. While no blink runs and the lid is closed, the line
says why (nothing to show, no agent at work; the logo switched off;
LogoLight has the logo; a refused change). The child notices a sleep as
wall-clock time that passed while Instant stood still, and the line then
says "the Mac went to sleep". With no child at all (switch off, no
server open) the app says nothing watches the lid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RC3's lid path went dark on the real MacBook without a word: it waited for
CBClamshellState, gave up after two tries, and wrote ClamshellUpdate 1
back on every failed proof, and none of its silent paths left a trace
without a Terminal launch.
Now, while the lid sensor (AppleClamshellState) says closed, the built-in
display is off and the Mac is on its power adapter, the logo is told the
lid is open (and each keyboard that it is closed) at the start of every
cycle, with no tries and no give-up: monotone, so nothing is toggled in
between. With no other display on the display sleeps, which also keeps
the logo dark, so if the first rise does not move it the logo is told the
display woke (DisplayPowerStateEvent 2); DidSleep is written back at the
end only if that was sent. The end of a blink, and the marker recovery,
still put the logo back to dark as a closed lid has it.
What the closed lid did last is a line in the View menu, under Apple Logo
Blink Styles: "Lid Closed: blinking", "blinking (display told awake)",
"stayed dark, macOS keeps the logo off", or why it was not tried (on
battery, no keyboard to keep dark, no lid sensor), with the time. The
child writes it to logo-lid next to desktop.json, with the readings on a
second line; the app reads it every two seconds.
The battery and keyboard gates stay: the lid helper only holds on the
adapter, and a keyboard told the lid is open would light under it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The proof that the logo moves under the closed lid now needs a fade level
above 0.01, not just above 0, so no drift of a fade held at 0 passes for a
rise; the slowest rise (the breath's) is past it in about 80 ms. The note
on stderr is a writeln! whose error is dropped: eprintln! panics on a
failed write, and the blink child has panic = "abort".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the lid closed, corebrightnessd tells every module so and keeps the
logo dark, so the blink stopped as soon as the lid came down, which is
exactly when the logo on the lid is the one thing in view.
While the Mac stays awake with the lid closed on its power adapter (Keep
This Mac Awake's lid helper, or an external display), the blink child now
does what LogoLight's Lit with Lid Closed measured on the real MacBook:
once the daemon has handled the lid (CBClamshellState), it tells the logo
the lid is open (ClamshellUpdate 0) and each keyboard alone that it is
closed, so the keyboard stays dark and is never relit after a Blank.
The first rise after that has to move the logo within 1.5 s. If it does
not (with no display on, the daemon may still hold the logo dark), the
lid is told closed again, tried once more, then left dark until the lid
opens, a display comes on or the agents' state changes. A logo the daemon
takes back (it resends the lid state when a display or keyboard comes up)
is told again. Ending a blink under the closed lid hands the logo back to
normal and then writes ClamshellUpdate 1, since normal there is dark; the
marker recovery does the same for a killed child. Never on battery (a
dark wake must not light the logo in a bag), never without the keyboard
IDs, and nothing is written when the lid opens. Every step is a line on
stderr so a real-Mac run shows which one stopped it.
Not seen on a real MacBook yet; the README says so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the review of #800: the window hid the app whenever a keep-awake mode
was chosen, also with no server open (the connect page) or with a Codeman
on another machine, where keep-awake holds nothing. Closing the window then
left a hidden app in the Dock for nothing, and it never quit on close again
until Cmd+Q.
close_hides_app() now decides from the open server as well: it hides only
while keep-awake watches a Codeman on this Mac, the same server and the same
is_this_mac() rule the worker uses. It still hides while keep-awake is only
paused (on battery with Only When Plugged In, no agent at work, no answer),
since it carries on by itself once that changes.
The test now covers loopback, this Mac's own address, another machine's
address and no server, and fails with the old rule. The frame is recorded
and the config saved once, before the hide or quit choice, and the Reopen
comment says how a hidden app comes back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up. omp declared `hooks: 'always'`, which every consumer reads
as Claude's full hook set, so an omp session picked up behaviour it cannot
back. omp goes back to `hooks: 'none'`; `capabilities.approvalHookFile` is now
a separate, narrower capability with its own predicate,
`approvalHookAvailableForMode()` (local, non-adopted sessions only).
- An explicit `stop` wait on omp is a 400 again; `blocked` still works.
- omp inbox items are alert-only (`answerInTerminal`, no `approvalId` on the
push), and only `permission_prompt` / `elicitation_complete` are accepted for
an approval-only session.
- The module is installed from POST /api/quick-start too (new and existing
cases, not Docker or remote) and on reboot-restore, after the launch
pre-flight on both create paths. Claude's settings.local.json block and the
Docker CLAUDE.md no longer land in omp workspaces.
- The generated module passes Bun's `tls: { rejectUnauthorized: false }` for an
https CODEMAN_API_URL (checked against a `--https` server), clears the alert
only when the last pending toolCallId resolves, sends reports in order,
carries omp's policy `reason` as `message`, and imports HookAPI from
`@oh-my-pi/pi-coding-agent/extensibility/hooks`.
- Every path component and the target file are lstat-checked.
- docs/omp-integration.md, verbs.md and the stock.ts comments updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ctrl+V in Firefox still pasted the clipboard twice some of the time.
Right-click → Paste never did.
`_handleImagePaste()` focuses a hidden contenteditable trap, calls
`document.execCommand('paste')`, and reads the clipboard from the paste
event that lands on the trap. Firefox dispatches that event synchronously,
inside the `execCommand` call. It then delivers the key's own paste as a
separate task, about 10 ms later in Playwright's Firefox. The one-shot flag
from b87bc687 drops that second event, but only if it still reaches the
trap. The trap removes itself and refocuses the terminal on a
`setTimeout(0)` queued by the first event, and that timer races the second
paste. When the timer wins, the second paste lands on xterm's textarea,
xterm's own `handlePasteEvent` sends the clipboard again, and the flag
never sees it.
The trap now cancels the keydown when its paste listener has already run
by the time `execCommand` returns, so Firefox never issues the second
paste. Both callers pass the keydown as `target.keyEvent`: the main pane's
key handler in terminal-ui.js and a tile's in terminal-tile.js. Chromium
and WebKit refuse the command and dispatch nothing from it, so their
keydown is left alone, because its default action delivers their only
paste event.
Measured in Playwright's Firefox with the trap removed before the key's
paste arrived: the paste reached xterm's textarea and the text went in
twice. With the keydown cancelled, the key's paste never fired. End to end
on an isolated instance with a shell session, 20 Ctrl+V presses gave 20
pastes in both Firefox and Chromium.
test/image-paste-trap.test.ts gains a harness option in which
`execCommand` delivers the paste from inside the call, as Firefox does.
New cases cover the keydown being cancelled for the main pane and for a
tile, being left alone when `execCommand` delivers nothing, and a caller
that passes no keydown. The two cancel cases fail without this change.
test/terminal-tile-input.test.ts now expects the tile to pass its keydown
along with its terminal and session.
Docs: the Terminal paste section of docs/architecture-invariants.md and
the paste-trap entry in src/web/CLAUDE.md now describe the race and the
rule. The section's old claim that the key's default action alone always
reaches the trap is replaced, since in Firefox it can miss it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An omp pane asking for tool approval raised no tab alert, no desktop
notification and no Approvals Inbox item. omp inherited hooks:'none' from
agentDefaults(), so hooksAvailableForMode() answered false and
hook-event-routes never opened an item; there was nothing to resolve either.
omp is one of the few non-claude CLIs with real approval events
(tool_approval_requested / tool_approval_resolved), so Codeman can install a
hook module into <workspace>/.claude/hooks/pre/ - the directory omp scans -
on session create. It forwards them as permission_prompt and
elicitation_complete, the names the server already routes on, so the existing
alert, notification and inbox pipeline works unchanged.
The file name comes from the new capabilities.approvalHookFile field rather
than a branch on the CLI id (cli-registry-no-id-branching). stop/idle_prompt
are deliberately NOT reported: omp's idle stays screen-read via workDetect,
which the agent skill's verbs.md now says explicitly.
The plain preview already serves a workspace dotenv file in full to the same user, so refusing the edit protected nothing. The edit gate now passes allowDotenv to the sensitive-path check, which skips only the two dotenv patterns; every other credential pattern, a dotenv file inside a blocked tree and Codeman's own data-dir login file stay blocked. Downloads and attachments are unchanged.
Folds the carried changesets of #732 (minor), #791 and #705 into one
patch changeset for the whole release, so the bump is 1.50.1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docs/api-reference.md and the voice-stream.ts overview now say a
transcript frame is the current utterance so far and a final frame
ends it, as the relay's own comment and the invariants doc do.
- VoiceInput.cleanup() hides the Send button before stop(true), so the
button stays up for the words stop(true) salvaged and inserted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_onWebSpeechEnd counted every run that ended without a result toward the
two-retry cap, however long the run lasted. Under 30 s or Manual, each
no-speech window of the engine is one silent run, so the third ended the
recording at about 18 s of silence, before the deadline, and Manual
stopped on its own. Both recognition.start() sites now stamp the run start;
an empty run counts toward the cap only when it ended within a second of
starting (a recognizer that cannot listen), and any other run resets it.
The 250 ms backoff is unchanged.
The vm harness in test/voice-silence-timeout.test.ts now passes the test's
Date into the context, whose own Date ignores fake timers.
Also from review: the VoiceInput stubs of the four settings-ui.js tests
carry SILENCE_TIMEOUT_SECONDS; Settings-Reference lists Stop after silence;
the touchscreen Linux fallback leaves out ChromeOS and says a touch Linux
laptop still matches; the dead _webSpeechStream teardown is gone; the two
voice tests use vitest expect instead of node:assert.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The floating log viewer reconnected every few seconds when its tail-file
stream ended with a startup error (path refused, file missing, too large,
stream limit, tail spawn failure). It now closes the EventSource on an
`error` message that arrives before `connected`; an established stream
keeps its reconnect.
Squashed from #720 (johoja12). Maintainer edits while landing, as the
review said: the onerror close is dropped (it stopped the browser's own
retry after a transport failure, leaving the window on "Connecting"),
the test's check for a button nothing renders is gone, and the stubbed
stream sets `retry: 200` so a reconnect, if one came, shows within the
shorter wait.
Co-authored-by: Codeman maintainer <kay@stutz.science>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep This Mac Awake lives in the app and ends when the app quits, and
closing the main window quit the app. So with a mode on, the red button
(or Cmd+W) followed by closing the lid let the MacBook sleep: the lid
request file was cleared and the helper released disablesleep at its
next run.
While a mode is on, closing the main window now hides the app the way
Cmd+H does (hide_application), so full screen and pop-outs come back as
they were on a click on the Dock icon. Cmd+Q and Quit from the Dock
still end it, through LoopDestroyed as before. With keep-awake off,
closing the window quits as it always did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owner found the options confusing after the first test build. The
menu now reads Off / While Agents Work / Always, then on a MacBook
Only When Plugged In and With the Lid Closed…, then one status line.
- "Always While Codeman Is Open" is now "Always"; the status line says
"Awake: Codeman is open".
- "Only on Power Adapter" is now "Only When Plugged In".
- "Remove the Lid Helper…" is gone: the lid item is the helper.
Ticking it installs the helper behind the administrator prompt,
unticking it removes it behind another. The setting changes only
once the prompt's command ran, so a cancelled prompt leaves the item
as it was.
- Shorter status lines ("Awake: agents working, lid closed too",
"Paused: on battery", "Sleeps as usual: no agent working").
Config keys and values are unchanged, so a saved choice carries over.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- session-ui.js: drop the answer when the case-sync chain moved during the read; settings-ui.js calls _applyServerCaseSettings with ?.
- i18n.js: zh-CN for the failed case-options save toast, case name passed through
- tests: regression for the stale read, zh-CN toast row
- sync-case-state browser and stale-refresh tests use lastPickedCase (the stale test now fails without its guard)
- saveLastUsedCase runs on the case-sync chain, and the follow is skipped while this device's own pick was not stored
- _caseStateSyncOn tolerates a harness without loadAppSettingsFromStorage
- lastPickedCase added to the config-backup volatile-key test and docs; rename rollback tests cover caseSettings and lastPickedCase
- wiki row moved to System
Codeman > Keep This Mac Awake in the macOS app: While Agents Work (an
agent at work, plus 5 minutes after the last one stops) or Always While
Codeman Is Open. Off by default, and only for a Codeman on this Mac.
- Idle sleep needs no password: the app holds an NSProcessInfo activity
with IdleSystemSleepDisabled, which the kernel drops with the process.
While it only watches for work, an activity that still allows sleep
keeps App Nap from slowing its look at the server.
- On a MacBook, Only on Power Adapter (on by default) pauses it on
battery, and Also With the Lid Closed installs a small LaunchDaemon
behind macOS's administrator prompt. It sets pmset disablesleep only
while the app keeps a fresh request file with its live pid, only on
the power adapter, and undoes only what it set itself. The app writes
the script and plist as root from the copies built into it.
- A status line at the bottom of the submenu says what it does now.
This brings the macOS part of the keep-awake idea from PR #595 into the
app; the server-side setting and the Linux route are left out for now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>