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 +69 -422
- package/assets/badge-dark.svg +18 -0
- package/assets/badge-light.svg +18 -0
- package/extensions/goal-completion.ts +8 -9
- package/extensions/goal-drafting.ts +16 -8
- package/extensions/goal-events.ts +7 -2
- package/extensions/goal-questionnaire.ts +74 -3
- package/extensions/goal-session-safety.ts +48 -0
- package/extensions/goal-state.ts +3 -0
- package/extensions/goal.ts +9 -0
- package/package.json +5 -3
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
pi install .
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Run it once from a local checkout:
|
|
26
|
+
## Create a goal
|
|
103
27
|
|
|
104
|
-
```
|
|
105
|
-
|
|
28
|
+
```text
|
|
29
|
+
/goal Add CSV export to the reports page, with documentation and tests.
|
|
106
30
|
```
|
|
107
31
|
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
+
If you already have a complete objective, use `/goal-direct <objective>` to create the goal and start immediately without drafting.
|
|
113
37
|
|
|
114
|
-
|
|
115
|
-
/goal Add account deletion to the application, including the user interface, data cleanup, documentation, and tests.
|
|
116
|
-
```
|
|
38
|
+
## Goal types
|
|
117
39
|
|
|
118
|
-
|
|
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
|
-
|
|
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.
|
|
129
|
-
4.
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
```text
|
|
190
|
-
/goal-status
|
|
191
|
-
```
|
|
55
|
+
Use `/sisyphus-direct <objective>` to start an ordered goal without drafting.
|
|
192
56
|
|
|
193
|
-
|
|
57
|
+
## Tasks and subtasks
|
|
194
58
|
|
|
195
|
-
|
|
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
|
-
|
|
61
|
+
For example, a CSV export goal could have this task plan:
|
|
200
62
|
|
|
201
63
|
```text
|
|
202
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
214
|
-
/goal-unfocus
|
|
215
|
-
```
|
|
79
|
+
## Completion auditor
|
|
216
80
|
|
|
217
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
+
## Progress and goal controls
|
|
224
86
|
|
|
225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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:
|
|
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
|
|
91
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/extensions/goal-state.ts
CHANGED
|
@@ -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,
|
package/extensions/goal.ts
CHANGED
|
@@ -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.
|
|
4
|
-
"description": "
|
|
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": [
|