Add a persistent isolated JavaScript worker for native app actions and batch
known operations without a model round trip between each input. Preserve
per-cell context, native errors, screenshot coordinates, and image types.
Align macOS gesture, key, inventory, capture, scroll, and clipboard behavior;
include a signed native receiver fixture and compiled sidecar regression tests.
Keep the Windows pixel route and fix cancellation with session-owned mouse
cleanup, lock revalidation, and portable signing-fixture tests.
Connecting Feishu meant creating a bot by hand on the open platform and
pasting an App ID and App Secret back. Feishu also exposes an RFC 8628
device-authorization flow, so the desktop can now render a QR code, and
confirming it in the app creates the bot and stores its credentials
directly. `adapters/feishu/registration.ts` implements that protocol
rather than importing `registerApp` from `@larksuiteoapi/node-sdk@1.73`:
the repository pins 1.60 for the chat client, and the SDK runs the whole
poll inside one un-cancellable promise where the desktop needs the
stateless begin/poll pair the DingTalk registration already uses. The
scan is create-only, so it can never rewrite the configuration of a bot
the user already runs, and it pre-fills exactly the scopes, events and
callbacks this adapter calls. International tenants finish on Lark's
domain, which is now persisted and honoured by the client.
WeCom, QQ and Slack join the same session model. WeCom and QQ bind by
scanning; Slack has no scan flow, so it uses an app manifest that
pre-fills the scopes and Socket Mode. All three run over long
connections, so no public callback URL is needed, and all three accept
private chats only — pairing authorizes one person, and answering in a
group would extend that authorization to everyone else in the room.
They are built on a new `adapters/common/chat-runtime.ts` instead of a
fourth copy of the loop the five existing adapters each carry. A platform
supplies a `ChatPort` — how to say something, how to open a streaming
reply, optionally how to send an image — and the runtime owns pairing,
command routing, session restore, permission bookkeeping and the
translation of the server's stream. The existing five are deliberately
left on their own copies; migrating them is a separate change with its
own regression surface.
Attachments are downloaded through a deferred loader that runs after the
pairing gate and inside the per-chat queue. Resolving them eagerly would
let an unpaired stranger make the adapter fetch bytes and write them
under ~/.claude/im-downloads — on Slack with the bot token attached —
and would let a slow attachment overtake a text message sent after it.
The sidecar launcher's per-adapter branches become one table. It is
declared above the mode dispatch on purpose: `runAdapters` is hoisted and
runs at module top level, so a table declared below it is still in its
temporal dead zone when the adapters mode reads it — which type checks,
lints and unit tests all miss, and only the compiled binary reveals.
Verified with the checks `check:impact` selects: adapters, server,
desktop, electron, policy, chat-contract, agent-flow, docs, native
(sidecar compile, packaging and an adapters-mode smoke against the real
binary) and coverage. The scan flows themselves are not verified against
live platforms — that needs real WeCom, QQ and Slack accounts and would
create real bots.
Claude-Session: https://claude.ai/code/session_01CCGoP316AK7wdQG3Ms6Uwq
The instruction files told every coding agent to reach for agent-browser
whenever a change needed browser-level evidence: copilot-instructions
listed "E2E or agent-browser smoke" as the remedy for cross-boundary
flows, and both contributing guides repeated it. That wording outlived
the tool. With the agent-browser skill uninstalled, agents still parsed
those lines as a recommendation and went looking for the binary instead
of using the browser skill that is actually installed.
Deleting the references would have made the docs wrong. agent-browser is
still a real dependency: check:desktop-ui-smoke spawns it on Linux CI,
and seven maintainer-run e2e scripts under desktop/scripts drive it
directly. It cannot be swapped for ego-browser either — ego lite is a
macOS-only GUI app with no headless mode and a one-time interactive
onboarding, so it cannot run on ubuntu-latest at all.
So the lanes keep the binary and the prose loses the recommendation.
agent-browser is now described as an implementation detail of those two
call sites, and ad-hoc browser work — manual verification, screenshots,
exploratory UI checks — is pointed at the ego-browser skill.
The quality contract asserted the old string, so it would have failed
closed on the reworded line. It now pins the replacement plus the new
routing rule; flipping either sentence turns the test red.
Built-in agents pin their own models — Explore and claude-code-guide run
on Haiku, statusline-setup on Sonnet — and there was no way to change
that. The only override mechanism was a same-named user agent, which
replaces the definition wholesale: AGENT_SLUG_PATTERN is lowercase-only
so `Explore` and `Plan` cannot even be created, and a replacement loses
the runtime getSystemPrompt and the built-in tool privileges keyed off
`source`. CLAUDE_CODE_SUBAGENT_MODEL is the only other lever and forces
every subagent onto one model.
Add `builtInAgentOverrides` to settings.json, carrying model and effort
per agentType. It is applied in getBuiltInAgents(), the single choke
point every consumer goes through, so the effective value reaches
spawning, `/agents` and the desktop list without any of them knowing an
override exists — and `source` stays `built-in`, preserving the prompt
and tool privileges.
Three constraints are load-bearing rather than stylistic:
- `model: "inherit"` is a real value, not a reset. Built-in defaults
differ per agent and per build, so clearing means deleting the field;
the entry and then the key are removed once empty.
- getSystemPrompt is never wrapped. serializeActiveAgent branches on
`.length === 0`, and claude-code-guide declares one parameter while
the others declare none, so a wrapper makes it destructure undefined.
- The settings write is a read-modify-write inside the file lock.
updateUserSettings is a top-level shallow merge and would replace the
whole record, losing an update when two agents are changed in quick
succession.
strictPluginOnlyCustomization is enforced when resolving, not only when
writing, since settings.json is user-editable by definition.
Also surface edit and delete on the agent list rows. Both already
existed but were reachable only after opening an agent's detail page.
Rows become a div with the primary button and the actions as siblings
rather than nested buttons, keep focus-within so the controls are not
tabbable while invisible, and stay visible on touch where hover never
fires. Built-in rows get the override entry point and no delete.
The store's create/update/override paths now share runAgentMutation
instead of a third hand-copy of the out-of-order guards; the existing
store tests pass unchanged.
4f9fec876 added this workflow with `cron: '0 18 * * *'`. That was the wrong call
to make unilaterally: the repository had no scheduled workflow at all before it,
so this was not one more cron among several but the introduction of recurring CI
spend — about ninety minutes per run — on a schedule nobody asked for.
The reasoning for the sweep still holds: a per-PR gate only covers what the diff
reaches, so it is blind to checks no recent PR selected and to failures that only
appear when the whole suite runs together. Keeping the workflow on
`workflow_dispatch` keeps that one click away without deciding for the maintainer
when to spend the time.
pr-quality-workflow.test.ts now asserts the absence of `schedule:` and `cron:`
rather than their presence, so a schedule cannot drift back in unnoticed —
verified by adding the cron back and watching the test go red. The docs' four-tier
table renames the tier accordingly; calling it "Nightly" when nothing runs nightly
is exactly the kind of comment that outlives its code.
The site had drifted from the product. Every screenshot predated the
v0.5.0 UI redesign, the reading experience shipped no search and no
syntax highlighting, and a third of the pages were internal process
artefacts — migration task lists addressed to agentic workers, a
release runbook, a proposal marked "historical".
Reorganise around the only two people who read this: someone getting
the desktop app running for the first time, and someone reading the
source. Five sections replace nine — start / desktop / im / cli /
internals — and the pages that served neither reader are gone.
Site rewrite:
- Palette lifted from the desktop app's 「纸·墨·印」 themes, so the
site and the product read as one thing. Light mirrors 纯白, dark
mirrors 墨夜, and dark mode exists at all now.
- Fonts are self-hosted. The old @import from Google Fonts is
unreachable from mainland China, which left every heading in a
fallback serif; it also only requested weight 600 while the CSS
asked for 900, so Latin and CJK in the same heading disagreed.
- Docs were shipped as one 968KB manifest downloaded on every page
view. Split into a 32KB index plus one lazily imported chunk per
page; the entry bundle is now 101KB gzipped.
- Add search, syntax highlighting, per-route meta with canonical and
hreflang, a sitemap, and an error boundary. Replace the 44vh
mobile sidebar with a drawer.
- Image dimensions are read at build time and written into the tag,
so lazy images reserve their space instead of collapsing.
Screenshots are recaptured from a real v0.5.0 build against a clean
demo project, with tokens, QR codes and paired accounts redacted.
The previous set is deleted rather than kept alongside.
Routes follow file paths, so the restructure would have broken every
inbound link; 37 old paths redirect, in both languages. The PR policy
gate and CODEOWNERS also hardcoded docs/guide/contributing.md.
Verified: check:docs 78 pages / 323 links / 0 problems, check:policy
127 pass. Walked every route at 1440 and 390 in both themes for
overflow, contrast, keyboard reachability and focus management.