@huseynsnmz/pi-suite 0.2.3 → 0.2.5
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/CHANGELOG.md +38 -0
- package/README.md +6 -4
- package/package.json +7 -2
- package/packages/board/README.md +1 -1
- package/packages/board/package.json +2 -1
- package/packages/frame/README.md +1 -1
- package/packages/memory/README.md +20 -1
- package/packages/memory/src/index.ts +60 -1
- package/packages/memory/src/mastermind.ts +112 -0
- package/packages/memory/src/playbook.ts +99 -0
- package/packages/modes/README.md +10 -2
- package/packages/modes/skills/review-permissions/SKILL.md +11 -2
- package/packages/modes/src/index.ts +31 -6
- package/packages/modes/src/judge-gate.ts +91 -7
- package/packages/modes/src/modes.ts +4 -1
- package/packages/modes/src/review/apply.mjs +11 -6
- package/packages/modes/src/review/collect.mjs +45 -2
- package/packages/modes/src/workdir.ts +163 -0
- package/packages/router/README.md +2 -1
- package/packages/router/src/config.ts +2 -1
- package/packages/router/src/index.ts +9 -0
- package/packages/subagent/LICENSE +21 -0
- package/packages/subagent/README.md +17 -0
- package/packages/subagent/package.json +22 -0
- package/packages/subagent/src/index.ts +85 -0
- package/packages/web/README.md +1 -1
- package/src/index.ts +1 -1
- package/src/settings.ts +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hüseyin Sönmez
|
|
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.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# pi-subagent
|
|
2
|
+
|
|
3
|
+
Part of [pi-suite](../../README.md). Turn it off with `/suite off subagent`.
|
|
4
|
+
|
|
5
|
+
One tool, `agent`, that hands a task to a fresh copy of Pi and gives back its final answer as text. Wide searches, heavy reading and independent reviews happen in their context rather than yours. There's nothing to set up, and no agents to define.
|
|
6
|
+
|
|
7
|
+
| `type` | What it can do |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `explore` (the default) | Only `read`, `grep`, `find` and `ls`. Also allowed in `plan` and `read-only` |
|
|
10
|
+
| `general` | Everything, under your permission rules. Blocked in `plan` and `read-only` |
|
|
11
|
+
|
|
12
|
+
- **Parallel:** several calls in one message run at once.
|
|
13
|
+
- **Model:** it uses the session's model and reasoning. With `router/auto`, that's the `subagent` role in `/router`, so a cheaper model can do the reading.
|
|
14
|
+
- **Permissions:** a child's asks come to your session, and `auto` carries over to it.
|
|
15
|
+
- **Limits:** a child can't start its own agents, its answer is cut at 30,000 characters, it isn't saved as a session, and it starts as fast as Pi does. For long work in the background, use [pi-board](../board/README.md).
|
|
16
|
+
|
|
17
|
+
If you have `pi-subagents` installed, remove it, or the model has two tools for the same job.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@huseynsnmz/pi-subagent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "One simple tool for handing a task to a fresh agent.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"private": true,
|
|
8
|
+
"scripts": {
|
|
9
|
+
"check": "tsc --noEmit",
|
|
10
|
+
"test": "node --test test/*.test.ts"
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
14
|
+
"typebox": "*"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@earendil-works/pi-coding-agent": "1.5.0",
|
|
18
|
+
"@types/node": "26.6.6",
|
|
19
|
+
"typescript": "7.0.2",
|
|
20
|
+
"typebox": "1.4.3"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** One tool, `agent`: hands a task to a fresh copy of Pi, and gives back its final answer as text. */
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { Type } from "typebox";
|
|
6
|
+
|
|
7
|
+
const READ_ONLY_TOOLS = ["read", "grep", "find", "ls"];
|
|
8
|
+
const ANSWER_CHARS = 30_000;
|
|
9
|
+
const ERROR_CHARS = 500;
|
|
10
|
+
|
|
11
|
+
export interface ChildOptions {
|
|
12
|
+
type: "explore" | "general";
|
|
13
|
+
model?: string;
|
|
14
|
+
thinking?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The prompt goes in on stdin, so it can't be taken for a flag and doesn't show in the process list. */
|
|
18
|
+
export function childArgs({ type, model, thinking }: ChildOptions): string[] {
|
|
19
|
+
return ["-p", "--no-session", ...(model === undefined ? [] : ["--model", model]), ...(thinking === undefined ? [] : ["--thinking", thinking]), ...(type === "explore" ? ["--tools", READ_ONLY_TOOLS.join(",")] : [])];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The mode a child runs in. Plan's only way out is a person, so its explore agents just read, and yolo has to be asked for. */
|
|
23
|
+
function childMode(mode: string): string {
|
|
24
|
+
if (mode === "plan") return "read-only";
|
|
25
|
+
return mode === "yolo" ? "normal" : mode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** How the permission system and pi-router know this is a child, and pi-modes knows its mode. */
|
|
29
|
+
export function childEnv(base: NodeJS.ProcessEnv, parentSession: string, mode: string | undefined): NodeJS.ProcessEnv {
|
|
30
|
+
return { ...base, PI_SUBAGENT_CHILD: "1", PI_SUBAGENT_PARENT_SESSION: parentSession, ...(mode === undefined ? {} : { PI_MODES_START: childMode(mode) }) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function trimAnswer(answer: string): string {
|
|
34
|
+
const text = answer.trim();
|
|
35
|
+
return text.length > ANSWER_CHARS ? `${text.slice(0, ANSWER_CHARS)}\n[The answer was cut at ${ANSWER_CHARS} characters.]` : text;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function runChild(cwd: string, command: string[], env: NodeJS.ProcessEnv, prompt: string, signal: AbortSignal | undefined): Promise<string> {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const child = spawn(process.execPath, command, { cwd, env, stdio: ["pipe", "pipe", "pipe"], signal });
|
|
41
|
+
let out = "";
|
|
42
|
+
let err = "";
|
|
43
|
+
// A character can be split across two chunks.
|
|
44
|
+
child.stdout.setEncoding("utf8");
|
|
45
|
+
child.stderr.setEncoding("utf8");
|
|
46
|
+
child.stdout.on("data", (data) => (out += data));
|
|
47
|
+
child.stderr.on("data", (data) => (err = (err + data).slice(-ERROR_CHARS)));
|
|
48
|
+
child.stdin.on("error", () => {}); // It can exit before reading. Its exit code says why.
|
|
49
|
+
child.on("error", (error) => reject(signal?.aborted ? new Error("The agent was stopped.") : error));
|
|
50
|
+
child.on("close", (code) => {
|
|
51
|
+
if (code !== 0) return reject(new Error(`The agent failed: ${err.trim() || `exit code ${code}`}`));
|
|
52
|
+
if (out.trim() === "") return reject(new Error("The agent gave no answer."));
|
|
53
|
+
resolve(trimAnswer(out));
|
|
54
|
+
});
|
|
55
|
+
child.stdin.end(prompt);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default function piSubagent(pi: ExtensionAPI): void {
|
|
60
|
+
// A child's job is its own task. Agents starting agents is how a small task gets a big bill.
|
|
61
|
+
if (process.env.PI_SUBAGENT_CHILD === "1") return;
|
|
62
|
+
|
|
63
|
+
pi.registerTool({
|
|
64
|
+
name: "agent",
|
|
65
|
+
label: "Agent",
|
|
66
|
+
description:
|
|
67
|
+
"Hand a task to a fresh agent, and get back its final answer as text. It starts with none of this conversation, so the prompt must say everything it needs, and what to report. Use it for wide searches, for reading a lot, and for an independent review, so all that doesn't fill this context. Several calls in one message run at once. Don't use it for what a direct tool call or two can answer.",
|
|
68
|
+
parameters: Type.Object({
|
|
69
|
+
description: Type.String({ description: "What it's doing, in 3 to 5 words" }),
|
|
70
|
+
prompt: Type.String({ description: "The whole task, standing on its own" }),
|
|
71
|
+
type: Type.Optional(Type.Union([Type.Literal("explore"), Type.Literal("general")], { description: "explore (the default) can only read. general can also edit and run commands, under the same permission rules" })),
|
|
72
|
+
}),
|
|
73
|
+
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
74
|
+
const probe: { mode?: string } = {};
|
|
75
|
+
pi.events.emit("pi-modes:mode", probe);
|
|
76
|
+
const model = ctx.model === undefined ? undefined : `${ctx.model.provider}/${ctx.model.id}`;
|
|
77
|
+
const args = childArgs({ type: params.type ?? "explore", model, thinking: pi.getThinkingLevel() });
|
|
78
|
+
// Pi may run from a compiled binary, with no script to name.
|
|
79
|
+
const script = process.argv[1];
|
|
80
|
+
const command = script !== undefined && existsSync(script) ? [script, ...args] : args;
|
|
81
|
+
const answer = await runChild(ctx.cwd, command, childEnv(process.env, ctx.sessionManager.getSessionId(), probe.mode), params.prompt, signal);
|
|
82
|
+
return { content: [{ type: "text" as const, text: answer }], details: {} };
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
package/packages/web/README.md
CHANGED
|
@@ -9,7 +9,7 @@ A web page can hold instructions aimed at the AI reading it. So a separate model
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
pi install npm:@huseynsnmz/pi-suite@0.2.
|
|
12
|
+
pi install npm:@huseynsnmz/pi-suite@0.2.5
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
`web_fetch` works straight away. It reads pages with the session's model, unless you set `readerModel` or use [pi-router](https://github.com/huseynsnmz/pi-suite/tree/main/packages/router), which can choose the reader. `web_search` stays off until you choose where searches go.
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// One package,
|
|
1
|
+
// One package, many parts. A part that's off isn't even imported, so it adds no tools, prompts or start-up time.
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
package/src/settings.ts
CHANGED
|
@@ -11,6 +11,7 @@ export const PARTS = {
|
|
|
11
11
|
frame: "The start card, framed editor, tool boxes, footer and todo list",
|
|
12
12
|
board: "The board of agent sessions",
|
|
13
13
|
commands: "/clear and /security-review",
|
|
14
|
+
subagent: "The agent tool: hand a task to a fresh agent, and get its answer back",
|
|
14
15
|
subscription: "Claude models on your Claude login, asking before anything is billed as extra usage",
|
|
15
16
|
} as const;
|
|
16
17
|
|