@aprimediet/minion 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/agents/code-reviewer.md +23 -0
- package/agents/code-simplifier.md +12 -0
- package/agents/comment-analyzer.md +12 -0
- package/agents/debugger.md +19 -0
- package/agents/docs-writer.md +10 -0
- package/agents/explore.md +23 -0
- package/agents/general-purpose.md +20 -0
- package/agents/plan.md +20 -0
- package/agents/pr-test-analyzer.md +17 -0
- package/agents/silent-failure-hunter.md +14 -0
- package/agents/test-writer.md +10 -0
- package/agents/type-design-analyzer.md +13 -0
- package/agents.ts +223 -0
- package/index.ts +146 -0
- package/minion.json +17 -0
- package/package.json +36 -0
- package/project.ts +170 -0
- package/prompts/implement-and-review.md +10 -0
- package/prompts/implement.md +10 -0
- package/prompts/review.md +11 -0
- package/prompts/scout-and-plan.md +9 -0
- package/subagent.ts +766 -0
- package/tasks.ts +380 -0
- package/todo.ts +149 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aditya Prima
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# @aprimediet/minion
|
|
2
|
+
|
|
3
|
+
Claude-Code-style **delegation** for the [pi coding agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent): a TodoWrite-style task tracker, a `subagent` (Task) tool that runs work in isolated `pi` subprocesses, a **persistent kanban task board** for cross-session delegation, and a **bundled library of 12 specialized agents** with per-agent model config.
|
|
4
|
+
|
|
5
|
+
pi is also made **aware of its delegation capability and the agent roster** every turn (injected into the system prompt), and on session start it **surfaces unfinished board tasks and resumes them** by delegating to each task's designated agent.
|
|
6
|
+
|
|
7
|
+
## Tools & commands
|
|
8
|
+
|
|
9
|
+
| Kind | Name | What it does |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Tool | `todo_write` | maintain an in-session task list (full-list replace; one `in_progress` at a time) |
|
|
12
|
+
| Command | `/todos` | show the current task list |
|
|
13
|
+
| Tool | `subagent` | delegate to an agent in an isolated context — **single / parallel / chain**; pass `taskId` to run a board task |
|
|
14
|
+
| Tool | `task` | manage the persistent kanban board — `create`/`update`/`list`/`get` cards with a designated agent + structured instruction |
|
|
15
|
+
| Command | `/tasks [all\|<id>]` | show the kanban board (or one card's detail) |
|
|
16
|
+
| Command | `/minion install-agents [--project]` | (optional) export the bundled agents for editing |
|
|
17
|
+
| Prompt | `/implement <x>` | chain: explore → plan → general-purpose |
|
|
18
|
+
| Prompt | `/scout-and-plan <x>` | chain: explore → plan (no implementation) |
|
|
19
|
+
| Prompt | `/implement-and-review <x>` | chain: general-purpose → code-reviewer → general-purpose |
|
|
20
|
+
| Prompt | `/review <x>` | parallel: code-reviewer + silent-failure-hunter + type-design-analyzer |
|
|
21
|
+
|
|
22
|
+
### `subagent` modes
|
|
23
|
+
- **single** — `{ agent, task }`
|
|
24
|
+
- **parallel** — `{ tasks: [{agent, task}, …] }` (≤8 total, ≤4 concurrent; output capped at 50 KB/task to the model)
|
|
25
|
+
- **chain** — `{ chain: [{agent, task}, …] }` where `task` may contain `{previous}` (the prior step's output)
|
|
26
|
+
|
|
27
|
+
Each subagent runs as a real `pi --mode json -p --no-session` subprocess (isolated context), streams progress live, and Ctrl+C kills it (SIGTERM→SIGKILL).
|
|
28
|
+
|
|
29
|
+
## Persistent task board & project storage (clean working tree)
|
|
30
|
+
|
|
31
|
+
minion keeps a durable **kanban board** so delegated work survives across sessions. The only thing written into your working tree is a single identifier file, `<cwd>/.pi/<project-id>.md`; everything else lives globally under `~/.pi/projects/<project-id>/`:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
<cwd>/.pi/<project-id>.md ← the ONLY working-tree artifact (a pointer)
|
|
35
|
+
~/.pi/projects/<project-id>/
|
|
36
|
+
project.json metadata (id, name, paths seen, timestamps)
|
|
37
|
+
tasks/<task-id>.md kanban cards: status, agent, instruction, acceptance, activity log
|
|
38
|
+
todos/<session>.md in-session todo snapshots
|
|
39
|
+
delegations/<ts>-*.md full record of every subagent delegation (task sent + result)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This is **shared with `@aprimediet/memory`**: both use the same deterministic project id (`<dir-slug>-<8charPathHash>`, recorded in the marker) and the same marker file, so the two extensions cooperate in one `~/.pi/projects/<id>/` workspace with a single cwd pointer.
|
|
43
|
+
|
|
44
|
+
**Kanban columns (status):** `backlog → todo → in_progress → blocked → review → done → cancelled`. A card carries a **designated agent** (assignee) and a **structured instruction** (+ acceptance criteria) the subagent can execute directly.
|
|
45
|
+
|
|
46
|
+
**Delegate a task:** `subagent({ agent, taskId })` loads the card's instruction, marks it `in_progress`, runs, then sets it to `review` (success) or `blocked` (failure) and appends to its activity log — and records the full delegation under `delegations/`.
|
|
47
|
+
|
|
48
|
+
**Resume on start:** unfinished cards (`todo`/`in_progress`/`blocked`) are injected into the system prompt at session start with an instruction to resume them by delegating to their agent. View anytime with `/tasks`.
|
|
49
|
+
|
|
50
|
+
## Bundled agents
|
|
51
|
+
|
|
52
|
+
`general-purpose`, `explore`, `plan`, `code-reviewer`, `code-simplifier`, `silent-failure-hunter`, `type-design-analyzer`, `comment-analyzer`, `pr-test-analyzer`, `debugger`, `test-writer`, `docs-writer`.
|
|
53
|
+
|
|
54
|
+
They are **bundled in the extension** and work immediately — no copy step. User agents in `~/.pi/agent/agents/` and (with `agentScope:"both"`, trust-gated) project agents in `.pi/agents/` override bundled ones by name.
|
|
55
|
+
|
|
56
|
+
## Per-agent models
|
|
57
|
+
|
|
58
|
+
The default model per agent lives in **`~/.pi/agent/minion.json`** (copied from the bundled default on first run — never overwriting an existing file). Edit it, or add a project `.pi/minion.json` to override:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{ "models": { "*": "claude-sonnet-4-6", "explore": "claude-haiku-4-5", "code-reviewer": "claude-opus-4-8" } }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Resolution: project per-name → global per-name → project `*` → global `*` → agent frontmatter `model:` → `MINION_DEFAULT_MODEL` / `--default-agent-model` → pi default. Re-read each invocation.
|
|
65
|
+
|
|
66
|
+
## Install / run
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pi install npm:@aprimediet/minion
|
|
70
|
+
pi list
|
|
71
|
+
|
|
72
|
+
# Quick try without installing
|
|
73
|
+
pi -e ./extensions/minion/index.ts
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Layout
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
minion/ # @aprimediet/minion
|
|
80
|
+
├── package.json # pi manifest: extensions + prompts
|
|
81
|
+
├── index.ts # factory: wires tools + /minion + /tasks + model seeding + resume
|
|
82
|
+
├── todo.ts # todo_write + /todos + todos/ snapshots
|
|
83
|
+
├── subagent.ts # subagent tool (subprocess engine) + delegation records + taskId
|
|
84
|
+
├── tasks.ts # persistent kanban board (task tool) + delegation/resume helpers
|
|
85
|
+
├── project.ts # project identity + ~/.pi/projects/<id>/ layout (memory-compatible)
|
|
86
|
+
├── agents.ts # discovery (bundled+user+project) + resolveAgentModel + delegation prompt
|
|
87
|
+
├── minion.json # default per-agent model map (seeded to ~/.pi/agent/)
|
|
88
|
+
├── agents/ # 12 bundled agent definitions
|
|
89
|
+
└── prompts/ # 4 workflow slash-commands
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
No third-party runtime deps — only the five pi-core packages (peer, bundled by pi).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-reviewer
|
|
3
|
+
description: Reviews code for adherence to project guidelines, style, and best practices. Use after writing or modifying code, especially before committing or opening a PR. Focuses on the recent diff unless told otherwise.
|
|
4
|
+
tools: read, grep, find, ls, bash
|
|
5
|
+
model: claude-opus-4-8
|
|
6
|
+
---
|
|
7
|
+
You are a senior code reviewer. Bash is READ-ONLY (`git diff`, `git log`, `git show` only) — never
|
|
8
|
+
modify files or run builds.
|
|
9
|
+
|
|
10
|
+
Strategy: `git diff` to see recent changes → read the modified files → check for bugs, security
|
|
11
|
+
issues, guideline/style violations (consult CLAUDE.md/AGENTS.md if present), and code smells.
|
|
12
|
+
|
|
13
|
+
Output:
|
|
14
|
+
## Files Reviewed
|
|
15
|
+
- `path` (lines X-Y)
|
|
16
|
+
## Critical (must fix)
|
|
17
|
+
- `file:line` — issue
|
|
18
|
+
## Warnings (should fix)
|
|
19
|
+
- `file:line` — issue
|
|
20
|
+
## Suggestions (consider)
|
|
21
|
+
- `file:line` — improvement
|
|
22
|
+
## Summary
|
|
23
|
+
2–3 sentence assessment. Be specific with paths and line numbers.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-simplifier
|
|
3
|
+
description: Simplifies recently written/modified code for clarity, consistency, and maintainability while preserving ALL functionality. Use after a coding task to refine the new code following project patterns.
|
|
4
|
+
tools: read, grep, find, ls, edit
|
|
5
|
+
model: claude-sonnet-4-6
|
|
6
|
+
---
|
|
7
|
+
You simplify code WITHOUT changing behavior. Work only on recently modified code unless told otherwise.
|
|
8
|
+
|
|
9
|
+
Rules: preserve all functionality and public APIs; match surrounding style, naming, and comment
|
|
10
|
+
density; remove duplication and needless complexity; do not add features or "improvements" beyond
|
|
11
|
+
clarity. Make edits in place. After editing, list each change and why it is behavior-preserving. If a
|
|
12
|
+
simplification is risky, leave it and flag it instead.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: comment-analyzer
|
|
3
|
+
description: Analyzes code comments/docstrings for accuracy, completeness, and long-term maintainability. Use after generating large doc comments or before finalizing a PR that adds/changes comments.
|
|
4
|
+
tools: read, grep, find, ls
|
|
5
|
+
model: claude-haiku-4-5
|
|
6
|
+
---
|
|
7
|
+
You audit comments (read-only). Flag: comments that contradict the code ("comment rot"), comments
|
|
8
|
+
restating the obvious, missing "why" on non-obvious logic, stale TODOs/refs, and docstrings whose
|
|
9
|
+
params/returns/throws don't match the signature.
|
|
10
|
+
|
|
11
|
+
Output per item: `file:line` — problem → recommendation (fix, delete, or add). Prefer fewer, higher-value
|
|
12
|
+
comments that explain intent over mechanics.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: debugger
|
|
3
|
+
description: Root-cause analysis for a bug, test failure, or unexpected behavior. Use when something is broken and you need the underlying cause, not a patch.
|
|
4
|
+
tools: read, grep, find, ls, bash
|
|
5
|
+
model: claude-sonnet-4-6
|
|
6
|
+
---
|
|
7
|
+
You are a debugging specialist. Form a hypothesis, then confirm it with evidence BEFORE proposing a
|
|
8
|
+
fix. Reproduce or locate the failure, read the relevant code and recent diff, and trace the actual
|
|
9
|
+
data/control flow.
|
|
10
|
+
|
|
11
|
+
Output:
|
|
12
|
+
## Symptom
|
|
13
|
+
What's observed.
|
|
14
|
+
## Root Cause
|
|
15
|
+
`file:line` — the real cause, with the evidence that proves it.
|
|
16
|
+
## Fix
|
|
17
|
+
The minimal correct change (and why it addresses the cause, not the symptom).
|
|
18
|
+
## Verification
|
|
19
|
+
How to confirm it's fixed. Do not guess — if unproven, say what evidence is still needed.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docs-writer
|
|
3
|
+
description: Writes or updates documentation (README sections, API docs, usage guides) to match the current code. Use after a feature lands or when docs have drifted.
|
|
4
|
+
tools: read, grep, find, ls, edit, write
|
|
5
|
+
model: claude-haiku-4-5
|
|
6
|
+
---
|
|
7
|
+
You write accurate, concise documentation grounded in the actual code — never invent APIs or flags.
|
|
8
|
+
Match the repo's existing doc tone and structure. Prefer runnable examples. When updating, preserve
|
|
9
|
+
manually-authored sections and only change what the code requires. List every file you touched and the
|
|
10
|
+
key additions.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: explore
|
|
3
|
+
description: Read-only search agent for broad fan-out exploration — locating code across many files and naming conventions and returning compressed findings for handoff. Does not edit. Specify breadth ("medium" or "very thorough").
|
|
4
|
+
tools: read, grep, find, ls, bash
|
|
5
|
+
model: claude-haiku-4-5
|
|
6
|
+
---
|
|
7
|
+
You are a fast scout. Investigate the codebase and return structured findings another agent can use
|
|
8
|
+
WITHOUT re-reading everything. Bash is for read-only inspection only (no mutations).
|
|
9
|
+
|
|
10
|
+
Strategy: grep/find to locate code → read only the critical sections → identify the key types,
|
|
11
|
+
functions, and dependencies.
|
|
12
|
+
|
|
13
|
+
Output:
|
|
14
|
+
## Files Retrieved
|
|
15
|
+
1. `path` (lines A-B) — what's here
|
|
16
|
+
## Key Code
|
|
17
|
+
```
|
|
18
|
+
the few critical types/functions, verbatim
|
|
19
|
+
```
|
|
20
|
+
## Architecture
|
|
21
|
+
How the pieces connect.
|
|
22
|
+
## Start Here
|
|
23
|
+
The first file to open and why.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: general-purpose
|
|
3
|
+
description: General-purpose agent for researching complex questions, searching code, and executing multi-step tasks autonomously in an isolated context. Use when a task is open-ended or you're unsure of the match in the first few tries.
|
|
4
|
+
model: claude-sonnet-4-6
|
|
5
|
+
---
|
|
6
|
+
You are a general-purpose worker agent operating in an isolated context window so your work does not
|
|
7
|
+
pollute the main conversation. Complete the delegated task end to end using all available tools.
|
|
8
|
+
|
|
9
|
+
Be autonomous: investigate, decide, implement, and verify. Prefer reusing existing code and patterns.
|
|
10
|
+
When finished, return a tight report:
|
|
11
|
+
|
|
12
|
+
## Completed
|
|
13
|
+
What you did.
|
|
14
|
+
|
|
15
|
+
## Files Changed
|
|
16
|
+
- `path` — what changed and why.
|
|
17
|
+
|
|
18
|
+
## Notes / Handoff
|
|
19
|
+
Anything the main agent must know (exact paths, key functions touched, follow-ups). If you could not
|
|
20
|
+
finish, say exactly what is left and why.
|
package/agents/plan.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plan
|
|
3
|
+
description: Software-architect agent that designs an implementation plan from context + requirements. Read-only — never edits. Returns ordered, concrete steps, the files to change, and risks.
|
|
4
|
+
tools: read, grep, find, ls
|
|
5
|
+
model: claude-sonnet-4-6
|
|
6
|
+
---
|
|
7
|
+
You are a planning specialist. You receive context (often from an explore agent) and requirements,
|
|
8
|
+
then produce a concrete plan. You MUST NOT make changes — only read, analyze, and plan.
|
|
9
|
+
|
|
10
|
+
Output:
|
|
11
|
+
## Goal
|
|
12
|
+
One sentence.
|
|
13
|
+
## Plan
|
|
14
|
+
Numbered, small, actionable steps — each naming the file/function to touch.
|
|
15
|
+
## Files to Modify
|
|
16
|
+
- `path` — what changes
|
|
17
|
+
## New Files (if any)
|
|
18
|
+
- `path` — purpose
|
|
19
|
+
## Risks
|
|
20
|
+
What to watch for. Keep it concrete; a worker will execute it verbatim.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pr-test-analyzer
|
|
3
|
+
description: Reviews a change for test coverage quality and completeness. Use after a PR adds logic to check that tests cover new behavior and edge cases.
|
|
4
|
+
tools: read, grep, find, ls, bash
|
|
5
|
+
model: claude-sonnet-4-6
|
|
6
|
+
---
|
|
7
|
+
You assess TEST coverage (read-only; bash = `git diff`, test discovery, NO running mutating commands).
|
|
8
|
+
Map new/changed behavior to tests. Identify: untested branches/paths, missing edge & error cases,
|
|
9
|
+
assertions that don't actually verify behavior, and flaky patterns.
|
|
10
|
+
|
|
11
|
+
Output:
|
|
12
|
+
## Covered
|
|
13
|
+
- behavior → test
|
|
14
|
+
## Gaps (by priority)
|
|
15
|
+
- `file:line` behavior — missing case, and the test to add
|
|
16
|
+
## Summary
|
|
17
|
+
Is coverage adequate to merge? One paragraph.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: silent-failure-hunter
|
|
3
|
+
description: Reviews changes for silent failures, swallowed errors, and inappropriate fallback behavior. Use after writing error handling, catch blocks, or fallback logic.
|
|
4
|
+
tools: read, grep, find, ls, bash
|
|
5
|
+
model: claude-sonnet-4-6
|
|
6
|
+
---
|
|
7
|
+
You hunt for SILENT FAILURES. Bash is read-only (`git diff`, `git log`). Inspect the diff and changed
|
|
8
|
+
files for: empty/`catch`-and-ignore blocks, errors logged-then-swallowed, default/fallback values that
|
|
9
|
+
mask failures, `|| <fallback>` hiding real errors, broad excepts, and ignored promise rejections /
|
|
10
|
+
unchecked return codes.
|
|
11
|
+
|
|
12
|
+
For each finding: `file:line` — what is swallowed, how it can hide a real fault, and the fix (fail
|
|
13
|
+
loud, propagate, or narrow the catch). Sort Critical → Warning → Suggestion. If error handling is
|
|
14
|
+
sound, say so explicitly.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-writer
|
|
3
|
+
description: Writes focused, meaningful tests for given code or behavior, following the project's existing test framework and conventions.
|
|
4
|
+
tools: read, grep, find, ls, edit, write, bash
|
|
5
|
+
model: claude-sonnet-4-6
|
|
6
|
+
---
|
|
7
|
+
You write tests. First detect the framework and conventions from existing tests; match them exactly.
|
|
8
|
+
Cover the happy path, edge cases, and error cases. Each test asserts real behavior (no
|
|
9
|
+
assertion-free or tautological tests). Prefer clear arrange/act/assert. Run the suite if a runner is
|
|
10
|
+
obvious. Report which behaviors are covered and any that remain hard to test and why.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: type-design-analyzer
|
|
3
|
+
description: Analyzes type design — encapsulation, invariant expression, making illegal states unrepresentable. Use when adding or refactoring types, or reviewing types in a PR.
|
|
4
|
+
tools: read, grep, find, ls
|
|
5
|
+
model: claude-opus-4-8
|
|
6
|
+
---
|
|
7
|
+
You are a type-design expert (read-only). For each notable type assess: does it encapsulate its
|
|
8
|
+
invariants, or can callers construct illegal states? Are optional/loose fields hiding a sum type? Could
|
|
9
|
+
a stronger type (enum/union/newtype/branded) make bad states unrepresentable?
|
|
10
|
+
|
|
11
|
+
Output per type: `file:line` — name, then ratings (1–5) for Encapsulation, Invariant expression,
|
|
12
|
+
Usefulness, Enforcement, with a one-line justification and a concrete redesign suggestion. End with the
|
|
13
|
+
single highest-leverage change.
|
package/agents.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent discovery (bundled + user + project) and per-agent model resolution.
|
|
3
|
+
*
|
|
4
|
+
* Extends pi's bundled subagent example with:
|
|
5
|
+
* - a third "bundled" source (the agents shipped inside this extension),
|
|
6
|
+
* - per-agent model config read from minion.json (project then global).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { CONFIG_DIR_NAME, getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
|
|
14
|
+
export type AgentScope = "user" | "project" | "both";
|
|
15
|
+
export type AgentSource = "bundled" | "user" | "project";
|
|
16
|
+
|
|
17
|
+
export interface AgentConfig {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
tools?: string[];
|
|
21
|
+
model?: string;
|
|
22
|
+
systemPrompt: string;
|
|
23
|
+
source: AgentSource;
|
|
24
|
+
filePath: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AgentDiscoveryResult {
|
|
28
|
+
agents: AgentConfig[];
|
|
29
|
+
projectAgentsDir: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const HERE =
|
|
33
|
+
typeof import.meta.dirname === "string" ? import.meta.dirname : path.dirname(fileURLToPath(import.meta.url));
|
|
34
|
+
const BUNDLED_AGENTS_DIR = path.join(HERE, "agents");
|
|
35
|
+
const BUNDLED_MODELS_FILE = path.join(HERE, "minion.json");
|
|
36
|
+
|
|
37
|
+
export function bundledAgentsDir(): string {
|
|
38
|
+
return BUNDLED_AGENTS_DIR;
|
|
39
|
+
}
|
|
40
|
+
export function bundledModelsFile(): string {
|
|
41
|
+
return BUNDLED_MODELS_FILE;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
45
|
+
const agents: AgentConfig[] = [];
|
|
46
|
+
if (!fs.existsSync(dir)) return agents;
|
|
47
|
+
|
|
48
|
+
let entries: fs.Dirent[];
|
|
49
|
+
try {
|
|
50
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
51
|
+
} catch {
|
|
52
|
+
return agents;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
57
|
+
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
58
|
+
|
|
59
|
+
const filePath = path.join(dir, entry.name);
|
|
60
|
+
let content: string;
|
|
61
|
+
try {
|
|
62
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
63
|
+
} catch {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
|
|
68
|
+
if (!frontmatter.name || !frontmatter.description) continue;
|
|
69
|
+
|
|
70
|
+
const tools = frontmatter.tools
|
|
71
|
+
?.split(",")
|
|
72
|
+
.map((t: string) => t.trim())
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
|
|
75
|
+
agents.push({
|
|
76
|
+
name: frontmatter.name,
|
|
77
|
+
description: frontmatter.description,
|
|
78
|
+
tools: tools && tools.length > 0 ? tools : undefined,
|
|
79
|
+
model: frontmatter.model,
|
|
80
|
+
systemPrompt: body,
|
|
81
|
+
source,
|
|
82
|
+
filePath,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return agents;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isDirectory(p: string): boolean {
|
|
89
|
+
try {
|
|
90
|
+
return fs.statSync(p).isDirectory();
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
97
|
+
let dir = cwd;
|
|
98
|
+
for (;;) {
|
|
99
|
+
const candidate = path.join(dir, CONFIG_DIR_NAME, "agents");
|
|
100
|
+
if (isDirectory(candidate)) return candidate;
|
|
101
|
+
const parent = path.dirname(dir);
|
|
102
|
+
if (parent === dir) return null;
|
|
103
|
+
dir = parent;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** bundled (always) → user (unless scope==="project") → project (scope project|both). Later wins. */
|
|
108
|
+
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
109
|
+
const userDir = path.join(getAgentDir(), "agents");
|
|
110
|
+
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
|
111
|
+
|
|
112
|
+
const bundled = loadAgentsFromDir(BUNDLED_AGENTS_DIR, "bundled");
|
|
113
|
+
const userAgents = scope === "project" ? [] : loadAgentsFromDir(userDir, "user");
|
|
114
|
+
const projectAgents = scope === "user" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, "project");
|
|
115
|
+
|
|
116
|
+
const map = new Map<string, AgentConfig>();
|
|
117
|
+
for (const a of bundled) map.set(a.name, a);
|
|
118
|
+
for (const a of userAgents) map.set(a.name, a);
|
|
119
|
+
for (const a of projectAgents) map.set(a.name, a);
|
|
120
|
+
|
|
121
|
+
return { agents: Array.from(map.values()), projectAgentsDir };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build the "Subagent delegation" section appended to pi's system prompt each turn, so the main
|
|
126
|
+
* agent always knows it can delegate and which agents exist. Lists the agents usable by default
|
|
127
|
+
* (bundled + user); notes project-local agents (which require agentScope:"both"). Returns null
|
|
128
|
+
* when no agents are discovered.
|
|
129
|
+
*/
|
|
130
|
+
export function buildDelegationSystemPrompt(cwd: string): string | null {
|
|
131
|
+
let discovery: AgentDiscoveryResult;
|
|
132
|
+
try {
|
|
133
|
+
discovery = discoverAgents(cwd, "both");
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
if (discovery.agents.length === 0) return null;
|
|
138
|
+
|
|
139
|
+
const usable = discovery.agents.filter((a) => a.source !== "project");
|
|
140
|
+
const project = discovery.agents.filter((a) => a.source === "project");
|
|
141
|
+
|
|
142
|
+
const fmt = (a: AgentConfig): string => {
|
|
143
|
+
const desc = a.description.replace(/\s+/g, " ").trim();
|
|
144
|
+
const short = desc.length > 140 ? `${desc.slice(0, 140)}…` : desc;
|
|
145
|
+
return `- ${a.name} — ${short}`;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const lines = [
|
|
149
|
+
"# Subagent delegation",
|
|
150
|
+
"",
|
|
151
|
+
"You can delegate work to specialized subagents with the `subagent` tool. Each subagent runs in its own isolated context window, so delegating keeps your context focused. Delegate well-scoped work that benefits from focused expertise or parallelism — broad code exploration, planning, code review, debugging, writing tests or docs — and decide for yourself when it is worthwhile (do trivial steps directly).",
|
|
152
|
+
"",
|
|
153
|
+
"Modes: single (one `agent` + `task`), parallel (a `tasks` array of independent jobs run at once), and chain (a `chain` array run sequentially, referencing the previous step's output with the {previous} placeholder).",
|
|
154
|
+
"",
|
|
155
|
+
"Available agents (call by exact name):",
|
|
156
|
+
...usable.map(fmt),
|
|
157
|
+
];
|
|
158
|
+
if (project.length > 0) {
|
|
159
|
+
lines.push("");
|
|
160
|
+
lines.push(
|
|
161
|
+
`Project-local agents (trusted repos only; pass agentScope:"both" to use): ${project.map((a) => a.name).join(", ")}.`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return lines.join("\n");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function formatAgentList(agents: AgentConfig[], maxItems: number): { text: string; remaining: number } {
|
|
168
|
+
if (agents.length === 0) return { text: "none", remaining: 0 };
|
|
169
|
+
const listed = agents.slice(0, maxItems);
|
|
170
|
+
return {
|
|
171
|
+
text: listed.map((a) => `${a.name} (${a.source}): ${a.description}`).join("; "),
|
|
172
|
+
remaining: agents.length - listed.length,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ------------------------------------------------------- per-agent model config
|
|
177
|
+
|
|
178
|
+
let flagDefaultModel: string | undefined;
|
|
179
|
+
export function setDefaultAgentModel(model: string | undefined): void {
|
|
180
|
+
flagDefaultModel = model && model.trim() ? model.trim() : undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function findNearestProjectModelsFile(cwd: string): string | null {
|
|
184
|
+
let dir = cwd;
|
|
185
|
+
for (;;) {
|
|
186
|
+
const candidate = path.join(dir, CONFIG_DIR_NAME, "minion.json");
|
|
187
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
188
|
+
const parent = path.dirname(dir);
|
|
189
|
+
if (parent === dir) return null;
|
|
190
|
+
dir = parent;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function readModels(file: string | null): Record<string, string> | undefined {
|
|
195
|
+
if (!file) return undefined;
|
|
196
|
+
try {
|
|
197
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf-8")) as { models?: Record<string, string> };
|
|
198
|
+
return parsed?.models;
|
|
199
|
+
} catch {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Resolve the model for an agent (first hit wins):
|
|
206
|
+
* project models[name] → global models[name] → project models["*"] → global models["*"]
|
|
207
|
+
* → frontmatter model → MINION_DEFAULT_MODEL / --default-agent-model → undefined.
|
|
208
|
+
* Re-reads the JSON each call so edits apply without a reload.
|
|
209
|
+
*/
|
|
210
|
+
export function resolveAgentModel(agent: AgentConfig, cwd: string): string | undefined {
|
|
211
|
+
const proj = readModels(findNearestProjectModelsFile(cwd));
|
|
212
|
+
const glob = readModels(path.join(getAgentDir(), "minion.json"));
|
|
213
|
+
return (
|
|
214
|
+
proj?.[agent.name] ??
|
|
215
|
+
glob?.[agent.name] ??
|
|
216
|
+
proj?.["*"] ??
|
|
217
|
+
glob?.["*"] ??
|
|
218
|
+
agent.model ??
|
|
219
|
+
process.env.MINION_DEFAULT_MODEL ??
|
|
220
|
+
flagDefaultModel ??
|
|
221
|
+
undefined
|
|
222
|
+
);
|
|
223
|
+
}
|