pi-better-harness 0.3.5 → 0.3.6
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/node_modules/pi-better-plan/README.md +1 -0
- package/node_modules/pi-better-plan/package.json +2 -2
- package/node_modules/pi-better-plan/src/index.ts +35 -3
- package/node_modules/pi-better-plan/src/plan-state.ts +15 -1
- package/node_modules/pi-better-plan/src/types.ts +1 -0
- package/package.json +2 -2
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
- Gives models `update_plan` and `get_plan` tools for atomic, explicit progress updates.
|
|
8
8
|
- Shows the complete checklist of completed, active, pending, and blocked steps above the editor.
|
|
9
9
|
- Persists plan state and display preferences on the active Pi session branch.
|
|
10
|
+
- Keeps a completed plan visible for 30 seconds, then clears it automatically.
|
|
10
11
|
- Opens the complete plan with `/plan`.
|
|
11
12
|
|
|
12
13
|
Plan progress is checklist progress, not an estimate of effort. The extension never infers completion from prose or successful tool calls.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-better-plan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Structured execution plans with persistent progress for Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -70,4 +70,4 @@
|
|
|
70
70
|
"engines": {
|
|
71
71
|
"node": ">=22.0.0"
|
|
72
72
|
}
|
|
73
|
-
}
|
|
73
|
+
}
|
|
@@ -4,6 +4,7 @@ import { Text } from "@earendil-works/pi-tui";
|
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
completedPlanClearDelay,
|
|
7
8
|
planClearEntry,
|
|
8
9
|
planDisplayEntry,
|
|
9
10
|
planProgress,
|
|
@@ -42,21 +43,53 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
42
43
|
let currentPlan: PlanSnapshot | null = null;
|
|
43
44
|
let displayMode: PlanDisplayMode = "auto";
|
|
44
45
|
let refreshWidget: ((force?: boolean) => void) | undefined;
|
|
46
|
+
let completedPlanClearTimer: ReturnType<typeof setTimeout> | undefined;
|
|
45
47
|
|
|
46
48
|
const refresh = (force = false): void => refreshWidget?.(force);
|
|
47
49
|
|
|
50
|
+
const cancelCompletedPlanClear = (): void => {
|
|
51
|
+
if (completedPlanClearTimer) clearTimeout(completedPlanClearTimer);
|
|
52
|
+
completedPlanClearTimer = undefined;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const clearPlan = (): void => {
|
|
56
|
+
cancelCompletedPlanClear();
|
|
57
|
+
currentPlan = null;
|
|
58
|
+
pi.appendEntry(EXTENSION_NAME, planClearEntry());
|
|
59
|
+
refresh(true);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const scheduleCompletedPlanClear = (): void => {
|
|
63
|
+
cancelCompletedPlanClear();
|
|
64
|
+
if (!currentPlan) return;
|
|
65
|
+
const delay = completedPlanClearDelay(currentPlan);
|
|
66
|
+
if (delay === null) return;
|
|
67
|
+
const completedPlan = currentPlan;
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
if (completedPlanClearTimer !== timer) return;
|
|
70
|
+
completedPlanClearTimer = undefined;
|
|
71
|
+
if (currentPlan?.planId !== completedPlan.planId || currentPlan.revision !== completedPlan.revision) return;
|
|
72
|
+
clearPlan();
|
|
73
|
+
}, delay);
|
|
74
|
+
completedPlanClearTimer = timer;
|
|
75
|
+
timer.unref?.();
|
|
76
|
+
};
|
|
77
|
+
|
|
48
78
|
const restore = (ctx: ExtensionContext): void => {
|
|
79
|
+
cancelCompletedPlanClear();
|
|
49
80
|
const state = reconstructPlanState(ctx.sessionManager.getBranch());
|
|
50
81
|
currentPlan = state.plan;
|
|
51
82
|
displayMode = state.displayMode;
|
|
52
83
|
if (ctx.hasUI) ctx.ui.setStatus(LEGACY_PLAN_NAV_STATUS_KEY, undefined);
|
|
53
84
|
refresh(true);
|
|
85
|
+
scheduleCompletedPlanClear();
|
|
54
86
|
};
|
|
55
87
|
|
|
56
88
|
const persistPlan = (plan: PlanSnapshot): void => {
|
|
57
89
|
currentPlan = plan;
|
|
58
90
|
pi.appendEntry(EXTENSION_NAME, planSetEntry(plan));
|
|
59
91
|
refresh(true);
|
|
92
|
+
scheduleCompletedPlanClear();
|
|
60
93
|
};
|
|
61
94
|
|
|
62
95
|
const setDisplayMode = (mode: PlanDisplayMode): void => {
|
|
@@ -171,9 +204,7 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
171
204
|
const input = args.trim().toLowerCase();
|
|
172
205
|
if (!input) return showFullPlan(ctx);
|
|
173
206
|
if (input === "clear") {
|
|
174
|
-
|
|
175
|
-
pi.appendEntry(EXTENSION_NAME, planClearEntry());
|
|
176
|
-
refresh(true);
|
|
207
|
+
clearPlan();
|
|
177
208
|
ctx.ui.notify("Plan cleared.", "info");
|
|
178
209
|
return;
|
|
179
210
|
}
|
|
@@ -209,6 +240,7 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
209
240
|
};
|
|
210
241
|
});
|
|
211
242
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
243
|
+
cancelCompletedPlanClear();
|
|
212
244
|
refreshWidget = undefined;
|
|
213
245
|
try {
|
|
214
246
|
ctx.ui.setStatus(LEGACY_PLAN_NAV_STATUS_KEY, undefined);
|
|
@@ -4,6 +4,7 @@ import { EXTENSION_NAME } from "./types.js";
|
|
|
4
4
|
const MAX_STEPS = 50;
|
|
5
5
|
const MAX_STEP_CHARS = 500;
|
|
6
6
|
const MAX_EXPLANATION_CHARS = 2_000;
|
|
7
|
+
export const COMPLETED_PLAN_RETENTION_MS = 30_000;
|
|
7
8
|
|
|
8
9
|
interface SessionEntryLike {
|
|
9
10
|
type: string;
|
|
@@ -54,6 +55,7 @@ export function replacePlan(
|
|
|
54
55
|
steps: readonly PlanStepInput[],
|
|
55
56
|
explanation?: string,
|
|
56
57
|
now = nowSeconds(),
|
|
58
|
+
completedAtMs = Date.now(),
|
|
57
59
|
): PlanSnapshot {
|
|
58
60
|
const error = validatePlanInput(steps, explanation);
|
|
59
61
|
if (error) throw new Error(error);
|
|
@@ -68,6 +70,8 @@ export function replacePlan(
|
|
|
68
70
|
status: item.status,
|
|
69
71
|
};
|
|
70
72
|
});
|
|
73
|
+
const isComplete = normalizedSteps.every((item) => item.status === "completed");
|
|
74
|
+
const currentIsComplete = current?.steps.every((item) => item.status === "completed") === true;
|
|
71
75
|
|
|
72
76
|
return {
|
|
73
77
|
version: 1,
|
|
@@ -77,9 +81,18 @@ export function replacePlan(
|
|
|
77
81
|
steps: normalizedSteps,
|
|
78
82
|
createdAt: current?.createdAt ?? now,
|
|
79
83
|
updatedAt: now,
|
|
84
|
+
...(isComplete
|
|
85
|
+
? { completedAtMs: currentIsComplete ? current.completedAtMs ?? current.updatedAt * 1_000 : completedAtMs }
|
|
86
|
+
: {}),
|
|
80
87
|
};
|
|
81
88
|
}
|
|
82
89
|
|
|
90
|
+
export function completedPlanClearDelay(plan: PlanSnapshot, now = Date.now()): number | null {
|
|
91
|
+
if (planProgress(plan).state !== "complete") return null;
|
|
92
|
+
const completedAtMs = plan.completedAtMs ?? plan.updatedAt * 1_000;
|
|
93
|
+
return Math.max(0, completedAtMs + COMPLETED_PLAN_RETENTION_MS - now);
|
|
94
|
+
}
|
|
95
|
+
|
|
83
96
|
export function planProgress(plan: PlanSnapshot): PlanProgress {
|
|
84
97
|
const completed = plan.steps.filter((item) => item.status === "completed").length;
|
|
85
98
|
const pending = plan.steps.filter((item) => item.status === "pending").length;
|
|
@@ -149,6 +162,7 @@ function isPlanSnapshot(value: unknown): value is PlanSnapshot {
|
|
|
149
162
|
typeof candidate.revision === "number" &&
|
|
150
163
|
Array.isArray(candidate.steps) &&
|
|
151
164
|
typeof candidate.createdAt === "number" &&
|
|
152
|
-
typeof candidate.updatedAt === "number"
|
|
165
|
+
typeof candidate.updatedAt === "number" &&
|
|
166
|
+
(candidate.completedAtMs === undefined || typeof candidate.completedAtMs === "number")
|
|
153
167
|
);
|
|
154
168
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-better-harness",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Pi extension bundle for a write sandbox, subagents, background tasks, SSH, goals, and structured plans.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"pi-better-background-tasks": "0.2.11",
|
|
54
54
|
"pi-better-goal": "0.2.1",
|
|
55
|
-
"pi-better-plan": "0.1.
|
|
55
|
+
"pi-better-plan": "0.1.4",
|
|
56
56
|
"pi-better-sandbox": "0.3.0",
|
|
57
57
|
"pi-better-ssh": "0.1.1",
|
|
58
58
|
"pi-better-subagents": "0.1.26"
|