pi-goal-x 0.31.2 → 0.31.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/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  <a href="https://pi.dev/packages?type=extension" target="_blank" rel="noopener noreferrer">
7
7
  <picture>
8
8
  <source media="(prefers-color-scheme: dark)" srcset="assets/badge-dark.svg">
9
- <img src="assets/badge-light.svg" alt="TOP 0.3% of Pi coding agent extensions: #7 of 3,216 by downloads · Sep 8, 2026 (best recorded rank)" width="480">
9
+ <img src="assets/badge-light.svg" alt="TOP 0.3% of Pi coding agent extensions: #7 of 3,200 by downloads · Sep 14, 2026 (best recorded rank)" width="480">
10
10
  </picture>
11
11
  </a>
12
12
  </div>
@@ -1,4 +1,4 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="480" height="54" viewBox="0 0 480 54" role="img" aria-label="TOP 0.3% of Pi coding agent extensions: #7 of 3,216 by downloads · Sep 8, 2026 (best recorded rank)">
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="480" height="54" viewBox="0 0 480 54" role="img" aria-label="TOP 0.3% of Pi coding agent extensions: #7 of 3,200 by downloads · Sep 14, 2026 (best recorded rank)">
2
2
  <!-- Dark mode -->
3
3
  <style>
4
4
  .frame { fill: #14181f; stroke: #2a3040; stroke-width: 1; }
@@ -13,6 +13,6 @@
13
13
  <text class="rank" x="18" y="34">TOP 0.3%</text>
14
14
  <line class="divider" x1="128" y1="13" x2="128" y2="41"/>
15
15
  <text class="label" x="143" y="25">of Pi coding agent extensions</text>
16
- <text class="meta" x="143" y="41">#7 of 3,216 by downloads · Sep 8, 2026</text>
16
+ <text class="meta" x="143" y="41">#7 of 3,200 by downloads · Sep 14, 2026</text>
17
17
  </a>
18
18
  </svg>
@@ -1,4 +1,4 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="480" height="54" viewBox="0 0 480 54" role="img" aria-label="TOP 0.3% of Pi coding agent extensions: #7 of 3,216 by downloads · Sep 8, 2026 (best recorded rank)">
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="480" height="54" viewBox="0 0 480 54" role="img" aria-label="TOP 0.3% of Pi coding agent extensions: #7 of 3,200 by downloads · Sep 14, 2026 (best recorded rank)">
2
2
  <!-- Light mode -->
3
3
  <style>
4
4
  .frame { fill: #f6f8fa; stroke: #d0d7de; stroke-width: 1; }
@@ -13,6 +13,6 @@
13
13
  <text class="rank" x="18" y="34">TOP 0.3%</text>
14
14
  <line class="divider" x1="128" y1="13" x2="128" y2="41"/>
15
15
  <text class="label" x="143" y="25">of Pi coding agent extensions</text>
16
- <text class="meta" x="143" y="41">#7 of 3,216 by downloads · Sep 8, 2026</text>
16
+ <text class="meta" x="143" y="41">#7 of 3,200 by downloads · Sep 14, 2026</text>
17
17
  </a>
18
18
  </svg>
@@ -86,6 +86,11 @@ export function registerGoalEvents(core: GoalCore): void {
86
86
  const { pi } = core;
87
87
  let continuationAfterSettleFor: string | null = null;
88
88
  let networkErrorRecoveryAfterSettleFor: string | null = null;
89
+ // Run-scoped empty-turn gate: turn_start resets the per-turn flag, so a text-only
90
+ // last turn after earlier work must not wipe the run. before_agent_start starts a
91
+ // new run. agent_settled must not auto-continue a run that did no goal work, or
92
+ // empty replies ("Paused. No action.") loop forever.
93
+ let goalWorkThisRun = false;
89
94
 
90
95
  pi.on("context", async (event) => {
91
96
  const filtered = filterGoalSessionContext(event.messages);
@@ -128,9 +133,10 @@ export function registerGoalEvents(core: GoalCore): void {
128
133
  `End the turn with a brief summary and yield to the user.`,
129
134
  };
130
135
  }
131
- // Track for #4 empty-turn gate.
136
+ // Track for #4 empty-turn gate (per-turn and per-run).
132
137
  if (isMeaningfulProgressToolCall(event.toolName, asRecord(event)?.args)) {
133
138
  core.goalWorkToolCalledThisTurn = true;
139
+ goalWorkThisRun = true;
134
140
  // Issue #26: record a meaningful work attempt against armed Oracle
135
141
  // advice. get_goal / echo-only reads are excluded upstream by
136
142
  // isMeaningfulProgressToolCall.
@@ -339,6 +345,7 @@ export function registerGoalEvents(core: GoalCore): void {
339
345
  });
340
346
 
341
347
  pi.on("before_agent_start", async (event, ctx) => {
348
+ goalWorkThisRun = false;
342
349
  core.advanceTurnSeq();
343
350
  if (!hasActiveDraft(core)) core.installGoalToolProfile(!loadGoalSettings(ctx.cwd).disableTasks);
344
351
  const currentSystemPrompt = () => ctx.getSystemPrompt?.() || event.systemPrompt;
@@ -524,6 +531,10 @@ export function registerGoalEvents(core: GoalCore): void {
524
531
  core.runtime.clearNetworkErrorBackoff();
525
532
  core.persist(ctx);
526
533
  core.updateUI(ctx);
534
+ // Empty-turn gate: agent_end clears any turn_end continuation timer, then
535
+ // agent_settled re-queues with force=true. Without this check, a no-tool
536
+ // reply (chat, or "Paused. No action." after resume) loops forever.
537
+ if (!goalWorkThisRun) return;
527
538
  // agent_end runs before pi finishes retries, compaction, terminating-tool
528
539
  // settlement, and queued messages. Starting the continuation timer here
529
540
  // can poll a stale busy context for minutes on pi 0.84. agent_settled is
@@ -33,21 +33,24 @@ export interface TaskConfirmationResult {
33
33
  }
34
34
 
35
35
  export async function showTaskConfirmation(ctx: ExtensionContext, proposalText: string): Promise<TaskConfirmationResult> {
36
- const autoConfirmEnv = process.env.PI_GOAL_AUTO_CONFIRM;
37
- if (autoConfirmEnv === "0") {
38
- // Explicit opt-out (benchmarking): in headless mode the proposal is
39
- // declined without a dialog.
40
- if (!ctx.hasUI) return { decision: "cancel" };
41
- } else if (!ctx.hasUI || autoConfirmEnv === "1") {
42
- // Headless default, or forced auto-confirm even with a UI.
43
- return { decision: "confirm" };
36
+ if (process.env.PI_GOAL_AUTO_CONFIRM === "1") return { decision: "confirm" };
37
+ if (!ctx.hasUI) return { decision: process.env.PI_GOAL_AUTO_CONFIRM === "0" ? "cancel" : "confirm" };
38
+ if (ctx.mode !== "rpc" && typeof ctx.ui.custom === "function") {
39
+ const result = await showTaskListConfirmationDialog(ctx, proposalText);
40
+ if (result) return result;
44
41
  }
45
- return showTaskListConfirmationDialog(ctx, proposalText);
42
+ if (typeof ctx.ui.select !== "function") return { decision: "cancel" };
43
+ const answer = await ctx.ui.select(`Task list confirmation\n\n${proposalText}`, ["Confirm task list", "Keep current tasks"]);
44
+ return { decision: answer === "Confirm task list" ? "confirm" : "cancel" };
46
45
  }
47
46
 
48
- async function showTaskListConfirmationDialog(ctx: ExtensionContext, proposalText: string): Promise<TaskConfirmationResult> {
49
- return await ctx.ui.custom<TaskConfirmationResult>(
50
- (tui: TUI, theme: Theme, _keybindings: unknown, done: (result: TaskConfirmationResult) => void): Component => {
47
+ async function showTaskListConfirmationDialog(ctx: ExtensionContext, proposalText: string): Promise<TaskConfirmationResult | undefined> {
48
+ return await ctx.ui.custom<TaskConfirmationResult | undefined>(
49
+ (tui: TUI, theme: Theme, _keybindings: unknown, done: (result: TaskConfirmationResult | undefined) => void): Component => {
50
+ if (!tui || typeof tui.getShowHardwareCursor !== "function" || typeof tui.setShowHardwareCursor !== "function" || typeof tui.requestRender !== "function") {
51
+ done(undefined);
52
+ return { render: () => [], invalidate: () => {} };
53
+ }
51
54
  const wasHardwareCursorShown = tui.getShowHardwareCursor();
52
55
  tui.setShowHardwareCursor(false);
53
56
  // Pause pi's working spinner for the dialog duration: its ~80ms re-renders
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-x",
3
- "version": "0.31.2",
3
+ "version": "0.31.3",
4
4
  "description": "Adds /goal to pi: conversational goal planning, flexible or ordered goals, persistent progress, and an independent completion auditor.",
5
5
  "license": "MIT",
6
6
  "author": "pi-goal-x contributors",