pi-sessions 0.13.1 → 0.14.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 -1
- package/extensions/session-ask/install.ts +19 -4
- package/extensions/session-ask/tool-contract.ts +8 -0
- package/extensions/session-auto-title/wizard.ts +1 -4
- package/extensions/session-handoff/board.ts +1 -1
- package/extensions/session-handoff/install.ts +2 -0
- package/extensions/session-handoff/picker.ts +1 -1
- package/extensions/session-handoff/spawn.ts +2 -2
- package/extensions/session-messaging/pi/reachable-tool.ts +2 -0
- package/extensions/session-search/extract.ts +8 -2
- package/extensions/session-search/install.ts +27 -4
- package/extensions/session-search/tool-contract.ts +28 -5
- package/extensions/subagents/report.ts +1 -0
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -12,10 +12,12 @@ import { isExactSessionId } from "../shared/session-ui.ts";
|
|
|
12
12
|
import type { SessionSettings } from "../shared/settings.ts";
|
|
13
13
|
import { runSessionAskAgent } from "./agent.ts";
|
|
14
14
|
import { renderSessionAskResult } from "./renderer.ts";
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
import {
|
|
16
|
+
SESSION_ASK_OUTPUT_SCHEMA,
|
|
17
|
+
type SessionAskOutput,
|
|
18
|
+
type SessionAskProgressDetails,
|
|
19
|
+
type SessionAskRelevantFile,
|
|
20
|
+
type SessionAskResultDetails,
|
|
19
21
|
} from "./tool-contract.ts";
|
|
20
22
|
|
|
21
23
|
interface SessionAskToolParams {
|
|
@@ -39,6 +41,7 @@ export function installAsk(
|
|
|
39
41
|
name: "session_ask",
|
|
40
42
|
label: "Session Ask",
|
|
41
43
|
description: "Interrogate a Pi session transcript",
|
|
44
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
42
45
|
promptSnippet: "Recall information, decisions, or reasoning from a Pi session by id",
|
|
43
46
|
promptGuidelines: [
|
|
44
47
|
"Use session_ask with focused questions rather than broad recap requests.",
|
|
@@ -56,6 +59,7 @@ export function installAsk(
|
|
|
56
59
|
description: "What to find in that session's transcript",
|
|
57
60
|
}),
|
|
58
61
|
}),
|
|
62
|
+
outputSchema: SESSION_ASK_OUTPUT_SCHEMA,
|
|
59
63
|
async execute(_toolCallId, params: SessionAskToolParams, signal, onUpdate, ctx) {
|
|
60
64
|
const sessionId = params.session.trim();
|
|
61
65
|
if (!sessionId) {
|
|
@@ -110,6 +114,7 @@ export function installAsk(
|
|
|
110
114
|
},
|
|
111
115
|
],
|
|
112
116
|
details,
|
|
117
|
+
structuredContent: formatSessionAskOutput(details),
|
|
113
118
|
};
|
|
114
119
|
}
|
|
115
120
|
|
|
@@ -179,6 +184,7 @@ export function installAsk(
|
|
|
179
184
|
},
|
|
180
185
|
],
|
|
181
186
|
details,
|
|
187
|
+
structuredContent: formatSessionAskOutput(details),
|
|
182
188
|
};
|
|
183
189
|
},
|
|
184
190
|
renderResult: renderSessionAskResult,
|
|
@@ -217,6 +223,15 @@ function resolveSessionAskTarget(
|
|
|
217
223
|
}
|
|
218
224
|
}
|
|
219
225
|
|
|
226
|
+
function formatSessionAskOutput(details: SessionAskResultDetails): SessionAskOutput {
|
|
227
|
+
return {
|
|
228
|
+
sessionId: details.sessionId,
|
|
229
|
+
sessionName: details.sessionName,
|
|
230
|
+
answer: details.answer,
|
|
231
|
+
relevantFiles: details.relevantFiles,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
220
235
|
function formatSessionAskHeader(sessionId: string, sessionName: string, question: string): string {
|
|
221
236
|
return [
|
|
222
237
|
`session: ${sessionId}`,
|
|
@@ -22,6 +22,14 @@ export const SESSION_ASK_RESULT_DETAILS_SCHEMA = Type.Object({
|
|
|
22
22
|
sessionPath: Type.String({ minLength: 1 }),
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
+
export const SESSION_ASK_OUTPUT_SCHEMA = Type.Object({
|
|
26
|
+
sessionId: Type.String(),
|
|
27
|
+
sessionName: Type.String(),
|
|
28
|
+
answer: Type.String(),
|
|
29
|
+
relevantFiles: Type.Array(SESSION_ASK_RELEVANT_FILE_SCHEMA),
|
|
30
|
+
});
|
|
31
|
+
|
|
25
32
|
export type SessionAskRelevantFile = Static<typeof SESSION_ASK_RELEVANT_FILE_SCHEMA>;
|
|
26
33
|
export type SessionAskProgressDetails = Static<typeof SESSION_ASK_PROGRESS_DETAILS_SCHEMA>;
|
|
27
34
|
export type SessionAskResultDetails = Static<typeof SESSION_ASK_RESULT_DETAILS_SCHEMA>;
|
|
35
|
+
export type SessionAskOutput = Static<typeof SESSION_ASK_OUTPUT_SCHEMA>;
|
|
@@ -165,10 +165,7 @@ class RetitleWizardPanel implements Focusable {
|
|
|
165
165
|
}
|
|
166
166
|
if (result) return result;
|
|
167
167
|
if (event.type === "wheel" && this.step.kind === "scope" && event.wheelDelta) {
|
|
168
|
-
this.selectedIndex = Math.max(
|
|
169
|
-
0,
|
|
170
|
-
Math.min(2, this.selectedIndex + Math.sign(event.wheelDelta)),
|
|
171
|
-
);
|
|
168
|
+
this.selectedIndex = Math.max(0, Math.min(2, this.selectedIndex + event.wheelDelta));
|
|
172
169
|
this.requestRender();
|
|
173
170
|
return { handled: true };
|
|
174
171
|
}
|
|
@@ -203,7 +203,7 @@ export class HandoffBoard implements Focusable {
|
|
|
203
203
|
return { handled: true };
|
|
204
204
|
}
|
|
205
205
|
if (event.type === "wheel" && event.wheelDelta && this.rowHitAt(event)) {
|
|
206
|
-
if (!this.busy) this.moveSelection(
|
|
206
|
+
if (!this.busy) this.moveSelection(event.wheelDelta);
|
|
207
207
|
return { handled: true };
|
|
208
208
|
}
|
|
209
209
|
return undefined;
|
|
@@ -68,6 +68,8 @@ export function installHandoff(
|
|
|
68
68
|
name: "session_handoff",
|
|
69
69
|
label: "Session Handoff",
|
|
70
70
|
description: "Start a new Pi session with a self-contained task.",
|
|
71
|
+
// Nested calls write no toolResult entry, so the board would never see the launch receipt.
|
|
72
|
+
exposure: "model-only",
|
|
71
73
|
promptSnippet:
|
|
72
74
|
"Delegate bounded work to a background subagent or hand off context to another Pi session",
|
|
73
75
|
promptGuidelines: buildHandoffPromptGuidelines(launchTargets, models, roster),
|
|
@@ -178,7 +178,7 @@ export class SessionReferencePickerComponent implements Focusable {
|
|
|
178
178
|
return undefined;
|
|
179
179
|
}
|
|
180
180
|
if (event.type === "wheel" && event.wheelDelta && this.sessionAt(event)) {
|
|
181
|
-
this.moveSelection(event.wheelDelta
|
|
181
|
+
this.moveSelection(event.wheelDelta);
|
|
182
182
|
return { handled: true };
|
|
183
183
|
}
|
|
184
184
|
if (event.type !== "click" || event.button !== "left") return undefined;
|
|
@@ -63,8 +63,8 @@ export function formatHandoffLaunchFailure(error: string, prepared: PreparedHand
|
|
|
63
63
|
return `${error} Created handoff session ${prepared.sessionId}; start it manually with: ${prepared.resumeCommand}`;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
// Pi intentionally defers writing a new session until
|
|
67
|
-
//
|
|
66
|
+
// Pi intentionally defers writing a new session until it holds a user or
|
|
67
|
+
// assistant message, so a prepared child needs one explicit initial flush of the
|
|
68
68
|
// manager-assembled state to be discoverable by `pi --session-id`.
|
|
69
69
|
function flushPreparedSession(manager: SessionManager): void {
|
|
70
70
|
const sessionFile = manager.getSessionFile();
|
|
@@ -77,6 +77,7 @@ export function createSessionReachableTool(deps: SessionReachableDeps): ToolDefi
|
|
|
77
77
|
"Before session_send_message, use session_reachable to find the target session id.",
|
|
78
78
|
],
|
|
79
79
|
parameters: subagentsAvailable ? SESSION_REACHABLE_PARAMS : SESSION_REACHABLE_USER_PARAMS,
|
|
80
|
+
outputSchema: SESSION_REACHABLE_TOOL_DETAILS_SCHEMA,
|
|
80
81
|
renderResult(result, options, theme, context) {
|
|
81
82
|
const output = result.content.find((item) => item.type === "text")?.text ?? "";
|
|
82
83
|
if (context.isError) {
|
|
@@ -108,6 +109,7 @@ export function createSessionReachableTool(deps: SessionReachableDeps): ToolDefi
|
|
|
108
109
|
return {
|
|
109
110
|
content: [{ type: "text" as const, text: JSON.stringify(details, null, 2) }],
|
|
110
111
|
details,
|
|
112
|
+
structuredContent: details,
|
|
111
113
|
};
|
|
112
114
|
},
|
|
113
115
|
});
|
|
@@ -633,11 +633,17 @@ function extractMessageFileTouches(
|
|
|
633
633
|
message: AgentMessage,
|
|
634
634
|
cwd: string,
|
|
635
635
|
): SessionFileTouch[] {
|
|
636
|
-
|
|
636
|
+
let toolCalls: { name: string; arguments?: Record<string, unknown> | undefined }[];
|
|
637
|
+
if (message.role === "assistant" && Array.isArray(message.content)) {
|
|
638
|
+
toolCalls = message.content.filter(isToolCallBlock);
|
|
639
|
+
} else if (message.role === "toolResult" && message.nestedCalls) {
|
|
640
|
+
// Calls made through ctx.executeTool(), e.g. from a codemode script, only appear here.
|
|
641
|
+
toolCalls = message.nestedCalls.calls;
|
|
642
|
+
} else {
|
|
637
643
|
return [];
|
|
638
644
|
}
|
|
639
645
|
|
|
640
|
-
return
|
|
646
|
+
return toolCalls.flatMap((toolCall) => {
|
|
641
647
|
const rawPath = stringValue(toolCall.arguments?.path);
|
|
642
648
|
const op = getToolCallFileTouchOp(toolCall.name);
|
|
643
649
|
if (!rawPath || !op) {
|
|
@@ -10,10 +10,12 @@ import {
|
|
|
10
10
|
} from "../shared/session-index/index.ts";
|
|
11
11
|
import type { SessionSettings } from "../shared/settings.ts";
|
|
12
12
|
import { renderSessionSearchResult } from "./renderer.ts";
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
import {
|
|
14
|
+
SESSION_SEARCH_OUTPUT_SCHEMA,
|
|
15
|
+
type SessionSearchOutput,
|
|
16
|
+
type SessionSearchResult,
|
|
17
|
+
type SessionSearchToolDetails,
|
|
18
|
+
type SessionSearchToolParams,
|
|
17
19
|
} from "./tool-contract.ts";
|
|
18
20
|
|
|
19
21
|
const DEFAULT_SESSION_SEARCH_LIMIT = 6;
|
|
@@ -30,6 +32,7 @@ export function installSearch(pi: ExtensionAPI, deps: SearchInstallDeps): void {
|
|
|
30
32
|
name: "session_search",
|
|
31
33
|
label: "Session Search",
|
|
32
34
|
description: "Search Pi sessions",
|
|
35
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
33
36
|
promptSnippet: "Locate Pi sessions for follow-up",
|
|
34
37
|
promptGuidelines: [
|
|
35
38
|
"Omit queries in session_search to list matching sessions chronologically.",
|
|
@@ -104,6 +107,7 @@ export function installSearch(pi: ExtensionAPI, deps: SearchInstallDeps): void {
|
|
|
104
107
|
}),
|
|
105
108
|
),
|
|
106
109
|
}),
|
|
110
|
+
outputSchema: SESSION_SEARCH_OUTPUT_SCHEMA,
|
|
107
111
|
async execute(_toolCallId, params: SessionSearchToolParams, _signal, onUpdate, ctx) {
|
|
108
112
|
const validationError = validateSearchParams(params);
|
|
109
113
|
if (validationError) {
|
|
@@ -130,6 +134,7 @@ export function installSearch(pi: ExtensionAPI, deps: SearchInstallDeps): void {
|
|
|
130
134
|
},
|
|
131
135
|
],
|
|
132
136
|
details,
|
|
137
|
+
structuredContent: formatSearchOutput(results),
|
|
133
138
|
};
|
|
134
139
|
});
|
|
135
140
|
},
|
|
@@ -170,6 +175,24 @@ function formatSearchResultsForModel(details: SessionSearchToolDetails): string
|
|
|
170
175
|
);
|
|
171
176
|
}
|
|
172
177
|
|
|
178
|
+
function formatSearchOutput(results: SessionSearchResult[]): SessionSearchOutput {
|
|
179
|
+
return {
|
|
180
|
+
results: results.map((result) => ({
|
|
181
|
+
sessionId: result.sessionId,
|
|
182
|
+
sessionName: result.sessionName,
|
|
183
|
+
cwd: result.cwd,
|
|
184
|
+
startedAt: result.startedAt,
|
|
185
|
+
modifiedAt: result.modifiedAt,
|
|
186
|
+
messageCount: result.messageCount,
|
|
187
|
+
snippet: stripSearchSnippetMarkers(result.snippet) ?? result.snippet,
|
|
188
|
+
hitCount: result.hitCount,
|
|
189
|
+
...(result.sessionOrigin ? { sessionOrigin: result.sessionOrigin } : {}),
|
|
190
|
+
...(result.relation ? { relation: result.relation } : {}),
|
|
191
|
+
...(result.parentSessionId ? { parentSessionId: result.parentSessionId } : {}),
|
|
192
|
+
})),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
173
196
|
function formatSearchResultForModel(result: SessionSearchResult): SessionSearchResult {
|
|
174
197
|
return {
|
|
175
198
|
...result,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { type Static, Type } from "typebox";
|
|
2
|
+
import {
|
|
3
|
+
SESSION_LINEAGE_RELATION_SCHEMA,
|
|
4
|
+
SESSION_ORIGIN_SCHEMA,
|
|
5
|
+
type SearchSessionResult,
|
|
6
|
+
type SearchSort,
|
|
7
|
+
type SessionIndexStatus,
|
|
8
|
+
type SessionKind,
|
|
6
9
|
} from "../shared/session-index/index.ts";
|
|
7
10
|
|
|
8
11
|
export interface SessionSearchToolParams {
|
|
@@ -29,3 +32,23 @@ export interface SessionSearchToolDetails {
|
|
|
29
32
|
results: SessionSearchResult[];
|
|
30
33
|
status?: SessionIndexStatus | undefined;
|
|
31
34
|
}
|
|
35
|
+
|
|
36
|
+
export const SESSION_SEARCH_OUTPUT_SCHEMA = Type.Object({
|
|
37
|
+
results: Type.Array(
|
|
38
|
+
Type.Object({
|
|
39
|
+
sessionId: Type.String(),
|
|
40
|
+
sessionName: Type.String(),
|
|
41
|
+
cwd: Type.String(),
|
|
42
|
+
startedAt: Type.String(),
|
|
43
|
+
modifiedAt: Type.String(),
|
|
44
|
+
messageCount: Type.Number(),
|
|
45
|
+
snippet: Type.String(),
|
|
46
|
+
hitCount: Type.Number(),
|
|
47
|
+
sessionOrigin: Type.Optional(SESSION_ORIGIN_SCHEMA),
|
|
48
|
+
relation: Type.Optional(SESSION_LINEAGE_RELATION_SCHEMA),
|
|
49
|
+
parentSessionId: Type.Optional(Type.String()),
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export type SessionSearchOutput = Static<typeof SESSION_SEARCH_OUTPUT_SCHEMA>;
|
|
@@ -43,6 +43,7 @@ export function createSubmitTaskReportTool(
|
|
|
43
43
|
label: "Submit task report",
|
|
44
44
|
description:
|
|
45
45
|
"Send a report to the parent session and end the current turn. Use it when the delegated task or requested follow-up is done, blocked, or cannot be completed.",
|
|
46
|
+
exposure: "model-only",
|
|
46
47
|
promptSnippet: "Submit the current task report to the parent session and end the turn",
|
|
47
48
|
promptGuidelines: [
|
|
48
49
|
"Call submit_task_report exactly once as the final tool call for every delegated task or follow-up that expects a response.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-sessions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pi session search, ask, handoff, messaging, auto-titling, and indexing tools",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@biomejs/biome": "^2.5.10",
|
|
41
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
42
|
-
"@earendil-works/pi-ai": "^0.
|
|
43
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
44
|
-
"@earendil-works/pi-tui": "^0.
|
|
41
|
+
"@earendil-works/pi-agent-core": "^0.99.1",
|
|
42
|
+
"@earendil-works/pi-ai": "^0.99.1",
|
|
43
|
+
"@earendil-works/pi-coding-agent": "^0.99.1",
|
|
44
|
+
"@earendil-works/pi-tui": "^0.99.1",
|
|
45
45
|
"@types/node": "^24.13.3",
|
|
46
46
|
"strip-ansi": "^7.2.0",
|
|
47
47
|
"typebox": "1.3.27",
|
|
@@ -49,9 +49,10 @@
|
|
|
49
49
|
"vitest": "^4.1.11"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@earendil-works/pi-agent-core": "
|
|
53
|
-
"@earendil-works/pi-ai": "
|
|
54
|
-
"@earendil-works/pi-coding-agent": "
|
|
55
|
-
"@earendil-works/pi-tui": "
|
|
52
|
+
"@earendil-works/pi-agent-core": "*",
|
|
53
|
+
"@earendil-works/pi-ai": "*",
|
|
54
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
55
|
+
"@earendil-works/pi-tui": "*",
|
|
56
|
+
"typebox": "*"
|
|
56
57
|
}
|
|
57
58
|
}
|