commit 0622ccbb3262b087fb6e699bbd2a1c83c94e478f Author: GRayHook Date: Wed Jun 17 08:58:00 2026 +0700 Add initial spec for pi-skill-reinject extension. Document skill tracking, post-compaction re-inject, pi-auto-compact compatibility, and configuration via /skill-reinject commands. Co-authored-by: Cursor diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62ccde4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +*.tsbuildinfo +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..c816543 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# pi-skill-reinject + +Pi Coding Agent extension: отслеживает вызванные skills и повторно инжектит их после auto compaction. + +**Статус:** спецификация (реализация не начата) + +## Документация + +- [SPEC.md](./SPEC.md) — полное ТЗ с ссылками на документацию Pi + +## Совместимость + +Рассчитан на совместную работу с [@capyup/pi-auto-compact](https://github.com/capyup/pi-auto-compact) (auto-continue после compaction). Детали — [SPEC.md §16](./SPEC.md#16-совместимость-с-capyuppi-auto-compact). + +## Быстрый контекст + +Pi хранит в контексте только описания skills; полный `SKILL.md` теряется при compaction. Extension решает это re-inject'ом через тот же механизм, что `/skill:name`. + +По умолчанию **выключено**. Включение: + +```text +/skill-reinject on # эта сессия +/skill-reinject global on # навсегда (~/.pi/agent/settings.json) +``` + +## Установка (план) + +```bash +pi -e ./src/index.ts # после реализации +``` + +## Ссылки + +- [Pi extensions](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md) +- [Pi skills](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/skills.md) +- [Pi compaction](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/compaction.md) +- [pi-auto-compact](https://github.com/capyup/pi-auto-compact) diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..f99387a --- /dev/null +++ b/SPEC.md @@ -0,0 +1,576 @@ +# ТЗ: pi-skill-reinject + +Extension для [Pi Coding Agent](https://github.com/earendil-works/pi), который отслеживает уже вызванные skills и повторно инжектит их в контекст после auto compaction. + +**Статус:** draft +**Репозиторий:** `pi-auto-reinjection` +**Целевая платформа:** upstream Pi (`earendil-works/pi`), не форки (oh-my-pi и т.п.) + +--- + +## 1. Проблема + +Pi использует progressive disclosure для skills: в system prompt всегда только name + description, полный `SKILL.md` попадает в контекст через `read` или `/skill:name` ([skills.md](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/skills.md)). + +При compaction старые сообщения суммаризуются; полный текст skill-блоков из «сжатой» части истории теряется ([compaction.md](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/compaction.md)). После auto compaction: + +- descriptions skills остаются в system prompt; +- модель **не обязана** снова читать `SKILL.md` ([skills.md — «models don't always do this»](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/skills.md)); +- встроенного механизма re-inject в upstream Pi нет. + +--- + +## 2. Цель + +Универсальный extension, который: + +1. **Отслеживает** skills, реально использованные в сессии. +2. **После auto compaction** повторно инжектит их в контекст (как при `/skill:name`). +3. **По умолчанию выключен**; включается командой на уровне сессии или глобально (навсегда). + +--- + +## 3. Не-цели (v1) + +- Замена/обёртка стандартного compaction summary. +- Re-inject после **ручного** `/compact` (опционально в v2, см. §8). +- Поддержка oh-my-pi-специфики (`compaction.autoContinue`, snapcompact и т.д.). +- Re-inject skills, которые модель «прочитала» через `read`, но пользователь явно не вызывал — только если включена опция `trackReadPaths` (см. §6.2, по умолчанию `true`). +- Изменения в сторонних extensions (в т.ч. [pi-auto-compact](https://github.com/capyup/pi-auto-compact)) — только опциональная интеграция на нашей стороне с автоопределением (§16). + +--- + +## 4. Ссылки на документацию Pi + +| Тема | Ссылка | +|------|--------| +| Extensions (API, события, команды) | [packages/coding-agent/docs/extensions.md](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md) | +| Compaction (когда срабатывает, что теряется) | [packages/coding-agent/docs/compaction.md](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/compaction.md) | +| Skills (вызов, формат, progressive disclosure) | [packages/coding-agent/docs/skills.md](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/skills.md) | +| Settings (глобальные / project overrides) | [packages/coding-agent/docs/settings.md](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/settings.md) | +| Примеры extensions | [packages/coding-agent/examples/extensions/](https://github.com/earendil-works/pi/tree/main/packages/coding-agent/examples/extensions) | +| `sendUserMessage` | [send-user-message.ts](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/send-user-message.ts) | +| Custom compaction hook | [custom-compaction.ts](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/custom-compaction.ts) | +| `parseSkillBlock` (формат skill-блока) | [agent-session.ts](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/agent-session.ts) | +| `_expandSkillCommand` (как Pi разворачивает `/skill:name`) | [agent-session.ts](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/agent-session.ts) | +| Agent Skills standard | [agentskills.io](https://agentskills.io/specification) | +| **pi-auto-compact** (совместимость) | [github.com/capyup/pi-auto-compact](https://github.com/capyup/pi-auto-compact) | +| pi-auto-compact исходник | [extensions/auto-compact.ts](https://github.com/capyup/pi-auto-compact/blob/main/extensions/auto-compact.ts) | +| Inter-extension events | [event-bus.ts example](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/event-bus.ts) | +| `before_agent_start` (inject message) | [extensions.md — before_agent_start](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md) | + +--- + +## 5. Поведение + +### 5.1. Режимы включения + +Три независимых слоя (приоритет сверху вниз): + +| Слой | Хранение | Default | Описание | +|------|----------|---------|----------| +| **Session override** | `pi.appendEntry("skill-reinject:config", …)` | `null` (нет override) | Вкл/выкл только в текущей сессии | +| **Global default** | `~/.pi/agent/settings.json` → `skillReinject.enabled` | `false` | «Навсегда» для всех новых сессий | +| **Effective** | вычисляется | `false` | `sessionOverride ?? globalDefault` | + +Команда `/skill-reinject` (см. §7) меняет session override или global default. + +**Важно:** session override не переживает `/resume` в другую сессию — восстанавливается из entries этой сессии. Global default читается при `session_start`. + +### 5.2. Когда делать re-inject + +Триггер: событие extension **`session_compact`** ([extensions.md — session_before_compact / session_compact](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md)). + +Условия (все должны выполниться): + +1. `effectiveEnabled === true` +2. Compaction **не** отменён (`session_compact` успешно отработал) +3. Источник compaction = **auto** (`threshold` или `overflow`), не manual `/compact` + → см. §8 про детекцию источника +4. Есть хотя бы один tracked skill, **отсутствующий** в kept-части контекста (§6.4) +5. Skill всё ещё зарегистрирован в `resourceLoader` (не удалён с диска) + +**Не** делать re-inject: + +- при `session_before_compact` с `cancel: true`; +- если список skills для re-inject пуст; +- если агент в момент `session_compact` стримит и `willRetry === true` (overflow recovery) — отложить до `agent_end` или использовать `deliverAs: "followUp"` (§6.5). + +### 5.3. Что именно инжектить + +Повторять поведение Pi `_expandSkillCommand`: user message с блоком + +```xml + +References are relative to {baseDir}. + +{body без YAML frontmatter} + +``` + +Опционально добавлять служебный суффикс (конфигурируемо): + +```text +[skill-reinject] Re-applied after compaction. Follow this skill's workflow. +``` + +**Не** использовать цепочку `/skill:a /skill:b` в одном сообщении — Pi разворачивает только один `/skill:` на сообщение. + +Для N skills при доставке через `sendUserMessage`: отдельное сообщение на skill, порядок = порядок первого вызова в сессии. + +При доставке через `before_agent_start` (§6.5, режим `defer`, в т.ч. с [pi-auto-compact](https://github.com/capyup/pi-auto-compact)): **одно** injected message со всеми skill-блоками подряд (меньше turn'ов, нет гонки с follow-up). + +--- + +## 6. Отслеживание skills + +### 6.1. Структура состояния + +```typescript +interface TrackedSkill { + name: string; // frontmatter name + filePath: string; // абсолютный путь к SKILL.md + baseDir: string; // директория skill + firstSeenAt: number; // timestamp + lastSeenAt: number; + sources: Array<"slash" | "skill-block" | "read">; +} + +interface ExtensionState { + version: 1; + sessionOverride: boolean | null; + skills: TrackedSkill[]; // dedupe by name, preserve insertion order + lastCompactionSource: "auto" | "manual" | null; + /** Skills, ожидающие re-inject на следующем before_agent_start (§6.5) */ + pendingReinject: string[]; // skill names +} + +/** Runtime-only, не персистить */ +interface RuntimeFlags { + autoCompactDetected: boolean; + autoCompactIntegration: "auto" | "defer" | "immediate" | "off"; +} +``` + +In-memory кэш + персистенция через `pi.appendEntry` при каждом изменении tracked skills / session override. + +### 6.2. Источники детекции + +| # | Событие | Условие | `source` | +|---|---------|---------|----------| +| 1 | `input` | `event.text` matches `/^\/skill:([a-z0-9-]+)/` | `slash` | +| 2 | `message_end` (user) | текст содержит `` +4. `/skill-reinject off` → compaction → re-inject не происходит +5. `/skill-reinject global on` → новая сессия → re-inject без `on` + +### 12.3. Manual E2E (с pi-auto-compact) + +Предусловие: `pi install npm:@capyup/pi-auto-compact` ([README](https://github.com/capyup/pi-auto-compact/blob/main/README.md)). + +1. Оба extension загружены; `/skill-reinject on` +2. `/skill-reinject` → status показывает `delivery: defer (pi-auto-compact detected)` +3. `/skill:some-skill` → сессия до срабатывания pi-auto-compact (порог по умолчанию 90% context window) +4. После auto-compaction: + - pi-auto-compact отправляет follow-up (*«Auto-compact ran… Continue…»*); + - агент **продолжает** работу без idle; + - в контексте turn'а есть skill-блок(и) **до** follow-up текста +5. В логах/TUI **нет** ошибки `Agent is already processing` / `Failed to send queued message` +6. Ручной `/compact` → pi-auto-compact **не** шлёт follow-up → re-inject только если `reinjectOnManualCompaction: true`, иначе `pendingReinject` сбрасывается на следующем user prompt +7. Во время compaction пользователь печатает сообщение → pi-auto-compact молчит → re-inject через `before_agent_start` на turn пользователя + +--- + +## 13. Критерии приёмки (v1) + +- [ ] Default off; `/skill-reinject on` включает re-inject в сессии +- [ ] `/skill-reinject global on` сохраняет в `~/.pi/agent/settings.json` и переживает restart +- [ ] После **auto** compaction re-injectятся все tracked skills, отсутствующие в kept window +- [ ] После **manual** `/compact` re-inject **не** происходит (при default `reinjectOnManualCompaction: false`) +- [ ] Tracked skills: `/skill:name`, skill-block в user msg, `read` на `SKILL.md` +- [ ] State переживает `/resume` той же сессии +- [ ] Footer status показывает on/off и count +- [ ] Нет duplicate skill blocks для skills уже в kept window +- [ ] С [pi-auto-compact](https://github.com/capyup/pi-auto-compact): auto-detect, режим `defer`, нет гонки `sendUserMessage`, continue после compaction работает +- [ ] С pi-auto-compact: ручной `/compact` не ломает ни один extension + +--- + +## 14. Будущие улучшения (v2+) + +- `reinjectOnManualCompaction: true` как осознанный opt-in +- `session_before_compact`: дописывать в custom summary список active skills +- Опциональный `pi.events` протокол с pi-auto-compact (встраивать skill-блоки в follow-up текст — без PR в capyup не обязателен) +- Pi package на npm (`keywords: ["pi-package"]`) +- Опция re-inject только «последнего активного» skill, а не всех + +--- + +## 15. Риски + +| Риск | Митигация | +|------|-----------| +| Re-inject раздувает контекст (N больших skills) | `skillReinject.maxSkills` (default unlimited; soft warn > 3) | +| `sendUserMessage` запускает нежеланный turn | `defer` по умолчанию при pi-auto-compact; иначе `triggerTurn: false` | +| Гонка `sendUserMessage` с pi-auto-compact follow-up | §16: не слать в `session_compact`; `before_agent_start` | +| Нельзя отличить auto/manual без эвристики | input hook на `/compact`; pi-auto-compact manual `/compact` не в его `onComplete` | +| Двойной compaction (Pi default + pi-auto-compact) | документировать; опционально warn в status | +| Pi изменит формат skill block | version в state; тест на `parseSkillBlock` regex | + +--- + +## 16. Совместимость с [@capyup/pi-auto-compact](https://github.com/capyup/pi-auto-compact) + +Типичная установка пользователя: `pi install npm:@capyup/pi-auto-compact`. Extension проактивно компактирует на `turn_start` / `turn_end` / emergency `context` и **автоматически продолжает** работу после auto-compaction. + +### 16.1. Как работает pi-auto-compact (релевантное нам) + +Источник: [extensions/auto-compact.ts](https://github.com/capyup/pi-auto-compact/blob/main/extensions/auto-compact.ts), [README](https://github.com/capyup/pi-auto-compact/blob/main/README.md). + +| Аспект | Поведение | +|--------|-----------| +| Триггер compaction | `ctx.compact({ customInstructions, onComplete, onError })` — не ручной `/compact` | +| Follow-up после auto-compact | `onComplete` → `setImmediate(() => { if (ctx.isIdle()) pi.sendUserMessage(AUTO_COMPACT_FOLLOW_UP[phase]); })` | +| Ручной `/compact` | **Не** попадает в их `onComplete` → follow-up **не** отправляется | +| Тексты follow-up | `"Auto-compact ran before this turn. Continue with the current task."` и ещё 3 фазы (mid-turn, emergency, session-resume) | +| Гонки | Специально откладывают follow-up в `setImmediate`, чтобы не проиграть flush `compactionQueuedMessages` Pi | +| Keep budget | `keepRecentPercent` (default 15% context window), не `keepRecentTokens` Pi | + +### 16.2. Конфликт без интеграции + +Если skill-reinject на `session_compact` вызовет `sendUserMessage` синхронно или в том же `setImmediate`: + +1. Агент перестаёт быть idle → pi-auto-compact **не** шлёт follow-up → **сессия замирает** после compaction. +2. Или оба шлют сообщения в одном tick → `"Agent is already processing"` ([комментарий в auto-compact.ts](https://github.com/capyup/pi-auto-compact/blob/main/extensions/auto-compact.ts)). + +**Вывод:** при обнаружении pi-auto-compact re-inject **обязан** идти через `defer` + `before_agent_start`, не через конкурирующий `sendUserMessage`. + +### 16.3. Целевой совместный flow + +```text +turn_start / turn_end / emergency + │ + ▼ +pi-auto-compact: ctx.compact() + │ + ├── session_before_compact (наш hook: только mark source=auto) + ├── … Pi summarization … + ├── session_compact (наш hook: pendingReinject := skills ∖ kept) + └── onComplete → setImmediate → sendUserMessage("Auto-compact ran…") + │ + ▼ + prompt() → before_agent_start (наш hook) + │ + ├── inject: × N ← re-inject + └── user prompt: "Auto-compact ran…" ← kickoff pi-auto-compact + │ + ▼ + agent продолжает с skills + summary + kept tail +``` + +### 16.4. Автоопределение pi-auto-compact + +Без зависимости от npm-пакета и без правок в capyup: + +```typescript +function detectPiAutoCompact(pi: ExtensionAPI): boolean { + return pi.getCommands().some((c) => c.name === "auto-compact"); +} +``` + +- Вызывать на `session_start` (`reason: "startup" | "resume" | "reload" | "switch"`). +- Кэшировать в `RuntimeFlags.autoCompactDetected`. +- При `false` → status `delivery: defer (standalone)` или `immediate` по `triggerTurn`. + +**Не** полагаться на путь к файлу / `package.json` — только публичный API (`getCommands`). + +Опция `skillReinject.autoCompactIntegration`: + +| Значение | Поведение | +|----------|-----------| +| `"auto"` (default) | `defer` если `detectPiAutoCompact()`, иначе по `triggerTurn` | +| `"defer"` | всегда `before_agent_start`, даже без pi-auto-compact | +| `"immediate"` | всегда `sendUserMessage` (для отладки; с pi-auto-compact — риск §16.2) | +| `"off"` | игнорировать detect; только `triggerTurn` | + +Команда `/skill-reinject integration auto|defer|immediate|off` — session override для `autoCompactIntegration` (персистить в `skill-reinject:config` entry). + +### 16.5. Что мы **не** ломаем в pi-auto-compact + +| Инвариант | Как обеспечиваем | +|-----------|------------------| +| Follow-up только когда idle | Не вызываем `sendUserMessage` в `session_compact` / `onComplete` tick | +| Follow-up только для своего auto-compact | Не трогаем `ctx.compact()` / `onComplete` | +| Ручной `/compact` без follow-up | Наш `pendingReinject` на manual: либо ждём user prompt (`defer`), либо skip (default) | +| Emergency `context` truncation | `session_compact` всё равно приходит после `ctx.compact()` — тот же `defer` | + +### 16.6. Что pi-auto-compact **не** ломает в нас + +| Сценарий | Результат | +|----------|-----------| +| Auto-compact + follow-up | Следующий `before_agent_start` — наш inject; skills в контексте | +| User печатает во время compaction | pi-auto-compact молчит; user prompt → наш inject на его turn | +| pi-auto-compact `keep-bookends` / `summarize-all` | Kept window определяем по `compactionEntry.firstKeptEntryId` в session branch (§6.4), не по их процентам | +| Два compaction подряд | Каждый `session_compact` пересчитывает `pendingReinject`; дедуп по kept window | + +### 16.7. Coexistence с Pi default auto-compaction + +Оба механизма могут сработать в одной сессии (Pi: после `agent_end`; pi-auto-compact: до turn). Рекомендация в README: + +- при использовании pi-auto-compact рассмотреть `"compaction.enabled": false` в [settings.json](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/settings.md) или оставить оба — skill-reinject отработает после **каждого** `session_compact`. + +Extension **не** отключает чужой compaction; максимум — `ui.notify` hint при первом detect обоих. + +### 16.8. Будущий опциональный протокол (v2, не блокирует v1) + +Через [`pi.events`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md): + +```typescript +// skill-reinject emits (optional, v2): +pi.events.emit("skill-reinject:pending", { skills: ["foo", "bar"] }); + +// pi-auto-compact could listen and append to follow-up — requires PR upstream +``` + +v1 **не требует** изменений в pi-auto-compact. + +### 16.9. Константы для тестов (follow-up фразы pi-auto-compact) + +Скопировать в `auto-compact.ts` как `PI_AUTO_COMPACT_FOLLOW_UP_PREFIXES` для документирования/тестов, **не** для матчинга в runtime v1: + +- `"Auto-compact ran before this turn."` +- `"Auto-compact ran mid-turn."` +- `"Emergency auto-compact ran."` +- `"Auto-compact ran on session resume."` + +Runtime v1 матчит follow-up **не нужен** — достаточно `pendingReinject` + любой следующий `before_agent_start`.