pi-goal-x 0.8.2 → 0.10.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-goal-x
|
|
2
2
|
|
|
3
|
-
> **Fork of [@capyup/pi-goal](https://github.com/capyup/pi-goal)** — this repository extends the upstream with quality-of-life features for the completion auditor, lifecycle reliability improvements, and drafting UX refinements. Upstream changes can be merged from the original repository.
|
|
3
|
+
> **Fork of [@capyup/pi-goal](https://github.com/capyup/pi-goal)** — this repository extends the upstream with quality-of-life features for the completion auditor, lifecycle reliability improvements, mid-flight objective updates, deferred archival, and drafting UX refinements. Upstream changes can be merged from the original repository.
|
|
4
4
|
|
|
5
5
|
`pi-goal-x` is a long-running goal extension for [pi](https://github.com/earendil-works/pi-coding-agent). It gives the agent a durable objective, a visible lifecycle, and schema-gated tools for drafting, executing, pausing, resuming, and completing work.
|
|
6
6
|
|
|
@@ -10,9 +10,24 @@ The extension is designed around one rule: **the user owns intent; the agent exe
|
|
|
10
10
|
|
|
11
11
|
All core features of [@capyup/pi-goal](https://github.com/capyup/pi-goal) are preserved. The following changes are specific to pi-goal-x:
|
|
12
12
|
|
|
13
|
+
### Mid-flight objective updates
|
|
14
|
+
|
|
15
|
+
- **`update_goal({updatedObjective})`** — the agent can now sync the goal objective mid-flight when user requirements change, *without* completing the goal. This ensures the completion auditor evaluates against the latest requirements. The combined path (`updatedObjective` + `status: "complete"`) applies the update first, then runs the normal completion+audit flow.
|
|
16
|
+
- **`apply_goal_tweak`** remains available for `/goal-tweak` drafting revisions; the new parameter is the lightest possible touch on the existing `update_goal` tool.
|
|
17
|
+
|
|
18
|
+
### Deferred archival
|
|
19
|
+
|
|
20
|
+
- **No more premature archiving**: previously, `update_goal` archived the goal file inline within the tool handler before the agent could see the audit result (or skip notification). Archival is now deferred until `turn_end` — after the agent has received the audit/skip result in the conversation. The goal remains visible in the active pool through the entire completion flow.
|
|
21
|
+
- **Cleaner lifecycle**: completed goals are archived by the `turn_end` lifecycle hook, not by the tool handler. The `accountProgress` guard skips disk reconciliation for completed goals.
|
|
22
|
+
|
|
23
|
+
### E2e test infrastructure
|
|
24
|
+
|
|
25
|
+
- **Deterministic fork tests using `--mode json`**: the e2e suite spawns a real `pi --fork --mode json` session, parses structured `tool_execution_start`/`tool_execution_end` JSON events for field-level assertions — no free-text AI output parsing. Uses `--append-system-prompt` + `--tools` to force deterministic tool calls.
|
|
26
|
+
- **Full coverage**: 131 tests total — function-level integration tests (12), mock-pi handler tests (4), file-validity checks (6), and real `pi --fork --mode json` tests (3 scenarios: quick-sync, combined sync+complete, deferred archival).
|
|
27
|
+
|
|
13
28
|
### Completion auditor
|
|
14
29
|
|
|
15
|
-
- **Live progress widget** — when the auditor runs, the TUI shows a spinner, the current tool being executed, and recent output lines. No more wondering if anything is happening.
|
|
30
|
+
- **Live progress widget** — when the auditor runs, the TUI shows a spinner, a progress bar (`[████░░░░] 40%`), step labels (`Inspecting files...`, `Verifying success criteria...`), the current tool being executed, and recent output lines. No more wondering if anything is happening.
|
|
16
31
|
- **Escape to skip** — press Escape during an audit to abort it and complete the goal immediately. The skip is recorded in the ledger as `audit_skipped` with reason `user_aborted` and auditor model metadata.
|
|
17
32
|
- **Disable the auditor entirely** — set `disabled: true` in `.pi/goal-auditor.json` (or toggle it via `/goal-settings` → `disabled`). The agent can still bypass with user confirmation by passing `confirmBypassAuditor: true` to `update_goal`.
|
|
18
33
|
- **Skipped audits are recorded** — every skip (whether disabled or Escape-aborted) is logged to the ledger with the reason, provider, model, and thinking level for full traceability.
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import type { Static } from "@earendil-works/pi-ai";
|
|
4
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
3
5
|
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
4
6
|
import type { Model } from "@earendil-works/pi-ai";
|
|
5
7
|
import {
|
|
6
8
|
createAgentSession,
|
|
7
9
|
createExtensionRuntime,
|
|
10
|
+
defineTool,
|
|
8
11
|
SessionManager,
|
|
9
12
|
SettingsManager,
|
|
10
13
|
type ExtensionContext,
|
|
@@ -29,9 +32,13 @@ export interface AuditorProgress {
|
|
|
29
32
|
/** Recent text output lines from the auditor's assistant messages */
|
|
30
33
|
recentOutput: string[];
|
|
31
34
|
/** Phase of the audit */
|
|
32
|
-
phase: "running" | "tool_executing" | "producing_report" | "done";
|
|
35
|
+
phase: "running" | "tool_executing" | "producing_report" | "thinking" | "done";
|
|
33
36
|
/** Elapsed ms since audit started */
|
|
34
37
|
elapsedMs: number;
|
|
38
|
+
/** Current step label shown to the user (e.g. "Inspecting files...") */
|
|
39
|
+
label?: string;
|
|
40
|
+
/** Completion percentage from 0 to 100 */
|
|
41
|
+
percentage?: number;
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
export type AuditorProgressCallback = (progress: AuditorProgress) => void;
|
|
@@ -156,9 +163,28 @@ export function buildGoalAuditorPrompt(args: {
|
|
|
156
163
|
"2. Inspect artifacts or command output that can prove or disprove those criteria.",
|
|
157
164
|
"3. Explain missing or weak evidence, especially scaffold-vs-final quality gaps.",
|
|
158
165
|
"4. End with exactly <approved/> only if the objective is truly complete; otherwise end with exactly <disapproved/>.",
|
|
166
|
+
"",
|
|
167
|
+
"Progress reporting:",
|
|
168
|
+
"You have the report_auditor_progress tool available to report your progress to the user.",
|
|
169
|
+
"Please use it at natural phase boundaries:",
|
|
170
|
+
" - When starting: report_auditor_progress(label='Starting audit...', percentage=0)",
|
|
171
|
+
" - When beginning file inspection: report_auditor_progress(label='Inspecting files...', percentage=25)",
|
|
172
|
+
" - When verifying success criteria: report_auditor_progress(label='Verifying success criteria...', percentage=50)",
|
|
173
|
+
" - When evaluating evidence: report_auditor_progress(label='Evaluating evidence...', percentage=75)",
|
|
174
|
+
" - When producing final report: report_auditor_progress(label='Producing report...', percentage=90)",
|
|
175
|
+
"This is purely for user visibility and does not affect the audit outcome.",
|
|
159
176
|
].join("\n");
|
|
160
177
|
}
|
|
161
178
|
|
|
179
|
+
/** Tool name for auditor progress reporting */
|
|
180
|
+
export const REPORT_AUDITOR_PROGRESS_TOOL_NAME = "report_auditor_progress";
|
|
181
|
+
|
|
182
|
+
/** Parameters for the report_auditor_progress tool */
|
|
183
|
+
export const reportAuditorProgressParams = Type.Object({
|
|
184
|
+
label: Type.String({ description: "Current step label describing what the auditor is doing (e.g. 'Inspecting files...', 'Verifying success criteria...', 'Producing report...')" }),
|
|
185
|
+
percentage: Type.Number({ description: "Completion percentage from 0 to 100", minimum: 0, maximum: 100 }),
|
|
186
|
+
});
|
|
187
|
+
|
|
162
188
|
function makeAuditorResourceLoader(): ResourceLoader {
|
|
163
189
|
return {
|
|
164
190
|
getExtensions: () => ({ extensions: [], errors: [], runtime: createExtensionRuntime() }),
|
|
@@ -170,9 +196,14 @@ function makeAuditorResourceLoader(): ResourceLoader {
|
|
|
170
196
|
"You are a read-only completion auditor running in an isolated pi agent session.",
|
|
171
197
|
"Inspect the repository and decide whether the claimed goal completion is genuinely satisfied.",
|
|
172
198
|
"Never modify files. Never approve unless the actual user objective is complete.",
|
|
199
|
+
"",
|
|
200
|
+
"You have the report_auditor_progress tool available. Use it to report your audit progress",
|
|
201
|
+
"to the user at natural phase boundaries (starting, inspecting files, verifying criteria,",
|
|
202
|
+
"producing report). This helps the user understand what the auditor is doing and how far",
|
|
203
|
+
"along it is.",
|
|
173
204
|
].join("\n"),
|
|
174
205
|
getAppendSystemPrompt: () => [],
|
|
175
|
-
|
|
206
|
+
extendResources: () => {},
|
|
176
207
|
reload: async () => {},
|
|
177
208
|
};
|
|
178
209
|
}
|
|
@@ -228,16 +259,6 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
228
259
|
}
|
|
229
260
|
try {
|
|
230
261
|
const createSession = args.createSession ?? createAgentSession;
|
|
231
|
-
const { session } = await createSession({
|
|
232
|
-
cwd: args.ctx.cwd,
|
|
233
|
-
model,
|
|
234
|
-
thinkingLevel,
|
|
235
|
-
modelRegistry: args.ctx.modelRegistry,
|
|
236
|
-
resourceLoader: makeAuditorResourceLoader(),
|
|
237
|
-
sessionManager: SessionManager.inMemory(args.ctx.cwd),
|
|
238
|
-
settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }),
|
|
239
|
-
tools: ["read", "grep", "find", "ls", "bash"],
|
|
240
|
-
});
|
|
241
262
|
const startedAt = Date.now();
|
|
242
263
|
const progress: AuditorProgress = {
|
|
243
264
|
recentOutput: [],
|
|
@@ -248,6 +269,49 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
248
269
|
progress.elapsedMs = Date.now() - startedAt;
|
|
249
270
|
args.onProgress?.({ ...progress });
|
|
250
271
|
}
|
|
272
|
+
|
|
273
|
+
// Build the report_auditor_progress tool, capturing the progress state
|
|
274
|
+
const reportProgressTool = defineTool({
|
|
275
|
+
name: REPORT_AUDITOR_PROGRESS_TOOL_NAME,
|
|
276
|
+
label: "Report Auditor Progress",
|
|
277
|
+
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.",
|
|
278
|
+
promptSnippet: "Report current audit progress (step label and completion percentage) to the user.",
|
|
279
|
+
promptGuidelines: [
|
|
280
|
+
"Use report_auditor_progress at natural phase boundaries during the audit:",
|
|
281
|
+
" - When starting the audit: label='Starting audit...' percentage=0",
|
|
282
|
+
" - When beginning file inspection: label='Inspecting files...' percentage=25",
|
|
283
|
+
" - When verifying success criteria: label='Verifying success criteria...' percentage=50",
|
|
284
|
+
" - When evaluating evidence: label='Evaluating evidence...' percentage=75",
|
|
285
|
+
" - When producing final report: label='Producing report...' percentage=90",
|
|
286
|
+
"This is purely for user visibility — it does not affect the audit outcome.",
|
|
287
|
+
"Do not call this tool more than once every few seconds to avoid flooding.",
|
|
288
|
+
],
|
|
289
|
+
parameters: reportAuditorProgressParams,
|
|
290
|
+
executionMode: "sequential",
|
|
291
|
+
async execute(_toolCallId, params) {
|
|
292
|
+
const { label, percentage } = params as Static<typeof reportAuditorProgressParams>;
|
|
293
|
+
progress.label = label;
|
|
294
|
+
progress.percentage = percentage;
|
|
295
|
+
progress.phase = "running";
|
|
296
|
+
emitProgress();
|
|
297
|
+
return {
|
|
298
|
+
content: [{ type: "text", text: `Progress reported: ${label} (${percentage}%)` }],
|
|
299
|
+
details: {},
|
|
300
|
+
};
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const { session } = await createSession({
|
|
305
|
+
cwd: args.ctx.cwd,
|
|
306
|
+
model,
|
|
307
|
+
thinkingLevel,
|
|
308
|
+
modelRegistry: args.ctx.modelRegistry,
|
|
309
|
+
resourceLoader: makeAuditorResourceLoader(),
|
|
310
|
+
sessionManager: SessionManager.inMemory(args.ctx.cwd),
|
|
311
|
+
settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }),
|
|
312
|
+
tools: ["read", "grep", "find", "ls", "bash", REPORT_AUDITOR_PROGRESS_TOOL_NAME],
|
|
313
|
+
customTools: [reportProgressTool],
|
|
314
|
+
});
|
|
251
315
|
const unsubscribe = session.subscribe((event) => {
|
|
252
316
|
if (event.type === "tool_execution_start") {
|
|
253
317
|
progress.currentTool = event.toolName;
|
|
@@ -268,6 +332,20 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
268
332
|
return;
|
|
269
333
|
}
|
|
270
334
|
if (event.type === "message_update") {
|
|
335
|
+
// Check for thinking events from the assistant stream
|
|
336
|
+
const streamEvent = (event as any).assistantMessageEvent;
|
|
337
|
+
if (streamEvent?.type === "thinking_start") {
|
|
338
|
+
progress.phase = "thinking";
|
|
339
|
+
if (!progress.label) progress.label = "Analyzing goal...";
|
|
340
|
+
emitProgress();
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (streamEvent?.type === "thinking_end") {
|
|
344
|
+
progress.phase = "running";
|
|
345
|
+
emitProgress();
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
// For text content, show producing_report phase
|
|
271
349
|
progress.phase = "producing_report";
|
|
272
350
|
const message = event.message as any;
|
|
273
351
|
if (message?.role === "assistant") {
|
|
@@ -300,6 +378,8 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
300
378
|
args.signal?.addEventListener("abort", abortSession, { once: true });
|
|
301
379
|
|
|
302
380
|
// Emit initial progress
|
|
381
|
+
progress.label = "Starting audit...";
|
|
382
|
+
progress.percentage = 0;
|
|
303
383
|
emitProgress();
|
|
304
384
|
try {
|
|
305
385
|
if (args.signal?.aborted) return { approved: false, disapproved: true, output: "", model: modelLabel(model), thinkingLevel, error: "Auditor aborted." };
|
|
@@ -307,6 +387,8 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
307
387
|
} finally {
|
|
308
388
|
args.signal?.removeEventListener("abort", abortSession);
|
|
309
389
|
progress.phase = "done";
|
|
390
|
+
progress.label = "Audit complete.";
|
|
391
|
+
progress.percentage = 100;
|
|
310
392
|
emitProgress();
|
|
311
393
|
unsubscribe();
|
|
312
394
|
}
|
|
@@ -43,6 +43,14 @@ export function validateGoalCompletion(args: {
|
|
|
43
43
|
return { ok: true };
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export function validateGoalUpdate(args: {
|
|
47
|
+
goal: GoalPolicyRecordLike | null;
|
|
48
|
+
}): PolicyValidation {
|
|
49
|
+
if (!args.goal) return { ok: false, message: "No goal is set; cannot update objective." };
|
|
50
|
+
if (args.goal.status === "complete") return { ok: false, message: "Goal is already complete; cannot update objective." };
|
|
51
|
+
return { ok: true };
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
export function validateGoalAbort(args: {
|
|
47
55
|
goal: GoalPolicyRecordLike | null;
|
|
48
56
|
runningGoalId?: string | null;
|
package/extensions/goal.ts
CHANGED
|
@@ -106,6 +106,7 @@ import {
|
|
|
106
106
|
shouldInjectPostCompactReminder,
|
|
107
107
|
validateGoalAbort,
|
|
108
108
|
validateGoalCompletion,
|
|
109
|
+
validateGoalUpdate,
|
|
109
110
|
validatePauseGoal,
|
|
110
111
|
validateResumeGoal,
|
|
111
112
|
} from "./goal-policy.ts";
|
|
@@ -1704,16 +1705,70 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
1704
1705
|
"Do not call update_goal merely because work is stopping, substantial progress was made, or tests passed without covering every requirement.",
|
|
1705
1706
|
"Do not use update_goal=complete as an escape hatch when you are blocked. If you are blocked, call pause_goal({reason, suggestedAction?}) instead so the user can intervene.",
|
|
1706
1707
|
"For sisyphus goals, do not mark complete until every numbered step has been executed and individually verified against its done criterion.",
|
|
1708
|
+
"If the user gives requirements, feedback, or corrections that differ from the goal objective, the goal is stale. Use update_goal with updatedObjective to sync the objective before continuing work or before marking the goal complete. This ensures the auditor evaluates against the latest requirements.",
|
|
1707
1709
|
],
|
|
1708
1710
|
parameters: Type.Object({
|
|
1709
|
-
status: StringEnum([COMPLETE_STATUS] as const, { description: "Set to complete only when the objective is achieved." }),
|
|
1711
|
+
status: Type.Optional(StringEnum([COMPLETE_STATUS] as const, { description: "Set to complete only when the objective is achieved." })),
|
|
1710
1712
|
completionSummary: Type.Optional(Type.String({ description: "Concise completion claim and evidence summary passed to the independent auditor agent." })),
|
|
1711
1713
|
confirmBypassAuditor: Type.Optional(Type.Boolean({ description: "Set to true to confirm bypassing the independent auditor when it is disabled in settings." })),
|
|
1714
|
+
updatedObjective: Type.Optional(Type.String({ description: "Revised goal objective. Use when the user's requirements have changed mid-flight. The goal remains active so the agent can continue working toward the new objective. Can be combined with status=complete to update the objective before the completion audit." })),
|
|
1712
1715
|
}),
|
|
1713
1716
|
executionMode: "sequential",
|
|
1714
1717
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
1715
1718
|
reconcileFocusedGoalFromDisk(ctx);
|
|
1716
|
-
|
|
1719
|
+
|
|
1720
|
+
// -- Phase 1: Objective update (quick sync) --
|
|
1721
|
+
// Apply updatedObjective before any completion logic so the completion
|
|
1722
|
+
// flow (if status=complete is also set) reads the latest objective.
|
|
1723
|
+
if (params.updatedObjective !== undefined) {
|
|
1724
|
+
const newObjective = params.updatedObjective.trim();
|
|
1725
|
+
if (!newObjective) throw new Error("update_goal requires a non-empty updatedObjective.");
|
|
1726
|
+
const updateGate = validateGoalUpdate({ goal: state.goal });
|
|
1727
|
+
if (!updateGate.ok) {
|
|
1728
|
+
return {
|
|
1729
|
+
content: [{ type: "text", text: updateGate.message }],
|
|
1730
|
+
details: goalDetails(state.goal),
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
if (!state.goal) throw new Error("Goal disappeared during objective update.");
|
|
1734
|
+
const next: GoalRecord = {
|
|
1735
|
+
...state.goal,
|
|
1736
|
+
objective: newObjective,
|
|
1737
|
+
updatedAt: nowIso(),
|
|
1738
|
+
};
|
|
1739
|
+
state.goal = writeActiveGoalFile(ctx, next);
|
|
1740
|
+
pi.appendEntry(STATE_ENTRY, goalDetails(state.goal));
|
|
1741
|
+
try {
|
|
1742
|
+
appendGoalEvent(ctx, {
|
|
1743
|
+
type: "goal_tweaked",
|
|
1744
|
+
goalId: state.goal.id,
|
|
1745
|
+
changeSummary: "Objective updated via update_goal",
|
|
1746
|
+
at: state.goal.updatedAt,
|
|
1747
|
+
});
|
|
1748
|
+
} catch {
|
|
1749
|
+
// Ledger append failure should not block update
|
|
1750
|
+
}
|
|
1751
|
+
updateUI(ctx);
|
|
1752
|
+
|
|
1753
|
+
// Quick sync only (no status=complete) — return without terminating
|
|
1754
|
+
if (params.status !== COMPLETE_STATUS) {
|
|
1755
|
+
return {
|
|
1756
|
+
content: [{ type: "text", text: `Goal objective updated.` }],
|
|
1757
|
+
details: goalDetails(state.goal),
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
// Fall through: status=complete also set, proceed with completion below
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// -- Phase 2: Status validation --
|
|
1764
|
+
if (params.status !== COMPLETE_STATUS) {
|
|
1765
|
+
if (params.updatedObjective === undefined) {
|
|
1766
|
+
throw new Error("update_goal requires either status=complete or updatedObjective.");
|
|
1767
|
+
}
|
|
1768
|
+
throw new Error("update_goal requires status=complete when marking a goal complete.");
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
// -- Phase 3: Completion --
|
|
1717
1772
|
const completionGate = validateGoalCompletion({ goal: state.goal, runningGoalId });
|
|
1718
1773
|
if (!completionGate.ok) {
|
|
1719
1774
|
return {
|
|
@@ -2009,7 +2064,8 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
2009
2064
|
};
|
|
2010
2065
|
},
|
|
2011
2066
|
renderCall(args, theme) {
|
|
2012
|
-
|
|
2067
|
+
const label = args?.status ?? args?.updatedObjective ? "sync" : "";
|
|
2068
|
+
return new Text(theme.fg("toolTitle", "update_goal ") + theme.fg("success", label), 0, 0);
|
|
2013
2069
|
},
|
|
2014
2070
|
renderResult(result, _options, theme) {
|
|
2015
2071
|
return renderGoalResult(result, theme);
|
|
@@ -44,7 +44,9 @@ If you hit a real blocker that you cannot resolve with one more reasonable next
|
|
|
44
44
|
|
|
45
45
|
If the user explicitly asks to abandon/cancel this goal, or the objective is obsolete, impossible, or unsafe to continue and should not be marked complete, call abort_goal({reason}) with a non-empty reason and stop.
|
|
46
46
|
|
|
47
|
-
Do NOT silently invent workarounds, fake completion, or quietly redefine the objective. Do NOT call update_goal=complete to escape a blocker
|
|
47
|
+
Do NOT silently invent workarounds, fake completion, or quietly redefine the objective. Do NOT call update_goal=complete to escape a blocker.
|
|
48
|
+
|
|
49
|
+
Goal evolution: if the user gives requirements, feedback, or corrections that differ from the goal objective, the goal is stale. Propose the updated objective concisely and wait for the user to confirm before continuing. Use update_goal with updatedObjective for narrow focus-area changes, or suggest /goal-tweak for broader revisions (boundaries, constraints, multiple sections). Do NOT mark the goal complete with a stale objective.${sisyphusDisciplineBlock(goal) ? `\n${sisyphusDisciplineBlock(goal)}` : ""}`;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
export function continuationPrompt(goal: GoalRecord): string {
|
|
@@ -77,6 +79,8 @@ export function continuationPrompt(goal: GoalRecord): string {
|
|
|
77
79
|
"Do not call update_goal unless the goal is complete enough to survive independent semantic auditing. Do not mark a goal complete merely because work is stopping.",
|
|
78
80
|
"Do not ask the user for confirmation unless there is a real blocker.",
|
|
79
81
|
"",
|
|
82
|
+
"Goal evolution: if the user gives requirements, feedback, or corrections that differ from the goal objective, the goal is stale. Propose the updated objective concisely and wait for the user to confirm before continuing. Use update_goal with updatedObjective for narrow focus-area changes, or suggest /goal-tweak for broader revisions (boundaries, constraints, multiple sections). Do NOT mark the goal complete with a stale objective.",
|
|
83
|
+
"",
|
|
80
84
|
"If you hit a real blocker (missing credentials, contradictory spec, file/permission you cannot access, dangerous operation pending user approval, or an unclear Sisyphus-style ordered plan), call pause_goal({reason, suggestedAction?}) and stop. If the user explicitly asks to abandon/cancel, or the objective is obsolete, impossible, or unsafe to continue, call abort_goal({reason}) and stop. Do not silently invent workarounds. Do not fake completion. pause_goal and abort_goal are structured lifecycle exits; update_goal=complete is not an escape hatch for blockers.",
|
|
81
85
|
...(goal.sisyphus ? ["", sisyphusDisciplineBlock(goal)] : []),
|
|
82
86
|
].join("\n");
|
|
@@ -24,8 +24,12 @@ export interface AuditorWidgetProgress {
|
|
|
24
24
|
currentToolArgs?: string;
|
|
25
25
|
currentToolStartedAt?: number;
|
|
26
26
|
recentOutput: string[];
|
|
27
|
-
phase: "running" | "tool_executing" | "producing_report" | "done";
|
|
27
|
+
phase: "running" | "tool_executing" | "producing_report" | "thinking" | "done";
|
|
28
28
|
elapsedMs: number;
|
|
29
|
+
/** Current step label shown to the user */
|
|
30
|
+
label?: string;
|
|
31
|
+
/** Completion percentage from 0 to 100 */
|
|
32
|
+
percentage?: number;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
export interface GoalWidgetOptions {
|
|
@@ -52,6 +56,13 @@ function branchLine(theme: Theme, width: number, isLast: boolean, content: strin
|
|
|
52
56
|
return fit(`${theme.fg("dim", prefix)} ${content}`, width);
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
function progressBar(pct: number, barWidth: number, theme: Theme): string {
|
|
60
|
+
const safeBar = Math.max(3, barWidth);
|
|
61
|
+
const filled = Math.min(safeBar, Math.max(0, Math.round((pct / 100) * safeBar)));
|
|
62
|
+
const empty = safeBar - filled;
|
|
63
|
+
return `[${theme.fg("accent", "█".repeat(filled))}${theme.fg("dim", "░".repeat(empty))}]`;
|
|
64
|
+
}
|
|
65
|
+
|
|
55
66
|
function displayIcon(goal: GoalWidgetRecord): { icon: string; color: GoalWidgetColor; label: string } {
|
|
56
67
|
if (goal.status === "complete") return { icon: "✓", color: "success", label: "complete" };
|
|
57
68
|
if (goal.status === "paused") {
|
|
@@ -81,8 +92,17 @@ function spinnerFrame(): string {
|
|
|
81
92
|
export function renderAuditorWidgetLines(progress: AuditorWidgetProgress, theme: Theme, width: number): string[] {
|
|
82
93
|
const safeWidth = Math.max(1, width);
|
|
83
94
|
const isActive = progress.phase !== "done";
|
|
84
|
-
const
|
|
85
|
-
const
|
|
95
|
+
const isThinking = progress.phase === "thinking";
|
|
96
|
+
const icon = isActive
|
|
97
|
+
? isThinking
|
|
98
|
+
? theme.fg("muted", "⟡")
|
|
99
|
+
: theme.fg("accent", spinnerFrame())
|
|
100
|
+
: theme.fg("success", "✓");
|
|
101
|
+
const label = isActive
|
|
102
|
+
? isThinking
|
|
103
|
+
? "thinking..."
|
|
104
|
+
: "auditing"
|
|
105
|
+
: "audit complete";
|
|
86
106
|
// formatDuration expects seconds, progress.elapsedMs is in milliseconds
|
|
87
107
|
const duration = formatDuration(Math.floor(progress.elapsedMs / 1000));
|
|
88
108
|
const lines: string[] = [
|
|
@@ -94,7 +114,30 @@ export function renderAuditorWidgetLines(progress: AuditorWidgetProgress, theme:
|
|
|
94
114
|
),
|
|
95
115
|
];
|
|
96
116
|
|
|
97
|
-
|
|
117
|
+
// Show step label when available
|
|
118
|
+
if (progress.label) {
|
|
119
|
+
lines.push(branchLine(
|
|
120
|
+
theme,
|
|
121
|
+
safeWidth,
|
|
122
|
+
false,
|
|
123
|
+
`${theme.fg("text", truncateText(progress.label, Math.max(8, safeWidth - 6)))}`,
|
|
124
|
+
));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Show progress bar when percentage is available
|
|
128
|
+
if (typeof progress.percentage === "number") {
|
|
129
|
+
const barWidth = Math.max(6, Math.min(safeWidth - 10, 30));
|
|
130
|
+
const bar = progressBar(progress.percentage, barWidth, theme);
|
|
131
|
+
const pct = `${theme.fg("muted", `${Math.round(progress.percentage)}%`)}`;
|
|
132
|
+
lines.push(branchLine(
|
|
133
|
+
theme,
|
|
134
|
+
safeWidth,
|
|
135
|
+
isActive && !progress.currentTool && progress.recentOutput.length === 0 && !isThinking,
|
|
136
|
+
`${bar} ${pct}`,
|
|
137
|
+
));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (isActive && !isThinking && progress.currentTool) {
|
|
98
141
|
const argText = progress.currentToolArgs
|
|
99
142
|
? truncateText(progress.currentToolArgs, Math.max(10, safeWidth - 24))
|
|
100
143
|
: "";
|
|
@@ -129,7 +172,7 @@ export function renderAuditorWidgetLines(progress: AuditorWidgetProgress, theme:
|
|
|
129
172
|
}
|
|
130
173
|
|
|
131
174
|
// Show skip hint when audit is actively running
|
|
132
|
-
if (isActive) {
|
|
175
|
+
if (isActive && !isThinking) {
|
|
133
176
|
lines.push(branchLine(
|
|
134
177
|
theme,
|
|
135
178
|
safeWidth,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-x",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Goal mode extension for pi: persistent long-running objectives, /goal-set drafting, Sisyphus prompt style, autoContinue, and an above-editor status overlay. Fork of @capyup/pi-goal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "pi-goal-x contributors",
|