pi-goal-x 0.31.0 → 0.31.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/README.md CHANGED
@@ -3,477 +3,124 @@
3
3
  </div>
4
4
 
5
5
  <div align="center">
6
- <a href="https://pi.dev/packages" target="_blank" rel="noopener noreferrer">
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 1% of Pi coding agent packages: #45 of 5,380 by downloads, as of Aug 2026" width="420">
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">
10
10
  </picture>
11
11
  </a>
12
12
  </div>
13
13
 
14
14
  # pi-goal-x
15
15
 
16
- `pi-goal-x` is a goal-management extension for [pi](https://github.com/earendil-works/pi-coding-agent).
16
+ Adds `/goal` functionality to [pi](https://github.com/earendil-works/pi-coding-agent). The agent helps you define a goal and plan, continues working on it automatically, and submits the result to an optional independent completion auditor.
17
17
 
18
- It gives the agent a persistent objective, a structured plan, visible progress, and an independent completion review. Goals remain available across sessions, so the agent can continue working with the same objective and progress record.
19
-
20
- ## Features
21
-
22
- ### Regular goals
23
-
24
- Regular goals describe an outcome for the agent to achieve. The agent can investigate the work, choose an appropriate sequence, create tasks, and adapt its plan as it progresses.
25
-
26
- Regular goals work well for research, implementation, debugging, documentation, and other work where the desired result is clear and the execution path can be determined during the task.
27
-
28
- ### Sisyphus goals
29
-
30
- Sisyphus goals describe work that should be completed in a specific order. The agent follows the listed sequence one step at a time and preserves dependencies between steps.
31
-
32
- Sisyphus goals work well for migrations, staged refactors, release procedures, data-processing workflows, and other tasks where each step prepares the way for the next.
33
-
34
- ### Guided goal creation
35
-
36
- The `/goal` and `/sisyphus` commands start a guided drafting process. The agent can ask focused questions, clarify the objective, and propose a task plan for confirmation.
37
-
38
- The proposal is written to the conversation as a durable summary (objective, plan, verification, automatic continuation, auditor state); confirming it creates and focuses the goal and starts working automatically.
39
-
40
- ### Direct goal creation
41
-
42
- The `/goal-direct` and `/sisyphus-direct` commands create a goal immediately from a complete objective.
43
-
44
- ### Persistent progress
45
-
46
- Open goals are stored in `.pi/goals/`. Their objectives, tasks, status, and progress remain available across sessions and context changes.
47
-
48
- ### Multiple open goals
49
-
50
- A project can contain several open goals. Each session focuses on one goal at a time, and you can switch between them with `/goal-focus`.
51
-
52
- ### Tasks and subtasks
53
-
54
- Goals can include structured tasks and subtasks. The agent updates their status and records completion evidence as work progresses.
55
-
56
- ### Verification contracts
57
-
58
- Goals and tasks can include plain-text completion requirements, such as:
59
-
60
- ```text
61
- Run npm test with zero failures.
62
- ```
63
-
64
- The completion auditor checks these requirements against evidence from the workspace.
65
-
66
- ### Independent completion review
67
-
68
- When the agent reports a goal as complete, a separate pi agent reviews the objective, tasks, verification requirements, and workspace.
69
-
70
- Approved goals are archived as complete. Goals requiring additional work remain open with review feedback.
71
-
72
- ### Visible status
73
-
74
- An above-editor widget shows the focused goal: its status, focus state, other open goals, time and token usage, task progress, the current task, and the goal file path.
75
-
76
- Press `Ctrl+Shift+T` to expand the widget into the full unified dashboard — the complete task tree with the current task highlighted, the current task's verification contract and evidence, the goal-level verification contract, and a recent-activity feed derived from the durable goal ledger. Press `Esc` or `Ctrl+Shift+T` again to collapse it.
77
-
78
- During an independent completion audit the widget shows a structured audit dashboard (five review stages and a progress bar); after the audit it shows the approval or changes-required result, then returns to the normal view.
79
-
80
- ### Goal controls
81
-
82
- Slash commands let you pause, resume, revise, select, unfocus, and archive goals.
83
-
84
- ### Configurable behaviour
85
-
86
- The settings menu controls task support, verification contracts, subtask depth, goal selection, and the completion auditor.
18
+ The extension saves goal objectives, tasks, and progress across sessions. You can pause, resume, revise, or switch goals as your work changes.
87
19
 
88
20
  ## Install
89
21
 
90
- Install from npm:
91
-
92
22
  ```bash
93
23
  pi install npm:pi-goal-x
94
24
  ```
95
25
 
96
- Install from a local checkout:
97
-
98
- ```bash
99
- pi install .
100
- ```
101
-
102
- Run it once from a local checkout:
26
+ ## Create a goal
103
27
 
104
- ```bash
105
- pi -e .
28
+ ```text
29
+ /goal Add CSV export to the reports page, with documentation and tests.
106
30
  ```
107
31
 
108
- ## Choose a goal style
32
+ The agent discusses the goal with you, asks focused questions where needed, and proposes an objective, task plan, and completion requirements. You review the proposal and choose whether to use the completion auditor. Once you confirm, the agent starts working and continues automatically while the goal is active.
109
33
 
110
- Use a **regular goal** when you have a clear outcome and want the agent to determine how to reach it.
34
+ You can specify completion requirements, such as passing the test suite or producing a report with every required section. The agent tracks tasks and subtasks, records evidence, and works toward those requirements. If it gets blocked and needs your input, you can resolve the issue and resume.
111
35
 
112
- For example:
36
+ If you already have a complete objective, use `/goal-direct <objective>` to create the goal and start immediately without drafting.
113
37
 
114
- ```text
115
- /goal Add account deletion to the application, including the user interface, data cleanup, documentation, and tests.
116
- ```
38
+ ## Goal types
117
39
 
118
- The agent can decide how to investigate the application, divide the work, and order the implementation.
40
+ | Type | Behaviour | Example uses |
41
+ | --- | --- | --- |
42
+ | **Regular** — `/goal` | An outcome to achieve, with the agent choosing and adapting the plan. | Features, debugging, research, and documentation. |
43
+ | **Sisyphus** — `/sisyphus` | An ordered plan that the agent follows one step at a time. | Migrations, staged refactors, and release procedures. |
119
44
 
120
- Use a **Sisyphus goal** when you already know the required sequence and want the agent to follow it step by step.
121
-
122
- For example:
45
+ For an ordered goal, you can provide the steps or define them with the agent:
123
46
 
124
47
  ```text
125
48
  /sisyphus Migrate authentication in this order:
126
49
  1. Add the new token validator.
127
- 2. Update login to use it.
128
- 3. Update session refresh to use it.
129
- 4. Remove the old validator.
130
- 5. Run the authentication test suite.
131
- ```
132
-
133
- The agent completes the migration in the stated order, preserving the dependency between each stage.
134
-
135
- ## Create a guided goal
136
-
137
- Start a guided regular goal:
138
-
139
- ```text
140
- /goal add structured logging to the authentication module
141
- ```
142
-
143
- The agent can ask questions and propose a complete objective and task plan. Confirm the proposal to create the goal and begin work.
144
-
145
- The questionnaire dialog never truncates the question; when it is taller than the terminal it stays within the height and scrolls — `PgUp`/`PgDn` page and `Ctrl+↑/↓` line-scroll without moving the selection, `↑/↓` selection auto-follows into view, and a `▲`/`… +N more` edge indicator shows what is clipped.
146
-
147
- Start a guided Sisyphus goal:
148
-
149
- ```text
150
- /sisyphus prepare and perform the customer-data migration
151
- ```
152
-
153
- The agent can help define the ordered steps and present them for confirmation.
154
-
155
- ## Create a goal directly
156
-
157
- Use `/goal-direct` when the objective already describes a complete outcome:
158
-
159
- ```text
160
- /goal-direct Add a health-check endpoint that verifies database connectivity, returns the service status as JSON, documents the endpoint, and includes passing tests.
161
- ```
162
-
163
- This creates and focuses the regular goal immediately.
164
-
165
- Use `/sisyphus-direct` when the objective already contains the complete ordered process:
166
-
167
- ```text
168
- /sisyphus-direct Upgrade the payment integration in this order:
169
- 1. Add support for the new API version.
170
- 2. Update payment creation.
171
- 3. Update refund handling.
172
- 4. Migrate the test fixtures.
173
- 5. Run the payment test suite.
174
- 6. Remove the old API integration.
175
- ```
176
-
177
- This creates and focuses the ordered goal immediately.
178
-
179
- ## Manage goals
180
-
181
- List open goals:
182
-
183
- ```text
184
- /goal-list
50
+ 2. Update login and session refresh to use it.
51
+ 3. Remove the old validator.
52
+ 4. Run the authentication tests.
185
53
  ```
186
54
 
187
- Show the focused goal:
188
-
189
- ```text
190
- /goal-status
191
- ```
55
+ Use `/sisyphus-direct <objective>` to start an ordered goal without drafting.
192
56
 
193
- Run read-only storage/runtime health checks:
57
+ ## Tasks and subtasks
194
58
 
195
- ```text
196
- /goal-status health
197
- ```
59
+ The agent can divide a goal into tasks and subtasks, each describing part of the work required to complete it. During guided goal creation, you review the proposed plan before work begins.
198
60
 
199
- Re-read goal storage caches (pool, ledger, settings) from disk and report what changed — picks up external edits to `.pi` files without file watchers:
61
+ For example, a CSV export goal could have this task plan:
200
62
 
201
63
  ```text
202
- /goal-refresh
64
+ Add CSV export to reports
65
+ ├─ Review the report data and active filters
66
+ ├─ Implement CSV export
67
+ │ ├─ Generate the CSV from filtered results
68
+ │ └─ Add a download button
69
+ ├─ Test the export
70
+ └─ Document how to use it
203
71
  ```
204
72
 
205
- Select an open goal for the current session:
73
+ As work progresses, the agent marks the current task, records completed work, and explains any skipped tasks. The dashboard shows what is done and what remains, including progress within subtasks. Task progress is saved when you pause and remains available in later sessions.
206
74
 
207
- ```text
208
- /goal-focus
209
- ```
75
+ Tasks can also have their own completion requirements—for example, “The download contains only rows matching the active filters.” The agent records evidence against those requirements, and the completion auditor uses that evidence when reviewing the overall result.
210
76
 
211
- Remove the current session’s focus while keeping the goal open:
77
+ Use `/goal-tweak <change>` to discuss revisions to the goal and its plan. Task tracking, completion requirements, and subtask depth are configurable in `/goal-settings`.
212
78
 
213
- ```text
214
- /goal-unfocus
215
- ```
79
+ ## Completion auditor
216
80
 
217
- Revise the focused objective and task plan:
81
+ When enabled, a separate agent reviews the work before the goal is accepted as complete. It checks the objective, tasks, recorded evidence, completion requirements, and workspace.
218
82
 
219
- ```text
220
- /goal-tweak <change>
221
- ```
83
+ If the auditor approves, the goal is archived as complete. If it identifies unmet requirements, the goal remains open with feedback describing the work still needed. You can choose the auditor model in `/goal-settings` and toggle auditing for the focused goal with `Ctrl+Shift+A`.
222
84
 
223
- Pause or resume the focused goal:
85
+ ## Progress and goal controls
224
86
 
225
- ```text
226
- /goal-pause
227
- /goal-resume
228
- ```
87
+ The dashboard above the editor shows the goal's status, task progress, current task, elapsed time, and token usage. Press `Ctrl+Shift+T` to expand it for the full task tree, completion requirements, evidence, and recent activity. Audit progress and results appear there too.
229
88
 
230
- Archive the focused goal:
231
-
232
- ```text
233
- /goal-clear
234
- ```
235
-
236
- Cancel an unconfirmed guided draft:
237
-
238
- ```text
239
- /goal-cancel
240
- ```
241
-
242
- Open the settings menu:
243
-
244
- ```text
245
- /goal-settings
246
- ```
247
-
248
- Pressing `Esc` during active work pauses the goal.
249
-
250
- ## Tasks and verification
251
-
252
- The agent can divide a goal into tasks and subtasks and update them as work progresses. The current task is tracked explicitly (persisted as the goal's execution focus) and highlighted in the dashboard; starting a task with `update_goal_task(status="start")` sets it, and completing or skipping it clears it.
253
-
254
- The model receives the objective excerpt, current task, next three pending tasks, progress, budget, and applicable rules. Full requirements, contracts, and evidence remain saved. `get_goal()` returns a compact summary; use `get_goal({section:"objective"})`, `section:"tasks"` (optionally `task_id`), or `section:"history"` for lossless pages of at most 4,000 content characters. Repeat the section and task with the returned `cursor` to continue. A changed source rejects stale cursors and asks for a restart. Existing `verbose` and `include_history` arguments remain supported.
255
-
256
- Task progress can be recorded atomically in order:
257
-
258
- ```json
259
- {"updates":[{"task_id":"child","status":"complete","evidence":"Tests passed"},{"task_id":"parent","status":"complete"},{"task_id":"next","status":"start"}]}
260
- ```
261
-
262
- Pass this to `update_goal_task`. A batch accepts 1–100 updates and validates every transition before applying any. Complete children before their parent; contracted tasks still require evidence, skips require reasons, and completed tasks cannot reopen. Single-task calls remain supported; do not mix the two forms. Each update retains its own ledger event. Turn-level writes check the original persisted revision, and completion audits wait for a successful flush.
263
-
264
- Goal tools appear when applicable. Outside drafting, creation and inspection remain available; outcomes appear for active, paused, or budget-limited goals, task planning for active or paused goals, and task progress for active goals with a task tree. Disabling tasks hides both task tools. Drafting keeps its question/proposal tools, and ordinary work tools remain available.
265
-
266
- Verification contracts describe the evidence required for completion. They can apply to the entire goal or to an individual task.
267
-
268
- Examples include:
269
-
270
- ```text
271
- Run npm test with zero failures.
272
- ```
273
-
274
- ```text
275
- Confirm the new command appears in the help menu.
276
- ```
277
-
278
- ```text
279
- Verify that the generated report contains every required section.
280
- ```
281
-
282
- ## Unified dashboard
283
-
284
- `pi-goal-x` renders one dashboard component in two modes; the above-editor widget, `/goal-status`, and the completion flow all derive from the same presentation model, so they can never disagree about the data.
285
-
286
- ### Compact mode
287
-
288
- Always visible above the editor while a goal is focused:
289
-
290
- ```text
291
- ╭─ pi-goal-x ─ Add CSV export to reports ────────────────────────────╮
292
- │ goal: running [12m47s 18.2K] (+2 open) │
293
- ├─ Tasks · ✓3 done · 2 open ──────────── [█████░░░] · Sub 2/3 [██░░] ┤
294
- │ ✓ t1 Review reports page and data source │
295
- │ ✓ t2 Implement filtered CSV export │
296
- │ ▸ t3 Add the download button ☑ ▸ 2/3 │
297
- │ · t4 Add documentation │
298
- │ … +1 more task │
299
- │ Current t3 · Add the download button │
300
- │ Verify Run npm test with zero failures. │
301
- │ File .pi/goals/active_goal_... │
302
- ╰─ Ctrl+Shift+T: expand tasks─────── Ctrl+Shift+A: toggle auditor ● ─╯
303
- ```
304
-
305
- The green/gray `●` at the bottom-right of the border is the focused goal's
306
- independent-auditor status (green = on, gray = off). Wide/medium footers
307
- right-align the `Ctrl+Shift+A: toggle auditor` note beside the dot — it
308
- shows the shortcut that turns the auditor on and off; narrow/minimal keep
309
- just the dot.
310
-
311
- ### Expanded mode
312
-
313
- `Ctrl+Shift+T` expands the same component: full task tree (✓ complete, ▸ current, ~ skipped, · pending), the current-task block with its contract and evidence, goal-level verification, and recent activity.
314
-
315
- ```text
316
- ├─ Progress ──────────────────────────────────────────────────────────┤
317
- │ [██████░░░░] 3/5 tasks · 60% │
318
- ├─ Tasks ─────────────────────────────────────────────────────────────┤
319
- │ ✓ t1 Review reports page and data source │
320
- │ ✓ t2 Implement filtered CSV export │
321
- │ ▸ t3 Add the download button │
322
- │ ✓ t3.1 Add loading state │
323
- │ · t3.3 Add error handling │
324
- │ · t4 Add documentation │
325
- ├─ Current task ──────────────────────────────────────────────────────┤
326
- │ t3 · Add the download button │
327
- │ Subtasks [███████░░░] 2/3 · 67% │
328
- │ Contract: The button downloads a CSV using the active filters. │
329
- ├─ Verification ──────────────────────────────────────────────────────┤
330
- │ Run npm test with zero failures. │
331
- └─ Esc/Ctrl+Shift+T: collapse ────────────────────────────────────────┘
332
- ```
333
-
334
- Every rendered line is width-aware: the dashboard adapts to wide, medium, narrow, and very-narrow terminals and never overflows the available width. It follows a pastel theme palette with a monochrome fallback: a light steel-gray-blue outer frame (`mdLink`) with gray interior rules, pastel-amber task rows with colour-coded markers and ids (✓ complete green, ▸ current teal, ~ skipped gray, · pending amber), accent-tinted progress and brand, and status symbols in their state color.
335
-
336
- The task list is a scrollable **window** that by default is anchored to the most recently completed task — recent completions stay visible instead of the earliest tasks. The expanded dashboard is modal and scrolls with the plain `↑/↓`, `PgUp/PgDn`, and `Home/End` keys; the compact widget keeps the editor's arrows untouched and scrolls with the free `Ctrl+Shift+↑/↓` chords (pi leaves those unbound). A new completion re-anchors the window.
337
-
338
- See [`docs/unified-dashboard.md`](docs/unified-dashboard.md) for the full layout specification, status states, scrolling behavior, and migration behavior.
339
-
340
- ## Completion review
341
-
342
- When the agent reports a goal as complete, `pi-goal-x` starts an independent completion review.
343
-
344
- The auditor examines:
345
-
346
- * The objective
347
- * The task plan and recorded evidence
348
- * Verification contracts
349
- * The current workspace
350
-
351
- An approved goal is archived as complete, and the archive path is reported. Review feedback is added to any goal that requires further work, and the dashboard shows the changes-required result before returning to the normal view.
352
-
353
- Press `Esc` to stop an active audit (completing without audit is recorded explicitly and never presented as independently approved).
354
-
355
- ## Goal storage
356
-
357
- Open goals are stored in:
358
-
359
- ```text
360
- .pi/goals/
361
- ```
362
-
363
- Completed and cleared goals are stored in:
364
-
365
- ```text
366
- .pi/goals/archived/
367
- ```
368
-
369
- Each session can focus on one goal while the project keeps other goals open.
370
-
371
- ## Session checkpoint recovery
372
-
373
- Auto-continue checkpoints are tiny structured markers (≤160 chars) that
374
- trigger the next turn; the full goal state is injected once per turn by the
375
- extension and is never persisted into checkpoints. Sessions created by older
376
- versions may still contain large "legacy" full-prompt checkpoints.
377
-
378
- Check `/goal-recovery` or `/goal-status health` for a read-only report:
379
-
380
- ```text
381
- Session checkpoints:
382
- total: 851
383
- legacy full checkpoints: 851
384
- checkpoint content: 5.4 MB
385
- projected content after recovery: 92 KB
386
- ```
387
-
388
- To repair an affected session file, close Pi first (the tool refuses to run
389
- while a live session could be open), then:
390
-
391
- ```bash
392
- # Report only — writes nothing:
393
- pi-goal-x-recover --session <session.jsonl>
394
-
395
- # Repair (creates a timestamped backup, rewrites only checkpoint entries):
396
- pi-goal-x-recover --session <session.jsonl> --apply --confirm-pi-closed
397
- ```
398
-
399
- Recovery preserves every entry id and parent link, keeps all non-goal lines
400
- byte-identical (including malformed lines), and is idempotent. Rollback is
401
- the timestamped `.backup-*` file created next to the session.
89
+ A project can have several open goals, with one focused goal per session. Switch with `/goal-focus`, pause with `/goal-pause`, or use `/goal-tweak` to discuss changes to the current goal. Pressing `Esc` during active work also pauses the goal; in the expanded dashboard, it collapses the view.
402
90
 
403
91
  ## Commands
404
92
 
405
- ```text
406
- /goal [seed] Start a guided regular goal
407
- /sisyphus [seed] Start a guided ordered goal
408
- /goal-direct <objective> Create a regular goal immediately
409
- /sisyphus-direct <objective> Create an ordered goal immediately
410
- /goal-list List open goals
411
- /goal-status Show the focused goal (unified dashboard)
412
- /goal-status verbose Show the focused goal with full diagnostic detail
413
- /goal-status health Check goal storage/runtime health (read-only)
414
- /goal-refresh Re-read storage caches and report external changes
415
- /goal-recovery Read-only recovery report; `/goal-recovery repair` fixes stale locks + snapshot after confirmation
416
- /goal-focus Select an open goal
417
- /goal-unfocus Remove the session’s focus
418
- /goal-tweak <change> Revise the focused goal
419
- /goal-pause Pause the focused goal
420
- /goal-resume Resume a paused or blocked goal
421
- /goal-settings Open the settings menu
422
- /goal-clear Archive the focused goal
423
- /goal-cancel Cancel the current draft
424
- ```
425
-
426
- ## Configuration
427
-
428
- Settings resolve per setting in this order (highest wins):
429
-
430
- ```text
431
- environment > project layer > global layer > defaults
432
- ```
433
-
434
- Files:
435
-
436
- ```text
437
- global: ~/.pi/pi-goal-x-settings.json (or $PI_CODING_AGENT_DIR, or $PI_GOAL_GLOBAL_SETTINGS_FILE)
438
- project: <cwd>/.pi/pi-goal-x-settings.json (or $PI_GOAL_SETTINGS_FILE)
439
- ```
440
-
441
- Define shared configuration once in the global file and override per project. Explicit `false`/`0` values in a lower layer override inherited values; nested `keybindings` inherit per key. `/goal-settings` shows each row's effective value and source, can switch the edited scope, and can remove a local override to return to inheritance.
442
-
443
- Use `/goal-settings` to configure task lists, verification contracts, subtask depth, automatic goal selection, and completion auditing. Goal objectives have no hard length limit by default; set `objectiveMaxChars` (or `PI_GOAL_OBJECTIVE_MAX_CHARS`, `0` = no limit) to cap objective length across `create_goal`, `propose_goal_draft`, and `/goal-tweak`.
444
-
445
- Configure the task shortcuts in the same file when the terminal captures the defaults:
446
-
447
- ```json
448
- {
449
- "keybindings": {
450
- "dashboard": {
451
- "toggleExpand": "ctrl+shift+t",
452
- "scrollUp": "ctrl+shift+up",
453
- "scrollDown": "ctrl+shift+down"
454
- }
455
- }
456
- }
457
- ```
458
-
459
- The default task bindings are `ctrl+shift+t`, `ctrl+shift+up`, and `ctrl+shift+down`. Use pi key names such as `ctrl+shift+up`.
460
-
461
- ### Blocker Oracle (opt-in)
462
-
463
- When an active goal reports blocked, a stronger read-only model can be consulted once per distinct blocker before the goal is allowed to stop. Off by default; configure under `/goal-settings → Blocker Oracle`:
464
-
465
- ```json
466
- {
467
- "oracle": {
468
- "enabled": true,
469
- "provider": "anthropic",
470
- "model": "<a stronger model>",
471
- "maxFailedAttemptsPerBlocker": 2
472
- }
473
- }
474
- ```
93
+ | Command | What it does |
94
+ | --- | --- |
95
+ | `/goal [idea]` | Discuss, plan, and confirm a regular goal. |
96
+ | `/sisyphus [idea]` | Discuss, plan, and confirm an ordered goal. |
97
+ | `/goal-direct <objective>` | Create and start a regular goal immediately. |
98
+ | `/sisyphus-direct <objective>` | Create and start an ordered goal immediately. |
99
+ | `/goal-list` | List open goals. |
100
+ | `/goal-status` | Show the focused goal and its progress. |
101
+ | `/goal-focus` | Choose an open goal to work on. |
102
+ | `/goal-unfocus` | Leave the current goal open without focusing on it. |
103
+ | `/goal-tweak <change>` | Revise the current goal with the agent. |
104
+ | `/goal-pause` | Pause work on the focused goal. |
105
+ | `/goal-resume` | Resume a paused or blocked goal. |
106
+ | `/goal-clear` | Archive the focused goal after confirmation. |
107
+ | `/goal-cancel` | Cancel an unconfirmed draft. |
108
+ | `/goal-settings` | Configure goal behaviour and the auditor. |
109
+
110
+ For troubleshooting, use `/goal-status verbose` for more detail, `/goal-status health` or `/goal-recovery` to check for problems, and `/goal-refresh` to reload saved goals and settings after external changes. `/goal-recovery repair` offers repairs after confirmation.
111
+
112
+ ## Settings
113
+
114
+ Open `/goal-settings` to change these options. You can save defaults for all projects, override them for the current project, or remove an override to use the inherited value.
115
+
116
+ | Setting | What it controls |
117
+ | --- | --- |
118
+ | Task tracking (`disableTasks`) | Turn task lists on or off. Set to `true` to disable them. |
119
+ | Subtask depth (`subtaskDepth`) | Limit how many levels of subtasks the agent can create. |
120
+ | Completion requirements (`disableContracts`) | Turn explicit goal and task completion requirements on or off. Set to `true` to disable them. |
121
+ | Auditor disabled | Turn off independent completion review. |
122
+ | Auditor provider, model, and thinking level | Choose which model reviews completed work and its reasoning effort. |
475
123
 
476
- Both `provider` and `model` must be set explicitly — the executor model is never used as a silent fallback. The Oracle session can only read files (`read`, `grep`, `find`, `ls`) and returns structured advice. Actionable advice keeps the goal running until you attempt it; advice that needs human input lets the goal block immediately.
477
124
 
478
125
  ## License
479
126
 
@@ -0,0 +1,18 @@
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)">
2
+ <!-- Dark mode -->
3
+ <style>
4
+ .frame { fill: #14181f; stroke: #2a3040; stroke-width: 1; }
5
+ .rank { font: 700 15px ui-monospace, 'SF Mono', Menlo, Consolas, monospace; letter-spacing: 0.04em; fill: #d4a656; }
6
+ .label { font: 400 13px ui-monospace, 'SF Mono', Menlo, Consolas, monospace; fill: #e8e6df; }
7
+ .meta { font: 400 11px ui-monospace, 'SF Mono', Menlo, Consolas, monospace; fill: #7d8494; }
8
+ .divider { stroke: #2a3040; stroke-width: 1; }
9
+ .link { text-decoration: none; }
10
+ </style>
11
+ <a class="link" href="https://pi.dev/packages?type=extension" target="_blank" rel="noopener noreferrer">
12
+ <rect class="frame" x="0.5" y="0.5" width="479" height="53" rx="6"/>
13
+ <text class="rank" x="18" y="34">TOP 0.3%</text>
14
+ <line class="divider" x1="128" y1="13" x2="128" y2="41"/>
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>
17
+ </a>
18
+ </svg>
@@ -0,0 +1,18 @@
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)">
2
+ <!-- Light mode -->
3
+ <style>
4
+ .frame { fill: #f6f8fa; stroke: #d0d7de; stroke-width: 1; }
5
+ .rank { font: 700 15px ui-monospace, 'SF Mono', Menlo, Consolas, monospace; letter-spacing: 0.04em; fill: #9a6700; }
6
+ .label { font: 400 13px ui-monospace, 'SF Mono', Menlo, Consolas, monospace; fill: #1f2328; }
7
+ .meta { font: 400 11px ui-monospace, 'SF Mono', Menlo, Consolas, monospace; fill: #59636e; }
8
+ .divider { stroke: #d0d7de; stroke-width: 1; }
9
+ .link { text-decoration: none; }
10
+ </style>
11
+ <a class="link" href="https://pi.dev/packages?type=extension" target="_blank" rel="noopener noreferrer">
12
+ <rect class="frame" x="0.5" y="0.5" width="479" height="53" rx="6"/>
13
+ <text class="rank" x="18" y="34">TOP 0.3%</text>
14
+ <line class="divider" x1="128" y1="13" x2="128" y2="41"/>
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>
17
+ </a>
18
+ </svg>
@@ -1,5 +1,5 @@
1
1
  import { type AgentToolResult, type ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { GOAL_AUDIT_ENTRY, detailedSummary, goalDetails, type GoalAuditEventDetails } from "./goal-format.ts";
2
+ import { GOAL_AUDIT_ENTRY, detailedSummary, goalDetails } from "./goal-format.ts";
3
3
  import {
4
4
  buildCompletionReport,
5
5
  buildTaskSummary,
@@ -22,7 +22,6 @@ import type { GoalMutationOutcome } from "./goal-service.ts";
22
22
  // actual workspace evidence. An optional completion_summary is forwarded as an
23
23
  // UNTRUSTED executor claim — never evidence and never an approval bypass.
24
24
  export async function runGoalCompletionFlow(core: GoalCore, ctx: ExtensionContext, completionSummary?: string): Promise<AgentToolResult<unknown>> {
25
- const { pi } = core;
26
25
  const flushError = core.goalService.flushForAudit(ctx);
27
26
  if (flushError) return {content: [{type: "text", text: flushError}], details: goalDetails(core.state.goal)};
28
27
  core.reconcileFocusedGoalFromDisk(ctx);
@@ -131,7 +130,7 @@ function commitGoalCompletion(core: GoalCore, ctx: ExtensionContext, opts: {
131
130
  // records remain readable and honored for compatibility; no model tool or
132
131
  // task dialog creates new per-goal bypass state).
133
132
  if (auditTarget.skipAuditor) {
134
- pi.sendMessage<GoalAuditEventDetails>({
133
+ core.auditMessages.enqueue(ctx, {
135
134
  customType: GOAL_AUDIT_ENTRY,
136
135
  content: `Goal completed — per-goal auditor disabled.`,
137
136
  display: true,
@@ -161,7 +160,7 @@ if (auditTarget.skipAuditor) {
161
160
  // the auditor, records audit_skipped, and proceeds through the normal
162
161
  // deferred-completion path. No model-side bypass flag is required.
163
162
  if (settings.disabled === true) {
164
- pi.sendMessage<GoalAuditEventDetails>({
163
+ core.auditMessages.enqueue(ctx, {
165
164
  customType: GOAL_AUDIT_ENTRY,
166
165
  content: `Goal completed — auditor disabled in settings.`,
167
166
  display: true,
@@ -188,7 +187,7 @@ if (settings.disabled === true) {
188
187
  }
189
188
 
190
189
  // Auditor is enabled — run the normal audit flow
191
- await pi.sendMessage<GoalAuditEventDetails>({
190
+ core.auditMessages.enqueue(ctx, {
192
191
  customType: GOAL_AUDIT_ENTRY,
193
192
  content: [
194
193
  "Auditor: I am starting the independent completion audit.",
@@ -197,7 +196,7 @@ if (settings.disabled === true) {
197
196
  ].filter((line): line is string => line !== undefined).join("\n"),
198
197
  display: true,
199
198
  details: { phase: "started", goalId: auditTarget.id, auditor: auditorLabel },
200
- }, { triggerTurn: false });
199
+ });
201
200
  if (!core.isFocusedOperationCurrent(completionFocus)) {
202
201
  return core.focusedOperationCancelledResult("Goal completion", completionFocus);
203
202
  }
@@ -298,7 +297,7 @@ if (settings.disabled === true) {
298
297
 
299
298
  if (userChoice === "complete_without_audit") {
300
299
  // ── Mark complete without audit ────────────────────────────
301
- pi.sendMessage<GoalAuditEventDetails>({
300
+ core.auditMessages.enqueue(ctx, {
302
301
  customType: GOAL_AUDIT_ENTRY,
303
302
  content: `Goal completed — user bypassed audit via Escape.`,
304
303
  display: true,
@@ -379,7 +378,7 @@ if (settings.disabled === true) {
379
378
  "",
380
379
  auditor.output || "Auditor produced no approval marker.",
381
380
  ].filter((line): line is string => line !== undefined).join("\n");
382
- pi.sendMessage<GoalAuditEventDetails>({
381
+ core.auditMessages.enqueue(ctx, {
383
382
  customType: GOAL_AUDIT_ENTRY,
384
383
  content: rejectionText,
385
384
  display: true,
@@ -396,7 +395,7 @@ if (settings.disabled === true) {
396
395
  "",
397
396
  auditor.output || "Auditor approved completion.",
398
397
  ].filter((line): line is string => line !== undefined).join("\n");
399
- pi.sendMessage<GoalAuditEventDetails>({
398
+ core.auditMessages.enqueue(ctx, {
400
399
  customType: GOAL_AUDIT_ENTRY,
401
400
  content: approvalText,
402
401
  display: true,
@@ -8,7 +8,7 @@ import { deriveTasksFromObjective } from "./goal-task-derive.ts";
8
8
  import { goalDetails, renderGoalResult } from "./goal-format.ts";
9
9
  import { buildGoalCreatedReport } from "./goal-policy.ts";
10
10
  import { loadGoalSettings } from "./goal-settings.ts";
11
- import { formatQuestionnaireAnswers, runGoalQuestionnaire, shouldAutoConfirmProposal, showProposalDialog, type GoalQuestionnaireQuestion, type ProposalDecision } from "./goal-questionnaire.ts";
11
+ import { DIALOG_UNAVAILABLE_HINT, proposalDialogFailureMessage, formatQuestionnaireAnswers, runGoalQuestionnaire, shouldAutoConfirmProposal, showProposalDialog, type GoalQuestionnaireQuestion, type ProposalDecision } from "./goal-questionnaire.ts";
12
12
  import { currentTaskIdIsPending, nowIso, type GoalRecord, type GoalTaskList } from "./goal-record.ts";
13
13
  import type { GoalCore } from "./goal-state.ts";
14
14
  import { convertFlatTasks, countTasks, mergeTasksWithExisting, type FlatTaskInput } from "./goal-task-tools.ts";
@@ -250,7 +250,10 @@ export function registerDraftingTools(core: GoalCore): void {
250
250
  core.enterGoalModal();
251
251
  try {
252
252
  const result = await runGoalQuestionnaire(ctx, [{ id: "question", question: params.question, options: params.options ?? [], recommended: params.recommended, allowCustom: params.allow_custom }]);
253
+ if (result.unavailable === true) return { content: [{ type: "text", text: `${DIALOG_UNAVAILABLE_HINT} Ask the question in chat instead.` }], details: goalDetails(core.state.goal) };
253
254
  return { content: [{ type: "text", text: result.cancelled ? "The user cancelled the question. Continue drafting conversationally." : formatQuestionnaireAnswers(result) }], details: goalDetails(core.state.goal) };
255
+ } catch (error) {
256
+ return { content: [{ type: "text", text: `Goal drafting dialog failed: ${error instanceof Error ? error.message : String(error)}. Drafting remains active; ask in chat instead of retrying the dialog.` }], details: goalDetails(core.state.goal) };
254
257
  } finally {
255
258
  core.exitGoalModal();
256
259
  }
@@ -284,7 +287,10 @@ export function registerDraftingTools(core: GoalCore): void {
284
287
  const active = activeDraft(core);
285
288
  if (active) active.questionnaireEcho = formatQuestionnaireAnswers(result); // E5
286
289
  }
290
+ if (result.unavailable === true) return { content: [{ type: "text", text: `${DIALOG_UNAVAILABLE_HINT} Ask the questions in chat instead.` }], details: goalDetails(core.state.goal) };
287
291
  return { content: [{ type: "text", text: result.cancelled ? "The user cancelled the questionnaire. Continue drafting conversationally." : formatQuestionnaireAnswers(result) }], details: goalDetails(core.state.goal) };
292
+ } catch (error) {
293
+ return { content: [{ type: "text", text: `Goal drafting dialog failed: ${error instanceof Error ? error.message : String(error)}. Drafting remains active; ask in chat instead of retrying the dialog.` }], details: goalDetails(core.state.goal) };
288
294
  } finally {
289
295
  core.exitGoalModal();
290
296
  }
@@ -326,16 +332,15 @@ export function registerDraftingTools(core: GoalCore): void {
326
332
  const target = draft.mode === "tweak" ? core.state.goal : undefined;
327
333
  if (draft.mode === "tweak" && (!target || target.id !== draft.targetGoalId)) return { content: [{ type: "text", text: "The goal changed while drafting; review it and start /goal-tweak again." }], details: goalDetails(core.state.goal) };
328
334
  if (draft.mode === "sisyphus" && !sisyphusObjectiveSufficient(objective)) return { content: [{ type: "text", text: "A Sisyphus goal needs ordered steps with explicit per-step done criteria. Refine the objective with numbered steps (1) ..., 2) ...) or Step N: blocks before proposing again." }], details: goalDetails(core.state.goal) };
329
- const auditorLine = draft.auditorEnabled
330
- ? "\n\nAuditor for this goal: enabled (independent approval required before completion)."
331
- : "\n\nAuditor for this goal: disabled (completion skips the audit).";
332
- let confirmation: { decision: ProposalDecision; auditorEnabled: boolean };
335
+ let confirmation: { decision: ProposalDecision; auditorEnabled: boolean; unavailable: boolean };
333
336
  if (shouldAutoConfirmProposal({ hasUI: ctx.hasUI, autoConfirmEnv: process.env.PI_GOAL_AUTO_CONFIRM })) {
334
- confirmation = { decision: "confirm" as const, auditorEnabled: draft.auditorEnabled };
337
+ confirmation = { decision: "confirm" as const, auditorEnabled: draft.auditorEnabled, unavailable: false };
335
338
  } else {
336
339
  core.enterGoalModal();
337
340
  try {
338
- confirmation = await showProposalDialog(ctx, proposalText(draft, objective, params.auto_continue !== false, taskResult.value, target ?? undefined) + auditorLine, draft.mode === "sisyphus" ? "sisyphus" : "goal", draft.auditorEnabled);
341
+ confirmation = await showProposalDialog(ctx, proposalText(draft, objective, params.auto_continue !== false, taskResult.value, target ?? undefined), draft.mode === "sisyphus" ? "sisyphus" : "goal", draft.auditorEnabled);
342
+ } catch (error) {
343
+ return { content: [{ type: "text", text: `${proposalDialogFailureMessage(error)} Do not retry the dialog until the host issue is resolved.` }], details: goalDetails(core.state.goal) };
339
344
  } finally {
340
345
  core.exitGoalModal();
341
346
  }
@@ -350,12 +355,15 @@ export function registerDraftingTools(core: GoalCore): void {
350
355
  taskList: taskResult.value,
351
356
  verificationContract: extracted.verificationContract,
352
357
  autoContinue: params.auto_continue !== false,
353
- auditorEnabled: draft.auditorEnabled,
358
+ auditorEnabled: confirmation.auditorEnabled,
354
359
  });
355
360
  if (confirmation.decision === "cancel") {
356
361
  clearGoalDrafting(core, ctx);
357
362
  return { content: [{ type: "text", text: `${summary}\n\nDraft cancelled; no goal was created. Run /goal or /sisyphus to start a new draft.` }], details: goalDetails(core.state.goal) };
358
363
  }
364
+ if (confirmation.unavailable) {
365
+ return { content: [{ type: "text", text: `${summary}\n\n${DIALOG_UNAVAILABLE_HINT} The goal was NOT created and drafting remains active; do not retry the dialog until the host supports it or the user explicitly restarts with PI_GOAL_AUTO_CONFIRM=1.` }], details: goalDetails(core.state.goal) };
366
+ }
359
367
  if (confirmation.decision !== "confirm") {
360
368
  // Continue refining: preserve the user's auditor choice for the
361
369
  // next proposal (memory and the durable session entry).
@@ -31,6 +31,7 @@ import { consumeOracleFollowupMarker, hasPendingOracleAdviceForFocusedGoal } fro
31
31
  import { hasActiveDraft, rehydrateDraft } from "./goal-drafting.ts";
32
32
  import { syncTerminalInputPause } from "./goal-widget.ts";
33
33
  import type { GoalCore } from "./goal-state.ts";
34
+ import { filterGoalSessionContext } from "./goal-session-safety.ts";
34
35
  import type { GoalMutationOutcome } from "./goal-service.ts";
35
36
 
36
37
  /**
@@ -87,8 +88,8 @@ export function registerGoalEvents(core: GoalCore): void {
87
88
  let networkErrorRecoveryAfterSettleFor: string | null = null;
88
89
 
89
90
  pi.on("context", async (event) => {
90
- const messages = compactGoalCheckpointContext(event.messages, core.state.goal);
91
- // Reference equality means no goal-event messages existed at all.
91
+ const filtered = filterGoalSessionContext(event.messages);
92
+ const messages = compactGoalCheckpointContext(filtered ?? event.messages, core.state.goal) ?? filtered;
92
93
  return messages === null ? undefined : { messages: messages as typeof event.messages };
93
94
  });
94
95
 
@@ -270,6 +271,7 @@ export function registerGoalEvents(core: GoalCore): void {
270
271
  });
271
272
 
272
273
  pi.on("session_start", async (event, ctx) => {
274
+ core.auditMessages.clear();
273
275
  // NAF: the zero-op read caches are session-scoped — a new session always
274
276
  // re-reads settings/pool/ledger fresh from disk (cross-process and
275
277
  // hand-edited changes are picked up at the session boundary).
@@ -327,6 +329,7 @@ export function registerGoalEvents(core: GoalCore): void {
327
329
  });
328
330
 
329
331
  pi.on("session_tree", async (_event, ctx) => {
332
+ core.auditMessages.clear();
330
333
  core.goalService.flushTurn(ctx); // P1-3: persist any buffered transaction before reload
331
334
  await core.loadState(ctx);
332
335
  rehydrateDraft(core, ctx);
@@ -530,6 +533,7 @@ export function registerGoalEvents(core: GoalCore): void {
530
533
  });
531
534
 
532
535
  pi.on("agent_settled", async (_event, ctx) => {
536
+ core.auditMessages.flush(ctx, pi);
533
537
  const goalId = continuationAfterSettleFor;
534
538
  continuationAfterSettleFor = null;
535
539
  const networkErrorGoalId = networkErrorRecoveryAfterSettleFor;
@@ -560,6 +564,7 @@ export function registerGoalEvents(core: GoalCore): void {
560
564
  });
561
565
 
562
566
  pi.on("session_shutdown", async (_event, ctx) => {
567
+ core.auditMessages.clear();
563
568
  continuationAfterSettleFor = null;
564
569
  networkErrorRecoveryAfterSettleFor = null;
565
570
  core.accountProgress(ctx);
@@ -26,6 +26,13 @@ export interface GoalQuestionnaireResult {
26
26
  answers: GoalQuestionnaireAnswer[];
27
27
  cancelled: boolean;
28
28
  auditorEnabled?: boolean;
29
+ /**
30
+ * The host could not present the dialog at all, so `cancelled` reflects an
31
+ * absent UI rather than a decision the user made. Hosts that expose a
32
+ * non-terminal UI keep `ctx.hasUI` true while `ctx.ui.custom` stays the
33
+ * SDK's headless default, which resolves to `undefined`.
34
+ */
35
+ unavailable?: boolean;
29
36
  }
30
37
 
31
38
  export type ProposalDecision = "confirm" | "continue" | "cancel";
@@ -298,6 +305,60 @@ export function isHeadlessQuestionSufficientForDraft(args: { topic: string; ques
298
305
  return !vagueTopic;
299
306
  }
300
307
 
308
+ const CUSTOM_ANSWER_LABEL = "Write your own answer...";
309
+
310
+ /** Sequential native dialogs for hosts that cannot render terminal components. */
311
+ async function runQuestionnaireWithBasicDialogs(
312
+ ctx: ExtensionContext,
313
+ questions: GoalQuestionnaireQuestion[],
314
+ auditorToggleInit?: { defaultEnabled: boolean },
315
+ ): Promise<GoalQuestionnaireResult> {
316
+ const unavailable = (): GoalQuestionnaireResult => ({ questions: [], answers: [], cancelled: true, unavailable: true, ...(auditorToggleInit ? { auditorEnabled: auditorToggleInit.defaultEnabled } : {}) });
317
+ // Require only the primitives this questionnaire can actually use.
318
+ if ((auditorToggleInit || questions.some(q => q.options.length > 0)) && typeof ctx.ui.select !== "function") return unavailable();
319
+ if (questions.some(q => q.options.length === 0 || q.allowCustom !== false) && typeof ctx.ui.input !== "function") return unavailable();
320
+ let auditorEnabled = auditorToggleInit?.defaultEnabled;
321
+ const cancelled = (): GoalQuestionnaireResult => ({ questions, answers: [], cancelled: true, auditorEnabled });
322
+ if (auditorToggleInit) {
323
+ const enabled = "Enabled — require independent approval";
324
+ const disabled = "Disabled — skip the completion audit";
325
+ const choices = auditorEnabled ? [enabled, disabled] : [disabled, enabled];
326
+ const picked = await ctx.ui.select(`Completion auditor (currently ${auditorEnabled ? "enabled" : "disabled"})`, choices);
327
+ if (picked === undefined) return cancelled();
328
+ if (!choices.includes(picked)) throw new Error("The host returned an unknown auditor choice");
329
+ auditorEnabled = picked === enabled;
330
+ }
331
+ const answers: GoalQuestionnaireAnswer[] = [];
332
+ for (const question of questions) {
333
+ let prompt = question.context ? `${question.question}\n\n${question.context}` : question.question;
334
+ if (auditorToggleInit) prompt += `\n\nAuditor for this goal: ${auditorEnabled ? "enabled (independent approval required)" : "disabled (completion skips the audit)"}.`;
335
+ let answer: string | undefined;
336
+ let wasCustom = question.options.length === 0;
337
+ if (!wasCustom) {
338
+ // Numbered labels keep duplicate/reserved option text unambiguous.
339
+ const labels = question.options.map((option, i) => `${i + 1}. ${option}${question.recommended === i ? " (Recommended)" : ""}`);
340
+ const customLabel = `${labels.length + 1}. ${CUSTOM_ANSWER_LABEL}`;
341
+ const options = question.allowCustom === false ? labels : [...labels, customLabel];
342
+ const picked = await ctx.ui.select(prompt, options);
343
+ if (picked === undefined) return cancelled();
344
+ if (!options.includes(picked)) throw new Error("The host returned an unknown questionnaire choice");
345
+ wasCustom = picked === customLabel && question.allowCustom !== false;
346
+ if (!wasCustom) answer = question.options[labels.indexOf(picked)];
347
+ }
348
+ if (wasCustom) {
349
+ // Match the terminal editor: whitespace alone is not a submitted answer.
350
+ do {
351
+ answer = (await ctx.ui.input(prompt, "Write your answer"))?.trim();
352
+ } while (answer === "");
353
+ }
354
+ if (answer === undefined) return cancelled();
355
+ answers.push({ id: question.id, question: question.question, answer, wasCustom });
356
+ }
357
+ return { questions, answers, cancelled: false, auditorEnabled };
358
+ }
359
+
360
+ export const DIALOG_UNAVAILABLE_HINT = "This host cannot display the required goal-drafting dialogs. Use a host with select/input support or the pi TUI, or explicitly restart with PI_GOAL_AUTO_CONFIRM=1 to confirm proposals without a dialog.";
361
+
301
362
  export function proposalDialogFailureMessage(error: unknown): string {
302
363
  const detail = error instanceof Error ? error.message : String(error);
303
364
  return `Goal draft confirmation failed: ${detail}. The goal was NOT created; drafting remains active.`;
@@ -314,10 +375,18 @@ export async function runGoalQuestionnaire(ctx: ExtensionContext, rawQuestions:
314
375
  }
315
376
 
316
377
  const questions = normalizeQuestionnaireQuestions(rawQuestions);
378
+ if (ctx.mode === "rpc" || typeof ctx.ui.custom !== "function") {
379
+ return runQuestionnaireWithBasicDialogs(ctx, questions, auditorToggleInit);
380
+ }
317
381
  const isMulti = questions.length > 1;
318
382
  const totalTabs = questions.length + 1;
319
383
 
320
- return await ctx.ui.custom<GoalQuestionnaireResult>((tui, theme, _kb, done) => {
384
+ const result = await ctx.ui.custom<GoalQuestionnaireResult | undefined>((tui, theme, _kb, done) => {
385
+ // Some web hosts invoke the factory with a render callback in place of a TUI.
386
+ if (!tui || typeof tui.getShowHardwareCursor !== "function" || typeof tui.setShowHardwareCursor !== "function" || typeof tui.requestRender !== "function") {
387
+ done(undefined);
388
+ return { render: () => [], invalidate: () => {} };
389
+ }
321
390
  // Suppress hardware cursor during dialog to reduce TUI auto-scroll
322
391
  // (the TUI render loop runs at ~60fps and writes ANSI cursor positioning
323
392
  // sequences every cycle, which can cause terminal viewport snapping).
@@ -947,6 +1016,8 @@ function advanceAfterAnswer() {
947
1016
  },
948
1017
  };
949
1018
  });
1019
+ if (result !== undefined) return result;
1020
+ return runQuestionnaireWithBasicDialogs(ctx, questions, auditorToggleInit);
950
1021
  }
951
1022
 
952
1023
  /**
@@ -958,7 +1029,7 @@ export async function showProposalDialog(
958
1029
  confirmationText: string,
959
1030
  focus: GoalDraftingFocus,
960
1031
  defaultAuditorEnabled?: boolean,
961
- ): Promise<{ decision: ProposalDecision; auditorEnabled: boolean }> {
1032
+ ): Promise<{ decision: ProposalDecision; auditorEnabled: boolean; unavailable: boolean }> {
962
1033
  const headerTitle = focus === "sisyphus" ? "Confirm Sisyphus Goal Draft" : "Confirm Goal Draft";
963
1034
  const result = await runGoalQuestionnaire(ctx, [{
964
1035
  id: "confirm",
@@ -972,5 +1043,5 @@ export async function showProposalDialog(
972
1043
  cancelled: result.cancelled,
973
1044
  answer: result.answers[0]?.answer,
974
1045
  });
975
- return { decision, auditorEnabled: result.auditorEnabled ?? true };
1046
+ return { decision, auditorEnabled: result.auditorEnabled ?? true, unavailable: result.unavailable === true };
976
1047
  }
@@ -0,0 +1,48 @@
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { GOAL_AUDIT_ENTRY, goalEventMessageId } from "./goal-format.ts";
3
+
4
+ /** pi-subagents marks fresh, forked, resumed and nested worker processes. */
5
+ export function isDelegatedGoalSession(env: Record<string, string | undefined> = process.env): boolean {
6
+ const depth = Number(env.PI_SUBAGENT_DEPTH);
7
+ return env.PI_SUBAGENT_CHILD === "1" || (Number.isFinite(depth) && depth > 0);
8
+ }
9
+
10
+ /** Display-only audits must never become user messages between a tool and its result. */
11
+ export function filterGoalSessionContext<T>(messages: readonly T[], child = false): T[] | null {
12
+ let filtered: T[] | null = null;
13
+ for (let i = 0; i < messages.length; i++) {
14
+ const message = messages[i]!;
15
+ const entry = message as { role?: string; customType?: string; details?: unknown; content?: unknown } | null;
16
+ const remove = entry && typeof entry === "object" && entry.role === "custom"
17
+ && (entry.customType === GOAL_AUDIT_ENTRY || (child && goalEventMessageId(entry) !== null));
18
+ if (remove) filtered ??= messages.slice(0, i);
19
+ else filtered?.push(message);
20
+ }
21
+ return filtered;
22
+ }
23
+
24
+ type TranscriptMessage = Parameters<ExtensionAPI["sendMessage"]>[0];
25
+ type SessionKey = string | ExtensionContext["sessionManager"];
26
+ const sessionKey = (ctx: ExtensionContext): SessionKey => ctx.sessionManager.getSessionId?.() ?? ctx.sessionManager;
27
+
28
+ /** Transcript output waits for the entire agent run, including tool results, to settle. */
29
+ export class GoalAuditMessages {
30
+ private pending: { session: SessionKey; message: TranscriptMessage }[] = [];
31
+
32
+ enqueue(ctx: ExtensionContext, message: TranscriptMessage): void {
33
+ this.pending.push({ session: sessionKey(ctx), message });
34
+ }
35
+
36
+ clear(): void {
37
+ this.pending = [];
38
+ }
39
+
40
+ flush(ctx: ExtensionContext, pi: ExtensionAPI): void {
41
+ const session = sessionKey(ctx);
42
+ this.pending = this.pending.filter(item => item.session === session);
43
+ if (!ctx.isIdle()) return;
44
+ const pending = this.pending;
45
+ this.pending = [];
46
+ for (const item of pending) pi.sendMessage(item.message, { triggerTurn: false });
47
+ }
48
+ }
@@ -32,6 +32,7 @@ import { GoalService } from "./goal-service.ts";
32
32
  import { goalActivityEvents } from "./goal-ledger.ts";
33
33
  import { GoalAccounting } from "./goal-accounting.ts";
34
34
  import { GoalRuntime } from "./goal-runtime.ts";
35
+ import { GoalAuditMessages } from "./goal-session-safety.ts";
35
36
  import {
36
37
  focusedGoalFromPool,
37
38
  openGoalsFromPool,
@@ -78,6 +79,7 @@ export interface GoalCore {
78
79
  goalWidgetComponentRef: { current: GoalWidgetComponent | null };
79
80
  goalService: GoalService;
80
81
  runtime: GoalRuntime;
82
+ auditMessages: GoalAuditMessages;
81
83
  accounting: GoalAccounting;
82
84
 
83
85
  assignFocusedGoalId(goalId: string | null): void;
@@ -1000,6 +1002,7 @@ export function createGoalCore(
1000
1002
  goalWidgetComponentRef,
1001
1003
  goalService,
1002
1004
  runtime,
1005
+ auditMessages: new GoalAuditMessages(),
1003
1006
  accounting,
1004
1007
  assignFocusedGoalId,
1005
1008
  focusedOperationToken,
@@ -12,6 +12,7 @@ import {
12
12
  import type { GoalEventDetails } from "./goal-record.ts";
13
13
  import { createGoalCore } from "./goal-state.ts";
14
14
  import { registerGoalTools } from "./goal-tools.ts";
15
+ import { filterGoalSessionContext, isDelegatedGoalSession } from "./goal-session-safety.ts";
15
16
 
16
17
  /**
17
18
  * pi-goal thin installer. All state lives in the GoalCore (goal-state.ts);
@@ -23,6 +24,14 @@ export default function goalExtension(
23
24
  pi: ExtensionAPI,
24
25
  dependencies: { runCompletionAuditor?: typeof runGoalCompletionAuditor } = {},
25
26
  ): void {
27
+ if (isDelegatedGoalSession()) {
28
+ // Inherit conversation without inheriting ownership of the parent's goal.
29
+ pi.on("context", async event => {
30
+ const messages = filterGoalSessionContext(event.messages, true);
31
+ return messages === null ? undefined : { messages };
32
+ });
33
+ return;
34
+ }
26
35
  pi.registerMessageRenderer<GoalEventDetails>(GOAL_EVENT_ENTRY, renderGoalEvent);
27
36
  pi.registerMessageRenderer<GoalAuditEventDetails>(GOAL_AUDIT_ENTRY, renderGoalAuditEvent);
28
37
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-goal-x",
3
- "version": "0.31.0",
4
- "description": "Goal mode extension for pi: durable long-running objectives, five model tools, structured tasks, independent completion audit, Sisyphus mode, auto-continue, and a status overlay.",
3
+ "version": "0.31.2",
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",
7
7
  "type": "module",
@@ -36,7 +36,9 @@
36
36
  "docs/architecture.md",
37
37
  "docs/pi-autoresearch-survey.md",
38
38
  "docs/pi-goal-x-unified-dashboard-plan.md",
39
- "docs/unified-dashboard.md"
39
+ "docs/unified-dashboard.md",
40
+ "assets/badge-light.svg",
41
+ "assets/badge-dark.svg"
40
42
  ],
41
43
  "pi": {
42
44
  "extensions": [