pi-observational-memory-extension 0.1.1 → 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.
Files changed (2) hide show
  1. package/extensions/index.ts +96 -15
  2. package/package.json +1 -1
@@ -380,7 +380,13 @@ export default function (pi: ExtensionAPI) {
380
380
  runtime.currentOperation?.abort();
381
381
  if (runtime.state) {
382
382
  try {
383
- if (runtime.state.enabled && runtime.state.pendingMessageTokens > 0) {
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) {
384
390
  await observeNow(ctx, { force: true, reason: "session_shutdown" });
385
391
  }
386
392
  } catch (error) {
@@ -388,8 +394,12 @@ export default function (pi: ExtensionAPI) {
388
394
  }
389
395
  await saveState(runtime.state);
390
396
  }
391
- ctx.ui.setStatus("om", undefined);
392
- ctx.ui.setWidget("om", undefined);
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
+ }
393
403
  });
394
404
 
395
405
  pi.on("model_select", async (event, ctx) => {
@@ -568,6 +578,14 @@ export default function (pi: ExtensionAPI) {
568
578
  }
569
579
 
570
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
+
571
589
  const state = await ensureState(ctx);
572
590
  if (!state.enabled || state.status === "failed") return;
573
591
  await refreshCounts(ctx);
@@ -586,17 +604,40 @@ async function safeBoundary(ctx: any, boundary: string): Promise<void> {
586
604
  }
587
605
  if (shouldBuffer(state)) {
588
606
  void bufferObservation(ctx, `${boundary}:buffer`).catch(async (error) => {
589
- const s = await ensureState(ctx);
590
- s.status = "failed";
591
- s.lastError = `OM buffer failed: ${errorMessage(error)}`;
592
- await saveState(s);
593
- updateStatus(ctx);
594
- ctx.ui.notify(s.lastError, "error");
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
+ }
595
625
  });
596
626
  }
597
627
  }
598
628
 
599
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
+
600
641
  if (runtime.state && runtime.state.cwd === ctx.cwd && runtime.state.sessionFile === ctx.sessionManager.getSessionFile()) return runtime.state;
601
642
  const sessionFile = ctx.sessionManager.getSessionFile?.();
602
643
  const sessionId = ctx.sessionManager.getSessionId?.() || (sessionFile ? basename(sessionFile, ".jsonl") : "in-memory");
@@ -651,6 +692,14 @@ async function saveState(state: PiOMRecord): Promise<void> {
651
692
  }
652
693
 
653
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
+
654
703
  const state = await ensureState(ctx);
655
704
  const branch = ctx.sessionManager.getBranch() as SessionEntry[];
656
705
  const pending = entriesAfter(branch, state.lastObservedEntryId).filter(isMessageLikeEntry);
@@ -662,11 +711,23 @@ async function refreshCounts(ctx: any): Promise<void> {
662
711
  function updateStatus(ctx: any): void {
663
712
  const state = runtime.state;
664
713
  if (!state) return;
665
- ctx.ui.setStatus("om", formatShortStatusColored(state));
714
+ try {
715
+ ctx.ui.setStatus("om", formatShortStatusColored(state));
716
+ } catch (e) {
717
+ // Context might be stale or UI disposed
718
+ }
666
719
  runtime.overlayHandle?.requestRender();
667
720
  }
668
721
 
669
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
+
670
731
  const state = await ensureState(ctx);
671
732
  assertNoOperation(state);
672
733
  const branch = ctx.sessionManager.getBranch() as SessionEntry[];
@@ -758,6 +819,14 @@ async function bufferObservation(ctx: any, reason: string): Promise<void> {
758
819
  }
759
820
 
760
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
+
761
830
  const state = await ensureState(ctx);
762
831
  if (state.buffered.observations.length === 0) return;
763
832
  assertNoOperation(state);
@@ -794,6 +863,14 @@ async function activateBuffered(ctx: any, reason: string): Promise<void> {
794
863
  }
795
864
 
796
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
+
797
874
  const state = await ensureState(ctx);
798
875
  assertNoOperation(state);
799
876
  if (!state.observations.trim()) throw new Error("No observations to reflect");
@@ -1320,11 +1397,15 @@ function formatTokens(tokens: number): string {
1320
1397
  }
1321
1398
 
1322
1399
  async function writeDebug(ctx: any, name: string, payload: unknown): Promise<void> {
1323
- const state = await ensureState(ctx);
1324
- const dir = runtime.debugDir ?? join(ctx.cwd, CONFIG_DIR_NAME, "om", "debug");
1325
- await mkdir(dir, { recursive: true });
1326
- const file = join(dir, `${new Date().toISOString().replace(/[:.]/g, "-")}-${sanitizeFileName(name)}.json`);
1327
- await writeFile(file, JSON.stringify({ extension: EXTENSION_ID, sessionId: state.sessionId, ...payload as any }, null, 2) + "\n", "utf8");
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
+ }
1328
1409
  }
1329
1410
 
1330
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.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>",