pi-sessions 0.5.1 → 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 -1
- package/extensions/session-ask/agent.ts +400 -0
- package/extensions/session-ask/navigate.ts +880 -0
- package/extensions/session-ask.ts +82 -134
- package/extensions/session-auto-title/command.ts +1 -1
- package/extensions/session-auto-title/controller.ts +3 -3
- package/extensions/session-auto-title/generate.ts +2 -2
- package/extensions/session-auto-title/model.ts +4 -12
- package/extensions/session-auto-title/retitle.ts +5 -5
- package/extensions/session-auto-title/state.ts +1 -1
- package/extensions/session-auto-title/wizard.ts +4 -4
- package/extensions/session-auto-title.ts +8 -8
- package/extensions/session-handoff/extract.ts +18 -5
- package/extensions/session-handoff/metadata.ts +4 -1
- package/extensions/session-handoff/picker.ts +52 -6
- package/extensions/session-handoff/query.ts +78 -62
- package/extensions/session-handoff/refs.ts +14 -20
- package/extensions/session-handoff/spawn.ts +1 -1
- package/extensions/session-handoff.ts +54 -35
- package/extensions/session-hooks.ts +3 -3
- package/extensions/session-index.ts +4 -4
- package/extensions/session-messaging/broker/process.ts +302 -0
- package/extensions/session-messaging/broker/spawn.ts +164 -0
- package/extensions/session-messaging/pi/incoming-runtime.ts +114 -0
- package/extensions/session-messaging/pi/message-contracts.ts +36 -0
- package/extensions/session-messaging/pi/message-view.ts +131 -0
- package/extensions/session-messaging/pi/renderer.ts +16 -0
- package/extensions/session-messaging/pi/service.ts +370 -0
- package/extensions/session-messaging/pi/tools.ts +92 -0
- package/extensions/session-messaging.ts +30 -0
- package/extensions/session-search/extract.ts +90 -440
- package/extensions/session-search/hooks.ts +20 -28
- package/extensions/session-search/normalize.ts +1 -1
- package/extensions/session-search/reindex.ts +3 -2
- package/extensions/session-search.ts +161 -132
- package/extensions/shared/model.ts +18 -0
- package/extensions/shared/session-broker/active.ts +26 -0
- package/extensions/shared/session-broker/client.ts +253 -0
- package/extensions/shared/session-broker/framing.ts +72 -0
- package/extensions/shared/session-broker/protocol.ts +95 -0
- package/extensions/shared/session-broker/socket-path.ts +30 -0
- package/extensions/shared/session-index/access.ts +128 -0
- package/extensions/shared/session-index/common.ts +46 -51
- package/extensions/shared/session-index/index.ts +6 -5
- package/extensions/shared/session-index/lineage.ts +19 -2
- 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 +25 -9
- package/extensions/shared/session-index/scoring.ts +80 -0
- package/extensions/shared/session-index/search.ts +555 -281
- package/extensions/shared/session-index/sqlite.ts +16 -9
- package/extensions/shared/session-index/store.ts +14 -23
- package/extensions/shared/settings.ts +62 -5
- package/extensions/shared/text.ts +50 -0
- package/package.json +4 -3
|
@@ -1,22 +1,14 @@
|
|
|
1
|
-
import type { Message } from "@earendil-works/pi-ai";
|
|
2
|
-
import { complete } from "@earendil-works/pi-ai/compat";
|
|
3
1
|
import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
2
|
+
import { getKeybindings, type Keybinding, Text } from "@earendil-works/pi-tui";
|
|
5
3
|
import { Type } from "typebox";
|
|
6
|
-
import {
|
|
4
|
+
import { runSessionAskAgent } from "./session-ask/agent.ts";
|
|
7
5
|
import {
|
|
8
|
-
getIndexStatus,
|
|
9
6
|
getSessionById,
|
|
10
|
-
INDEX_SCHEMA_VERSION,
|
|
11
|
-
openIndexDatabase,
|
|
12
7
|
type SessionLineageRow,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
const SESSION_ASK_SYSTEM_PROMPT = `You are analyzing a Pi coding session transcript. The transcript includes the entire session tree, including abandoned branches and summaries.
|
|
18
|
-
|
|
19
|
-
Answer the user's question using only the session contents. Be specific and concise. Include exact file paths, decisions, and outcomes when present. If the answer is not in the session, say so clearly.`;
|
|
8
|
+
withSessionIndex,
|
|
9
|
+
} from "./shared/session-index/index.ts";
|
|
10
|
+
import { formatSessionTitleOrShortId, isExactSessionId } from "./shared/session-ui.ts";
|
|
11
|
+
import { loadSettings } from "./shared/settings.ts";
|
|
20
12
|
|
|
21
13
|
const COLLAPSED_ANSWER_PREVIEW_ROWS = 6;
|
|
22
14
|
|
|
@@ -25,21 +17,21 @@ interface SessionAskToolParams {
|
|
|
25
17
|
question: string;
|
|
26
18
|
}
|
|
27
19
|
|
|
20
|
+
interface SessionAskRelevantFile {
|
|
21
|
+
path: string;
|
|
22
|
+
reason: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
28
25
|
interface SessionAskToolDetails {
|
|
29
|
-
cancelled?: boolean | undefined;
|
|
30
|
-
error?: boolean | undefined;
|
|
31
26
|
answer?: string | undefined;
|
|
27
|
+
debugSessionPath?: string | undefined;
|
|
32
28
|
question?: string | undefined;
|
|
29
|
+
relevantFiles?: SessionAskRelevantFile[] | undefined;
|
|
33
30
|
sessionId?: string | undefined;
|
|
34
31
|
sessionName?: string | undefined;
|
|
35
32
|
sessionPath?: string | undefined;
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
interface TextContentBlock {
|
|
39
|
-
type: "text";
|
|
40
|
-
text: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
35
|
export default function sessionAskExtension(pi: ExtensionAPI): void {
|
|
44
36
|
const settings = loadSettings();
|
|
45
37
|
|
|
@@ -61,32 +53,17 @@ export default function sessionAskExtension(pi: ExtensionAPI): void {
|
|
|
61
53
|
async execute(_toolCallId, params: SessionAskToolParams, signal, onUpdate, ctx) {
|
|
62
54
|
const sessionId = params.session.trim();
|
|
63
55
|
if (!sessionId) {
|
|
64
|
-
|
|
56
|
+
throw new Error("session_ask requires a session id.");
|
|
65
57
|
}
|
|
66
58
|
|
|
67
59
|
const question = params.question.trim();
|
|
68
60
|
if (!question) {
|
|
69
|
-
|
|
61
|
+
throw new Error("session_ask requires a question.");
|
|
70
62
|
}
|
|
71
63
|
|
|
72
64
|
const resolvedTarget = resolveSessionAskTarget(sessionId, settings.index.path);
|
|
73
65
|
if (!resolvedTarget.resolved) {
|
|
74
|
-
|
|
75
|
-
error: true,
|
|
76
|
-
sessionId,
|
|
77
|
-
question,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (!ctx.model) {
|
|
82
|
-
return errorResult("No active model is available for session_ask.", { error: true });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
|
|
86
|
-
if (!auth.ok || !auth.apiKey) {
|
|
87
|
-
return errorResult(`No API key is available for ${ctx.model.provider}/${ctx.model.id}.`, {
|
|
88
|
-
error: true,
|
|
89
|
-
});
|
|
66
|
+
throw new Error(resolvedTarget.error ?? "Unable to resolve session id.");
|
|
90
67
|
}
|
|
91
68
|
|
|
92
69
|
const progressDetails: SessionAskToolDetails = {
|
|
@@ -100,68 +77,41 @@ export default function sessionAskExtension(pi: ExtensionAPI): void {
|
|
|
100
77
|
details: progressDetails,
|
|
101
78
|
});
|
|
102
79
|
|
|
103
|
-
let rendered: RenderedSessionTree;
|
|
104
|
-
try {
|
|
105
|
-
rendered = renderSessionTreeMarkdown(resolvedTarget.resolved.sessionPath);
|
|
106
|
-
} catch (error) {
|
|
107
|
-
return errorResult(formatSessionAskLoadError(resolvedTarget.resolved.sessionPath, error), {
|
|
108
|
-
error: true,
|
|
109
|
-
sessionId,
|
|
110
|
-
sessionPath: resolvedTarget.resolved.sessionPath,
|
|
111
|
-
question,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const loadedDetails: SessionAskToolDetails = {
|
|
116
|
-
question,
|
|
117
|
-
sessionId: rendered.sessionId,
|
|
118
|
-
sessionName: rendered.sessionName,
|
|
119
|
-
sessionPath: resolvedTarget.resolved.sessionPath,
|
|
120
|
-
};
|
|
121
80
|
onUpdate?.({
|
|
122
81
|
content: [
|
|
123
82
|
{
|
|
124
83
|
type: "text",
|
|
125
|
-
text: formatSessionAskHeader(
|
|
84
|
+
text: formatSessionAskHeader(
|
|
85
|
+
resolvedTarget.resolved.sessionId,
|
|
86
|
+
resolvedTarget.resolved.sessionName,
|
|
87
|
+
question,
|
|
88
|
+
),
|
|
126
89
|
},
|
|
127
90
|
],
|
|
128
|
-
details:
|
|
91
|
+
details: progressDetails,
|
|
129
92
|
});
|
|
130
93
|
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const response = await complete(
|
|
143
|
-
ctx.model,
|
|
144
|
-
{ systemPrompt: SESSION_ASK_SYSTEM_PROMPT, messages: [userMessage] },
|
|
145
|
-
signal
|
|
146
|
-
? {
|
|
147
|
-
apiKey: auth.apiKey,
|
|
148
|
-
...(auth.headers ? { headers: auth.headers } : {}),
|
|
149
|
-
signal,
|
|
150
|
-
}
|
|
151
|
-
: {
|
|
152
|
-
apiKey: auth.apiKey,
|
|
153
|
-
...(auth.headers ? { headers: auth.headers } : {}),
|
|
154
|
-
},
|
|
155
|
-
);
|
|
94
|
+
const agentResult = await runSessionAskAgent({
|
|
95
|
+
ctx,
|
|
96
|
+
target: resolvedTarget.resolved,
|
|
97
|
+
question,
|
|
98
|
+
indexPath: settings.index.path,
|
|
99
|
+
askSettings: settings.ask,
|
|
100
|
+
thinkingLevel: pi.getThinkingLevel(),
|
|
101
|
+
signal,
|
|
102
|
+
});
|
|
156
103
|
|
|
157
|
-
if (
|
|
158
|
-
|
|
104
|
+
if (signal?.aborted) {
|
|
105
|
+
throw new Error("Session ask was cancelled.");
|
|
159
106
|
}
|
|
160
107
|
|
|
161
|
-
const answer =
|
|
108
|
+
const answer = agentResult?.answer || "Could not determine an answer from the session.";
|
|
109
|
+
const relevantFiles = agentResult?.relevantFiles ?? [];
|
|
162
110
|
|
|
163
111
|
const details: SessionAskToolDetails = {
|
|
164
112
|
answer,
|
|
113
|
+
debugSessionPath: agentResult?.debugSessionPath,
|
|
114
|
+
relevantFiles,
|
|
165
115
|
sessionId: resolvedTarget.resolved.sessionId,
|
|
166
116
|
sessionName: resolvedTarget.resolved.sessionName,
|
|
167
117
|
sessionPath: resolvedTarget.resolved.sessionPath,
|
|
@@ -172,21 +122,29 @@ export default function sessionAskExtension(pi: ExtensionAPI): void {
|
|
|
172
122
|
{
|
|
173
123
|
type: "text",
|
|
174
124
|
text: [
|
|
175
|
-
formatSessionAskHeader(
|
|
176
|
-
|
|
125
|
+
formatSessionAskHeader(
|
|
126
|
+
resolvedTarget.resolved.sessionId,
|
|
127
|
+
resolvedTarget.resolved.sessionName,
|
|
128
|
+
question,
|
|
129
|
+
),
|
|
130
|
+
formatSessionAskAnswer(answer, relevantFiles, agentResult?.debugSessionPath),
|
|
177
131
|
].join("\n\n"),
|
|
178
132
|
},
|
|
179
133
|
],
|
|
180
134
|
details,
|
|
181
135
|
};
|
|
182
136
|
},
|
|
183
|
-
renderResult(result, { expanded, isPartial }, theme) {
|
|
137
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
184
138
|
const details = result.details as SessionAskToolDetails | undefined;
|
|
185
139
|
const content = result.content[0];
|
|
186
140
|
if (content?.type !== "text") {
|
|
187
141
|
return new Text(theme.fg("error", "No session output"), 0, 0);
|
|
188
142
|
}
|
|
189
143
|
|
|
144
|
+
if (context.isError) {
|
|
145
|
+
return new Text(theme.fg("error", content.text), 0, 0);
|
|
146
|
+
}
|
|
147
|
+
|
|
190
148
|
if (isPartial) {
|
|
191
149
|
const lines = [theme.bold(theme.fg("warning", "Reading session..."))];
|
|
192
150
|
if (details?.sessionId || details?.sessionName) {
|
|
@@ -199,14 +157,6 @@ export default function sessionAskExtension(pi: ExtensionAPI): void {
|
|
|
199
157
|
return new Text(lines.join("\n"), 0, 0);
|
|
200
158
|
}
|
|
201
159
|
|
|
202
|
-
if (details?.cancelled) {
|
|
203
|
-
return new Text(theme.fg("warning", content.text), 0, 0);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (details?.error) {
|
|
207
|
-
return new Text(theme.fg("error", content.text), 0, 0);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
160
|
const answer = (details?.answer ?? "").trim() || "No answer generated.";
|
|
211
161
|
const identity = formatSessionTitleOrShortId(details?.sessionName, details?.sessionId);
|
|
212
162
|
const lines = [`title: ${theme.bold(identity)}`];
|
|
@@ -234,30 +184,20 @@ function resolveSessionAskTarget(
|
|
|
234
184
|
};
|
|
235
185
|
}
|
|
236
186
|
|
|
237
|
-
const status = getIndexStatus(indexPath);
|
|
238
|
-
if (!status.exists || status.schemaVersion !== INDEX_SCHEMA_VERSION) {
|
|
239
|
-
return {
|
|
240
|
-
error: `Session index missing or incompatible at ${indexPath}. Run /session-index and press r to rebuild it.`,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const db = openIndexDatabase(status.dbPath, { create: false });
|
|
245
187
|
try {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
188
|
+
return withSessionIndex(indexPath, { mode: "read", required: true }, ({ db }) => {
|
|
189
|
+
const row = getSessionById(db, sessionId);
|
|
190
|
+
if (!row) {
|
|
191
|
+
return { error: `No indexed session found for id: ${sessionId}` };
|
|
192
|
+
}
|
|
250
193
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
194
|
+
return { resolved: row };
|
|
195
|
+
});
|
|
196
|
+
} catch (error) {
|
|
197
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
254
198
|
}
|
|
255
199
|
}
|
|
256
200
|
|
|
257
|
-
function collectTextBlocks(content: Array<{ type: string; text?: string }>): string[] {
|
|
258
|
-
return content.filter(isTextContentBlock).map((block) => block.text);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
201
|
function formatSessionAskHeader(sessionId: string, sessionName: string, question: string): string {
|
|
262
202
|
return [
|
|
263
203
|
`session: ${sessionId}`,
|
|
@@ -266,14 +206,20 @@ function formatSessionAskHeader(sessionId: string, sessionName: string, question
|
|
|
266
206
|
].join("\n");
|
|
267
207
|
}
|
|
268
208
|
|
|
269
|
-
function
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
209
|
+
function formatSessionAskAnswer(
|
|
210
|
+
answer: string,
|
|
211
|
+
relevantFiles: SessionAskRelevantFile[],
|
|
212
|
+
debugSessionPath: string | undefined,
|
|
213
|
+
): string {
|
|
214
|
+
const lines = [answer || "No answer generated."];
|
|
215
|
+
if (relevantFiles.length > 0) {
|
|
216
|
+
lines.push("", "Relevant files:");
|
|
217
|
+
lines.push(...relevantFiles.map((file) => `- ${file.path}: ${file.reason}`));
|
|
218
|
+
}
|
|
219
|
+
if (debugSessionPath) {
|
|
220
|
+
lines.push("", `debug_session: ${debugSessionPath}`);
|
|
221
|
+
}
|
|
222
|
+
return lines.join("\n");
|
|
277
223
|
}
|
|
278
224
|
|
|
279
225
|
function formatSessionAskAnswerPreview(answer: string, expanded: boolean, theme: Theme): string[] {
|
|
@@ -282,17 +228,19 @@ function formatSessionAskAnswerPreview(answer: string, expanded: boolean, theme:
|
|
|
282
228
|
return lines;
|
|
283
229
|
}
|
|
284
230
|
|
|
285
|
-
return [
|
|
231
|
+
return [
|
|
232
|
+
...lines.slice(0, COLLAPSED_ANSWER_PREVIEW_ROWS),
|
|
233
|
+
formatOverflowHint(lines.length - COLLAPSED_ANSWER_PREVIEW_ROWS, lines.length, theme),
|
|
234
|
+
];
|
|
286
235
|
}
|
|
287
236
|
|
|
288
|
-
function
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return `Unable to load session file: ${sessionPath}`;
|
|
237
|
+
function formatOverflowHint(remaining: number, total: number, theme: Theme): string {
|
|
238
|
+
return `${theme.fg("muted", `... (${remaining} more lines, ${total} total,`)} ${theme.fg(
|
|
239
|
+
"dim",
|
|
240
|
+
formatKeyHint("app.tools.expand"),
|
|
241
|
+
)}${theme.fg("muted", " to expand)")}`;
|
|
294
242
|
}
|
|
295
243
|
|
|
296
|
-
function
|
|
297
|
-
return
|
|
244
|
+
function formatKeyHint(keybinding: Keybinding): string {
|
|
245
|
+
return getKeybindings().getKeys(keybinding).join("/");
|
|
298
246
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { AutocompleteItem } from "@earendil-works/pi-tui";
|
|
3
|
-
import { isTuiMode } from "../shared/pi-mode.
|
|
3
|
+
import { isTuiMode } from "../shared/pi-mode.ts";
|
|
4
4
|
|
|
5
5
|
export const TITLE_USAGE = "Usage: /title [this|folder|pi] [-f]";
|
|
6
6
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { buildSessionContext, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { AutoTitleSettings } from "../shared/settings.
|
|
3
|
-
import type { AutoTitleFailure } from "./generate.
|
|
2
|
+
import type { AutoTitleSettings } from "../shared/settings.ts";
|
|
3
|
+
import type { AutoTitleFailure } from "./generate.ts";
|
|
4
4
|
import {
|
|
5
5
|
type AutoTitleMode,
|
|
6
6
|
type AutoTitlePersistedState,
|
|
7
7
|
type AutoTitleTrigger,
|
|
8
8
|
createAutoTitleState,
|
|
9
9
|
getLatestAutoTitleState,
|
|
10
|
-
} from "./state.
|
|
10
|
+
} from "./state.ts";
|
|
11
11
|
|
|
12
12
|
export interface SessionAutoTitleStateSnapshot {
|
|
13
13
|
currentSessionFile: string | undefined;
|
|
@@ -4,8 +4,8 @@ import { join } from "node:path";
|
|
|
4
4
|
import type { Api, Model, TextContent, UserMessage } from "@earendil-works/pi-ai";
|
|
5
5
|
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
6
6
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import type { AutoTitleContext } from "./context.
|
|
8
|
-
import type { AutoTitleTrigger } from "./state.
|
|
7
|
+
import type { AutoTitleContext } from "./context.ts";
|
|
8
|
+
import type { AutoTitleTrigger } from "./state.ts";
|
|
9
9
|
|
|
10
10
|
const AUTO_TITLE_REQUEST_TIMEOUT_MS = 15_000;
|
|
11
11
|
const AUTO_TITLE_MAX_TOKENS = 64;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import {
|
|
3
|
+
import { findModelByReference } from "../shared/model.ts";
|
|
4
|
+
import { ModelReference } from "../shared/settings.ts";
|
|
4
5
|
|
|
5
6
|
const DEFAULT_AUTO_TITLE_FALLBACK_MODELS: readonly ModelReference[] = [
|
|
6
7
|
new ModelReference("google", "gemini-flash-lite-latest"),
|
|
@@ -22,7 +23,7 @@ export function resolveAutoTitleModel(
|
|
|
22
23
|
const availableModels = ctx.modelRegistry.getAvailable();
|
|
23
24
|
|
|
24
25
|
if (configuredModel) {
|
|
25
|
-
const configuredMatch =
|
|
26
|
+
const configuredMatch = findModelByReference(availableModels, configuredModel);
|
|
26
27
|
if (configuredMatch) {
|
|
27
28
|
return {
|
|
28
29
|
model: configuredMatch,
|
|
@@ -32,7 +33,7 @@ export function resolveAutoTitleModel(
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
for (const fallbackReference of DEFAULT_AUTO_TITLE_FALLBACK_MODELS) {
|
|
35
|
-
const fallbackMatch =
|
|
36
|
+
const fallbackMatch = findModelByReference(availableModels, fallbackReference);
|
|
36
37
|
if (fallbackMatch) {
|
|
37
38
|
return {
|
|
38
39
|
model: fallbackMatch,
|
|
@@ -50,12 +51,3 @@ export function resolveAutoTitleModel(
|
|
|
50
51
|
source: "current",
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
-
function findMatchingModel(
|
|
55
|
-
availableModels: Model<Api>[],
|
|
56
|
-
reference: ModelReference,
|
|
57
|
-
): Model<Api> | undefined {
|
|
58
|
-
return availableModels.find(
|
|
59
|
-
(model) => model.provider === reference.provider && model.id === reference.modelId,
|
|
60
|
-
);
|
|
61
|
-
}
|
|
@@ -6,19 +6,19 @@ import type {
|
|
|
6
6
|
SessionInfo,
|
|
7
7
|
} from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import type { RetitleMode, RetitleScope } from "./command.
|
|
10
|
-
import { buildAutoTitleContext } from "./context.
|
|
11
|
-
import type { AutoTitleTriggerPlan, SessionAutoTitleController } from "./controller.
|
|
9
|
+
import type { RetitleMode, RetitleScope } from "./command.ts";
|
|
10
|
+
import { buildAutoTitleContext } from "./context.ts";
|
|
11
|
+
import type { AutoTitleTriggerPlan, SessionAutoTitleController } from "./controller.ts";
|
|
12
12
|
import {
|
|
13
13
|
type AutoTitleGenerationResult,
|
|
14
14
|
createAutoTitleFailure,
|
|
15
15
|
generateAutoTitle,
|
|
16
|
-
} from "./generate.
|
|
16
|
+
} from "./generate.ts";
|
|
17
17
|
import {
|
|
18
18
|
AUTO_TITLE_STATE_CUSTOM_TYPE,
|
|
19
19
|
type AutoTitlePersistedState,
|
|
20
20
|
createAutoTitleState,
|
|
21
|
-
} from "./state.
|
|
21
|
+
} from "./state.ts";
|
|
22
22
|
|
|
23
23
|
export interface RetitleScopeScan {
|
|
24
24
|
scope: Exclude<RetitleScope, "this">;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { type Static, Type } from "typebox";
|
|
3
|
-
import { safeParseTypeBoxValue } from "../shared/typebox.
|
|
3
|
+
import { safeParseTypeBoxValue } from "../shared/typebox.ts";
|
|
4
4
|
|
|
5
5
|
export const AUTO_TITLE_STATE_CUSTOM_TYPE = "pi-sessions.auto-title";
|
|
6
6
|
export const AUTO_TITLE_STATE_VERSION = 1;
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
} from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import type { Focusable, TUI } from "@earendil-works/pi-tui";
|
|
9
9
|
import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
10
|
-
import type { RetitleCommandOutcome, RetitleMode, RetitleScope } from "./command.
|
|
11
|
-
import type { AutoRetitleStatus, SessionAutoTitleController } from "./controller.
|
|
12
|
-
import type { AutoTitleFailure } from "./generate.
|
|
10
|
+
import type { RetitleCommandOutcome, RetitleMode, RetitleScope } from "./command.ts";
|
|
11
|
+
import type { AutoRetitleStatus, SessionAutoTitleController } from "./controller.ts";
|
|
12
|
+
import type { AutoTitleFailure } from "./generate.ts";
|
|
13
13
|
import {
|
|
14
14
|
buildBulkRetitleMessage,
|
|
15
15
|
buildRetitleScopeScan,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
type RetitleScopeScan,
|
|
21
21
|
runBulkRetitle,
|
|
22
22
|
runRetitlePlan,
|
|
23
|
-
} from "./retitle.
|
|
23
|
+
} from "./retitle.ts";
|
|
24
24
|
|
|
25
25
|
interface RetitleWizardOptions {
|
|
26
26
|
initialInvocation?: {
|
|
@@ -11,29 +11,29 @@ import {
|
|
|
11
11
|
getRetitleArgumentCompletions,
|
|
12
12
|
type RetitleCommandInvocation,
|
|
13
13
|
type RetitleCommandOutcome,
|
|
14
|
-
} from "./session-auto-title/command.
|
|
14
|
+
} from "./session-auto-title/command.ts";
|
|
15
15
|
import {
|
|
16
16
|
createSessionAutoTitleController,
|
|
17
17
|
type SessionAutoTitleController,
|
|
18
|
-
} from "./session-auto-title/controller.
|
|
19
|
-
import { resolveAutoTitleModel } from "./session-auto-title/model.
|
|
18
|
+
} from "./session-auto-title/controller.ts";
|
|
19
|
+
import { resolveAutoTitleModel } from "./session-auto-title/model.ts";
|
|
20
20
|
import {
|
|
21
21
|
buildRetitleScopeScan,
|
|
22
22
|
notifyBulkRetitleResult,
|
|
23
23
|
persistAutoTitleState,
|
|
24
24
|
runBulkRetitle,
|
|
25
25
|
runRetitlePlan,
|
|
26
|
-
} from "./session-auto-title/retitle.
|
|
27
|
-
import { showRetitleWizard } from "./session-auto-title/wizard.
|
|
28
|
-
import { isTuiMode } from "./shared/pi-mode.
|
|
29
|
-
import { loadSettings } from "./shared/settings.
|
|
26
|
+
} from "./session-auto-title/retitle.ts";
|
|
27
|
+
import { showRetitleWizard } from "./session-auto-title/wizard.ts";
|
|
28
|
+
import { isTuiMode } from "./shared/pi-mode.ts";
|
|
29
|
+
import { loadSettings } from "./shared/settings.ts";
|
|
30
30
|
|
|
31
31
|
export {
|
|
32
32
|
createSessionAutoTitleCommandHandler,
|
|
33
33
|
getRetitleArgumentCompletions,
|
|
34
34
|
parseRetitleCommand,
|
|
35
35
|
TITLE_USAGE,
|
|
36
|
-
} from "./session-auto-title/command.
|
|
36
|
+
} from "./session-auto-title/command.ts";
|
|
37
37
|
|
|
38
38
|
interface TitleRunState {
|
|
39
39
|
controller: SessionAutoTitleController;
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
serializeConversation,
|
|
20
20
|
} from "@earendil-works/pi-coding-agent";
|
|
21
21
|
import { type Static, Type } from "typebox";
|
|
22
|
-
import { parseTypeBoxValue } from "../shared/typebox.
|
|
22
|
+
import { parseTypeBoxValue } from "../shared/typebox.ts";
|
|
23
23
|
|
|
24
24
|
const MAX_RELEVANT_FILES = 12;
|
|
25
25
|
const MAX_OPEN_QUESTIONS = 8;
|
|
@@ -88,6 +88,7 @@ export async function generateHandoffDraft(
|
|
|
88
88
|
goal: string,
|
|
89
89
|
thinkingLevel: ThinkingLevel | undefined,
|
|
90
90
|
signal?: AbortSignal,
|
|
91
|
+
requestResponse = false,
|
|
91
92
|
): Promise<HandoffDraftResult | undefined> {
|
|
92
93
|
if (!ctx.model) {
|
|
93
94
|
throw new Error("No model is available for handoff.");
|
|
@@ -99,6 +100,7 @@ export async function generateHandoffDraft(
|
|
|
99
100
|
goal,
|
|
100
101
|
thinkingLevel,
|
|
101
102
|
signal,
|
|
103
|
+
requestResponse,
|
|
102
104
|
);
|
|
103
105
|
}
|
|
104
106
|
|
|
@@ -108,6 +110,7 @@ export async function generateHandoffDraftFromSessionManager(
|
|
|
108
110
|
goal: string,
|
|
109
111
|
thinkingLevel: ThinkingLevel | undefined,
|
|
110
112
|
signal?: AbortSignal,
|
|
113
|
+
requestResponse = false,
|
|
111
114
|
): Promise<HandoffDraftResult | undefined> {
|
|
112
115
|
if (!ctx.model) {
|
|
113
116
|
throw new Error("No model is available for handoff.");
|
|
@@ -139,7 +142,7 @@ export async function generateHandoffDraftFromSessionManager(
|
|
|
139
142
|
const sessionPath = sourceSessionManager.getSessionFile();
|
|
140
143
|
|
|
141
144
|
return {
|
|
142
|
-
draft: assembleHandoffDraft(sessionId, sessionPath, handoffContext, goal),
|
|
145
|
+
draft: assembleHandoffDraft(sessionId, sessionPath, handoffContext, goal, requestResponse),
|
|
143
146
|
context: handoffContext,
|
|
144
147
|
sessionId,
|
|
145
148
|
sessionPath,
|
|
@@ -241,8 +244,9 @@ export function assembleHandoffDraft(
|
|
|
241
244
|
sessionPath: string | undefined,
|
|
242
245
|
handoffContext: HandoffContext,
|
|
243
246
|
goal: string,
|
|
247
|
+
requestResponse = false,
|
|
244
248
|
): string {
|
|
245
|
-
const sections = [buildContinuityLine(sessionId, sessionPath)];
|
|
249
|
+
const sections = [buildContinuityLine(sessionId, sessionPath, requestResponse)];
|
|
246
250
|
const nextTask = handoffContext.nextTask.trim() || goal.trim();
|
|
247
251
|
|
|
248
252
|
if (nextTask) {
|
|
@@ -326,8 +330,17 @@ function extractHandoffContextFromArguments(
|
|
|
326
330
|
};
|
|
327
331
|
}
|
|
328
332
|
|
|
329
|
-
function buildContinuityLine(
|
|
330
|
-
|
|
333
|
+
function buildContinuityLine(
|
|
334
|
+
sessionId: string,
|
|
335
|
+
_sessionPath: string | undefined,
|
|
336
|
+
requestResponse: boolean,
|
|
337
|
+
): string {
|
|
338
|
+
const base = `Continuing work from session ${sessionId}. When you lack specific information you can use session_ask.`;
|
|
339
|
+
if (!requestResponse) {
|
|
340
|
+
return base;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return `${base} When this work is complete, send that session a completion report with session_send_message.`;
|
|
331
344
|
}
|
|
332
345
|
|
|
333
346
|
function normalizeStringArray(value: unknown, limit: number): string[] {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
2
|
import type { CustomEntry, SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { type Static, Type } from "typebox";
|
|
4
|
-
import { safeParseTypeBoxValue } from "../shared/typebox.
|
|
4
|
+
import { safeParseTypeBoxValue } from "../shared/typebox.ts";
|
|
5
5
|
|
|
6
6
|
export const HANDOFF_METADATA_CUSTOM_TYPE = "pi-sessions.handoff";
|
|
7
7
|
export const HANDOFF_BOOTSTRAP_ENV = "PI_SESSIONS_HANDOFF_BOOTSTRAP";
|
|
@@ -30,6 +30,7 @@ export const CHILD_GENERATED_HANDOFF_BOOTSTRAP_SCHEMA = Type.Object({
|
|
|
30
30
|
goal: Type.String(),
|
|
31
31
|
title: Type.String(),
|
|
32
32
|
parentSessionFile: Type.String(),
|
|
33
|
+
requestResponse: Type.Optional(Type.Boolean()),
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
export const HANDOFF_BOOTSTRAP_SCHEMA = Type.Union([
|
|
@@ -80,6 +81,7 @@ export function createChildGeneratedHandoffBootstrap(options: {
|
|
|
80
81
|
goal: string;
|
|
81
82
|
title: string;
|
|
82
83
|
parentSessionFile: string;
|
|
84
|
+
requestResponse?: boolean | undefined;
|
|
83
85
|
}): ChildGeneratedHandoffBootstrap {
|
|
84
86
|
return {
|
|
85
87
|
mode: "generate",
|
|
@@ -87,6 +89,7 @@ export function createChildGeneratedHandoffBootstrap(options: {
|
|
|
87
89
|
goal: options.goal.trim(),
|
|
88
90
|
title: options.title.trim(),
|
|
89
91
|
parentSessionFile: options.parentSessionFile,
|
|
92
|
+
...(options.requestResponse === undefined ? {} : { requestResponse: options.requestResponse }),
|
|
90
93
|
};
|
|
91
94
|
}
|
|
92
95
|
|