Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,11 +360,12 @@ Shared modules live in `src/components/graph/`: `analyticsUtils.ts`, `analyticsH
| `electron/ipc/agent.ts` | All `agent:*` IPC channels — PTY spawn/kill, file I/O, git diff, `assertWithinCodeDirectory` |
| `electron/ipc/chat.ts` | AI chat loop — `runToolLoop` + IPC handler registration |
| `electron/ipc/chat-executor.ts` | `executeTool` — all AI tool implementations |
| `electron/ipc/pi-agent.ts` | Cairn native agent IPC handler — `sessions` Map, all `pi-agent:*` channels |
| `electron/lib/pi-agent-loop.ts` | `runAgentLoop()` — multi-turn SSE loop, parallel tool execution, callbacks, retry, `transformContext` hook; `AgentToolContext` groups cwd/db/IPC/session state |
| `electron/lib/compaction.ts` | `buildCompactionTransformer` — async LLM-based context compaction; fires `pi-agent:compact` events; falls back to sliding-window pruner while summary is in flight |
| `electron/ipc/pi-agent.ts` | Cairn native agent IPC handler — `sessions` Map, all `pi-agent:*` channels; `runSession()` shared helper wires compaction + callbacks + `runAgentLoop` for both `prompt` and `approve-plan` handlers |
| `electron/lib/pi-agent-loop.ts` | `runAgentLoop()` — multi-turn SSE loop, parallel tool execution, callbacks, retry, `transformContext` hook, `shouldStopAfterTurn` hook; `AgentToolContext` groups cwd/db/IPC/session state |
| `electron/lib/compaction.ts` | `buildCompactionTransformer` — async LLM-based compaction, signal read live from session; `compactNow` — synchronous on-demand compaction for `/compact` slash command |
| `electron/lib/truncation.ts` | `truncateOutput(text, opts)` — unified byte+line cap for all coding tool outputs; exports `DEFAULT_MAX_BYTES`, `DEFAULT_MAX_LINES`, `TruncationResult` |
| `electron/lib/pi-agent-prompt.ts` | System prompt builder; mandatory Cairn workflow; `spawn_subagent` description |
| `electron/lib/coding-tools/` | 8 coding tools (`read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`, `spawn_subagent`) + `file-mutex.ts` |
| `electron/lib/coding-tools/` | 8 coding tools (`read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`, `spawn_subagent`) + `file-mutex.ts`; all tools use `truncateOutput` from `truncation.ts` |
| `electron/lib/llm.ts` | `LLMConfig`, `callLLM`, `streamCompletion`, `isLocalEndpoint`, `normaliseBaseUrl` |
| `electron/lib/tools.ts` | `TOOLS` (OpenAI function definitions), `TOOL_LABELS`, `buildSystemPrompt` |
| `electron/lib/context.ts` | `buildContextResponse` — canonical `get_cairn_context` response |
Expand Down Expand Up @@ -572,6 +573,7 @@ renderer main process
│ ◄── pi-agent:usage ──────────────┤ token counts → context ring
│ ◄── pi-agent:retry ──────────────┤ (on transient error) countdown badge
│ ◄── pi-agent:compact ────────────┤ (when compaction fires) "Compacting…" indicator
│ ◄── pi-agent:compact-result ─────┤ (/compact slash command result)
│ ◄── pi-agent:done ───────────────┤ turn complete
```

Expand All @@ -581,7 +583,15 @@ The `pending` status fires during streaming as soon as a tool name is seen in th

**Automatic retry** — `isRetryable(status, body)` in `pi-agent-loop.ts` detects transient errors (429, 5xx, overloaded/rate-limit body patterns). Up to `AgentLLMConfig.maxRetries` attempts (default 3) with exponential backoff starting at `baseRetryDelayMs` (default 2000 ms). `onRetry` callback fires before each sleep; `pi-agent:retry` is emitted to the renderer.

**Context compaction** — `buildCompactionTransformer` in `electron/lib/compaction.ts` returns a `transformContext` function passed to `runAgentLoop`. When `session.lastPromptTokens` exceeds 80% of `llmConfig.contextWindow`, it fires a background LLM summarisation call (non-streaming, `temperature: 0.1`). The current turn uses a sliding-window fallback; subsequent turns use the cached summary. `onCompactionStart`/`onCompactionEnd` fire `pi-agent:compact` events to the renderer. To change the threshold or number of verbatim turns preserved, edit `COMPACT_THRESHOLD` and `KEEP_RECENT_TURNS` in `compaction.ts`.
**Context compaction** — `buildCompactionTransformer` in `electron/lib/compaction.ts` returns a `transformContext` function stored on `PiAgentSession.compactionTransformer` (built once, reused across prompts so the `cachedSummary` survives multi-turn sessions). When `session.lastPromptTokens` exceeds 80% of `llmConfig.contextWindow`, it fires a background LLM summarisation call (non-streaming, `temperature: 0.1`). The signal is read live from `session.abortCtrl` each invocation. The current turn uses a sliding-window fallback; subsequent turns use the cached summary. `pi-agent:compact` IPC events drive the `"Compacting context…"` status bar indicator. To change the threshold or number of verbatim turns preserved, edit `COMPACT_THRESHOLD` and `KEEP_RECENT_TURNS` in `compaction.ts`.

**`/compact` slash command** — typing `/compact` in the chat input calls `compactNow(session, llmConfig)` in `compaction.ts` via the `pi-agent:compact-now` IPC channel. Unlike the automatic transformer (fire-and-forget), `compactNow` awaits the LLM summary call synchronously and emits a `pi-agent:compact-result` event. The renderer shows a centred italic system message: *"Context compacted — session history summarised into N messages."* `PiAgentMessage.role` includes `"system"` for this purpose; `PiMessageBubble` renders it as muted centred text.

**`shouldStopAfterTurn` hook** — `AgentLoopCallbacks.shouldStop?: (messages) => boolean | Promise<boolean>` is checked after each turn's tool results are appended, before the next LLM call. Returning `true` fires `onDone` cleanly. Use for semantic stop conditions (e.g. stop when a linked task card reaches Done) without modifying the loop. Wire it in `runSession()` in `pi-agent.ts`.

**Output truncation** — all coding tools use `truncateOutput(text, opts)` from `electron/lib/truncation.ts` instead of ad-hoc byte slicing. The library enforces a byte cap (`DEFAULT_MAX_BYTES = 50 000`) and optional line cap (`DEFAULT_MAX_LINES = 2 000`), returns a `TruncationResult` with metadata, and appends a consistent `[Truncated: showed X of Y. Use offset/limit to page.]` hint. When adding a new coding tool, import and call `truncateOutput` on the output before returning.

**`runSession` invariant** — within `pi-agent.ts`, always call `runAgentLoop` via `runSession()` rather than directly. `runSession` is the single place that builds the compaction transformer, wires all IPC-forwarding callbacks, and handles `onDone`/`onError` persistence. The only legitimate direct call site for `runAgentLoop` is `pi-agent-loop.test.ts`.

**Subagents**

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ The agent can delegate contained sub-tasks to a fresh sub-agent via `spawn_subag

A small ring in the agent pane header shows how full the model's context window is after each step. Configure the limit for your model in **Settings → AI & Chat → Context window** (presets: 8k / 32k / 128k / 200k).

When usage reaches 80% the agent automatically summarises older context with a background LLM call — the status bar shows `"Compacting context…"` while this is in flight. If a transient API error occurs, the status bar shows a countdown (`"Transient error — retrying (1/3) in 8s…"`) and the agent retries automatically.
When usage reaches 80% the agent automatically summarises older context with a background LLM call — the status bar shows `"Compacting context…"` while this is in flight. Type `/compact` in the chat input to trigger compaction on demand at any time. If a transient API error occurs, the status bar shows a countdown (`"Transient error — retrying (1/3) in 8s…"`) and the agent retries automatically.

## MCP server

Expand Down
50 changes: 50 additions & 0 deletions changelogs/v1.4.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# v1.4.0

## What's new

### Features

- **`/compact` slash command** — type `/compact` in the Pi agent chat input to trigger on-demand context compaction. The agent immediately summarises the full session history with a dedicated LLM call, then injects the summary as a structured context block. A centred italic system message confirms the result: *"Context compacted — session history summarised into N messages."* Previously compaction only fired automatically at the 80% token threshold.

- **Unified output truncation** — all coding tools now share a single `truncateOutput()` library (`electron/lib/truncation.ts`) with consistent byte-budget enforcement, structured metadata (`truncated`, `totalBytes`, `shownBytes`, `totalLines`, `shownLines`), and a uniform pagination hint so the model knows to use `offset`/`limit` to retrieve the rest. `ls` gains truncation for the first time. All tools produce the same hint format: *"[Truncated: showed X of Y bytes. Use offset/limit to page.]"*

- **`shouldStopAfterTurn` hook** — `AgentLoopCallbacks` gains an optional `shouldStop?: (messages: AgentMessage[]) => boolean | Promise<boolean>` callback checked after each turn's tool results are appended. Returning `true` stops the loop cleanly with `onDone` instead of `onError`. Enables semantic stop conditions (e.g. stop when a task card reaches Done) without modifying the loop.

- **Compaction transformer persisted across prompts** — the LLM summary cached inside the compaction transformer now survives multiple `pi-agent:prompt` calls on the same session. Previously a fresh transformer (with empty cache) was built on every prompt, discarding any summary from the previous turn. `pi-agent:clear` resets the transformer along with message history.

- **`runSession` extracted in IPC handler** — the ~120-line duplicated block (compaction setup + all callbacks + `runAgentLoop` call) shared between the `pi-agent:prompt` and `pi-agent:approve-plan` handlers is now a single `runSession()` helper. Both handlers are reduced to ~15 lines each. Future callback additions are a single-site change.

### Fixes

- **Compaction signal staleness** — the `AbortSignal` used by the compaction transformer was captured at build time. Since `abortCtrl` is replaced on every new prompt, the old signal was already aborted by the time compaction ran in a multi-turn session. The signal is now read live from `session.abortCtrl.signal` inside the transformer on each invocation.

- **`pi-agent:clear` now resets compaction state** — clearing a session previously left `compactionTransformer` and `lastPromptTokens` stale on the session object. Both are now reset alongside `messages` so the new conversation starts from a clean state.

- **Skill system** — the pi-agent now discovers and loads `SKILL.md` files by walking up from the project `cwd` to the git root, checking `.cairn/skills/`, `.opencode/skills/`, `.cline/skills/`, `.claude/skills/`, and `.agents/skills/` at every ancestor directory. Global paths (`~/.config/cairn/skills/`, `~/.opencode/skills/`) are checked last. This means skills placed at the project root are always found even when `cwd` is a subdirectory (e.g. `packages/web/`). First occurrence per skill name wins (closest directory takes precedence). Metadata (name + description) is always injected into the system prompt as an `<available_skills>` XML block; the full body is loaded on demand via a new `skill` tool call. Compatible with skills authored for OpenCode, Cline, and Claude Code — same SKILL.md format, same invocation contract. Skills are allowed in both execute and plan mode.

- **Settings → Skills & System Prompt preview** — the Coding Agents settings panel now includes a live preview section showing all discovered skills for the current workspace and the full assembled system prompt. Mode toggle (execute / plan) lets you inspect both variants. Each skill card shows name, description, file path, and optional compatibility/license badges. The system prompt preview is collapsible, shows line count, and has a one-click copy button.

### Changes

- `electron/lib/skills.ts` — new file; exports `discoverSkills(cwd)`, `loadSkill(name, skills)`, `renderSkillsXml(skills)`, `SkillMeta`, `SkillContent`
- `electron/lib/coding-tools/skill.ts` — new file; `skillTool(args, skills)` and `makeSkillToolDefinition(skills)` (dynamic — lists available skill names in description)
- `electron/lib/coding-tools/index.ts` — barrel exports `skillTool`, `makeSkillToolDefinition`, `SkillArgs`
- `electron/lib/pi-agent-loop.ts` — `AgentToolContext` gains `skills?: SkillMeta[]`; `getAllToolDefs` takes skills param and conditionally includes the `skill` tool; `"skill"` dispatched in `executeSingleTool`; `"skill"` added to `PLAN_MODE_ALLOWED`
- `electron/lib/pi-agent-prompt.ts` — `PiAgentPromptContext` gains `skillsXml?: string`; skills section injected into both plan and execute mode prompts when non-empty
- `electron/ipc/pi-agent.ts` — `discoverSkills` + `renderSkillsXml` called in both `pi-agent:prompt` and `pi-agent:approve-plan` handlers; `skills` passed into `toolCtx`; new `pi-agent:preview-prompt` IPC handle returns `{ systemPrompt, skills }` for the settings preview
- `electron/preload.ts` — `piAgent.previewPrompt(req)` invoke method added
- `src/components/settings/AgentSettings.tsx` — new `SkillsPreviewSection` component with skill discovery display, mode toggle, system prompt preview with expand/collapse and copy

- `electron/lib/truncation.ts` — new file; exports `truncateOutput`, `TruncationResult`, `TruncateOptions`, `DEFAULT_MAX_BYTES`, `DEFAULT_MAX_LINES`
- `electron/lib/coding-tools/bash.ts` — imports `DEFAULT_MAX_BYTES` from truncation; hint message aligned to library format
- `electron/lib/coding-tools/grep.ts` — uses `truncateOutput`; removes manual suffix
- `electron/lib/coding-tools/find.ts` — uses `truncateOutput`; removes manual suffix
- `electron/lib/coding-tools/ls.ts` — uses `truncateOutput`; adds truncation (previously unbounded)
- `electron/lib/coding-tools/read.ts` — imports `DEFAULT_MAX_LINES`; uses `truncateOutput` for byte cap; retains precise line-range pagination hints
- `electron/lib/pi-agent-loop.ts` — `AgentLoopCallbacks` gains `shouldStop?`; `shouldStop` check wired after tool results; `setImmediate` yield documented; `runAgentLoop` export annotated with the `runSession` invariant; `PiAgentSession` gains `compactionTransformer?`
- `electron/lib/compaction.ts` — `buildCompactionTransformer` drops `signal` param (reads live from `session.abortCtrl`); new `compactNow(session, llmConfig)` export for on-demand compaction
- `electron/ipc/pi-agent.ts` — `runSession()` helper extracted; `session.compactionTransformer ??=` replaces per-call `buildCompactionTransformer`; `pi-agent:compact-now` IPC handler added; `pi-agent:compact-result` event emitted; `pi-agent:clear` resets `compactionTransformer` and `lastPromptTokens`; unused `ChatRequest` import removed
- `electron/preload.ts` — `compactNow` send method added; `onCompactResult` subscription added
- `src/components/agent/PiAgentPane.tsx` — `/compact` slash command intercepted before `sendPrompt`; `onCompactResult` subscription added; system message rendered on completion
- `src/store/slices/terminal-sessions.ts` — `PiAgentMessage.role` extended with `"system"`
- `src/components/agent/PiMessageBubble.tsx` — `"system"` role renders as centred italic muted text
Loading