@narumitw/pi-goal 0.1.3
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 +63 -0
- package/package.json +38 -0
- package/src/goal.ts +143 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 narumiruna
|
|
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,63 @@
|
|
|
1
|
+
# pi-goal
|
|
2
|
+
|
|
3
|
+
A public [pi](https://pi.dev) extension package that adds `/goal <goal_to_complete>`.
|
|
4
|
+
|
|
5
|
+
Goal mode keeps sending automatic follow-up messages until the agent calls the `goal_complete` tool, so tasks such as `/goal implement snake game` continue past planning and partial progress.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pi install npm:@narumitw/pi-goal
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Try without installing:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pi -e npm:@narumitw/pi-goal
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Try this package locally from the repository root:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pi -e ./extensions/pi-goal
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
/goal implement snake game
|
|
29
|
+
/goal-status
|
|
30
|
+
/goal-stop
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- `/goal <goal_to_complete>` starts goal mode and asks the agent to keep working until complete.
|
|
34
|
+
- `/goal-status` shows the active goal.
|
|
35
|
+
- `/goal-stop` cancels the active goal loop.
|
|
36
|
+
|
|
37
|
+
## How completion works
|
|
38
|
+
|
|
39
|
+
The extension registers a `goal_complete` tool. While a goal is active, the system prompt tells the agent to keep working, verify the result, and call `goal_complete` only when the goal is fully done.
|
|
40
|
+
|
|
41
|
+
If an agent turn ends before `goal_complete` is called, the extension automatically sends a follow-up prompt to continue the same goal.
|
|
42
|
+
|
|
43
|
+
## Package layout
|
|
44
|
+
|
|
45
|
+
```txt
|
|
46
|
+
extensions/pi-goal/
|
|
47
|
+
├── src/
|
|
48
|
+
│ └── goal.ts
|
|
49
|
+
├── README.md
|
|
50
|
+
├── LICENSE
|
|
51
|
+
├── tsconfig.json
|
|
52
|
+
└── package.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The package exposes its extension through `package.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"pi": {
|
|
60
|
+
"extensions": ["./src/goal.ts"]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@narumitw/pi-goal",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Pi extension that keeps working on a /goal until the agent marks it complete.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"private": false,
|
|
8
|
+
"keywords": [
|
|
9
|
+
"pi-package",
|
|
10
|
+
"pi-extension",
|
|
11
|
+
"pi",
|
|
12
|
+
"goal",
|
|
13
|
+
"automation"
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"pi": {
|
|
21
|
+
"extensions": [
|
|
22
|
+
"./src/goal.ts"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"check": "biome check . && npm run typecheck",
|
|
27
|
+
"format": "biome check --write .",
|
|
28
|
+
"typecheck": "tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"typebox": "^1.1.37"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@biomejs/biome": "2.4.14",
|
|
35
|
+
"@mariozechner/pi-coding-agent": "0.73.0",
|
|
36
|
+
"typescript": "6.0.3"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/goal.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { defineTool, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
|
|
4
|
+
interface ActiveGoal {
|
|
5
|
+
text: string;
|
|
6
|
+
startedAt: number;
|
|
7
|
+
iteration: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface GoalCompleteDetails {
|
|
11
|
+
goal: string;
|
|
12
|
+
summary: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let activeGoal: ActiveGoal | undefined;
|
|
16
|
+
|
|
17
|
+
const goalCompleteTool = defineTool({
|
|
18
|
+
name: "goal_complete",
|
|
19
|
+
label: "Goal Complete",
|
|
20
|
+
description:
|
|
21
|
+
"Mark the active /goal as complete. Only call this after the requested goal is fully done and verified.",
|
|
22
|
+
promptSnippet: "Mark the active /goal as complete after fully finishing and verifying it",
|
|
23
|
+
promptGuidelines: [
|
|
24
|
+
"When a /goal is active, keep working until the goal is complete; do not stop with only a plan or partial progress.",
|
|
25
|
+
"Call goal_complete only after the requested goal is fully implemented, verified, and no known required work remains.",
|
|
26
|
+
],
|
|
27
|
+
parameters: Type.Object({
|
|
28
|
+
summary: Type.String({
|
|
29
|
+
description: "Concise summary of what was completed and how it was verified.",
|
|
30
|
+
}),
|
|
31
|
+
}),
|
|
32
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
33
|
+
const completedGoal = activeGoal;
|
|
34
|
+
activeGoal = undefined;
|
|
35
|
+
ctx.ui.setStatus("goal", undefined);
|
|
36
|
+
|
|
37
|
+
const goal = completedGoal?.text ?? "unknown goal";
|
|
38
|
+
const summary = params.summary.trim();
|
|
39
|
+
|
|
40
|
+
ctx.ui.notify(`Goal complete: ${goal}`, "info");
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: "text", text: `Goal complete: ${summary}` }],
|
|
44
|
+
details: { goal, summary } satisfies GoalCompleteDetails,
|
|
45
|
+
terminate: true,
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export default function goal(pi: ExtensionAPI) {
|
|
51
|
+
pi.registerTool(goalCompleteTool);
|
|
52
|
+
|
|
53
|
+
pi.registerCommand("goal", {
|
|
54
|
+
description: "Run a goal to completion: /goal <goal_to_complete>",
|
|
55
|
+
handler: async (args, ctx) => {
|
|
56
|
+
const goalText = args.trim();
|
|
57
|
+
if (!goalText) {
|
|
58
|
+
ctx.ui.notify("Usage: /goal <goal_to_complete>", "warning");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
activeGoal = {
|
|
63
|
+
text: goalText,
|
|
64
|
+
startedAt: Date.now(),
|
|
65
|
+
iteration: 0,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
ctx.ui.setStatus("goal", `goal: ${goalText}`);
|
|
69
|
+
ctx.ui.notify(`Goal started: ${goalText}`, "info");
|
|
70
|
+
|
|
71
|
+
const prompt = buildGoalPrompt(goalText);
|
|
72
|
+
if (ctx.isIdle()) {
|
|
73
|
+
pi.sendUserMessage(prompt);
|
|
74
|
+
} else {
|
|
75
|
+
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
pi.registerCommand("goal-stop", {
|
|
81
|
+
description: "Stop the active /goal loop",
|
|
82
|
+
handler: async (_args, ctx) => {
|
|
83
|
+
if (!activeGoal) {
|
|
84
|
+
ctx.ui.notify("No active goal.", "info");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const stoppedGoal = activeGoal.text;
|
|
89
|
+
activeGoal = undefined;
|
|
90
|
+
ctx.ui.setStatus("goal", undefined);
|
|
91
|
+
ctx.ui.notify(`Goal stopped: ${stoppedGoal}`, "warning");
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
pi.registerCommand("goal-status", {
|
|
96
|
+
description: "Show the active /goal status",
|
|
97
|
+
handler: async (_args, ctx) => {
|
|
98
|
+
if (!activeGoal) {
|
|
99
|
+
ctx.ui.notify("No active goal.", "info");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
ctx.ui.notify(`Active goal: ${activeGoal.text} (iteration ${activeGoal.iteration})`, "info");
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
pi.on("session_start", (_event, ctx) => {
|
|
108
|
+
if (activeGoal) ctx.ui.setStatus("goal", `goal: ${activeGoal.text}`);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
112
|
+
ctx.ui.setStatus("goal", undefined);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
pi.on("before_agent_start", (event) => {
|
|
116
|
+
if (!activeGoal) return;
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
systemPrompt: `${event.systemPrompt}\n\n${buildGoalSystemPrompt(activeGoal.text)}`,
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
pi.on("agent_end", (_event, ctx) => {
|
|
124
|
+
if (!activeGoal) return;
|
|
125
|
+
|
|
126
|
+
activeGoal.iteration += 1;
|
|
127
|
+
ctx.ui.setStatus("goal", `goal: ${activeGoal.text} (${activeGoal.iteration})`);
|
|
128
|
+
|
|
129
|
+
pi.sendUserMessage(buildContinuePrompt(activeGoal), { deliverAs: "followUp" });
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function buildGoalPrompt(goalText: string) {
|
|
134
|
+
return `Goal mode is active. Complete this goal fully:\n\n${goalText}\n\nKeep working until the goal is done. Do not stop after planning or partial progress. When the goal is fully complete and verified, call the goal_complete tool with a concise completion summary.`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function buildGoalSystemPrompt(goalText: string) {
|
|
138
|
+
return `Active /goal: ${goalText}\n\nGoal-mode rules:\n- Continue making concrete progress until the active goal is fully complete.\n- Do not end your response with only a plan, TODO list, or partial progress.\n- Verify the result when possible using appropriate checks.\n- If the goal is not complete at the end of a turn, expect an automatic follow-up and continue from where you left off.\n- Only call the goal_complete tool after the goal is fully complete and verified.`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function buildContinuePrompt(goal: ActiveGoal) {
|
|
142
|
+
return `Continue the active /goal until it is complete:\n\n${goal.text}\n\nThis is automatic continuation #${goal.iteration}. If the goal is not complete yet, keep working and verify progress. If it is fully complete and verified, call the goal_complete tool.`;
|
|
143
|
+
}
|