Releases: wwppee/pilot
Release list
v0.7.0 — /workflows MVP (reusable agent workflow templates)
v0.7.0 — /workflows MVP (reusable agent workflow templates)
First release of the workflow concept that replaces v0.4's
L1/L2/L3/L4 capability layers. The biggest product pivot
since v0.4: instead of "absorb an npm package and file it
under L1 or L2", the user now composes sequences of
LLM-powered steps in a visual editor. Each step holds its
own model config (provider + model + key ref + system
prompt + tools), edges describe the data flow, and the
user can save the result as a reusable template.
This is a major version bump because the product
position changed, even though no field on a /compose
connection changed.
What you can do
- Open
/workflowsand see every saved workflow as a
card (name, description, step count, connection count,
updated time). - Click + New workflow, type a kebab-case id
(e.g.research-and-test), hit Enter → empty editor
opens, ready to add steps. - Each step has its own form: name, model (provider +
model name), system prompt, input template, output var,
tools, on-failure strategy (stop / skip / retry /
escalate). - Connect steps with edges (each step's "Connect to…"
button opens a dropdown of the other steps). The SVG
preview on the right shows the data flow live. - Auto-layout button writes back a BFS-computed
layout to each node'spositionso the preview
survives reload. - Save is disabled until you change something; the
status text shows "Saved at {time}" or "•" (unsaved). - Duplicate loads + saves under a new id; the new
editor opens on the copy. - Delete with a confirm dialog.
What you CAN'T do yet (v0.7.1+)
- Drag-and-drop on the SVG preview. The form is the only
way to edit a step today. - Run the workflow. The runtime (drive a pi session
through the node sequence) is v0.7.3. - Field-level data mapping on edges. Today the whole
outputVaris bound.
Files
| Area | Files |
|---|---|
| Data model | web/src/lib/types.ts (+5 types) |
| Server core | src/core/workflow.ts (new, ~250 lines) |
| Service + API | src/core/service.ts, service-impl.ts, server.ts |
| Browser API | web/src/lib/pilot-browser.ts, pilot.ts |
| List page | web/src/app/workflows/page.tsx, WorkflowListView.tsx |
| Editor | web/src/app/workflows/[id]/page.tsx, WorkflowEditor.tsx |
| Styles | web/src/app/workflows/workflow.css |
| i18n | web/src/lib/i18n/{types,dict.en,dict.zh}.ts |
| Nav | web/src/components/NavLinks.tsx (1 new entry) |
| Tests | test/unit/workflow.test.ts (9), web/tests/workflow-layout.test.ts (7) |
How to try
cd ~/path/to/pilot
git pull origin main
git checkout v0.7.0
pilot dashboard
# open http://localhost:<port>/workflows
# click "+ New workflow" → type "research-and-test" → Enter
# editor opens, click "+ Add step" a few times, fill in model + prompt
# click "Connect to…" on each step to chain them
# click "Auto-layout" to tidy the preview
# click "Save"
# close the editor, go back to /workflows, your workflow is thereStats
- root: 622/622 ✓ (+9 in
workflow.test.ts) - web: 245/245 ✓ (+7 in
workflow-layout.test.ts) - i18n: 0 placeholder mismatches across 1015 shared keys
- format:check root + web: ✓
- lint: ✓
- tsc: ✓
- production build: not run (UI / CRUD only)
v0.6.23 — /compose mobile layout hotfix (P1 from user testing)
v0.6.23 — /compose mobile layout hotfix (P1 bug)
User reported (with screenshot) that /compose becomes unusable at
viewport widths < 1024px: the sidebar's natural content height
pushed the canvas + inspector off-screen, leaving only the session
list visible.
What changed (CSS-only)
- At <1024px,
.compose-gridswitches fromdisplay: gridto
display: flex; flex-direction: column; height: calc(100vh - 200px). - Sidebar capped at
35vh(a search bar + filter + a scrollable
items list, more on scroll). - Canvas gets
flex: 1; min-height: 0so it fills the remaining
space and shrinks to fit instead of overflowing. - Inspector is unchanged (already
position: fixedon mobile,
removed from the flex flow). - Desktop layout (≥1024px) is unchanged — original 3-column
grid preserved.
How to verify
git pull origin main
git checkout v0.6.23
pilot dashboard
# Resize the browser window below 1024px wide
# You should see: toolbar at top, sidebar (capped), canvas (the
# bulk of the visible area), and the inspector bottom-sheet
# unchanged at the bottomLesson
- Unit tests + tsc + lint verify the code works, not that the user
can see what they need to see. Headless tests don't exercise
rendered layout. - "Tests pass ≠ UI works." Add a smoke checklist for any
viewport-sized page: render at 800×600, 1024×768, 1440×900 and
verify all primary panels are visible.
Thanks to the user for the screenshot — that's the only reason this
bug got fixed.
v0.6.22 — useHistoryStack hook extracted from ComposeBoard.tsx
v0.6.22 — useHistoryStack hook extracted from ComposeBoard.tsx
The first slice of the long-deferred "ComposeBoard hooks/state 抽离"
backlog item. The undo/redo stack is the most self-contained piece
of ComposeBoard state — it only reads state (passed in), writes
to setState / setSelectedId / announce (all passed in), and
consumes the pure applyEntry / invertEntry functions that
already live in lib/compose-history.ts. So it's the lowest-risk
extraction: the behaviour is unchanged, the public surface is
mechanical, and any regression is caught by a new dedicated test
suite.
What got extracted
- The
{ past, future }state. - The
commitcallback (apply + push entry + announce label). - The
undo/redocallbacks (apply inverted / forward entry,
push onto the other stack, announce). - The "coalesce arrow-key moves" logic from
moveBlock— the
single thing the lib version didn't know about. - The "clear history on wholesale canvas replacement" path
(load board / import JSON / reset canvas).
New hook surface
const { history, commit, pushEntry, pushOrMergeMoveEntry,
clearHistory, undo, redo, canUndo, canRedo }
= useHistoryStack({ state, setState, setSelectedId, announce, t });The 5 entry-point methods map to 5 distinct use cases:
| Method | Used by | Why a separate method |
|---|---|---|
commit |
20+ callbacks (connection edits, addBlock, connect, disconnect, ...) | Standard "I have a before/after transition to record" path. |
pushEntry |
endBlockDrag |
State already mutated; recording only the final delta. |
pushOrMergeMoveEntry |
moveBlock (arrow-key handler) |
Holding an arrow key fires many moveBlock calls; merge into ONE undo step. |
clearHistory |
loadBoardFromServer, importJson, resetCanvas |
Wholesale canvas replacement. |
undo / redo |
keyboard handler, toolbar buttons | Apply inverted / forward entry, push onto the other stack, announce. |
Files touched (8)
| Area | Files |
|---|---|
| New hook | web/src/app/compose/use-history-stack.ts (209 lines, well-commented) |
| Refactored | web/src/app/compose/ComposeBoard.tsx (-40 lines net) |
| Tests | web/tests/use-history-stack.test.tsx (12 new tests, 516 lines) |
| Docs | CHANGELOG.md, AGENTS.md |
| Versions | package.json, web/package.json (both → 0.6.22) |
File size
ComposeBoard.tsx: 2184 → 2144 lines (-40). The drop is
smaller than the extracted code because theuseHistoryStack
call site + explanatory comments take ~30 lines. The
cognitive drop is bigger — the commit / undo / redo
triplet is now testable in isolation.
Backlog after v0.6.22
v0.6.23+— MoreComposeBoard.tsxextractions: drag/drop,
server persistence, keyboard shortcuts, view state. The
pattern established here (custom hook that owns a slice of
state and exposes a small public surface) should make the
next 4-5 extractions mechanical, but each is its own release.- block-center avoidance for orthogonal routes (real A* grid
router) — still the v0.6.20 followup, never started - per-direction palette — still the v0.6.19 followup, never started
v0.6.21 — cleanup batch (AGENTS.md + empty state + placeholder audit)
v0.6.21 — Cleanup batch (AGENTS.md + empty state dedup + placeholder audit)
A small user-flagged cleanup release that closes three leftover
P2/P3 items. No new features, no schema bump; this is a
hotfix-shaped release that nudges a few long-standing paper cuts
and adds a regression test so the placeholder audit doesn't
drift again.
What's in the box
- AGENTS.md version drift fixed. Two places (the "30 秒
判断题" header and the "Last updated" footer) saidv0.6.14
even though the project is at v0.6.20. Bumped both. /usageempty state no longer duplicates its hint. The
usage.emptytitle used to re-state the "run pi with a real
model" message thatusage.empty.hintalready said, so the
user saw the actionable message twice. Now the title is a
short descriptive label and the hint carries the actionable
step alone.- Placeholder parameter audit closed. v0.6.16 closed 8 of
15 historical placeholder mismatches between en and zh but
punted the rest. v0.6.21 closes the last 7:- 2 hardcoded-
"1"in en singular keys (blockCount.one,
packageCount.one) — both now use{n}like their
*.othercounterparts - 5 en-only plural-suffix placeholders (
{s}/{es})
— dropped in favor of always-plural English - 1 zh missing
{n}(tools.subtitle) — added
- 2 hardcoded-
- Regression test added.
tests/i18n.test.tsnow has a
placeholder consistency across localesblock that walks
every key in both en and zh, asserts the placeholder sets
match, and dumps the full offender list on failure.
Files touched (7)
| Area | Files |
|---|---|
| Docs | AGENTS.md (2 version refs), CHANGELOG.md (v0.6.21 entry) |
| i18n | web/src/lib/i18n/dict.en.ts (5 strings), web/src/lib/i18n/dict.zh.ts (1 string) |
| Tests | web/tests/i18n.test.ts (1 new describe block) |
| Versions | package.json, web/package.json (both → 0.6.21) |
Backlog after v0.6.21
v0.6.22— block-center avoidance (real A* grid router on
top of the v0.6.20routeenum) + hooks 抽离- per-direction palette (e.g. "all forward connections get this
color") — deferred; per-edge is the v0.6.19 minimum
v0.6.20 — per-edge routing style (curve / orthogonal)
v0.6.20 — Per-edge routing style (curve / orthogonal)
Each /compose edge can now pick between the original cubic bezier
("curve", the v0.6.19 look) and a 3-segment right-angle polyline
("orthogonal", Visio / Lucidchart style). The choice is per-edge,
so a single board can mix both: a "main flow" line curves smoothly
while a "control plane" line goes through right angles.
What you get
- Inspector gets a 5th control: a
<select>with the two
options. The label ("Routing" / "路径") and option labels
("Curve" / "曲线" and "Orthogonal" / "直角") are i18n'd. - Canvas uses a single SVG
<path>for both styles. The
curvecase is the original cubic bezier; theorthogonal
case is a 3-segment polyline (right → up/down → right). Both
end with a horizontal segment, so the v0.6.18 marker logic
(markerStart/markerEndwith
orient="auto-start-reverse") keeps working without any marker
changes — the orthogonal case's arrow head still sits on the
last horizontal segment pointing right (or left for backward).
Scope of v0.6.20 (deliberately minimal)
- The two routing styles are the v0.6.20 surface — pick one,
the renderer takes care of the rest. - Block-center avoidance is out of scope. A connection that
goes through other blocks in the middle will still do so with
"orthogonal". A real A* grid router (or visibility-graph) on
top of this enum is a separate concern and would need its own
release. Theroutefield is designed to extend (a future
obstacleAvoidance?: booleancan layer on top without a v7
bump).
Files touched (15)
| Area | Files |
|---|---|
| Schema | web/src/lib/types.ts, src/core/compose-boards.ts (zod enum + v6 version union) |
| Compose state | web/src/lib/compose-history.ts, web/src/app/compose/ComposeBoard.tsx |
| UI | web/src/app/compose/Inspector.tsx, web/src/app/compose/ConnectionPath.tsx |
| i18n | web/src/lib/i18n/types.ts, web/src/lib/i18n/dict.en.ts, web/src/lib/i18n/dict.zh.ts (+4 keys) |
| Docs | CHANGELOG.md, docs/roadmap.md |
| Tests | 3 schema + 4 history + 1 backward-compat (v5↔v6 round-trip) |
Backward compat
route is optional in the schema and omitted by default —
same pattern as v0.6.18's dir and v0.6.19's color. A v0.6.19
board saved before this release round-trips through v0.6.20
byte-identical. The schema is now v6; v1 / v2 / v3 / v4 / v5 boards
all continue to load.
Why a minor bump
Same hotfix / additive-feature precedent as v0.6.6, v0.6.13,
v0.6.17, v0.6.18, v0.6.19: v0.6.20 is purely additive (every
v0.6.19 board still opens and edits fine), so a minor bump is in
line with the project's release conventions.
Backlog after v0.6.20
v0.6.21— block-center avoidance (real A* grid router on
top of the v0.6.20routeenum) + hooks 抽离- placeholder parameter audit (15 keys remain hardcoded)
- per-direction palette (e.g. "all forward connections get this
color") — deferred; per-edge is the v0.6.19 minimum
v0.6.19 — per-edge connection color (hex picker)
v0.6.19 — Per-edge connection color (hex picker)
Each /compose edge can now pick its own color. The Inspector gets a
native color swatch next to the existing label / kind / direction
controls, and the SVG line + arrow head render in the picked color
through the existing currentColor cascade — no new marker shapes.
What you get
- Inspector gets a 4th control:
<input type="color">for the hex
swatch, plus a small↺reset button (visible only when a color is
set). Click reset to drop the override and fall back to the theme
accent — same as a v0.6.18 edge. - Canvas threads the color through
style.coloron the wrapping
<g>. The line stroke and the marker fill both consume
currentColor, so the single style change cascades to both ends.
Selected vs unselected markers still differ via the existing
markerIdswap; the color is independent of selection state. - Format is hex-only —
#rgb/#rgba/#rrggbb/#rrggbbaa.
Named colors andrgb()/hsl()are deliberately rejected so the
"use theme accent" semantic stays clean (a user who wants theme
default should leave the field empty, not set a color that
coincidentally matches).
Files touched (15)
| Area | Files |
|---|---|
| Schema | web/src/lib/types.ts, src/core/compose-boards.ts (zod regex + v5 version union) |
| Compose state | web/src/lib/compose-history.ts, web/src/app/compose/ComposeBoard.tsx |
| UI | web/src/app/compose/Inspector.tsx, web/src/app/compose/ConnectionPath.tsx |
| i18n | web/src/lib/i18n/types.ts, web/src/lib/i18n/dict.en.ts, web/src/lib/i18n/dict.zh.ts (+5 keys) |
| Docs | CHANGELOG.md, docs/roadmap.md |
| Tests | 2 schema + 4 history + 2 backward-compat (v4↔v5 round-trip) |
Backward compat
color is optional in the schema and omitted by default —
same pattern as v0.6.18's dir field. A v0.6.18 board saved before
this release round-trips through v0.6.19 byte-identical (no
color: undefined floating around, no migration script needed).
The schema is now v5; v1 / v2 / v3 / v4 boards all continue to load.
Why a minor bump
Same hotfix / additive-feature precedent as v0.6.6, v0.6.13, v0.6.17,
v0.6.18: v0.6.19 is purely additive (every v0.6.18 board still opens
and edits fine), so a minor bump is in line with the project's
release-please conventions.
Backlog after v0.6.19
v0.6.20— auto-route (avoid overlapping edges on the canvas)v0.6.21— hooks 抽离 (move web/src/app/api hooks into src/hooks/)- placeholder parameter audit (15 keys remain hardcoded)
- per-direction palette (e.g. "all forward connections get this
color") — deferred; per-edge is the v0.6.19 minimum and the
schema'scolor?: stringfield already supports a future palette
expansion without a v6 bump.
v0.6.18 — connection direction: forward / backward / bidirectional
v0.6.18 — connection direction: forward / backward / bidirectional
Connections are no longer one-way. Each connection now has an explicit
dir: "forward" | "backward" | "bidirectional", rendered with SVG arrow
markers so the topology reads at a glance.
What you get
- Inspector shows a third select next to label:
A → B/B → A/A ↔ B
(arrow glyphs are locale-stable, so no translation is needed). - Canvas renders
marker-start(backward) andmarker-end(forward) for
the same edge; bidirectional gets arrows on both ends. The same marker id
is reused withorient="auto-start-reverse"— no new asset. - Up to 3 connections per unordered node pair — one per direction. Dedupe
is keyed on the triple(from, to, dir). forwardis the default, and is stored as the absence ofdir(not as an
explicit"forward"field), so a v0.6.17 board round-trips through v0.6.18
byte-identical.
Files touched (15)
| Area | Files |
|---|---|
| Schema | web/src/lib/types.ts, src/core/compose-boards.ts |
| Compose state | web/src/lib/compose-history.ts, web/src/app/compose/ComposeBoard.tsx |
| UI | web/src/app/compose/Inspector.tsx, web/src/app/compose/ConnectionPath.tsx |
| i18n | web/src/lib/i18n/types.ts, web/src/lib/i18n/dict.en.ts, web/src/lib/i18n/dict.zh.ts (+5 keys) |
| Docs | CHANGELOG.md, docs/roadmap.md |
| Tests | 3 unit + 1 history test updated for v3/v4 version unions |
Why a minor bump (and not a patch)
Per the project's hotfix precedent (v0.6.6, v0.6.13, v0.6.17), additive
schema/UI features ship as minor bumps. v0.6.18 is additive: every v0.6.17
board still opens and edits fine.
Backlog after v0.6.18
v0.6.19— connection color (per-edge / per-direction palette)v0.6.20— auto-route (avoid overlapping edges on the canvas)v0.6.21— hooks 抽离 (move web/src/app/api hooks into src/hooks/)- placeholder parameter audit (15 keys remain hardcoded)
v0.6.17 — /usage range picker active label is now white (1-line visual hotfix)
v0.6.17 — /usage range picker active label is now white (1-line visual hotfix)
A follow-up to v0.6.16: the user reported that the active range button label read as "green and unreadable" on their display.
Root cause
v0.6.16 used text-[var(--bg)] (#0b0d10) on bg-[var(--accent)] (#79c0ff) — both colors sit in the same dark-blue value range, and at the small text-xs font size the contrast degrades to the point where the label visually merges with the active pill on many display profiles.
Fix
- Active range label is now
text-white(nottext-[var(--bg)]). Pure white on the saturated#79c0ffbackground passes WCAG AA on every display profile (≥ 4.5:1 contrast for the 12px label size). - Non-active labels keep their
text-[var(--text-muted)]so the visual hierarchy "muted → active" still reads correctly.
Stats
- root tests: 543/543 ✓ (unchanged)
- web tests: 214/214 ✓ (unchanged)
- format:check root + web: ✓
- tsc root + web: ✓
Lesson
i18n complete ≠ UX complete. v0.6.16 shipped with all i18n keys wired up but the active button's color combination turned out to be unreadable on some displays. Always let the user actually look at the result before claiming "done" — tsc + format + lint + tests doesn't catch a contrast bug.
Commit
bc3bb02v0.6.17: /usage range picker active label is now white (visual hotfix)
v0.6.16 — 6 more i18n cleanups + 1 UX polish (4-button range picker)
v0.6.16 — 6 more i18n cleanups + 1 UX polish (4-button range picker)
A focused cleanup release that closes a small user-reported backlog of i18n hardcoded strings + one toolbar visual issue flagged from the /usage page screenshot.
P1 — i18n hardcoded strings (4 fixes)
profiles/[name]/page.tsx:61"✓ Created" banner. Was<div>✓ Created <code>{name}</code>.</div>— the leading "✓ Created" string was raw English. Now usesRichTwithprofiles.createdBanner = "✓ Created {name}."(en) /"✓ 已创建 {name}。"(zh).profiles/[name]/page.tsx:83not-found error card. Was<div>Profile <code>{name}</code> not found.</div>. NowRichTwithprofiles.notFound = "Profile {name} not found."/"未找到 Profile {name}。".profiles/[name]/page.tsx:195env section heading. Was<h2>env (read-only — edit TOML directly)</h2>— English-only. New keyprofiles.envHeading(en/zh).policy/page.tsx:150load error title. Was<h2><T k="error.couldntLoad.title" />: policies</h2>—<T>part was i18n'd but the trailing raw: policiesrendered as English even in zh, producing "加载失败: policies". Folded the noun into a single i18n key:policy.loadErrorTitle.
P2 — relative-time suffix hardcoded English (1 fix)
Inspector.tsx:726-737formatRelative()was English shorthand only. Returned${sec}s ago/${min}m ago/ etc. — the postfix "ago" was always English. Now each suffix is an i18n key:compose.inspector.time.{second,minute,hour,day,month,year}. The helper is module-level so it can'tuseT(); the callers (the session-detail inspector block) pass theirtin explicitly:formatRelative(iso, t).
P3 — translation consistency (1 fix)
dict.zh.ts"fork" 翻译不一致.try.session.forkHerewas "从此处派生" andtry.hint.forkFromHerewas "从这里分叉". Aligned to "从此处派生" in both.
P3 — placeholder parameter drift (deferred)
dict.zh.tshas 15 keys where the placeholder list doesn't matchdict.en.tsexactly. None of these impact the rendered output. Cleaning this up would require auditing every call site; punted to v0.6.17+ (or whenever someone next adds a new locale).
UX — /usage range picker
- Active tab no longer "shrinks". Added
min-w-[5rem]so all four pills share a minimum width (5rem fits the longest current label in any locale; longer future labels grow as needed). - Active state visual +
aria-current="page". The active button now also getsfont-semibold.aria-current="page"makes the active tab discoverable to screen readers. - Non-active hover gains color + bg.
text-[var(--text-muted)] hover:text-[var(--text)] hover:bg-[var(--surface-2)]— hover gives both a brighter text color and a subtle bg fill.
Stats
- root tests: 543/543 ✓ (unchanged)
- web tests: 214/214 ✓ (unchanged)
- format:check root + web: ✓
- tsc root + web: ✓
- production build (
next build): ✓
Deliberately NOT done (v0.6.17+ backlog)
- multiple connections (A↔B 双向)
- connection color 自定义
- auto-route 避开 block 中心
- ComposeBoard.tsx hooks/state 抽离
- full placeholder parameter audit (15 keys) — see P3 above
Commit
1ee471fv0.6.16: 6 more i18n cleanups + /usage 4-button UX polish
v0.6.15 — pilot forge absorb now lazy-inits ~/.pilot/capabilities/ + clearer EPERM error
v0.6.15 — pilot forge absorb now lazy-inits ~/.pilot/capabilities/ + clearer EPERM error
A user-reported hotfix: pilot forge absorb <pkg> failed with EPERM: operation not permitted, mkdir '/Users/feng/.pilot/capabilities' on macOS sandboxed shells (Cursor / VSCode devcontainer / sandboxed Terminal). The directory was only ever created by pilot init, and users who skipped init hit a bare permission error with no actionable hint.
The fix in one line
forgeAbsorb now ensures ~/.pilot/capabilities/ exists before writing, instead of relying on the user having run pilot init first.
P0 — silent failure on a real-user path (1 fix)
forgeAbsorbnow lazy-inits the capabilities directory. NewensurePilotCapabilitiesDir(home)helper incore/types.tsdoes themkdir recursive: truebefore the per-idcapDirmkdir. Idempotent — a no-op when the directory already exists, so the hot-path cost is one syscall for users who have runpilot init(the common case). Users who skipped init and jumped straight to absorb will now have the directory materialised by absorb itself.- Actionable EPERM/EACCES error message. The previous error was the raw
Failed to write /Users/feng/.pilot/capabilities/caveman-code/capability.json: EPERM: operation not permitted, mkdir '/Users/feng/.pilot/capabilities'— technically correct but gave no hint about why or what to do. The new error reads:Cannot write /Users/feng/.pilot/capabilities/caveman-code/capability.json: operation not permitted (EPERM). Your shell is sandboxed or otherwise blocked from writing to ~/.pilot/. Run \pilot init` from a non-sandboxed Terminal, or check that /Users/feng/.pilot/capabilities is accessible.The detection checkserr.code === "EPERM" || "EACCES"` specifically — generic IO errors (disk full, read-only volume, etc.) still get the original bare message because they don't have a one-line "do this" fix.
Testability hook
ensurePilotCapabilitiesDirreads an optionalglobalThis[Symbol.for("pilot.test.ensureCapabilities")]override before falling through to the realmkdir. Production code never sets this; tests inforge.test.tsuse it to inject a synthetic EPERM failure without having to mock the read-only ESMnode:fs/promisesmodule. The hook is documented in the helper's docstring.
Tests
- root: 543/543 ✓ (was 541 in v0.6.14; +2
forgeAbsorbregression cases — one for the lazy-init happy path, one for the EPERM error message) - web: 214/214 ✓ (unchanged — the fix is server-side core)
- format:check root + web: ✓
- lint (root
eslint src test --max-warnings 0): ✓ - tsc root + web: ✓
- production build (
next build): ✓
Operator note for the reporting user
If you're reading this because pilot forge absorb is failing in your shell: open a non-sandboxed Terminal (/Applications/Utilities/Terminal.app) and run pilot init once. That creates ~/.pilot/capabilities/ in a context where your shell actually has write permission. Subsequent pilot forge absorb calls will work, even from sandboxed shells (v0.6.15 will lazy-init the directory on first use, so re-running pilot init is no longer required).
Deliberately NOT done (v0.6.16+ backlog)
- multiple connections (A↔B 双向)
- connection color 自定义
- auto-route 避开 block 中心
- ComposeBoard.tsx hooks/state 抽离
Commit
ff1a9fcv0.6.15: forge absorb now lazy-inits ~/.pilot/capabilities/ + clearer EPERM error