@signalridge/pi-goal 1.4.0 → 1.4.2

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 CHANGED
@@ -1,5 +1,87 @@
1
1
  # @signalridge/pi-goal
2
2
 
3
+ ## 1.4.2
4
+ ### Patch Changes
5
+
6
+ - d9219d4: Correct shipped documentation that contradicted the code or the manifest.
7
+
8
+ - `pi-subagents`: the summary line still advertised protocol-v3 spawning while
9
+ the feature list and the managed-spawn section described v4.
10
+ - `pi-worktree`: the workspace-switching section named `/tree`; the registered
11
+ command is `/worktree`.
12
+ - `pi-workflows`: removed an orphaned sentence fragment left behind by an edit
13
+ to the saved-workflow paragraph.
14
+ - `pi-plan-mode`, `pi-goal`, `pi-usage-extension`: the stated Pi floor
15
+ (`0.80.6`, `0.42.4+`) contradicted the declared `^0.84.0` peer range, which
16
+ resolves to `0.84.x`. `pi-usage-extension` also carried a "Last updated"
17
+ stamp from a version that predates the workspace baseline.
18
+ - `pi-welcome`: the example card was a real session capture. It is now a
19
+ generic sample, and Install moved above the configuration sections instead of
20
+ sitting below them.
21
+ - 1f586f8: Expand Pi peer support to `^0.84.0 || ^0.85.0`, retaining 0.84 compatibility while admitting 0.85 releases. The previous zero-major caret range excluded Pi 0.85.1. Update existing Pi development dependency pins to 0.85.1.
22
+ - d9219d4: Converge every README on one house style: plain sentence-case headings, no
23
+ decorative emoji.
24
+
25
+ Ten packages carried an emoji heading scheme inherited from their upstream
26
+ forks while the other nineteen used plain headings, so the same monorepo
27
+ rendered as two unrelated projects on npmjs.com. Headings are now emoji-free
28
+ and titles are sentence case.
29
+
30
+ Also removes the `Keywords` section from those ten. It duplicated each
31
+ package's `package.json` `keywords` field, which is what npm actually indexes,
32
+ and no plain-style README carried one. `Installation` is now `Install`
33
+ everywhere.
34
+
35
+ Headings that were Title Case are sentence case too, so one convention now
36
+ covers the whole monorepo. Existing in-page anchor links are unaffected:
37
+ GitHub lowercases heading slugs already.
38
+
39
+ Documentation only — no runtime change.
40
+ - 1f586f8: Keep goal iteration and continuation numbering correct when Pi compacts between tool results and the next response within one run. Preserve existing response limits and manual/post-run compaction handling.
41
+
42
+ Capture subagent invocation results from finalized assistant events with bounded message retention so compaction cannot hide a final error or mix in historical output. Recognize explicitly selected PowerShell when the host provides it, without requiring its export on older Pi versions or expanding default wildcard tools.
43
+
44
+ Use real session contexts for workflow replay and live TUI progress, release widget bindings on shutdown, and allow foreground checkpoint dialogs in RPC mode while retaining print-mode defaults. Cancel unanswered dialogs with their owning execution on tool abort, pause, stop, disposal, or fatal failure without journaling cancelled answers.
45
+ - Updated dependencies [1f586f8]
46
+ - Updated dependencies [1f586f8]
47
+ - Updated dependencies [1f586f8]
48
+ - Updated dependencies [1f586f8]
49
+ - @signalridge/pi-ui@1.3.1
50
+
51
+ ## 1.4.1
52
+ ### Patch Changes
53
+
54
+ - c1b1741: Durability and containment fixes found in a repository-wide scan.
55
+
56
+ `pi-gpt-fast` rewrote pi's own global `settings.json` with a plain
57
+ `writeFileSync`. That file is shared with pi and every other extension, and this
58
+ one is the only writer of it; a torn write would have left the user with no pi
59
+ configuration at all rather than just no gpt-fast setting. It now writes a temp
60
+ file and renames, matching every other settings writer in the repository.
61
+ `pi-goal` gets the same treatment for its cross-project goal state file, which
62
+ its own settings module already did.
63
+
64
+ `pi-ralph-wiggum` resolved `/ralph start <path>` straight against the session
65
+ cwd, so `../../notes.md` would create directories and a file outside the project
66
+ the user opened — silently, before the loop started. This extension drives long
67
+ unattended loops, so that command is as likely to come from a model as from a
68
+ person. Task-file paths are now required to stay inside the workspace, on the
69
+ command, on the `ralph_start` tool, and on the paths read back out of persisted
70
+ state, so a state file written earlier or edited by hand cannot pull a file in
71
+ from outside either.
72
+
73
+ The containment test canonicalizes both sides before comparing, because a purely
74
+ lexical one is defeated by a symlink: every segment of `linked/plan.md` reads as
75
+ inside the workspace when `linked` is a door out of it. `realpathSync` throws on
76
+ a path that does not exist yet — the ordinary case, since `start` is usually
77
+ creating the file — so it resolves the deepest existing ancestor and re-attaches
78
+ the not-yet-created suffix, which cannot itself be a link. The traversal test
79
+ matches a `..` segment rather than a `..` prefix; the prefix form also rejected
80
+ `..notes.md`, an ordinary filename sitting in the workspace.
81
+
82
+ Every remaining bare `catch {}` now says why the error is safe to drop, matching
83
+ the convention the rest of the repository already follows.
84
+
3
85
  ## 1.4.0
4
86
  ### Minor Changes
5
87
 
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # 🎯 pi-goal — Goal Mode for the Pi Coding Agent
1
+ # pi-goal — goal mode for the Pi coding agent
2
2
 
3
3
  [![Pi extension](https://img.shields.io/badge/Pi-extension-blue)](https://pi.dev) [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
4
4
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  Goal mode uses Codex-like persistence instructions and sends guarded continuation messages from Pi's fully settled idle boundary until the agent completes the goal, the user pauses or clears it, a safety circuit breaker trips, a true blocker or provider usage limit stops it, or an optional token budget is reached. With ordered goals enabled, the same lifecycle advances through queued objectives one at a time.
8
8
 
9
- ## ✨ Features
9
+ ## Features
10
10
 
11
11
  - Adds `/goal <goal_to_complete>` to start goal mode, with confirmation before replacing an existing goal.
12
12
  - Bare `/goal` opens a standard current-state manager in the TUI, with guided start, pause,
@@ -36,9 +36,9 @@ Goal mode uses Codex-like persistence instructions and sends guarded continuatio
36
36
  - Applies one evidence-based completion audit across kickoff, resume, edit, system, continuation, and budget-wrap-up prompts.
37
37
  - Optionally exposes a default-off, run-scoped `pi.events` protocol for trusted sibling extensions to start, observe, and cancel one Goal lifecycle without emulating `/goal` input.
38
38
 
39
- ## 📦 Install
39
+ ## Install
40
40
 
41
- Requires Pi `0.80.6` or newer for the `agent_settled` lifecycle event.
41
+ Supports Pi `0.84.x` and `0.85.x`; tested with `0.85.1` — see the `peerDependencies` range. The `agent_settled` lifecycle event this extension depends on landed in `0.80.6`.
42
42
 
43
43
  ```bash
44
44
  pi install npm:@signalridge/pi-goal
@@ -52,7 +52,7 @@ From the repository root:
52
52
  pi -e ./packages/pi-goal
53
53
  ```
54
54
 
55
- ## ⚙️ Configuration
55
+ ## Configuration
56
56
 
57
57
  Settings are optional. When `~/.pi/agent/pi-goal.json` is absent, pi-goal uses these
58
58
  built-in defaults without creating the file:
@@ -107,7 +107,7 @@ restores only the exact tools that pi-goal previously hid, while switching to
107
107
 
108
108
  Tool visibility is a baseline, not ownership of Pi's global active-tool list. Plan mode or another restrictive policy may temporarily hide the tools. pi-goal does not fight that policy on restore or on every turn: activation is rejected if both tools cannot be made available, and an already-active goal is paused without automatic continuation if they disappear. The pause aborts a Goal-owned kickoff, resume, active-edit, or automatic-continuation prompt, but it does not cancel or stale-block an unrelated user or extension turn, including startup follow-ups after a restrictive restore.
109
109
 
110
- ## 🚀 Commands
110
+ ## Commands
111
111
 
112
112
  ```text
113
113
  /goal
@@ -161,7 +161,7 @@ When the experiment is disabled, queue words retain the original parser behavior
161
161
 
162
162
  Goal objectives are limited to 4,000 characters. Put longer instructions in a file and reference the file path from `/goal`.
163
163
 
164
- ## 🔁 Session and reload behavior
164
+ ## Session and reload behavior
165
165
 
166
166
  Goal state is stored as Pi session state, similar to Codex's thread-owned goals. `/reload` and reopening the same Pi session can restore that session's unfinished goal. An active restored goal already at or above its finite automatic-work limit pauses before another provider request and reports that progress is saved; use `/goal` to review and continue. With `"after-first-goal"`, an unfinished restore marks the tools unlocked in the new extension runtime, but it does not widen an active-tool set already restricted by an earlier lifecycle handler; an active goal instead restores as paused when either terminal tool is missing. If no unfinished goal remains, a fresh runtime starts locked again. Active elapsed time is checkpointed before shutdown and restarted after reload, so offline and stopped wall-clock time is excluded. Automatic-response counts, repeat fingerprints, and safety-pause causes persist across reload and compaction. A direct non-`/goal` user/RPC input resets the safety epoch only while the goal is active and reclassifies the in-flight run as manual; extension input and messages sent while stopped do not reset it. Starting a new Pi session in the same working directory does not inherit the old goal.
167
167
 
@@ -171,7 +171,7 @@ If a session still contains multiple goals or a pending queue transition when `e
171
171
 
172
172
  Older versions wrote unfinished goals to `~/.pi/agent/pi-goal-state.json` keyed by working directory. This version no longer reads that global file, and `/goal clear` removes any legacy entry for the current working directory.
173
173
 
174
- ## 📊 Statusline states
174
+ ## Statusline states
175
175
 
176
176
  `pi-goal` writes compact plain status strings for statusline extensions. `@signalridge/pi-statusline` adds the default `🎯` icon unless configured otherwise:
177
177
 
@@ -186,7 +186,7 @@ Older versions wrote unfinished goals to `~/.pi/agent/pi-goal-state.json` keyed
186
186
  - `complete` — shown briefly after `goal_complete` succeeds.
187
187
  - `queue off` — retained ordered goals are frozen because `experimental.goals` is disabled.
188
188
 
189
- ## 💰 Token budgets and elapsed time
189
+ ## Token budgets and elapsed time
190
190
 
191
191
  The TUI budget chooser describes token budgets as cumulative Goal usage, warns that the final model
192
192
  call may exceed the chosen value, and keeps the independent automatic-work response limit visible.
@@ -204,7 +204,7 @@ The default 25-response automatic-work limit is a response-count boundary, not a
204
204
 
205
205
  Elapsed time is accumulated only while status is `active`. Pause, blocked, usage-limited, budget-limited, shutdown, and offline periods do not increase it. Legacy session entries are migrated by preserving their accumulated seconds and starting a fresh active clock when loaded.
206
206
 
207
- ## ✅ How completion works
207
+ ## How completion works
208
208
 
209
209
  While a goal is active, `pi-goal` injects persistence rules, a `<goal_id>` stale-turn guard, and exposes `goal_complete`. Kickoff, resume, edited-objective, system, and automatic-continuation prompts all place a trust boundary before the escaped objective, identifying it as user-provided task data; they preserve its full scope across turns and require the agent to derive concrete requirements from the objective and referenced artifacts. They treat the current worktree, command output, tests, runtime behavior, PR state, rendered artifacts, and external state as authoritative; previous conversation and plans are context rather than proof.
210
210
 
@@ -216,17 +216,17 @@ If a turn ends before completion, `pi-goal` records usage and creates one contin
216
216
 
217
217
  Manual compaction does not emit `agent_settled`, so its completion hook uses the same single-flight dispatcher as a narrow idle-only fallback. Pi extensions cannot reserve an idle turn atomically like Codex core; another extension can still win the race after the idle check, and its newer turn supersedes the old continuation intent.
218
218
 
219
- ## 🚧 Blocked goals
219
+ ## Blocked goals
220
220
 
221
221
  `goal_blocked` is intentionally narrower than completion or ordinary clarification. Every goal-mode prompt repeats the blocked audit: the model must provide the exact current `goal_id`, a specific reason describing the user or external action required (up to 1,000 characters), concrete evidence from the failed resolution attempts (up to 4,000 characters), and `repeated_turns` showing the same blocker recurred for at least three consecutive goal turns. A resumed goal starts a fresh blocker audit. Empty or oversized reasons/evidence, stale ids, non-whole turn counts, stopped goals, and fewer than three turns are rejected. Accepted blocker reports set `blocked`, stop automatic continuation, and terminate the tool batch when Pi can do so safely.
222
222
 
223
223
  Do not use `goal_blocked` merely because work is difficult, incomplete, uncertain, awaiting normal clarification, or affected by a recoverable tool/provider failure. The user can resolve the external condition and run `/goal resume` to rotate the goal id and continue.
224
224
 
225
- ## 🛑 Interruption and queued-input behavior
225
+ ## Interruption and queued-input behavior
226
226
 
227
227
  A user pause or aborted turn produces `paused`; a terminal provider/account quota error produces `usage_limited`; another non-retryable agent error produces `blocked`. Each stopped transition cancels pending continuation intent or delivery, aborts stale work when applicable, and blocks stale tool calls until the next non-goal user prompt, successful reactivation/replacement, or `/goal clear`. On `/goal clear`, the extension clears goal state, continuation markers, and any stale tool-call block without aborting an unrelated in-flight turn. Retryable provider interruptions and overflow compaction retries stay `active` while Pi retries; no extra continuation is queued, and automatic ownership remains charged through retry `agent_start` events. If matching recovery still exists at `agent_settled`, retries are exhausted and the goal becomes `blocked` before any pending queue transition dispatches. Stale recovery cannot block a replacement goal. User and extension work that starts before settlement supersedes the older continuation intent, and pending messages always take priority.
228
228
 
229
- ## 🤝 Managed run RPC
229
+ ## Managed run RPC
230
230
 
231
231
  With `rpc.enabled: true`, pi-goal exposes a session-local, dependency-free protocol over Pi's shared `pi.events` bus. It is intended for trusted sibling extensions that need to start, observe, and cancel one Goal lifecycle without driving the `/goal` command. Installed Pi extensions remain fully privileged: this setting controls only whether pi-goal cooperates with these channels and is not authentication or sandboxing.
232
232
 
@@ -296,7 +296,7 @@ Start never prompts for replacement: any pre-existing Goal is rejected. The prot
296
296
 
297
297
  This breaking contract replaces and removes `pi-goal:rpc:start`, `pi-goal:rpc:pause`, request-scoped start replies, and the global `pi-goal:state` broadcast. No compatibility aliases are registered.
298
298
 
299
- ## 🧠 Use cases
299
+ ## Use cases
300
300
 
301
301
  - Finish implementation tasks without stopping at a plan.
302
302
  - Keep debugging until the bug is verified fixed.
@@ -304,7 +304,7 @@ This breaking contract replaces and removes `pi-goal:rpc:start`, `pi-goal:rpc:pa
304
304
  - Encourage agents to test, lint, or typecheck before completion.
305
305
  - Make long-running Pi coding sessions more autonomous.
306
306
 
307
- ## 🗂️ Package layout
307
+ ## Package layout
308
308
 
309
309
  ```txt
310
310
  packages/pi-goal/
@@ -339,10 +339,6 @@ packages/pi-goal/
339
339
  }
340
340
  ```
341
341
 
342
- ## 🔎 Keywords
343
-
344
- Pi extension, Pi coding agent, goal mode, autonomous coding agent, AI agent workflow, task completion, agent loop, verification, TypeScript Pi package.
345
-
346
- ## 📄 License
342
+ ## License
347
343
 
348
344
  MIT. See [`LICENSE`](./LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signalridge/pi-goal",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "Pi extension for autonomous /goal completion with opt-in experimental ordered queues.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -36,22 +36,22 @@
36
36
  "lint": "biome check ."
37
37
  },
38
38
  "peerDependencies": {
39
- "@earendil-works/pi-ai": "^0.84.0",
40
- "@earendil-works/pi-coding-agent": "^0.84.0",
41
- "@earendil-works/pi-tui": "^0.84.0",
39
+ "@earendil-works/pi-ai": "^0.84.0 || ^0.85.0",
40
+ "@earendil-works/pi-coding-agent": "^0.84.0 || ^0.85.0",
41
+ "@earendil-works/pi-tui": "^0.84.0 || ^0.85.0",
42
42
  "typebox": "^1.3.11"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@biomejs/biome": "2.5.7",
46
- "@earendil-works/pi-ai": "0.84.1",
47
- "@earendil-works/pi-coding-agent": "0.84.1",
48
- "@earendil-works/pi-tui": "0.84.1",
46
+ "@earendil-works/pi-ai": "0.85.1",
47
+ "@earendil-works/pi-coding-agent": "0.85.1",
48
+ "@earendil-works/pi-tui": "0.85.1",
49
49
  "typebox": "1.3.11",
50
50
  "typescript": "7.0.2"
51
51
  },
52
52
  "dependencies": {
53
53
  "@narumitw/pi-tui-kit": "^0.54.0",
54
- "@signalridge/pi-ui": "^1.3.0"
54
+ "@signalridge/pi-ui": "^1.3.1"
55
55
  },
56
56
  "repository": {
57
57
  "type": "git",
package/src/lifecycle.ts CHANGED
@@ -205,6 +205,9 @@ export function registerGoalLifecycle(
205
205
 
206
206
  const wasPiRetry = runtime.isPiOwnedCompactionRetry(event, runtime.activeGoal.id);
207
207
  if (wasPiRetry) return;
208
+ // Between-tool compaction continues the same low-level run. agent_end owns
209
+ // its iteration and continuation ticket; creating one here skips that count.
210
+ if (runtime.agentRunGoalId !== undefined) return;
208
211
  runtime.clearGoalRecoveryForGoal(runtime.activeGoal.id);
209
212
  runtime.requestContinuation(runtime.activeGoal);
210
213
  // Pi emits session_compact before it clears its manual-compaction controller,
@@ -1,5 +1,6 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
+ import { basename, dirname, join } from "node:path";
3
4
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
5
  import { isNonNegativeFiniteNumber, nonNegativeFiniteNumber, normalizeTokenBudget } from "./accounting.js";
5
6
  import type { GoalStatus } from "./prompts.js";
@@ -238,12 +239,28 @@ function normalizeSafetyPauseCause(value: unknown): SafetyPauseCause | undefined
238
239
  return value === "continuation_limit" || value === "no_progress" ? value : undefined;
239
240
  }
240
241
 
242
+ /**
243
+ * Atomic for the same reason `saveGoalSettings` is: this file holds every
244
+ * project's goal state, so a torn write while clearing one project's entry
245
+ * would take the rest with it.
246
+ */
241
247
  export function clearLegacyPersistedGoal(cwd: string) {
242
248
  if (!existsSync(STATE_FILE)) return;
243
249
  const goals = readState();
244
250
  delete goals[cwd];
245
- mkdirSync(dirname(STATE_FILE), { recursive: true });
246
- writeFileSync(STATE_FILE, `${JSON.stringify(goals, null, 2)}\n`);
251
+ const document = `${JSON.stringify(goals, null, 2)}\n`;
252
+ const temporaryPath = join(dirname(STATE_FILE), `.${basename(STATE_FILE)}.${randomUUID()}.tmp`);
253
+ try {
254
+ mkdirSync(dirname(STATE_FILE), { recursive: true });
255
+ writeFileSync(temporaryPath, document, { encoding: "utf8", flag: "wx" });
256
+ renameSync(temporaryPath, STATE_FILE);
257
+ } finally {
258
+ try {
259
+ rmSync(temporaryPath, { force: true });
260
+ } catch {
261
+ // Best-effort cleanup must not replace the clear result.
262
+ }
263
+ }
247
264
  }
248
265
 
249
266
  function readState(): Record<string, unknown> {