@aprimediet/minion 1.0.0 → 1.1.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/README.md +10 -5
- package/index.ts +0 -3
- package/package.json +2 -1
- package/todo.ts +0 -149
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# @aprimediet/minion
|
|
2
2
|
|
|
3
|
-
Claude-Code-style **delegation** for the [pi coding agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent): a
|
|
3
|
+
Claude-Code-style **delegation** for the [pi coding agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent): 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 configuration.
|
|
4
|
+
|
|
5
|
+
> **v1.1.0 breaking change:** the `todo_write` tool and `/todos` command have been
|
|
6
|
+
> extracted into `@aprimediet/todo`. Install alongside minion:
|
|
7
|
+
> `pi install npm:@aprimediet/todo`.
|
|
4
8
|
|
|
5
9
|
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
10
|
|
|
@@ -8,8 +12,7 @@ pi is also made **aware of its delegation capability and the agent roster** ever
|
|
|
8
12
|
|
|
9
13
|
| Kind | Name | What it does |
|
|
10
14
|
|---|---|---|
|
|
11
|
-
|
|
12
|
-
| Command | `/todos` | show the current task list |
|
|
15
|
+
<!-- todo_write and /todos moved to @aprimediet/todo v1.0.0+ -->
|
|
13
16
|
| Tool | `subagent` | delegate to an agent in an isolated context — **single / parallel / chain**; pass `taskId` to run a board task |
|
|
14
17
|
| Tool | `task` | manage the persistent kanban board — `create`/`update`/`list`/`get` cards with a designated agent + structured instruction |
|
|
15
18
|
| Command | `/tasks [all\|<id>]` | show the kanban board (or one card's detail) |
|
|
@@ -65,12 +68,15 @@ Resolution: project per-name → global per-name → project `*` → global `*`
|
|
|
65
68
|
|
|
66
69
|
## Install / run
|
|
67
70
|
|
|
71
|
+
**Required companion:** `@aprimediet/todo` (provides `todo_write` + `/todos`):
|
|
72
|
+
|
|
68
73
|
```bash
|
|
69
74
|
pi install npm:@aprimediet/minion
|
|
75
|
+
pi install npm:@aprimediet/todo
|
|
70
76
|
pi list
|
|
71
77
|
|
|
72
78
|
# Quick try without installing
|
|
73
|
-
pi -e ./extensions/minion/index.ts
|
|
79
|
+
pi -e ./extensions/minion/index.ts -e /path/to/todo/index.ts
|
|
74
80
|
```
|
|
75
81
|
|
|
76
82
|
## Layout
|
|
@@ -79,7 +85,6 @@ pi -e ./extensions/minion/index.ts
|
|
|
79
85
|
minion/ # @aprimediet/minion
|
|
80
86
|
├── package.json # pi manifest: extensions + prompts
|
|
81
87
|
├── index.ts # factory: wires tools + /minion + /tasks + model seeding + resume
|
|
82
|
-
├── todo.ts # todo_write + /todos + todos/ snapshots
|
|
83
88
|
├── subagent.ts # subagent tool (subprocess engine) + delegation records + taskId
|
|
84
89
|
├── tasks.ts # persistent kanban board (task tool) + delegation/resume helpers
|
|
85
90
|
├── project.ts # project identity + ~/.pi/projects/<id>/ layout (memory-compatible)
|
package/index.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* @aprimediet/minion
|
|
3
3
|
*
|
|
4
4
|
* Claude-Code-style delegation for the pi coding agent:
|
|
5
|
-
* - todo_write (TodoWrite) + /todos
|
|
6
5
|
* - subagent (Task: single / parallel / chain, isolated pi subprocesses)
|
|
7
6
|
* - a bundled library of 12 specialized agents, with per-agent model config
|
|
8
7
|
*
|
|
@@ -17,7 +16,6 @@ import { buildDelegationSystemPrompt, bundledAgentsDir, bundledModelsFile, setDe
|
|
|
17
16
|
import { ensureProject, resolveProject } from "./project.ts";
|
|
18
17
|
import { registerSubagentTool } from "./subagent.ts";
|
|
19
18
|
import { buildResumePrompt, getTask, listTasks, registerTaskTool, renderBoard } from "./tasks.ts";
|
|
20
|
-
import { registerTodoTool } from "./todo.ts";
|
|
21
19
|
|
|
22
20
|
function copyAgentFiles(srcDir: string, destDir: string): { copied: number; skipped: number } {
|
|
23
21
|
fs.mkdirSync(destDir, { recursive: true });
|
|
@@ -37,7 +35,6 @@ function copyAgentFiles(srcDir: string, destDir: string): { copied: number; skip
|
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
export default function minionExtension(pi: ExtensionAPI): void {
|
|
40
|
-
registerTodoTool(pi);
|
|
41
38
|
registerSubagentTool(pi);
|
|
42
39
|
registerTaskTool(pi);
|
|
43
40
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aprimediet/minion",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "TodoWrite + subagent/Task + a bundled library of 12 specialized agents for the pi coding agent.",
|
|
6
6
|
"keywords": ["pi-package"],
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"files": ["*.ts", "minion.json", "agents/**", "prompts/**", "README.md", "LICENSE"],
|
|
29
29
|
"peerDependencies": {
|
|
30
|
+
"@aprimediet/todo": "^1.0.0",
|
|
30
31
|
"@earendil-works/pi-coding-agent": "*",
|
|
31
32
|
"@earendil-works/pi-agent-core": "*",
|
|
32
33
|
"@earendil-works/pi-ai": "*",
|
package/todo.ts
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `todo_write` — Claude Code's TodoWrite for pi.
|
|
3
|
-
*
|
|
4
|
-
* The model calls it with the full, updated task list (it replaces prior state).
|
|
5
|
-
* State lives in the tool result's `details` (branch-correct on session fork) and
|
|
6
|
-
* is reconstructed by scanning the session branch on session_start / session_tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import * as fs from "node:fs";
|
|
10
|
-
import * as path from "node:path";
|
|
11
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import { StringEnum } from "@earendil-works/pi-ai";
|
|
13
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
14
|
-
import { Type } from "typebox";
|
|
15
|
-
import { resolveProject } from "./project.ts";
|
|
16
|
-
|
|
17
|
-
type Status = "pending" | "in_progress" | "completed";
|
|
18
|
-
interface Todo {
|
|
19
|
-
content: string;
|
|
20
|
-
activeForm: string;
|
|
21
|
-
status: Status;
|
|
22
|
-
}
|
|
23
|
-
interface TodoDetails {
|
|
24
|
-
todos: Todo[];
|
|
25
|
-
warning?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const TodoItem = Type.Object({
|
|
29
|
-
content: Type.String({ description: "Imperative form, e.g. 'Run tests'" }),
|
|
30
|
-
activeForm: Type.String({ description: "Present-continuous form shown while active, e.g. 'Running tests'" }),
|
|
31
|
-
status: StringEnum(["pending", "in_progress", "completed"] as const),
|
|
32
|
-
});
|
|
33
|
-
const TodoWriteParams = Type.Object({
|
|
34
|
-
todos: Type.Array(TodoItem, { description: "The full, updated todo list (replaces prior state)" }),
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const ICON: Record<Status, string> = { pending: "☐", in_progress: "◐", completed: "☑" };
|
|
38
|
-
|
|
39
|
-
function renderChecklist(todos: Todo[], fg?: (role: string, s: string) => string, strike?: (s: string) => string): string {
|
|
40
|
-
if (todos.length === 0) return "(no todos)";
|
|
41
|
-
const done = todos.filter((t) => t.status === "completed").length;
|
|
42
|
-
const header = `${done}/${todos.length} completed`;
|
|
43
|
-
const lines = todos.map((t) => {
|
|
44
|
-
const label = t.status === "in_progress" ? t.activeForm : t.content;
|
|
45
|
-
if (!fg) return `${ICON[t.status]} ${label}`;
|
|
46
|
-
if (t.status === "completed") return fg("success", `${ICON.completed} `) + fg("muted", strike ? strike(label) : label);
|
|
47
|
-
if (t.status === "in_progress") return fg("accent", `${ICON.in_progress} `) + fg("text", label);
|
|
48
|
-
return fg("muted", `${ICON.pending} `) + label;
|
|
49
|
-
});
|
|
50
|
-
return `${header}\n${lines.join("\n")}`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function registerTodoTool(pi: ExtensionAPI): void {
|
|
54
|
-
let todos: Todo[] = [];
|
|
55
|
-
// one snapshot file per session, overwritten as the list changes
|
|
56
|
-
const snapshotName = `${new Date().toISOString().slice(0, 10)}-${Math.random().toString(36).slice(2, 8)}.md`;
|
|
57
|
-
|
|
58
|
-
const persistSnapshot = (ctx: ExtensionContext) => {
|
|
59
|
-
try {
|
|
60
|
-
const { todosDir } = resolveProject(ctx.cwd);
|
|
61
|
-
fs.mkdirSync(todosDir, { recursive: true });
|
|
62
|
-
const text = `# Todo snapshot — ${new Date().toISOString()}\n\n${renderChecklist(todos)}\n`;
|
|
63
|
-
fs.writeFileSync(path.join(todosDir, snapshotName), text, { encoding: "utf-8", mode: 0o600 });
|
|
64
|
-
} catch {
|
|
65
|
-
/* non-fatal */
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const reconstruct = (ctx: ExtensionContext) => {
|
|
70
|
-
todos = [];
|
|
71
|
-
try {
|
|
72
|
-
for (const entry of (ctx.sessionManager as any).getBranch() ?? []) {
|
|
73
|
-
if (entry?.type !== "message") continue;
|
|
74
|
-
const msg = entry.message;
|
|
75
|
-
if (msg?.role !== "toolResult" || msg?.toolName !== "todo_write") continue;
|
|
76
|
-
const details = msg.details as TodoDetails | undefined;
|
|
77
|
-
if (details?.todos) todos = details.todos;
|
|
78
|
-
}
|
|
79
|
-
} catch {
|
|
80
|
-
/* ignore */
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const updateStatus = (ctx: ExtensionContext) => {
|
|
85
|
-
if (!ctx.hasUI) return;
|
|
86
|
-
if (todos.length === 0) {
|
|
87
|
-
ctx.ui.setStatus("todos", undefined);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
const done = todos.filter((t) => t.status === "completed").length;
|
|
91
|
-
ctx.ui.setStatus("todos", ctx.ui.theme.fg("muted", `▣ ${done}/${todos.length}`));
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
pi.on("session_start", async (_e, ctx) => {
|
|
95
|
-
reconstruct(ctx);
|
|
96
|
-
updateStatus(ctx);
|
|
97
|
-
});
|
|
98
|
-
pi.on("session_tree", async (_e, ctx) => {
|
|
99
|
-
reconstruct(ctx);
|
|
100
|
-
updateStatus(ctx);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
pi.registerTool({
|
|
104
|
-
name: "todo_write",
|
|
105
|
-
label: "Todos",
|
|
106
|
-
description:
|
|
107
|
-
"Manage the task list for the current work. Call with the FULL updated list whenever a task starts or finishes — it replaces the previous list. Keep exactly one task 'in_progress' at a time and mark a task 'completed' immediately when done (do not batch).",
|
|
108
|
-
promptSnippet: "Track multi-step work with a todo list",
|
|
109
|
-
promptGuidelines: [
|
|
110
|
-
"Use todo_write for any non-trivial multi-step task; update it as you start/finish each step.",
|
|
111
|
-
"Exactly one todo should be in_progress at a time.",
|
|
112
|
-
],
|
|
113
|
-
parameters: TodoWriteParams,
|
|
114
|
-
|
|
115
|
-
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
116
|
-
todos = params.todos as Todo[];
|
|
117
|
-
const inProgress = todos.filter((t) => t.status === "in_progress").length;
|
|
118
|
-
let warning: string | undefined;
|
|
119
|
-
if (todos.length > 0 && inProgress !== 1) {
|
|
120
|
-
warning = `Expected exactly one in_progress task, found ${inProgress}.`;
|
|
121
|
-
}
|
|
122
|
-
updateStatus(ctx);
|
|
123
|
-
persistSnapshot(ctx);
|
|
124
|
-
const text = renderChecklist(todos);
|
|
125
|
-
return {
|
|
126
|
-
content: [{ type: "text" as const, text: warning ? `${text}\n\n⚠ ${warning}` : text }],
|
|
127
|
-
details: { todos, warning } satisfies TodoDetails,
|
|
128
|
-
};
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
renderResult(result, _opts, theme) {
|
|
132
|
-
const d = result.details as TodoDetails | undefined;
|
|
133
|
-
const list = d?.todos ?? [];
|
|
134
|
-
let text = renderChecklist(list, (role, s) => theme.fg(role, s), (s) => theme.strikethrough(s));
|
|
135
|
-
if (d?.warning) text += `\n${theme.fg("warning", `⚠ ${d.warning}`)}`;
|
|
136
|
-
return new Text(text, 0, 0);
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
pi.registerCommand("todos", {
|
|
141
|
-
description: "Show the current task list",
|
|
142
|
-
handler: async (_args, ctx) => {
|
|
143
|
-
reconstruct(ctx);
|
|
144
|
-
if (!ctx.hasUI) return;
|
|
145
|
-
const text = renderChecklist(todos, (role, s) => ctx.ui.theme.fg(role, s), (s) => ctx.ui.theme.strikethrough(s));
|
|
146
|
-
ctx.ui.notify(`Todos:\n${text}`, "info");
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
}
|