pi-sessions 0.6.0 → 0.7.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 +22 -11
- package/extensions/session-ask/agent.ts +400 -0
- package/extensions/session-ask/navigate.ts +880 -0
- package/extensions/session-ask.ts +79 -131
- package/extensions/session-auto-title/model.ts +3 -11
- package/extensions/session-handoff/picker.ts +50 -4
- package/extensions/session-handoff/query.ts +74 -56
- package/extensions/session-handoff/refs.ts +12 -18
- package/extensions/session-handoff.ts +36 -27
- package/extensions/session-messaging/broker/process.ts +23 -26
- package/extensions/session-messaging/broker/spawn.ts +7 -2
- package/extensions/session-messaging/pi/incoming-runtime.ts +2 -18
- package/extensions/session-messaging/pi/message-contracts.ts +9 -10
- package/extensions/session-messaging/pi/message-view.ts +12 -1
- package/extensions/session-messaging/pi/service.ts +117 -62
- package/extensions/session-messaging/pi/tools.ts +4 -56
- package/extensions/session-search/extract.ts +86 -436
- package/extensions/session-search/hooks.ts +17 -25
- package/extensions/session-search/normalize.ts +1 -1
- package/extensions/session-search/reindex.ts +1 -0
- package/extensions/session-search.ts +157 -128
- package/extensions/shared/model.ts +18 -0
- package/extensions/shared/session-broker/active.ts +26 -0
- package/extensions/{session-messaging/pi → shared/session-broker}/client.ts +16 -19
- package/extensions/{session-messaging/shared → shared/session-broker}/framing.ts +1 -1
- package/extensions/{session-messaging/shared → shared/session-broker}/protocol.ts +5 -12
- package/extensions/shared/session-index/access.ts +128 -0
- package/extensions/shared/session-index/common.ts +43 -48
- package/extensions/shared/session-index/index.ts +1 -0
- package/extensions/shared/session-index/lineage.ts +10 -0
- package/extensions/shared/session-index/query/ast.ts +40 -0
- package/extensions/shared/session-index/query/compiler.ts +146 -0
- package/extensions/shared/session-index/query/lexer.ts +140 -0
- package/extensions/shared/session-index/query/parser.ts +178 -0
- package/extensions/shared/session-index/schema.ts +22 -6
- package/extensions/shared/session-index/scoring.ts +80 -0
- package/extensions/shared/session-index/search.ts +552 -278
- package/extensions/shared/session-index/sqlite.ts +16 -9
- package/extensions/shared/session-index/store.ts +12 -21
- package/extensions/shared/settings.ts +61 -4
- package/extensions/shared/text.ts +50 -0
- package/package.json +1 -1
- /package/extensions/{session-messaging/shared → shared/session-broker}/socket-path.ts +0 -0
|
@@ -48,22 +48,29 @@ function loadDatabaseConstructor(): SqliteConstructor {
|
|
|
48
48
|
|
|
49
49
|
export function openSqlite(
|
|
50
50
|
dbPath: string,
|
|
51
|
-
options: { create: boolean; timeoutMs?: number | undefined },
|
|
51
|
+
options: { create: boolean; readonly?: boolean | undefined; timeoutMs?: number | undefined },
|
|
52
52
|
): SqliteDatabase {
|
|
53
53
|
const Database = loadDatabaseConstructor();
|
|
54
|
+
const readonly = options.readonly ?? false;
|
|
54
55
|
const db = isBun
|
|
55
|
-
? new Database(dbPath, {
|
|
56
|
-
|
|
56
|
+
? new Database(dbPath, {
|
|
57
|
+
create: readonly ? false : options.create,
|
|
58
|
+
readonly,
|
|
59
|
+
readwrite: !readonly,
|
|
60
|
+
})
|
|
61
|
+
: new Database(dbPath, { fileMustExist: readonly || !options.create, readonly });
|
|
57
62
|
|
|
58
|
-
// busy_timeout must be set before any other statement: the journal_mode
|
|
59
|
-
// pragma below is the connection's first lock acquisition (and may run WAL
|
|
60
|
-
// recovery), and immediate transactions rely on this timeout to queue behind
|
|
61
|
-
// concurrent writers instead of failing.
|
|
62
63
|
db.exec(`PRAGMA busy_timeout = ${options.timeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS}`);
|
|
63
|
-
db.exec("PRAGMA journal_mode = WAL");
|
|
64
|
-
db.exec("PRAGMA synchronous = NORMAL");
|
|
65
64
|
db.exec("PRAGMA foreign_keys = ON");
|
|
66
65
|
|
|
66
|
+
if (!readonly) {
|
|
67
|
+
// journal_mode is the connection's first write-capable lock acquisition (and
|
|
68
|
+
// may run WAL recovery), and immediate transactions rely on busy_timeout to
|
|
69
|
+
// queue behind concurrent writers instead of failing.
|
|
70
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
71
|
+
db.exec("PRAGMA synchronous = NORMAL");
|
|
72
|
+
}
|
|
73
|
+
|
|
67
74
|
return withStatementCache(db);
|
|
68
75
|
}
|
|
69
76
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { type Static, Type } from "typebox";
|
|
2
3
|
import { parseTypeBoxValue } from "../typebox.ts";
|
|
3
4
|
import {
|
|
4
|
-
compactSessionId,
|
|
5
5
|
INDEX_SCHEMA_VERSION,
|
|
6
6
|
NULLABLE_STRING_SCHEMA,
|
|
7
7
|
parseRepoRoots,
|
|
@@ -76,7 +76,7 @@ export function insertSession(
|
|
|
76
76
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
77
77
|
`,
|
|
78
78
|
).run(...sessionRowBindings(row, indexSource));
|
|
79
|
-
|
|
79
|
+
syncSessionRepoRoots(db, row);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
export function upsertSession(
|
|
@@ -117,7 +117,7 @@ export function upsertSession(
|
|
|
117
117
|
index_source = excluded.index_source
|
|
118
118
|
`,
|
|
119
119
|
).run(...sessionRowBindings(row, indexSource));
|
|
120
|
-
|
|
120
|
+
syncSessionRepoRoots(db, row);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
export function getSessionRowByPath(
|
|
@@ -186,24 +186,15 @@ function buildSessionRow(row: Static<typeof SESSION_ROW_QUERY_SCHEMA>): SessionR
|
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
sessionId: row.sessionId,
|
|
192
|
-
entryType: "session_info",
|
|
193
|
-
ts: row.modifiedAt,
|
|
194
|
-
sourceKind: "session_id",
|
|
195
|
-
text: buildSessionIdSearchText(row.sessionId),
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function syncSessionIdChunk(db: SessionIndexDatabase, row: SessionRow): void {
|
|
200
|
-
clearSessionChunksBySourceKind(db, row.sessionId, "session_id");
|
|
201
|
-
insertSessionIdChunk(db, row);
|
|
202
|
-
}
|
|
189
|
+
function syncSessionRepoRoots(db: SessionIndexDatabase, row: SessionRow): void {
|
|
190
|
+
db.prepare(`DELETE FROM session_repo_roots WHERE session_id = ?`).run(row.sessionId);
|
|
203
191
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
192
|
+
const insert = db.prepare(
|
|
193
|
+
`INSERT INTO session_repo_roots(session_id, repo_root, repo_basename) VALUES (?, ?, ?)`,
|
|
194
|
+
);
|
|
195
|
+
for (const repoRoot of [...new Set(row.repoRoots)]) {
|
|
196
|
+
insert.run(row.sessionId, repoRoot, path.basename(repoRoot));
|
|
197
|
+
}
|
|
207
198
|
}
|
|
208
199
|
|
|
209
200
|
export function clearSessionChunksBySourceKind(
|
|
@@ -231,7 +222,7 @@ export function insertTextChunk(db: SessionIndexDatabase, row: SessionTextChunkR
|
|
|
231
222
|
`,
|
|
232
223
|
).run(
|
|
233
224
|
row.sessionId,
|
|
234
|
-
row.entryId
|
|
225
|
+
row.entryId,
|
|
235
226
|
row.entryType,
|
|
236
227
|
row.role ?? null,
|
|
237
228
|
row.ts,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
3
4
|
import { SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
4
5
|
import type { KeyId } from "@earendil-works/pi-tui";
|
|
5
6
|
import { type Static, Type } from "typebox";
|
|
@@ -22,9 +23,17 @@ const SESSION_FILE_SETTINGS_SCHEMA = Type.Object({
|
|
|
22
23
|
Type.Object({
|
|
23
24
|
refreshTurns: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
24
25
|
model: Type.Optional(Type.String()),
|
|
26
|
+
thinkingLevel: Type.Optional(Type.String()),
|
|
25
27
|
prompt: Type.Optional(Type.String()),
|
|
26
28
|
}),
|
|
27
29
|
),
|
|
30
|
+
ask: Type.Optional(
|
|
31
|
+
Type.Object({
|
|
32
|
+
model: Type.Optional(Type.String()),
|
|
33
|
+
thinkingLevel: Type.Optional(Type.String()),
|
|
34
|
+
persistRuns: Type.Optional(Type.Boolean()),
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
28
37
|
});
|
|
29
38
|
const ROOT_SETTINGS_SCHEMA = Type.Object({
|
|
30
39
|
sessions: Type.Optional(SESSION_FILE_SETTINGS_SCHEMA),
|
|
@@ -41,12 +50,20 @@ export class ModelReference {
|
|
|
41
50
|
}
|
|
42
51
|
}
|
|
43
52
|
|
|
44
|
-
export interface
|
|
53
|
+
export interface AgentModelSettings {
|
|
54
|
+
model?: ModelReference | undefined;
|
|
55
|
+
thinkingLevel?: ThinkingLevel | undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface AutoTitleSettings extends AgentModelSettings {
|
|
45
59
|
refreshTurns: number;
|
|
46
|
-
model: ModelReference | undefined;
|
|
47
60
|
prompt: string;
|
|
48
61
|
}
|
|
49
62
|
|
|
63
|
+
export interface AskSettings extends AgentModelSettings {
|
|
64
|
+
persistRuns: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
50
67
|
export interface SessionSettings {
|
|
51
68
|
handoff: {
|
|
52
69
|
pickerShortcut: KeyId;
|
|
@@ -55,6 +72,7 @@ export interface SessionSettings {
|
|
|
55
72
|
path: string;
|
|
56
73
|
};
|
|
57
74
|
autoTitle: AutoTitleSettings;
|
|
75
|
+
ask: AskSettings;
|
|
58
76
|
}
|
|
59
77
|
|
|
60
78
|
type SessionFileSettings = Static<typeof SESSION_FILE_SETTINGS_SCHEMA>;
|
|
@@ -67,6 +85,10 @@ export function getDefaultIndexPath(): string {
|
|
|
67
85
|
return path.join(getDefaultIndexDir(), "index.sqlite");
|
|
68
86
|
}
|
|
69
87
|
|
|
88
|
+
export function getDefaultSessionAskRunsDir(): string {
|
|
89
|
+
return path.join(getDefaultIndexDir(), "session-ask");
|
|
90
|
+
}
|
|
91
|
+
|
|
70
92
|
function expandHome(rawPath: string): string {
|
|
71
93
|
if (rawPath === "~") {
|
|
72
94
|
return os.homedir();
|
|
@@ -102,7 +124,7 @@ function normalizePickerShortcut(value: string | undefined): KeyId {
|
|
|
102
124
|
return (trimmed ? trimmed : "alt+o") as KeyId;
|
|
103
125
|
}
|
|
104
126
|
|
|
105
|
-
function parseModelReference(value: string | undefined): ModelReference | undefined {
|
|
127
|
+
export function parseModelReference(value: string | undefined): ModelReference | undefined {
|
|
106
128
|
const trimmed = value?.trim();
|
|
107
129
|
if (!trimmed) {
|
|
108
130
|
return undefined;
|
|
@@ -116,6 +138,37 @@ function parseModelReference(value: string | undefined): ModelReference | undefi
|
|
|
116
138
|
return new ModelReference(trimmed.slice(0, slashIndex), trimmed.slice(slashIndex + 1));
|
|
117
139
|
}
|
|
118
140
|
|
|
141
|
+
function parseThinkingLevel(value: string | undefined): ThinkingLevel | undefined {
|
|
142
|
+
const trimmed = value?.trim();
|
|
143
|
+
switch (trimmed) {
|
|
144
|
+
case "off":
|
|
145
|
+
case "minimal":
|
|
146
|
+
case "low":
|
|
147
|
+
case "medium":
|
|
148
|
+
case "high":
|
|
149
|
+
case "xhigh":
|
|
150
|
+
return trimmed;
|
|
151
|
+
default:
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function resolveAgentModelSettings(
|
|
157
|
+
value:
|
|
158
|
+
| {
|
|
159
|
+
model?: string | undefined;
|
|
160
|
+
thinkingLevel?: string | undefined;
|
|
161
|
+
}
|
|
162
|
+
| undefined,
|
|
163
|
+
): AgentModelSettings {
|
|
164
|
+
const model = parseModelReference(value?.model);
|
|
165
|
+
const thinkingLevel = parseThinkingLevel(value?.thinkingLevel);
|
|
166
|
+
return {
|
|
167
|
+
...(model ? { model } : {}),
|
|
168
|
+
...(thinkingLevel ? { thinkingLevel } : {}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
119
172
|
function normalizeAutoTitlePrompt(value: string | undefined): string {
|
|
120
173
|
const trimmed = value?.trim();
|
|
121
174
|
return trimmed ? trimmed : DEFAULT_AUTO_TITLE_PROMPT;
|
|
@@ -138,10 +191,14 @@ function resolveSessionSettings(fileSettings: SessionFileSettings): SessionSetti
|
|
|
138
191
|
path: path.join(indexDir, "index.sqlite"),
|
|
139
192
|
},
|
|
140
193
|
autoTitle: {
|
|
194
|
+
...resolveAgentModelSettings(fileSettings.autoTitle),
|
|
141
195
|
refreshTurns: fileSettings.autoTitle?.refreshTurns ?? DEFAULT_AUTO_TITLE_REFRESH_TURNS,
|
|
142
|
-
model: parseModelReference(fileSettings.autoTitle?.model),
|
|
143
196
|
prompt: normalizeAutoTitlePrompt(fileSettings.autoTitle?.prompt),
|
|
144
197
|
},
|
|
198
|
+
ask: {
|
|
199
|
+
...resolveAgentModelSettings(fileSettings.ask),
|
|
200
|
+
persistRuns: fileSettings.ask?.persistRuns ?? false,
|
|
201
|
+
},
|
|
145
202
|
};
|
|
146
203
|
}
|
|
147
204
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function contentToText(content: unknown): string {
|
|
2
|
+
if (typeof content === "string") {
|
|
3
|
+
return content.trim();
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (!Array.isArray(content)) {
|
|
7
|
+
return "";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return content
|
|
11
|
+
.filter(isTextBlock)
|
|
12
|
+
.map((part) => part.text)
|
|
13
|
+
.join("\n")
|
|
14
|
+
.trim();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
18
|
+
return typeof value === "object" && value !== null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function truncateInline(value: string, maxChars: number): string {
|
|
22
|
+
const cleaned = value.replace(/\s+/g, " ").trim();
|
|
23
|
+
if (cleaned.length <= maxChars) {
|
|
24
|
+
return cleaned;
|
|
25
|
+
}
|
|
26
|
+
return `${cleaned.slice(0, maxChars)}…`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function truncateBlock(
|
|
30
|
+
value: string,
|
|
31
|
+
maxChars: number,
|
|
32
|
+
): { text: string; truncated: boolean } {
|
|
33
|
+
if (value.length <= maxChars) {
|
|
34
|
+
return { text: value, truncated: false };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
text: `${value.slice(0, maxChars)}\n\n[truncated to ${maxChars} characters]`,
|
|
39
|
+
truncated: true,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface TextBlock {
|
|
44
|
+
type: "text";
|
|
45
|
+
text: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isTextBlock(part: unknown): part is TextBlock {
|
|
49
|
+
return isRecord(part) && part.type === "text" && typeof part.text === "string";
|
|
50
|
+
}
|
package/package.json
CHANGED
|
File without changes
|