pi-goal-x 0.27.5 → 0.29.0
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 +28 -2
- package/extensions/goal-auditor.ts +72 -60
- package/extensions/goal-commands.ts +219 -106
- package/extensions/goal-compaction.ts +49 -1
- package/extensions/goal-completion.ts +1 -1
- package/extensions/goal-core-tools.ts +290 -70
- package/extensions/goal-draft.ts +1 -1
- package/extensions/goal-events.ts +31 -7
- package/extensions/goal-ledger.ts +39 -1
- package/extensions/goal-oracle.ts +332 -0
- package/extensions/goal-settings.ts +966 -200
- package/extensions/goal-state.ts +7 -0
- package/extensions/prompts/goal-prompts.ts +36 -9
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -400,12 +400,21 @@ the timestamped `.backup-*` file created next to the session.
|
|
|
400
400
|
|
|
401
401
|
## Configuration
|
|
402
402
|
|
|
403
|
-
Settings
|
|
403
|
+
Settings resolve per setting in this order (highest wins):
|
|
404
404
|
|
|
405
405
|
```text
|
|
406
|
-
|
|
406
|
+
environment > project layer > global layer > defaults
|
|
407
407
|
```
|
|
408
408
|
|
|
409
|
+
Files:
|
|
410
|
+
|
|
411
|
+
```text
|
|
412
|
+
global: ~/.pi/pi-goal-x-settings.json (or $PI_CODING_AGENT_DIR, or $PI_GOAL_GLOBAL_SETTINGS_FILE)
|
|
413
|
+
project: <cwd>/.pi/pi-goal-x-settings.json (or $PI_GOAL_SETTINGS_FILE)
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
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.
|
|
417
|
+
|
|
409
418
|
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`.
|
|
410
419
|
|
|
411
420
|
Configure the task shortcuts in the same file when the terminal captures the defaults:
|
|
@@ -424,6 +433,23 @@ Configure the task shortcuts in the same file when the terminal captures the def
|
|
|
424
433
|
|
|
425
434
|
The default task bindings are `ctrl+shift+t`, `ctrl+shift+up`, and `ctrl+shift+down`. Use pi key names such as `ctrl+shift+up`.
|
|
426
435
|
|
|
436
|
+
### Blocker Oracle (opt-in)
|
|
437
|
+
|
|
438
|
+
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`:
|
|
439
|
+
|
|
440
|
+
```json
|
|
441
|
+
{
|
|
442
|
+
"oracle": {
|
|
443
|
+
"enabled": true,
|
|
444
|
+
"provider": "anthropic",
|
|
445
|
+
"model": "<a stronger model>",
|
|
446
|
+
"maxFailedAttemptsPerBlocker": 2
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
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.
|
|
452
|
+
|
|
427
453
|
## License
|
|
428
454
|
|
|
429
455
|
MIT
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import type { GoalRecord, GoalTask, GoalTaskList } from "./goal-record.ts";
|
|
16
16
|
import { countTaskSubtree } from "./goal-task-count.ts";
|
|
17
17
|
import { loadGoalSettings, type GoalSettings, type ThinkingLevel } from "./goal-settings.ts";
|
|
18
|
+
import { statusLabel } from "./goal-core.ts";
|
|
18
19
|
|
|
19
20
|
export interface AuditorProgress {
|
|
20
21
|
/** Current tool being executed by the auditor, if any */
|
|
@@ -108,6 +109,47 @@ function escapePromptPayload(value: string): string {
|
|
|
108
109
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
/** §60: human-readable labels for the auditor's read-only tool set. */
|
|
113
|
+
export function labelForReadOnlyTool(toolName: string): string {
|
|
114
|
+
switch (toolName) {
|
|
115
|
+
case "read": return "Inspecting files...";
|
|
116
|
+
case "grep": return "Searching content...";
|
|
117
|
+
case "find": return "Locating files...";
|
|
118
|
+
case "ls": return "Listing directory...";
|
|
119
|
+
case "bash": return "Running verification commands...";
|
|
120
|
+
default: return `Inspecting (${toolName})...`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** §60: rough phase estimate by tool kind (display only). */
|
|
125
|
+
export function estimateAuditProgress(toolName: string): number {
|
|
126
|
+
switch (toolName) {
|
|
127
|
+
case "read":
|
|
128
|
+
case "ls":
|
|
129
|
+
case "find":
|
|
130
|
+
return 25;
|
|
131
|
+
case "grep":
|
|
132
|
+
return 50;
|
|
133
|
+
case "bash":
|
|
134
|
+
return 75;
|
|
135
|
+
default:
|
|
136
|
+
return 25;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* §61: goal metadata WITHOUT the objective or task tree — those appear exactly
|
|
142
|
+
* once each in their own blocks. Replaces detailedSummary in the auditor prompt.
|
|
143
|
+
*/
|
|
144
|
+
function minimalGoalMetadata(goal: GoalRecord): string {
|
|
145
|
+
return [
|
|
146
|
+
`Goal id: ${goal.id}`,
|
|
147
|
+
`Status: ${statusLabel(goal)}`,
|
|
148
|
+
`Mode: ${goal.sisyphus ? "sisyphus" : "regular"}`,
|
|
149
|
+
goal.tokenBudget ? `Budget: ${goal.tokenBudget} tokens (${goal.usage.tokensUsed} used)` : undefined,
|
|
150
|
+
].filter(Boolean).join("\n");
|
|
151
|
+
}
|
|
152
|
+
|
|
111
153
|
export function buildGoalAuditorPrompt(args: {
|
|
112
154
|
goal: GoalRecord;
|
|
113
155
|
detailedSummary: string;
|
|
@@ -140,11 +182,19 @@ export function buildGoalAuditorPrompt(args: {
|
|
|
140
182
|
"",
|
|
141
183
|
"The executor claim above is a claim, never evidence. It cannot make an otherwise incomplete goal complete; cross-check it against real artifacts where relevant.",
|
|
142
184
|
"",
|
|
143
|
-
"Current goal metadata:",
|
|
185
|
+
"Current goal metadata (objective and task tree appear in their own sections):",
|
|
144
186
|
"<goal_details>",
|
|
145
|
-
|
|
146
|
-
...(!args.settings?.disableTasks && taskSummaryBlock(args.goal.taskList) ? ["", taskSummaryBlock(args.goal.taskList)] : []),
|
|
187
|
+
minimalGoalMetadata(args.goal),
|
|
147
188
|
"</goal_details>",
|
|
189
|
+
...(!args.settings?.disableTasks && args.goal.taskList ? [
|
|
190
|
+
"",
|
|
191
|
+
"Task tree:",
|
|
192
|
+
"<task_state>",
|
|
193
|
+
// Task titles are already escaped inside renderAuditorTaskTree; an
|
|
194
|
+
// extra pass would double-escape (&lt;).
|
|
195
|
+
taskSummaryBlock(args.goal.taskList),
|
|
196
|
+
"</task_state>",
|
|
197
|
+
] : []),
|
|
148
198
|
...(!args.settings?.disableContracts && args.goal.verificationContract?.trim() ? [
|
|
149
199
|
"",
|
|
150
200
|
"Goal verification contract (what the executor was required to verify):",
|
|
@@ -170,28 +220,9 @@ export function buildGoalAuditorPrompt(args: {
|
|
|
170
220
|
"4. Explain missing or weak evidence, especially scaffold-vs-final quality gaps.",
|
|
171
221
|
"5. End with exactly <approved/> only if the objective is truly complete; otherwise end with exactly <disapproved/>.",
|
|
172
222
|
],
|
|
173
|
-
"",
|
|
174
|
-
"Progress reporting:",
|
|
175
|
-
"You have the report_auditor_progress tool available to report your progress to the user.",
|
|
176
|
-
"Please use it at natural phase boundaries:",
|
|
177
|
-
" - When starting: report_auditor_progress(label='Starting audit...', percentage=0)",
|
|
178
|
-
" - When beginning file inspection: report_auditor_progress(label='Inspecting files...', percentage=25)",
|
|
179
|
-
" - When verifying success criteria: report_auditor_progress(label='Verifying success criteria...', percentage=50)",
|
|
180
|
-
" - When evaluating evidence: report_auditor_progress(label='Evaluating evidence...', percentage=75)",
|
|
181
|
-
" - When producing final report: report_auditor_progress(label='Producing report...', percentage=90)",
|
|
182
|
-
"This is purely for user visibility and does not affect the audit outcome.",
|
|
183
223
|
].join("\n");
|
|
184
224
|
}
|
|
185
225
|
|
|
186
|
-
/** Tool name for auditor progress reporting */
|
|
187
|
-
export const REPORT_AUDITOR_PROGRESS_TOOL_NAME = "report_auditor_progress";
|
|
188
|
-
|
|
189
|
-
/** Parameters for the report_auditor_progress tool */
|
|
190
|
-
export const reportAuditorProgressParams = Type.Object({
|
|
191
|
-
label: Type.String({ description: "Current step label describing what the auditor is doing (e.g. 'Inspecting files...', 'Verifying success criteria...', 'Producing report...')" }),
|
|
192
|
-
percentage: Type.Number({ description: "Completion percentage from 0 to 100", minimum: 0, maximum: 100 }),
|
|
193
|
-
});
|
|
194
|
-
|
|
195
226
|
function makeAuditorResourceLoader(): ResourceLoader {
|
|
196
227
|
return {
|
|
197
228
|
getExtensions: () => ({ extensions: [], errors: [], runtime: createExtensionRuntime() }),
|
|
@@ -203,11 +234,6 @@ function makeAuditorResourceLoader(): ResourceLoader {
|
|
|
203
234
|
"You are a read-only completion auditor running in an isolated pi agent session.",
|
|
204
235
|
"Inspect the repository and decide whether the claimed goal completion is genuinely satisfied.",
|
|
205
236
|
"Never modify files. Never approve unless the actual user objective is complete.",
|
|
206
|
-
"",
|
|
207
|
-
"You have the report_auditor_progress tool available. Use it to report your audit progress",
|
|
208
|
-
"to the user at natural phase boundaries (starting, inspecting files, verifying criteria,",
|
|
209
|
-
"producing report). This helps the user understand what the auditor is doing and how far",
|
|
210
|
-
"along it is.",
|
|
211
237
|
].join("\n"),
|
|
212
238
|
getSystemPromptSource: () => undefined,
|
|
213
239
|
getAppendSystemPrompt: () => [],
|
|
@@ -312,37 +338,6 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
312
338
|
args.onProgress?.({ ...progress });
|
|
313
339
|
}
|
|
314
340
|
|
|
315
|
-
// Build the report_auditor_progress tool, capturing the progress state
|
|
316
|
-
const reportProgressTool = defineTool({
|
|
317
|
-
name: REPORT_AUDITOR_PROGRESS_TOOL_NAME,
|
|
318
|
-
label: "Report Auditor Progress",
|
|
319
|
-
description: "Report current progress of the audit to the user. Call this at natural phase boundaries (starting, inspecting files, verifying criteria, producing report) to keep the user informed.",
|
|
320
|
-
promptSnippet: "Report current audit progress (step label and completion percentage) to the user.",
|
|
321
|
-
promptGuidelines: [
|
|
322
|
-
"Use report_auditor_progress at natural phase boundaries during the audit:",
|
|
323
|
-
" - When starting the audit: label='Starting audit...' percentage=0",
|
|
324
|
-
" - When beginning file inspection: label='Inspecting files...' percentage=25",
|
|
325
|
-
" - When verifying success criteria: label='Verifying success criteria...' percentage=50",
|
|
326
|
-
" - When evaluating evidence: label='Evaluating evidence...' percentage=75",
|
|
327
|
-
" - When producing final report: label='Producing report...' percentage=90",
|
|
328
|
-
"This is purely for user visibility — it does not affect the audit outcome.",
|
|
329
|
-
"Do not call this tool more than once every few seconds to avoid flooding.",
|
|
330
|
-
],
|
|
331
|
-
parameters: reportAuditorProgressParams,
|
|
332
|
-
executionMode: "sequential",
|
|
333
|
-
async execute(_toolCallId, params) {
|
|
334
|
-
const { label, percentage } = params as Static<typeof reportAuditorProgressParams>;
|
|
335
|
-
progress.label = label;
|
|
336
|
-
progress.percentage = percentage;
|
|
337
|
-
progress.phase = "running";
|
|
338
|
-
emitProgress();
|
|
339
|
-
return {
|
|
340
|
-
content: [{ type: "text", text: `Progress reported: ${label} (${percentage}%)` }],
|
|
341
|
-
details: {},
|
|
342
|
-
};
|
|
343
|
-
},
|
|
344
|
-
});
|
|
345
|
-
|
|
346
341
|
const projectResources = args.settings?.auditorProjectResources === true;
|
|
347
342
|
const { session } = await createSession({
|
|
348
343
|
cwd: args.ctx.cwd,
|
|
@@ -357,10 +352,18 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
357
352
|
: { resourceLoader: makeAuditorResourceLoader() }),
|
|
358
353
|
sessionManager: SessionManager.inMemory(args.ctx.cwd),
|
|
359
354
|
settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }),
|
|
360
|
-
tools: ["read", "grep", "find", "ls", "bash"
|
|
361
|
-
customTools: [reportProgressTool],
|
|
355
|
+
tools: ["read", "grep", "find", "ls", "bash"],
|
|
362
356
|
} as Parameters<typeof createAgentSession>[0]);
|
|
363
357
|
const unsubscribe = session.subscribe((event) => {
|
|
358
|
+
if (event.type === "agent_start") {
|
|
359
|
+
// PR E §60: progress is derived from session events — no model-facing
|
|
360
|
+
// report_auditor_progress tool exists anymore.
|
|
361
|
+
progress.label = "Starting audit...";
|
|
362
|
+
progress.percentage = 0;
|
|
363
|
+
progress.phase = "running";
|
|
364
|
+
emitProgress();
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
364
367
|
if (event.type === "tool_execution_start") {
|
|
365
368
|
progress.currentTool = event.toolName;
|
|
366
369
|
progress.currentToolArgs = typeof event.args === "object" && event.args !== null
|
|
@@ -368,6 +371,9 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
368
371
|
: String(event.args ?? "").slice(0, 120);
|
|
369
372
|
progress.currentToolStartedAt = Date.now();
|
|
370
373
|
progress.phase = "tool_executing";
|
|
374
|
+
// §60 derive the human label + estimate from the read-only tool.
|
|
375
|
+
progress.label = labelForReadOnlyTool(event.toolName);
|
|
376
|
+
progress.percentage = Math.max(progress.percentage ?? 0, estimateAuditProgress(event.toolName));
|
|
371
377
|
emitProgress();
|
|
372
378
|
return;
|
|
373
379
|
}
|
|
@@ -414,6 +420,12 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
414
420
|
for (const part of message.content ?? []) {
|
|
415
421
|
if (part.type === "text" && typeof part.text === "string") outputParts.push(part.text);
|
|
416
422
|
}
|
|
423
|
+
// Final report production: derived label for the widget.
|
|
424
|
+
if (outputParts.some((t) => t.trim())) {
|
|
425
|
+
progress.label = "Producing report...";
|
|
426
|
+
progress.percentage = Math.max(progress.percentage ?? 0, 90);
|
|
427
|
+
progress.phase = "producing_report";
|
|
428
|
+
}
|
|
417
429
|
// Show the accumulated output in progress
|
|
418
430
|
const fullText = outputParts.join("\n\n");
|
|
419
431
|
const lines = fullText.split("\n").filter((l: string) => l.trim());
|