Skip to content

Peak Code v0.4.0

Choose a tag to compare

@github-actions github-actions released this 18 Sep 01:11
· 0 commits to 0cf628be659c3d9bec62b3a6adef470659d900c2 since this release

The changes in this release, from CHANGELOG.md:

Added

  • The agent can use this Mac: the computer tool drives native apps the way a person does. get_state reads 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 — and act presses, focuses, raises or writes a value on that ref without moving your pointer or taking your focus. click/type/key post 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. A screenshot comes back as an image with the display's scale already accounted for, so the point you read off the picture goes straight into click. 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 in Contents/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 — scroll with a direction and a distance, drag as one gesture along a path (a slider, a reorder, a selection, a canvas) with modifiers if the gesture needs them, and click with double and right-button forms — and the clipboard is part of the desktop rather than a secret behind type: read_clipboard gets what the user or an app just copied, write_clipboard hands text to an app that wants a paste, and type can take strategy: \"paste\" to enter long text or CJK as one ⌘V instead of hundreds of keystrokes (the user's clipboard is put back afterwards). list_windows reports every on-screen window with bounds a click can be aimed at, displays reports 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 an internal_error that 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 as mentions exactly the way an @name token 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 after sessionIdleHours idle (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 and POST /api/im/task by 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 an https://*.trycloudflare.com address 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 real enabled transitions 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 install understands — 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 same settings.json the CLI reads and a package installed here is visible to pi 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 /reload picks it up in an open thread. This is the surface that makes third-party packages like pi-crew usable in Peak Code: its six crew_* subagent tools, orchestration skill, and /pi-crew-plan / /pi-crew-review prompt 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.ts turns oh-my-pi's 100 pi TUI themes into Peak Code ChromeTheme seeds (apps/web/src/theme/oh-my-pi.seed.generated.ts), selectable in the appearance picker as omp-*; 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, so read_skill and /skill: resolve them with no network. Both are additive: the skills follow the existing AGENT_SKILL_PACKS switch (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_skill refuse it, so a skill you do not want cannot be listed or opened. It is stored as AGENT_DISABLED_SKILLS (empty by default — nothing changes until you use it) and keys on the skill's directory name, the same id read_skill resolves. Three composer commands make the ported skills usable without describing them by hand: /compress drives semantic-compression, /prompt-review drives system-prompts, and /review-prs runs 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.warning naming 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_task agent 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-skills pack 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_comment agent 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 / !shell key hint. ([#19], [#20])

  • Add/edit-model dialog: model id, context window (default 1000000), max output tokens (default 128000) 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 migration 040_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.0 to 0.85.1, which is what packages built against current pi require (pi-crew needs >=0.84.3) and what the pi CLI on the same machine already runs. The migration replaces the removed AuthStorage + ModelRegistry.create pair with the async ModelRuntime in the adapter, the provider health probe, git text generation, and the "test connection" probe, and reads completion through the @earendil-works/pi-ai/compat entrypoint.
  • Extensions no longer need a warning to explain themselves: pi supplies a no-op UI context with ctx.hasUI === false, so ctx.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_task tool 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.dev across the Electron dev launcher, the Windows AUMID and electron-builder's appId, 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 peakcode instead of t3, 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 in CONTRIBUTING.md, CONTRIBUTING.zh.md and AGENTS.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 -L is 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.json by 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 .zshrc loads 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. Migration 041_AutomationSchedules rebuilds 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. The thread.handoff.create command 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 afterPack hook (apps/desktop/scripts/electron-builder-after-pack.cjs) rather than in electron-builder's mac.sign hook, 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 bundled workspace: 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 workspaces globs 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-runtime bundles are re-patched when LAUNCHER_VERSION changes, 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.json that pi loads: video/pdf are 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