pi-sessions 0.7.1 → 0.8.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 +1 -0
- package/extensions/session-ask.ts +2 -3
- package/extensions/session-auto-title/generate.ts +2 -2
- package/extensions/session-auto-title/retitle.ts +18 -3
- package/extensions/session-auto-title/wizard.ts +5 -0
- package/extensions/session-auto-title.ts +15 -1
- package/extensions/session-handoff.ts +4 -5
- package/extensions/session-messaging/pi/tools.ts +1 -1
- package/extensions/session-search.ts +3 -3
- package/extensions/shared/settings.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -190,6 +190,7 @@ To change auto-titling settings, edit `~/.pi/agent/settings.json`:
|
|
|
190
190
|
"sessions": {
|
|
191
191
|
"autoTitle": {
|
|
192
192
|
"refreshTurns": 4,
|
|
193
|
+
"timeoutSecs": 15,
|
|
193
194
|
"model": "anthropic/claude-haiku-4-5",
|
|
194
195
|
"prompt": "Custom prompt that overrides the default."
|
|
195
196
|
}
|
|
@@ -39,9 +39,8 @@ export default function sessionAskExtension(pi: ExtensionAPI): void {
|
|
|
39
39
|
name: "session_ask",
|
|
40
40
|
label: "Session Ask",
|
|
41
41
|
description: "Interrogate a Pi session",
|
|
42
|
-
promptSnippet:
|
|
43
|
-
|
|
44
|
-
promptGuidelines: ["Prefer focused follow-up questions over broad recap requests"],
|
|
42
|
+
promptSnippet: "Recall information, decisions, or reasoning from a Pi session by id",
|
|
43
|
+
promptGuidelines: ["Use session_ask with focused questions rather than broad recap requests."],
|
|
45
44
|
parameters: Type.Object({
|
|
46
45
|
session: Type.String({
|
|
47
46
|
description: "Bare UUID for the session",
|
|
@@ -7,7 +7,6 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
7
7
|
import type { AutoTitleContext } from "./context.ts";
|
|
8
8
|
import type { AutoTitleTrigger } from "./state.ts";
|
|
9
9
|
|
|
10
|
-
const AUTO_TITLE_REQUEST_TIMEOUT_MS = 15_000;
|
|
11
10
|
const AUTO_TITLE_MAX_TOKENS = 64;
|
|
12
11
|
const AUTO_TITLE_CHAR_MAX = 80;
|
|
13
12
|
|
|
@@ -34,6 +33,7 @@ export async function generateAutoTitle(
|
|
|
34
33
|
context: AutoTitleContext,
|
|
35
34
|
trigger: AutoTitleTrigger,
|
|
36
35
|
systemPrompt: string,
|
|
36
|
+
timeoutMs: number,
|
|
37
37
|
): Promise<AutoTitleGenerationResult> {
|
|
38
38
|
if (!context.conversationText) {
|
|
39
39
|
return {
|
|
@@ -64,7 +64,7 @@ export async function generateAutoTitle(
|
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
const abortController = new AbortController();
|
|
67
|
-
const timeoutId = setTimeout(() => abortController.abort(),
|
|
67
|
+
const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);
|
|
68
68
|
|
|
69
69
|
try {
|
|
70
70
|
const requestContext = {
|
|
@@ -58,6 +58,7 @@ export async function runBulkRetitle(
|
|
|
58
58
|
mode: RetitleMode,
|
|
59
59
|
getSessionEpoch: () => number,
|
|
60
60
|
systemPrompt: string,
|
|
61
|
+
timeoutMs: number,
|
|
61
62
|
): Promise<BulkRetitleResult> {
|
|
62
63
|
const result: BulkRetitleResult = {
|
|
63
64
|
attempted: 0,
|
|
@@ -85,6 +86,7 @@ export async function runBulkRetitle(
|
|
|
85
86
|
model,
|
|
86
87
|
isManual: true,
|
|
87
88
|
systemPrompt,
|
|
89
|
+
timeoutMs,
|
|
88
90
|
getSessionEpoch,
|
|
89
91
|
notifyOnSuccess: false,
|
|
90
92
|
});
|
|
@@ -92,7 +94,7 @@ export async function runBulkRetitle(
|
|
|
92
94
|
continue;
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
const outcome = await retitleStoredSession(ctx, model, session.path, systemPrompt);
|
|
97
|
+
const outcome = await retitleStoredSession(ctx, model, session.path, systemPrompt, timeoutMs);
|
|
96
98
|
result[outcome] += 1;
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -165,6 +167,7 @@ export interface RetitlePlanOptions {
|
|
|
165
167
|
model: Model<Api> | undefined;
|
|
166
168
|
isManual: boolean;
|
|
167
169
|
systemPrompt: string;
|
|
170
|
+
timeoutMs: number;
|
|
168
171
|
existingPlan?: AutoTitleTriggerPlan;
|
|
169
172
|
getSessionEpoch?: () => number;
|
|
170
173
|
notifyOnSuccess?: boolean;
|
|
@@ -173,8 +176,17 @@ export interface RetitlePlanOptions {
|
|
|
173
176
|
export async function runRetitlePlan(
|
|
174
177
|
options: RetitlePlanOptions,
|
|
175
178
|
): Promise<AutoTitleGenerationResult> {
|
|
176
|
-
const {
|
|
177
|
-
|
|
179
|
+
const {
|
|
180
|
+
pi,
|
|
181
|
+
controller,
|
|
182
|
+
ctx,
|
|
183
|
+
model,
|
|
184
|
+
isManual,
|
|
185
|
+
systemPrompt,
|
|
186
|
+
timeoutMs,
|
|
187
|
+
existingPlan,
|
|
188
|
+
getSessionEpoch,
|
|
189
|
+
} = options;
|
|
178
190
|
const notifyOnSuccess = options.notifyOnSuccess ?? isManual;
|
|
179
191
|
|
|
180
192
|
const plan = existingPlan ?? controller.handleManualRetitle(ctx);
|
|
@@ -211,6 +223,7 @@ export async function runRetitlePlan(
|
|
|
211
223
|
titleContext,
|
|
212
224
|
plan.reason,
|
|
213
225
|
systemPrompt,
|
|
226
|
+
timeoutMs,
|
|
214
227
|
);
|
|
215
228
|
if (!generatedTitle.ok) {
|
|
216
229
|
return generatedTitle;
|
|
@@ -257,6 +270,7 @@ async function retitleStoredSession(
|
|
|
257
270
|
model: Model<Api>,
|
|
258
271
|
sessionPath: string,
|
|
259
272
|
systemPrompt: string,
|
|
273
|
+
timeoutMs: number,
|
|
260
274
|
): Promise<"retitled" | "unchanged" | "failed"> {
|
|
261
275
|
const sessionManager = SessionManager.open(sessionPath);
|
|
262
276
|
const currentTitle = sessionManager.getSessionName();
|
|
@@ -279,6 +293,7 @@ async function retitleStoredSession(
|
|
|
279
293
|
titleContext,
|
|
280
294
|
plan.reason,
|
|
281
295
|
systemPrompt,
|
|
296
|
+
timeoutMs,
|
|
282
297
|
);
|
|
283
298
|
if (!generatedTitle.ok) {
|
|
284
299
|
return "failed";
|
|
@@ -57,6 +57,7 @@ export async function showRetitleWizard(
|
|
|
57
57
|
model: Model<Api> | undefined,
|
|
58
58
|
getSessionEpoch: () => number,
|
|
59
59
|
systemPrompt: string,
|
|
60
|
+
timeoutMs: number,
|
|
60
61
|
options?: RetitleWizardOptions,
|
|
61
62
|
): Promise<RetitleCommandOutcome> {
|
|
62
63
|
return ctx.ui.custom<RetitleCommandOutcome>(
|
|
@@ -70,6 +71,7 @@ export async function showRetitleWizard(
|
|
|
70
71
|
model,
|
|
71
72
|
getSessionEpoch,
|
|
72
73
|
systemPrompt,
|
|
74
|
+
timeoutMs,
|
|
73
75
|
done,
|
|
74
76
|
options,
|
|
75
77
|
),
|
|
@@ -99,6 +101,7 @@ class RetitleWizardPanel implements Focusable {
|
|
|
99
101
|
private readonly model: Model<Api> | undefined,
|
|
100
102
|
private readonly getSessionEpoch: () => number,
|
|
101
103
|
private readonly systemPrompt: string,
|
|
104
|
+
private readonly timeoutMs: number,
|
|
102
105
|
private readonly done: (result: RetitleCommandOutcome) => void,
|
|
103
106
|
options?: RetitleWizardOptions,
|
|
104
107
|
) {
|
|
@@ -250,6 +253,7 @@ class RetitleWizardPanel implements Focusable {
|
|
|
250
253
|
model: this.model,
|
|
251
254
|
isManual: true,
|
|
252
255
|
systemPrompt: this.systemPrompt,
|
|
256
|
+
timeoutMs: this.timeoutMs,
|
|
253
257
|
getSessionEpoch: this.getSessionEpoch,
|
|
254
258
|
});
|
|
255
259
|
if (result.ok) {
|
|
@@ -310,6 +314,7 @@ class RetitleWizardPanel implements Focusable {
|
|
|
310
314
|
mode,
|
|
311
315
|
this.getSessionEpoch,
|
|
312
316
|
this.systemPrompt,
|
|
317
|
+
this.timeoutMs,
|
|
313
318
|
);
|
|
314
319
|
notifyBulkRetitleResult(this.ctx, scan, mode, result);
|
|
315
320
|
this.done(
|
|
@@ -75,6 +75,7 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
|
|
|
75
75
|
model,
|
|
76
76
|
invocation,
|
|
77
77
|
settings.autoTitle.prompt,
|
|
78
|
+
settings.autoTitle.timeoutMs,
|
|
78
79
|
);
|
|
79
80
|
},
|
|
80
81
|
),
|
|
@@ -104,6 +105,7 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
|
|
|
104
105
|
getSessionEpoch: () => sessionEpoch,
|
|
105
106
|
notifyOnSuccess: false,
|
|
106
107
|
systemPrompt: settings.autoTitle.prompt,
|
|
108
|
+
timeoutMs: settings.autoTitle.timeoutMs,
|
|
107
109
|
})
|
|
108
110
|
.then((outcome) => {
|
|
109
111
|
if (outcome.ok) {
|
|
@@ -139,6 +141,7 @@ async function handleTitleInvocation(
|
|
|
139
141
|
model: Model<Api> | undefined,
|
|
140
142
|
invocation: RetitleCommandInvocation,
|
|
141
143
|
systemPrompt: string,
|
|
144
|
+
timeoutMs: number,
|
|
142
145
|
): Promise<RetitleCommandOutcome> {
|
|
143
146
|
const retitleOpts = {
|
|
144
147
|
pi,
|
|
@@ -147,6 +150,7 @@ async function handleTitleInvocation(
|
|
|
147
150
|
model,
|
|
148
151
|
isManual: true,
|
|
149
152
|
systemPrompt,
|
|
153
|
+
timeoutMs,
|
|
150
154
|
getSessionEpoch: state.getSessionEpoch,
|
|
151
155
|
};
|
|
152
156
|
|
|
@@ -165,7 +169,15 @@ async function handleTitleInvocation(
|
|
|
165
169
|
return retitleCurrentSession();
|
|
166
170
|
}
|
|
167
171
|
|
|
168
|
-
return showRetitleWizard(
|
|
172
|
+
return showRetitleWizard(
|
|
173
|
+
pi,
|
|
174
|
+
state.controller,
|
|
175
|
+
ctx,
|
|
176
|
+
model,
|
|
177
|
+
state.getSessionEpoch,
|
|
178
|
+
systemPrompt,
|
|
179
|
+
timeoutMs,
|
|
180
|
+
);
|
|
169
181
|
}
|
|
170
182
|
|
|
171
183
|
if (invocation.scope === "this") {
|
|
@@ -180,6 +192,7 @@ async function handleTitleInvocation(
|
|
|
180
192
|
model,
|
|
181
193
|
state.getSessionEpoch,
|
|
182
194
|
systemPrompt,
|
|
195
|
+
timeoutMs,
|
|
183
196
|
{
|
|
184
197
|
initialInvocation: {
|
|
185
198
|
scope: invocation.scope,
|
|
@@ -201,6 +214,7 @@ async function handleTitleInvocation(
|
|
|
201
214
|
mode,
|
|
202
215
|
state.getSessionEpoch,
|
|
203
216
|
systemPrompt,
|
|
217
|
+
timeoutMs,
|
|
204
218
|
);
|
|
205
219
|
notifyBulkRetitleResult(ctx, scan, mode, result);
|
|
206
220
|
return "success";
|
|
@@ -87,16 +87,15 @@ export default function sessionHandoffExtension(pi: ExtensionAPI): void {
|
|
|
87
87
|
"Start a background pi session in a terminal split based on the current session",
|
|
88
88
|
promptGuidelines: [
|
|
89
89
|
"Use session_handoff only when it is clear the work should be forked to a new context.",
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"Only capable of a background handoff; to replace the current session, tell the user to run /handoff instead.",
|
|
90
|
+
"Reach for session_handoff by direction of the user, not as an unsolicited default.",
|
|
91
|
+
"session_handoff should only request a response when there is a specific ask-and-response expectation: the user asked for a report back, or this session needs the child result to continue. Leave it off by default and for independent background work.",
|
|
92
|
+
"session_handoff can only fork a background session; to replace the current session, tell the user to run /handoff instead.",
|
|
94
93
|
],
|
|
95
94
|
executionMode: "sequential",
|
|
96
95
|
parameters: Type.Object({
|
|
97
96
|
goal: Type.String({
|
|
98
97
|
description:
|
|
99
|
-
"Goal for the new session
|
|
98
|
+
"Goal for the new session. Capture enough detail to encompass the ask and any directions the next session should consider.",
|
|
100
99
|
}),
|
|
101
100
|
splitDirection: Type.Union(
|
|
102
101
|
[Type.Literal("left"), Type.Literal("right"), Type.Literal("up"), Type.Literal("down")],
|
|
@@ -14,7 +14,7 @@ export function registerSessionMessagingTools(
|
|
|
14
14
|
description: "Send a message to another live pi session",
|
|
15
15
|
promptSnippet: "Send a message to another live pi session",
|
|
16
16
|
promptGuidelines: [
|
|
17
|
-
"
|
|
17
|
+
"Before session_send_message, use session_search with live: true (no other filters) to list all live sessions and find the target.",
|
|
18
18
|
],
|
|
19
19
|
parameters: SEND_MESSAGE_PARAMS,
|
|
20
20
|
renderCall(args, theme, context) {
|
|
@@ -64,10 +64,10 @@ export default function sessionSearchExtension(
|
|
|
64
64
|
name: "session_search",
|
|
65
65
|
label: "Session Search",
|
|
66
66
|
description: "Search Pi sessions",
|
|
67
|
-
promptSnippet: "
|
|
67
|
+
promptSnippet: "Locate Pi sessions for follow-up",
|
|
68
68
|
promptGuidelines: [
|
|
69
|
-
"Omit
|
|
70
|
-
"
|
|
69
|
+
"Omit queries in session_search to list matching sessions chronologically.",
|
|
70
|
+
"After session_search finds a session id, switch to session_ask for questions about it.",
|
|
71
71
|
],
|
|
72
72
|
parameters: Type.Object({
|
|
73
73
|
query: Type.Optional(
|
|
@@ -7,6 +7,7 @@ import { type Static, Type } from "typebox";
|
|
|
7
7
|
import { parseTypeBoxValue } from "./typebox.ts";
|
|
8
8
|
|
|
9
9
|
export const DEFAULT_AUTO_TITLE_REFRESH_TURNS = 4;
|
|
10
|
+
export const DEFAULT_AUTO_TITLE_TIMEOUT_SECONDS = 15;
|
|
10
11
|
export const DEFAULT_AUTO_TITLE_PROMPT = `Name this coding session (under 80 chars). Be specific to what is being discussed. Your exact output will be displayed to the user, so make sure that it contains ONLY the title itself and nothing else.`;
|
|
11
12
|
const SESSION_FILE_SETTINGS_SCHEMA = Type.Object({
|
|
12
13
|
handoff: Type.Optional(
|
|
@@ -22,6 +23,7 @@ const SESSION_FILE_SETTINGS_SCHEMA = Type.Object({
|
|
|
22
23
|
autoTitle: Type.Optional(
|
|
23
24
|
Type.Object({
|
|
24
25
|
refreshTurns: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
26
|
+
timeoutSecs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
25
27
|
model: Type.Optional(Type.String()),
|
|
26
28
|
thinkingLevel: Type.Optional(Type.String()),
|
|
27
29
|
prompt: Type.Optional(Type.String()),
|
|
@@ -57,6 +59,7 @@ export interface AgentModelSettings {
|
|
|
57
59
|
|
|
58
60
|
export interface AutoTitleSettings extends AgentModelSettings {
|
|
59
61
|
refreshTurns: number;
|
|
62
|
+
timeoutMs: number;
|
|
60
63
|
prompt: string;
|
|
61
64
|
}
|
|
62
65
|
|
|
@@ -193,6 +196,7 @@ function resolveSessionSettings(fileSettings: SessionFileSettings): SessionSetti
|
|
|
193
196
|
autoTitle: {
|
|
194
197
|
...resolveAgentModelSettings(fileSettings.autoTitle),
|
|
195
198
|
refreshTurns: fileSettings.autoTitle?.refreshTurns ?? DEFAULT_AUTO_TITLE_REFRESH_TURNS,
|
|
199
|
+
timeoutMs: (fileSettings.autoTitle?.timeoutSecs ?? DEFAULT_AUTO_TITLE_TIMEOUT_SECONDS) * 1000,
|
|
196
200
|
prompt: normalizeAutoTitlePrompt(fileSettings.autoTitle?.prompt),
|
|
197
201
|
},
|
|
198
202
|
ask: {
|