pi-sessions 0.7.2 → 0.9.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 +71 -37
- package/extensions/pi-sessions.ts +125 -0
- package/extensions/session-ask/agent.ts +34 -14
- package/extensions/{session-ask.ts → session-ask/install.ts} +73 -90
- package/extensions/session-ask/navigate.ts +3 -1
- package/extensions/session-ask/presenter.ts +23 -0
- package/extensions/session-ask/renderer.ts +63 -0
- package/extensions/session-ask/tool-contract.ts +27 -0
- package/extensions/session-ask/view-model.ts +18 -0
- package/extensions/session-auto-title/generate.ts +18 -18
- package/extensions/{session-auto-title.ts → session-auto-title/install.ts} +67 -46
- package/extensions/session-auto-title/model.ts +25 -17
- package/extensions/session-auto-title/retitle.ts +26 -12
- package/extensions/session-auto-title/wizard.ts +12 -6
- package/extensions/session-handoff/board-view-model.ts +181 -0
- package/extensions/session-handoff/board.ts +663 -0
- package/extensions/session-handoff/bootstrap.ts +174 -0
- package/extensions/session-handoff/extract.ts +101 -122
- package/extensions/session-handoff/install.ts +225 -0
- package/extensions/session-handoff/kickoff.ts +92 -0
- package/extensions/session-handoff/launch/backend.ts +16 -0
- package/extensions/session-handoff/launch/deferred.ts +20 -0
- package/extensions/session-handoff/launch/ghostty.ts +99 -0
- package/extensions/session-handoff/launch/resolution.ts +57 -0
- package/extensions/session-handoff/launch/shell.ts +7 -0
- package/extensions/session-handoff/launch/tmux.ts +31 -0
- package/extensions/session-handoff/launch-options.ts +61 -0
- package/extensions/session-handoff/launch-target.ts +91 -0
- package/extensions/session-handoff/metadata.ts +152 -63
- package/extensions/session-handoff/model.ts +58 -0
- package/extensions/session-handoff/query.ts +0 -1
- package/extensions/session-handoff/receipt.ts +96 -0
- package/extensions/session-handoff/review.ts +2 -28
- package/extensions/session-handoff/spawn.ts +76 -181
- package/extensions/session-handoff/strong-modal.ts +13 -0
- package/extensions/session-handoff/tool-contract.ts +12 -0
- package/extensions/session-handoff/tool-presenter.ts +35 -0
- package/extensions/session-handoff/tool-renderer.ts +66 -0
- package/extensions/session-handoff/tool-schema.ts +54 -0
- package/extensions/session-handoff/tool-view-model.ts +49 -0
- package/extensions/session-handoff/tool.ts +206 -0
- package/extensions/session-handoff/ui.ts +66 -0
- package/extensions/session-hooks/install.ts +75 -0
- package/extensions/{session-index.ts → session-index/install.ts} +9 -7
- package/extensions/session-messaging/broker/process.ts +21 -28
- package/extensions/session-messaging/install.ts +78 -0
- package/extensions/session-messaging/pi/cancel-session-presenter.ts +64 -0
- package/extensions/session-messaging/pi/incoming-message-presenter.ts +32 -0
- package/extensions/session-messaging/pi/incoming-message-view-model.ts +20 -0
- package/extensions/session-messaging/pi/incoming-runtime.ts +27 -23
- package/extensions/session-messaging/pi/message-contracts.ts +32 -0
- package/extensions/session-messaging/pi/renderer.ts +19 -12
- package/extensions/session-messaging/pi/send-message-presenter.ts +39 -0
- package/extensions/session-messaging/pi/send-message-view-model.ts +54 -0
- package/extensions/session-messaging/pi/service.ts +262 -81
- package/extensions/session-messaging/pi/tools.ts +184 -40
- package/extensions/session-search/extract.ts +60 -14
- package/extensions/session-search/hooks.ts +10 -13
- package/extensions/session-search/install.ts +373 -0
- package/extensions/session-search/renderer.ts +195 -0
- package/extensions/session-search/tool-contract.ts +51 -0
- package/extensions/shared/composition.ts +20 -0
- package/extensions/shared/errors.ts +3 -0
- package/extensions/shared/model-resolution.ts +73 -0
- package/extensions/shared/model-runtime.ts +33 -0
- package/extensions/shared/model.ts +7 -10
- package/extensions/shared/rendering/collapsible-text.ts +89 -0
- package/extensions/shared/rendering/expandable-content-layout.ts +84 -0
- package/extensions/shared/rendering/theme.ts +5 -0
- package/extensions/shared/session-broker/client.ts +30 -29
- package/extensions/shared/session-broker/protocol.ts +128 -10
- package/extensions/shared/session-index/common.ts +5 -5
- package/extensions/shared/session-index/lineage.ts +0 -3
- package/extensions/shared/session-index/schema.ts +0 -1
- package/extensions/shared/session-index/scoring.ts +1 -2
- package/extensions/shared/session-index/search.ts +11 -4
- package/extensions/shared/session-index/store.ts +4 -9
- package/extensions/shared/settings.ts +80 -38
- package/extensions/shared/text.ts +5 -0
- package/extensions/shared/thinking-levels.ts +22 -0
- package/extensions/shared/tmux.ts +205 -0
- package/extensions/subagents/cancel.ts +111 -0
- package/extensions/subagents/classify.ts +52 -0
- package/extensions/subagents/install.ts +344 -0
- package/extensions/subagents/launch-target.ts +75 -0
- package/extensions/subagents/ledger.ts +217 -0
- package/extensions/subagents/reconcile.ts +386 -0
- package/extensions/subagents/report-message-presenter.ts +36 -0
- package/extensions/subagents/report-message-renderer.ts +21 -0
- package/extensions/subagents/report-message-view-model.ts +29 -0
- package/extensions/subagents/report.ts +159 -0
- package/extensions/subagents/roster.ts +262 -0
- package/extensions/subagents/wake.ts +173 -0
- package/images/handoff-board-subagents.png +0 -0
- package/images/handoff-board-user-sessions.png +0 -0
- package/images/handoff-subagent-report.png +0 -0
- package/images/session-handoff-tool.png +0 -0
- package/package.json +10 -16
- package/extensions/session-handoff.ts +0 -741
- package/extensions/session-hooks.ts +0 -75
- package/extensions/session-messaging/pi/message-view.ts +0 -131
- package/extensions/session-messaging.ts +0 -30
- package/extensions/session-search.ts +0 -419
- package/extensions/shared/session-broker/active.ts +0 -26
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ExpandableContentPresentation } from "../shared/rendering/expandable-content-layout.ts";
|
|
2
|
+
import type { RenderTheme } from "../shared/rendering/theme.ts";
|
|
3
|
+
import { formatSessionTitleOrShortId } from "../shared/session-ui.ts";
|
|
4
|
+
import type { SessionAskViewModel } from "./view-model.ts";
|
|
5
|
+
|
|
6
|
+
export function buildSessionAskPresentation(
|
|
7
|
+
model: SessionAskViewModel,
|
|
8
|
+
theme: RenderTheme,
|
|
9
|
+
): ExpandableContentPresentation {
|
|
10
|
+
const identity = formatSessionTitleOrShortId(model.sessionName, model.sessionId);
|
|
11
|
+
return {
|
|
12
|
+
header: `title: ${theme.bold(identity)}`,
|
|
13
|
+
...(model.question
|
|
14
|
+
? {
|
|
15
|
+
metadata: [theme.fg("muted", `prompt: ${model.question}`)],
|
|
16
|
+
}
|
|
17
|
+
: {}),
|
|
18
|
+
body: {
|
|
19
|
+
text: model.answer,
|
|
20
|
+
collapsedRows: 6,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentToolResult,
|
|
3
|
+
Theme,
|
|
4
|
+
ToolRenderResultOptions,
|
|
5
|
+
} from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { type Component, Text } from "@earendil-works/pi-tui";
|
|
7
|
+
import { ExpandableContentLayout } from "../shared/rendering/expandable-content-layout.ts";
|
|
8
|
+
import { formatSessionTitleOrShortId } from "../shared/session-ui.ts";
|
|
9
|
+
import { safeParseTypeBoxValue } from "../shared/typebox.ts";
|
|
10
|
+
import { buildSessionAskPresentation } from "./presenter.ts";
|
|
11
|
+
import {
|
|
12
|
+
SESSION_ASK_PROGRESS_DETAILS_SCHEMA,
|
|
13
|
+
SESSION_ASK_RESULT_DETAILS_SCHEMA,
|
|
14
|
+
} from "./tool-contract.ts";
|
|
15
|
+
import { buildSessionAskView } from "./view-model.ts";
|
|
16
|
+
|
|
17
|
+
interface SessionAskToolRendererState {
|
|
18
|
+
layout?: ExpandableContentLayout | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface SessionAskToolRenderContext {
|
|
22
|
+
state: SessionAskToolRendererState;
|
|
23
|
+
isError: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function renderSessionAskResult(
|
|
27
|
+
result: AgentToolResult<unknown>,
|
|
28
|
+
options: ToolRenderResultOptions,
|
|
29
|
+
theme: Theme,
|
|
30
|
+
context: SessionAskToolRenderContext,
|
|
31
|
+
): Component {
|
|
32
|
+
const content = result.content[0];
|
|
33
|
+
if (content?.type !== "text") {
|
|
34
|
+
return new Text(theme.fg("error", "No session output"), 0, 0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (context.isError) {
|
|
38
|
+
return new Text(theme.fg("error", content.text), 0, 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (options.isPartial) {
|
|
42
|
+
const details = safeParseTypeBoxValue(SESSION_ASK_PROGRESS_DETAILS_SCHEMA, result.details);
|
|
43
|
+
const lines = [theme.bold(theme.fg("warning", "Reading session..."))];
|
|
44
|
+
if (details) {
|
|
45
|
+
const identity = formatSessionTitleOrShortId(details.sessionName, details.sessionId);
|
|
46
|
+
lines.push(`title: ${theme.fg("accent", identity)}`);
|
|
47
|
+
}
|
|
48
|
+
if (details?.question) {
|
|
49
|
+
lines.push(theme.fg("muted", `prompt: ${details.question}`));
|
|
50
|
+
}
|
|
51
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const details = safeParseTypeBoxValue(SESSION_ASK_RESULT_DETAILS_SCHEMA, result.details);
|
|
55
|
+
if (!details) {
|
|
56
|
+
return new Text(content.text, 0, 0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const layout = context.state.layout ?? new ExpandableContentLayout(theme);
|
|
60
|
+
context.state.layout = layout;
|
|
61
|
+
layout.update(buildSessionAskPresentation(buildSessionAskView(details), theme), options.expanded);
|
|
62
|
+
return layout;
|
|
63
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Static, Type } from "typebox";
|
|
2
|
+
|
|
3
|
+
export const SESSION_ASK_RELEVANT_FILE_SCHEMA = Type.Object({
|
|
4
|
+
path: Type.String(),
|
|
5
|
+
reason: Type.String(),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const SESSION_ASK_PROGRESS_DETAILS_SCHEMA = Type.Object({
|
|
9
|
+
question: Type.String({ minLength: 1 }),
|
|
10
|
+
sessionId: Type.String({ minLength: 1 }),
|
|
11
|
+
sessionName: Type.String(),
|
|
12
|
+
sessionPath: Type.String({ minLength: 1 }),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const SESSION_ASK_RESULT_DETAILS_SCHEMA = Type.Object({
|
|
16
|
+
answer: Type.String({ minLength: 1 }),
|
|
17
|
+
debugSessionPath: Type.Optional(Type.String({ minLength: 1 })),
|
|
18
|
+
question: Type.String({ minLength: 1 }),
|
|
19
|
+
relevantFiles: Type.Array(SESSION_ASK_RELEVANT_FILE_SCHEMA),
|
|
20
|
+
sessionId: Type.String({ minLength: 1 }),
|
|
21
|
+
sessionName: Type.String(),
|
|
22
|
+
sessionPath: Type.String({ minLength: 1 }),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type SessionAskRelevantFile = Static<typeof SESSION_ASK_RELEVANT_FILE_SCHEMA>;
|
|
26
|
+
export type SessionAskProgressDetails = Static<typeof SESSION_ASK_PROGRESS_DETAILS_SCHEMA>;
|
|
27
|
+
export type SessionAskResultDetails = Static<typeof SESSION_ASK_RESULT_DETAILS_SCHEMA>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { normalizeOptionalText } from "../shared/text.ts";
|
|
2
|
+
import type { SessionAskResultDetails } from "./tool-contract.ts";
|
|
3
|
+
|
|
4
|
+
export interface SessionAskViewModel {
|
|
5
|
+
sessionId: string;
|
|
6
|
+
sessionName?: string | undefined;
|
|
7
|
+
question: string;
|
|
8
|
+
answer: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function buildSessionAskView(details: SessionAskResultDetails): SessionAskViewModel {
|
|
12
|
+
return {
|
|
13
|
+
sessionId: details.sessionId.trim(),
|
|
14
|
+
sessionName: normalizeOptionalText(details.sessionName),
|
|
15
|
+
question: details.question.trim(),
|
|
16
|
+
answer: details.answer.trim(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
4
5
|
import type { Api, Model, TextContent, UserMessage } from "@earendil-works/pi-ai";
|
|
5
|
-
import {
|
|
6
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import type { ModelRuntime } 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
|
|
|
@@ -18,6 +17,12 @@ export interface AutoTitleFailure {
|
|
|
18
17
|
message: string;
|
|
19
18
|
}
|
|
20
19
|
|
|
20
|
+
export interface AutoTitleGeneration {
|
|
21
|
+
systemPrompt: string;
|
|
22
|
+
timeoutMs: number;
|
|
23
|
+
thinkingLevel: ThinkingLevel | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
export type AutoTitleGenerationResult =
|
|
22
27
|
| {
|
|
23
28
|
ok: true;
|
|
@@ -29,11 +34,11 @@ export type AutoTitleGenerationResult =
|
|
|
29
34
|
};
|
|
30
35
|
|
|
31
36
|
export async function generateAutoTitle(
|
|
32
|
-
|
|
37
|
+
modelRuntime: ModelRuntime,
|
|
33
38
|
model: Model<Api>,
|
|
34
39
|
context: AutoTitleContext,
|
|
35
40
|
trigger: AutoTitleTrigger,
|
|
36
|
-
|
|
41
|
+
generation: AutoTitleGeneration,
|
|
37
42
|
): Promise<AutoTitleGenerationResult> {
|
|
38
43
|
if (!context.conversationText) {
|
|
39
44
|
return {
|
|
@@ -46,16 +51,11 @@ export async function generateAutoTitle(
|
|
|
46
51
|
};
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
50
|
-
if (!auth.ok) {
|
|
51
|
-
return {
|
|
52
|
-
ok: false,
|
|
53
|
-
failure: createAutoTitleFailure(trigger, model, "Failed to authenticate auto-title model."),
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
54
|
const shouldPreserveTitle = trigger === "periodic" && Boolean(context.currentTitle?.trim());
|
|
58
|
-
const resolvedSystemPrompt = buildAutoTitleSystemPrompt(
|
|
55
|
+
const resolvedSystemPrompt = buildAutoTitleSystemPrompt(
|
|
56
|
+
generation.systemPrompt,
|
|
57
|
+
shouldPreserveTitle,
|
|
58
|
+
);
|
|
59
59
|
const userPrompt = buildAutoTitlePrompt(context, resolvedSystemPrompt, shouldPreserveTitle);
|
|
60
60
|
const message: UserMessage = {
|
|
61
61
|
role: "user",
|
|
@@ -64,7 +64,8 @@ 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(), generation.timeoutMs);
|
|
68
|
+
const thinkingLevel = generation.thinkingLevel;
|
|
68
69
|
|
|
69
70
|
try {
|
|
70
71
|
const requestContext = {
|
|
@@ -72,10 +73,9 @@ export async function generateAutoTitle(
|
|
|
72
73
|
messages: [message],
|
|
73
74
|
};
|
|
74
75
|
writeAutoTitleDebugRequest(model, trigger, requestContext);
|
|
75
|
-
const response = await completeSimple(model, requestContext, {
|
|
76
|
-
...(auth.apiKey && { apiKey: auth.apiKey }),
|
|
77
|
-
...(auth.headers && { headers: auth.headers }),
|
|
76
|
+
const response = await modelRuntime.completeSimple(model, requestContext, {
|
|
78
77
|
maxTokens: AUTO_TITLE_MAX_TOKENS,
|
|
78
|
+
...(thinkingLevel && thinkingLevel !== "off" ? { reasoning: thinkingLevel } : {}),
|
|
79
79
|
signal: abortController.signal,
|
|
80
80
|
});
|
|
81
81
|
writeAutoTitleDebugResponse(model, trigger, response);
|
|
@@ -3,37 +3,30 @@ import type {
|
|
|
3
3
|
ExtensionAPI,
|
|
4
4
|
ExtensionCommandContext,
|
|
5
5
|
ExtensionContext,
|
|
6
|
-
|
|
6
|
+
ModelRuntime,
|
|
7
7
|
TurnEndEvent,
|
|
8
8
|
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import type { SessionLifecycle } from "../shared/composition.ts";
|
|
10
|
+
import type { ModelRuntimeProvider } from "../shared/model-runtime.ts";
|
|
11
|
+
import { isTuiMode } from "../shared/pi-mode.ts";
|
|
12
|
+
import type { SessionSettings } from "../shared/settings.ts";
|
|
9
13
|
import {
|
|
10
14
|
createSessionAutoTitleCommandHandler,
|
|
11
15
|
getRetitleArgumentCompletions,
|
|
12
16
|
type RetitleCommandInvocation,
|
|
13
17
|
type RetitleCommandOutcome,
|
|
14
|
-
} from "./
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} from "./session-auto-title/controller.ts";
|
|
19
|
-
import { resolveAutoTitleModel } from "./session-auto-title/model.ts";
|
|
18
|
+
} from "./command.ts";
|
|
19
|
+
import { createSessionAutoTitleController, type SessionAutoTitleController } from "./controller.ts";
|
|
20
|
+
import type { AutoTitleGeneration } from "./generate.ts";
|
|
21
|
+
import { type AutoTitleModelResolution, resolveAutoTitleModel } from "./model.ts";
|
|
20
22
|
import {
|
|
21
23
|
buildRetitleScopeScan,
|
|
22
24
|
notifyBulkRetitleResult,
|
|
23
25
|
persistAutoTitleState,
|
|
24
26
|
runBulkRetitle,
|
|
25
27
|
runRetitlePlan,
|
|
26
|
-
} from "./
|
|
27
|
-
import { showRetitleWizard } from "./
|
|
28
|
-
import { isTuiMode } from "./shared/pi-mode.ts";
|
|
29
|
-
import { loadSettings } from "./shared/settings.ts";
|
|
30
|
-
|
|
31
|
-
export {
|
|
32
|
-
createSessionAutoTitleCommandHandler,
|
|
33
|
-
getRetitleArgumentCompletions,
|
|
34
|
-
parseRetitleCommand,
|
|
35
|
-
TITLE_USAGE,
|
|
36
|
-
} from "./session-auto-title/command.ts";
|
|
28
|
+
} from "./retitle.ts";
|
|
29
|
+
import { showRetitleWizard } from "./wizard.ts";
|
|
37
30
|
|
|
38
31
|
interface TitleRunState {
|
|
39
32
|
controller: SessionAutoTitleController;
|
|
@@ -42,12 +35,26 @@ interface TitleRunState {
|
|
|
42
35
|
clearInFlight: () => void;
|
|
43
36
|
}
|
|
44
37
|
|
|
45
|
-
export
|
|
46
|
-
|
|
38
|
+
export function installAutoTitle(
|
|
39
|
+
pi: ExtensionAPI,
|
|
40
|
+
deps: {
|
|
41
|
+
settings: SessionSettings;
|
|
42
|
+
getModelRuntime: ModelRuntimeProvider;
|
|
43
|
+
getSessionEpoch: () => number;
|
|
44
|
+
},
|
|
45
|
+
): SessionLifecycle {
|
|
46
|
+
const { settings, getModelRuntime, getSessionEpoch } = deps;
|
|
47
47
|
const controller = createSessionAutoTitleController(settings.autoTitle);
|
|
48
|
-
let sessionEpoch = 0;
|
|
49
48
|
let titleWorkInFlight: Promise<void> | undefined;
|
|
50
|
-
|
|
49
|
+
|
|
50
|
+
// An explicit thinkingLevel setting overrides a thinking suffix on the configured model.
|
|
51
|
+
const buildGeneration = (
|
|
52
|
+
resolution: AutoTitleModelResolution | undefined,
|
|
53
|
+
): AutoTitleGeneration => ({
|
|
54
|
+
systemPrompt: settings.autoTitle.prompt,
|
|
55
|
+
timeoutMs: settings.autoTitle.timeoutMs,
|
|
56
|
+
thinkingLevel: settings.autoTitle.thinkingLevel ?? resolution?.thinkingLevel,
|
|
57
|
+
});
|
|
51
58
|
|
|
52
59
|
pi.registerCommand("title", {
|
|
53
60
|
description: "Generate titles for this session, this folder, or all of Pi",
|
|
@@ -58,12 +65,13 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
|
|
|
58
65
|
await titleWorkInFlight;
|
|
59
66
|
}
|
|
60
67
|
|
|
61
|
-
const
|
|
68
|
+
const modelRuntime = await getModelRuntime(ctx.modelRegistry);
|
|
69
|
+
const resolution = resolveAutoTitleModel(modelRuntime, ctx.model, settings.autoTitle.model);
|
|
62
70
|
return handleTitleInvocation(
|
|
63
71
|
pi,
|
|
64
72
|
{
|
|
65
73
|
controller,
|
|
66
|
-
getSessionEpoch
|
|
74
|
+
getSessionEpoch,
|
|
67
75
|
setInFlight: (work) => {
|
|
68
76
|
titleWorkInFlight = work;
|
|
69
77
|
},
|
|
@@ -72,20 +80,15 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
|
|
|
72
80
|
},
|
|
73
81
|
},
|
|
74
82
|
ctx,
|
|
75
|
-
|
|
83
|
+
modelRuntime,
|
|
84
|
+
resolution?.model,
|
|
76
85
|
invocation,
|
|
77
|
-
|
|
86
|
+
buildGeneration(resolution),
|
|
78
87
|
);
|
|
79
88
|
},
|
|
80
89
|
),
|
|
81
90
|
});
|
|
82
91
|
|
|
83
|
-
pi.on("session_start", async (_event: SessionStartEvent, ctx: ExtensionContext) => {
|
|
84
|
-
sessionEpoch += 1;
|
|
85
|
-
resolvedModel = resolveAutoTitleModel(ctx, settings.autoTitle.model)?.model;
|
|
86
|
-
persistAutoTitleState(pi, controller.handleSessionStart(ctx));
|
|
87
|
-
});
|
|
88
|
-
|
|
89
92
|
pi.on("turn_end", async (_event: TurnEndEvent, ctx: ExtensionContext) => {
|
|
90
93
|
const result = controller.handleTurnEnd(ctx);
|
|
91
94
|
persistAutoTitleState(pi, result.persistedState);
|
|
@@ -94,16 +97,19 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
|
|
|
94
97
|
return;
|
|
95
98
|
}
|
|
96
99
|
|
|
100
|
+
const modelRuntime = await getModelRuntime(ctx.modelRegistry);
|
|
101
|
+
const resolution = resolveAutoTitleModel(modelRuntime, ctx.model, settings.autoTitle.model);
|
|
97
102
|
titleWorkInFlight = runRetitlePlan({
|
|
98
103
|
pi,
|
|
99
104
|
controller,
|
|
100
105
|
ctx,
|
|
101
|
-
|
|
106
|
+
modelRuntime,
|
|
107
|
+
model: resolution?.model,
|
|
102
108
|
isManual: false,
|
|
103
109
|
existingPlan: result.plan,
|
|
104
|
-
getSessionEpoch
|
|
110
|
+
getSessionEpoch,
|
|
105
111
|
notifyOnSuccess: false,
|
|
106
|
-
|
|
112
|
+
generation: buildGeneration(resolution),
|
|
107
113
|
})
|
|
108
114
|
.then((outcome) => {
|
|
109
115
|
if (outcome.ok) {
|
|
@@ -124,29 +130,34 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
|
|
|
124
130
|
});
|
|
125
131
|
});
|
|
126
132
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
return {
|
|
134
|
+
onSessionStart(_event, ctx) {
|
|
135
|
+
persistAutoTitleState(pi, controller.handleSessionStart(ctx));
|
|
136
|
+
},
|
|
137
|
+
onSessionShutdown() {
|
|
138
|
+
controller.handleSessionShutdown();
|
|
139
|
+
titleWorkInFlight = undefined;
|
|
140
|
+
},
|
|
141
|
+
};
|
|
133
142
|
}
|
|
134
143
|
|
|
135
144
|
async function handleTitleInvocation(
|
|
136
145
|
pi: ExtensionAPI,
|
|
137
146
|
state: TitleRunState,
|
|
138
147
|
ctx: ExtensionCommandContext,
|
|
148
|
+
modelRuntime: ModelRuntime,
|
|
139
149
|
model: Model<Api> | undefined,
|
|
140
150
|
invocation: RetitleCommandInvocation,
|
|
141
|
-
|
|
151
|
+
generation: AutoTitleGeneration,
|
|
142
152
|
): Promise<RetitleCommandOutcome> {
|
|
143
153
|
const retitleOpts = {
|
|
144
154
|
pi,
|
|
145
155
|
controller: state.controller,
|
|
146
156
|
ctx,
|
|
157
|
+
modelRuntime,
|
|
147
158
|
model,
|
|
148
159
|
isManual: true,
|
|
149
|
-
|
|
160
|
+
generation,
|
|
150
161
|
getSessionEpoch: state.getSessionEpoch,
|
|
151
162
|
};
|
|
152
163
|
|
|
@@ -165,7 +176,15 @@ async function handleTitleInvocation(
|
|
|
165
176
|
return retitleCurrentSession();
|
|
166
177
|
}
|
|
167
178
|
|
|
168
|
-
return showRetitleWizard(
|
|
179
|
+
return showRetitleWizard(
|
|
180
|
+
pi,
|
|
181
|
+
state.controller,
|
|
182
|
+
ctx,
|
|
183
|
+
modelRuntime,
|
|
184
|
+
model,
|
|
185
|
+
state.getSessionEpoch,
|
|
186
|
+
generation,
|
|
187
|
+
);
|
|
169
188
|
}
|
|
170
189
|
|
|
171
190
|
if (invocation.scope === "this") {
|
|
@@ -177,9 +196,10 @@ async function handleTitleInvocation(
|
|
|
177
196
|
pi,
|
|
178
197
|
state.controller,
|
|
179
198
|
ctx,
|
|
199
|
+
modelRuntime,
|
|
180
200
|
model,
|
|
181
201
|
state.getSessionEpoch,
|
|
182
|
-
|
|
202
|
+
generation,
|
|
183
203
|
{
|
|
184
204
|
initialInvocation: {
|
|
185
205
|
scope: invocation.scope,
|
|
@@ -196,11 +216,12 @@ async function handleTitleInvocation(
|
|
|
196
216
|
pi,
|
|
197
217
|
state.controller,
|
|
198
218
|
ctx,
|
|
219
|
+
modelRuntime,
|
|
199
220
|
model,
|
|
200
221
|
scan,
|
|
201
222
|
mode,
|
|
202
223
|
state.getSessionEpoch,
|
|
203
|
-
|
|
224
|
+
generation,
|
|
204
225
|
);
|
|
205
226
|
notifyBulkRetitleResult(ctx, scan, mode, result);
|
|
206
227
|
return "success";
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
1
2
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
|
-
import type {
|
|
3
|
-
import { findModelByReference } from "../shared/model.ts";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
import type { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { type ExactModelReference, findModelByReference } from "../shared/model.ts";
|
|
5
|
+
import { resolveAuthenticatedModel } from "../shared/model-resolution.ts";
|
|
6
|
+
import { freshenModel } from "../shared/model-runtime.ts";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_AUTO_TITLE_FALLBACK_MODELS: readonly ExactModelReference[] = [
|
|
9
|
+
{ provider: "openai-codex", modelId: "gpt-5.6-luna" },
|
|
10
|
+
{ provider: "openai", modelId: "gpt-5.6-luna" },
|
|
11
|
+
{ provider: "anthropic", modelId: "claude-haiku-4-5" },
|
|
12
|
+
{ provider: "google", modelId: "gemini-flash-lite-latest" },
|
|
10
13
|
] as const;
|
|
11
14
|
|
|
12
15
|
export type AutoTitleModelSource = "configured" | "fallback" | "current";
|
|
@@ -14,24 +17,29 @@ export type AutoTitleModelSource = "configured" | "fallback" | "current";
|
|
|
14
17
|
export interface AutoTitleModelResolution {
|
|
15
18
|
model: Model<Api>;
|
|
16
19
|
source: AutoTitleModelSource;
|
|
20
|
+
thinkingLevel?: ThinkingLevel | undefined;
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
export function resolveAutoTitleModel(
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
modelRuntime: ModelRuntime,
|
|
25
|
+
currentModel: Model<Api> | undefined,
|
|
26
|
+
configuredModel: string | undefined,
|
|
22
27
|
): AutoTitleModelResolution | undefined {
|
|
23
|
-
const availableModels = ctx.modelRegistry.getAvailable();
|
|
24
|
-
|
|
25
28
|
if (configuredModel) {
|
|
26
|
-
const
|
|
27
|
-
|
|
29
|
+
const configured = resolveAuthenticatedModel({
|
|
30
|
+
modelRuntime,
|
|
31
|
+
modelPattern: configuredModel,
|
|
32
|
+
});
|
|
33
|
+
if (configured.ok) {
|
|
28
34
|
return {
|
|
29
|
-
model:
|
|
35
|
+
model: configured.model,
|
|
30
36
|
source: "configured",
|
|
37
|
+
thinkingLevel: configured.thinkingLevel,
|
|
31
38
|
};
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
41
|
|
|
42
|
+
const availableModels = modelRuntime.getAvailableSnapshot();
|
|
35
43
|
for (const fallbackReference of DEFAULT_AUTO_TITLE_FALLBACK_MODELS) {
|
|
36
44
|
const fallbackMatch = findModelByReference(availableModels, fallbackReference);
|
|
37
45
|
if (fallbackMatch) {
|
|
@@ -42,12 +50,12 @@ export function resolveAutoTitleModel(
|
|
|
42
50
|
}
|
|
43
51
|
}
|
|
44
52
|
|
|
45
|
-
if (!
|
|
53
|
+
if (!currentModel) {
|
|
46
54
|
return undefined;
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
return {
|
|
50
|
-
model:
|
|
58
|
+
model: freshenModel(modelRuntime, currentModel),
|
|
51
59
|
source: "current",
|
|
52
60
|
};
|
|
53
61
|
}
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
ExtensionAPI,
|
|
4
4
|
ExtensionCommandContext,
|
|
5
5
|
ExtensionContext,
|
|
6
|
+
ModelRuntime,
|
|
6
7
|
SessionInfo,
|
|
7
8
|
} from "@earendil-works/pi-coding-agent";
|
|
8
9
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
@@ -10,6 +11,7 @@ import type { RetitleMode, RetitleScope } from "./command.ts";
|
|
|
10
11
|
import { buildAutoTitleContext } from "./context.ts";
|
|
11
12
|
import type { AutoTitleTriggerPlan, SessionAutoTitleController } from "./controller.ts";
|
|
12
13
|
import {
|
|
14
|
+
type AutoTitleGeneration,
|
|
13
15
|
type AutoTitleGenerationResult,
|
|
14
16
|
createAutoTitleFailure,
|
|
15
17
|
generateAutoTitle,
|
|
@@ -53,11 +55,12 @@ export async function runBulkRetitle(
|
|
|
53
55
|
pi: ExtensionAPI,
|
|
54
56
|
controller: SessionAutoTitleController,
|
|
55
57
|
ctx: ExtensionCommandContext,
|
|
58
|
+
modelRuntime: ModelRuntime,
|
|
56
59
|
model: Model<Api> | undefined,
|
|
57
60
|
scan: RetitleScopeScan,
|
|
58
61
|
mode: RetitleMode,
|
|
59
62
|
getSessionEpoch: () => number,
|
|
60
|
-
|
|
63
|
+
generation: AutoTitleGeneration,
|
|
61
64
|
): Promise<BulkRetitleResult> {
|
|
62
65
|
const result: BulkRetitleResult = {
|
|
63
66
|
attempted: 0,
|
|
@@ -82,9 +85,10 @@ export async function runBulkRetitle(
|
|
|
82
85
|
pi,
|
|
83
86
|
controller,
|
|
84
87
|
ctx,
|
|
88
|
+
modelRuntime,
|
|
85
89
|
model,
|
|
86
90
|
isManual: true,
|
|
87
|
-
|
|
91
|
+
generation,
|
|
88
92
|
getSessionEpoch,
|
|
89
93
|
notifyOnSuccess: false,
|
|
90
94
|
});
|
|
@@ -92,7 +96,7 @@ export async function runBulkRetitle(
|
|
|
92
96
|
continue;
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
const outcome = await retitleStoredSession(
|
|
99
|
+
const outcome = await retitleStoredSession(modelRuntime, model, session.path, generation);
|
|
96
100
|
result[outcome] += 1;
|
|
97
101
|
}
|
|
98
102
|
|
|
@@ -162,9 +166,10 @@ export interface RetitlePlanOptions {
|
|
|
162
166
|
pi: ExtensionAPI;
|
|
163
167
|
controller: SessionAutoTitleController;
|
|
164
168
|
ctx: ExtensionContext;
|
|
169
|
+
modelRuntime: ModelRuntime;
|
|
165
170
|
model: Model<Api> | undefined;
|
|
166
171
|
isManual: boolean;
|
|
167
|
-
|
|
172
|
+
generation: AutoTitleGeneration;
|
|
168
173
|
existingPlan?: AutoTitleTriggerPlan;
|
|
169
174
|
getSessionEpoch?: () => number;
|
|
170
175
|
notifyOnSuccess?: boolean;
|
|
@@ -173,8 +178,17 @@ export interface RetitlePlanOptions {
|
|
|
173
178
|
export async function runRetitlePlan(
|
|
174
179
|
options: RetitlePlanOptions,
|
|
175
180
|
): Promise<AutoTitleGenerationResult> {
|
|
176
|
-
const {
|
|
177
|
-
|
|
181
|
+
const {
|
|
182
|
+
pi,
|
|
183
|
+
controller,
|
|
184
|
+
ctx,
|
|
185
|
+
modelRuntime,
|
|
186
|
+
model,
|
|
187
|
+
isManual,
|
|
188
|
+
generation,
|
|
189
|
+
existingPlan,
|
|
190
|
+
getSessionEpoch,
|
|
191
|
+
} = options;
|
|
178
192
|
const notifyOnSuccess = options.notifyOnSuccess ?? isManual;
|
|
179
193
|
|
|
180
194
|
const plan = existingPlan ?? controller.handleManualRetitle(ctx);
|
|
@@ -206,11 +220,11 @@ export async function runRetitlePlan(
|
|
|
206
220
|
},
|
|
207
221
|
);
|
|
208
222
|
const generatedTitle = await generateAutoTitle(
|
|
209
|
-
|
|
223
|
+
modelRuntime,
|
|
210
224
|
model,
|
|
211
225
|
titleContext,
|
|
212
226
|
plan.reason,
|
|
213
|
-
|
|
227
|
+
generation,
|
|
214
228
|
);
|
|
215
229
|
if (!generatedTitle.ok) {
|
|
216
230
|
return generatedTitle;
|
|
@@ -253,10 +267,10 @@ export function persistAutoTitleState(
|
|
|
253
267
|
}
|
|
254
268
|
|
|
255
269
|
async function retitleStoredSession(
|
|
256
|
-
|
|
270
|
+
modelRuntime: ModelRuntime,
|
|
257
271
|
model: Model<Api>,
|
|
258
272
|
sessionPath: string,
|
|
259
|
-
|
|
273
|
+
generation: AutoTitleGeneration,
|
|
260
274
|
): Promise<"retitled" | "unchanged" | "failed"> {
|
|
261
275
|
const sessionManager = SessionManager.open(sessionPath);
|
|
262
276
|
const currentTitle = sessionManager.getSessionName();
|
|
@@ -274,11 +288,11 @@ async function retitleStoredSession(
|
|
|
274
288
|
currentTitle,
|
|
275
289
|
};
|
|
276
290
|
const generatedTitle = await generateAutoTitle(
|
|
277
|
-
|
|
291
|
+
modelRuntime,
|
|
278
292
|
model,
|
|
279
293
|
titleContext,
|
|
280
294
|
plan.reason,
|
|
281
|
-
|
|
295
|
+
generation,
|
|
282
296
|
);
|
|
283
297
|
if (!generatedTitle.ok) {
|
|
284
298
|
return "failed";
|