pi-observational-memory-extension 0.1.0 → 0.1.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/extensions/index.ts +105 -15
- package/package.json +1 -1
package/extensions/index.ts
CHANGED
|
@@ -378,9 +378,28 @@ export default function (pi: ExtensionAPI) {
|
|
|
378
378
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
379
379
|
if (runtime.statusTimer) clearInterval(runtime.statusTimer);
|
|
380
380
|
runtime.currentOperation?.abort();
|
|
381
|
-
if (runtime.state)
|
|
382
|
-
|
|
383
|
-
|
|
381
|
+
if (runtime.state) {
|
|
382
|
+
try {
|
|
383
|
+
let isStale = false;
|
|
384
|
+
try {
|
|
385
|
+
const _ = ctx.cwd;
|
|
386
|
+
} catch {
|
|
387
|
+
isStale = true;
|
|
388
|
+
}
|
|
389
|
+
if (!isStale && runtime.state.enabled && runtime.state.pendingMessageTokens > 0) {
|
|
390
|
+
await observeNow(ctx, { force: true, reason: "session_shutdown" });
|
|
391
|
+
}
|
|
392
|
+
} catch (error) {
|
|
393
|
+
// Safe catch on shutdown
|
|
394
|
+
}
|
|
395
|
+
await saveState(runtime.state);
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
ctx.ui.setStatus("om", undefined);
|
|
399
|
+
ctx.ui.setWidget("om", undefined);
|
|
400
|
+
} catch (e) {
|
|
401
|
+
// Ignore if UI or context is already stale
|
|
402
|
+
}
|
|
384
403
|
});
|
|
385
404
|
|
|
386
405
|
pi.on("model_select", async (event, ctx) => {
|
|
@@ -559,6 +578,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
559
578
|
}
|
|
560
579
|
|
|
561
580
|
async function safeBoundary(ctx: any, boundary: string): Promise<void> {
|
|
581
|
+
let isStale = false;
|
|
582
|
+
try {
|
|
583
|
+
const _ = ctx.cwd;
|
|
584
|
+
} catch {
|
|
585
|
+
isStale = true;
|
|
586
|
+
}
|
|
587
|
+
if (isStale) return;
|
|
588
|
+
|
|
562
589
|
const state = await ensureState(ctx);
|
|
563
590
|
if (!state.enabled || state.status === "failed") return;
|
|
564
591
|
await refreshCounts(ctx);
|
|
@@ -577,17 +604,40 @@ async function safeBoundary(ctx: any, boundary: string): Promise<void> {
|
|
|
577
604
|
}
|
|
578
605
|
if (shouldBuffer(state)) {
|
|
579
606
|
void bufferObservation(ctx, `${boundary}:buffer`).catch(async (error) => {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
607
|
+
try {
|
|
608
|
+
let innerStale = false;
|
|
609
|
+
try {
|
|
610
|
+
const _ = ctx.cwd;
|
|
611
|
+
} catch {
|
|
612
|
+
innerStale = true;
|
|
613
|
+
}
|
|
614
|
+
if (innerStale) return;
|
|
615
|
+
|
|
616
|
+
const s = await ensureState(ctx);
|
|
617
|
+
s.status = "failed";
|
|
618
|
+
s.lastError = `OM buffer failed: ${errorMessage(error)}`;
|
|
619
|
+
await saveState(s);
|
|
620
|
+
updateStatus(ctx);
|
|
621
|
+
ctx.ui.notify(s.lastError, "error");
|
|
622
|
+
} catch (innerError) {
|
|
623
|
+
// Prevent any uncaught exceptions from background handler
|
|
624
|
+
}
|
|
586
625
|
});
|
|
587
626
|
}
|
|
588
627
|
}
|
|
589
628
|
|
|
590
629
|
async function ensureState(ctx: any): Promise<PiOMRecord> {
|
|
630
|
+
let isStale = false;
|
|
631
|
+
try {
|
|
632
|
+
const _ = ctx.cwd;
|
|
633
|
+
} catch {
|
|
634
|
+
isStale = true;
|
|
635
|
+
}
|
|
636
|
+
if (isStale) {
|
|
637
|
+
if (runtime.state) return runtime.state;
|
|
638
|
+
throw new Error("Cannot ensure state because the extension context is stale.");
|
|
639
|
+
}
|
|
640
|
+
|
|
591
641
|
if (runtime.state && runtime.state.cwd === ctx.cwd && runtime.state.sessionFile === ctx.sessionManager.getSessionFile()) return runtime.state;
|
|
592
642
|
const sessionFile = ctx.sessionManager.getSessionFile?.();
|
|
593
643
|
const sessionId = ctx.sessionManager.getSessionId?.() || (sessionFile ? basename(sessionFile, ".jsonl") : "in-memory");
|
|
@@ -642,6 +692,14 @@ async function saveState(state: PiOMRecord): Promise<void> {
|
|
|
642
692
|
}
|
|
643
693
|
|
|
644
694
|
async function refreshCounts(ctx: any): Promise<void> {
|
|
695
|
+
let isStale = false;
|
|
696
|
+
try {
|
|
697
|
+
const _ = ctx.cwd;
|
|
698
|
+
} catch {
|
|
699
|
+
isStale = true;
|
|
700
|
+
}
|
|
701
|
+
if (isStale) return;
|
|
702
|
+
|
|
645
703
|
const state = await ensureState(ctx);
|
|
646
704
|
const branch = ctx.sessionManager.getBranch() as SessionEntry[];
|
|
647
705
|
const pending = entriesAfter(branch, state.lastObservedEntryId).filter(isMessageLikeEntry);
|
|
@@ -653,11 +711,23 @@ async function refreshCounts(ctx: any): Promise<void> {
|
|
|
653
711
|
function updateStatus(ctx: any): void {
|
|
654
712
|
const state = runtime.state;
|
|
655
713
|
if (!state) return;
|
|
656
|
-
|
|
714
|
+
try {
|
|
715
|
+
ctx.ui.setStatus("om", formatShortStatusColored(state));
|
|
716
|
+
} catch (e) {
|
|
717
|
+
// Context might be stale or UI disposed
|
|
718
|
+
}
|
|
657
719
|
runtime.overlayHandle?.requestRender();
|
|
658
720
|
}
|
|
659
721
|
|
|
660
722
|
async function observeNow(ctx: any, opts: { force: boolean; reason: string; signal?: AbortSignal; manualText?: string }): Promise<void> {
|
|
723
|
+
let isStale = false;
|
|
724
|
+
try {
|
|
725
|
+
const _ = ctx.cwd;
|
|
726
|
+
} catch {
|
|
727
|
+
isStale = true;
|
|
728
|
+
}
|
|
729
|
+
if (isStale) return;
|
|
730
|
+
|
|
661
731
|
const state = await ensureState(ctx);
|
|
662
732
|
assertNoOperation(state);
|
|
663
733
|
const branch = ctx.sessionManager.getBranch() as SessionEntry[];
|
|
@@ -749,6 +819,14 @@ async function bufferObservation(ctx: any, reason: string): Promise<void> {
|
|
|
749
819
|
}
|
|
750
820
|
|
|
751
821
|
async function activateBuffered(ctx: any, reason: string): Promise<void> {
|
|
822
|
+
let isStale = false;
|
|
823
|
+
try {
|
|
824
|
+
const _ = ctx.cwd;
|
|
825
|
+
} catch {
|
|
826
|
+
isStale = true;
|
|
827
|
+
}
|
|
828
|
+
if (isStale) return;
|
|
829
|
+
|
|
752
830
|
const state = await ensureState(ctx);
|
|
753
831
|
if (state.buffered.observations.length === 0) return;
|
|
754
832
|
assertNoOperation(state);
|
|
@@ -785,6 +863,14 @@ async function activateBuffered(ctx: any, reason: string): Promise<void> {
|
|
|
785
863
|
}
|
|
786
864
|
|
|
787
865
|
async function reflectNow(ctx: any, opts: { reason: string; signal?: AbortSignal; manualPrompt?: string }): Promise<void> {
|
|
866
|
+
let isStale = false;
|
|
867
|
+
try {
|
|
868
|
+
const _ = ctx.cwd;
|
|
869
|
+
} catch {
|
|
870
|
+
isStale = true;
|
|
871
|
+
}
|
|
872
|
+
if (isStale) return;
|
|
873
|
+
|
|
788
874
|
const state = await ensureState(ctx);
|
|
789
875
|
assertNoOperation(state);
|
|
790
876
|
if (!state.observations.trim()) throw new Error("No observations to reflect");
|
|
@@ -1311,11 +1397,15 @@ function formatTokens(tokens: number): string {
|
|
|
1311
1397
|
}
|
|
1312
1398
|
|
|
1313
1399
|
async function writeDebug(ctx: any, name: string, payload: unknown): Promise<void> {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1400
|
+
try {
|
|
1401
|
+
const state = await ensureState(ctx);
|
|
1402
|
+
const dir = runtime.debugDir || join(ctx.cwd, CONFIG_DIR_NAME, "om", "debug");
|
|
1403
|
+
await mkdir(dir, { recursive: true });
|
|
1404
|
+
const file = join(dir, `${new Date().toISOString().replace(/[:.]/g, "-")}-${sanitizeFileName(name)}.json`);
|
|
1405
|
+
await writeFile(file, JSON.stringify({ extension: EXTENSION_ID, sessionId: state.sessionId, ...payload as any }, null, 2) + "\n", "utf8");
|
|
1406
|
+
} catch (error) {
|
|
1407
|
+
// Ignore debug write errors if context is stale
|
|
1408
|
+
}
|
|
1319
1409
|
}
|
|
1320
1410
|
|
|
1321
1411
|
function sanitizeFileName(name: string): string {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-observational-memory-extension",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Mastra-style Observational Memory extension for Pi compaction and runtime context.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nikita Nosov <20nik.nosov21@gmail.com>",
|