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.
@@ -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 latestGoalFromSession(ctx: ExtensionContext): GoalState | null {
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 entry.data?.goal ?? null;
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
- const line = statusLine(next);
136
- ctx.ui.setStatus(CUSTOM_TYPE, line ?? "");
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 thread goal and budget state",
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 clear a long-running goal",
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
- goal = latestGoalFromSession(ctx);
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.ui.setStatus(CUSTOM_TYPE, statusLine(goal) ?? "");
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
+ ![pi-goal](docs/assets/pi-goal-poster.png)
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`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Persistent autonomous goals for pi — /goal loops until complete, paused, or budget-limited",
5
5
  "type": "commonjs",
6
6
  "keywords": [