@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 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 = serializeConversation(convertToLlm(messages));
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 [serializeConversation(convertToLlm([entry.message]))];
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}` : ""}]: ${typeof entry.content === "string" ? entry.content : JSON.stringify(entry.data ?? {})}`];
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 messages = getHandoffMessages(ctx.sessionManager.getBranch());
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 llmMessages = convertToLlm(messages);
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 ? serializeConversation(convertToLlm(messages)) : fallbackText;
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 === undefined || nextTitle === tmuxLastPaneTitle) return;
4582
- tmuxLastPaneTitle = nextTitle;
4583
- setTmuxPaneTitle(target, nextTitle);
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.1.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",