Release v1.6.0 - #27
Open
porcelaincode wants to merge 21 commits into
Open
porcelaincode wants to merge 21 commits into
porcelaincode wants to merge 21 commits into
Conversation
sarvam-30b was deprecated upstream, leaving sarvam-105b as Sarvam's only served model. Regenerate models_catalog.json without it and fix up every place that assumed a second, cheaper Sarvam sibling existed: - BYOK provider shortlist + validation probe now use sarvam-105b. - cheapCompletion has no cheap Sarvam tier anymore; callers already swallow failures to '' and fall back to deterministic paths. - the fallback map points sarvam-105b at pier-hybrid, so a Sarvam-side outage lands on a different upstream instead of a dead sibling. - compact.test drops its dependence on sarvam-30b's auto_compact_token_limit (no catalog model sets one now) and exercises the context_window * 0.9 fallback instead. Co-authored-by: Pier Code <no-reply@piercode.com>
… skills and MCP Plugins live in $PIER_HOME/plugins (plus cowork_plugins for the cowork surface) and contribute content dirs that the existing loaders now scan: - `pier plugin install|list|remove|enable|disable` (src/commands/pluginCli.ts). - custom commands from a plugin are namespaced `<plugin>:<name>` so they can never collide with user or project commands. - agents and skills scan plugin dirs between user and project, keeping project > plugin > user precedence on a name collision. - AgentDef carries the source `path` for disk-loaded agents, so the capability inventory can attribute an agent to its origin. Co-authored-by: Pier Code <no-reply@piercode.com>
Ship docx/xlsx/pptx/pdf authoring skills with the binary and seed them into $PIER_HOME/skills on session init, so they are discoverable like any user skill. Seeding is idempotent and never clobbers a user-modified copy. assets/skills/** is the source of truth; src/skills/bundled.ts is generated from it by scripts/gen-bundled-skills.ts. Skills may also declare `when:` activation globs in frontmatter. When a user message references a matching filename, the turn injects a hint that the skill exists — hint only, backed by a cheap filesystem scan. Co-authored-by: Pier Code <no-reply@piercode.com>
…that server A permission rule naming just `mcp__gmail` now matches `mcp__gmail__*`, so a whole connector can be allowed or denied in one rule instead of one rule per tool. Matched as a string prefix on the RULE — never by re-splitting the tool name on `__`, since server names may themselves contain underscores. A rule for `mcp__gmail` must not shadow `mcp__gmail_backup__send`. Co-authored-by: Pier Code <no-reply@piercode.com>
…upport Remote (http) MCP servers can now authenticate: - a passive auth provider attaches stored OAuth tokens and refreshes them silently; when a server demands a NEW interactive grant it throws NeedsAuthError rather than opening a browser mid-init. - `pier mcp auth <name>` runs the interactive flow against a loopback listener and stores the tokens. - configured `headers` now ride every HTTP request via requestInit — without this, header-authenticated servers 401. The manager also tracks per-server state (connected / failed / disabled / needs_auth) plus discovered tools, exposed via snapshot() for capability queries and connector UIs, and honors a `disabled` flag in .mcp.json. Co-authored-by: Pier Code <no-reply@piercode.com>
…sting Task subagents were opaque: the parent saw one tool_begin and nothing until the sub-turn finished. Tools now receive an optional ToolContext emit sink (wired by the serve loop; unset in TUI/exec), and the Task tool streams subagent_begin / subagent_tool / subagent_delta / subagent_end events correlated by a fresh subagent_id. Text deltas are coalesced on a 250ms window so a chatty sub-model can't flood the parent's stream, and tool events carry one-line summaries only — payloads stay in the sub-session. Two guards come with it: - depth: sub-agents may not spawn sub-agents. Unbounded nesting multiplies model calls invisibly while the parent still sees a single tool_begin, so a nested Task is rejected with guidance to do the work directly. - concurrency: a counting semaphore caps concurrent subagents (config subagentConcurrency, default 4). Over-cap calls queue FIFO rather than failing, so a parallel tool batch executes in waves. Co-authored-by: Pier Code <no-reply@piercode.com>
… the thread
runTurn now yields a todo_update event whenever a todo tool succeeds
(both tools return the updated list in res.data.todos), so an embedder
can render a live task list without scraping tool output.
The thread record gains two fields, persisted together since they share
the same record and persistSession literal:
- `todos`: the last task list, restored on resume.
- `surface`: which client surface owns the thread ('chat' | 'code' |
'cowork'), so listings can route a resume back to the right UI. The
surface sticks once set — a resume that omits the field must not
strip it.
Co-authored-by: Pier Code <no-reply@piercode.com>
…urns Extract the dynamic (per-cwd) tool set — the Skill tool plus connected MCP tools — into buildDynamicTools, and expose reloadCapabilities() to close the existing MCP connections, rebuild the set, and re-register it. Safe ONLY between turns: dynamic tools are process-global under the one-session-per-process invariant, so swapping them mid-turn would change the tool set under a running model call. Callers must reject while a turn is running. initSession also seeds the bundled office skills and returns the live McpManager so callers can query per-server status. Co-authored-by: Pier Code <no-reply@piercode.com>
… control protocol
Introduces the desktop's agentic knowledge-work surface and the protocol
additions its client needs. Kept as one change because the session
surface, the tool allowlist and the new control subtypes are threaded
through the same initialize path.
Cowork surface:
- `surface` ('chat' | 'code' | 'cowork') on initialize, persisted to the
thread and inherited on resume unless re-stated.
- cowork appends the knowledge-work prompt overlay, defaults the sandbox
to workspace-write + network, and clamps danger-full-access back to
workspace-write — the OS sandbox stays the backstop.
- cowork requires an explicitly trusted folder: an untrusted cwd fails
with 'untrusted_folder' so the client can run its trust-grant flow.
Tool allowlist:
- `tools` on initialize restricts which tools are offered and callable.
An empty array is meaningful and is NOT the same as omitting the field:
`[]` means no tools at all (the folderless chat mode), absent means the
full set for the mode. serve uses `??`, never `||`, so `[]` survives.
New control subtypes (all additive, feature-detected via
InitializeResponse.capabilities; older servers answer "unsupported
subtype", which clients read as absence):
get_trust, set_trust, get_capabilities, expand_command,
reload_capabilities, set_tool_policy, mcp_authenticate.
Also: custom slash commands expand server-side, so `/name args` works as
a session's first message without a round trip; mcp_authenticate is
deliberately not awaited inline, since awaiting a browser flow would
wedge the inbound frame reader.
Co-authored-by: Pier Code <no-reply@piercode.com>
Thread sync becomes a real two-way mirror instead of local-wins: - Guarded pushes with an expected_count cursor so two devices can't interleave history (bridge 409s → reconcile by item-id prefix). - reconcileThread merges local vs server history: remote-extension adopts server, local-extension keeps local, divergence keeps the longer side and preserves the loser as a conflict copy. - Outbox: failed pushes stamp pendingPush, retried at boot. - Per-thread /sync off opt-out; /share, /unshare, /sessions un-gated. Session sharing gets a reconnect supervisor + takeover: - HostShare.rebind re-announces and re-offers outstanding approvals on a transport drop; UUID request ids; /unshare vs transport-drop split. - Repl reconnect loop with backoff; serve --share auto-mirrors via a TeeIO; work_diff streamed to remote Changes panels. New controls and commands: - serve-protocol `rewind` (truncate history + durable bridge copy). - `pier adopt <id>` (cloud→laptop handoff) and `pier devices`. - set_model / end_session host controls. Mid-turn steering: queued messages are handed to the running turn at the next tool-loop boundary (steering_injected retires the chip by text), instead of waiting for the whole turn to resolve. Co-Authored-By: Pier Code <no-reply@piercode.com>
useConst on a never-reassigned let, and biome-format two functions flagged after the sync/steering commit.
Companion to the bridge fix. In a live session Auto mode blocked every network
command ("This exact command already ran…") when none had ever executed, and
the agent burned ~12 turns rewording the same git push before the user
interrupted.
The blocking itself came from a bridge-side short-circuit, but two problems
live here:
1. The escape hatch never fired. MAX_REPEATED_BLOCKS=3 was added for exactly
this case ("the git push duplicate loop"), but it keyed on exact equality of
the action command. A blocked agent rewords its retry — which the block
message actively invited — so lastBlockedAction reset to 1 every iteration
and the turn spun to max_iterations without ever asking the user. It now
counts consecutive Auto-mode blocks regardless of the command text and
resets on progress (a classifier allow, or any tool that actually ran), so
rephrasing is irrelevant. The hatch's outcome is a user prompt, never an
auto-approve, so firing more often is strictly safer. The block message now
states the action did not run and tells the agent not to retry a reworded
variant.
2. BYOK had no repeat handling at all, despite localClassify.ts documenting
itself as a faithful port kept in sync with the Go classifier. The two paths
had silently diverged on a load-bearing security behavior.
detectRepeat now lives in safety/autoClassify.ts as the single source of truth
for both paths, with the Go side mirroring it. A prior call counts as executed
only when it has a paired, non-error result: blocked attempts are marked
is_error (rendered "[error] " by flattenMessage) and never count, and a match
with no result yet is not "already ran" either — that is the parallel-batch
window, where results for a whole batch are appended only after it completes,
which is exactly when Auto mode classifies. Matching reads the parsed command
field and ignores the agent-authored description.
For BYOK, buildClassifierMessages now renders the same advisory NOTE as the
bridge, byte-for-byte, and only for a genuinely executed repeat.
Tests: a shared fixture (test/fixtures/repeat-detection.json) that both this
implementation and the Go mirror must satisfy; a regression test proving
reworded retries still reach the user prompt; and classifierParity.test.ts
pinning system-prompt byte-identity and NOTE shape across the two languages, so
the drift that produced the BYOK gap cannot recur silently. Each new test was
verified to fail against the reintroduced old behavior.
Co-Authored-By: Pier Code <no-reply@piercode.com>
Formatting only, no behavior change. The Auto-mode repeat-detection commit landed without running the formatter, so CI's lint job failed on both the push and the PR check. Three call sites collapse onto one line (detectRepeat's signature, a buildClassifierMessages call, repeatNote's return) and two useTemplate findings fold concatenation into a single template literal. Both folded strings are byte-identical to before — repeatNote's is pinned by the Go parity fixture, and turn.ts's is the Auto-mode block message. Co-Authored-By: Pier Code <no-reply@piercode.com>
`bun test` runs all 126 files in ONE process and `mock.module` is process-global with no unwind, so the `../src/bridge/models.js` stub in outputFormat.test.ts (file #6 in CI order) replaced the real module for every file after it. Later tests resolved a 1-model catalog (`sarvam-105b`, no `pier-hybrid`) instead of the 15-model bundled one. That silently broke 4 tests on a clean machine — 3 in mentorCommand ("Unknown model: pier-hybrid") and hydrateRemoteThread in discovery. The runner proved it: `fetchModels.toString()` there was the stub body, with `readCache() = null` and localhost:9000 unreachable, so the bundled fallback (15 models, has pier-hybrid) could never be reached. It reproduced only on CI because a populated ~/.pier-bun disk cache masks it locally, and it only surfaced now because the Test step had been skipped behind a failing Lint step in every prior run. The stub was never needed: it only existed to keep runExec offline, and an empty PIER_HOME plus a dead PIER_BASE_URL already forces the bundled catalog, which resolves `sarvam-105b` fine. Drops the mock, so nothing leaks. Also folds the duplicated `_mkdtemp`/`_join`/`_tmp` aliases into the now top-level node imports. Co-Authored-By: Pier Code <no-reply@piercode.com>
Second, independent instance of the same class of bug. `syncEnabled` in threads/bridge.ts memoizes for the whole process and `disableThreadSync()` latches it to false permanently. forkThread.test.ts (#52) and threads.test.ts (#94) both call it at MODULE scope, so by the time discovery.test.ts (#116) runs, sync is off for reasons that have nothing to do with discovery. loadRemoteThread then short-circuits on `!threadSyncOn()` and hydrateRemoteThread returns null without ever issuing a request — which is why the runner failed it in 3ms, far too fast for the local Bun.serve round-trip it was supposed to make. Adds resetThreadSyncCache() (tests only, mirrors resetModelsCache) and calls it in the test so the assertion no longer depends on which files ran before it. Co-Authored-By: Pier Code <no-reply@piercode.com>
The test polled until the output contained `done`, then immediately asserted the status was `completed`. Those are two different events: the write being flushed does not mean the child has exited and been reaped, so on a slower runner the next read still returned `<status>running</status>` and the assertion failed. Flaky rather than broken — the same commit passed the preceding CI run and failed the next. Polls for the status transition (same 6s budget as the loop above) so it waits for the thing it actually asserts. Co-Authored-By: Pier Code <no-reply@piercode.com>
Fix the three CI-only test failures uncovered once the lint gate stopped skipping the Test step: a process-global mock.module leak, a memoized thread-sync latch, and a bg-shell status race. Co-Authored-By: Pier Code <no-reply@piercode.com>
…ions
Adds an opt-in memory of *generalized* lessons ("when the suite hangs, check
the stack-size env first"), distinct from CLAUDE.md/AGENTS.md project memory,
which stores codebase facts and stays always-on.
src/memory/:
types lesson shape; description states when a lesson does NOT apply
store one markdown file per lesson under $PIER_HOME/memory, capped at 50
and evicted by net usefulness (hits - misses); files are the source
of truth so a user can read and rm a bad lesson by hand
signals per-turn outcome labelling from LOCAL state only — tool errors, a
user correction, /undo, test exit — no LLM judge; biased to
`unknown` because a mislabelled lesson is worse than none
inject injects an INDEX (id/title/description), never bodies; the model is
the retriever, so no embeddings. The activating imperative lives in
a per-turn reminder, not this block — measured twice, an imperative
16 KB back in the system prompt is skipped by both models
distill one constrained callModel at teardown, max 3 lessons/session, on a
compacted transcript; the no-literal-strings rule is enforced in
code, since a global bank injects repo-A lessons into repo B
consolidate supersede near-duplicates and prune lessons the model keeps
declining — fixes ReasoningBank's append-only rot
flush session-end wiring at the controller turn boundary + teardown, not
SessionEnd (misses exec/serve/crashes) or the debounced persist path
Wiring: MemoryRecall tool pulls a body on demand and records the hit; the
index is injected for the main session, exec, and subagents — all of which
READ but never WRITE, since only the main session sees the outcome signals
that label a lesson. `/memory list|show|forget|clear` manages the bank; bare
`/memory` keeps its AGENTS.md meaning. Off by default behind `reasoningBank`
while the retrieval loop is validated.
Tests: test/reasoningBank.test.ts, test/reasoningBankWrite.test.ts.
Co-Authored-By: Pier Code <no-reply@piercode.com>
… each other Adds ListAgents / SendMessage so sessions in different terminals can coordinate — e.g. ask which files a peer owns before working in the same repo. engine/siblings.ts: discovery is a PID registry ($PIER_HOME/sessions/<pid>.json) rather than a socket-dir scan, valid because one-session-per-process is already an invariant. Each session listens on $PIER_HOME/socks/<pid>.sock; a send is one NDJSON line plus a one-line ack — fire-and-forget, no correlation id, and a "reply" is just an independent send back to the sender's address. Stale entries are reaped in listSiblings() via process.kill(pid, 0), with a strict /^\d+\.json$/ filename guard. Unix sockets only: every entry point no-ops on win32 and the tools are not registered there. Delivery: an inbound message enters the conversation at the next safe boundary — steered into a running turn, or a fresh turn when idle. The REPL renders it as an attributed peer cell (never as this session's own user), skips @-mention resolution so a peer's paths can't inject local files, and keeps it out of prompt history; rehydrate replays it the same way. Identity: init registers the session with cwd, launch patches in threadId/model, the auto-derived thread title publishes as the display name, and `--name` sets a user name that wins. TodoWrite/UpdatePlan publish the in-progress item as "working on: …". A <sibling-sessions> roster is rebuilt into <env> on every model call, so it tracks sessions starting and exiting mid-conversation. Safety: ListAgents is read-only; SendMessage is allowlisted for auto-approval in every mode — a same-machine send stays inside the machine's trust boundary, the receiver renders it clearly attributed, and this keeps message text out of the bridge classifier. Tests: test/siblings.test.ts, plus a tools.test.ts registry assertion. Co-Authored-By: Pier Code <no-reply@piercode.com>
…suites `bun test` runs all 129 files in ONE process and `mock.module` is process-global with no unwind, so the `../src/safety/autoClassify.js` stub in autoTurn.test.ts (file #11 in CI order) replaced the real module for every file after it. `classifyAction` became `async () => verdict` — returning whatever verdict the last autoTurn test happened to leave behind (`block` / "network command"). That failed 7 tests downstream: 6 in autoClassify.test.ts (#125) and the routing test in localClassify.test.ts (#49). Each one calls the real `classifyAction`, so each got the canned `block` and never reached its own fetch stub — hence `lastBody` staying null in the transcript assertions. The tests asserting `classifyActionLocal` directly kept passing, which is what localised the leak to the mocked module. It reproduced only on CI because the leak depends on whether a victim file resolves its `autoClassify` import before or after this file installs the mock — static top-level imports can win the race, dynamic `await import()` inside a test body never does. Proven directly rather than by inference: with the mock in place, `classifyAction.toString()` in a later file is `"async classifyAction() { return verdict; }"`; with this change it is the real implementation. The mock was never needed. `classifyAction` reaches the network only through `globalThis.fetch`, and the session model here is `sarvam-105b` (a `pier` model, not BYOK), so stubbing fetch drives the REAL function down the bridge path — the same technique autoClassify.test.ts already uses. An `afterAll` restores `fetch` and the two env vars, so this file now leaks nothing itself (verified with a probe). Co-Authored-By: Pier Code <no-reply@piercode.com>
Follow-up to 37e4c62, which swapped autoTurn's leaking `mock.module` for a `fetch` stub driving the real `classifyAction`. That fixed the 7 downstream failures but broke the two verdict tests in this file on CI. `loadAuth()` memoizes into a module-level cache. This file writes a `pier_auth.json` into a temp PIER_HOME at module scope, but ten files run before it in CI order, and any of them that resolves auth first caches `null` from its own empty home. `requireToken()` then throws on that stale null, `classifyAction` fail-safes to `prompt`, and both tests saw the user being prompted instead of the classifier deciding. It passed on macOS purely because the local file order left the cache unpopulated. Reproduced deterministically by loading auth in a file scheduled before this one, which reproduced both failures exactly; `resetAuthCache()` after setting PIER_HOME fixes them, mirroring what autoClassify.test.ts already does. `afterAll` resets again so the temp home's token does not linger for later files. Verified in a linux/amd64 oven/bun:1.3.13 container (CI's platform and Bun version) rather than only on macOS: 956 pass, 2 skip, 0 fail, and tsc clean. Co-Authored-By: Pier Code <no-reply@piercode.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Each commit is scoped to one change. Full gate passes on the tip: typecheck + lint clean, 958 tests, 0 failures (
bun test, the command CI runs).What's in it
96b8116style: fix lint findings (useConst, formatting) surfaced by the sync commit6303f42472ae32feat(cowork): add the cowork surface, tool allowlists, and capability control protocol492cd45feat(engine): allow reconnecting MCP and re-scanning skills between turnsb0e62b9feat(threads): emit todo_update and persist task lists and surface on the threadc0bdd84feat(subagents): stream Task progress, cap concurrency, and forbid nestingbc12886feat(mcp): OAuth for remote servers, per-server status,disabledflag4bcdd8ffeat(permissions): baremcp__<server>rules cover a whole server89b2bacfeat(skills): bundled office skills +when:glob auto-activation89d18e5feat(plugins): installable plugin system for commands, agents, skills, MCP6d5e8d6chore(models): drop deprecated sarvam-30b, regenerate the catalogLanded since that table was written:
94ebc3bfix(test): reset the memoized auth cache in autoTurn's classifier stub (CI fix, part 2 — see below)37e4c62fix(test): stop autoTurn's classifier mock leaking into the classify suites (CI fix, part 1 — see below)584ca33feat(siblings): let pier sessions on one machine discover and message each other8c4adf3feat(memory): reasoning bank — distilled strategy lessons across sessions79cf9c7fix(test): wait for the bg shell to reach completed instead of racing it85c2bedfix(test): reset the memoized thread-sync latch in the discovery test2ea24fcfix(test): stop outputFormat's models mock leaking into every later test70027ccfix(auto): share repeat detection, fix the Auto-mode block escape hatchf862f0bstyle: biome-format the repeat-detection changesNotes for review
Cross-device sync (
6303f42) is the big new piece since the last description update. Thread sync becomes a real two-way mirror instead of local-wins:expected_countcursor so two devices can't interleave history (bridge 409s → reconcile by item-id prefix).reconcileThreadmerges local vs server history: remote-extension adopts server, local-extension keeps local, divergence keeps the longer side and preserves the loser as a conflict copy.pendingPush, retried at boot./sync offopt-out;/share,/unshare,/sessionsun-gated.Session sharing gets a reconnect supervisor + takeover:
HostShare.rebindre-announces and re-offers outstanding approvals on a transport drop, UUID request ids,/unsharevs transport-drop split.Replgets a reconnect loop with backoff;serve --shareauto-mirrors via aTeeIO;work_diffstreams to remote Changes panels.New controls/commands: serve-protocol
rewind(truncate history + durable bridge copy),pier adopt <id>(cloud→laptop handoff),pier devices,set_model/end_sessionhost controls.Mid-turn steering: queued messages are handed to the running turn at the next tool-loop boundary (
steering_injectedretires the chip by text), instead of waiting for the whole turn to resolve.sarvam-30b is gone upstream. It was Sarvam's only cheap tier, so a few assumptions had to move: the BYOK shortlist and validation probe,
cheapCompletion(Sarvam BYOK now pays full freight for throwaway completions — callers already swallow failures to''), and the fallback map, which now pointssarvam-105batpier-hybridso a Sarvam-side outage lands on a different upstream rather than a dead sibling.compact.testno longer depends on sarvam-30b'sauto_compact_token_limit(no catalog model sets one now) and exercises thecontext_window * 0.9fallback instead.The empty tool allowlist is load-bearing.
tools: []means no tools (the desktop's folderless chat mode); omitting the field means the full set for the mode. serve uses??, never||, so[]survives.test/chatModeAllowlist.test.tspins both that and the fact that every chat-allowlisted tool still resolves, so a registry rename can't silently empty chat mode's toolset.MCP prefix rules match on the rule, not by splitting the tool name. Server names may contain underscores, so
mcp__gmailmust not shadowmcp__gmail_backup__send— covered by test.reloadCapabilities()is only safe between turns. Dynamic tools are process-global under the one-session-per-process invariant; the serve handlers reject withbusywhile a turn is running.All new control subtypes are additive and feature-detected via
InitializeResponse.capabilities. Older servers answerunsupported subtype, which clients read as absence.CLIENT_SUBTYPESis kept in lockstep with the schema and asserted by a parity test.CI fix (
37e4c62,94ebc3b)CI was red on 7 tests — 6 in
autoClassify.test.ts, 1 inlocalClassify.test.ts— while the same commit passed locally.Root cause.
bun testruns all 129 files in ONE process andmock.moduleis process-global with no unwind.autoTurn.test.ts(file #11 in CI order) mocked../src/safety/autoClassify.jsto swap the classifier verdict per test, which replaced the real module for every file after it.classifyActionbecameasync () => verdict, returning whatever the last autoTurn test left behind —block/"network command". Every downstream test that calls the realclassifyActiongot that cannedblockand never reached its ownfetchstub, which is why the transcript assertions saw a nulllastBody. Tests callingclassifyActionLocaldirectly kept passing, which localised the leak to the mocked module.Why CI only. The leak depends on whether a victim file resolves its
autoClassifyimport before or after file #11 installs the mock. A static top-level import can win that race; a dynamicawait import()inside a test body never does. Confirmed directly rather than by inference: with the mock in place,classifyAction.toString()in a later file is"async classifyAction() { return verdict; }"; with the fix it is the real implementation.Fix. The mock was never needed.
classifyActionreaches the network only throughglobalThis.fetch, and the session model there issarvam-105b(apiermodel, not BYOK), so stubbingfetchdrives the real function down the bridge path — the techniqueautoClassify.test.tsalready uses. AnafterAllrestoresfetchand the two env vars, so the file leaks nothing itself. Same class of bug, and same remedy, as2ea24fc.Part 2 (
94ebc3b) — the follow-on failure. Removing the mock fixed the 7 downstream tests but broke the two verdict tests inautoTurn.test.tsitself, again only on CI.loadAuth()memoizes into a module-level cache; this file writes apier_auth.jsoninto a tempPIER_HOMEat module scope, but ten files run before it in CI order and any of them that resolves auth first cachesnullfrom its own empty home.requireToken()then throws on that stale null,classifyActionfail-safes toprompt, and both tests saw the user being prompted instead of the classifier deciding.resetAuthCache()after settingPIER_HOMEfixes it — the same thingautoClassify.test.tsalready does — with a second reset inafterAllso the temp home's token does not linger.Verification. Both fixes were checked in a
linux/amd64oven/bun:1.3.13container — CI's platform and Bun version — not just on macOS, since every failure in this pair was ordering- and platform-dependent and passed locally throughout: 956 pass, 2 skip, 0 fail,tscclean. Both CI checks on the PR are green.Reasoning bank (
8c4adf3)An opt-in memory of generalized strategy lessons ("when the suite hangs, check the stack-size env first"), distinct from
CLAUDE.md/AGENTS.mdproject memory, which holds codebase facts and stays always-on. Off by default behind thereasoningBankconfig key while the retrieval loop is validated.$PIER_HOME/memory, capped at 50 and evicted by net usefulness (hits - misses) — files stay the source of truth so a user can read andrma bad lesson by hand.MemoryRecall, which doubles as the relevance signal./undo, test exit — with no LLM judge, and biased tounknown, since a mislabelled lesson is worse than none.exec, and subagents all read the bank but never write to it — only the main session sees the outcome signals that label a lesson./memory list|show|forget|clearmanages it; bare/memorykeeps itsAGENTS.mdmeaning.Sibling sessions (
584ca33)ListAgents/SendMessagelet pier sessions in different terminals on the same machine discover and message each other — e.g. ask which files a peer owns before working in the same repo.$PIER_HOME/sessions/<pid>.json), valid because one-session-per-process is already an invariant; stale entries are reaped viaprocess.kill(pid, 0)behind a strict/^\d+\.json$/filename guard.$PIER_HOME/socks/<pid>.sock; a send is one NDJSON line plus a one-line ack — fire-and-forget, no correlation id, and a "reply" is just an independent send back.@-mention resolution so a peer's paths cannot inject local files, and keeps it out of prompt history.--nameoverriding;TodoWrite/UpdatePlanpublish the in-progress item as "working on: …". A<sibling-sessions>roster is rebuilt into<env>on every model call.ListAgentsis read-only;SendMessageis allowlisted for auto-approval — a same-machine send stays inside the machine's trust boundary and keeps message text out of the bridge classifier. Unix sockets only: every entry point no-ops on win32.Commit splitting
session.ts,turn.tsandstore.tswere split at the hunk level so the fallback-map change, skill-hint injection, subagent emit plumbing, andtodo_updateevent each landed separately.Cowork, the tool allowlist and the capability subtypes stayed in one commit (
472ae32) — their changes interleave inside the same hunks (SessionController.create,InitializeRequestSchema, theinitializehandler), so separating them would mean intermediate commits referencingopts.toolsbefore it is declared, i.e. commits that don't compile.Version
Title says v1.6.0, but nothing in the tree encodes it —
package.jsonis the0.0.0-devsentinel and versions come from release tags. No version bump is included here; tag after merge.