pi-goal 0.1.1 → 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/.pi/extensions/pi-goal/index.ts +52 -12
- package/README.md +4 -0
- package/package.json +1 -1
|
@@ -21,6 +21,7 @@ type GoalState = {
|
|
|
21
21
|
type GoalEventKind = "active" | "continuation" | "paused" | "resumed" | "cleared" | "budget_limited" | "complete";
|
|
22
22
|
|
|
23
23
|
let goal: GoalState | null = null;
|
|
24
|
+
let statusBarEnabled = true;
|
|
24
25
|
let activeTurnStartedAt: number | null = null;
|
|
25
26
|
let continuationQueued = false;
|
|
26
27
|
let pendingControlPrompt: string | null = null;
|
|
@@ -118,22 +119,46 @@ function emitGoalEvent(
|
|
|
118
119
|
);
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
function
|
|
122
|
+
function latestStateFromSession(ctx: ExtensionContext): { goal: GoalState | null; statusBarEnabled: boolean } {
|
|
122
123
|
const entries = ctx.sessionManager.getBranch?.() ?? ctx.sessionManager.getEntries();
|
|
123
124
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
124
125
|
const entry = entries[i] as any;
|
|
125
126
|
if (entry.type === "custom" && entry.customType === CUSTOM_TYPE) {
|
|
126
|
-
return
|
|
127
|
+
return {
|
|
128
|
+
goal: entry.data?.goal ?? null,
|
|
129
|
+
statusBarEnabled: entry.data?.statusBarEnabled ?? true,
|
|
130
|
+
};
|
|
127
131
|
}
|
|
128
132
|
}
|
|
129
|
-
return null;
|
|
133
|
+
return { goal: null, statusBarEnabled: true };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function updateStatusBar(ctx: ExtensionContext) {
|
|
137
|
+
ctx.ui.setStatus(CUSTOM_TYPE, statusBarEnabled ? statusLine(goal) ?? "" : "");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const GOAL_TOOL_NAMES = ["get_goal", "update_goal"];
|
|
141
|
+
|
|
142
|
+
// Expose goal tools to the LLM only while a goal is actively being pursued.
|
|
143
|
+
// When no goal exists (or it is paused / complete / budget-limited), keep them
|
|
144
|
+
// hidden so unrelated sessions are not tempted to call them every turn.
|
|
145
|
+
function syncGoalTools(pi: ExtensionAPI) {
|
|
146
|
+
const want = goal?.status === "active";
|
|
147
|
+
const active = new Set(pi.getActiveTools());
|
|
148
|
+
for (const name of GOAL_TOOL_NAMES) (want ? active.add(name) : active.delete(name));
|
|
149
|
+
pi.setActiveTools(Array.from(active));
|
|
130
150
|
}
|
|
131
151
|
|
|
132
152
|
function persist(pi: ExtensionAPI, ctx: ExtensionContext, next: GoalState | null) {
|
|
133
153
|
goal = next;
|
|
134
|
-
pi.appendEntry(CUSTOM_TYPE, { goal: next });
|
|
135
|
-
|
|
136
|
-
|
|
154
|
+
pi.appendEntry(CUSTOM_TYPE, { goal: next, statusBarEnabled });
|
|
155
|
+
updateStatusBar(ctx);
|
|
156
|
+
syncGoalTools(pi);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function persistSettings(pi: ExtensionAPI, ctx: ExtensionContext) {
|
|
160
|
+
pi.appendEntry(CUSTOM_TYPE, { goal, statusBarEnabled });
|
|
161
|
+
updateStatusBar(ctx);
|
|
137
162
|
}
|
|
138
163
|
|
|
139
164
|
function continuationPrompt(state: GoalState): string {
|
|
@@ -235,7 +260,10 @@ export default function piGoal(pi: ExtensionAPI) {
|
|
|
235
260
|
name: "get_goal",
|
|
236
261
|
label: "Get Goal",
|
|
237
262
|
description: "Read the current active thread goal, if one exists.",
|
|
238
|
-
promptSnippet: "Read the current
|
|
263
|
+
promptSnippet: "Read the current pi-goal objective and remaining budget while pursuing it",
|
|
264
|
+
promptGuidelines: [
|
|
265
|
+
"Only call get_goal when you actually need the current objective or remaining budget; the continuation prompt already injects them.",
|
|
266
|
+
],
|
|
239
267
|
parameters: {
|
|
240
268
|
type: "object",
|
|
241
269
|
properties: {},
|
|
@@ -286,9 +314,9 @@ export default function piGoal(pi: ExtensionAPI) {
|
|
|
286
314
|
});
|
|
287
315
|
|
|
288
316
|
pi.registerCommand("goal", {
|
|
289
|
-
description: "Set, view, pause, resume, or
|
|
317
|
+
description: "Set, view, pause, resume, clear, or configure a long-running goal",
|
|
290
318
|
getArgumentCompletions: (prefix) => {
|
|
291
|
-
const values = ["pause", "resume", "clear", "status"];
|
|
319
|
+
const values = ["pause", "resume", "clear", "status", "statusbar", "statusbar on", "statusbar off"];
|
|
292
320
|
const filtered = values.filter((value) => value.startsWith(prefix));
|
|
293
321
|
return filtered.length ? filtered.map((value) => ({ value, label: value })) : null;
|
|
294
322
|
},
|
|
@@ -298,7 +326,15 @@ export default function piGoal(pi: ExtensionAPI) {
|
|
|
298
326
|
|
|
299
327
|
if (!trimmed || trimmed === "status") {
|
|
300
328
|
if (!goal) ctx.ui.notify("Usage: /goal [--tokens 50k] <objective>", "info");
|
|
301
|
-
else ctx.ui.notify(`${statusLine(goal)}\nObjective: ${goal.objective}`, "info");
|
|
329
|
+
else ctx.ui.notify(`${statusLine(goal)}\nObjective: ${goal.objective}\nStatus bar: ${statusBarEnabled ? "on" : "off"}`, "info");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (trimmed === "statusbar" || trimmed === "statusbar toggle" || trimmed === "statusbar on" || trimmed === "statusbar off") {
|
|
334
|
+
const [, value] = trimmed.split(/\s+/, 2);
|
|
335
|
+
statusBarEnabled = value === "on" ? true : value === "off" ? false : !statusBarEnabled;
|
|
336
|
+
persistSettings(pi, ctx);
|
|
337
|
+
ctx.ui.notify(`Goal status bar ${statusBarEnabled ? "enabled" : "disabled"}.`, "info");
|
|
302
338
|
return;
|
|
303
339
|
}
|
|
304
340
|
|
|
@@ -357,10 +393,14 @@ export default function piGoal(pi: ExtensionAPI) {
|
|
|
357
393
|
});
|
|
358
394
|
|
|
359
395
|
pi.on("session_start", (event, ctx) => {
|
|
360
|
-
|
|
396
|
+
const restored = latestStateFromSession(ctx);
|
|
397
|
+
goal = restored.goal;
|
|
398
|
+
statusBarEnabled = restored.statusBarEnabled;
|
|
361
399
|
pendingControlPrompt = null;
|
|
362
400
|
continuationQueued = false;
|
|
363
401
|
activeTurnStartedAt = null;
|
|
402
|
+
// Hide goal tools from the LLM unless we have an active goal to pursue.
|
|
403
|
+
syncGoalTools(pi);
|
|
364
404
|
if (goal?.status === "active" && event.reason === "reload") {
|
|
365
405
|
goal = { ...goal, status: "paused", updatedAt: Date.now() };
|
|
366
406
|
persist(pi, ctx, goal);
|
|
@@ -372,7 +412,7 @@ export default function piGoal(pi: ExtensionAPI) {
|
|
|
372
412
|
);
|
|
373
413
|
return;
|
|
374
414
|
}
|
|
375
|
-
ctx
|
|
415
|
+
updateStatusBar(ctx);
|
|
376
416
|
if (goal?.status === "active") {
|
|
377
417
|
emitGoalEvent(
|
|
378
418
|
pi,
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# pi-goal
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
Persistent autonomous goals for [pi](https://github.com/badlogic/pi-mono).
|
|
4
6
|
|
|
5
7
|
`pi-goal` adds a `/goal` command and goal tools so Pi can keep working toward a long-running objective until the goal is complete, paused, cleared, or token-budget-limited.
|
|
@@ -25,6 +27,7 @@ pi install git:github.com/Michaelliv/pi-goal
|
|
|
25
27
|
/goal pause
|
|
26
28
|
/goal resume
|
|
27
29
|
/goal clear
|
|
30
|
+
/goal statusbar off
|
|
28
31
|
```
|
|
29
32
|
|
|
30
33
|
When a goal is active, the extension shows compact visible lifecycle markers like `Goal active` and `Goal continuing`; expand them with `ctrl+o` to inspect the objective and usage. The actual continuation instructions are injected into the next turn's system prompt, so the full prompt does not clutter the transcript.
|
|
@@ -38,6 +41,7 @@ The same Pi agent keeps running normal turns in the same session context until i
|
|
|
38
41
|
- `/goal pause`: stop autonomous continuation without deleting the goal
|
|
39
42
|
- `/goal resume`: reactivate a paused goal
|
|
40
43
|
- `/goal clear`: remove the goal
|
|
44
|
+
- `/goal statusbar on|off`: show or hide the footer status line
|
|
41
45
|
- `get_goal` tool: read current goal state
|
|
42
46
|
- `update_goal` tool: model can only mark the goal `complete`
|
|
43
47
|
- footer status: `Pursuing goal`, `Goal paused`, `Goal achieved`, or `Goal unmet`
|