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 <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
*.tsbuildinfo
|
||||||
|
.DS_Store
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
<skill name="{name}" location="{filePath}">
|
||||||
|
References are relative to {baseDir}.
|
||||||
|
|
||||||
|
{body без YAML frontmatter}
|
||||||
|
</skill>
|
||||||
|
```
|
||||||
|
|
||||||
|
Опционально добавлять служебный суффикс (конфигурируемо):
|
||||||
|
|
||||||
|
```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) | текст содержит `<skill name="…"` (regex из `parseSkillBlock`) | `skill-block` |
|
||||||
|
| 3 | `tool_call` / `tool_result` | `toolName === "read"` и `path` совпадает с `SKILL.md` известного skill | `read` |
|
||||||
|
|
||||||
|
Для (3): сопоставлять `path` с `skill.filePath` из `ctx` / `getSkills()` ([formatSkillsForPrompt](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/skills.ts)).
|
||||||
|
|
||||||
|
Опция `skillReinject.trackReadPaths` (default `true`): если `false`, игнорировать детекцию через `read`.
|
||||||
|
|
||||||
|
### 6.3. Восстановление при `session_start` / `/resume`
|
||||||
|
|
||||||
|
На `session_start` (включая `reason: "reload"`):
|
||||||
|
|
||||||
|
1. Пройти `ctx.sessionManager.getBranch()`:
|
||||||
|
- entries `type === "custom"` с `customType === "skill-reinject:state"` → восстановить state;
|
||||||
|
- user messages → re-scan skill blocks;
|
||||||
|
2. Если entries state нет — full rescan ветки.
|
||||||
|
|
||||||
|
### 6.4. Skills уже в kept-контексте
|
||||||
|
|
||||||
|
После compaction часть recent messages сохраняется (`keepRecentTokens`, default 20k — [settings.md](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/settings.md)).
|
||||||
|
|
||||||
|
Перед re-inject:
|
||||||
|
|
||||||
|
1. Взять entries от `compactionEntry.firstKeptEntryId` до хвоста ветки.
|
||||||
|
2. Для каждого tracked skill: если в kept user messages есть `<skill name="{name}"` → **пропустить** re-inject для этого skill.
|
||||||
|
|
||||||
|
### 6.5. Доставка после compaction
|
||||||
|
|
||||||
|
Два режима доставки (выбор — §6.5.1 / §16):
|
||||||
|
|
||||||
|
#### 6.5.1. Режим `defer` (рекомендуемый по умолчанию при наличии pi-auto-compact)
|
||||||
|
|
||||||
|
1. На `session_compact`: записать имена skills в `state.pendingReinject` (не вызывать `sendUserMessage`).
|
||||||
|
2. На следующем `before_agent_start`: если `pendingReinject` не пуст — вернуть injected `message` с объединёнными skill-блоками, очистить очередь.
|
||||||
|
|
||||||
|
Skills попадают в контекст **в том же turn**, что и kickoff-сообщение (в т.ч. auto-continue от pi-auto-compact), **без гонки** `sendUserMessage`.
|
||||||
|
|
||||||
|
#### 6.5.2. Режим `immediate` (`sendUserMessage`)
|
||||||
|
|
||||||
|
Использовать `pi.sendUserMessage` ([extensions.md — sendUserMessage](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md)):
|
||||||
|
|
||||||
|
| Ситуация | Стратегия |
|
||||||
|
|----------|-----------|
|
||||||
|
| Агент idle после compaction | Первый skill → `sendUserMessage(expandedBlock)`; остальные → `followUp` |
|
||||||
|
| Overflow recovery (`willRetry`) | Все skills → `sendUserMessage(..., { deliverAs: "followUp" })` |
|
||||||
|
| Агент стримит | `deliverAs: "followUp"` для всех |
|
||||||
|
|
||||||
|
**Не использовать `immediate`**, если обнаружен [pi-auto-compact](https://github.com/capyup/pi-auto-compact) и `autoCompactIntegration !== "off"` — см. §16.
|
||||||
|
|
||||||
|
#### 6.5.3. Выбор режима
|
||||||
|
|
||||||
|
| Условие | Режим доставки |
|
||||||
|
|---------|----------------|
|
||||||
|
| `autoCompactIntegration: "defer"` | всегда `defer` |
|
||||||
|
| `autoCompactIntegration: "immediate"` | всегда `sendUserMessage` |
|
||||||
|
| `autoCompactIntegration: "off"` | по `triggerTurn` (§7.3) |
|
||||||
|
| `autoCompactIntegration: "auto"` (default) + pi-auto-compact обнаружен | `defer` |
|
||||||
|
| `autoCompactIntegration: "auto"` + pi-auto-compact **не** обнаружен + `triggerTurn: false` | `defer` (ждать следующий user prompt) |
|
||||||
|
| `autoCompactIntegration: "auto"` + pi-auto-compact **не** обнаружен + `triggerTurn: true` | `immediate` |
|
||||||
|
|
||||||
|
**Важно:** встроенный Pi auto-compaction ([`_runAutoCompaction`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/agent-session.ts)) при `reason: "threshold"` **не** продолжает turn (`willRetry: false`). С установленным pi-auto-compact продолжение обеспечивает **он**, не Pi — наш `defer` рассчитан на этот сценарий.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Команда `/skill-reinject`
|
||||||
|
|
||||||
|
Зарегистрировать через `pi.registerCommand("skill-reinject", …)` ([extensions.md — registerCommand](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md)).
|
||||||
|
|
||||||
|
### 7.1. Синтаксис
|
||||||
|
|
||||||
|
```text
|
||||||
|
/skill-reinject # статус
|
||||||
|
/skill-reinject on # вкл в этой сессии
|
||||||
|
/skill-reinject off # выкл в этой сессии
|
||||||
|
/skill-reinject reset # сброс session override → global default
|
||||||
|
/skill-reinject global on # вкл навсегда (~/.pi/agent/settings.json)
|
||||||
|
/skill-reinject global off # выкл навсегда
|
||||||
|
/skill-reinject list # tracked skills
|
||||||
|
/skill-reinject clear # очистить tracked skills (не трогает toggle)
|
||||||
|
/skill-reinject now # принудительный re-inject (debug)
|
||||||
|
/skill-reinject integration auto|defer|immediate|off # режим доставки (§6.5, §16)
|
||||||
|
```
|
||||||
|
|
||||||
|
Алиасы (опционально): `/sr`, `/skills-reinject`.
|
||||||
|
|
||||||
|
### 7.2. Вывод `/skill-reinject` (status)
|
||||||
|
|
||||||
|
```text
|
||||||
|
skill-reinject: off (session override) | on (global) | on (session)
|
||||||
|
delivery: defer (pi-auto-compact detected) | immediate | defer
|
||||||
|
tracked: 2 skills — redmine-issue-context, fup-blame-commits
|
||||||
|
pending reinject: 0
|
||||||
|
last compaction: auto @ 14:32
|
||||||
|
```
|
||||||
|
|
||||||
|
Footer status через `ctx.ui.setStatus("skill-reinject", "on·2")` ([status-line.ts example](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/status-line.ts)).
|
||||||
|
|
||||||
|
### 7.3. Global settings
|
||||||
|
|
||||||
|
Писать в `~/.pi/agent/settings.json` (merge, не затирать файл целиком):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"skillReinject": {
|
||||||
|
"enabled": false,
|
||||||
|
"trackReadPaths": true,
|
||||||
|
"triggerTurn": false,
|
||||||
|
"reinjectOnManualCompaction": false,
|
||||||
|
"autoCompactIntegration": "auto",
|
||||||
|
"suffix": "[skill-reinject] Re-applied after compaction."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Поле | Default | Описание |
|
||||||
|
|------|---------|----------|
|
||||||
|
| `autoCompactIntegration` | `"auto"` | `"auto"` \| `"defer"` \| `"immediate"` \| `"off"` — см. §6.5.3, §16 |
|
||||||
|
|
||||||
|
Project override: `.pi/settings.json` → тот же ключ (merged по правилам Pi, [settings.md — Project Overrides](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/settings.md)).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Детекция auto vs manual compaction
|
||||||
|
|
||||||
|
Extension event `session_compact` **не** передаёт `reason` ([SessionCompactEvent](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/extensions/types.ts)).
|
||||||
|
|
||||||
|
**Стратегия v1:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// input handler (до expansion)
|
||||||
|
if (text.startsWith("/compact")) pendingCompactionSource = "manual";
|
||||||
|
|
||||||
|
// session_before_compact
|
||||||
|
if (pendingCompactionSource !== "manual") pendingCompactionSource = "auto";
|
||||||
|
|
||||||
|
// session_compact
|
||||||
|
const shouldReinject =
|
||||||
|
effectiveEnabled &&
|
||||||
|
(pendingCompactionSource === "auto" || settings.reinjectOnManualCompaction);
|
||||||
|
pendingCompactionSource = null;
|
||||||
|
```
|
||||||
|
|
||||||
|
Ручной RPC compact (`{type: "compact"}`) тоже помечать как `manual` если extension получает соответствующий сигнал (v2).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Архитектура файлов
|
||||||
|
|
||||||
|
```
|
||||||
|
pi-auto-reinjection/
|
||||||
|
├── SPEC.md # это ТЗ
|
||||||
|
├── README.md
|
||||||
|
├── package.json # pi package manifest (опционально npm)
|
||||||
|
├── src/
|
||||||
|
│ ├── index.ts # export default function(pi: ExtensionAPI)
|
||||||
|
│ ├── state.ts # load/save/merge state, appendEntry
|
||||||
|
│ ├── detect.ts # skill detection helpers
|
||||||
|
│ ├── expand.ts # expand skill → block (mirror Pi logic)
|
||||||
|
│ ├── reinject.ts # post-compaction reinject orchestration
|
||||||
|
│ ├── auto-compact.ts # detect @capyup/pi-auto-compact, delivery mode (§16)
|
||||||
|
│ ├── settings.ts # read/write skillReinject.* in settings.json
|
||||||
|
│ └── commands.ts # /skill-reinject handler
|
||||||
|
├── test/
|
||||||
|
│ ├── detect.test.ts
|
||||||
|
│ ├── kept-window.test.ts
|
||||||
|
│ └── expand.test.ts
|
||||||
|
└── .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.1. Установка (целевая)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# dev
|
||||||
|
pi -e ~/Documents/repos/pi-auto-reinjection/src/index.ts
|
||||||
|
|
||||||
|
# production
|
||||||
|
cp -r src/index.ts ~/.pi/agent/extensions/skill-reinject.ts
|
||||||
|
# или через pi package / settings.extensions
|
||||||
|
```
|
||||||
|
|
||||||
|
См. [extensions.md — Extension Locations](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md): `~/.pi/agent/extensions/`, `.pi/extensions/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Зависимости
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pi": {
|
||||||
|
"extensions": ["./src/index.ts"]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@earendil-works/pi-coding-agent": "workspace:* или latest",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Импорты только из публичного API Pi ([extensions.md — Available Imports](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md)).
|
||||||
|
|
||||||
|
Для expand skill body: `readFileSync` + strip YAML frontmatter (как Pi), без дублирования приватных internal imports если возможно; иначе — локальная копия логики с комментарием «mirror agent-session._expandSkillCommand».
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Edge cases
|
||||||
|
|
||||||
|
| Case | Поведение |
|
||||||
|
|------|-----------|
|
||||||
|
| Skill удалён с диска | skip + `ui.notify` warning |
|
||||||
|
| Skill `disable-model-invocation: true` | re-inject всё равно (explicit inject) |
|
||||||
|
| Два skill с одним name (collision) | использовать первый из resourceLoader; warn |
|
||||||
|
| Compaction во время стрима | `followUp` delivery |
|
||||||
|
| Пустой tracked list | no-op |
|
||||||
|
| `/tree` branch switch | state из entries новой ветки; rescan |
|
||||||
|
| `session_shutdown` | flush pending appendEntry |
|
||||||
|
| RPC / print mode (`hasUI === false`) | команды работают; notify → no-op; reinject без UI feedback |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Тестирование
|
||||||
|
|
||||||
|
### 12.1. Unit
|
||||||
|
|
||||||
|
- `parseSkillBlock` / detect в user text
|
||||||
|
- kept-window: skill in / not in kept messages
|
||||||
|
- expand: frontmatter strip, path resolution
|
||||||
|
- settings merge read/write
|
||||||
|
|
||||||
|
### 12.2. Manual E2E (standalone)
|
||||||
|
|
||||||
|
1. `pi` + extension, `/skill-reinject on`
|
||||||
|
2. `/skill:some-skill` → длинная сессия → дождаться auto compaction (или уменьшить `keepRecentTokens` / `reserveTokens` в `.pi/settings.json`)
|
||||||
|
3. Проверить: после compaction в контексте снова есть `<skill name="some-skill"…>`
|
||||||
|
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: <skill>…</skill> × 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`.
|
||||||
Reference in New Issue
Block a user