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.
- package/extensions/session-handoff/extract.ts +15 -14
- package/extensions/session-handoff.ts +2 -1
- package/extensions/session-search/reindex.ts +6 -1
- package/extensions/shared/session-index/common.ts +2 -2
- package/extensions/shared/session-index/lineage.ts +2 -2
- package/extensions/shared/session-index/schema.ts +3 -12
- package/extensions/shared/session-index/sqlite.ts +65 -0
- package/package.json +3 -4
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
1
2
|
import {
|
|
2
3
|
type AssistantMessage,
|
|
3
|
-
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
-
"
|
|
66
|
+
"optionalDependencies": {
|
|
68
67
|
"better-sqlite3": "^12.9.0"
|
|
69
68
|
}
|
|
70
69
|
}
|