pi-better-goal 0.2.0 → 0.2.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/package.json +1 -1
- package/src/index.ts +38 -6
- package/src/subagents.ts +42 -4
- package/src/types.ts +1 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -162,6 +162,7 @@ function formatSnapshot(snapshot: ActivitySnapshot): string {
|
|
|
162
162
|
|
|
163
163
|
export default function (pi: ExtensionAPI): void {
|
|
164
164
|
const providers = new Map<string, BackgroundActivityProvider>();
|
|
165
|
+
const providerUnsubscribers = new Map<string, () => void>();
|
|
165
166
|
providers.set("subagents", {
|
|
166
167
|
id: "subagents",
|
|
167
168
|
label: "Subagents",
|
|
@@ -172,6 +173,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
172
173
|
let foregroundRunning = false;
|
|
173
174
|
let pollTimer: ReturnType<typeof setInterval> | undefined;
|
|
174
175
|
let collecting = false;
|
|
176
|
+
let collectionPending = false;
|
|
175
177
|
let latestSnapshot: ActivitySnapshot | null = null;
|
|
176
178
|
let backgroundDrainTracker: BackgroundDrainTracker | null = null;
|
|
177
179
|
let lastWakeSignature = "";
|
|
@@ -230,6 +232,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
230
232
|
continuationQueuedFor = null;
|
|
231
233
|
backgroundDrainTracker = null;
|
|
232
234
|
clearIdleContinuation();
|
|
235
|
+
syncPollingState();
|
|
233
236
|
// Force a full redraw when the dock height changes (absent ↔ visible clock).
|
|
234
237
|
refreshGoalWidget?.(!wasVisible || !isGoalClockVisible(goal));
|
|
235
238
|
};
|
|
@@ -241,6 +244,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
241
244
|
continuationQueuedFor = null;
|
|
242
245
|
backgroundDrainTracker = null;
|
|
243
246
|
clearIdleContinuation();
|
|
247
|
+
syncPollingState();
|
|
244
248
|
refreshGoalWidget?.(wasVisible);
|
|
245
249
|
};
|
|
246
250
|
|
|
@@ -409,12 +413,22 @@ export default function (pi: ExtensionAPI): void {
|
|
|
409
413
|
|
|
410
414
|
const collectIfPossible = (): void => {
|
|
411
415
|
const ctx = currentCtx;
|
|
412
|
-
if (!ctx
|
|
416
|
+
if (!ctx) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (collecting) {
|
|
420
|
+
collectionPending = true;
|
|
413
421
|
return;
|
|
414
422
|
}
|
|
415
423
|
collecting = true;
|
|
416
424
|
void publishSnapshot(ctx).finally(() => {
|
|
417
425
|
collecting = false;
|
|
426
|
+
if (collectionPending) {
|
|
427
|
+
collectionPending = false;
|
|
428
|
+
collectIfPossible();
|
|
429
|
+
} else {
|
|
430
|
+
syncPollingState();
|
|
431
|
+
}
|
|
418
432
|
});
|
|
419
433
|
};
|
|
420
434
|
|
|
@@ -424,7 +438,6 @@ export default function (pi: ExtensionAPI): void {
|
|
|
424
438
|
}
|
|
425
439
|
pollTimer = setInterval(collectIfPossible, POLL_INTERVAL_MS);
|
|
426
440
|
pollTimer.unref?.();
|
|
427
|
-
collectIfPossible();
|
|
428
441
|
};
|
|
429
442
|
|
|
430
443
|
const stopPolling = (): void => {
|
|
@@ -435,6 +448,16 @@ export default function (pi: ExtensionAPI): void {
|
|
|
435
448
|
pollTimer = undefined;
|
|
436
449
|
};
|
|
437
450
|
|
|
451
|
+
const syncPollingState = (): void => {
|
|
452
|
+
const ctx = currentCtx;
|
|
453
|
+
const goal = ctx ? currentGoalSnapshot(ctx) : null;
|
|
454
|
+
if (ctx && (isPokeable(goal) || latestSnapshot?.backgroundRunning === true)) {
|
|
455
|
+
startPolling();
|
|
456
|
+
} else {
|
|
457
|
+
stopPolling();
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
438
461
|
const installGoalWidget = (ctx: ExtensionContext): void => {
|
|
439
462
|
if (!ctx.hasUI) {
|
|
440
463
|
return;
|
|
@@ -494,8 +517,14 @@ export default function (pi: ExtensionAPI): void {
|
|
|
494
517
|
if (!candidate || typeof candidate.id !== "string" || typeof candidate.getActivity !== "function") {
|
|
495
518
|
return;
|
|
496
519
|
}
|
|
497
|
-
|
|
498
|
-
|
|
520
|
+
const accepted = candidate as BackgroundActivityProvider;
|
|
521
|
+
providerUnsubscribers.get(accepted.id)?.();
|
|
522
|
+
providerUnsubscribers.delete(accepted.id);
|
|
523
|
+
providers.set(accepted.id, accepted);
|
|
524
|
+
if (typeof accepted.onActivityChanged === "function") {
|
|
525
|
+
providerUnsubscribers.set(accepted.id, accepted.onActivityChanged(collectIfPossible));
|
|
526
|
+
}
|
|
527
|
+
if (latestSnapshot) collectIfPossible();
|
|
499
528
|
});
|
|
500
529
|
|
|
501
530
|
pi.registerCommand("goal", {
|
|
@@ -661,7 +690,8 @@ export default function (pi: ExtensionAPI): void {
|
|
|
661
690
|
foregroundRunning = !ctx.isIdle();
|
|
662
691
|
pi.events.emit(EVENT_READY, { version: EXTENSION_VERSION });
|
|
663
692
|
installGoalWidget(ctx);
|
|
664
|
-
|
|
693
|
+
latestSnapshot = await publishSnapshot(ctx);
|
|
694
|
+
syncPollingState();
|
|
665
695
|
});
|
|
666
696
|
|
|
667
697
|
pi.on("input", async (event, ctx) => {
|
|
@@ -683,7 +713,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
683
713
|
}
|
|
684
714
|
|
|
685
715
|
const backgroundInstruction = snapshot.backgroundRunning
|
|
686
|
-
? ` The goal still has delegated background work running (${summarizeActiveBackground(snapshot)}). Foreground idleness alone is not goal completion; do not mark the goal complete until delegated
|
|
716
|
+
? ` The goal still has delegated background work running (${summarizeActiveBackground(snapshot)}). Foreground idleness alone is not goal completion; keep any structured plan current and do not mark verification, the plan, or the goal complete until every relevant delegated task reaches a terminal state and its result or failure has been inspected and integrated.`
|
|
687
717
|
: "";
|
|
688
718
|
return {
|
|
689
719
|
systemPrompt:
|
|
@@ -762,6 +792,8 @@ export default function (pi: ExtensionAPI): void {
|
|
|
762
792
|
foregroundRunning = false;
|
|
763
793
|
clearIdleContinuation();
|
|
764
794
|
currentCtx = undefined;
|
|
795
|
+
latestSnapshot = null;
|
|
796
|
+
collectionPending = false;
|
|
765
797
|
try {
|
|
766
798
|
ctx.ui.setStatus(EXTENSION_NAME, undefined);
|
|
767
799
|
ctx.ui.setWidget(EXTENSION_NAME, undefined);
|
package/src/subagents.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
@@ -82,6 +82,46 @@ export function readSubagentMetas(baseDir = defaultSubagentBaseDir()): SubagentR
|
|
|
82
82
|
return metas.sort((left, right) => right.startedAt - left.startedAt);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
export function readParentSubagentMetas(
|
|
86
|
+
baseDir = defaultSubagentBaseDir(),
|
|
87
|
+
parentPid = process.pid,
|
|
88
|
+
): SubagentRunMeta[] {
|
|
89
|
+
const directory = join(baseDir, "by-parent", String(parentPid));
|
|
90
|
+
ensureParentIndex(baseDir, directory, parentPid);
|
|
91
|
+
let ids: string[];
|
|
92
|
+
try {
|
|
93
|
+
ids = readdirSync(directory).filter((id) => id !== ".initialized");
|
|
94
|
+
} catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
return ids
|
|
98
|
+
.map((id) => readSubagentMeta(baseDir, id))
|
|
99
|
+
.filter((meta): meta is SubagentRunMeta => meta !== undefined && meta.spawnPid === parentPid)
|
|
100
|
+
.sort((left, right) => right.startedAt - left.startedAt);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function readSubagentMeta(baseDir: string, id: string): SubagentRunMeta | undefined {
|
|
104
|
+
try {
|
|
105
|
+
const meta = JSON.parse(readFileSync(join(baseDir, "runs", id, "meta.json"), "utf8")) as SubagentRunMeta;
|
|
106
|
+
return typeof meta.id === "string" && typeof meta.status === "string" ? meta : undefined;
|
|
107
|
+
} catch {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function ensureParentIndex(baseDir: string, directory: string, parentPid: number): void {
|
|
113
|
+
try {
|
|
114
|
+
readFileSync(join(directory, ".initialized"));
|
|
115
|
+
return;
|
|
116
|
+
} catch {
|
|
117
|
+
// Existing registries are backfilled once for this parent process.
|
|
118
|
+
}
|
|
119
|
+
const owned = readSubagentMetas(baseDir).filter((meta) => meta.spawnPid === parentPid);
|
|
120
|
+
mkdirSync(directory, { recursive: true });
|
|
121
|
+
for (const meta of owned) writeFileSync(join(directory, meta.id), "");
|
|
122
|
+
writeFileSync(join(directory, ".initialized"), "1");
|
|
123
|
+
}
|
|
124
|
+
|
|
85
125
|
export function subagentStatusToItem(
|
|
86
126
|
meta: SubagentRunMeta,
|
|
87
127
|
status: SubagentEffectiveStatus,
|
|
@@ -122,9 +162,7 @@ export function collectSubagentActivity(
|
|
|
122
162
|
): BackgroundProviderSnapshot {
|
|
123
163
|
const parentPid = options.parentPid ?? process.pid;
|
|
124
164
|
const processExists = options.processExists ?? defaultProcessExists;
|
|
125
|
-
const metas =
|
|
126
|
-
isCurrentParentSubagent(meta, parentPid),
|
|
127
|
-
);
|
|
165
|
+
const metas = readParentSubagentMetas(options.baseDir, parentPid);
|
|
128
166
|
|
|
129
167
|
return {
|
|
130
168
|
providerId: "subagents",
|
package/src/types.ts
CHANGED
|
@@ -47,6 +47,7 @@ export interface BackgroundActivityProvider {
|
|
|
47
47
|
id: string;
|
|
48
48
|
label?: string;
|
|
49
49
|
getActivity(ctx: ExtensionContext): BackgroundProviderSnapshot | Promise<BackgroundProviderSnapshot>;
|
|
50
|
+
onActivityChanged?(notify: () => void): () => void;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export interface ActivitySnapshot {
|