pi-sessions 0.2.0 → 0.2.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,7 @@
1
- import { complete, type Message } from "@mariozechner/pi-ai";
2
- import type { ExtensionAPI, Theme } from "@mariozechner/pi-coding-agent";
3
- import { Text } from "@mariozechner/pi-tui";
4
- import { Type } from "@sinclair/typebox";
1
+ import { complete, type Message } from "@earendil-works/pi-ai";
2
+ import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
3
+ import { Text } from "@earendil-works/pi-tui";
4
+ import { Type } from "typebox";
5
5
  import { type RenderedSessionTree, renderSessionTreeMarkdown } from "./session-search/extract.js";
6
6
  import {
7
7
  getIndexStatus,
@@ -1,5 +1,5 @@
1
- import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
- import type { AutocompleteItem } from "@mariozechner/pi-tui";
1
+ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
+ import type { AutocompleteItem } from "@earendil-works/pi-tui";
3
3
 
4
4
  export const TITLE_USAGE = "Usage: /title [this|folder|pi] [-f]";
5
5
 
@@ -3,7 +3,7 @@ import {
3
3
  convertToLlm,
4
4
  type SessionEntry,
5
5
  serializeConversation,
6
- } from "@mariozechner/pi-coding-agent";
6
+ } from "@earendil-works/pi-coding-agent";
7
7
 
8
8
  export interface AutoTitleContext {
9
9
  cwd: string | undefined;
@@ -1,4 +1,4 @@
1
- import { buildSessionContext, type ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import { buildSessionContext, type ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { AutoTitleSettings } from "../shared/settings.js";
3
3
  import {
4
4
  type AutoTitleMode,
@@ -1,5 +1,5 @@
1
- import type { Api, Model } from "@mariozechner/pi-ai";
2
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  import { ModelReference } from "../shared/settings.js";
4
4
 
5
5
  const DEFAULT_AUTO_TITLE_FALLBACK_MODELS: readonly ModelReference[] = [
@@ -4,14 +4,14 @@ import {
4
4
  type Model,
5
5
  type TextContent,
6
6
  type UserMessage,
7
- } from "@mariozechner/pi-ai";
7
+ } from "@earendil-works/pi-ai";
8
8
  import type {
9
9
  ExtensionAPI,
10
10
  ExtensionCommandContext,
11
11
  ExtensionContext,
12
12
  SessionInfo,
13
- } from "@mariozechner/pi-coding-agent";
14
- import { SessionManager } from "@mariozechner/pi-coding-agent";
13
+ } from "@earendil-works/pi-coding-agent";
14
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
15
15
  import type { RetitleMode, RetitleScope } from "./command.js";
16
16
  import { type AutoTitleContext, buildAutoTitleContext } from "./context.js";
17
17
  import type {
@@ -1,5 +1,5 @@
1
- import type { SessionEntry } from "@mariozechner/pi-coding-agent";
2
- import { type Static, Type } from "@sinclair/typebox";
1
+ import type { SessionEntry } from "@earendil-works/pi-coding-agent";
2
+ import { type Static, Type } from "typebox";
3
3
  import { safeParseTypeBoxValue } from "../shared/typebox.js";
4
4
 
5
5
  export const AUTO_TITLE_STATE_CUSTOM_TYPE = "pi-sessions.auto-title";
@@ -1,12 +1,12 @@
1
- import type { Api, Model } from "@mariozechner/pi-ai";
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
2
  import {
3
3
  copyToClipboard,
4
4
  type ExtensionAPI,
5
5
  type ExtensionCommandContext,
6
6
  type Theme,
7
- } from "@mariozechner/pi-coding-agent";
8
- import type { Focusable, TUI } from "@mariozechner/pi-tui";
9
- import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
7
+ } from "@earendil-works/pi-coding-agent";
8
+ import type { Focusable, TUI } from "@earendil-works/pi-tui";
9
+ import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
10
10
  import type { RetitleCommandOutcome, RetitleMode, RetitleScope } from "./command.js";
11
11
  import {
12
12
  type AutoRetitleStatus,
@@ -1,11 +1,11 @@
1
- import type { Api, Model } from "@mariozechner/pi-ai";
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
2
  import type {
3
3
  ExtensionAPI,
4
4
  ExtensionCommandContext,
5
5
  ExtensionContext,
6
6
  SessionStartEvent,
7
7
  TurnEndEvent,
8
- } from "@mariozechner/pi-coding-agent";
8
+ } from "@earendil-works/pi-coding-agent";
9
9
  import {
10
10
  createSessionAutoTitleCommandHandler,
11
11
  getRetitleArgumentCompletions,
@@ -6,14 +6,14 @@ import {
6
6
  type ThinkingContent,
7
7
  type Tool,
8
8
  type ToolCall,
9
- } from "@mariozechner/pi-ai";
10
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
9
+ } from "@earendil-works/pi-ai";
10
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
11
11
  import {
12
12
  buildSessionContext,
13
13
  convertToLlm,
14
14
  serializeConversation,
15
- } from "@mariozechner/pi-coding-agent";
16
- import { type Static, Type } from "@sinclair/typebox";
15
+ } from "@earendil-works/pi-coding-agent";
16
+ import { type Static, Type } from "typebox";
17
17
  import { parseTypeBoxValue } from "../shared/typebox.js";
18
18
 
19
19
  const MAX_RELEVANT_FILES = 12;
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from "node:buffer";
2
- import type { CustomEntry, SessionEntry } from "@mariozechner/pi-coding-agent";
3
- import { type Static, Type } from "@sinclair/typebox";
2
+ import type { CustomEntry, SessionEntry } from "@earendil-works/pi-coding-agent";
3
+ import { type Static, Type } from "typebox";
4
4
  import { safeParseTypeBoxValue } from "../shared/typebox.js";
5
5
 
6
6
  export const HANDOFF_METADATA_CUSTOM_TYPE = "pi-sessions.handoff";
@@ -1,4 +1,4 @@
1
- import type { ExtensionContext, KeybindingsManager, Theme } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionContext, KeybindingsManager, Theme } from "@earendil-works/pi-coding-agent";
2
2
  import {
3
3
  type Focusable,
4
4
  Input,
@@ -7,7 +7,7 @@ import {
7
7
  type TUI,
8
8
  truncateToWidth,
9
9
  visibleWidth,
10
- } from "@mariozechner/pi-tui";
10
+ } from "@earendil-works/pi-tui";
11
11
  import {
12
12
  stripSearchSnippetMarkers,
13
13
  transformSearchSnippetMatches,
@@ -2,15 +2,15 @@ import type {
2
2
  ExtensionCommandContext,
3
3
  ExtensionUIContext,
4
4
  Theme,
5
- } from "@mariozechner/pi-coding-agent";
6
- import type { Component } from "@mariozechner/pi-tui";
5
+ } from "@earendil-works/pi-coding-agent";
6
+ import type { Component } from "@earendil-works/pi-tui";
7
7
  import {
8
8
  Key,
9
9
  matchesKey,
10
10
  truncateToWidth,
11
11
  visibleWidth,
12
12
  wrapTextWithAnsi,
13
- } from "@mariozechner/pi-tui";
13
+ } from "@earendil-works/pi-tui";
14
14
 
15
15
  const PREVIEW_TIMEOUT_MS = 8_000;
16
16
  const PREVIEW_BODY_LINE_LIMIT = 16;
@@ -6,7 +6,7 @@ import {
6
6
  type ExtensionAPI,
7
7
  type ExtensionCommandContext,
8
8
  type SessionHeader,
9
- } from "@mariozechner/pi-coding-agent";
9
+ } from "@earendil-works/pi-coding-agent";
10
10
  import { HANDOFF_BOOTSTRAP_ENV } from "./metadata.js";
11
11
 
12
12
  const GHOSTTY_MACOS_ONLY_MESSAGE = "Split handoff currently supports Ghostty on macOS only.";
@@ -1,6 +1,6 @@
1
- import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
- import { buildSessionContext } from "@mariozechner/pi-coding-agent";
3
- import { Key, matchesKey } from "@mariozechner/pi-tui";
1
+ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
+ import { buildSessionContext } from "@earendil-works/pi-coding-agent";
3
+ import { Key, matchesKey } from "@earendil-works/pi-tui";
4
4
  import { generateHandoffDraft, type HandoffDraftResult } from "./session-handoff/extract.js";
5
5
  import {
6
6
  createHandoffBootstrap,
@@ -130,22 +130,16 @@ export default function sessionHandoffExtension(pi: ExtensionAPI): void {
130
130
  return;
131
131
  }
132
132
 
133
- const previousBootstrapValue = process.env[HANDOFF_BOOTSTRAP_ENV];
134
- process.env[HANDOFF_BOOTSTRAP_ENV] = bootstrapValue;
135
-
136
- let switchResult: { cancelled: boolean };
137
- try {
138
- switchResult = await ctx.switchSession(createdSession.sessionFile);
139
- } finally {
140
- restoreProcessEnv(HANDOFF_BOOTSTRAP_ENV, previousBootstrapValue);
141
- }
133
+ const switchResult = await ctx.switchSession(createdSession.sessionFile, {
134
+ withSession: async (nextCtx) => {
135
+ await nextCtx.sendUserMessage(approvedDraft);
136
+ nextCtx.ui.notify("Handoff started in a new session.", "info");
137
+ },
138
+ });
142
139
 
143
140
  if (switchResult.cancelled) {
144
141
  ctx.ui.notify("Session switch cancelled", "info");
145
- return;
146
142
  }
147
-
148
- ctx.ui.notify("Handoff started in a new session.", "info");
149
143
  },
150
144
  });
151
145
 
@@ -332,12 +326,3 @@ function parseHandoffCommandArgs(
332
326
  splitDirection,
333
327
  };
334
328
  }
335
-
336
- function restoreProcessEnv(key: string, previousValue: string | undefined): void {
337
- if (previousValue === undefined) {
338
- delete process.env[key];
339
- return;
340
- }
341
-
342
- process.env[key] = previousValue;
343
- }
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { HANDOFF_BOOTSTRAP_ENV, parseHandoffBootstrap } from "./session-handoff/metadata.js";
3
3
  import { createSessionHookController } from "./session-search/hooks.js";
4
4
  import { loadSettings } from "./shared/settings.js";
@@ -3,8 +3,8 @@ import {
3
3
  type ExtensionAPI,
4
4
  type ExtensionCommandContext,
5
5
  type Theme,
6
- } from "@mariozechner/pi-coding-agent";
7
- import { type Focusable, matchesKey, visibleWidth } from "@mariozechner/pi-tui";
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import { type Focusable, matchesKey, visibleWidth } from "@earendil-works/pi-tui";
8
8
  import { type ReindexResult, rebuildSessionIndex } from "./session-search/reindex.js";
9
9
  import { getIndexStatus, type SessionIndexStatus } from "./shared/session-index/index.js";
10
10
  import { loadSettings } from "./shared/settings.js";
@@ -1,15 +1,15 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
- import type { AgentMessage } from "@mariozechner/pi-agent-core";
4
- import type { AssistantMessage, ToolCall, ToolResultMessage } from "@mariozechner/pi-ai";
3
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
4
+ import type { AssistantMessage, ToolCall, ToolResultMessage } from "@earendil-works/pi-ai";
5
5
  import {
6
6
  type CustomEntry,
7
7
  parseSessionEntries,
8
8
  type SessionEntry,
9
9
  type SessionHeader,
10
10
  type SessionMessageEntry,
11
- } from "@mariozechner/pi-coding-agent";
12
- import { Type } from "@sinclair/typebox";
11
+ } from "@earendil-works/pi-coding-agent";
12
+ import { Type } from "typebox";
13
13
  import {
14
14
  HANDOFF_METADATA_CUSTOM_TYPE,
15
15
  type HandoffSessionMetadata,
@@ -1,10 +1,10 @@
1
1
  import { existsSync } from "node:fs";
2
- import type { ImageContent, TextContent } from "@mariozechner/pi-ai";
2
+ import type { ImageContent, TextContent } from "@earendil-works/pi-ai";
3
3
  import {
4
4
  isToolCallEventType,
5
5
  type ToolCallEvent,
6
6
  type ToolResultEvent,
7
- } from "@mariozechner/pi-coding-agent";
7
+ } from "@earendil-works/pi-coding-agent";
8
8
  import {
9
9
  clearSessionIndexedData,
10
10
  getIndexStatus,
@@ -22,6 +22,9 @@ import {
22
22
  import { type ExtractedSessionRecord, extractSessionRecord } from "./extract.js";
23
23
 
24
24
  const TOOL_RESULT_TEXT_LIMIT = 500;
25
+ const HOOK_INDEX_WRITE_TIMEOUT_MS = 500;
26
+ const HOOK_INDEX_WRITE_MAX_ATTEMPTS = 4;
27
+ const HOOK_INDEX_WRITE_RETRY_DELAYS_MS = [150, 400, 1000];
25
28
 
26
29
  type TrackedToolName = "read" | "edit" | "write";
27
30
 
@@ -96,15 +99,24 @@ export function createSessionHookController(options: { indexPath: string }): Ses
96
99
  return syncAttachedSession(indexPath, state, "session_start");
97
100
  },
98
101
  async handleSessionSwitch(previousSessionFile, sessionFile, cwd, sessionOrigin) {
99
- const previousSynced = syncSessionFile(indexPath, previousSessionFile, "session_switch");
102
+ const previousSynced = await syncSessionFile(
103
+ indexPath,
104
+ previousSessionFile,
105
+ "session_switch",
106
+ );
100
107
  attachSession(state, sessionFile, cwd);
101
- const currentSynced = syncAttachedSession(indexPath, state, "session_switch", sessionOrigin);
108
+ const currentSynced = await syncAttachedSession(
109
+ indexPath,
110
+ state,
111
+ "session_switch",
112
+ sessionOrigin,
113
+ );
102
114
  return previousSynced || currentSynced;
103
115
  },
104
116
  async handleSessionFork(previousSessionFile, sessionFile, cwd) {
105
- const previousSynced = syncSessionFile(indexPath, previousSessionFile, "session_fork");
117
+ const previousSynced = await syncSessionFile(indexPath, previousSessionFile, "session_fork");
106
118
  attachSession(state, sessionFile, cwd);
107
- const currentSynced = syncAttachedSession(indexPath, state, "session_fork", "fork");
119
+ const currentSynced = await syncAttachedSession(indexPath, state, "session_fork", "fork");
108
120
  return previousSynced || currentSynced;
109
121
  },
110
122
  handleToolCall(event, sessionFile, cwd) {
@@ -131,9 +143,11 @@ export function createSessionHookController(options: { indexPath: string }): Ses
131
143
  },
132
144
  async handleTurnEnd(sessionFile, cwd) {
133
145
  attachSession(state, sessionFile, cwd);
134
- const synced = syncAttachedSession(indexPath, state, "turn_end");
135
- clearTurnState(state);
136
- return synced;
146
+ try {
147
+ return await syncAttachedSession(indexPath, state, "turn_end");
148
+ } finally {
149
+ clearTurnState(state);
150
+ }
137
151
  },
138
152
  async handleSessionTree(sessionFile, cwd) {
139
153
  attachSession(state, sessionFile, cwd);
@@ -145,11 +159,13 @@ export function createSessionHookController(options: { indexPath: string }): Ses
145
159
  },
146
160
  async handleSessionShutdown(sessionFile, cwd) {
147
161
  attachSession(state, sessionFile, cwd);
148
- const synced = syncAttachedSession(indexPath, state, "session_shutdown");
149
- clearTurnState(state);
150
- state.currentSessionFile = undefined;
151
- state.currentCwd = undefined;
152
- return synced;
162
+ try {
163
+ return await syncAttachedSession(indexPath, state, "session_shutdown");
164
+ } finally {
165
+ clearTurnState(state);
166
+ state.currentSessionFile = undefined;
167
+ state.currentCwd = undefined;
168
+ }
153
169
  },
154
170
  };
155
171
  }
@@ -166,22 +182,66 @@ function attachSession(
166
182
  state.currentCwd = cwd;
167
183
  }
168
184
 
185
+ async function retryIndexWrite<T>(action: () => T): Promise<T> {
186
+ for (let attempt = 1; attempt <= HOOK_INDEX_WRITE_MAX_ATTEMPTS; attempt += 1) {
187
+ try {
188
+ return action();
189
+ } catch (error) {
190
+ if (!isRetryableSqliteLock(error) || attempt >= HOOK_INDEX_WRITE_MAX_ATTEMPTS) {
191
+ throw error;
192
+ }
193
+
194
+ await sleep(getRetryDelayMs(attempt));
195
+ }
196
+ }
197
+
198
+ return action();
199
+ }
200
+
201
+ function getRetryDelayMs(attempt: number): number {
202
+ return (
203
+ HOOK_INDEX_WRITE_RETRY_DELAYS_MS[attempt - 1] ??
204
+ HOOK_INDEX_WRITE_RETRY_DELAYS_MS[HOOK_INDEX_WRITE_RETRY_DELAYS_MS.length - 1] ??
205
+ 0
206
+ );
207
+ }
208
+
209
+ function isRetryableSqliteLock(error: unknown): boolean {
210
+ if (!(error instanceof Error)) {
211
+ return false;
212
+ }
213
+
214
+ const code = "code" in error && typeof error.code === "string" ? error.code : undefined;
215
+ const message = error.message.toLowerCase();
216
+ return (
217
+ code === "SQLITE_BUSY" ||
218
+ code === "SQLITE_LOCKED" ||
219
+ code?.startsWith("SQLITE_BUSY_") === true ||
220
+ code?.startsWith("SQLITE_LOCKED_") === true ||
221
+ message.includes("database is locked")
222
+ );
223
+ }
224
+
225
+ function sleep(ms: number): Promise<void> {
226
+ return new Promise((resolve) => setTimeout(resolve, ms));
227
+ }
228
+
169
229
  function syncAttachedSession(
170
230
  indexPath: string,
171
231
  state: SessionHookState,
172
232
  eventType: string,
173
233
  sessionOrigin?: SessionOrigin,
174
- ): boolean {
234
+ ): Promise<boolean> {
175
235
  return syncSessionFile(indexPath, state.currentSessionFile, eventType, state, sessionOrigin);
176
236
  }
177
237
 
178
- function syncSessionFile(
238
+ async function syncSessionFile(
179
239
  indexPath: string,
180
240
  sessionFile: string | undefined,
181
241
  eventType: string,
182
242
  state?: SessionHookState,
183
243
  sessionOrigin?: SessionOrigin,
184
- ): boolean {
244
+ ): Promise<boolean> {
185
245
  if (!sessionFile || !existsSync(sessionFile)) {
186
246
  return false;
187
247
  }
@@ -196,31 +256,36 @@ function syncSessionFile(
196
256
  return false;
197
257
  }
198
258
 
199
- const db = openIndexDatabase(indexPath, { create: false });
200
- try {
201
- db.transaction(() => {
202
- const existingSession = getSessionById(db, extracted.sessionId);
203
- const sessionRow = mergeSessionLineage(extracted, existingSession, sessionOrigin);
204
- upsertSession(db, sessionRow, "hook");
205
- if (shouldRefreshLineageRelations(existingSession, sessionRow)) {
206
- rebuildSessionLineageRelations(db);
207
- }
208
- clearSessionIndexedData(db, extracted.sessionId);
209
-
210
- for (const chunk of extracted.chunks) {
211
- insertTextChunk(db, { sessionId: extracted.sessionId, ...chunk });
212
- }
213
-
214
- for (const fileTouch of extracted.fileTouches) {
215
- insertSessionFileTouch(db, { sessionId: extracted.sessionId, ...fileTouch });
216
- }
217
-
218
- setMetadata(db, "hook_updated_at", new Date().toISOString());
219
- setMetadata(db, "hook_last_event", eventType);
220
- })();
221
- } finally {
222
- db.close();
223
- }
259
+ await retryIndexWrite(() => {
260
+ const db = openIndexDatabase(indexPath, {
261
+ create: false,
262
+ timeoutMs: HOOK_INDEX_WRITE_TIMEOUT_MS,
263
+ });
264
+ try {
265
+ db.transaction(() => {
266
+ const existingSession = getSessionById(db, extracted.sessionId);
267
+ const sessionRow = mergeSessionLineage(extracted, existingSession, sessionOrigin);
268
+ upsertSession(db, sessionRow, "hook");
269
+ if (shouldRefreshLineageRelations(existingSession, sessionRow)) {
270
+ rebuildSessionLineageRelations(db);
271
+ }
272
+ clearSessionIndexedData(db, extracted.sessionId);
273
+
274
+ for (const chunk of extracted.chunks) {
275
+ insertTextChunk(db, { sessionId: extracted.sessionId, ...chunk });
276
+ }
277
+
278
+ for (const fileTouch of extracted.fileTouches) {
279
+ insertSessionFileTouch(db, { sessionId: extracted.sessionId, ...fileTouch });
280
+ }
281
+
282
+ setMetadata(db, "hook_updated_at", new Date().toISOString());
283
+ setMetadata(db, "hook_last_event", eventType);
284
+ })();
285
+ } finally {
286
+ db.close();
287
+ }
288
+ });
224
289
 
225
290
  if (state) {
226
291
  state.lastFlushedSessionFile = sessionFile;
@@ -1,5 +1,5 @@
1
1
  import { renameSync } from "node:fs";
2
- import { SessionManager } from "@mariozechner/pi-coding-agent";
2
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
3
3
  import {
4
4
  createTempIndexPath,
5
5
  initializeSchema,
@@ -1,7 +1,7 @@
1
1
  import path from "node:path";
2
- import type { ExtensionAPI, ExtensionContext, Theme } from "@mariozechner/pi-coding-agent";
3
- import { Text } from "@mariozechner/pi-tui";
4
- import { Type } from "@sinclair/typebox";
2
+ import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
3
+ import { Text } from "@earendil-works/pi-tui";
4
+ import { Type } from "typebox";
5
5
  import {
6
6
  stripSearchSnippetMarkers,
7
7
  transformSearchSnippetMatches,
@@ -1,5 +1,5 @@
1
- import { Type } from "@sinclair/typebox";
2
1
  import type Database from "better-sqlite3";
2
+ import { Type } from "typebox";
3
3
  import type { FileTouchOp, FileTouchSource, PathScope } from "../../session-search/normalize.js";
4
4
  import { safeParseTypeBoxJson } from "../typebox.js";
5
5
 
@@ -1,4 +1,4 @@
1
- import { type Static, Type } from "@sinclair/typebox";
1
+ import { type Static, Type } from "typebox";
2
2
  import { parseTypeBoxRows, parseTypeBoxValue } from "../typebox.js";
3
3
  import {
4
4
  NULLABLE_STRING_SCHEMA,
@@ -26,10 +26,15 @@ export function createTempIndexPath(finalPath: string): string {
26
26
 
27
27
  export function openIndexDatabase(
28
28
  dbPath: string,
29
- options?: { create?: boolean },
29
+ options?: { create?: boolean; timeoutMs?: number },
30
30
  ): SessionIndexDatabase {
31
31
  const create = options?.create ?? true;
32
- const db = new Database(dbPath, { fileMustExist: !create });
32
+ const db = new Database(
33
+ dbPath,
34
+ options?.timeoutMs === undefined
35
+ ? { fileMustExist: !create }
36
+ : { fileMustExist: !create, timeout: options.timeoutMs },
37
+ );
33
38
  db.pragma("journal_mode = WAL");
34
39
  db.pragma("synchronous = NORMAL");
35
40
  db.pragma("foreign_keys = ON");
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { type Static, Type } from "@sinclair/typebox";
2
+ import { type Static, Type } from "typebox";
3
3
  import {
4
4
  type FileTouchOp,
5
5
  matchesRepoRoot,
@@ -1,8 +1,8 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
- import { getAgentDir, SettingsManager } from "@mariozechner/pi-coding-agent";
4
- import type { KeyId } from "@mariozechner/pi-tui";
5
- import { type Static, Type } from "@sinclair/typebox";
3
+ import { SettingsManager } from "@earendil-works/pi-coding-agent";
4
+ import type { KeyId } from "@earendil-works/pi-tui";
5
+ import { type Static, Type } from "typebox";
6
6
  import { parseTypeBoxValue } from "./typebox.js";
7
7
 
8
8
  export const DEFAULT_AUTO_TITLE_REFRESH_TURNS = 4;
@@ -114,7 +114,7 @@ function parseModelReference(value: string | undefined): ModelReference | undefi
114
114
  }
115
115
 
116
116
  function loadSessionFileSettings(): SessionFileSettings {
117
- const globalSettings = SettingsManager.create(undefined, getAgentDir()).getGlobalSettings();
117
+ const globalSettings = SettingsManager.create(process.cwd()).getGlobalSettings();
118
118
  const parsed = parseTypeBoxValue(ROOT_SETTINGS_SCHEMA, globalSettings, "Invalid settings");
119
119
  return parsed.sessions ?? {};
120
120
  }
@@ -1,5 +1,5 @@
1
- import type { Static, TSchema } from "@sinclair/typebox";
2
- import { Value } from "@sinclair/typebox/value";
1
+ import type { Static, TSchema } from "typebox";
2
+ import { Value } from "typebox/value";
3
3
 
4
4
  export function isTypeBoxValue<T extends TSchema>(schema: T, value: unknown): value is Static<T> {
5
5
  return Value.Check(schema, value);
@@ -51,11 +51,11 @@ export function safeParseTypeBoxJson<T extends TSchema>(
51
51
  }
52
52
 
53
53
  function formatTypeBoxError(schema: TSchema, value: unknown, context: string): string {
54
- const firstError = Value.Errors(schema, value).First();
54
+ const firstError = Value.Errors(schema, value)[0];
55
55
  if (!firstError) {
56
56
  return `${context}: invalid value.`;
57
57
  }
58
58
 
59
- const path = firstError.path.length > 0 ? firstError.path : "/";
59
+ const path = firstError.instancePath.length > 0 ? firstError.instancePath : "/";
60
60
  return `${context}: ${path} ${firstError.message}`;
61
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-sessions",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "description": "Pi session search, ask, handoff, auto-titling, and indexing tools",
6
6
  "license": "MIT",
@@ -15,6 +15,9 @@
15
15
  "keywords": [
16
16
  "pi-package"
17
17
  ],
18
+ "engines": {
19
+ "node": ">=20 <26"
20
+ },
18
21
  "files": [
19
22
  "extensions",
20
23
  "images"
@@ -38,23 +41,25 @@
38
41
  "prepare": "husky"
39
42
  },
40
43
  "devDependencies": {
41
- "@biomejs/biome": "^2.4.12",
42
- "@mariozechner/pi-ai": "^0.67.68",
43
- "@mariozechner/pi-coding-agent": "^0.67.68",
44
- "@mariozechner/pi-tui": "^0.67.68",
45
- "@sinclair/typebox": "^0.34.49",
44
+ "@biomejs/biome": "^2.4.14",
45
+ "@earendil-works/pi-agent-core": "^0.74.0",
46
+ "@earendil-works/pi-ai": "^0.74.0",
47
+ "@earendil-works/pi-coding-agent": "^0.74.0",
48
+ "@earendil-works/pi-tui": "^0.74.0",
46
49
  "@types/better-sqlite3": "^7.6.13",
47
- "@types/node": "^25.6.0",
50
+ "@types/node": "^25.6.2",
48
51
  "husky": "^9.1.7",
49
- "lint-staged": "^16.4.0",
52
+ "lint-staged": "^17.0.3",
53
+ "typebox": "^1.1.38",
50
54
  "typescript": "^6.0.3",
51
- "vitest": "^4.1.4"
55
+ "vitest": "^4.1.5"
52
56
  },
53
57
  "peerDependencies": {
54
- "@mariozechner/pi-ai": ">=0.67.68",
55
- "@mariozechner/pi-coding-agent": ">=0.67.68",
56
- "@mariozechner/pi-tui": ">=0.67.68",
57
- "@sinclair/typebox": ">=0.34.41"
58
+ "@earendil-works/pi-agent-core": ">=0.74.0",
59
+ "@earendil-works/pi-ai": ">=0.74.0",
60
+ "@earendil-works/pi-coding-agent": ">=0.74.0",
61
+ "@earendil-works/pi-tui": ">=0.74.0",
62
+ "typebox": ">=1.1.24"
58
63
  },
59
64
  "lint-staged": {
60
65
  "*.{js,cjs,mjs,jsx,ts,tsx,json,jsonc}": "biome check --write --no-errors-on-unmatched"