pi-sessions 0.9.0 → 0.11.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 +29 -20
- package/dist/session-messaging/broker/client-frame.js +62 -0
- package/dist/session-messaging/broker/process.js +234 -0
- package/dist/shared/session-broker/framing.js +59 -0
- package/dist/shared/session-broker/socket-path.js +19 -0
- package/extensions/pi-sessions.ts +14 -1
- package/extensions/session-auto-title/generate.ts +56 -66
- package/extensions/session-auto-title/install.ts +2 -0
- package/extensions/session-auto-title/runs.ts +50 -0
- package/extensions/session-handoff/board-view-model.ts +2 -4
- package/extensions/session-handoff/bootstrap.ts +14 -16
- package/extensions/session-handoff/extract.ts +34 -16
- package/extensions/session-handoff/install.ts +1 -1
- package/extensions/session-handoff/kickoff.ts +4 -3
- package/extensions/session-handoff/launch-target.ts +1 -1
- package/extensions/session-handoff/metadata.ts +72 -23
- package/extensions/session-messaging/broker/client-frame.ts +113 -0
- package/extensions/session-messaging/broker/process.ts +12 -21
- package/extensions/session-messaging/broker/spawn.ts +12 -7
- package/extensions/session-messaging/pi/reachable-contract.ts +59 -0
- package/extensions/session-messaging/pi/reachable-presenter.ts +49 -0
- package/extensions/session-messaging/pi/reachable-tool.ts +190 -0
- package/extensions/session-messaging/pi/reachable-view-model.ts +59 -0
- package/extensions/session-messaging/pi/renderer.ts +1 -1
- package/extensions/session-messaging/pi/tools.ts +8 -8
- package/extensions/session-search/hooks.ts +45 -52
- package/extensions/session-search/install.ts +3 -158
- package/extensions/session-search/reindex.ts +3 -3
- package/extensions/session-search/renderer.ts +1 -32
- package/extensions/session-search/tool-contract.ts +1 -21
- package/extensions/shared/model-runtime.ts +8 -1
- package/extensions/shared/session-broker/client.ts +3 -4
- package/extensions/shared/session-broker/framing.ts +6 -7
- package/extensions/shared/session-index/lineage.ts +6 -2
- package/extensions/shared/session-index/sqlite.ts +44 -17
- package/extensions/shared/settings.ts +25 -9
- package/extensions/shared/tmux.ts +22 -0
- package/extensions/subagents/classify.ts +6 -0
- package/extensions/subagents/install.ts +22 -137
- package/extensions/subagents/report-message-renderer.ts +2 -2
- package/extensions/subagents/settle.ts +182 -0
- package/package.json +18 -26
|
@@ -228,7 +228,7 @@ function syncSessionFileWithDb(
|
|
|
228
228
|
const stat = statSync(sessionFile);
|
|
229
229
|
|
|
230
230
|
if (baseline && isIndexCurrent(baseline, stat, sessionOrigin)) {
|
|
231
|
-
db.transaction(() => writeHookSyncMetadata(db, eventType))
|
|
231
|
+
db.transaction(() => writeHookSyncMetadata(db, eventType));
|
|
232
232
|
return true;
|
|
233
233
|
}
|
|
234
234
|
|
|
@@ -279,11 +279,6 @@ function isIndexCurrent(
|
|
|
279
279
|
);
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
// All write transactions are immediate: they read before they write, and a
|
|
283
|
-
// deferred transaction whose snapshot goes stale fails with SQLITE_BUSY on the
|
|
284
|
-
// read-to-write upgrade without ever invoking the busy handler. Immediate mode
|
|
285
|
-
// takes the write lock at BEGIN, where busy_timeout queues us behind concurrent
|
|
286
|
-
// writers from other pi processes.
|
|
287
282
|
function applyFullSync(
|
|
288
283
|
db: SessionIndexDatabase,
|
|
289
284
|
extracted: ExtractedSessionRecord,
|
|
@@ -312,7 +307,7 @@ function applyFullSync(
|
|
|
312
307
|
}
|
|
313
308
|
|
|
314
309
|
writeHookSyncMetadata(db, eventType);
|
|
315
|
-
})
|
|
310
|
+
});
|
|
316
311
|
}
|
|
317
312
|
|
|
318
313
|
function applyTailSync(
|
|
@@ -322,60 +317,58 @@ function applyTailSync(
|
|
|
322
317
|
eventType: string,
|
|
323
318
|
sessionOrigin?: SessionOrigin,
|
|
324
319
|
): boolean {
|
|
325
|
-
return db
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
320
|
+
return db.transaction((): boolean => {
|
|
321
|
+
// Another process may have advanced the index between our baseline read
|
|
322
|
+
// and this transaction; the tail deltas would then double-count.
|
|
323
|
+
const current = getSessionRowByPath(db, baseline.sessionPath);
|
|
324
|
+
if (
|
|
325
|
+
!current ||
|
|
326
|
+
current.sessionId !== baseline.sessionId ||
|
|
327
|
+
current.indexedFileSize !== baseline.indexedFileSize
|
|
328
|
+
) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
337
331
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (scan.sessionName !== undefined && scan.sessionName !== baseline.sessionName) {
|
|
342
|
-
clearSessionChunksBySourceKind(db, baseline.sessionId, "session_name");
|
|
343
|
-
if (scan.sessionName && scan.sessionNameEntryId) {
|
|
344
|
-
insertTextChunk(db, {
|
|
345
|
-
sessionId: baseline.sessionId,
|
|
346
|
-
...createSessionNameChunk(
|
|
347
|
-
scan.sessionName,
|
|
348
|
-
scan.sessionNameTs ?? baseline.startedAt,
|
|
349
|
-
scan.sessionNameEntryId,
|
|
350
|
-
),
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
}
|
|
332
|
+
const scan = tail.scan;
|
|
333
|
+
upsertSession(db, buildTailSessionRow(baseline, tail, sessionOrigin), "hook");
|
|
354
334
|
|
|
355
|
-
|
|
356
|
-
|
|
335
|
+
if (scan.sessionName !== undefined && scan.sessionName !== baseline.sessionName) {
|
|
336
|
+
clearSessionChunksBySourceKind(db, baseline.sessionId, "session_name");
|
|
337
|
+
if (scan.sessionName && scan.sessionNameEntryId) {
|
|
357
338
|
insertTextChunk(db, {
|
|
358
339
|
sessionId: baseline.sessionId,
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
340
|
+
...createSessionNameChunk(
|
|
341
|
+
scan.sessionName,
|
|
342
|
+
scan.sessionNameTs ?? baseline.startedAt,
|
|
343
|
+
scan.sessionNameEntryId,
|
|
344
|
+
),
|
|
364
345
|
});
|
|
365
346
|
}
|
|
347
|
+
}
|
|
366
348
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
349
|
+
if (!baseline.handoffGoal && scan.handoffMetadata) {
|
|
350
|
+
const { entryId, ts, metadata } = scan.handoffMetadata;
|
|
351
|
+
insertTextChunk(db, {
|
|
352
|
+
sessionId: baseline.sessionId,
|
|
353
|
+
entryId,
|
|
354
|
+
entryType: "custom",
|
|
355
|
+
ts,
|
|
356
|
+
sourceKind: "handoff_goal",
|
|
357
|
+
text: metadata.goal,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
370
360
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
361
|
+
for (const chunk of scan.chunks) {
|
|
362
|
+
insertTextChunk(db, { sessionId: baseline.sessionId, ...chunk });
|
|
363
|
+
}
|
|
374
364
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
|
|
365
|
+
for (const fileTouch of scan.fileTouches) {
|
|
366
|
+
insertSessionFileTouch(db, { sessionId: baseline.sessionId, ...fileTouch });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
writeHookSyncMetadata(db, eventType);
|
|
370
|
+
return true;
|
|
371
|
+
});
|
|
379
372
|
}
|
|
380
373
|
|
|
381
374
|
function buildTailSessionRow(
|
|
@@ -1,24 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ExtensionAPI,
|
|
3
|
-
type ExtensionContext,
|
|
4
|
-
SessionManager,
|
|
5
|
-
} from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
2
|
import { Type } from "typebox";
|
|
7
|
-
import { isSessionStarting } from "../session-handoff/metadata.ts";
|
|
8
|
-
import type { MessagingHandle } from "../session-messaging/install.ts";
|
|
9
3
|
import type { IndexHandle } from "../shared/composition.ts";
|
|
10
|
-
import { formatError } from "../shared/errors.ts";
|
|
11
4
|
import { stripSearchSnippetMarkers } from "../shared/search-snippet.ts";
|
|
12
5
|
import {
|
|
13
|
-
getSessionById,
|
|
14
6
|
type SearchSessionsParams,
|
|
15
7
|
type SessionSearchEvidence,
|
|
16
8
|
searchSessions,
|
|
17
9
|
withSessionIndex,
|
|
18
10
|
} from "../shared/session-index/index.ts";
|
|
19
11
|
import type { SessionSettings } from "../shared/settings.ts";
|
|
20
|
-
import type { SubagentState } from "../subagents/classify.ts";
|
|
21
|
-
import type { SubagentRoster } from "../subagents/roster.ts";
|
|
22
12
|
import { renderSessionSearchResult } from "./renderer.ts";
|
|
23
13
|
import type {
|
|
24
14
|
SessionSearchResult,
|
|
@@ -31,8 +21,6 @@ const DEFAULT_SESSION_SEARCH_LIMIT = 6;
|
|
|
31
21
|
export interface SearchInstallDeps {
|
|
32
22
|
settings: SessionSettings;
|
|
33
23
|
index: IndexHandle;
|
|
34
|
-
messaging?: MessagingHandle | undefined;
|
|
35
|
-
roster?: SubagentRoster | undefined;
|
|
36
24
|
}
|
|
37
25
|
|
|
38
26
|
export function installSearch(pi: ExtensionAPI, deps: SearchInstallDeps): void {
|
|
@@ -46,7 +34,6 @@ export function installSearch(pi: ExtensionAPI, deps: SearchInstallDeps): void {
|
|
|
46
34
|
promptGuidelines: [
|
|
47
35
|
"Omit queries in session_search to list matching sessions chronologically.",
|
|
48
36
|
"After session_search finds a session id, switch to session_ask for questions about it.",
|
|
49
|
-
'To identify your own subagents, use session_search with relationScope: "branch".',
|
|
50
37
|
],
|
|
51
38
|
parameters: Type.Object({
|
|
52
39
|
query: Type.Optional(
|
|
@@ -105,22 +92,11 @@ export function installSearch(pi: ExtensionAPI, deps: SearchInstallDeps): void {
|
|
|
105
92
|
},
|
|
106
93
|
),
|
|
107
94
|
),
|
|
108
|
-
live: Type.Optional(
|
|
109
|
-
Type.Boolean({
|
|
110
|
-
description: "When true, only return currently active sessions",
|
|
111
|
-
}),
|
|
112
|
-
),
|
|
113
95
|
kind: Type.Optional(
|
|
114
96
|
Type.Union([Type.Literal("user"), Type.Literal("subagent")], {
|
|
115
97
|
description: "Filter by session kind",
|
|
116
98
|
}),
|
|
117
99
|
),
|
|
118
|
-
relationScope: Type.Optional(
|
|
119
|
-
Type.Union([Type.Literal("branch"), Type.Literal("tree")], {
|
|
120
|
-
description:
|
|
121
|
-
"Restrict results to subagents launched from the active branch or anywhere in this session's conversation tree",
|
|
122
|
-
}),
|
|
123
|
-
),
|
|
124
100
|
limit: Type.Optional(
|
|
125
101
|
Type.Number({
|
|
126
102
|
description: "Number of matches to return",
|
|
@@ -142,35 +118,9 @@ export function installSearch(pi: ExtensionAPI, deps: SearchInstallDeps): void {
|
|
|
142
118
|
details: progressDetails,
|
|
143
119
|
});
|
|
144
120
|
|
|
145
|
-
const scope = await resolveSearchScope(params, deps);
|
|
146
|
-
|
|
147
121
|
return withSessionIndex(indexPath, { mode: "read", required: true }, ({ db, status }) => {
|
|
148
|
-
const results = searchSessions(
|
|
149
|
-
|
|
150
|
-
buildSearchParams(params, ctx, scope.includeSessionIds),
|
|
151
|
-
).map((result): SessionSearchResult => {
|
|
152
|
-
const annotation = scope.annotations.get(result.sessionId);
|
|
153
|
-
if (!annotation) {
|
|
154
|
-
return result;
|
|
155
|
-
}
|
|
156
|
-
if ("depth" in annotation) {
|
|
157
|
-
return {
|
|
158
|
-
...result,
|
|
159
|
-
state: annotation.state,
|
|
160
|
-
depth: annotation.depth,
|
|
161
|
-
onActiveBranch: annotation.onActiveBranch,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
return { ...result, state: annotation.state };
|
|
165
|
-
});
|
|
166
|
-
const details: SessionSearchToolDetails = {
|
|
167
|
-
params,
|
|
168
|
-
status,
|
|
169
|
-
results,
|
|
170
|
-
...(scope.total === undefined
|
|
171
|
-
? {}
|
|
172
|
-
: { scope: { matched: results.length, total: scope.total } }),
|
|
173
|
-
};
|
|
122
|
+
const results = searchSessions(db, buildSearchParams(params, ctx));
|
|
123
|
+
const details: SessionSearchToolDetails = { params, status, results };
|
|
174
124
|
return {
|
|
175
125
|
content: [
|
|
176
126
|
{
|
|
@@ -189,7 +139,6 @@ export function installSearch(pi: ExtensionAPI, deps: SearchInstallDeps): void {
|
|
|
189
139
|
function buildSearchParams(
|
|
190
140
|
params: SessionSearchToolParams,
|
|
191
141
|
ctx: ExtensionContext,
|
|
192
|
-
liveSessionIds?: string[] | undefined,
|
|
193
142
|
): SearchSessionsParams {
|
|
194
143
|
const currentSessionId = ctx.sessionManager.getSessionId();
|
|
195
144
|
|
|
@@ -204,119 +153,15 @@ function buildSearchParams(
|
|
|
204
153
|
kind: params.kind,
|
|
205
154
|
sort: params.sort,
|
|
206
155
|
limit: params.limit ?? DEFAULT_SESSION_SEARCH_LIMIT,
|
|
207
|
-
includeSessionIds: liveSessionIds,
|
|
208
156
|
relativeToSessionId: currentSessionId,
|
|
209
157
|
};
|
|
210
158
|
}
|
|
211
159
|
|
|
212
|
-
interface StartingSearchResultAnnotation {
|
|
213
|
-
state: "starting";
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
interface RelatedSubagentSearchResultAnnotation {
|
|
217
|
-
state: SubagentState;
|
|
218
|
-
depth: number;
|
|
219
|
-
onActiveBranch: boolean;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
type SearchResultAnnotation =
|
|
223
|
-
| StartingSearchResultAnnotation
|
|
224
|
-
| RelatedSubagentSearchResultAnnotation;
|
|
225
|
-
|
|
226
|
-
interface ResolvedSearchScope {
|
|
227
|
-
includeSessionIds: string[] | undefined;
|
|
228
|
-
annotations: ReadonlyMap<string, SearchResultAnnotation>;
|
|
229
|
-
total: number | undefined;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
async function resolveSearchScope(
|
|
233
|
-
params: SessionSearchToolParams,
|
|
234
|
-
deps: SearchInstallDeps,
|
|
235
|
-
): Promise<ResolvedSearchScope> {
|
|
236
|
-
if (params.relationScope) {
|
|
237
|
-
if (!deps.roster) {
|
|
238
|
-
throw new Error("Subagent relations are unavailable because subagents are not active.");
|
|
239
|
-
}
|
|
240
|
-
try {
|
|
241
|
-
const roster = await deps.roster.resolve(params.relationScope);
|
|
242
|
-
const entries = params.live
|
|
243
|
-
? roster.entries.filter((entry) => entry.managedLive)
|
|
244
|
-
: roster.entries;
|
|
245
|
-
const annotations = new Map<string, SearchResultAnnotation>();
|
|
246
|
-
for (const entry of roster.entries) {
|
|
247
|
-
annotations.set(entry.sessionId, {
|
|
248
|
-
state: entry.state,
|
|
249
|
-
depth: entry.depth,
|
|
250
|
-
onActiveBranch: entry.onActiveBranch,
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
return {
|
|
254
|
-
includeSessionIds: entries.map((entry) => entry.sessionId),
|
|
255
|
-
annotations,
|
|
256
|
-
total: roster.total,
|
|
257
|
-
};
|
|
258
|
-
} catch (error) {
|
|
259
|
-
throw new Error(`Subagent relation scope is unavailable: ${formatError(error)}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (!params.live) {
|
|
264
|
-
return {
|
|
265
|
-
includeSessionIds: undefined,
|
|
266
|
-
annotations: new Map(),
|
|
267
|
-
total: undefined,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const liveSessionIds = await getLiveSessionIds(deps.messaging);
|
|
272
|
-
return {
|
|
273
|
-
includeSessionIds: liveSessionIds,
|
|
274
|
-
annotations: getStartingSessionAnnotations(liveSessionIds, deps.index.path),
|
|
275
|
-
total: undefined,
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function getStartingSessionAnnotations(
|
|
280
|
-
sessionIds: readonly string[],
|
|
281
|
-
indexPath: string,
|
|
282
|
-
): ReadonlyMap<string, SearchResultAnnotation> {
|
|
283
|
-
return withSessionIndex(indexPath, { mode: "read", required: true }, ({ db }) => {
|
|
284
|
-
const annotations = new Map<string, SearchResultAnnotation>();
|
|
285
|
-
for (const sessionId of sessionIds) {
|
|
286
|
-
const session = getSessionById(db, sessionId);
|
|
287
|
-
if (!session) {
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
try {
|
|
291
|
-
if (isSessionStarting(SessionManager.open(session.sessionPath).getBranch())) {
|
|
292
|
-
annotations.set(sessionId, { state: "starting" });
|
|
293
|
-
}
|
|
294
|
-
} catch {}
|
|
295
|
-
}
|
|
296
|
-
return annotations;
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
async function getLiveSessionIds(messaging: MessagingHandle | undefined): Promise<string[]> {
|
|
301
|
-
if (!messaging) {
|
|
302
|
-
throw new Error(
|
|
303
|
-
"Session messaging is not active; live session search requires session messaging.",
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
try {
|
|
308
|
-
return await messaging.listSessions();
|
|
309
|
-
} catch (error) {
|
|
310
|
-
throw new Error(`Session messaging is unavailable: ${formatError(error)}`);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
160
|
function formatSearchResultsForModel(details: SessionSearchToolDetails): string {
|
|
315
161
|
return JSON.stringify(
|
|
316
162
|
{
|
|
317
163
|
params: details.params,
|
|
318
164
|
status: details.status,
|
|
319
|
-
...(details.scope ? { scope: details.scope } : {}),
|
|
320
165
|
results: details.results.map(formatSearchResultForModel),
|
|
321
166
|
},
|
|
322
167
|
null,
|
|
@@ -34,7 +34,7 @@ export async function rebuildSessionIndex(options: ReindexOptions): Promise<Rein
|
|
|
34
34
|
db.transaction(() => {
|
|
35
35
|
dropIndexTables(db);
|
|
36
36
|
initializeSchema(db);
|
|
37
|
-
})
|
|
37
|
+
});
|
|
38
38
|
|
|
39
39
|
let sessionCount = 0;
|
|
40
40
|
let chunkCount = 0;
|
|
@@ -44,7 +44,7 @@ export async function rebuildSessionIndex(options: ReindexOptions): Promise<Rein
|
|
|
44
44
|
continue;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
db.transaction(() => indexSession(db, extracted))
|
|
47
|
+
db.transaction(() => indexSession(db, extracted));
|
|
48
48
|
sessionCount += 1;
|
|
49
49
|
chunkCount += extracted.chunks.length;
|
|
50
50
|
}
|
|
@@ -53,7 +53,7 @@ export async function rebuildSessionIndex(options: ReindexOptions): Promise<Rein
|
|
|
53
53
|
rebuildSessionLineageRelations(db);
|
|
54
54
|
setMetadata(db, "indexed_at", new Date().toISOString());
|
|
55
55
|
setMetadata(db, "session_source", "SessionManager.listAll()");
|
|
56
|
-
})
|
|
56
|
+
});
|
|
57
57
|
|
|
58
58
|
return { sessionCount, chunkCount, indexPath };
|
|
59
59
|
} finally {
|
|
@@ -11,8 +11,6 @@ import {
|
|
|
11
11
|
} from "../shared/search-snippet.ts";
|
|
12
12
|
import { formatSessionTitleOrShortId } from "../shared/session-ui.ts";
|
|
13
13
|
import type {
|
|
14
|
-
AnnotatedSearchResult,
|
|
15
|
-
RelatedSubagentSearchResult,
|
|
16
14
|
SessionSearchResult,
|
|
17
15
|
SessionSearchToolDetails,
|
|
18
16
|
SessionSearchToolParams,
|
|
@@ -51,11 +49,6 @@ export function renderSessionSearchResult(
|
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
const lines = formatSessionSearchContextLines(details.params, theme);
|
|
54
|
-
if (details.scope) {
|
|
55
|
-
lines.push(
|
|
56
|
-
theme.fg("dim", `scope: ${details.scope.matched} matched • ${details.scope.total} total`),
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
52
|
if (details.results.length === 0) {
|
|
60
53
|
if (lines.length > 0) lines.push("");
|
|
61
54
|
lines.push(theme.fg("warning", "No matching sessions found."));
|
|
@@ -85,9 +78,7 @@ function formatSessionSearchContextLines(
|
|
|
85
78
|
filters.push(`files.touched: ${params.files.touched.join(", ")}`);
|
|
86
79
|
if (params.files?.changed?.length)
|
|
87
80
|
filters.push(`files.changed: ${params.files.changed.join(", ")}`);
|
|
88
|
-
if (params.live) filters.push("live: true");
|
|
89
81
|
if (params.kind) filters.push(`kind: ${params.kind}`);
|
|
90
|
-
if (params.relationScope) filters.push(`relationScope: ${params.relationScope}`);
|
|
91
82
|
if (params.sort) filters.push(`sort: ${params.sort}`);
|
|
92
83
|
if (params.time?.after?.trim()) filters.push(`after: ${params.time.after.trim()}`);
|
|
93
84
|
if (params.time?.before?.trim()) filters.push(`before: ${params.time.before.trim()}`);
|
|
@@ -114,9 +105,7 @@ function formatSessionSearchPanelResults(
|
|
|
114
105
|
const lines = visibleResults.flatMap((result, index) => {
|
|
115
106
|
const location = formatSearchResultLocation(result.cwd);
|
|
116
107
|
const relation = result.relation ? ` ${theme.fg("dim", `[${result.relation}]`)}` : "";
|
|
117
|
-
const
|
|
118
|
-
const state = annotation.length > 0 ? ` ${theme.fg("dim", `[${annotation.join(" • ")}]`)}` : "";
|
|
119
|
-
const heading = `${index + 1}. ${theme.bold(formatSearchResultLabel(result))}${relation}${state}${location ? ` ${theme.fg("dim", `(${location})`)}` : ""}`;
|
|
108
|
+
const heading = `${index + 1}. ${theme.bold(formatSearchResultLabel(result))}${relation}${location ? ` ${theme.fg("dim", `(${location})`)}` : ""}`;
|
|
120
109
|
const snippets = params?.query ? formatSearchSnippets(result).slice(0, 3) : [];
|
|
121
110
|
return snippets.length > 0
|
|
122
111
|
? [heading, ...snippets.map((snippet) => theme.fg("dim", ` - ${snippet}`))]
|
|
@@ -130,26 +119,6 @@ function formatSessionSearchPanelResults(
|
|
|
130
119
|
return lines;
|
|
131
120
|
}
|
|
132
121
|
|
|
133
|
-
function formatSearchResultAnnotation(result: SessionSearchResult): string[] {
|
|
134
|
-
if (!isAnnotatedSearchResult(result)) {
|
|
135
|
-
return [];
|
|
136
|
-
}
|
|
137
|
-
if (!isRelatedSubagentSearchResult(result)) {
|
|
138
|
-
return [result.state];
|
|
139
|
-
}
|
|
140
|
-
return [result.state, `depth ${result.depth}`, ...(result.onActiveBranch ? [] : ["history"])];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function isAnnotatedSearchResult(result: SessionSearchResult): result is AnnotatedSearchResult {
|
|
144
|
-
return "state" in result;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function isRelatedSubagentSearchResult(
|
|
148
|
-
result: AnnotatedSearchResult,
|
|
149
|
-
): result is RelatedSubagentSearchResult {
|
|
150
|
-
return "depth" in result;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
122
|
function formatSearchResultLabel(result: SessionSearchResult): string {
|
|
154
123
|
return formatSessionTitleOrShortId(result.sessionName, result.sessionId);
|
|
155
124
|
}
|
|
@@ -4,7 +4,6 @@ import type {
|
|
|
4
4
|
SessionIndexStatus,
|
|
5
5
|
SessionKind,
|
|
6
6
|
} from "../shared/session-index/index.ts";
|
|
7
|
-
import type { SubagentState } from "../subagents/classify.ts";
|
|
8
7
|
|
|
9
8
|
export interface SessionSearchToolParams {
|
|
10
9
|
query?: string;
|
|
@@ -20,32 +19,13 @@ export interface SessionSearchToolParams {
|
|
|
20
19
|
};
|
|
21
20
|
sort?: SearchSort;
|
|
22
21
|
limit?: number;
|
|
23
|
-
live?: boolean;
|
|
24
22
|
kind?: SessionKind;
|
|
25
|
-
relationScope?: "branch" | "tree";
|
|
26
23
|
}
|
|
27
24
|
|
|
28
|
-
export
|
|
29
|
-
matched: number;
|
|
30
|
-
total: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface StartingSearchResult extends SearchSessionResult {
|
|
34
|
-
state: "starting";
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface RelatedSubagentSearchResult extends SearchSessionResult {
|
|
38
|
-
state: SubagentState;
|
|
39
|
-
depth: number;
|
|
40
|
-
onActiveBranch: boolean;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type AnnotatedSearchResult = StartingSearchResult | RelatedSubagentSearchResult;
|
|
44
|
-
export type SessionSearchResult = SearchSessionResult | AnnotatedSearchResult;
|
|
25
|
+
export type SessionSearchResult = SearchSessionResult;
|
|
45
26
|
|
|
46
27
|
export interface SessionSearchToolDetails {
|
|
47
28
|
params?: SessionSearchToolParams | undefined;
|
|
48
29
|
results: SessionSearchResult[];
|
|
49
30
|
status?: SessionIndexStatus | undefined;
|
|
50
|
-
scope?: SessionSearchScopeDetails | undefined;
|
|
51
31
|
}
|
|
@@ -7,13 +7,20 @@ export type ModelRuntimeProvider = (modelRegistry: ModelRegistry) => Promise<Mod
|
|
|
7
7
|
// ModelRuntime, but ExtensionContext only exposes the ModelRegistry compatibility facade,
|
|
8
8
|
// which keeps its runtime private. Until pi exposes the runtime to extensions, build a
|
|
9
9
|
// fresh one per use and mirror the registered providers (e.g. pi-claude-bridge) so their
|
|
10
|
-
//
|
|
10
|
+
// live stream closures carry over by reference. Extensions register either a native
|
|
11
|
+
// Provider or a legacy provider config, and the facade exposes each through its own
|
|
12
|
+
// accessor while listing both id sets together.
|
|
11
13
|
export async function createSessionModelRuntime(
|
|
12
14
|
modelRegistry: ModelRegistry,
|
|
13
15
|
): Promise<ModelRuntime> {
|
|
14
16
|
const modelRuntime = await ModelRuntime.create({ allowModelNetwork: false });
|
|
15
17
|
|
|
16
18
|
for (const providerId of modelRegistry.getRegisteredProviderIds()) {
|
|
19
|
+
const native = modelRegistry.getRegisteredNativeProvider(providerId);
|
|
20
|
+
if (native) {
|
|
21
|
+
modelRuntime.registerNativeProvider(native);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
17
24
|
const config = modelRegistry.getRegisteredProviderConfig(providerId);
|
|
18
25
|
if (config) {
|
|
19
26
|
modelRuntime.registerProvider(providerId, config);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
3
|
import net from "node:net";
|
|
4
|
+
import { parseTypeBoxValue } from "../typebox.ts";
|
|
4
5
|
import { readFrames, writeFrame } from "./framing.ts";
|
|
5
6
|
import {
|
|
6
7
|
BROKER_FRAME_SCHEMA,
|
|
@@ -176,10 +177,8 @@ export class SessionMessagingClient extends EventEmitter {
|
|
|
176
177
|
|
|
177
178
|
private async readSocket(socket: net.Socket): Promise<void> {
|
|
178
179
|
try {
|
|
179
|
-
for await (const frame of readFrames(
|
|
180
|
-
|
|
181
|
-
BROKER_FRAME_SCHEMA,
|
|
182
|
-
"Invalid session messaging broker frame",
|
|
180
|
+
for await (const frame of readFrames(socket, (value) =>
|
|
181
|
+
parseTypeBoxValue(BROKER_FRAME_SCHEMA, value, "Invalid session messaging broker frame"),
|
|
183
182
|
)) {
|
|
184
183
|
this.handleFrame(frame);
|
|
185
184
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { Socket } from "node:net";
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
|
-
import type { Static, TSchema } from "typebox";
|
|
4
|
-
import { parseTypeBoxValue } from "../typebox.ts";
|
|
5
3
|
|
|
6
4
|
const MAX_FRAME_BYTES = 256 * 1024;
|
|
7
5
|
|
|
@@ -9,11 +7,12 @@ export function writeFrame<T>(socket: Socket, frame: T): void {
|
|
|
9
7
|
socket.write(`${JSON.stringify(frame)}\n`);
|
|
10
8
|
}
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
// The parser is injected so the detached broker can run with no dependencies
|
|
11
|
+
// installed alongside it, while Pi-side callers keep using TypeBox schemas.
|
|
12
|
+
export async function* readFrames<T>(
|
|
13
13
|
socket: Socket,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
): AsyncGenerator<Static<T>> {
|
|
14
|
+
parseFrame: (value: unknown) => T,
|
|
15
|
+
): AsyncGenerator<T> {
|
|
17
16
|
const reader = createInterface({ input: socket, crlfDelay: Number.POSITIVE_INFINITY });
|
|
18
17
|
const lines: string[] = [];
|
|
19
18
|
const waiters: Array<() => void> = [];
|
|
@@ -64,7 +63,7 @@ export async function* readFrames<T extends TSchema>(
|
|
|
64
63
|
throw parseError instanceof Error ? parseError : new Error(String(parseError));
|
|
65
64
|
}
|
|
66
65
|
|
|
67
|
-
yield
|
|
66
|
+
yield parseFrame(parsed);
|
|
68
67
|
}
|
|
69
68
|
} finally {
|
|
70
69
|
reader.close();
|
|
@@ -239,6 +239,7 @@ function buildSessionGraph(db: SessionIndexDatabase): SessionGraph {
|
|
|
239
239
|
"Invalid session graph rows",
|
|
240
240
|
);
|
|
241
241
|
|
|
242
|
+
const sessionIds = new Set(rows.map((row) => row.sessionId));
|
|
242
243
|
const pathToId = new Map(rows.map((row) => [row.sessionPath, row.sessionId]));
|
|
243
244
|
const nodes = new Map<string, SessionGraphNode>(
|
|
244
245
|
rows.map((row) => [
|
|
@@ -246,8 +247,11 @@ function buildSessionGraph(db: SessionIndexDatabase): SessionGraph {
|
|
|
246
247
|
{
|
|
247
248
|
...row,
|
|
248
249
|
resolvedParentSessionId:
|
|
249
|
-
row.parentSessionId
|
|
250
|
-
|
|
250
|
+
row.parentSessionId && sessionIds.has(row.parentSessionId)
|
|
251
|
+
? row.parentSessionId
|
|
252
|
+
: row.parentSessionPath
|
|
253
|
+
? pathToId.get(row.parentSessionPath)
|
|
254
|
+
: undefined,
|
|
251
255
|
},
|
|
252
256
|
]),
|
|
253
257
|
);
|