observal-pi 0.1.0 → 1.1.0

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 CHANGED
@@ -15,7 +15,7 @@ pi install npm:observal-pi
15
15
 
16
16
  ## Prerequisites
17
17
 
18
- 1. An Observal account run `observal auth login` to authenticate
18
+ 1. An Observal account - run `observal auth login` to authenticate
19
19
  2. Pi installed (`>=0.74.0`)
20
20
 
21
21
  ## What it does
@@ -35,11 +35,11 @@ pi install npm:observal-pi
35
35
 
36
36
  ## Design
37
37
 
38
- - **Zero dependencies** only `node:*` built-ins
39
- - **Fail-open** never throws, never crashes pi. If the server is unreachable, pi continues normally
40
- - **5s timeout** all HTTP calls abort after 5 seconds
41
- - **Chunked uploads** batches of 500 lines max per request
42
- - **Dedup-safe** server deduplicates by `(session_id, line_offset, line_hash)`
38
+ - **Zero dependencies** - only `node:*` built-ins
39
+ - **Fail-open** - never throws, never crashes pi. If the server is unreachable, pi continues normally
40
+ - **5s timeout** - all HTTP calls abort after 5 seconds
41
+ - **Chunked uploads** - batches of 500 lines max per request
42
+ - **Dedup-safe** - server deduplicates by `(session_id, line_offset, line_hash)`
43
43
 
44
44
  ## Configuration
45
45
 
@@ -56,4 +56,4 @@ Sync state is tracked in `~/.observal/sync_state.json` (per-session byte offsets
56
56
 
57
57
  ## License
58
58
 
59
- AGPL-3.0-only see [LICENSE](./LICENSE)
59
+ AGPL-3.0-only - see [LICENSE](./LICENSE)
@@ -34,6 +34,7 @@ interface CursorEntry {
34
34
  offset: number;
35
35
  line_count: number;
36
36
  finalized?: boolean;
37
+ last_pushed_at?: number;
37
38
  }
38
39
 
39
40
  interface ObservalState {
@@ -159,7 +160,7 @@ export default function (pi: ExtensionAPI) {
159
160
  if (fs.existsSync(SYNC_STATE_PATH)) {
160
161
  data = JSON.parse(fs.readFileSync(SYNC_STATE_PATH, "utf-8"));
161
162
  }
162
- data[sessionId] = { offset, line_count: lineCount, finalized };
163
+ data[sessionId] = { offset, line_count: lineCount, finalized, last_pushed_at: Date.now() };
163
164
  fs.writeFileSync(SYNC_STATE_PATH, JSON.stringify(data, null, 2));
164
165
  } catch {
165
166
  // Fail-open
@@ -266,7 +267,7 @@ export default function (pi: ExtensionAPI) {
266
267
  (res) => {
267
268
  clearTimeout(timer);
268
269
  res.resume(); // drain response
269
- resolve(res.statusCode === 200);
270
+ resolve(res.statusCode! >= 200 && res.statusCode! < 300);
270
271
  },
271
272
  );
272
273
 
@@ -290,10 +291,12 @@ export default function (pi: ExtensionAPI) {
290
291
  fs.readFileSync(SYNC_STATE_PATH, "utf-8"),
291
292
  );
292
293
 
294
+ // Use sessionManager to resolve session directory when available,
295
+ // falling back to the conventional path layout.
296
+ const sessionsDir = ctx.sessionManager.getSessionsDir?.()
297
+ ?? path.join(os.homedir(), ".pi", "agent", "sessions");
293
298
  const cwd = ctx.cwd;
294
299
  const projectKey = cwd.replace(/\//g, "-");
295
- const sessionsDir = path.join(os.homedir(), ".pi", "agent", "sessions");
296
- // Pi uses --<path>-- format for directory names
297
300
  const fullDir = path.join(sessionsDir, `-${projectKey}-`);
298
301
 
299
302
  if (!fs.existsSync(fullDir)) return;
@@ -369,8 +372,8 @@ export default function (pi: ExtensionAPI) {
369
372
  const entries = Object.entries(data);
370
373
  if (entries.length <= 50) return; // No pruning needed
371
374
 
372
- // Keep only the 50 most recent entries (by offset as proxy for recency)
373
- const sorted = entries.sort((a, b) => b[1].offset - a[1].offset);
375
+ // Keep only the 50 most recent entries (by last push time, falling back to offset)
376
+ const sorted = entries.sort((a, b) => (b[1].last_pushed_at ?? 0) - (a[1].last_pushed_at ?? 0));
374
377
  const pruned: Record<string, CursorEntry> = {};
375
378
  for (const [key, value] of sorted.slice(0, 50)) {
376
379
  pruned[key] = value;
package/package.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
2
  "name": "observal-pi",
3
- "version": "0.1.0",
3
+ "version": "1.1.0",
4
4
  "description": "Observal session telemetry for Pi — zero-dependency extension that pushes session traces to your Observal server",
5
- "keywords": ["pi-package", "pi-extension", "observal", "telemetry", "tracing"],
5
+ "keywords": [
6
+ "pi-package",
7
+ "pi-extension",
8
+ "observal",
9
+ "telemetry",
10
+ "tracing"
11
+ ],
6
12
  "license": "AGPL-3.0-only",
7
13
  "repository": {
8
14
  "type": "git",
@@ -10,7 +16,9 @@
10
16
  "directory": "packages/pi-extension"
11
17
  },
12
18
  "pi": {
13
- "extensions": ["./extensions/observal.ts"]
19
+ "extensions": [
20
+ "./extensions/observal.ts"
21
+ ]
14
22
  },
15
23
  "peerDependencies": {
16
24
  "@earendil-works/pi-coding-agent": ">=0.74.0"