Conflict in skills/codeman/SKILL.md (and its plugin mirror): the list of
modes with neither stop nor blocked gained jcode here and the omp
exception from #798 on the release branch; both are kept.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The four review items of #797, applied at merge.
The editor's name rule lowercases, the data dir's .env pattern did not:
.codeman/.ENV passed both, and on a case-insensitive filesystem (the macOS
default) that is ~/.codeman/.env, the login file codeman attach reads. A
session whose workspace is the home folder could open and save it. The
pattern is case-insensitive now, pinned in the blocklist table and on the
three editor routes.
A save writes a new file and renames it over the old one, so the file took
the server's own group: a 0640 .env owned you:www-data came back you:you
and the web server lost read access. The save now puts the old user and
group back where the account may (best effort), and the wiki says mode
and group instead of permissions.
Tests: an .env in an outside worktree is refused on edit=1 and PUT too,
now that the editor's own gate lets dotenv through; the comment on why
.env.example is blocked for downloads and attachments is back.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The review items of #777, finished here so the PR can land.
A statusline report fires the trigger in the middle of a turn, and the
command then waits for that turn to end. Until now it went out whatever
had happened meanwhile: with auto-clear at 140k, a report of 150k fired
while the session worked, Claude Code compacted by itself, the report
fell to 20k, the session went idle and /clear wiped the conversation.
Both actions now ask again at that moment and return unsent when the
context is no longer at the threshold, when the option was switched off,
or when its threshold was raised past the context. The skipped command
stamps nothing, so the next report is judged like any other, and the
autoCompact and autoClear events carry the size at the moment of sending.
With auto-resume on, a trigger that fired before a usage limit hit sent
/clear once the pane went idle, and auto-resume then typed continue into
an empty conversation. The checks start nothing from the limit message
until the resume prompt went out, and an action that waited through the
start of a pause returns unsent.
Also: the threshold cap never drops below the 1,000 minimum (a reported
window of 500 stored a threshold of 500), a test pins that a conversation
switch drops the report, and docs/api-reference.md names the new
contextWindowSize session field.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
POST /api/local-model/use, DELETE /api/local-model/models and
POST /api/local-model/agent are experimental like the routes beside them,
and so are the install object's library, store and harnesses lists.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The Mac app keeps a closed MacBook awake through the kernel's lid-sleep
switch, held by a child process of the app, instead of the root helper that
1.50.1 installed behind an administrator prompt. The old helper is no longer
installed or used; Keep This Mac Awake offers to remove it while its files
exist.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Keep This Mac Awake > With the Lid Closed is a plain switch now. 1.50.1
installed a root LaunchDaemon behind the administrator prompt, which set
`pmset disablesleep` while the app asked for it. The app now turns off lid
sleep in the kernel's power management root itself
(`kPMSetClamshellSleepState`, a call any process may make), the method
LogoLight ships as Lit with Lid Closed.
That switch has no owner (read from Apple's sources of the kernel and of
powerd): the kernel keeps it after the process that set it has gone and
honours it on battery, macOS's power daemon and LogoLight write the same
bit, and clearing it under a closed lid puts the Mac to sleep at once. On an
Apple silicon Mac it is also what keeps a docked MacBook awake with its lid
closed, set by the power daemon once, when a display comes. So a child
process holds it (`src/lid_awake.rs`, this binary with `--keep-awake-lid`):
- it sets the switch again every second and holds two assertions with it
(system sleep and idle sleep), the three LogoLight measured together;
- it reads the power source itself and holds nothing on battery;
- it writes nothing at all while an external display is connected: a zero
from here would put a docked Mac to sleep, or leave it without its
closed-lid mode until the display is plugged in again. What the child set
before the display came is cleared once the display has been gone for
five seconds, and the child outlives the app for that if it has to;
- it lets go when the pipe from the app closes (quit, crash, Force Quit) and
when the app has not written for a minute (a hang), the assertions first,
then the switch;
- it ignores SIGHUP, SIGINT and SIGTERM, so the pipe is its one way out;
- it leaves the switch alone at its end while LogoLight holds it for itself
(LogoLight sets it once and would not notice it gone).
One child at a time holds a lock on `lid-awake` next to `desktop.json`, and
the file is non-empty while the switch may be set. A child that was killed
leaves it marked and unlocked: the app turns lid sleep back on when it sees a
child end and at its next launch, and the next child takes the mark over.
The status line says what came of it: the kernel took the call, the lid is
left to macOS at a display, on battery, refused, or that the Mac slept under
the lid all the same and until when. A display has its own wording so a Mac
that sleeps there never reads as covered.
The helper of 1.50.1 is no longer installed or used. Its request file is
never written and is deleted at launch, so a helper that is still there does
nothing. While it is installed the menu has Remove the Old Lid Helper…, the
one item left behind the administrator prompt. `macos/keep-awake-lid.sh` and
its vitest suite go with the install path.
Checked on a Mac mini (macOS 27.2, no lid, no display): 276 tests, clippy and
fmt; the kernel takes the call from an unprivileged process; the real child
holds its assertions, lets go on a closed pipe and after a minute of silence,
ignores SIGTERM, waits for a child before it, writes nothing when told a
display is connected, and a SIGKILLed child is replaced by the app and its
mark undone. 34 deliberate breaks of these rules each fail a test. Not run
yet: the app itself with this change (the mini has no session to open it
in), a real closed lid, the menu items, and the removal of the old helper.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The four edits promised in the review of #798:
- An omp alert could clear while omp's dialog was still up. omp items got
the same pane `capture` as Claude's, which the inbox re-reads with
Claude's numbered-dialog parser. omp's dialog has no numbered rows, but an
agent's plan above it does, so the list was parsed as the item's options,
and once it scrolled out of the frame the item was resolved as answered in
the terminal. An approval-only item now takes the frame once as `context`
and gets no `capture`, like a screen-read item (web/screen-blocked.ts), so
the module's `elicitation_complete` is the only thing that clears it. A
route test puts a numbered list above an omp-shaped dialog and fails on
the old code at both steps (options parsed, alert cleared).
- The reboot-restore install is dropped: the restore plan skips every CLI
whose transcript is not `claude-jsonl` (omp's is `omp-jsonl`), so it never
ran, and it sat after the start, when omp has already scanned its hooks.
- The docs that say which CLIs raise `blocked` and open alert-only items now
name omp: api-reference, CLAUDE.md, architecture-invariants, an entry for
`approvalHookFile` in cli-registry, and the skill with its plugin mirror
(verbs.md listed omp among the modes where `blocked` is a 400, one line
above the paragraph that says it works).
- Two comments: the schema's file-name rule is a pattern, not a closed list,
and the marker check takes the marker anywhere in the file.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Selecting a chip stored it for the device at once. Someone who clicked
"DeepSeek" (Needs setup) or "Pi" (Not installed) just to see what it
says then got a window that reopened on that chip: "Install Pi", or no
main button at all, with "Start a session" out of sight.
The device now remembers the agent a session was STARTED with, an
opening of the window selects it only while it can be started, and a
selection made in one opening does not carry into the next. Otherwise
the server's default agent is selected, as before.
Also: a real-browser test for the chips (they wrap inside the window at
1440, 390 and 320 px, the install command included, one tab stop), and
one sentence in the API reference: a readiness mark says the composer is
there, not that nothing else is (DeepSeek Harness's setup guide draws
the same glyph).
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The two edits promised in the review of #799:
- The main pane's Ctrl+V branch hands the keydown to the paste trap, and
nothing tested that call: with it reverted to `_handleImagePaste()` every
test still passed and Firefox would paste twice again. A source pin now
fails on that, like the Ctrl+C one in terminal-copy-clean.test.ts.
- architecture-invariants and src/web/CLAUDE.md described the key's own paste
as reaching the trap in every browser. Since #799 that is Chromium and
WebKit only: in Firefox the trap cancels the keydown.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A second scripted run, on dsh 0.2.2-rc.1 with a freshly installed
dsh-TUI 0.19.0 under a scratch home: the model name reaches the endpoint
unchanged, the first request is 6,983 tokens with 27 tools, and a tool
round trip completes.
Two things that version does differently are written down where a reader
of the harness table looks: a setup guide opens in every session until a
person has finished it once (and its cursor is the readiness mark), and
its transcript file was renamed, so `last-response` is empty for it.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
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>
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>
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>
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>
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.
- 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 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>
- 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>
- follow lastPickedCase (written by saveLastUsedCase while sync is on), not lastUsedCase, so a quick-start or an agent's spawned worker no longer moves other devices
- close the open case-options popover when the follow or an import changes what it shows; follow through selectQuickStartCase(save:false)
- failed save reuses _noteSettingsWriteResult and says the choice lasts until the next sync
- rename undo renames the shared entry back instead of restoring the whole map; lastPickedCase follows a rename
- caseSettings and lastPickedCase added to the redaction, rename and backup-volatile lists and the docs
The 1.50.0 blink switched LEBacklightMode on and off. Every style was a lit
logo with short dark blips that differed only in how many there were, and
Working (Heartbeat) looked busier than Waiting for you (Wink), so urgency
read backwards. Each change was also a write to a setting corebrightnessd
keeps in root's preferences, and new users had to pick a style in three
submenus before anything happened.
Now one rule: the more the logo moves, the more it needs you.
- View > Blink Apple Logo is one switch, off until turned on. It shows
Breathe while agents work, Calm for a yellow tab and Alarm for a red one.
Each state can take another style under View > Apple Logo Blink Styles.
Picking a style turns the switch on, and turning it on with every state
off brings back those three.
- Breathe is new: a slow fade out and back in, every 6.3 s.
- The logo moves on the LED's own hardware fades (LEPWMFactorFadeDuration,
then DisplayLogicalState Blank or Unblank), as LogoLight measured them on
the real logo (MIT, reimplemented in Rust, not copied). A blink writes no
setting and leaves nothing behind a restart. LEBacklightMode is only read,
so a logo the user switched off stays off.
- The direction of a fade is tracked, because the daemon ignores a write in
the direction it already heads and the state can't be read back. When the
direction is unknown, the logo is turned round with a 1e9 s fade first.
- After every Blank the keyboards alone are told Unblank, and only while
the built-in display is awake (CGDisplayIsAsleep). They are told Blank when
it sleeps and Unblank when it wakes. When it sleeps, the fade duration
also goes back to automatic, as LogoLight does.
- A rise from exactly dark waits for a display frame, up to 0.5 s. Short
dark beats (up to 0.35 s) turn round before their fall ends, so they never
reach 0. Long ones time their light from when the rise starts.
- Every end puts the logo back: lit, with the automatic fade duration. A
new marker file (logo-fade) covers a killed child. 1.50.0's marker
(logo-blink) still switches back on a logo that version left off.
- While LogoLight runs, Codeman leaves the logo to it (process-name check),
and the app says so when the switch is turned on.
- Settings from 1.50.0 keep their styles, with the switch on if any was
chosen. All three off was that version's default, so it reads as
untouched.
The tests run the blink against a fake corebrightnessd that moves in
17.5 ms frames on the measured spring curve. It starts each fade 19 ms
after the write, ignores a write in the direction it already heads, and
holds a rise from exactly dark back by a configurable delay. They pin these
cases:
- dips never reach 0
- long dark beats keep their light
- no keyboard is told to light while the display sleeps
- every end leaves the automatic duration and a lit logo
- a logo switched off or driven by LogoLight is left alone
- old settings are migrated
An ignored probe (what_this_mac_answers) reads a Mac's displays,
processes, keyboards and logo without writing anything.
Not measured on the real logo, since there was no MacBook to test on:
- how the 0.12 to 0.36 s fades of the hard styles look
- whether a rise from a dip (about 7%) really skips the frame wait
- the keyboard during Alarm
- the keyboards through a real display sleep
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixes#529's review asked for, each with a test.
- No inline handler. The button quoted the path inside
onclick="...decodeURIComponent('<path>')", and encodeURIComponent leaves
' alone, so a file named x'-alert(1)-'.txt ran script in Codeman's
origin and it's.txt was a syntax error. It is now bound in the existing
.file-tree-item loops from the row's escaped data-path.
- Symbolic links are refused, never followed. validateSessionFilePath()
returns the realpath, so deleting AGENTS.md -> CLAUDE.md deleted
CLAUDE.md and left the link dangling. The path as named is lstat'ed,
a link answers 400, and the inode is compared again before the unlink.
- The edit route's confinement whole: validateLocalSessionFilePath()
behind withGuardedOutsideWorktree(), so an absolute path in a followed
outside worktree obeys the same rules, and the sensitive, blocked and
.git checks run on both the realpath and the path as named.
- Adopted sessions are refused like remote ones (the /tmp exception of
the older file surfaces is not copied to a new one); Docker workspaces
are allowed, as on the edit route. The workspace is probed bounded
first (#516). Errors are thrown with their status.
- Deleting from search results re-runs the search, the row buttons stay
visible on touch screens, the confirm and toasts have zh-CN text, and
no button is drawn for remote or adopted sessions.
- Docs: CLAUDE.md, architecture-invariants, api-reference and the
Working With Files wiki page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- session-ui.js: no GET /api/settings on a tab return unless this device's cached syncCaseState is true
- system-routes.ts: caseSettings is merged only while syncCaseState is true (as stored or set by the same PUT)
- sync-case-state tests cover both; wiki notes the stored options stay when sync is turned off
jcode (1jehuang/jcode) joins the CLI registry as the `jcode` run mode:
Run menu, phone overview, TUI, quick-start and POST /api/sessions
(`jcodeConfig: { model?, resumeSessionId? }`), local, SSH and Docker
(dedicated agent-image layer), plus codeman doctor and install.sh
through the generated catalogue. Measured on a live jcode 0.98.1 pane.
- Work detection reads jcode's braille status row and `<n>>` composer;
alt screen with mouse strip and PageUp/PageDown paging like opencode.
- Every jcode TUI is a client of one shared per-user daemon whose tools
run with the daemon's env, so no agent skill injection and no JCODE_*
envOverrides prefix.
- New `capabilities.sharedDaemon`: a session kill spares descendants
outside the pane's process group, so closing the tab that started the
daemon no longer cuts every other jcode session off (measured).
- last-response answers an honest empty for an external CLI with no
transcript reader that the pane segmenter cannot read (jcode alone).
- GET /api/jcode/status via the generic registry resolver.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>