Repository navigation
Peak Code v0.4.0
The changes in this release, from CHANGELOG.md:
Added
-
The agent can use this Mac: the
computertool drives native apps the way a person does.get_statereads the accessibility tree of the app in front — each control's role, name, position, size and the actions it actually supports, with a ref to act on — andactpresses, focuses, raises or writes a value on that ref without moving your pointer or taking your focus.click/type/keypost real input for the targets a tree cannot express (a canvas, a custom-drawn control) and say so, because they do move the cursor and type into your focus. Ascreenshotcomes back as an image with the display's scale already accounted for, so the point you read off the picture goes straight intoclick. Observation is bounded twice so a wedged app cannot hang the socket, element actions through the accessibility API are preferred over coordinates, and a ref from an older observation is refused with an explanation rather than quietly landing on whatever now sits at that position. The work runs in a native helper of its own — a small Peak Code Computer Use app, which is what macOS files the Accessibility and Screen Recording grants against rather than Peak Code itself — installed at a fixed path, launched through LaunchServices, and rebuilt only when its own source changes, so those two switches in System Settings are a once-per-machine decision that survives every rebuild. A packaged build ships the helper inContents/Resources/computer-use; a checkout compiles it on first launch; either way the installed bundle is fingerprinted and left untouched when it is already current. When a permission is missing, the model is told exactly which one and to stop until the user grants it. The same tool moves through a window the way a person does —scrollwith a direction and a distance,dragas one gesture along a path (a slider, a reorder, a selection, a canvas) with modifiers if the gesture needs them, andclickwith double and right-button forms — and the clipboard is part of the desktop rather than a secret behindtype:read_clipboardgets what the user or an app just copied,write_clipboardhands text to an app that wants a paste, andtypecan takestrategy: \"paste\"to enter long text or CJK as one ⌘V instead of hundreds of keystrokes (the user's clipboard is put back afterwards).list_windowsreports every on-screen window with bounds a click can be aimed at,displaysreports the displays and their scale factors, and coordinates are handled properly on both counts: a capture carries where it started on screen and the scale of the display it is on, so a point read off a screenshot of a window on a second monitor lands where it looks like it should — the earlier code divided by the main display's scale and ignored the capture's origin, which put clicks off by the window's distance from the screen's corner. The helper now answers a bug in one of its own handlers with aninternal_errorthat names it instead of taking the process down with it. macOS only. (.docs/computer-use.md) -
Plugins attach to a turn, several at a time. A plugin card in
/plugins— or one of the installed tiles — opens a detail view (long description, capabilities, the skills it ships, example prompts) whose Use plugin / 立即使用 action puts the plugin on the composer of the chat you were last in, opening a new chat when there is none. The composer's+menu lists the discovered plugins too, as checkboxes, so one turn can carry more than one, and each shows up as a removable chip. Chips live on the composer draft next to images, pasted screenshots and assistant selections — attaching a plugin never costs you an attachment — and the turn carries them asmentionsexactly the way an@nametoken does, deduped so a plugin both named in the prompt and picked as a chip is sent once. (.docs/bundled-plugins.md) -
IM channels: a message sent from a chat app opens (or continues) a thread in the workspace and the answer is sent back into the chat, so the run stays a conversation you can open and continue in the app. Six channel ids: personal WeChat (a QR handshake mints its credentials, then a bridge long-polls outward, so no public URL is needed), Feishu/Lark and QQ over their own outbound sockets, 企业微信 and 微信公众号 as signed Tencent callbacks (those two need public HTTPS), and a generic webhook that carries both directions — the push-only group robots that announce a finished run and the secret-guarded inbound bridge. The bridge keeps its conversation index in SQLite (migration
042_ImConversations), starts a fresh thread aftersessionIdleHoursidle (default 12,0= never), resolves workspace and model per turn because an IM run has no composer, queues a message that arrives mid-turn, and withdraws the channel's own "working on it" notice once the answer lands. Config and status traffic is owner-only — these endpoints can mint a pairing link — while the Tencent callbacks authenticate by signature andPOST /api/im/taskby its own secret, which is mandatory as soon as the server is reachable beyond loopback. Settings → 频道 renders the whole surface: per-channel status, the callback URL to paste into Tencent, Save/Test, the remembered conversations and the recent traffic. (.docs/im-channels.md) -
Phone access without a networking project: a Cloudflare quick tunnel (
cloudflared) publishes the server on anhttps://*.trycloudflare.comaddress so a phone reaches it without port forwarding or a public IP, and a pairing QR opens the workspace — or the conversation that was on screen — on the other device, with the one-time credential in the URL hash so it never reaches a server log. The tunnel is refused unless the server requires authentication (the address is public), acts only on realenabledtransitions so an unrelated settings write cannot rotate the address, and stops itself when the backend shuts down. (.docs/im-channels.md) -
Pi packages under Settings → Pi Packages: install, list and remove the packages
pi installunderstands —npm:@scope/pkg,git:host/user/repo, and local paths — from the app. Installs go through pi's own package manager, so sources land in the samesettings.jsonthe CLI reads and a package installed here is visible topi list. The listing reports what each package contributes (skills, prompts, extensions, themes) and where it was installed; new threads load it on their next start, and/reloadpicks it up in an open thread. This is the surface that makes third-party packages like pi-crew usable in Peak Code: its sixcrew_*subagent tools, orchestration skill, and/pi-crew-plan//pi-crew-reviewprompt templates all run in-process. -
Themes and prompt-engineering skills ported from can1357/oh-my-pi (MIT):
bun scripts/generate-oh-my-pi-theme-seeds.tsturns oh-my-pi's 100 pi TUI themes into Peak CodeChromeThemeseeds (apps/web/src/theme/oh-my-pi.seed.generated.ts), selectable in the appearance picker asomp-*; and its two prompt-engineering skills (system-prompts,semantic-compression) ship as a bundled in-tree payload written to the shared skill library at startup, soread_skilland/skill:resolve them with no network. Both are additive: the skills follow the existingAGENT_SKILL_PACKSswitch (the same one that governs the default pack, so turning it off drops both), and the themes have no switch of their own — they are extra options in the picker, and not choosing one changes nothing. (.docs/oh-my-pi-integration.md) -
Skills have a per-skill switch, and the ported content has commands. Settings → Skills now carries a toggle on every local skill: switching one off keeps its files on disk but removes it from the agent's per-turn skill list and makes
read_skillrefuse it, so a skill you do not want cannot be listed or opened. It is stored asAGENT_DISABLED_SKILLS(empty by default — nothing changes until you use it) and keys on the skill's directory name, the same idread_skillresolves. Three composer commands make the ported skills usable without describing them by hand:/compressdrivessemantic-compression,/prompt-reviewdrivessystem-prompts, and/review-prsruns this repository's own gate (bash scripts/pr-review.sh <PR number|--all|nothing to auto-detect>, reporting its verdict). All three insert their instruction into the composer for you to read before sending, like/subagents. (.docs/oh-my-pi-integration.md) -
Extension failures are reported in the thread instead of being dropped: every extension that fails to load, and every extension handler that throws while a turn runs, now emits a
runtime.warningnaming the extension and the event. Extension load errors were previously invisible unless the model happened to notice a missing tool. -
Scheduled tasks (Automations,
/automations): a plan plus one instruction plus the workspace it runs in. When the plan comes due, the server opens a real thread in that workspace and sends it the instruction, so every run leaves a conversation you can read and continue. Plans are once, daily and weekly, evaluated as wall-clock times in an IANA timezone; a task can be paused, resumed or run by hand, a one-off switches itself off once it has run, and a trigger missed by more than six hours is rolled forward instead of firing late. Each task carries the composer's interaction mode (Agent / Plan / Goal) into its runs. (.docs/automations.md) -
The
schedule_taskagent tool: "every morning, summarise what changed here" now schedules the task from the conversation itself. It resolves the workspace from the thread it was called in, reports the task id and the next run instant back to the model, and is off in Plan mode. (.docs/automations.md) -
The engineering workflow is the default for every request, and it is installed rather than assumed. The system prompt now carries a workflow section (DEFINE → PLAN → BUILD → VERIFY → REVIEW → SHIP, with the skill to read at each stage and the judgement call for when a task does not need the process at all) plus the list of the machine's other skills. The
addyosmani/agent-skillspack backs it — 25 skills, MIT — and the server keeps it installed at the system level through the official CLI (npx skills add … --global --agent universal), forked at startup so an offline machine costs a log line and nothing else. Both sections are injected per turn and in every interaction mode, so a fresh thread already has them. (.docs/skills-and-workflow.md) -
The
kanban_commentagent tool and the board's standing instructions: a task dispatched from 进行中 now leaves one comment per finished step on its card (what finished, what proved it, what comes next), because conversation text never reaches the board. The card is resolved from the thread the task was dispatched from, so the model cannot aim a comment at another task, and a conversation that is not a board task is told that nothing was written instead of getting a silent success. Off in Plan mode, no approval prompt. (.docs/skills-and-workflow.md) -
Run modes in the composer: Agent (default), Plan and Goal. The mode travels with the turn and is kept in thread state. Goal mode stores the objective, its acceptance criteria and a token budget, and the harness continues the work across turns (capped by
AGENT_GOAL_MAX_CONTINUATIONS) until the goal is completed, dropped, or out of budget; the composer's goal panel can pause, resume, complete or drop it. ([#20]) -
Model providers under Settings → Model Providers: a provider rail with built-in and custom groups and status dots, an add-provider form with template picker (OpenAI, Anthropic, Google Gemini, OpenRouter, Ollama, DeepSeek, 智谱 AI) and the
ENV_VAR/!shellkey hint. ([#19], [#20]) -
Add/edit-model dialog: model id, context window (default
1000000), max output tokens (default128000) and input kinds (text, image, video, PDF). Model rows show context/output badges. ([#20]) -
Kanban boards, one per project at
.kanban/board.json: 待开始 / 进行中 / 已完成 / 已阻塞 / 归档 columns with drag and drop, agent dispatch when a task lands in 进行中 or is created there, requirement briefs an agent can draft from the title, and a task detail view that merges board comments with the agent's messages from the thread it ran in — including steer and interrupt for a running turn. ([#20]) -
@peakcode/agent-toolkit, the agent harness hosted from the server: tools, plans, goals, approvals, skills, runtime paths, their sqlite store, and migration040_AgentToolkit. ([#20]) -
Settings navigation split (
SettingsNav, with the skills panel among its sections) and chat activity rows for plan/goal/approval steps. ([#20])
Changed
- The kanban board reads like a board now rather than a stack of boxes. Every column header and card carries a status glyph (待开始 dashed, 进行中 half-filled, 已完成 checked, 已阻塞 crossed, 归档 struck), and a card shows its work-item code (
PC-29B8, derived from the task id and the project title), a priority meter, the comment and image counts, the assignee, and how long ago the task moved. The board grew its own left menu — the project switcher plus status / priority / agent-run filters with live counts, each row carrying its own glyph — and a toolbar with a search over titles, requirements and codes, a board↔list switch that renders the same filtered tasks grouped by status, and a "new task" row at the foot of every column. Glyphs, codes, and the priority colours live in one place (lib/kanbanPresentation,components/KanbanPresentation) that the task page, the create form, and the left menu all draw from, so one task looks the same everywhere. The view mode and menu state persist per browser (kanbanUiState), drag-and-drop still resolves its order against the unfiltered column while a filter is on, and column headers keep following the UI language rather than the Chinese names the board file stores. - The vendored pi SDK moves from
0.74.0to0.85.1, which is what packages built against current pi require (pi-crew needs>=0.84.3) and what thepiCLI on the same machine already runs. The migration replaces the removedAuthStorage+ModelRegistry.createpair with the asyncModelRuntimein the adapter, the provider health probe, git text generation, and the "test connection" probe, and reads completion through the@earendil-works/pi-ai/compatentrypoint. - Extensions no longer need a warning to explain themselves: pi supplies a no-op UI context with
ctx.hasUI === false, soctx.ui.*calls are inert rather than fatal. The session warning now says that instead of implying extension behavior is unsupported. ([#20]) - The automation model is now Peak Code's own rather than the ported cron shape: plans are once/daily/weekly with a persisted next-run instant instead of five-field cron expressions, a run ends when its conversation reports back (previously a run was recorded as completed the moment its turn was queued), and the run history shows the outcome with a summary and a link into the conversation.
- Scheduling a task from a chat goes through the agent's
schedule_tasktool instead of the client-side keyword interceptor, which used to swallow the message before it reached the model. - The automations page is rebuilt around the new model: a task description and a workspace picker in the editor, a run history under each task, and react-query polling so scheduled runs appear without a manual reload.
- Documentation covers the new surface: a Scheduled Tasks section in README (English and Chinese) and .docs/automations.md.
- App identity is unified on
com.peakcode.app/com.peakcode.app.devacross the Electron dev launcher, the Windows AUMID and electron-builder'sappId, so one app owns its taskbar grouping, shortcuts and notifications. The macOS About panel and the staged installer metadata now read Peak Code AI, matching the LICENSE holder. ([#20]) - The server CLI command is
peakcodeinstead oft3, matching the published bin name. ([#20]) - Documentation covers the new surfaces: README (English and Chinese),
.docs/runtime-modes.md,.docs/encyclopedia.md,.docs/workspace-layout.md,.docs/scripts.md, and the package tables inCONTRIBUTING.md,CONTRIBUTING.zh.mdandAGENTS.md. ([#20]) - REMOTE.md documents the SSH-tunnel setup for an agent that works on a remote dev box: the server binds its own loopback on the machine that holds the code,
ssh -Lis the only way in, and the browser stays on your laptop. - Startup no longer runs the user's login shell on every launch. The capture that a GUI-launched process cannot inherit — PATH,
SSH_AUTH_SOCK, the Homebrew and XDG prefixes — is written to<baseDir>/userdata/shell-environment.jsonby whichever process probes first (the desktop does, before it spawns the backend) and reused while the shell's startup files (.zshrc,.zprofile,/etc/paths.d, …) are unchanged, with a six-hour upper bound because a PATH can also depend on state no file records. On a machine whose.zshrcloads a plugin manager that is ~1.1s off every start for both the desktop and the standalone server (measured on the packaged build: ~2.2s → ~1.1s to "Peak Code running"), and an entry that is missing, corrupt, or written by an older version simply falls back to probing.
Removed
- The automation script columns (
scriptId,scriptName,scriptCommand), which no code path ever wrote, and the automation templates that were tied to cron expressions. Migration041_AutomationSchedulesrebuilds both automation tables; existing cron-based rows are not converted — see .docs/automations.md. - The client-side provider-to-provider handoff path:
useThreadHandoff(called from nowhere), the target-provider list that could only ever be empty now that Pi is the only provider, and the handoff creation, title and model-selection helpers around them. Thethread.handoff.createcommand stays on the server, where it is tested and still reads the handoff metadata older threads carry. Threads created before this change keep their handoff badge.
Fixed
- Unsigned macOS releases are ad-hoc signed instead of shipping with a bundle whose nested code no longer matches its signature, which is the state macOS reports to the user as a damaged download. The signing happens in the build's own
afterPackhook (apps/desktop/scripts/electron-builder-after-pack.cjs) rather than in electron-builder'smac.signhook, because that hook is only reached once electron-builder has found an identity to sign with — exactly what an unsigned release does not have, so a build that configured one still shipped signed-nothing. The bundle is signed inside-out, the computer-use helper keeps the identifier requirement its Accessibility and Screen Recording grants are filed against, and the result is verified before it is packaged; a failure fails the build instead of publishing the artifact. A Developer ID build is untouched and still notarizes as before. - Desktop packaging (
bun run dist:desktop:*) no longer fails on bundledworkspace:dependencies. tsdown inlines every@peakcode/*package into the server bundle, so they are skipped when staging the production install. ([#20]) - The release smoke test derives its workspace manifest fixture from the root
workspacesglobs instead of a hardcoded list, so a newly added package can no longer break the release-only steps by being absent from the fixture. ([#20]) - Cached
.electron-runtimebundles are re-patched whenLAUNCHER_VERSIONchanges, and each patched bundle keeps its own metadata file, so Dev and Alpha can no longer validate against the other variant's stale patch. ([#20]) - Saving a model in the provider settings no longer invalidates the
models.jsonthat pi loads:video/pdfare stored in a key pi ignores, and cleared fields are dropped from the saved config instead of being written as empty values. ([#20])
Full Changelog: v0.3.0...v0.4.0