Release v1.7.3: Customize → Agents + remove unused pi SDK#47
Merged
Conversation
…, pi adapter Spec 13 §§2-4. Adds the agent data model and the pi-SDK boundary: - AgentType/AgentsConfig schema + config wiring (config.ts) - SkillStore + 5 builtin skills (filesystem/web/code/task-tracking/compose) - Default-deny FS guard with symlink-safe containment (realpathSync canonicalization) - pi-adapter: local-gateway provider, tool bridge (delegates to ToolRegistry.executeTool), guard enforced inside each custom tool's execute (SDK has no extension tool_call hook), builtins disabled via noTools:'builtin' - DB migration v10 (agent_runs.agent_id) - pin @earendil-works/pi-coding-agent@0.80.2 (exact) Conforms to real pi defineTool contract (TypeBox params, 5-arg execute). 20/20 tests pass.
…uilt routes Spec 13 §§3.4,5-7. Daemon-owned agent runs on the pi SDK: - AgentRunManager: ports the reconnect machinery (RunBuffer ring, EventEmitter live-tail, subscribe replay, reconcileOnStartup, interruptActive) from the retired AgentRunner, but drives runs via runAgentSession (pi) — resolves agent skills→tools, builds FS + bridged tools + the guard, streams to the run's conversation. - Gate-wrapped provider (§3.4): custom streamSimple acquires GenerationGate at 'bg' priority around pi-ai's openai-completions streamer, releases on stream completion, so foreground chat preempts background agent engine calls. - Rebuilt agent-routes: agent CRUD + skills CRUD + run/stream/cancel; host-disk config gated to isLocalRequest. - Deleted retired runner.ts; rewired deps.agents + cli startup. Full project typechecks, build succeeds, 20/20 agent tests pass.
Validated Phase 2 end-to-end against live gemma-4-26B (streaming + tool round-trip both confirmed). Two fixes the smoke test surfaced: - cwd: pass the agent's primary write root as pi's cwd + SessionManager dir, so pi's default system prompt treats that folder as the workspace. Without it the model refused paths 'outside the project' (pi defaulted cwd to the repo dir). - toolResultText: pi's tool_execution_end.result is an AgentToolResult object, not a string — extract its text content so the SSE result field isn't '[object Object]'.
Spec 13 §8. Replaces the old background-task screen with the agent surface: - AgentsScreen: three tabs — Contracts (launch + streaming transcript with tool-call rows), Agents (list + Hire/edit form: name, description, skill multi-select, folder scope; default agent editable but not deletable), Skills (library CRUD; builtins read-only). - agent-api: agent CRUD + skills CRUD + per-agent run launch; keeps the SSE generator. - agent-types: AgentType + Skill + updated AgentRun (agentId). Routing (/agents) + nav already pointed here. Web typecheck + build pass.
…etion loop, auto-compact Spec 13 §§12-16: - DB v11/v12/v13: agent_run_docs (durable working doc), agent_track_record (per-Hitman per-model outcomes), agent_runs.archived_at/completion. Plus getRunDoc/upsertRunDoc, addTrackRecord/trackRecordForAgent/modelStatsForAgent, setRunDisposition, list active/ archived, pruneTrackRecordForAgent. updateConversation gains modelKey. - task-tools: update_doc (working doc) + complete_task (the model's done-signal), granted by the task-tracking skill; wired into every run. - Routes: /complete (record+archive), /flag-miss (record miss+feedback), /doc, /:id/track-record (rows+modelStats), /:id/archive. Active runs list excludes archived. Track record pruned on agent delete. - Context-awareness (§12.1): enable pi's NATIVE auto-compaction (setAutoCompactionEnabled) instead of a hand-rolled 80% loop — the working doc survives because it's DB-persisted, not in pi's transcript. Surface compaction_start/end to the UI. Full backend typechecks, 20/20 tests pass.
…ve, track-record warning Spec 13 §§14-15. Extends the Agents screen: - Disposition bar on a finished, non-archived run: Mark complete / Reply / Flag miss (Flag miss takes a typed correction). Records the per-model outcome. - Collapsible Progress doc viewer (fetchRunDoc), refetches while running. - Archived contracts list (per-Hitman, read-only). - Track-record warning: amber when the loaded model has >=5 contracts and <60% success on this Hitman; suggests the best-performing model. Silent on thin data (fixed thresholds). - agent-api: completeRun/flagMiss/fetchRunDoc/fetchTrackRecord/fetchArchive; AgentRun gains archivedAt/completion. Web typecheck + build pass.
Live test surfaced a real hang: a small model (gemma-26B) looped calling update_doc forever without complete_task, holding the engine slot indefinitely (spec §12.2's 'open-source model never self-declares done' case). pi's createAgentSession has no maxSteps option, so the ceiling was never enforced. Count tool calls in the guard (it fires before every tool execution); once maxIterations (default 30) is exceeded, abort the run + block further tools. Reaching the ceiling surfaces as 'done' (awaiting review) with an explanatory error — NOT cancel/fail — so the user dispositions it like any finished contract. Validated live end-to-end: agent CRUD with skill subsets, working doc (update_doc), complete_task, Mark complete + Flag miss disposition, per-model track record + stats aggregation, archive, and track-record prune on agent delete.
…eaks, state corruption Two adversarial reviews (security + robustness) against the agent feature. Fixes: SECURITY (filesystem + authz): - fs-guard: rewrite canonicalization to resolve symlinks via nearest existing ancestor (a symlink anywhere in the path can't escape); case-fold on Windows; reject embedded NUL / non-absolute / drive-relative / UNC roots; check the RIGHT field per tool (glob uses , not ); delete the parent-escape () clause. - glob: confine the walk to the validated root, cap results (5000) + depth (25), never recurse symlinked dirs (no filesystem-wide OOM/cycle DoS). - run_code is never bridged into an autonomous run (no human-in-loop). - config.validate(): enforce agent schema — writeRoots ⊆ ~/.turbollm (v1 invariant), absolute roots, unique ids, exactly one builtin, maxIterations 1–200. - SkillStore.write/delete validate the id (no path escape); routes mirror it. - local-gate run-launch, disposition, skill-delete; cap userMessage/feedback/title; cap agents (100) + skills (200); coerce NaN fromSeq. ROBUSTNESS (hangs / leaks / state): - gate leak: forwardStream no longer re-throws (no unhandled rejection); guaranteed release on synchronous stream-construction failure. - H1 eviction: wire interruptActive() into engine start/stop/restart; thread the run's abort signal + a 10-min generation timeout into pi's fetch so a killed/stalled engine can't wedge the gate forever. - run-manager: pre-run DB writes moved inside try (deleted-conversation can't wedge the queue); null-safe assistantMsg; always emit a synthetic terminal done/error SSE frame. - disposition routes: reject on non-finished status + already-dispositioned (no duplicate track-record rows / stat corruption); flag-miss archives. 25/25 tests pass (added symlink-escape, glob-field, write-scope, NUL, skill-id tests). Full typecheck + build green.
…xed root, sandboxed scripts) Spec 13 redesign (ADR-133/137). Drops the rejected 'Hitman' concept for a simple model: an Agent = a persona with powers, chatted with on its own surface. Backend (rides chat's existing tool loop, NOT pi — pi is for the swarm later): - conversations.agent_id (DB v14); null = plain chat, byte-identical to today - agent-tools.ts: guarded FS tools (read/list/glob/write) + compute-sandbox run_code, in ToolRegistry shape, all routed through the hardened makeToolCallGuard - chat-routes: when agent-bound, inject the agent's CURRENT system prompt + folder note at gen time + merge its tools + route them to the guarded executor - AgentType gains systemPrompt (an agent IS a persona); write root FORCED to ~/.turbollm in CRUD (not user-settable); conversation create validates agentId - run_code description fixed (needs , not last-expression eval) Frontend: - AgentsScreen rebuilt: agent conversation sidebar + agent picker for new chats + a manage-agents panel (name, system prompt, skills, read-folders; write shown read-only). Reuses the chat conversation/streaming components. No Hitman/contract/disposition. 42 backend tests pass (4 new toolset tests); web + backend typecheck + build green.
…omplete) Spec 13 redesign (ADR-134/135; Reflexion). The agent learns from flagged tasks: - DB v15: conversations.completed_at + agent_lessons (per-agent, FTS-ready). Methods: markConversationComplete, addAgentLesson, listAgentLessons, pruneAgentLessons. - reviewer.ts: the constrained reviewer using the Phase-0-validated recipe (NO_LESSON default + evidence/citation required; reasoning_budget:0 + json_object + fence-strip + exact model alias). Returns a lesson or null; rejects lessons without evidence. - Routes: POST /conversations/:id/complete (archive, no AI) and /reflect-complete (archive + DETACHED reviewer → store a per-agent lesson). The human 'reflect' click is the evidence gate (avoids the Huang et al. self-correction trap). - Lesson INJECTION: the agent's top-3 recent lessons are added to its system prompt at generation time → it applies what it learned on future chats. - Lessons pruned on agent delete. - Frontend: 'Complete' / 'Reflect & complete' bar above the composer + a 'completed' badge. Live-validated end-to-end vs gemma-26B: friction conversation → specific evidence-cited lesson stored; clean conversation → no false positive; lesson retrievable for injection. 42 tests pass; web + backend typecheck + build green.
…lder / a conversation) Spec 13 redesign (ADR-135; Voyager). Fixes the original complaint: skills are GROWN, not hand-authored JSON. - DB v16: agent_skills (per-agent: name, description, procedure, source). Methods incl. hasAgentSkillNamed (Curator dedupe), countAgentSkills (cap), prune on agent delete. - distiller.ts: distillFromFolder (reads ~20 text files under a folder → a reusable skill) + distillFromConversation (a successful task → a skill). Uses the validated gateway recipe (reasoning_budget:0 + json_object + fence-strip + exact alias). - Routes: POST /agents/:id/learn-folder (the 'point it at a folder' feature, local-gated), POST /conversations/:id/save-skill, GET /agents/:id/learned, DELETE a skill. Skill cap 50, name-dedupe. All distills run DETACHED. - Skill INJECTION: the agent's top-3 skills (name + description + procedure) are added to its system prompt at generation time, alongside lessons. - Frontend: 'Save as skill' button in the completion bar; a 'Learned' section in the agent editor (learn-from-folder input + a live-polled list of grown skills/lessons with delete). Live-validated vs gemma-26B: a folder of changelogs → a 'generate-changelog-template' skill; a successful conversation → an 'aggregate-markdown-files' skill — both well-named + reusable. 42 tests pass; web + backend typecheck + build green.
The production bundle (dist/cli.js) crashed on startup: 'Dynamic require of child_process is not supported' — esbuild can't inline pi's cross-spawn (CommonJS require) into ESM. Invisible in dev because tsx doesn't bundle; only the built dist hits it. Broken since pi was added (Phase 2). The shipped agent feature (redesign Phases 1-4) rides chat's own tool loop and does NOT use pi — the pi-based AgentRunManager + its old Hitman run routes were dead code. Removing the cli.ts wiring lets esbuild tree-shake the entire pi chain (run-manager/pi-adapter/task-tools/ pi-fs-tools/pi SDK) out of the bundle. Result: dist/cli.js 8.11 MB → 484 KB, 0 pi refs, boots cleanly, agent endpoints live. The pi machinery stays in the tree for the Phase-5 swarm (re-introduce behind a pi-external tsup config then). pi also belongs in turbollm/package.json deps when Phase 5 ships, not root.
When an agent conversation triggers a background LLM task (Reflect & complete → reviewer, Save as skill / Learn from folder → distiller), there was no signal it was running. Surface them: - AgentTaskState: in-memory bg-task registry (start/step/done/fail, convId tagging, 50-task cap, 5-min GC) mirroring BuildState; exposed via GET /api/v1/status as `agentTasks`. - reflect-complete / save-skill / learn-folder emit task steps and a settled result (lesson learned / skill learned / nothing to learn). - Agents screen: inline clickable status rows under the conversation for tasks tied to the active chat, a "N working" header pill, and a right-side panel showing each task's step log + result/error. No new polling — rides the existing /status poll. No extra deps.
Two agent-UX fixes that were making the agent feel broken.
1. Streaming never looks hung. Tool calls were stacked in a detached
panel above the reply, and the activity footer hid during tool
execution and the re-prefill gap between agent-loop steps, so a
working agent looked frozen. Replace it with an ordered, interleaved
timeline (text + tool steps in arrival order, shared by chat +
agents): each running tool shows an inline spinner + elapsed timer,
and an always-on activity line ("Working…/Processing prompt…/tok·s")
guarantees a visible heartbeat at every moment.
2. Skills are SKILL.md files, authored from chat (skill-creator model).
- Storage moves from DB rows to a shared library of Claude-style
SKILL.md files at ~/.turbollm/skills/<id>/SKILL.md (frontmatter
name + description, markdown instructions). SkillStore reads/writes
that format (legacy <id>.json still read for back-compat).
- Creation is in-chat only: the agent gets a save_skill tool that
triggers a background job (saveSkillFromConversation) which reads
the conversation and writes the SKILL.md. Shared by the (now
removed) button's route. Surfaces in the bg-task indicator.
- Saving is NOT proactive: the agent only calls save_skill when the
user explicitly asks to make a skill, and must never reach for an
external memory/knowledge-graph tool. Using skills stays proactive
(the library is injected into the agent's system prompt).
- learn-from-folder now writes one SKILL.md per file; the manage
panel's "Skill library" lists/deletes library skills.
Tool-call fragments were accumulated silently and the pending tool_call SSE event only fired at execution time — after the model finished streaming the whole call. So a long tool-argument generation (e.g. the model composing a big save_skill body after "let me save all 39 skills") produced zero events and looked frozen. Emit the pending tool_call as soon as the tool's name+id arrive, before the arguments stream. The execution-phase pending event reuses the same id, so the frontend merges it into the one inline step — which now shows its spinner + elapsed timer for the entire generation, no dead air.
…te UI Three fixes from testing against a real skill library. 1. Learn-from-folder imports SKILL.md skills VERBATIM. Pointing the agent at an existing skill library (e.g. a Claude/Codex skills dir) re-distilled every file through the LLM and saved them under random names. Now we detect SKILL.md files and copy them preserving the folder name + frontmatter — no LLM, no renaming. The parser tolerates CRLF, a BOM, and quoted frontmatter values; a skill folder is a leaf (we don't descend into its references/). Only folders WITHOUT any SKILL.md fall back to LLM distillation. Cap raised 50 → 200 (injection still uses only the top 8). 2. Read access is CHAT-bound, via a file/folder picker. Removed the per-agent "read folders" textarea; read scope now lives on the conversation (new read_scope column, v17) and is attached through the FsBrowser picker (new 'any' mode: pick a file or the current folder). The agent's read guard uses conv.readScope; home-confined like /fs/browse. New routes: POST/DELETE /conversations/:id/read-scope. 3. Removed the "Write folder" row from agent settings — writes always go to ~/.turbollm and were never configurable.
The agents were running on chat's compute-only tool loop: run_code is a
JS sandbox with no filesystem or process access, so the agent literally
could not start a server, run a script, or read a file back — it just
flailed (e.g. "execute wan animate" went nowhere). The pi coding-agent
integration that does this for real already existed but was orphaned.
- Reinstall @earendil-works/pi-coding-agent + pi-ai (0.80.2).
- Fix the bundling crash that got pi unwired: mark pi external in tsup so
its dynamic CommonJS require('child_process') resolves at runtime
instead of being inlined by esbuild (the "Dynamic require" crash).
Proven: the production bundle boots clean with pi wired.
- Re-wire AgentRunManager in cli.ts (the run routes + reconnectable SSE
were already present, just inert).
- Enable pi's REAL built-in tools (read, bash, edit, write) instead of
disabling them — the agent now has actual shell + file power. Dropped
the guarded compute-only duplicates that crippled it.
- Thread a permission `mode` ('ask'|'auto'|'bypass'|'read') into the
adapter; 'read' restricts to read-only built-ins, others get full
execution. UI selector + 'ask' approval gate land next.
Verified end to end: a run executed `echo`, wrote a file, and read it
back — real bash + filesystem, confirmed on disk.
Backend foundation for wiring the Agents chat onto pi (each message = a pi run that continues the thread). - conversations.agent_mode column (v18) + setConversationMode; the pi permission mode is per-conversation. - AgentRunManager.launch accepts an existing convId: it runs the pi turn in that conversation and feeds pi the prior transcript as context, so follow-up messages continue the same thread. The mode flows through to the adapter. - Routes: POST /agents/conversations/:convId/run (run a turn in the thread, using the conversation's mode) and POST /agents/conversations/:convId/mode (set ask|auto|bypass|read). The frontend (send via this route, stream the run, mode dropdown in the chat header) lands next.
The Agents screen now drives the pi engine instead of the old crippled chat loop. Each message launches a pi run that continues the thread and streams real tool execution inline. - send → POST /agents/conversations/:id/run → stream the run's SSE (delta/reasoning/tool_call/done/error) into the existing live timeline, so bash/file/edit steps render inline as they happen. - Stop cancels the run server-side (DELETE the run), not just the client stream. - Permission-mode dropdown in the chat header (auto/bypass/ask/read), per conversation, persisted via POST /agents/conversations/:id/mode. - agent-api: runAgentTurn, streamAgentRun, setAgentMode; chat-types + Conversation.agentMode. Dropped the dead chat-loop send path.
…closes empty A client that opened the run's SSE stream right after launch (the UI does this immediately) got an instantly-closed stream: subscribe() returns a done iterator when the run's emitter doesn't exist yet, but the emitter was created later in processNext (async, or after a queued run). Create the buffer + emitter in launch() so an early/queued subscriber finds them and waits for events. Verified: reasoning, tool_call (bash), and delta events now stream live end to end.
pi's tool_execution_end event carries only the tool id (no toolName), so the UI was merging an empty name over the real one from the start event — every completed tool showed "undefined". Remember the name from the start event and echo it on end; also harden upsertToolCall so a missing name/args can never clobber the values captured on the pending event.
…hats Pass A of the agents UX overhaul. - Completing a task ARCHIVES it: the conversation is filtered out of the active sidebar list and shown under a collapsible "Archived" section. - Complete LOCKS the chat: the composer + send are disabled, and the run route rejects a completed conversation (409). A "Reopen" button clears completed_at so it accepts messages again (db.reopenConversation). - Agent chats AUTO-NAME from their content after the first run (reuses the chat auto-title; fired from the run-manager on done).
Pass B. Replaces the clunky "Files the agent can read / No files attached" bar with a "+" button in the composer that opens a small menu: Attach file / Attach photo / Attach folder. Attached items show as compact, removable chips inside the composer. File → file picker, Folder → folder picker; all add to the conversation's read scope. (Photo currently uses the file picker + read scope; vision passthrough is a separate follow-up.)
…evices Agent routes were loopback-only (isLocalRequest), so triggering an agent from another device on the LAN returned 403 — you couldn't drive your own box from a phone/laptop. Add isLocalOrAuthenticated: permits a remote client when the daemon REQUIRES an API key (lanAuth has already verified it), while an open (keyless) LAN still can't trigger host code execution. Swap all agent routes (runs, config, skills, learn-folder) to it. Engine add/scan (caller-supplied binary) stays strictly loopback-only. To use: enable "Require API key" (Settings → Network), create a key (Developer screen), and paste it on the other device.
Two issues from testing: 1. No processing signal. Agent runs are minutes long and outlive a page refresh / tab switch, but the UI kept the live stream only in component state — so after a reload the run kept working with zero signal in the UI. Reconnect on conversation open: find a 'running' run for it and re-attach to its (replayable, seq-0) stream. Also drop the empty assistant placeholder the run-manager persists, so it doesn't render as a blank bubble next to the live one. 2. Auto-naming was an ungated model call that competed with the engine. Acquire the gate at 'bg' priority so title generation always yields to foreground chat/agent work (it's a low-priority afterthought, "second").
Split the AgentsScreen monolith into two surfaces:
- Workspace nav (was "Chat", new PanelsTopLeft icon) hosts a Chat | Agent
tab bar. Chat = plain LLM chat; Agent = agent conversations — the chat
half moved verbatim into workspace/AgentChatView.
- Agents nav is now pure management: a grid of agent cards → full-page
edit (agents/AgentEditPage). No conversations there.
Chat-tab sidebar now excludes agent conversations. Routes moved to
/workspace/{chat,agent}; /chat/:convId kept so LAN share links survive.
The agent engine's pi SDK spawns its shell tool without windowsHide, so
every bash step popped a console window when the daemon has no console of
its own (e.g. after a detached self-restart). pi is external and does a
named `import { spawn }`, so reassigning child_process.spawn can't reach
its binding — instead patch the shared ChildProcess.prototype.spawn (the
funnel every spawn path hits) to force windowsHide:true on win32. A
headless daemon never wants a child console window. Installed first thing
in cli.ts so it's active before anything spawns.
Agent turns run through the pi SDK, which exposes no stream seam to read llama.cpp's prompt_progress, so the Agent tab only showed a spinner during the (minutes-long) prefill while plain chat showed a "Processing prompt — N%" bar. Add a header-gated global fetch tee: agent engine requests carry an x-turbollm-run header and return_progress (via pi-ai's onPayload), and the tap reads prompt_progress off that one response, forwarding it to the run's SSE as the existing 'progress' event. Passthrough is byte-identical so pi's own parsing is untouched, and the tap is fully defensive — any failure falls back to the original response and can never break a run. AgentChatView renders it with the same prefill bar as chat.
…PI-key prompt - Agents grid + edit page are now full-width (two-column edit on lg+), dropping the cramped max-w column the user flagged. Grid scales to 4–5 cards per row. - The edit page's Skills section now lists the whole shared library (fetchSkills) instead of two hardcoded entries, so a skill saved from ANY agent can be re-picked for a new one. "Grant all skills" maps to the ['*'] grant. - AuthGate is latched once a 401 is seen and stays up until a poll finally succeeds. On a flaky LAN link (the remote machine where you paste the key) an occasional poll flips 401 → generic error, which used to tear the dialog down, swap in the "lost connection" overlay, and wipe the half-typed key. Sticky mount keeps the input's value + focus.
…them Skills are now a first-class, independent library instead of living inside the agent editor. The Agents nav gains an "Agents | Skills" tab bar: - Skills tab: the shared library — list every skill (built-in + learned), create a skill manually (name / id / description / tools / instructions), edit or delete non-builtin ones (built-ins are read-only), and learn one from a folder. - Agents tab: unchanged grid → editor; the editor keeps only the Skills *picker* (which library skills this agent may use) and links to the Skills tab to manage them. The learn-from-folder + management UI moved out of the agent editor. Routes: /agents/skills and /agents/skills/:skillId (static, so they outrank /agents/:id). Skills stay global (~/.turbollm/skills), so one saved via any agent shows up here and is re-pickable by any other.
An agent's tools no longer come indirectly from its skills' tool lists. Every tool in the registry — built-ins (web_search, fetch_url) + all connected MCP servers — is now available to an agent by default, and the editor exposes a Tools section to switch individual ones off per agent. - AgentType gains `disabledTools: string[]` (denylist; empty = all on). - buildBridgedTools flips from a skill-granted allowlist to "all registry tools minus NON_BRIDGEABLE (run_code) minus the agent's disabledTools". Skills keep shaping the system prompt; they no longer gate tool access. - New GET /api/v1/agents/tools returns the full catalog (name + description, incl. MCP) for the editor. - Editor: a Tools picker, every tool on by default, unchecking adds to disabledTools. pi's read/shell/edit/write stay controlled by the per-chat permission mode. Verified: catalog returns 79 tools (web_search, fetch_url, Playwright/Memory/ GitHub MCP); editor shows "79/79 on" and toggling decrements the count.
…ace, fixed root, sandboxed scripts)
Skills are now a shared SKILL.md library any chat conversation can enable directly (Thread Settings, or a new '/' picker in the composer) instead of requiring a bound Agent. The 'skill-creator' skill grants the save_skill tool so the model can distill the current conversation into a new skill on explicit request. No FS/shell tools are exposed to chat yet (that's deferred to a future dedicated Code surface). - conversations.skill_ids (v21) replaces agentId/readScope/agentMode - chat-routes.ts: skill instructions injected into the system prompt; save_skill granted only when 'skill-creator' is enabled - agent-tools.ts: buildAgentToolset (FS/code) replaced with the minimal buildSaveSkillTool - agent-routes.ts: stripped Agent CRUD/Runs/Hitman-disposition routes; kept skill CRUD + added /skills/import (upload) and /skills/learn-folder (now agentless) - removed the Agents nav item, the Workspace Chat|Agent tab strip, and the AgentsScreen/AgentEditPage/AgentChatView screens - Skills promoted to a top-level /skills route with an upload button
…s into Customize - db.ts: guard the v16/17/19/20/21 ADD COLUMN migrations with a column- existence check — this branch's migration numbering has been reshuffled across its history, so a real database can have a column already added under an earlier version number, which previously crashed startup with "duplicate column name" - ChatScreen composer: Tab now completes the '/' picker's highlighted match into the input (Enter still selects + enables); previously Tab did nothing useful - Skills moved out of the top-level nav into a section inside Customize, next to MCP Servers, instead of being a separate side-nav destination
…lls/MCP The 'awaiting_approval' status only ever landed in live.timeline (the interleaved text/tool display); live.toolCalls, which ToolApprovalBar's visibility check actually reads, was initialized empty and never updated. So the approval popup could never appear — any 'ask'-policy tool call (fetch_url, etc.) sat invisibly stuck until the connection eventually dropped and auto-denied it. Derived pendingApproval from the timeline instead and removed the dead toolCalls field. Also: Customize is now two tabs (Skills / MCP Servers, ?tab= addressable) instead of both sections stacked on one scrolling page, and the skill editor's "back" now returns to /customize?tab=skills instead of the removed /skills route (which was falling through to the chat redirect).
…of clearing the input
selectSkill() (used by both click and Enter) blanked the composer after
enabling a skill. Now it fills in '/{id} ' the same way Tab-complete does,
so you can see what was just enabled and keep typing your message.
The system-prompt injection collapsed newlines and sliced every enabled skill's instructions to 300 characters before sending them to the model. Fine for the old built-in one-liners, but a real skill (e.g. wan-animate-reel, ~13KB) was having ~97% of its procedure silently dropped — the model never saw the actual steps, only a truncated fragment of the intro. Now injects the full instructions with formatting intact (headers, code blocks), capped at 20,000 chars per skill purely as a safety net against a pathological file, not an everyday limit.
…bles the skill Root cause of "the skill didn't follow": Tab-complete (by design, per earlier request) only fills the composer text — it never calls the enable mutation. If the user then typed their real message after the tab-completed name and just hit send, the literal "/wan-animate-reel" text went to the model as plain content while conv.skillIds stayed empty, so none of the skill's instructions were ever injected. send() now parses a leading '/skill-id' token against the real skill library regardless of how it got there (click, Tab, or hand-typed), strips it from the text before it reaches the model (the skill's own instructions may define a different natural-language trigger), and enables that skill — via the create-conversation payload for a new chat, or an update mutate awaited before sendMessage for an existing one, so the system-prompt injection sees it on this very turn.
mohitsoni48
added a commit
that referenced
this pull request
Jul 6, 2026
…ools The allowed_tools allow-list (Customize → Agents) was filtering the combined built-in+MCP+skill-granted tool list, but skill-granted tools like save_skill are never enumerated by /api/v1/tools (they only exist once their skill is enabled), so they could never appear in the allow-list checklist. Any custom or overridden agent with 'skill-creator' enabled AND a non-empty tools allow-list would silently lose save_skill. Scope the filter to the base (built-in + MCP) registry only — enabling a skill is already the allow decision for the tools it grants. Caught during self-review before merging PR #47. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… system-prompt agents New Agents tab in Customize (alongside Skills and MCP Servers): built-in personas can now be edited in place (name/description/system prompt/skill+tool access) with a Reset button to restore the default, and users can create their own custom agents with a name, description, system prompt, and checklists of which shared skills and which tools the agent may use (everything on by default). MCP tools are grouped by server in the tool checklist, with one toggle to select/deselect all of a server's tools at once. Also adds two new built-in agents: Lite (bare-bones system prompt for the fastest responses) and Code (tuned for precise, idiomatic code). Backend: new customAgents + builtinAgentOverrides config blocks and /api/v1/chat-agents* routes, plus a per-conversation tool allow-list (allowed_tools column) baked in from the selected agent at creation time. The allow-list only scopes the built-in/MCP tool registry — a skill's own granted tools (e.g. save_skill from skill-creator) aren't re-gated by it, since enabling the skill is already the allow decision for its tools.
eaa9ce4 to
d8530ff
Compare
…1.7.2 The pi SDK and its background-agent engine were added and removed entirely within this unreleased window (v1.7.2's package.json never depended on it), so removing it isn't a user-facing change. Skills didn't exist in v1.7.2 either, so the bug-fix commits along the way were fixing an unreleased feature, not something users had. The real, honest diff since v1.7.2: the standalone Agents screen (which WAS shipped in v1.7.2) is retired, replaced by the shared Skills library used directly in chat, plus the new Agents tab in Customize.
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.
Summary
/picker (Tab-complete included) or a conversation's settings. Manage it from Customize → Skills: upload directly, or have TurboLLM learn one from a folder or distill one from an existing conversation.@earendil-works/pi-ai/pi-coding-agent) that a prior commit on this same branch introduced. Net effect on the published package: zero, since pi was never in a releasedpackage.json— not a user-facing change, just cleanup of unshipped churn.Test plan
npm run typecheck— cleannpm test— 685/685 passingnpm run build(backend + frontend) — succeedsRelease steps remaining (per
docs/RELEASE.md)v1.7.3, push tag,gh release createnpm publish(maintainer's login/2FA)docs/repo: CHANGELOG entry + TODO drain + ADR status flips