48 KiB
48 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.7.1] - 2026-05-07
Heads-up — behavior change:
isolation: "worktree"now fails loud (returns an error) instead of silently falling back to the main tree. Affects users running pi in a non-git directory or a fresh repo with no commits.
Changed
isolation: "worktree"now fails loud instead of silently falling back. Previously whencreateWorktreereturned undefined (not a git repo, no commits yet, orgit worktree addfailed), the agent ran in the maincwdwith a[WARNING: ...]block prepended to its prompt — visible only to the LLM, never surfaced to the caller. Now the failure throws a structured error that propagates back to theAgenttool response; no agent record is created. Failed scheduled fires are recorded aslastStatus: "error"with the reason in thesubagents:schedulederror event. Queued background spawns whose worktree creation fails when they dequeue are marked terminal-error and don't block the rest of the queue.
Fixed
- Headless
pi --printruns no longer hang or crash after background subagents complete. Cleanup timers no longer keep the process alive, and stale completion notifications are treated as best-effort shutdown side effects.
[0.7.0] - 2026-05-04
Heads-up — behavior changes:
subagents:completed/failedeventtokens.totalnow excludescacheRead(previously double-counted across turns) — see Fixed [#38].- Cron
?is now a wildcard (same as*), not "current time value" — affects Quartz-style expressions only.
Changed
@mariozechner/pi-{ai,coding-agent,tui}moved topeerDependencies(>=0.70.5). Avoids duplicate framework instances when the host loads this extension.@sinclair/typeboxpinned fromlatestto^0.34.49so installs are reproducible.cronerbumped 8 → 10. Heads-up: in cron strings,?now means wildcard (same as*) instead of "current time value" — affects Quartz-style expressions only.
Added
- Master switch for scheduling — new
schedulingEnabledsetting (defaulttrue) under/agents → Settings → Scheduling. When set tofalse: thescheduleparameter and its guideline are stripped from theAgenttool spec at registration (zero LLM-context cost), the scheduler does not bind to the session, the/agents → Scheduled jobsmenu entry is hidden, and any in-flight scheduler is stopped immediately. The schema-level removal applies on next pi session; the runtime kill (menu, fire path) takes effect immediately. Persisted at<cwd>/.pi/subagents.json. - Schedule subagent spawns — the
Agenttool now accepts an optionalscheduleparameter. When set, the spawn registers a job that fires later instead of running immediately. Three formats: 6-field cron ("0 0 9 * * 1"— 9am every Monday), interval ("5m","1h"), or one-shot ("+10m"or ISO timestamp). Returns the job ID. Schedules are session-scoped — they reset on/new, restore on/resume(mirrors the persistence model of pi-chonky-tasks). Storage at<cwd>/.pi/subagent-schedules/<sessionId>.json, with PID-based file locking + atomic temp+rename for concurrent-instance safety. Result delivery is identical to today's background-spawn completions: when the scheduled agent finishes, the existingsubagent-notificationfollowUp path emits the result to the conversation — no new delivery code, no new message types. Concurrency: scheduled fires bypassmaxConcurrentso a 5-minute interval can't be deferred behind 4 long-running manual agents. Management:/agents→ "Scheduled jobs" lists active jobs and lets you cancel any one of them. Creation is via theAgenttool only — no parallel manual-create wizard in this iteration. Events:subagents:scheduled({ type: "added" | "removed" | "updated" | "fired" | "error", … }) andsubagents:scheduler_readyfor cross-extension consumers. Restrictions:scheduleis incompatible withinherit_context(no parent at fire time) andresume(schedules create fresh agents); forcesrun_in_background: true. Scheduler engine mirrorspi-cron-schedule(cronerfor cron,setInterval/setTimeoutfor interval/once); past one-shot timestamps and invalid cron expressions are caught at create time. - Context-window utilization indicator in the subagent overlay — token count is now followed by a colored
(NN%)showing how full the subagent's context is right now (estimateContextTokens(messages) / model.contextWindow * 100, sourced from upstreamcontextUsage.percent). Threshold colors: <70% dim, 70–85% warning, ≥85% error. Gracefully omitted when the model has nocontextWindowdeclared, or right after compaction before the next assistant turn (tokensisnullin that window). The same annotation slot also surfaces a compaction count↻Nwhen the agent has compacted at least once — e.g.12.3k token (84% · ↻3)(percent + compactions joined with·),12.3k token (↻1)(compactions only, immediately post-compaction while percent is still null). The compaction glyph stays dim regardless; the percent's threshold color carries the urgency signal. Two live overlays get the annotations (running stats line; inspect-overlay header); post-completion notifications and result/event payloads only get the count (the indicator is no longer actionable once the agent is done). - Token usage and context% exposed to the parent agent at every interaction surface —
get_subagent_resultaddsContext: NN%to its stats line;steer_subagentreturns aCurrent state: 12.3k token · 5 tool uses · context 72% fullline so the steering agent knows whether it has room before sending more context;task-notificationXML adds<context_percent>NN</context_percent>(omitted when null). All plain-text, no ANSI codes — designed for LLM consumption, not human display. - New
subagents:compactedlifecycle event fires when a subagent's session successfully compacts. Payload:{ id, type, description, reason: "manual" | "threshold" | "overflow", tokensBefore, compactionCount }—tokensBeforeis upstream's pre-compaction context size estimate;compactionCountis the running total for this agent (also persisted onAgentRecord.compactionCountand surfaced inget_subagent_result/steer_subagent/task-notificationwhen > 0). Aborted compactions don't fire. Routed through a new manager-levelonCompactconstructor callback, matching the existingonStart/onCompletepattern.
Fixed
- Subagent token count was inflated 5–15× and reset mid-run (#38). Two distinct bugs in the same field. (1) Upstream
getSessionStats().tokens.totalsums per-turncacheReadacross every assistant message — but each turn'scacheReadis the cumulative cached prefix re-read on that one API call, so summing N turns counts the prefix N times (quadratic inflation, very visible on long sessions). (2) Even with that fixed, anything derived fromsession.state.messagesresets at compaction because upstream replaces the array viathis.agent.state.messages = sessionContext.messages. Fix replaces all six display readers with a lifetime accumulator (AgentRecord.lifetimeUsageandAgentActivity.lifetimeUsage—{ input, output, cacheWrite }) fed by a newonAssistantUsagecallback dispatched frommessage_endevents in bothrunAgentandresumeAgent. The accumulator is independent ofstate.messagesmutation, so it survives compaction; total = input + output + cacheWrite by construction (cacheRead deliberately excluded — same prefix-double-counting reason). Thesubagents:completed/failedevent payload'stokensfield is now also lifetime-accumulated forinput,output, andtotaltogether (was:totallifetime,input/outputsession-derived → inconsistent after compaction). - ESC during a foreground
Agentcall now actually stops the subagent (#44 — thanks @Zeng-Zer). Pi's interrupt path isesc → agent.abort()on the parent →AbortSignaldelivered to every tool'sexecute(toolCallId, params, signal, …), but theAgenttool dropped that signal on the floor: subagents ran on their own independentAbortControllerinsideAgentManager, so the parent abort was invisible and the subagent kept running until natural completion ormax_turns. Fix threadssignalthroughAgent.execute→manager.spawnAndWait()→SpawnOptions.signal, andAgentManager.startAgent()now attaches an{ once: true }"abort"listener that callsthis.abort(id)(which setsstatus: "stopped"and aborts the child controller). The listener is detached in both.thenand.catchto avoid leaking on natural settle. Scope: foreground only — background agents intentionally outlive the parent tool call, so their spawn deliberately does not forwardsignal. Resume path (AgentManager.resume()) has the same blind spot and is tracked as a follow-up.
0.6.3 - 2026-04-28
Fixed
run_in_background: true(andinherit_context,isolated) silently ignored on default agents (#37 — thanks @kylesnowschwartz for the diagnosis). The three built-in defaults (general-purpose,Explore,Plan) bakedrunInBackground: false,inheritContext: false, andisolated: falseinto their configs.resolveAgentInvocationConfigusesagentConfig?.field ?? params.field ?? false, and??only falls through onnull/undefined— so an explicitfalsefrom the agent config silently won over the caller'strue. CallingAgent({ subagent_type: "general-purpose", run_in_background: true })returned the result inline instead of backgrounding, blocking the parent UI for the agent's full runtime. Fix drops the three lines from each default (and from the unreachable defensive fallback inagent-runner.ts) — the type already declared each asfield?: booleanwith JSDoc "undefined = caller decides", so the runtime now matches the documented contract. Behavior: custom agents that explicitly set these fields in frontmatter still lock as before (the v0.5.1 "frontmatter is authoritative" guarantee is preserved); the fix only stops defaults from spuriously claiming an opinion on callsite-strategy fields they don't actually have. The unreachable fallback now spreadsDEFAULT_AGENTS.get("general-purpose")instead of duplicating the config inline, so future drift is impossible.
0.6.2 - 2026-04-28
Fixed
Agenttool fails on Windows withENOENTcreating output directory (#27 — thanks @sixnathan for the diagnosis). The cwd-encoding regex inoutput-file.tsonly handled POSIX/separators, so on Windowscwd = "C:\\Users\\foo\\project"survived unchanged andpath.join(tmpRoot, encoded, …)produced an invalid nested-absolute path. Now extracts a smallencodeCwd()helper that handles both/and\\separators, strips the Windows drive-letter prefix, and preserves UNC server/share segments. ThechmodSync(root, 0o700)call is also wrapped in a try/catch that swallows errors only on Windows (where chmod is a no-op and can throw on some filesystems); on Unix the error still propagates so umask-defeating0o700enforcement is preserved.
0.6.1 - 2026-04-25
Added
- Persistent
/agents→ Settings (#24) — the four runtime tuning values (maxConcurrent,defaultMaxTurns,graceTurns,defaultJoinMode) now survive pi restarts via a two-file dual-scope model mirroring pi's ownSettingsManager. Global~/.pi/agent/subagents.jsonprovides machine-wide defaults (edit by hand; the menu never writes here); project<cwd>/.pi/subagents.jsonholds per-project overrides (written by/agents→ Settings). Load merges both with project winning on conflicts. Invalid fields are silently dropped per field; malformed JSON emits a warning to stderr and falls back to defaults so startup always proceeds; write failures downgrade the settings toast to a warning with(session only; failed to persist)so changes aren't silently reverted on next restart. - New lifecycle events —
subagents:settings_loaded(emitted once at extension init with the merged settings) andsubagents:settings_changed(emitted on each/agents→ Settings mutation with the new snapshot and apersisted: booleanflag so listeners can react to write failures).
Fixed
AGENTS.md/CLAUDE.md/APPEND_SYSTEM.mdno longer leak into sub-agent prompts (#26 — thanks @mikeyobrien for the diagnosis). UpstreambuildSystemPrompt()re-appendscontextFilesandappendSystemPromptafter oursystemPromptOverrideruns, which silently defeatedprompt_mode: replaceandisolated: true— parent project context (e.g. autoresearch-mode blocks) was bleeding into freshExplore/ custom sub-agents regardless of frontmatter. Fix uses upstream'snoContextFiles: trueflag (skips the load entirely, introduced in pi 0.68) plusappendSystemPromptOverride: () => [](no flag equivalent for append sources). Behavior change: subagents no longer implicitly inherit parentAGENTS.md/CLAUDE.md/APPEND_SYSTEM.md. To get parent project context into a subagent, useprompt_mode: append(parent's already-built system prompt flows in viasystemPromptOverride), orinherit_context: true(parent conversation), or inline the content into the agent's own frontmatter.- Custom agent discovery respects
PI_CODING_AGENT_DIR(#35, closes #23 — thanks @Amolith for the diagnosis). Two remaining hardcoded~/.pi/agent/agents/paths incustom-agents.tsandindex.tsbypassed the env var, so users who relocated their agent directory (e.g. viaPI_CODING_AGENT_DIR) still had global agents loaded from the default location and help text referencing the wrong path. Both now use upstreamgetAgentDir(), consistent withagent-runner.tsandsettings.ts; tilde expansion is handled by upstream.
0.6.0 - 2026-04-24
⚠️ Breaking: drops support for
pi< 0.68. The upstreampi-coding-agentpackage shipped breaking API changes in v0.68 (and further ones in v0.70). This release migrates to^0.70.2and is not backward-compatible with hosts onpi0.62–0.67. Users on those versions must upgrade theirpiinstallation (npm install -g @mariozechner/pi-coding-agent@latest) before updating this extension.
Changed
- Bumped peer
@mariozechner/pi-coding-agentto^0.70.2(#28) — crosses the v0.68 breaking-change line upstream. Specifically: tools are now passed asstring[](wasTool[]);cwd/agentDirare mandatory onSettingsManager.create()andDefaultResourceLoader;session_switchevent renamed tosession_before_switch;ToolDefinition.paramswidens tounknownunder contextual typing, requiringdefineTool(...). - Tool registrations wrapped with
defineTool(...)— preservesTParamsinference soexecutehandlers get properly-typedparamsinstead ofunknown. Applies to theAgent,get_subagent_result, andsteer_subagenttools.
Removed
- Cwd-bound tool factory registry — the internal
TOOL_FACTORIESclosure table andcreate{Bash,Edit,Read,Write,Grep,Find,Ls}Toolimports are gone. Exported helpers renamed:getToolsForType(type, cwd)→getToolNamesForType(type),getMemoryTools(cwd, set)→getMemoryToolNames(set),getReadOnlyMemoryTools(cwd, set)→getReadOnlyMemoryToolNames(set)— all returningstring[]instead ofTool[]. The host binds cwd when resolving tool names, so the extension no longer instantiates tools directly.
Fixed
- Subagent
SettingsManagerread wrong project settings in worktree mode (#30) —SettingsManager.create()was called without arguments, defaultingcwdtoprocess.cwd(). When the subagent's effective cwd differed (worktree isolation or explicitcwdoverride), its settings manager read.pi/settings.jsonfrom the parent's cwd rather than its own, diverging from the loader and session manager. Now passeseffectiveCwdandagentDirexplicitly, keeping all three managers consistent.
0.5.2 - 2026-03-26
Fixed
- Extension
session_starthandlers now fire in subagent sessions (#20) —bindExtensions()was never called on subagent sessions, so extensions that initialize state insession_start(e.g. loading credentials, setting up connections) silently failed at runtime. Tools appeared registered but were non-functional. Now callssession.bindExtensions()after tool filtering and before prompting, matching the lifecycle used by pi's interactive, print, and RPC modes. Also triggersextendResourcesFromExtensions("startup")so extension-provided skills and prompts are discovered.
0.5.1 - 2026-03-24
Changed
- Agent config is authoritative — frontmatter values for
model,thinking,max_turns,inherit_context,run_in_background,isolated, andisolationnow take precedence overAgenttool-call parameters. Tool-call params only fill fields the agent config leaves unspecified. join_modeis now a global setting only — removed the per-calljoin_modeparameter from theAgenttool. Join behavior is configured via/agents→ Settings → Join mode.max_turns: 0means unlimited — agent files can now explicitly setmax_turns: 0to lock unlimited turns. Previously0was silently clamped to1.
Fixed
- Final subagent text preserved from non-streaming providers — agents using providers that return the final message without streaming
text_deltaevents no longer return empty results. Falls back to extracting text from the completed session history. effectiveMaxTurnspassed to spawn calls — previouslyparams.max_turnswas passed raw to both foreground and background spawn, bypassing the agent config entirely.
0.5.0 - 2026-03-22
Added
- RPC stop handler — new
subagents:rpc:stopevent bus RPC allows other extensions to stop running subagents by agent ID. Returns structured error ("Agent not found") on failure. abortinSpawnCapableinterface — cross-extension RPC consumers can now stop agents, not just spawn them.- Live turn counter — all agents now show a live turn count in the widget, inline result, and completion notification. With a turn limit:
⟳5≤30(5 of 30 turns). Without:⟳5. Updates in real time as turns progress viaonTurnEndcallback. - Biome linting — added Biome for correctness linting (unused imports, suspicious patterns). Style rules disabled. Run
npm run lintto check,npm run lint:fixto auto-fix. - CI workflow — GitHub Actions runs lint, typecheck, and tests on push to master and PRs.
- Auto-trigger parent turn on background completion — background agent completion notifications now use
triggerTurn: true, automatically prompting the parent agent to process results instead of waiting for user input.
Changed
- Standardized RPC envelope — cross-extension RPC handlers (
ping,spawn,stop) now use ahandleRpcwrapper that emits structured envelopes ({ success: true, data }/{ success: false, error }), matching pi-mono'sRpcResponseconvention. - Protocol versioning via ping — ping reply now includes
{ version: PROTOCOL_VERSION }(currently v2). Callers can detect version mismatches and warn users to update. - Default max turns is now unlimited — subagents no longer have a 50-turn default cap. The default is unlimited (no turn limit), matching Claude Code's main loop behavior. Users can still set explicit limits per-agent via
max_turnsfrontmatter or the Agent tool parameter, or globally via/agents→ Settings (0= unlimited). - Stale dist in published package — added
prepublishOnlyhook to build freshdist/on everynpm publish.
Fixed
- Tool name display —
getAgentConversationnow readsToolCall.name(the correct property) instead oftoolName, resolving[Tool: unknown]in conversation viewer and verbose output. - Env test CI failure —
detectEnvtest assumed a branch name exists, but CI checks out detached HEAD. Split into separate tests for repo detection and branch detection with a controlled temp repo.
0.4.9 - 2026-03-18
Fixed
- Conversation viewer crash in narrow terminals (#7) —
buildContentLines()in the live conversation viewer could return lines wider than the terminal whenwrapTextWithAnsi()misjudged visible width on ANSI-heavy input (e.g. tool output with embedded escape codes, long URLs, wide tables). All content lines are now clamped withtruncateToWidth()before returning. Same class of bug as the widget fix in v0.2.7, different component.
Added
- Conversation viewer width-safety tests — 17 tests covering
render()andbuildContentLines()across varied content (plain text, ANSI codes, unicode, tables, long URLs, narrow terminals). Includes mock-based regression tests that simulate upstreamwrapTextWithAnsireturning overwidth lines, ensuring the safety net catches them.
0.4.8 - 2026-03-18
Added
- Cross-extension RPC — other pi extensions can spawn subagents via
pi.eventsevent bus (subagents:rpc:ping,subagents:rpc:spawn). Emitssubagents:readyon load. - Session persistence for agent records — completed agent records are persisted via
pi.appendEntry("subagents:record", ...)for cross-extension history reconstruction.
Fixed
- Background agent notification race condition —
pi.sendMessage()is fire-and-forget, so completion notifications sent eagerly fromonCompletecould not be retracted whenget_subagent_resultwas called in the same turn. Notifications are now held behind a 200ms cancellable timer;get_subagent_resultcancels the pending timer before it fires, eliminating duplicate notifications. Group notifications also re-checkresultConsumedat send time so consumed agents are filtered out.
0.4.7 - 2026-03-17
Added
- Custom notification renderer — background agent completion notifications now render as styled, themed boxes instead of raw XML. Uses
pi.registerMessageRenderer()with the"subagent-notification"custom message type. The LLM continues to receive<task-notification>XML viacontent; only the user-facing display changes. - Group notification rendering — group completions render each agent as its own styled block (icon, description, stats, result preview) instead of showing only the first agent.
- Output file streaming for background agents — background agents now get the same output file transcript as foreground agents, with
onSessionCreatedwiring and proper cleanup on completion/error. NotificationDetailstype intypes.ts— structured details for the notification renderer, with optionalothersarray for group notifications.buildNotificationDetails()helper — extracts renderer-facing details from anAgentRecord.
Changed
- Notification delivery —
sendIndividualNudgeand group notification now usepi.sendMessage()(custom message) instead ofpi.sendUserMessage()(plain text), enabling renderer-controlled display. - Steered status rendering — steered agents show "completed (steered)" in the notification box instead of plain "completed".
Fixed
- Output file cleanup on completion —
agent-manager.tsnow callsrecord.outputCleanup()in both the success and error paths of agent completion, ensuring the streaming subscription is flushed and released.
0.4.6 - 2026-03-16
Fixed
- Graceful shutdown aborts agents instead of blocking —
session_shutdownnow callsabortAll()instead ofwaitForAll(), so the process exits immediately instead of hanging until all background agents complete. Agent results are undeliverable after shutdown anyway.
Added
abortAll()method onAgentManager— stops all queued and running agents at once, returning the count of affected agents.
0.4.5 - 2026-03-16
Changed
- Widget render-once pattern — the widget callback is now registered once via
setWidget()and subsequent updates userequestRender()instead of re-registering the entire widget on everyupdate()call. Eliminates layout thrashing from repeated widget teardown/setup cycles. - Status bar dedup —
setStatus()is now only called when the status text actually changes, avoiding redundant TUI updates. - UICtx change detection —
setUICtx()detects context changes and forces widget re-registration, correctly handling session switches.
Refactored
- Extracted
renderWidget()private method — moves all widget content rendering out of theupdate()closure into a standalone method that reads live state on each call. update()is now a lightweight coordinator: counts agents, manages registration lifecycle, and triggers re-renders.
0.4.4 - 2026-03-16
Fixed
- Race condition in
get_subagent_resultwithwait: true—resultConsumedis now set beforeawait record.promise, preventing a redundant follow-up notification. Previously theonCompletecallback (attached at spawn time via.then()) always fired before the await resumed, seeingresultConsumedas false. - Stale agent records across sessions — new
clearCompleted()method removes all completed/stopped/errored agent records onsession_startandsession_switchevents, so tasks from a prior session don't persist into a new one. steer_subagentrace on freshly launched agents — steering an agent before its session initialized silently dropped the message. Now steers are queued on the record and flushed onceonSessionCreatedfires.
Changed
- Extracted
removeRecord()private helper inAgentManager— deduplicates dispose+delete logic betweencleanup()andclearCompleted().
Added
- 8 new tests covering
resultConsumedrace condition andclearCompletedbehavior (185 total).
0.4.3 - 2026-03-13
Added
- Persistent agent memory — new
memoryfrontmatter field with three scopes:"user"(global~/.pi/),"project"(per-project.pi/),"local"(gitignored.pi/). Agents with write/edit tools get full read-write memory; read-only agents get a read-only fallback that injects existing MEMORY.md content without granting write access or creating directories. - Git worktree isolation — new
isolation: "worktree"frontmatter field and Agent tool parameter. Creates a temporarygit worktreeso agents work on an isolated copy of the repo. On completion, changes are auto-committed to api-agent-<id>branch; clean worktrees are removed. Includes crash recovery viapruneWorktrees(). - Skill preloading —
skillsfrontmatter now accepts a comma-separated list of skill names (e.g.skills: planning, review). Reads from.pi/skills/(project) then~/.pi/skills/(global), tries.md/.txt/bare extensions. Content injected into the system prompt as# Preloaded Skill: {name}. - Tool denylist — new
disallowed_toolsfrontmatter field (e.g.disallowed_tools: bash, write). Blocks specified tools even ifbuiltinToolNamesor extensions would provide them. Enforced for both extension-enabled and extension-disabled agents. - Prompt extras system — new
PromptExtrasinterface inprompts.ts;buildAgentPrompt()accepts optional memory and skill blocks appended in bothreplaceandappendmodes. getMemoryTools(),getReadOnlyMemoryTools()inagent-types.ts.buildMemoryBlock(),buildReadOnlyMemoryBlock(),isSymlink(),safeReadFile()inmemory.ts.preloadSkills()inskill-loader.ts.createWorktree(),cleanupWorktree(),pruneWorktrees()inworktree.ts.MemoryScope,IsolationModetypes;memory,isolation,disallowedToolsfields onAgentConfig;worktree,worktreeResultfields onAgentRecord.- 177 total tests across 8 test files (41 new tests).
Fixed
- Read-only agents no longer escalated to read-write — enabling
memoryon a read-only agent (e.g. Explore) previously auto-addedwrite/edittools. Now the runner detects write capability and branches: read-write agents get full memory tools, read-only agents get read-only memory prompt with only thereadtool added. - Denylist-aware memory detection — write capability check now accounts for
disallowedTools. An agent withtools: write+disallowed_tools: writecorrectly gets read-only memory instead of broken read-write instructions. - Worktree requires commits — repos with no commits (empty HEAD) are now rejected early with a warning instead of failing silently at
git worktree add. - Worktree failure warning — when worktree creation fails, a warning is prepended to the agent's prompt instead of silently falling through to the main cwd.
- No force-branch overwrite — worktree cleanup appends a timestamp suffix on branch name conflict instead of using
git branch -f.
Security
- Whitelist name validation — agent/skill names must match
^[a-zA-Z0-9][a-zA-Z0-9._-]*$, max 128 chars. Rejects path traversal, leading dots, spaces, and special characters. - Symlink protection —
safeReadFile()andisSymlink()reject symlinks in memory directories, MEMORY.md files, and skill files, preventing arbitrary file reads. - Symlink-safe directory creation —
ensureMemoryDir()throws on symlinked directories.
Changed
agent-runner.ts: tool/extension/skill resolution moved before memory detection;ctx.cwd→effectiveCwdthroughout.custom-agents.ts: extractedparseCsvField()helper; addedcsvListOptional()andparseMemory().skill-loader.ts: usessafeReadFile()frommemory.tsinstead of rawreadFileSync.- Agent tool schema updated with
isolationparameter and help text formemory,isolation,disallowed_tools, and skill list.
0.4.2 - 2026-03-12
Added
- Event bus — agent lifecycle events emitted via
pi.events.emit(), enabling other extensions to react to sub-agent activity:subagents:created— background agent registered (includesid,type,description,isBackground)subagents:started— agent transitions to running (includes queued→running)subagents:completed— agent finished successfully (includesdurationMs,tokens,toolUses,result)subagents:failed— agent errored, stopped, or aborted (same payload as completed)subagents:steered— steering message sent to a running agent
OnAgentStartcallback andonStartconstructor parameter onAgentManager.- Cross-package manager now also exposes
spawn()andgetRecord()via theSymbol.for("pi-subagents:manager")global.
0.4.1 - 2026-03-11
Fixed
- Graceful shutdown in headless mode — the CLI now waits for all running and queued background agents to complete before exiting (
waitForAllonsession_shutdown). Previously, background agents could be silently killed mid-execution when the session ended. Only affects headless/non-interactive mode; interactive sessions already kept the process alive.
Added
hasRunning()/waitForAll()methods onAgentManager.- Cross-package manager access — agent manager exposed via
Symbol.for("pi-subagents:manager")onglobalThisfor other extensions to check status or await completion.
0.4.0 - 2026-03-11
Added
- XML-delimited prompt sections — append-mode agents now wrap inherited content in
<inherited_system_prompt>,<sub_agent_context>, and<agent_instructions>XML tags, giving the model explicit structure to distinguish inherited rules from sub-agent-specific instructions. Replace mode is unchanged. - Token count in agent results — foreground agent results, background completion notifications, and
get_subagent_resultnow include the token count alongside tool uses and duration (e.g.Agent completed in 4.2s (12 tool uses, 33.8k token)). - Widget overflow cap — the running agents widget now caps at 12 lines. When exceeded, running agents are prioritized over finished ones and an overflow summary line shows hidden counts (e.g.
+3 more (1 running, 2 finished)).
Changed - changing behavior
- General-purpose agent inherits parent prompt — the default
general-purposeagent now usespromptMode: "append"with an empty system prompt, making it a "parent twin" that inherits the full parent system prompt (including CLAUDE.md rules, project conventions, and safety guardrails). Previously it used a standalone prompt that duplicated a subset of the parent's rules. Explore and Plan are unchanged (standalone prompts). To customize: eject via/agents→ selectgeneral-purpose→ Eject, then edit the resulting.mdfile. Setprompt_mode: replaceto go back to a standalone prompt, or keepprompt_mode: appendand add extra instructions in the body. - Append-mode agents receive parent system prompt —
buildAgentPromptnow accepts the parent's system prompt and threads it into append-mode agents (env header + parent prompt + sub-agent context bridge + optional custom instructions). Replace-mode agents are unchanged. - Prompt pipeline simplified — removed
systemPromptOverride/systemPromptAppendfromSpawnOptionsandRunOptions. These were a separate code path whereindex.tspre-resolved the prompt mode and passed raw strings into the runner, bypassingbuildAgentPrompt. Now all prompt assembly flows throughbuildAgentPromptusing the agent'spromptModeconfig — one code path, no special cases.
Removed
- Deprecated backwards-compat aliases:
registerCustomAgents,getCustomAgentConfig,getCustomAgentNames(useregisterAgents,getAgentConfig,getUserAgentNames). resolveCustomPrompt()helper in index.ts — no longer needed now that prompt routing is config-driven.
0.3.1 - 2026-03-09
Added
- Live conversation viewer — selecting a running (or completed) agent in
/agents→ "Running agents" now opens a scrollable overlay showing the agent's full conversation in real time. Auto-scrolls to follow new content; scroll up to pause, End to resume. Press Esc to close.
0.3.0 - 2026-03-08
Added
- Case-insensitive agent type lookup —
"explore","EXPLORE", and"Explore"all resolve to the same agent. LLMs frequently lowercase type names; this prevents validation failures. - Unknown type fallback — unrecognized agent types fall back to
general-purposewith a note, instead of hard-rejecting. Matches Claude Code behavior. - Dynamic tool list for general-purpose —
builtinToolNamesis now optional inAgentConfig. When omitted, the agent gets all tools fromTOOL_FACTORIESat lookup time, so new tools added upstream are automatically available. - Agent source indicators in
/agentsmenu —•(project),◦(global),✕(disabled) with legend. Defaults are unmarked. - Disabled agents visible in UI — disabled agents now show in the "Agent types" list (marked
✕) with an Enable action, instead of being invisible. - Enable action — re-enable a disabled agent from the
/agentsmenu. Stub files are auto-cleaned. - Disable action for all agent types — custom and ejected default agents can now be disabled from the UI, not just built-in defaults.
resolveType()export — case-insensitive type name resolution for external use.getAllTypes()export — returns all agent names including disabled (for UI listing).sourcefield onAgentConfig— tracks where an agent was loaded from ("default","project","global").
Fixed
- Model resolver checks auth for exact matches —
resolveModel("anthropic/claude-haiku-4-5-20251001")now fails gracefully when no Anthropic API key is configured, instead of returning a model that errors at the API call. Explore silently falls back to the parent model on non-Anthropic setups.
Changed
- Unified agent registry — built-in and custom agents now use the same
AgentConfigtype and a single registry. No more separate code paths for built-in vs custom agents. - Default agents are overridable — creating a
.mdfile with the same name as a default agent (e.g..pi/agents/Explore.md) overrides it. /agentsmenu — "Agent types" list shows defaults and custom agents together with source indicators. Default agents get Eject/Disable actions; overridden defaults get Reset to default.- Eject action — export a default agent's embedded config as a
.mdfile to project or personal location for customization. - Model labels — provider-agnostic: strips
provider/prefix and-YYYYMMDDdate suffix (e.g.anthropic/claude-haiku-4-5-20251001→claude-haiku-4-5). Works for any provider. - New frontmatter fields —
display_name(UI display name) andenabled(default: true; set to false to disable). - Menu navigation — Esc in agent detail returns to agent list (not main menu).
Removed
statusline-setupandclaude-code-guideagents — removed as built-in types (never spawned programmatically). Users can recreate them as custom agents if needed.BuiltinSubagentTypeunion type,SUBAGENT_TYPESarray,DISPLAY_NAMESmap,SubagentTypeConfiginterface — replaced by unifiedAgentConfig.buildSystemPrompt()switch statement — replaced by config-drivenbuildAgentPrompt().HAIKU_MODEL_IDSfallback array — Explore's haiku default is now just themodelfield in its config.BUILTIN_MODEL_LABELS— model labels now derived from config.ALL_TOOLShardcoded constant — general-purpose now derives tools dynamically.
Added
src/default-agents.ts— embedded default configs for general-purpose, Explore, and Plan.
0.2.7 - 2026-03-08
Fixed
- Widget crash in narrow terminals — agent widget lines were not truncated to terminal width, causing
doRenderto throw when the tmux pane was narrower than the rendered content. All widget lines are now truncated usingtruncateToWidth()with the actual terminal column count.
0.2.6 - 2026-03-07
Added
- Background task join strategies — smart grouping of background agent completion notifications
smart(default): 2+ background agents spawned in the same turn are auto-grouped into a single consolidated notification instead of individual nudgesasync: each agent notifies individually on completion (previous behavior)group: force grouping even for solo agents- 30s timeout after first completion delivers partial results; 15s straggler re-batch window for remaining agents
join_modeparameter on theAgenttool — override join strategy per agent ("async"or"group")- Join mode setting in
/agents→ Settings — configure the default join mode at runtime - New
src/group-join.ts—GroupJoinManagerclass for batched completion notifications
Changed
AgentRecordnow includes optionalgroupId,joinMode, andresultConsumedfields- Background agent completion routing refactored: individual nudge logic extracted to
sendIndividualNudge(), group delivery viaGroupJoinManager
Fixed
- Debounce window race — agents that complete during the 100ms batch debounce window are now deferred and retroactively fed into the group once it's registered, preventing split notifications (one individual + one partial group) and zombie groups
- Solo agent swallowed notification — if only one agent was spawned (no group formed) but it completed during the debounce window, its deferred notification is now sent when the batch finalizes
- Duplicate notifications after polling — calling
get_subagent_resulton a completed agent now marks its result as consumed, suppressing the subsequent completion notification (both individual and group)
0.2.5 - 2026-03-06
Added
- Interactive
/agentsmenu — single command replaces/agentand/agentswith a full management wizard- Browse and manage running agents
- Custom agents submenu — edit or delete existing agents
- Create new custom agents via manual wizard or AI-generated (with comprehensive frontmatter documentation for the generator)
- Settings: configure max concurrency, default max turns, and grace turns at runtime
- Built-in agent types shown with model info (e.g.
Explore · haiku) - Aligned formatting for agent lists
- Configurable turn limits —
defaultMaxTurnsandgraceTurnsare now runtime-adjustable via/agents→ Settings - Sub-menus return to main menu instead of exiting
Removed
/agent <type> <prompt>command (useAgenttool directly, or create custom agents via/agents)
0.2.4 - 2026-03-06
Added
- Global custom agents — agents in
~/.pi/agent/agents/*.mdare now discovered automatically and available across all projects - Two-tier discovery hierarchy: project-level (
.pi/agents/) overrides global (~/.pi/agent/agents/)
0.2.3 - 2026-03-05
Added
- Screenshot in README
0.2.2 - 2026-03-05
Changed
- Renamed package to
@tintinweb/pi-subagents - Fuzzy model resolver now only matches models with auth configured (prevents selecting unconfigured providers)
- Custom agents hot-reload on each
Agenttool call (no restart needed for new.pi/agents/*.mdfiles) - Updated pi dependencies to 0.56.1
Refactored
- Extracted
createActivityTracker()— eliminates duplicated tool activity wiring between foreground and background paths - Extracted
safeFormatTokens()— replaces 4 repeated try-catch blocks - Extracted
buildDetails()— consolidates AgentDetails construction - Extracted
getStatusLabel()/getStatusNote()— consolidates 3 duplicated status formatting chains - Shared
extractText()— consolidated duplicate from context.ts and agent-runner.ts - Added
ERROR_STATUSESconstant in widget for consistent status checks getDisplayName()now delegates togetConfig()instead of separate lookups- Removed unused
Tooltype export from agent-types
0.2.1 - 2026-03-05
Added
- Persistent above-editor widget — tree view of all running/queued/finished agents with animated spinners and live stats
- Concurrency queue — configurable max concurrent background agents (default: 4), auto-drain
- Queued agents collapsed to single summary line in widget
- Turn-based widget linger — completed agents clear after 1 turn, errors/aborted linger for 2 extra turns
- Colored status icons — themed rendering via
setWidgetcallback form (✓green,✓yellow,✗red,■dim) - Live response streaming —
onTextDeltashows truncated agent response text instead of static "thinking..."
Changed
- Tool names match Claude Code:
Agent,get_subagent_result,steer_subagent - Labels use "Agent" / "Agents" (not "Subagent")
- Widget heading:
●when active,○when only lingering finished agents - Extracted all UI code to
src/ui/agent-widget.ts
0.2.0 - 2026-03-05
Added
- Claude Code-style UI rendering —
renderCall/renderResult/onUpdatefor live streaming progress- Live activity descriptions: "searching, reading 3 files…"
- Token count display: "33.8k token"
- Per-agent tool use counter
- Expandable completed results (ctrl+o)
- Distinct states: running, background, completed, error, aborted
- Async environment detection — replaced
execSyncwithpi.exec()for non-blocking git/platform detection - Status bar integration — running background agent count shown in pi's status bar
- Fuzzy model selection —
"haiku","sonnet"resolve to best matching available model
Changed
- Tool label changed from "Spawn Agent" to "Agent" (matches Claude Code style)
onToolUsecallback replaced with richeronToolActivity(includes tool name + start/end)onSessionCreatedcallback for accessing session stats (token counts)env.tsnow requiresExtensionAPIparameter (asyncpi.exec()instead ofexecSync)
0.1.0 - 2026-03-05
Initial release.
Added
- Autonomous sub-agents — spawn specialized agents via tool call, each running in an isolated pi session
- Built-in agent types — general-purpose, Explore (defaults to haiku), Plan, statusline-setup, claude-code-guide
- Custom user-defined agents — define agents in
.pi/agents/<name>.mdwith YAML frontmatter + system prompt body - Frontmatter configuration — tools, extensions, skills, model, thinking, max_turns, prompt_mode, inherit_context, run_in_background, isolated
- Graceful max_turns — steer message at limit, 5 grace turns, then hard abort
- Background execution —
run_in_backgroundwith completion notifications get_subagent_resulttool — check status, wait for completion, verbose conversation outputsteer_subagenttool — inject steering messages into running agents mid-execution- Agent resume — continue a previous agent's session with a new prompt
- Context inheritance — fork the parent conversation into the sub-agent
- Model override — per-agent model selection
- Thinking level — per-agent extended thinking control
/agentand/agentscommands