pi-goal-x 0.7.2 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
# pi-goal-x
|
|
2
2
|
|
|
3
|
-
> **Fork of [@capyup/pi-goal](https://github.com/capyup/pi-goal)
|
|
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.
|
|
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
|
|
|
7
7
|
The extension is designed around one rule: **the user owns intent; the agent executes only after the goal is explicit and confirmed**.
|
|
8
8
|
|
|
9
|
-
## What's
|
|
9
|
+
## What's different from upstream
|
|
10
10
|
|
|
11
|
-
pi-goal-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
13
|
+
### Completion auditor
|
|
14
|
+
|
|
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.
|
|
16
|
+
- **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
|
+
- **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
|
+
- **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.
|
|
19
|
+
- **Robust abort detection** — the auditor detects aborts both from exceptions *and* from `session.prompt()` returning after an abort signal, preventing stuck goals or ghost states.
|
|
20
|
+
- **Cleaner lifecycle** — `AbortSignal` is properly wired to `session.abort()`, animation timers are cleaned up, and the unsubscribe path is always executed. No more having to kill the session.
|
|
21
|
+
- **Completion report includes full auditor output** — the auditor's full report is included in the goal completion conversation message upon approval, not just a verdict.
|
|
22
|
+
- **Session factory injection** — `runGoalCompletionAuditor` accepts an optional `createSession` parameter for testability, enabling mock auditor sessions in tests.
|
|
23
|
+
|
|
24
|
+
### Drafting & UX
|
|
25
|
+
|
|
26
|
+
- **Normalized proposal-refinement language** — consistent terminology ("keep refining through normal proposal cycles") across all drafting prompts and tools.
|
|
27
|
+
- **`PI_GOAL_AUTO_CONFIRM=0` opt-out** — explicitly set the env var to `0` to disable auto-confirm even in headless contexts (useful for benchmarking).
|
|
28
|
+
|
|
29
|
+
### Testing
|
|
30
|
+
|
|
31
|
+
- **Comprehensive abort/skip coverage** — unit tests for `audit_skipped` ledger events, disabled auditor config, Esc-to-skip widget behaviour, post-prompt abort detection, and the `confirmBypassAuditor` parameter.
|
|
18
32
|
|
|
19
33
|
## What it provides
|
|
20
34
|
|
package/extensions/goal-draft.ts
CHANGED
|
@@ -104,7 +104,7 @@ export function goalDraftingPrompt(topic: string, focus: GoalDraftingFocus): str
|
|
|
104
104
|
"- If the topic is already concrete, you may proceed directly to propose_goal_draft.",
|
|
105
105
|
"- The goal contract should make the objective, success criteria, boundaries, constraints, and blocker rule explicit.",
|
|
106
106
|
"- Keep grilling assumptions until the objective, success criteria, boundaries, constraints, and blocker rule are clear enough to confirm.",
|
|
107
|
-
"- propose_goal_draft opens the user's Confirm / Continue Chatting dialog. Confirm creates and focuses the goal; Continue Chatting means keep
|
|
107
|
+
"- propose_goal_draft opens the user's Confirm / Continue Chatting dialog. Confirm creates and focuses the goal; Continue Chatting means keep refining through normal proposal cycles.",
|
|
108
108
|
"- create_goal is not a shortcut. Direct create_goal calls are rejected so the user keeps explicit say in goal creation.",
|
|
109
109
|
];
|
|
110
110
|
|
|
@@ -116,11 +116,14 @@ export function abortGoalCommandMessage(args: { archived: boolean; wasDrafting:
|
|
|
116
116
|
return args.archived ? "Goal aborted and archived." : args.wasDrafting ? "Drafting cancelled." : "No goal is set.";
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
export function buildCompletionReport(args: { detailedSummary: string; completionSummary?: string | null; auditorReport?: string | null }): string {
|
|
119
|
+
export function buildCompletionReport(args: { detailedSummary: string; completionSummary?: string | null; auditorReport?: string | null; auditSkippedReason?: string | null }): string {
|
|
120
|
+
const auditSkipped = args.auditSkippedReason?.trim();
|
|
120
121
|
const auditorReport = args.auditorReport?.trim();
|
|
121
|
-
const lines =
|
|
122
|
-
? ["Goal audit
|
|
123
|
-
:
|
|
122
|
+
const lines = auditSkipped
|
|
123
|
+
? ["Goal audit skipped.", "", "Reason: " + auditSkipped, "", "Goal complete."]
|
|
124
|
+
: auditorReport
|
|
125
|
+
? ["Goal audit approved.", "", "Auditor approval:", auditorReport, "", "Goal complete."]
|
|
126
|
+
: ["Goal complete."];
|
|
124
127
|
const summary = args.completionSummary?.trim();
|
|
125
128
|
if (summary) {
|
|
126
129
|
lines.push("", "Completion summary:", summary);
|
|
@@ -56,6 +56,7 @@ export function formatQuestionnaireAnswers(result: GoalQuestionnaireResult): str
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export function shouldAutoConfirmProposal(args: { hasUI: boolean; autoConfirmEnv?: string }): boolean {
|
|
59
|
+
if (args.autoConfirmEnv === "0") return false; // explicit opt-out (benchmarking)
|
|
59
60
|
return !args.hasUI || args.autoConfirmEnv === "1";
|
|
60
61
|
}
|
|
61
62
|
|
package/extensions/goal.ts
CHANGED
|
@@ -667,7 +667,8 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
667
667
|
clearActiveAccounting();
|
|
668
668
|
return;
|
|
669
669
|
}
|
|
670
|
-
|
|
670
|
+
// Skip disk reconciliation for complete goals — they are pending archival at turn_end.
|
|
671
|
+
if (state.goal?.activePath && state.goal?.status !== "complete" && !reconcileFocusedGoalFromDisk(ctx, { preserveMemoryUsage: true })) return;
|
|
671
672
|
if (!state.goal || state.goal.status !== "active" || accounting.activeGoalId !== state.goal.id) {
|
|
672
673
|
beginAccounting();
|
|
673
674
|
return;
|
|
@@ -1677,7 +1678,7 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
1677
1678
|
return {
|
|
1678
1679
|
content: [{
|
|
1679
1680
|
type: "text",
|
|
1680
|
-
text: "
|
|
1681
|
+
text: "Goal draft refinement requested (Continue Chatting). The goal was not created — drafting remains active. Ask the user what they want changed about the draft (objective, scope, criteria, steps), then revise and call propose_goal_draft again. Do not re-propose the same content — wait for the user's input first.",
|
|
1681
1682
|
}],
|
|
1682
1683
|
details: goalDetails(state.goal),
|
|
1683
1684
|
};
|
|
@@ -1751,13 +1752,16 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
1751
1752
|
details: goalDetails(state.goal),
|
|
1752
1753
|
};
|
|
1753
1754
|
}
|
|
1754
|
-
// Auditor disabled and confirmed — skip audit
|
|
1755
|
-
|
|
1755
|
+
// Auditor disabled and confirmed — skip audit.
|
|
1756
|
+
// Defer archival: set goal complete in-memory + write active file WITHOUT
|
|
1757
|
+
// archiving. Archival happens at turn_end so the agent has a chance to
|
|
1758
|
+
// recognise the skipped audit before the goal is archived.
|
|
1759
|
+
pi.sendMessage<GoalAuditEventDetails>({
|
|
1756
1760
|
customType: GOAL_AUDIT_ENTRY,
|
|
1757
|
-
content: `
|
|
1761
|
+
content: `Goal completed — auditor disabled in settings.`,
|
|
1758
1762
|
display: true,
|
|
1759
1763
|
details: { phase: "skipped", goalId: auditTarget.id, auditor: auditorLabel },
|
|
1760
|
-
}
|
|
1764
|
+
});
|
|
1761
1765
|
try {
|
|
1762
1766
|
appendGoalEvent(ctx, {
|
|
1763
1767
|
type: "audit_skipped",
|
|
@@ -1771,41 +1775,32 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
1771
1775
|
} catch {
|
|
1772
1776
|
// Ledger append failure should not block completion
|
|
1773
1777
|
}
|
|
1774
|
-
//
|
|
1778
|
+
// Set goal complete in memory (defer archival to turn_end)
|
|
1775
1779
|
accountProgress(ctx);
|
|
1776
|
-
state.goal = auditTarget;
|
|
1777
|
-
stopActiveGoal("complete", "agent", ctx);
|
|
1778
|
-
const completedGoal = state.goal;
|
|
1779
|
-
turnStoppedFor = completedGoal?.id ?? null;
|
|
1780
1780
|
auditProgress = null;
|
|
1781
1781
|
goalWidgetComponent?.invalidate();
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
at: nowIso(),
|
|
1795
|
-
});
|
|
1796
|
-
} catch {
|
|
1797
|
-
// Ledger append failure should not crash completion
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1782
|
+
state.goal = {
|
|
1783
|
+
...auditTarget,
|
|
1784
|
+
status: "complete",
|
|
1785
|
+
stopReason: "agent",
|
|
1786
|
+
updatedAt: nowIso(),
|
|
1787
|
+
};
|
|
1788
|
+
state.goal = writeActiveGoalFile(ctx, state.goal);
|
|
1789
|
+
pi.appendEntry(STATE_ENTRY, goalDetails(state.goal));
|
|
1790
|
+
turnStoppedFor = state.goal?.id ?? null;
|
|
1791
|
+
resetGetGoalNudgeState(state.goal?.id);
|
|
1792
|
+
syncGoalTools();
|
|
1793
|
+
updateUI(ctx);
|
|
1800
1794
|
return {
|
|
1801
1795
|
content: [{
|
|
1802
1796
|
type: "text",
|
|
1803
1797
|
text: buildCompletionReport({
|
|
1804
|
-
detailedSummary: detailedSummary(
|
|
1798
|
+
detailedSummary: detailedSummary(state.goal),
|
|
1805
1799
|
completionSummary: params.completionSummary,
|
|
1800
|
+
auditSkippedReason: "auditor disabled in settings",
|
|
1806
1801
|
}),
|
|
1807
1802
|
}],
|
|
1808
|
-
details: goalDetails(
|
|
1803
|
+
details: goalDetails(state.goal),
|
|
1809
1804
|
terminate: true,
|
|
1810
1805
|
};
|
|
1811
1806
|
}
|
|
@@ -1884,47 +1879,39 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
1884
1879
|
// skip notification is exposed exactly once to the agent as part of the
|
|
1885
1880
|
// update_goal tool execution, matching the disabled-flow pattern exactly.
|
|
1886
1881
|
if (auditor.error === "Auditor aborted.") {
|
|
1887
|
-
|
|
1882
|
+
// Esc-skip: same deferred archival pattern as disabled bypass.
|
|
1883
|
+
pi.sendMessage<GoalAuditEventDetails>({
|
|
1888
1884
|
customType: GOAL_AUDIT_ENTRY,
|
|
1889
|
-
content: `
|
|
1885
|
+
content: `Goal completed — auditor bypassed (user pressed Escape during audit).`,
|
|
1890
1886
|
display: true,
|
|
1891
1887
|
details: { phase: "skipped", goalId: auditTarget.id, auditor: auditorLabel },
|
|
1892
|
-
}
|
|
1893
|
-
//
|
|
1888
|
+
});
|
|
1889
|
+
// Set goal complete in memory (defer archival to turn_end)
|
|
1894
1890
|
accountProgress(ctx);
|
|
1895
|
-
state.goal = auditTarget;
|
|
1896
|
-
stopActiveGoal("complete", "agent", ctx);
|
|
1897
|
-
const completedGoal = state.goal;
|
|
1898
|
-
turnStoppedFor = completedGoal?.id ?? null;
|
|
1899
1891
|
auditProgress = null;
|
|
1900
1892
|
goalWidgetComponent?.invalidate();
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
at: nowIso(),
|
|
1914
|
-
});
|
|
1915
|
-
} catch {
|
|
1916
|
-
// Ledger append failure should not crash completion
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1893
|
+
state.goal = {
|
|
1894
|
+
...auditTarget,
|
|
1895
|
+
status: "complete",
|
|
1896
|
+
stopReason: "agent",
|
|
1897
|
+
updatedAt: nowIso(),
|
|
1898
|
+
};
|
|
1899
|
+
state.goal = writeActiveGoalFile(ctx, state.goal);
|
|
1900
|
+
pi.appendEntry(STATE_ENTRY, goalDetails(state.goal));
|
|
1901
|
+
turnStoppedFor = state.goal?.id ?? null;
|
|
1902
|
+
resetGetGoalNudgeState(state.goal?.id);
|
|
1903
|
+
syncGoalTools();
|
|
1904
|
+
updateUI(ctx);
|
|
1919
1905
|
return {
|
|
1920
1906
|
content: [{
|
|
1921
1907
|
type: "text",
|
|
1922
1908
|
text: buildCompletionReport({
|
|
1923
|
-
detailedSummary: detailedSummary(
|
|
1909
|
+
detailedSummary: detailedSummary(state.goal),
|
|
1924
1910
|
completionSummary: params.completionSummary,
|
|
1911
|
+
auditSkippedReason: "auditor bypassed (user pressed Escape during audit)",
|
|
1925
1912
|
}),
|
|
1926
1913
|
}],
|
|
1927
|
-
details: goalDetails(
|
|
1914
|
+
details: goalDetails(state.goal),
|
|
1928
1915
|
terminate: true,
|
|
1929
1916
|
};
|
|
1930
1917
|
}
|
|
@@ -1989,45 +1976,35 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
1989
1976
|
display: true,
|
|
1990
1977
|
details: { phase: "approved", goalId: auditTarget.id, auditor: auditor.model },
|
|
1991
1978
|
});
|
|
1992
|
-
// Account for any remaining elapsed time
|
|
1979
|
+
// Account for any remaining elapsed time.
|
|
1980
|
+
// Defer archival: set goal complete in-memory + write active file WITHOUT
|
|
1981
|
+
// archiving. Archival happens at turn_end so the agent can see the auditor
|
|
1982
|
+
// approval before the goal is archived.
|
|
1993
1983
|
accountProgress(ctx);
|
|
1994
|
-
state.goal = auditTarget;
|
|
1995
|
-
stopActiveGoal("complete", "agent", ctx);
|
|
1996
|
-
const completedGoal = state.goal;
|
|
1997
|
-
// C9 fix: mark turn-stopped so subsequent in-turn tool calls are blocked.
|
|
1998
|
-
turnStoppedFor = completedGoal?.id ?? null;
|
|
1999
|
-
// Clear auditor progress to restore normal widget state
|
|
2000
1984
|
auditProgress = null;
|
|
2001
1985
|
goalWidgetComponent?.invalidate();
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
archivePath: completedGoal.archivedPath,
|
|
2015
|
-
at: nowIso(),
|
|
2016
|
-
});
|
|
2017
|
-
} catch {
|
|
2018
|
-
// Ledger append failure should not crash completion
|
|
2019
|
-
}
|
|
2020
|
-
}
|
|
1986
|
+
state.goal = {
|
|
1987
|
+
...auditTarget,
|
|
1988
|
+
status: "complete",
|
|
1989
|
+
stopReason: "agent",
|
|
1990
|
+
updatedAt: nowIso(),
|
|
1991
|
+
};
|
|
1992
|
+
state.goal = writeActiveGoalFile(ctx, state.goal);
|
|
1993
|
+
pi.appendEntry(STATE_ENTRY, goalDetails(state.goal));
|
|
1994
|
+
turnStoppedFor = state.goal?.id ?? null;
|
|
1995
|
+
resetGetGoalNudgeState(state.goal?.id);
|
|
1996
|
+
syncGoalTools();
|
|
1997
|
+
updateUI(ctx);
|
|
2021
1998
|
return {
|
|
2022
1999
|
content: [{
|
|
2023
2000
|
type: "text",
|
|
2024
2001
|
text: buildCompletionReport({
|
|
2025
|
-
detailedSummary: detailedSummary(
|
|
2002
|
+
detailedSummary: detailedSummary(state.goal),
|
|
2026
2003
|
completionSummary: params.completionSummary,
|
|
2027
2004
|
auditorReport: auditor.output,
|
|
2028
2005
|
}),
|
|
2029
2006
|
}],
|
|
2030
|
-
details: goalDetails(
|
|
2007
|
+
details: goalDetails(state.goal),
|
|
2031
2008
|
terminate: true,
|
|
2032
2009
|
};
|
|
2033
2010
|
},
|
|
@@ -2403,6 +2380,31 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
2403
2380
|
return;
|
|
2404
2381
|
}
|
|
2405
2382
|
refreshGoalDisplayFromDisk(ctx);
|
|
2383
|
+
|
|
2384
|
+
// Archive a goal that was marked complete by update_goal but whose archival
|
|
2385
|
+
// was deferred so the agent could see/recognize the audit result first.
|
|
2386
|
+
// This runs after the agent's turn ends — the agent has now seen the result.
|
|
2387
|
+
if (state.goal?.status === "complete" && !state.goal?.archivedPath) {
|
|
2388
|
+
const completedGoal = state.goal;
|
|
2389
|
+
const archived = archiveGoalFile(ctx, completedGoal);
|
|
2390
|
+
resetGetGoalNudgeState(completedGoal.id);
|
|
2391
|
+
goalsById.delete(completedGoal.id);
|
|
2392
|
+
focusedGoalId = null;
|
|
2393
|
+
appendFocusEntry(null, "completed");
|
|
2394
|
+
syncGoalTools();
|
|
2395
|
+
updateUI(ctx);
|
|
2396
|
+
try {
|
|
2397
|
+
appendGoalEvent(ctx, {
|
|
2398
|
+
type: "goal_completed",
|
|
2399
|
+
goalId: completedGoal.id,
|
|
2400
|
+
archivePath: archived.archivedPath,
|
|
2401
|
+
at: nowIso(),
|
|
2402
|
+
});
|
|
2403
|
+
} catch {
|
|
2404
|
+
// Ledger append failure should not crash completion
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2406
2408
|
// If the assistant ended a turn without queuing more tool calls, push a continuation right away.
|
|
2407
2409
|
// #4: only queue if some real work was done this turn — otherwise the model is
|
|
2408
2410
|
// just chatting and we should not keep firing turns on noise.
|
|
@@ -205,7 +205,6 @@ export function parseGoalFile(filePath: string): GoalRecord | null {
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
export function writeActiveGoalFile(ctx: GoalFileContext, current: GoalRecord): GoalRecord {
|
|
208
|
-
if (current.status === "complete") return archiveGoalFile(ctx, current);
|
|
209
208
|
const activePath = activePathForGoal(ctx, current);
|
|
210
209
|
const next = sanitizeGoalPaths(ctx, { ...current, activePath, updatedAt: nowIso() });
|
|
211
210
|
atomicWriteGoalFile(ctx, GOALS_DIR, activePath, serializeGoalFile(next));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-x",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.2",
|
|
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",
|