Skip to content

Release v1.7.3: Customize → Agents + remove unused pi SDK#47

Merged
mohitsoni48 merged 42 commits into
mainfrom
feature/skills
Jul 6, 2026
Merged

Release v1.7.3: Customize → Agents + remove unused pi SDK#47
mohitsoni48 merged 42 commits into
mainfrom
feature/skills

Conversation

@mohitsoni48

@mohitsoni48 mohitsoni48 commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • New: Skills. A shared library of reusable instructions (SKILL.md format) any conversation can enable — via the / 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.
  • New: Agents in Customize. Built-in personas are now editable in place — system prompt, skills, and tool access — with a Reset button, and users can create fully custom agents with their own skill/tool allow-lists (MCP tools grouped per server with a select-all toggle). Two new built-ins: Lite and Code.
  • Removed: the standalone Agents screen. v1.7.2 shipped a separate "Agents" nav tab for background tasks (web_search/fetch_url/run_code only). It's retired here, replaced by Skills used directly in chat — no separate surface to launch into or reconnect to.
  • Along the way, this branch also tried a pi-SDK-based rewrite of that Agents screen (real shell/file execution) and then abandoned it in favor of folding Skills into chat instead — so this PR also removes the now-dead pi SDK code (@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 released package.json — not a user-facing change, just cleanup of unshipped churn.

Test plan

  • npm run typecheck — clean
  • npm test — 685/685 passing
  • npm run build (backend + frontend) — succeeds
  • Manually verified against a live daemon on the maintainer's real config: Customize → Agents (create/edit/reset agents, tabbed skill/tool checklists, grouped MCP tools), Skills, chat, and MCP tool calling all working

Release steps remaining (per docs/RELEASE.md)

  • Merge this PR
  • Tag v1.7.3, push tag, gh release create
  • npm publish (maintainer's login/2FA)
  • Discord announcement (dry-run + explicit approval first)
  • docs/ repo: CHANGELOG entry + TODO drain + ADR status flips

…, 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.
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.
…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.
@mohitsoni48 mohitsoni48 merged commit eac41c6 into main Jul 6, 2026
1 check passed
@mohitsoni48 mohitsoni48 deleted the feature/skills branch July 6, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant