@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.
Files changed (4) hide show
  1. package/README.md +10 -5
  2. package/index.ts +0 -3
  3. package/package.json +2 -1
  4. 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 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.
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
- | 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 |
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.0.0",
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
- }