pi-sessions 0.8.0 → 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 +70 -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} +60 -53
- package/extensions/session-auto-title/model.ts +25 -17
- package/extensions/session-auto-title/retitle.ts +17 -18
- package/extensions/session-auto-title/wizard.ts +12 -11
- 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 +76 -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,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,9 +1,9 @@
|
|
|
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
|
|
|
@@ -17,6 +17,12 @@ export interface AutoTitleFailure {
|
|
|
17
17
|
message: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export interface AutoTitleGeneration {
|
|
21
|
+
systemPrompt: string;
|
|
22
|
+
timeoutMs: number;
|
|
23
|
+
thinkingLevel: ThinkingLevel | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
export type AutoTitleGenerationResult =
|
|
21
27
|
| {
|
|
22
28
|
ok: true;
|
|
@@ -28,12 +34,11 @@ export type AutoTitleGenerationResult =
|
|
|
28
34
|
};
|
|
29
35
|
|
|
30
36
|
export async function generateAutoTitle(
|
|
31
|
-
|
|
37
|
+
modelRuntime: ModelRuntime,
|
|
32
38
|
model: Model<Api>,
|
|
33
39
|
context: AutoTitleContext,
|
|
34
40
|
trigger: AutoTitleTrigger,
|
|
35
|
-
|
|
36
|
-
timeoutMs: number,
|
|
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(), timeoutMs);
|
|
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,21 +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
|
-
|
|
78
|
-
settings.autoTitle.timeoutMs,
|
|
86
|
+
buildGeneration(resolution),
|
|
79
87
|
);
|
|
80
88
|
},
|
|
81
89
|
),
|
|
82
90
|
});
|
|
83
91
|
|
|
84
|
-
pi.on("session_start", async (_event: SessionStartEvent, ctx: ExtensionContext) => {
|
|
85
|
-
sessionEpoch += 1;
|
|
86
|
-
resolvedModel = resolveAutoTitleModel(ctx, settings.autoTitle.model)?.model;
|
|
87
|
-
persistAutoTitleState(pi, controller.handleSessionStart(ctx));
|
|
88
|
-
});
|
|
89
|
-
|
|
90
92
|
pi.on("turn_end", async (_event: TurnEndEvent, ctx: ExtensionContext) => {
|
|
91
93
|
const result = controller.handleTurnEnd(ctx);
|
|
92
94
|
persistAutoTitleState(pi, result.persistedState);
|
|
@@ -95,17 +97,19 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
|
|
|
95
97
|
return;
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
const modelRuntime = await getModelRuntime(ctx.modelRegistry);
|
|
101
|
+
const resolution = resolveAutoTitleModel(modelRuntime, ctx.model, settings.autoTitle.model);
|
|
98
102
|
titleWorkInFlight = runRetitlePlan({
|
|
99
103
|
pi,
|
|
100
104
|
controller,
|
|
101
105
|
ctx,
|
|
102
|
-
|
|
106
|
+
modelRuntime,
|
|
107
|
+
model: resolution?.model,
|
|
103
108
|
isManual: false,
|
|
104
109
|
existingPlan: result.plan,
|
|
105
|
-
getSessionEpoch
|
|
110
|
+
getSessionEpoch,
|
|
106
111
|
notifyOnSuccess: false,
|
|
107
|
-
|
|
108
|
-
timeoutMs: settings.autoTitle.timeoutMs,
|
|
112
|
+
generation: buildGeneration(resolution),
|
|
109
113
|
})
|
|
110
114
|
.then((outcome) => {
|
|
111
115
|
if (outcome.ok) {
|
|
@@ -126,31 +130,34 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
|
|
|
126
130
|
});
|
|
127
131
|
});
|
|
128
132
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
return {
|
|
134
|
+
onSessionStart(_event, ctx) {
|
|
135
|
+
persistAutoTitleState(pi, controller.handleSessionStart(ctx));
|
|
136
|
+
},
|
|
137
|
+
onSessionShutdown() {
|
|
138
|
+
controller.handleSessionShutdown();
|
|
139
|
+
titleWorkInFlight = undefined;
|
|
140
|
+
},
|
|
141
|
+
};
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
async function handleTitleInvocation(
|
|
138
145
|
pi: ExtensionAPI,
|
|
139
146
|
state: TitleRunState,
|
|
140
147
|
ctx: ExtensionCommandContext,
|
|
148
|
+
modelRuntime: ModelRuntime,
|
|
141
149
|
model: Model<Api> | undefined,
|
|
142
150
|
invocation: RetitleCommandInvocation,
|
|
143
|
-
|
|
144
|
-
timeoutMs: number,
|
|
151
|
+
generation: AutoTitleGeneration,
|
|
145
152
|
): Promise<RetitleCommandOutcome> {
|
|
146
153
|
const retitleOpts = {
|
|
147
154
|
pi,
|
|
148
155
|
controller: state.controller,
|
|
149
156
|
ctx,
|
|
157
|
+
modelRuntime,
|
|
150
158
|
model,
|
|
151
159
|
isManual: true,
|
|
152
|
-
|
|
153
|
-
timeoutMs,
|
|
160
|
+
generation,
|
|
154
161
|
getSessionEpoch: state.getSessionEpoch,
|
|
155
162
|
};
|
|
156
163
|
|
|
@@ -173,10 +180,10 @@ async function handleTitleInvocation(
|
|
|
173
180
|
pi,
|
|
174
181
|
state.controller,
|
|
175
182
|
ctx,
|
|
183
|
+
modelRuntime,
|
|
176
184
|
model,
|
|
177
185
|
state.getSessionEpoch,
|
|
178
|
-
|
|
179
|
-
timeoutMs,
|
|
186
|
+
generation,
|
|
180
187
|
);
|
|
181
188
|
}
|
|
182
189
|
|
|
@@ -189,10 +196,10 @@ async function handleTitleInvocation(
|
|
|
189
196
|
pi,
|
|
190
197
|
state.controller,
|
|
191
198
|
ctx,
|
|
199
|
+
modelRuntime,
|
|
192
200
|
model,
|
|
193
201
|
state.getSessionEpoch,
|
|
194
|
-
|
|
195
|
-
timeoutMs,
|
|
202
|
+
generation,
|
|
196
203
|
{
|
|
197
204
|
initialInvocation: {
|
|
198
205
|
scope: invocation.scope,
|
|
@@ -209,12 +216,12 @@ async function handleTitleInvocation(
|
|
|
209
216
|
pi,
|
|
210
217
|
state.controller,
|
|
211
218
|
ctx,
|
|
219
|
+
modelRuntime,
|
|
212
220
|
model,
|
|
213
221
|
scan,
|
|
214
222
|
mode,
|
|
215
223
|
state.getSessionEpoch,
|
|
216
|
-
|
|
217
|
-
timeoutMs,
|
|
224
|
+
generation,
|
|
218
225
|
);
|
|
219
226
|
notifyBulkRetitleResult(ctx, scan, mode, result);
|
|
220
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,12 +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
|
-
|
|
61
|
-
timeoutMs: number,
|
|
63
|
+
generation: AutoTitleGeneration,
|
|
62
64
|
): Promise<BulkRetitleResult> {
|
|
63
65
|
const result: BulkRetitleResult = {
|
|
64
66
|
attempted: 0,
|
|
@@ -83,10 +85,10 @@ export async function runBulkRetitle(
|
|
|
83
85
|
pi,
|
|
84
86
|
controller,
|
|
85
87
|
ctx,
|
|
88
|
+
modelRuntime,
|
|
86
89
|
model,
|
|
87
90
|
isManual: true,
|
|
88
|
-
|
|
89
|
-
timeoutMs,
|
|
91
|
+
generation,
|
|
90
92
|
getSessionEpoch,
|
|
91
93
|
notifyOnSuccess: false,
|
|
92
94
|
});
|
|
@@ -94,7 +96,7 @@ export async function runBulkRetitle(
|
|
|
94
96
|
continue;
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
const outcome = await retitleStoredSession(
|
|
99
|
+
const outcome = await retitleStoredSession(modelRuntime, model, session.path, generation);
|
|
98
100
|
result[outcome] += 1;
|
|
99
101
|
}
|
|
100
102
|
|
|
@@ -164,10 +166,10 @@ export interface RetitlePlanOptions {
|
|
|
164
166
|
pi: ExtensionAPI;
|
|
165
167
|
controller: SessionAutoTitleController;
|
|
166
168
|
ctx: ExtensionContext;
|
|
169
|
+
modelRuntime: ModelRuntime;
|
|
167
170
|
model: Model<Api> | undefined;
|
|
168
171
|
isManual: boolean;
|
|
169
|
-
|
|
170
|
-
timeoutMs: number;
|
|
172
|
+
generation: AutoTitleGeneration;
|
|
171
173
|
existingPlan?: AutoTitleTriggerPlan;
|
|
172
174
|
getSessionEpoch?: () => number;
|
|
173
175
|
notifyOnSuccess?: boolean;
|
|
@@ -180,10 +182,10 @@ export async function runRetitlePlan(
|
|
|
180
182
|
pi,
|
|
181
183
|
controller,
|
|
182
184
|
ctx,
|
|
185
|
+
modelRuntime,
|
|
183
186
|
model,
|
|
184
187
|
isManual,
|
|
185
|
-
|
|
186
|
-
timeoutMs,
|
|
188
|
+
generation,
|
|
187
189
|
existingPlan,
|
|
188
190
|
getSessionEpoch,
|
|
189
191
|
} = options;
|
|
@@ -218,12 +220,11 @@ export async function runRetitlePlan(
|
|
|
218
220
|
},
|
|
219
221
|
);
|
|
220
222
|
const generatedTitle = await generateAutoTitle(
|
|
221
|
-
|
|
223
|
+
modelRuntime,
|
|
222
224
|
model,
|
|
223
225
|
titleContext,
|
|
224
226
|
plan.reason,
|
|
225
|
-
|
|
226
|
-
timeoutMs,
|
|
227
|
+
generation,
|
|
227
228
|
);
|
|
228
229
|
if (!generatedTitle.ok) {
|
|
229
230
|
return generatedTitle;
|
|
@@ -266,11 +267,10 @@ export function persistAutoTitleState(
|
|
|
266
267
|
}
|
|
267
268
|
|
|
268
269
|
async function retitleStoredSession(
|
|
269
|
-
|
|
270
|
+
modelRuntime: ModelRuntime,
|
|
270
271
|
model: Model<Api>,
|
|
271
272
|
sessionPath: string,
|
|
272
|
-
|
|
273
|
-
timeoutMs: number,
|
|
273
|
+
generation: AutoTitleGeneration,
|
|
274
274
|
): Promise<"retitled" | "unchanged" | "failed"> {
|
|
275
275
|
const sessionManager = SessionManager.open(sessionPath);
|
|
276
276
|
const currentTitle = sessionManager.getSessionName();
|
|
@@ -288,12 +288,11 @@ async function retitleStoredSession(
|
|
|
288
288
|
currentTitle,
|
|
289
289
|
};
|
|
290
290
|
const generatedTitle = await generateAutoTitle(
|
|
291
|
-
|
|
291
|
+
modelRuntime,
|
|
292
292
|
model,
|
|
293
293
|
titleContext,
|
|
294
294
|
plan.reason,
|
|
295
|
-
|
|
296
|
-
timeoutMs,
|
|
295
|
+
generation,
|
|
297
296
|
);
|
|
298
297
|
if (!generatedTitle.ok) {
|
|
299
298
|
return "failed";
|