pi-sessions 0.3.1 → 0.3.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.
@@ -1,7 +1,9 @@
1
+ import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
1
2
  import {
2
3
  type AssistantMessage,
3
- complete,
4
+ completeSimple,
4
5
  type Message,
6
+ type SimpleStreamOptions,
5
7
  type TextContent,
6
8
  type ThinkingContent,
7
9
  type Tool,
@@ -86,6 +88,7 @@ export interface HandoffDraftResult {
86
88
  export async function generateHandoffDraft(
87
89
  ctx: ExtensionContext,
88
90
  goal: string,
91
+ thinkingLevel: ThinkingLevel | undefined,
89
92
  signal?: AbortSignal,
90
93
  ): Promise<HandoffDraftResult | undefined> {
91
94
  if (!ctx.model) {
@@ -117,25 +120,23 @@ export async function generateHandoffDraft(
117
120
  timestamp: Date.now(),
118
121
  };
119
122
 
120
- const response = await complete(
123
+ const requestOptions: SimpleStreamOptions = {
124
+ apiKey: auth.apiKey,
125
+ ...(auth.headers ? { headers: auth.headers } : {}),
126
+ ...(signal ? { signal } : {}),
127
+ ...(ctx.model.reasoning && thinkingLevel && thinkingLevel !== "off"
128
+ ? { reasoning: thinkingLevel }
129
+ : {}),
130
+ };
131
+
132
+ const response = await completeSimple(
121
133
  ctx.model,
122
134
  {
123
135
  systemPrompt: HANDOFF_SYSTEM_PROMPT,
124
136
  messages: [userMessage],
125
137
  tools: [HANDOFF_EXTRACTION_TOOL],
126
138
  },
127
- signal
128
- ? {
129
- apiKey: auth.apiKey,
130
- ...(auth.headers ? { headers: auth.headers } : {}),
131
- signal,
132
- toolChoice: "any",
133
- }
134
- : {
135
- apiKey: auth.apiKey,
136
- ...(auth.headers ? { headers: auth.headers } : {}),
137
- toolChoice: "any",
138
- },
139
+ requestOptions,
139
140
  );
140
141
 
141
142
  if (response.stopReason === "aborted") {
@@ -77,7 +77,8 @@ export default function sessionHandoffExtension(pi: ExtensionAPI): void {
77
77
  generatedDraft = await runWithLoader(
78
78
  ctx,
79
79
  "Generating handoff draft...",
80
- async (signal: AbortSignal) => generateHandoffDraft(ctx, parsedArgs.goal, signal),
80
+ async (signal: AbortSignal) =>
81
+ generateHandoffDraft(ctx, parsedArgs.goal, pi.getThinkingLevel(), signal),
81
82
  );
82
83
  } catch (error) {
83
84
  ctx.ui.notify(formatHandoffError(error), "error");
@@ -1,4 +1,4 @@
1
- import { renameSync } from "node:fs";
1
+ import { renameSync, rmSync } from "node:fs";
2
2
  import { SessionManager } from "@earendil-works/pi-coding-agent";
3
3
  import {
4
4
  createTempIndexPath,
@@ -34,11 +34,16 @@ export async function rebuildSessionIndex(options: ReindexOptions): Promise<Rein
34
34
  try {
35
35
  initializeSchema(db);
36
36
  ({ sessionCount, chunkCount } = indexSessionFiles(db, sessionFiles));
37
+ // Fold the WAL into the main file so the single-file rename below is
38
+ // self-contained. bun:sqlite does not checkpoint on close.
39
+ db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
37
40
  } finally {
38
41
  db.close();
39
42
  }
40
43
 
41
44
  renameSync(tempIndexPath, finalIndexPath);
45
+ rmSync(`${tempIndexPath}-wal`, { force: true });
46
+ rmSync(`${tempIndexPath}-shm`, { force: true });
42
47
  return { sessionCount, chunkCount, indexPath: finalIndexPath };
43
48
  }
44
49
 
@@ -1,7 +1,7 @@
1
- import type Database from "better-sqlite3";
2
1
  import { Type } from "typebox";
3
2
  import type { FileTouchOp, FileTouchSource, PathScope } from "../../session-search/normalize.js";
4
3
  import { safeParseTypeBoxJson } from "../typebox.js";
4
+ import type { SqliteDatabase } from "./sqlite.js";
5
5
 
6
6
  export const INDEX_SCHEMA_VERSION = 7;
7
7
 
@@ -119,7 +119,7 @@ export interface SessionIndexStatus {
119
119
  lastFullReindexAt?: string | undefined;
120
120
  }
121
121
 
122
- export type SessionIndexDatabase = Database.Database;
122
+ export type SessionIndexDatabase = SqliteDatabase;
123
123
 
124
124
  export const NULLABLE_STRING_SCHEMA = Type.Union([Type.String(), Type.Null()]);
125
125
  export const SESSION_ORIGIN_SCHEMA = Type.Union([
@@ -113,7 +113,7 @@ export function getSessionById(
113
113
  )
114
114
  .get(sessionId);
115
115
 
116
- if (row === undefined) {
116
+ if (row === undefined || row === null) {
117
117
  return undefined;
118
118
  }
119
119
 
@@ -140,7 +140,7 @@ export function getSessionByPath(
140
140
  )
141
141
  .get(sessionPath);
142
142
 
143
- if (row === undefined) {
143
+ if (row === undefined || row === null) {
144
144
  return undefined;
145
145
  }
146
146
 
@@ -1,6 +1,5 @@
1
1
  import { existsSync, mkdirSync } from "node:fs";
2
2
  import path from "node:path";
3
- import Database from "better-sqlite3";
4
3
  import { parseTypeBoxValue } from "../typebox.js";
5
4
  import {
6
5
  INDEX_SCHEMA_VERSION,
@@ -9,6 +8,7 @@ import {
9
8
  type SessionIndexDatabase,
10
9
  type SessionIndexStatus,
11
10
  } from "./common.js";
11
+ import { openSqlite } from "./sqlite.js";
12
12
 
13
13
  export function ensureIndexDir(dir: string): string {
14
14
  if (!existsSync(dir)) {
@@ -29,16 +29,7 @@ export function openIndexDatabase(
29
29
  options?: { create?: boolean; timeoutMs?: number },
30
30
  ): SessionIndexDatabase {
31
31
  const create = options?.create ?? true;
32
- const db = new Database(
33
- dbPath,
34
- options?.timeoutMs === undefined
35
- ? { fileMustExist: !create }
36
- : { fileMustExist: !create, timeout: options.timeoutMs },
37
- );
38
- db.pragma("journal_mode = WAL");
39
- db.pragma("synchronous = NORMAL");
40
- db.pragma("foreign_keys = ON");
41
- return db;
32
+ return openSqlite(dbPath, { create, timeoutMs: options?.timeoutMs });
42
33
  }
43
34
 
44
35
  export function initializeSchema(db: SessionIndexDatabase): void {
@@ -148,7 +139,7 @@ export function setMetadata(db: SessionIndexDatabase, key: string, value: string
148
139
 
149
140
  export function getMetadata(db: SessionIndexDatabase, key: string): string | undefined {
150
141
  const row = db.prepare(`SELECT value FROM metadata WHERE key = ?`).get(key);
151
- if (row === undefined) {
142
+ if (row === undefined || row === null) {
152
143
  return undefined;
153
144
  }
154
145
 
@@ -0,0 +1,65 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ const requireModule = createRequire(import.meta.url);
4
+ const isBun = Boolean(process.versions.bun);
5
+
6
+ export type SqliteBindValue = string | number | bigint | null;
7
+
8
+ export interface SqliteRunResult {
9
+ changes: number;
10
+ lastInsertRowid: number | bigint;
11
+ }
12
+
13
+ export interface SqliteStatement {
14
+ get(...params: SqliteBindValue[]): unknown;
15
+ all(...params: SqliteBindValue[]): unknown[];
16
+ run(...params: SqliteBindValue[]): SqliteRunResult;
17
+ }
18
+
19
+ export interface SqliteDatabase {
20
+ readonly inTransaction: boolean;
21
+ prepare(sql: string): SqliteStatement;
22
+ exec(sql: string): void;
23
+ transaction<Args extends unknown[], Result>(
24
+ fn: (...args: Args) => Result,
25
+ ): (...args: Args) => Result;
26
+ close(): void;
27
+ }
28
+
29
+ interface SqliteConstructor {
30
+ new (path: string, options?: Record<string, unknown>): SqliteDatabase;
31
+ }
32
+
33
+ // The driver is resolved at runtime so Bun never loads the native better-sqlite3
34
+ // addon (unsupported) and Node never loads the bun:sqlite builtin. The require
35
+ // result is untyped, so the cast to our structural contract is unavoidable here.
36
+ function loadDatabaseConstructor(): SqliteConstructor {
37
+ if (isBun) {
38
+ return (requireModule("bun:sqlite") as { Database: SqliteConstructor }).Database;
39
+ }
40
+ return requireModule("better-sqlite3") as SqliteConstructor;
41
+ }
42
+
43
+ export function openSqlite(
44
+ dbPath: string,
45
+ options: { create: boolean; timeoutMs?: number | undefined },
46
+ ): SqliteDatabase {
47
+ const Database = loadDatabaseConstructor();
48
+ const db = isBun
49
+ ? new Database(dbPath, { create: options.create, readwrite: true })
50
+ : new Database(
51
+ dbPath,
52
+ options.timeoutMs === undefined
53
+ ? { fileMustExist: !options.create }
54
+ : { fileMustExist: !options.create, timeout: options.timeoutMs },
55
+ );
56
+
57
+ db.exec("PRAGMA journal_mode = WAL");
58
+ db.exec("PRAGMA synchronous = NORMAL");
59
+ db.exec("PRAGMA foreign_keys = ON");
60
+ if (isBun && options.timeoutMs !== undefined) {
61
+ db.exec(`PRAGMA busy_timeout = ${options.timeoutMs}`);
62
+ }
63
+
64
+ return db;
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-sessions",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "description": "Pi session search, ask, handoff, auto-titling, and indexing tools",
6
6
  "license": "MIT",
@@ -38,7 +38,7 @@
38
38
  "typecheck": "tsc --noEmit",
39
39
  "test": "vitest run",
40
40
  "check": "biome check . && tsc --noEmit && vitest run",
41
- "prepare": "husky"
41
+ "prepare": "husky || true"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@biomejs/biome": "^2.4.14",
@@ -46,7 +46,6 @@
46
46
  "@earendil-works/pi-ai": "^0.78.1",
47
47
  "@earendil-works/pi-coding-agent": "^0.78.1",
48
48
  "@earendil-works/pi-tui": "^0.78.1",
49
- "@types/better-sqlite3": "^7.6.13",
50
49
  "@types/node": "^25.6.2",
51
50
  "husky": "^9.1.7",
52
51
  "lint-staged": "^17.0.3",
@@ -64,7 +63,7 @@
64
63
  "lint-staged": {
65
64
  "*.{js,cjs,mjs,jsx,ts,tsx,json,jsonc}": "biome check --write --no-errors-on-unmatched"
66
65
  },
67
- "dependencies": {
66
+ "optionalDependencies": {
68
67
  "better-sqlite3": "^12.9.0"
69
68
  }
70
69
  }