pi-better-goal 0.1.18 → 0.1.19
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 +1 -0
- package/package.json +1 -1
- package/src/goal-state.ts +1 -1
- package/src/index.ts +59 -36
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ Use `pi-better-goal` when a Pi session should keep an explicit objective visible
|
|
|
13
13
|
## Core Features
|
|
14
14
|
|
|
15
15
|
- `/goal` runtime for starting, pausing, resuming, completing, and clearing the current objective.
|
|
16
|
+
- `escape` pauses the active goal; paused goals are never poked.
|
|
16
17
|
- A compact goal widget that does not replace Pi's footer.
|
|
17
18
|
- Background activity tracking for subagents and other registered providers.
|
|
18
19
|
- A progress-aware follow-up loop that holds after repeated identical outcomes.
|
package/package.json
CHANGED
package/src/goal-state.ts
CHANGED
|
@@ -23,7 +23,7 @@ interface GoalLike {
|
|
|
23
23
|
completedAt?: number | null;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export type GoalEntrySource = "command" | "tool" | "runtime";
|
|
26
|
+
export type GoalEntrySource = "command" | "tool" | "runtime" | "shortcut";
|
|
27
27
|
|
|
28
28
|
export interface ContinuationState {
|
|
29
29
|
goalId: string;
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
goalWithStatus,
|
|
20
20
|
validateObjective,
|
|
21
21
|
validateTokenBudget,
|
|
22
|
+
type GoalEntrySource,
|
|
22
23
|
} from "./goal-state.js";
|
|
23
24
|
import { continuationEvidence, type ContinuationEvidence } from "./continuation.js";
|
|
24
25
|
import { observeGoalStall } from "./stall.js";
|
|
@@ -148,6 +149,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
148
149
|
|
|
149
150
|
const getGoal = (ctx: ExtensionContext): GoalSnapshot | null => currentGoalSnapshot(ctx);
|
|
150
151
|
|
|
152
|
+
/** Only active goals receive autonomous pokes; paused, complete, and budget-limited goals never do. */
|
|
153
|
+
const isPokeable = (goal: GoalSnapshot | null): goal is GoalSnapshot => goal?.status === "active";
|
|
154
|
+
|
|
151
155
|
const appendContinuationState = (state: ReturnType<typeof createContinuationState>): void => {
|
|
152
156
|
pi.appendEntry(EXTENSION_NAME, continuationStateEntry(state));
|
|
153
157
|
};
|
|
@@ -156,7 +160,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
156
160
|
appendContinuationState(createContinuationState(goal.goalId));
|
|
157
161
|
};
|
|
158
162
|
|
|
159
|
-
const setGoal = (goal: GoalSnapshot, ctx: ExtensionContext, source:
|
|
163
|
+
const setGoal = (goal: GoalSnapshot, ctx: ExtensionContext, source: GoalEntrySource): void => {
|
|
160
164
|
pi.appendEntry(EXTENSION_NAME, goalSetEntry(goal, source));
|
|
161
165
|
continuationQueuedFor = null;
|
|
162
166
|
backgroundDrainTracker = null;
|
|
@@ -164,7 +168,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
164
168
|
refreshGoalWidget?.();
|
|
165
169
|
};
|
|
166
170
|
|
|
167
|
-
const clearGoal = (ctx: ExtensionContext, source:
|
|
171
|
+
const clearGoal = (ctx: ExtensionContext, source: GoalEntrySource): void => {
|
|
168
172
|
const current = getGoal(ctx);
|
|
169
173
|
pi.appendEntry(EXTENSION_NAME, goalClearEntry(current?.goalId ?? null, source));
|
|
170
174
|
continuationQueuedFor = null;
|
|
@@ -174,7 +178,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
174
178
|
};
|
|
175
179
|
|
|
176
180
|
const queueGoalContinuation = (goal: GoalSnapshot): void => {
|
|
177
|
-
if (goal
|
|
181
|
+
if (!isPokeable(goal) || continuationQueuedFor === goal.goalId) {
|
|
178
182
|
return;
|
|
179
183
|
}
|
|
180
184
|
clearIdleContinuation();
|
|
@@ -205,7 +209,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
205
209
|
kind: "continuation" | "background-drained",
|
|
206
210
|
snapshot?: ActivitySnapshot,
|
|
207
211
|
): void => {
|
|
208
|
-
if (WAKE_DISABLED || goal
|
|
212
|
+
if (WAKE_DISABLED || !isPokeable(goal) || continuationQueuedFor === goal.goalId) {
|
|
209
213
|
return;
|
|
210
214
|
}
|
|
211
215
|
if (kind === "continuation" && currentContinuationState(ctx, goal.goalId)?.blocked) {
|
|
@@ -232,7 +236,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
232
236
|
priorSnapshot?: ActivitySnapshot,
|
|
233
237
|
): Promise<void> => {
|
|
234
238
|
const goal = currentGoalSnapshot(ctx);
|
|
235
|
-
if (goal
|
|
239
|
+
if (!isPokeable(goal) || goal.goalId !== goalId || continuationQueuedFor === goal.goalId || foregroundRunning) {
|
|
236
240
|
return;
|
|
237
241
|
}
|
|
238
242
|
const snapshot = await collectActivitySnapshot(ctx, providers.values(), foregroundRunning);
|
|
@@ -299,7 +303,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
299
303
|
const goal = currentGoalSnapshot(ctx);
|
|
300
304
|
const wakePlan = planBackgroundDrainWake(backgroundDrainTracker, goal, snapshot);
|
|
301
305
|
backgroundDrainTracker = wakePlan.nextTracker;
|
|
302
|
-
if (goal
|
|
306
|
+
if (isPokeable(goal) && wakePlan.wakeSignature) {
|
|
303
307
|
const wakeSignature = wakePlan.wakeSignature;
|
|
304
308
|
if (wakeSignature !== lastWakeSignature) {
|
|
305
309
|
lastWakeSignature = wakeSignature;
|
|
@@ -539,6 +543,24 @@ export default function (pi: ExtensionAPI): void {
|
|
|
539
543
|
},
|
|
540
544
|
});
|
|
541
545
|
|
|
546
|
+
pi.registerShortcut("escape", {
|
|
547
|
+
description: "Pause the active goal (also interrupts a running agent turn)",
|
|
548
|
+
handler: (ctx) => {
|
|
549
|
+
const goal = getGoal(ctx);
|
|
550
|
+
const running = !ctx.isIdle();
|
|
551
|
+
if (isPokeable(goal)) {
|
|
552
|
+
setGoal(goalWithStatus(goal, "paused"), ctx, "shortcut");
|
|
553
|
+
if (ctx.hasUI) {
|
|
554
|
+
ctx.ui.notify("Goal paused.", "info");
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (running) {
|
|
558
|
+
// ESC keeps its built-in interrupt meaning while the agent is streaming.
|
|
559
|
+
ctx.abort();
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
|
|
542
564
|
pi.registerCommand("better-activity", {
|
|
543
565
|
description: "Show foreground/background activity known to pi-better-goal",
|
|
544
566
|
handler: async (_args, ctx) => {
|
|
@@ -586,7 +608,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
586
608
|
currentCtx = ctx;
|
|
587
609
|
const goal = currentGoalSnapshot(ctx);
|
|
588
610
|
const snapshot = await publishSnapshot(ctx);
|
|
589
|
-
if (goal
|
|
611
|
+
if (!isPokeable(goal)) {
|
|
590
612
|
return;
|
|
591
613
|
}
|
|
592
614
|
|
|
@@ -618,37 +640,38 @@ export default function (pi: ExtensionAPI): void {
|
|
|
618
640
|
foregroundRunning = false;
|
|
619
641
|
const snapshot = await publishSnapshot(ctx);
|
|
620
642
|
const goal = getGoal(ctx);
|
|
621
|
-
if (goal
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
)
|
|
647
|
-
|
|
648
|
-
|
|
643
|
+
if (!isPokeable(goal) || snapshot.backgroundRunning) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const previous = currentContinuationState(ctx, goal.goalId) ?? createContinuationState(goal.goalId);
|
|
647
|
+
const evidence = lastAgentEvidence ?? continuationEvidence([]);
|
|
648
|
+
const repeated = previous.lastEvidenceSignature === evidence.signature;
|
|
649
|
+
const noProgressRetries = repeated ? previous.noProgressRetries + 1 : 0;
|
|
650
|
+
const blocked = repeated && noProgressRetries >= MAX_NO_PROGRESS_RETRIES;
|
|
651
|
+
const now = Date.now();
|
|
652
|
+
appendContinuationState({
|
|
653
|
+
goalId: goal.goalId,
|
|
654
|
+
lastEvidenceSignature: evidence.signature,
|
|
655
|
+
lastEvidenceSummary: evidence.summary,
|
|
656
|
+
...(repeated
|
|
657
|
+
? (previous.lastProgressAt !== undefined ? { lastProgressAt: previous.lastProgressAt } : {})
|
|
658
|
+
: { lastProgressAt: now }),
|
|
659
|
+
noProgressRetries,
|
|
660
|
+
blocked,
|
|
661
|
+
updatedAt: Math.floor(now / 1000),
|
|
662
|
+
});
|
|
663
|
+
if (blocked) {
|
|
664
|
+
refreshGoalWidget?.();
|
|
665
|
+
if (ctx.hasUI) {
|
|
666
|
+
ctx.ui.setStatus(EXTENSION_NAME, "waiting: no progress");
|
|
667
|
+
ctx.ui.notify(
|
|
668
|
+
`Goal automatic continuation is waiting after ${noProgressRetries} identical retries (${evidence.summary}). New input or background activity will resume it.`,
|
|
669
|
+
"warning",
|
|
670
|
+
);
|
|
649
671
|
}
|
|
650
|
-
|
|
672
|
+
return;
|
|
651
673
|
}
|
|
674
|
+
scheduleIdleContinuation(goal, ctx, "continuation", snapshot);
|
|
652
675
|
});
|
|
653
676
|
|
|
654
677
|
pi.on("session_shutdown", async (_event, ctx) => {
|