@vanillagreen/pi-qol 1.1.1 → 1.2.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/qol.ts +116 -51
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -63,6 +63,7 @@ Settings are exposed through `pi-extension-manager` under **QOL**.
|
|
|
63
63
|
- `replaceFooter`: hide Pi's default footer while QOL's compact statusline is active; default on.
|
|
64
64
|
- `compactPrompt`: use the compact `π` prompt editor; default on.
|
|
65
65
|
- `showSessionNameTitle`: show the named session above the prompt, or in tmux's pane title/border; default on.
|
|
66
|
+
- `showSessionNameWindow`: also rename the current tmux window to `π <session>` so it appears in the tmux status line. Disables `automatic-rename` for the window while active and restores it on shutdown. Requires `showSessionNameTitle`; default on.
|
|
66
67
|
- `inputBottomPaddingLines`: blank lines below the compact prompt; default `0`.
|
|
67
68
|
- Git display knobs: `gitRefreshTimeoutMs`, `showDirtyMarker`.
|
|
68
69
|
- If `pi-caveman` is loaded, QOL reads its bridge to show a Caveman icon in the statusline and to make `Alt+C` cycle Caveman modes. The Caveman package's `showStatusBadge` setting also controls this QOL badge.
|
package/extensions/qol.ts
CHANGED
|
@@ -1307,6 +1307,14 @@ function setTmuxWindowOption(target: string, option: string, value: string): voi
|
|
|
1307
1307
|
execFile("tmux", ["set-option", "-wq", "-t", target, option, value], { timeout: 1000 }, () => undefined);
|
|
1308
1308
|
}
|
|
1309
1309
|
|
|
1310
|
+
function readTmuxWindowName(target: string, callback: (name: string | undefined) => void): void {
|
|
1311
|
+
execFile("tmux", ["display-message", "-p", "-t", target, "#{window_name}"], { timeout: 1000 }, (error, stdout) => callback(error ? undefined : stdout.replace(/\r?\n$/, "")));
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function setTmuxWindowName(target: string, name: string): void {
|
|
1315
|
+
execFile("tmux", ["rename-window", "-t", target, name], { timeout: 1000 }, () => undefined);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1310
1318
|
function cavemanIconTone(mode: string, active: boolean): "muted" | "text" | "success" | "thinkingHigh" | "error" {
|
|
1311
1319
|
if (!active) return "muted";
|
|
1312
1320
|
switch (mode) {
|
|
@@ -1371,6 +1379,32 @@ function compactionProfileInstructions(profile: QolSummaryProfile): string {
|
|
|
1371
1379
|
return "Be complete but not verbose. Preserve enough detail for a future assistant to continue without the old transcript.";
|
|
1372
1380
|
}
|
|
1373
1381
|
|
|
1382
|
+
function stripThinkingForSummary(messages: Message[]): Message[] {
|
|
1383
|
+
return messages.map((message) => {
|
|
1384
|
+
if (message.role !== "assistant" || !Array.isArray(message.content)) return message;
|
|
1385
|
+
return {
|
|
1386
|
+
...message,
|
|
1387
|
+
content: message.content.filter((part: any) => part?.type !== "thinking"),
|
|
1388
|
+
};
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function serializeMessagesForSummary(messages: AgentMessage[]): string {
|
|
1393
|
+
return serializeConversation(stripThinkingForSummary(convertToLlm(messages)));
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
function customMessageContentToText(content: unknown): string {
|
|
1397
|
+
if (typeof content === "string") return content;
|
|
1398
|
+
if (!Array.isArray(content)) return "";
|
|
1399
|
+
const parts: string[] = [];
|
|
1400
|
+
for (const part of content) {
|
|
1401
|
+
if (part?.type === "text" && typeof part.text === "string") parts.push(part.text);
|
|
1402
|
+
else if (part?.type === "image") parts.push(`[image${typeof part.mimeType === "string" ? ` ${part.mimeType}` : ""}]`);
|
|
1403
|
+
else if (part?.type) parts.push(`[${String(part.type)}]`);
|
|
1404
|
+
}
|
|
1405
|
+
return parts.join("\n").trim();
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1374
1408
|
function buildSummaryPrompt(options: {
|
|
1375
1409
|
conversationText: string;
|
|
1376
1410
|
customInstructions?: string;
|
|
@@ -1497,7 +1531,7 @@ async function handleQolCompaction(event: any, ctx: ExtensionContext): Promise<a
|
|
|
1497
1531
|
const tokensBefore = typeof preparation.tokensBefore === "number" ? preparation.tokensBefore : 0;
|
|
1498
1532
|
compactionNotify(ctx, `QOL compaction: summarizing ${messages.length} message(s), ${tokensBefore.toLocaleString()} token(s).`, "info");
|
|
1499
1533
|
try {
|
|
1500
|
-
const conversationText =
|
|
1534
|
+
const conversationText = serializeMessagesForSummary(messages);
|
|
1501
1535
|
const result = await generateQolSummary(ctx, {
|
|
1502
1536
|
conversationText,
|
|
1503
1537
|
customInstructions: event.customInstructions,
|
|
@@ -1529,10 +1563,10 @@ async function handleQolCompaction(event: any, ctx: ExtensionContext): Promise<a
|
|
|
1529
1563
|
}
|
|
1530
1564
|
|
|
1531
1565
|
function summarizeEntryForBranch(entry: any): string[] {
|
|
1532
|
-
if (entry?.type === "message" && entry.message) return [
|
|
1566
|
+
if (entry?.type === "message" && entry.message) return [serializeMessagesForSummary([entry.message])];
|
|
1533
1567
|
if (entry?.type === "compaction" && typeof entry.summary === "string") return [`[Compaction summary]: ${entry.summary}`];
|
|
1534
1568
|
if (entry?.type === "branch_summary" && typeof entry.summary === "string") return [`[Branch summary]: ${entry.summary}`];
|
|
1535
|
-
if (entry?.type === "custom_message") return [`[Custom message${entry.customType ? `:${entry.customType}` : ""}]: ${
|
|
1569
|
+
if (entry?.type === "custom_message") return [`[Custom message${entry.customType ? `:${entry.customType}` : ""}]: ${customMessageContentToText(entry.content) || "[empty]"}`];
|
|
1536
1570
|
return [];
|
|
1537
1571
|
}
|
|
1538
1572
|
|
|
@@ -2131,39 +2165,6 @@ function getQuestionService(): QuestionServiceLike | undefined {
|
|
|
2131
2165
|
return undefined;
|
|
2132
2166
|
}
|
|
2133
2167
|
|
|
2134
|
-
function entryToHandoffMessage(entry: SessionEntry): AgentMessage | undefined {
|
|
2135
|
-
if (entry.type === "message") return entry.message;
|
|
2136
|
-
if (entry.type === "compaction") {
|
|
2137
|
-
return {
|
|
2138
|
-
role: "compactionSummary",
|
|
2139
|
-
summary: entry.summary,
|
|
2140
|
-
tokensBefore: entry.tokensBefore,
|
|
2141
|
-
timestamp: new Date(entry.timestamp).getTime(),
|
|
2142
|
-
};
|
|
2143
|
-
}
|
|
2144
|
-
return undefined;
|
|
2145
|
-
}
|
|
2146
|
-
|
|
2147
|
-
function getHandoffMessages(branch: SessionEntry[]): AgentMessage[] {
|
|
2148
|
-
let compactionIndex = -1;
|
|
2149
|
-
for (let i = branch.length - 1; i >= 0; i -= 1) {
|
|
2150
|
-
if (branch[i]?.type === "compaction") {
|
|
2151
|
-
compactionIndex = i;
|
|
2152
|
-
break;
|
|
2153
|
-
}
|
|
2154
|
-
}
|
|
2155
|
-
if (compactionIndex < 0) return branch.map(entryToHandoffMessage).filter((message): message is AgentMessage => message !== undefined);
|
|
2156
|
-
|
|
2157
|
-
const compaction = branch[compactionIndex];
|
|
2158
|
-
const firstKeptIndex = compaction?.type === "compaction" ? branch.findIndex((entry) => entry.id === compaction.firstKeptEntryId) : -1;
|
|
2159
|
-
const compactedBranch = [
|
|
2160
|
-
compaction,
|
|
2161
|
-
...(firstKeptIndex >= 0 ? branch.slice(firstKeptIndex, compactionIndex) : []),
|
|
2162
|
-
...branch.slice(compactionIndex + 1),
|
|
2163
|
-
];
|
|
2164
|
-
return compactedBranch.map(entryToHandoffMessage).filter((message): message is AgentMessage => message !== undefined);
|
|
2165
|
-
}
|
|
2166
|
-
|
|
2167
2168
|
async function runHandoff(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
2168
2169
|
if (!ctx.hasUI) {
|
|
2169
2170
|
ctx.ui.notify("handoff requires interactive mode", "error");
|
|
@@ -2181,15 +2182,15 @@ async function runHandoff(args: string, ctx: ExtensionCommandContext): Promise<v
|
|
|
2181
2182
|
return;
|
|
2182
2183
|
}
|
|
2183
2184
|
|
|
2184
|
-
const
|
|
2185
|
+
const built = (ctx.sessionManager as any).buildSessionContext?.();
|
|
2186
|
+
const messages = Array.isArray(built?.messages) ? built.messages as AgentMessage[] : [];
|
|
2185
2187
|
|
|
2186
2188
|
if (messages.length === 0) {
|
|
2187
2189
|
ctx.ui.notify("No conversation to hand off", "error");
|
|
2188
2190
|
return;
|
|
2189
2191
|
}
|
|
2190
2192
|
|
|
2191
|
-
const
|
|
2192
|
-
const conversationText = serializeConversation(llmMessages);
|
|
2193
|
+
const conversationText = serializeMessagesForSummary(messages);
|
|
2193
2194
|
const currentSessionFile = ctx.sessionManager.getSessionFile();
|
|
2194
2195
|
|
|
2195
2196
|
const releaseModalLock = acquireVstackModalLock();
|
|
@@ -2377,10 +2378,24 @@ interface QolSessionSearchPendingMessage {
|
|
|
2377
2378
|
interface QolSessionPaletteAction {
|
|
2378
2379
|
type: "cancel" | "resume" | "fork" | "copy" | "summarize" | "newSession";
|
|
2379
2380
|
customPrompt?: string;
|
|
2381
|
+
keepCurrentModel?: boolean;
|
|
2380
2382
|
message?: QolSessionUserMessage;
|
|
2381
2383
|
result?: QolSessionSearchResult;
|
|
2382
2384
|
}
|
|
2383
2385
|
|
|
2386
|
+
function pinSessionModel(sessionPath: string, model: NonNullable<ExtensionContext["model"]>, thinkingLevel?: string): void {
|
|
2387
|
+
const manager = SessionManager.open(sessionPath);
|
|
2388
|
+
const context = manager.buildSessionContext();
|
|
2389
|
+
if (context.model?.provider !== model.provider || context.model?.modelId !== model.id) {
|
|
2390
|
+
manager.appendModelChange(model.provider, model.id);
|
|
2391
|
+
}
|
|
2392
|
+
if (thinkingLevel) {
|
|
2393
|
+
const branch = manager.getBranch();
|
|
2394
|
+
const lastThinking = [...branch].reverse().find((entry: any) => entry?.type === "thinking_level_change") as { thinkingLevel?: string } | undefined;
|
|
2395
|
+
if (lastThinking?.thinkingLevel !== thinkingLevel) manager.appendThinkingLevelChange(thinkingLevel as any);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2384
2399
|
let qolSessionSearchCache: QolSessionSearchSession[] = [];
|
|
2385
2400
|
let qolSessionSearchLoadedAt = 0;
|
|
2386
2401
|
let qolSessionSearchLoading: Promise<QolSessionSearchSession[]> | undefined;
|
|
@@ -3561,6 +3576,11 @@ class QolSessionSearchComponent {
|
|
|
3561
3576
|
if (hit) this.done({ result: hit.result, type: "resume" });
|
|
3562
3577
|
return;
|
|
3563
3578
|
}
|
|
3579
|
+
if (matchesKey(data, "alt+m")) {
|
|
3580
|
+
const hit = state.results[state.selected];
|
|
3581
|
+
if (hit) this.done({ keepCurrentModel: true, result: hit.result, type: "resume" });
|
|
3582
|
+
return;
|
|
3583
|
+
}
|
|
3564
3584
|
if (matchesKey(data, "alt+i")) {
|
|
3565
3585
|
const hit = state.results[state.selected];
|
|
3566
3586
|
if (hit) this.openContextConfirm("summarize", hit.result, hit.message, "search");
|
|
@@ -3655,6 +3675,10 @@ class QolSessionSearchComponent {
|
|
|
3655
3675
|
this.done({ result: state.result, type: "resume" });
|
|
3656
3676
|
return;
|
|
3657
3677
|
}
|
|
3678
|
+
if (matchesKey(data, "alt+m")) {
|
|
3679
|
+
this.done({ keepCurrentModel: true, result: state.result, type: "resume" });
|
|
3680
|
+
return;
|
|
3681
|
+
}
|
|
3658
3682
|
if (matchesKey(data, "alt+c")) {
|
|
3659
3683
|
const message = state.messages[state.selected];
|
|
3660
3684
|
if (message) this.done({ message, result: state.result, type: "copy" });
|
|
@@ -3719,6 +3743,10 @@ class QolSessionSearchComponent {
|
|
|
3719
3743
|
this.done({ result: state.result, type: "resume" });
|
|
3720
3744
|
return;
|
|
3721
3745
|
}
|
|
3746
|
+
if (matchesKey(data, "alt+m")) {
|
|
3747
|
+
this.done({ keepCurrentModel: true, result: state.result, type: "resume" });
|
|
3748
|
+
return;
|
|
3749
|
+
}
|
|
3722
3750
|
if (matchesKey(data, "alt+i")) {
|
|
3723
3751
|
this.openContextConfirm("summarize", state.result, state.message, "actions");
|
|
3724
3752
|
return;
|
|
@@ -3801,10 +3829,10 @@ class QolSessionSearchComponent {
|
|
|
3801
3829
|
|
|
3802
3830
|
lines.push(divider());
|
|
3803
3831
|
if (compact) {
|
|
3804
|
-
lines.push(row(`${ansiYellow("alt+c/f/r/i/n/a")} ${dim("actions")} ${ansiYellow("tab")} ${dim("scope")}`));
|
|
3832
|
+
lines.push(row(`${ansiYellow("alt+c/f/r/m/i/n/a")} ${dim("actions")} ${ansiYellow("tab")} ${dim("scope")}`));
|
|
3805
3833
|
} else {
|
|
3806
3834
|
lines.push(row(`${ansiYellow("-/=")} ${dim("page")} ${ansiYellow("alt+c")} ${dim("copy")} ${ansiYellow("alt+f")} ${dim("fork")} ${ansiYellow("alt+r")} ${dim("resume")}`));
|
|
3807
|
-
lines.push(row(`${ansiYellow("alt+i")} ${dim("inject+ctx")} ${ansiYellow("alt+n")} ${dim("new+ctx")} ${ansiYellow("alt+a")} ${dim("actions")} ${ansiYellow("tab")} ${dim("scope")}`));
|
|
3835
|
+
lines.push(row(`${ansiYellow("alt+m")} ${dim("resume+model")} ${ansiYellow("alt+i")} ${dim("inject+ctx")} ${ansiYellow("alt+n")} ${dim("new+ctx")} ${ansiYellow("alt+a")} ${dim("actions")} ${ansiYellow("tab")} ${dim("scope")}`));
|
|
3808
3836
|
}
|
|
3809
3837
|
lines.push(bottom());
|
|
3810
3838
|
return lines;
|
|
@@ -3930,7 +3958,7 @@ class QolSessionSearchComponent {
|
|
|
3930
3958
|
}
|
|
3931
3959
|
lines.push(divider(), empty());
|
|
3932
3960
|
lines.push(row(`${ansiYellow("-/=")} ${dim("page")} ${ansiYellow("alt+c")} ${dim("copy")} ${ansiYellow("alt+f")} ${dim("fork")} ${ansiYellow("alt+r")} ${dim("resume")}`));
|
|
3933
|
-
lines.push(row(`${ansiYellow("alt+i")} ${dim("inject+ctx")} ${ansiYellow("alt+n")} ${dim("new+ctx")}`));
|
|
3961
|
+
lines.push(row(`${ansiYellow("alt+m")} ${dim("resume+model")} ${ansiYellow("alt+i")} ${dim("inject+ctx")} ${ansiYellow("alt+n")} ${dim("new+ctx")}`));
|
|
3934
3962
|
lines.push(bottom());
|
|
3935
3963
|
return lines;
|
|
3936
3964
|
}
|
|
@@ -3959,7 +3987,7 @@ class QolSessionSearchComponent {
|
|
|
3959
3987
|
for (const line of wrapVisible(dim(help), inner, 2)) lines.push(row(line));
|
|
3960
3988
|
}
|
|
3961
3989
|
lines.push(empty(), divider(), empty());
|
|
3962
|
-
lines.push(row(`${ansiYellow("alt+c")} ${dim("copy")} ${ansiYellow("alt+f")} ${dim("fork")} ${ansiYellow("alt+r")} ${dim("resume")}`));
|
|
3990
|
+
lines.push(row(`${ansiYellow("alt+c")} ${dim("copy")} ${ansiYellow("alt+f")} ${dim("fork")} ${ansiYellow("alt+r")} ${dim("resume")} ${ansiYellow("alt+m")} ${dim("resume+model")}`));
|
|
3963
3991
|
lines.push(row(`${ansiYellow("alt+i")} ${dim("inject+ctx")} ${ansiYellow("alt+n")} ${dim("new+ctx")}`));
|
|
3964
3992
|
lines.push(bottom());
|
|
3965
3993
|
return lines;
|
|
@@ -4131,7 +4159,7 @@ function messagesForSessionSummary(sessionPath: string): AgentMessage[] {
|
|
|
4131
4159
|
async function buildSessionSearchContextMessage(ctx: ExtensionContext, result: QolSessionSearchResult, customPrompt?: string, signal?: AbortSignal): Promise<QolSessionSearchPendingMessage> {
|
|
4132
4160
|
const messages = messagesForSessionSummary(result.path);
|
|
4133
4161
|
const fallbackText = result.allMessagesText || result.firstMessage;
|
|
4134
|
-
const conversationText = messages.length > 0 ?
|
|
4162
|
+
const conversationText = messages.length > 0 ? serializeMessagesForSummary(messages) : fallbackText;
|
|
4135
4163
|
if (!conversationText.trim()) throw new Error("Selected session has no text content to summarize");
|
|
4136
4164
|
const focus = customPrompt?.trim();
|
|
4137
4165
|
const summary = await generateQolSummary(ctx, {
|
|
@@ -4229,17 +4257,21 @@ function queueSessionSearchCommandAction(ctx: ExtensionContext, action: QolSessi
|
|
|
4229
4257
|
ctx.ui.notify(`${sessionDisplayName(action.result!)} — press Enter to ${action.type === "fork" ? "fork" : "resume"}`, "info");
|
|
4230
4258
|
}
|
|
4231
4259
|
|
|
4232
|
-
async function runSessionSearchResumeOrFork(ctx: ExtensionContext, action: QolSessionPaletteAction): Promise<boolean> {
|
|
4260
|
+
async function runSessionSearchResumeOrFork(pi: ExtensionAPI, ctx: ExtensionContext, action: QolSessionPaletteAction): Promise<boolean> {
|
|
4233
4261
|
if (!action.result || (action.type !== "resume" && action.type !== "fork")) return true;
|
|
4234
4262
|
const commandCtx = asCommandContext(ctx);
|
|
4235
4263
|
if (typeof commandCtx.switchSession !== "function") return false;
|
|
4236
4264
|
const targetTitle = sessionDisplayName(action.result);
|
|
4237
4265
|
const selectedMessage = action.type === "fork" ? action.message : undefined;
|
|
4266
|
+
const currentModel = action.keepCurrentModel ? ctx.model : undefined;
|
|
4267
|
+
const currentThinking = action.keepCurrentModel && typeof pi.getThinkingLevel === "function" ? pi.getThinkingLevel() : undefined;
|
|
4268
|
+
if (currentModel) pinSessionModel(action.result.path, currentModel, currentThinking);
|
|
4238
4269
|
let replacementStarted = false;
|
|
4239
4270
|
try {
|
|
4240
4271
|
const result = await commandCtx.switchSession(action.result.path, {
|
|
4241
4272
|
withSession: async (replacementCtx: any) => {
|
|
4242
4273
|
replacementStarted = true;
|
|
4274
|
+
if (currentModel) replacementCtx.ui.notify(`Using current model: ${currentModel.provider}/${currentModel.id}`, "info");
|
|
4243
4275
|
if (selectedMessage) {
|
|
4244
4276
|
const manager = replacementCtx.sessionManager as any;
|
|
4245
4277
|
if (selectedMessage.entryId && selectedMessage.parentId && typeof manager?.branch === "function") manager.branch(selectedMessage.parentId);
|
|
@@ -4251,7 +4283,7 @@ async function runSessionSearchResumeOrFork(ctx: ExtensionContext, action: QolSe
|
|
|
4251
4283
|
timer.unref?.();
|
|
4252
4284
|
return;
|
|
4253
4285
|
}
|
|
4254
|
-
replacementCtx.ui.notify(`Resumed session: ${targetTitle}`, "info");
|
|
4286
|
+
replacementCtx.ui.notify(`Resumed session: ${targetTitle}${currentModel ? " with current model" : ""}`, "info");
|
|
4255
4287
|
},
|
|
4256
4288
|
});
|
|
4257
4289
|
if (result.cancelled) ctx.ui.notify(selectedMessage ? "Fork cancelled" : "Resume cancelled", "info");
|
|
@@ -4327,7 +4359,7 @@ async function openQolSessionSearch(pi: ExtensionAPI, ctx: ExtensionContext, ini
|
|
|
4327
4359
|
}
|
|
4328
4360
|
if (!action || action.type === "cancel" || !action.result) return;
|
|
4329
4361
|
if (action.type === "resume" || action.type === "fork") {
|
|
4330
|
-
if (!(await runSessionSearchResumeOrFork(ctx, action))) queueSessionSearchCommandAction(ctx, action);
|
|
4362
|
+
if (!(await runSessionSearchResumeOrFork(pi, ctx, action))) queueSessionSearchCommandAction(ctx, action);
|
|
4331
4363
|
return;
|
|
4332
4364
|
}
|
|
4333
4365
|
if (action.type === "copy") {
|
|
@@ -4552,6 +4584,10 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
4552
4584
|
let tmuxChangedPaneBorderStatus = false;
|
|
4553
4585
|
let tmuxChangedPaneBorderFormat = false;
|
|
4554
4586
|
let tmuxLastPaneTitle: string | undefined;
|
|
4587
|
+
let tmuxOriginalWindowNameTitle: string | undefined;
|
|
4588
|
+
let tmuxOriginalAutomaticRename: string | undefined;
|
|
4589
|
+
let tmuxChangedAutomaticRename = false;
|
|
4590
|
+
let tmuxLastWindowNameTitle: string | undefined;
|
|
4555
4591
|
let lastSessionTitle: string | undefined;
|
|
4556
4592
|
|
|
4557
4593
|
const requestRender = () => activeTui?.requestRender();
|
|
@@ -4578,9 +4614,17 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
4578
4614
|
const target = tmuxPaneTitleTarget;
|
|
4579
4615
|
if (!target) return;
|
|
4580
4616
|
const nextTitle = sessionTitle ? formatTmuxSessionTitle(sessionTitle) : tmuxOriginalPaneTitle;
|
|
4581
|
-
if (nextTitle
|
|
4582
|
-
|
|
4583
|
-
|
|
4617
|
+
if (nextTitle !== undefined && nextTitle !== tmuxLastPaneTitle) {
|
|
4618
|
+
tmuxLastPaneTitle = nextTitle;
|
|
4619
|
+
setTmuxPaneTitle(target, nextTitle);
|
|
4620
|
+
}
|
|
4621
|
+
if (settingBoolean("showSessionNameWindow", true, ctx.cwd)) {
|
|
4622
|
+
const nextWindow = sessionTitle ? formatTmuxSessionTitle(sessionTitle) : tmuxOriginalWindowNameTitle;
|
|
4623
|
+
if (nextWindow !== undefined && nextWindow !== tmuxLastWindowNameTitle) {
|
|
4624
|
+
tmuxLastWindowNameTitle = nextWindow;
|
|
4625
|
+
setTmuxWindowName(target, nextWindow);
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4584
4628
|
};
|
|
4585
4629
|
|
|
4586
4630
|
const installSessionTitle = (ctx: ExtensionContext) => {
|
|
@@ -4609,6 +4653,21 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
4609
4653
|
setTmuxWindowOption(tmuxTarget, "pane-border-format", TMUX_SESSION_TITLE_BORDER_FORMAT);
|
|
4610
4654
|
}
|
|
4611
4655
|
});
|
|
4656
|
+
if (settingBoolean("showSessionNameWindow", true, ctx.cwd)) {
|
|
4657
|
+
readTmuxWindowName(tmuxTarget, (name) => {
|
|
4658
|
+
if (tmuxPaneTitleTarget !== tmuxTarget) return;
|
|
4659
|
+
tmuxOriginalWindowNameTitle = name;
|
|
4660
|
+
syncSessionTitle(ctx);
|
|
4661
|
+
});
|
|
4662
|
+
readTmuxWindowOption(tmuxTarget, "automatic-rename", (value) => {
|
|
4663
|
+
if (tmuxPaneTitleTarget !== tmuxTarget) return;
|
|
4664
|
+
tmuxOriginalAutomaticRename = value;
|
|
4665
|
+
if (value !== "off") {
|
|
4666
|
+
tmuxChangedAutomaticRename = true;
|
|
4667
|
+
setTmuxWindowOption(tmuxTarget, "automatic-rename", "off");
|
|
4668
|
+
}
|
|
4669
|
+
});
|
|
4670
|
+
}
|
|
4612
4671
|
return;
|
|
4613
4672
|
}
|
|
4614
4673
|
ctx.ui.setHeader((tui, theme) => {
|
|
@@ -4634,6 +4693,8 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
4634
4693
|
if (tmuxPaneTitleTarget && tmuxOriginalPaneTitle !== undefined) setTmuxPaneTitle(tmuxPaneTitleTarget, tmuxOriginalPaneTitle);
|
|
4635
4694
|
if (tmuxPaneTitleTarget && tmuxChangedPaneBorderStatus && tmuxOriginalPaneBorderStatus !== undefined) setTmuxWindowOption(tmuxPaneTitleTarget, "pane-border-status", tmuxOriginalPaneBorderStatus);
|
|
4636
4695
|
if (tmuxPaneTitleTarget && tmuxChangedPaneBorderFormat && tmuxOriginalPaneBorderFormat !== undefined) setTmuxWindowOption(tmuxPaneTitleTarget, "pane-border-format", tmuxOriginalPaneBorderFormat);
|
|
4696
|
+
if (tmuxPaneTitleTarget && tmuxOriginalWindowNameTitle !== undefined && tmuxLastWindowNameTitle !== undefined) setTmuxWindowName(tmuxPaneTitleTarget, tmuxOriginalWindowNameTitle);
|
|
4697
|
+
if (tmuxPaneTitleTarget && tmuxChangedAutomaticRename && tmuxOriginalAutomaticRename !== undefined) setTmuxWindowOption(tmuxPaneTitleTarget, "automatic-rename", tmuxOriginalAutomaticRename);
|
|
4637
4698
|
tmuxPaneTitleTarget = undefined;
|
|
4638
4699
|
tmuxOriginalPaneTitle = undefined;
|
|
4639
4700
|
tmuxOriginalPaneBorderStatus = undefined;
|
|
@@ -4641,6 +4702,10 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
4641
4702
|
tmuxChangedPaneBorderStatus = false;
|
|
4642
4703
|
tmuxChangedPaneBorderFormat = false;
|
|
4643
4704
|
tmuxLastPaneTitle = undefined;
|
|
4705
|
+
tmuxOriginalWindowNameTitle = undefined;
|
|
4706
|
+
tmuxOriginalAutomaticRename = undefined;
|
|
4707
|
+
tmuxChangedAutomaticRename = false;
|
|
4708
|
+
tmuxLastWindowNameTitle = undefined;
|
|
4644
4709
|
lastSessionTitle = undefined;
|
|
4645
4710
|
ctx.ui.setStatus(SESSION_MANAGER_STATUS_KEY, undefined);
|
|
4646
4711
|
ctx.ui.setWidget("statusline", undefined);
|
|
@@ -4953,7 +5018,7 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
4953
5018
|
return;
|
|
4954
5019
|
}
|
|
4955
5020
|
qolSessionSearchPendingActions.delete(id);
|
|
4956
|
-
if (!(await runSessionSearchResumeOrFork(ctx, action))) ctx.ui.notify("Session resume/fork is unavailable in this context.", "error");
|
|
5021
|
+
if (!(await runSessionSearchResumeOrFork(pi, ctx, action))) ctx.ui.notify("Session resume/fork is unavailable in this context.", "error");
|
|
4957
5022
|
},
|
|
4958
5023
|
});
|
|
4959
5024
|
const shortcut = sessionSearchShortcut();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vanillagreen/pi-qol",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Pi quality-of-life extension: compact statusline/π prompt, reliable multiline input, styled pasted-image chips, session naming/search/context import, handoff, permission prompts, notifications, custom compaction, and a collapsed-thinking timer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -60,6 +60,16 @@
|
|
|
60
60
|
"apply": "reload",
|
|
61
61
|
"requiresReload": true
|
|
62
62
|
},
|
|
63
|
+
{
|
|
64
|
+
"key": "showSessionNameWindow",
|
|
65
|
+
"label": "Sync session name to tmux window name",
|
|
66
|
+
"description": "Also rename the current tmux window to \"π <session>\" so the session name shows in the tmux status line. Disables tmux automatic-rename for this window while active and restores the original window name and automatic-rename setting on shutdown.",
|
|
67
|
+
"type": "boolean",
|
|
68
|
+
"default": true,
|
|
69
|
+
"category": "Statusline",
|
|
70
|
+
"apply": "reload",
|
|
71
|
+
"requiresReload": true
|
|
72
|
+
},
|
|
63
73
|
{
|
|
64
74
|
"key": "inputBottomPaddingLines",
|
|
65
75
|
"label": "Input bottom padding",
|