pi-cc-extensions 0.8.42 → 0.8.43

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.en.md CHANGED
@@ -22,11 +22,9 @@
22
22
  ## Preview
23
23
 
24
24
  <table>
25
- <img src="./assets/readme/welcome.webp" width="100%" alt="Pi welcome screen">
25
+ <img width="2788" height="1846" alt="demo_new" src="https://github.com/user-attachments/assets/d4f9bb51-a49a-4a34-aa60-006514d37b09" />
26
26
  </table>
27
27
 
28
- https://github.com/user-attachments/assets/4ae094d2-7480-484b-a0c8-6e782495ce9f
29
-
30
28
  ## Quick start
31
29
 
32
30
  ```bash
@@ -113,9 +111,18 @@ Run `/reload` after changing extensions.
113
111
  ## Compatibility
114
112
 
115
113
  - Node.js `>=22.19.0`
116
- - Loaded through `pi.extensions` and `pi.themes` in the root `package.json`
114
+ - Pi `^0.84.0` (loaded through `pi.extensions` and `pi.themes` in the root `package.json`)
115
+ - For Pi 0.80.x and earlier, use an older release of this package (peerDependencies are now `^0.84.0`)
117
116
  - If a Pi, TUI, or `@tifan/pi-fixed-editor` update causes display issues, try `/reload` first
118
117
 
118
+ ### Fullscreen mode & sunset features
119
+
120
+ - Official `--tui-mode fullscreen` (and runtime switching via `/settings`) is reused as-is: when `tui.mode === "fullscreen"`, the extension stands down and installs no rendering layer (tool styling, compact mode, tool grouping, and the fixed editor are all skipped), leaving fullscreen entirely to the official TUI. Data features (context, session references, autocomplete, etc.) are unaffected.
121
+ - Capabilities that overlap with the official fullscreen TUI are sunset (frozen, kept working only): fixed-editor layout, independently scrollable transcript, etc. — no second implementation.
122
+ - Known boundary: when switching from regular to fullscreen at runtime, per-TUI rendering patches are automatically abandoned with the old TUI instance, but component-level style patches remain active (they do not affect the official layout); for full isolation, start the session in fullscreen mode.
123
+ - Pi 0.84+ wraps the TUI reference in a lazy Proxy (`createInteractiveTuiReference`): capturing `doRender`/`render`/`handleInput` through it resolves to the wrapper itself and recurses infinitely. The extension detects the lazy Proxy and skips line-level rendering patches; `@tifan/pi-fixed-editor`'s compositor is likewise disabled on 0.84+ (Fixed editor interactions are temporarily unavailable; rendering is handled by the official pipeline).
124
+ - The `compact-thinking.json` config file is sunset (no longer read or maintained); compact-thinking settings are managed exclusively by `claude-code-style.json`.
125
+
119
126
  ## Recommended companions
120
127
 
121
128
 
package/README.md CHANGED
@@ -22,11 +22,9 @@
22
22
  ## 界面预览
23
23
 
24
24
  <table>
25
- <img src="./assets/readme/welcome.webp" width="100%" alt="Pi 启动后的欢迎界面">
25
+ <img width="2788" height="1846" alt="demo_new" src="https://github.com/user-attachments/assets/d4f9bb51-a49a-4a34-aa60-006514d37b09" />
26
26
  </table>
27
27
 
28
- https://github.com/user-attachments/assets/4ae094d2-7480-484b-a0c8-6e782495ce9f
29
-
30
28
  ## 快速开始
31
29
 
32
30
  ```bash
@@ -61,9 +59,18 @@ pi -e .
61
59
  ## 兼容性
62
60
 
63
61
  - Node.js `>=22.19.0`
64
- - 通过根目录 `package.json` 的 `pi.extensions` 和 `pi.themes` 加载
62
+ - Pi `^0.84.0`(通过根目录 `package.json` 的 `pi.extensions` 和 `pi.themes` 加载)
63
+ - 0.80.x 及更早版本请使用本包的旧版本(peerDependencies 已收紧为 `^0.84.0`)
65
64
  - `@tifan/pi-fixed-editor`、Pi 或 TUI 内部实现升级后若显示异常,先执行 `/reload`
66
65
 
66
+ ### Fullscreen 模式与日落功能
67
+
68
+ - 官方 `--tui-mode fullscreen`(及 `/settings` 运行时切换)复用官方布局:插件在 `tui.mode === "fullscreen"` 时自动让位,不安装任何渲染层(工具样式、紧凑模式、工具分组、固定编辑器均跳过),fullscreen 完全由官方 TUI 提供。数据类功能(context、session 引用、自动补全等)不受影响。
69
+ - 与官方 fullscreen 重叠的能力已日落(冻结开发、仅维持可用):固定编辑器布局、独立滚动 transcript 等不再二次实现。
70
+ - 已知边界:运行时从 regular 切换到 fullscreen 时,插件行级渲染 patch 随旧 TUI 实例自动失效,但组件级样式 patch 仍生效(不影响官方布局);如需完全隔离,建议以 fullscreen 模式启动会话。
71
+ - Pi 0.84+ 的 TUI 引用是惰性 Proxy(`createInteractiveTuiReference`),通过它捕获 `doRender`/`render`/`handleInput` 会解析到包装自身形成无限递归。插件检测到惰性 Proxy 后跳过行级渲染 patch;`@tifan/pi-fixed-editor` 的 compositor 因同样原因在 0.84+ 下停用(Fixed editor 交互功能暂时不可用,渲染由官方管线接管)。
72
+ - `compact-thinking.json` 配置文件已日落(不再读取、不再兼容),compact-thinking 的配置统一由 `claude-code-style.json` 管控。
73
+
67
74
  ## 推荐搭配
68
75
 
69
76
  | 扩展 | 用途 |
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { getSettingsListTheme, ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
3
+ import { isFullscreenUi, isLazyProxyTui } from "./fullscreen-detect.ts";
3
4
  import {
4
5
  installCompactStyle,
5
6
  type CompactStyleHooks,
@@ -227,7 +228,6 @@ function formatExcludeRenderers(names: readonly string[]): string {
227
228
  export function formatConfigStatus(source: Config = config): string {
228
229
  return [
229
230
  `mode=${source.mode}`,
230
- `fixedEditor=${source.fixedEditorFeatures ? "on" : "off"}`,
231
231
  `exclude=[${source.excludeRenderers.join(", ") || "none"}]`,
232
232
  `diffView=${source.diffViewMode}`,
233
233
  `diffIndicator=${source.diffIndicatorMode}`,
@@ -1515,6 +1515,8 @@ function renderScrollButton(width: number, theme: any): string[] {
1515
1515
  */
1516
1516
  function patchToolMouseInputCapture(tui: any): void {
1517
1517
  if (toolMouseInputPatchTui === tui) return;
1518
+ // 0.84+ 惰性 Proxy:捕获 handleInput 会解析到 wrapper 自身(无限递归),跳过。
1519
+ if (isLazyProxyTui(tui)) return;
1518
1520
 
1519
1521
  restoreToolMouseInputCapture();
1520
1522
  const originalHandle = tui?.handleInput;
@@ -1722,6 +1724,8 @@ function patchToolMouseMotionAfterRender(tui: any): void {
1722
1724
  ) {
1723
1725
  return;
1724
1726
  }
1727
+ // 0.84+ 惰性 Proxy:捕获 doRender 会解析到 wrapper 自身(无限递归),跳过。
1728
+ if (isLazyProxyTui(tui)) return;
1725
1729
  restoreToolMouseRenderPatch();
1726
1730
  const original = tui?.doRender;
1727
1731
  const terminal = tui?.terminal;
@@ -1947,8 +1951,16 @@ export function installToolMouseInteraction(
1947
1951
 
1948
1952
  toolMouseUi = ctx.ui;
1949
1953
  toolMouseFixedFeaturesEnabled = fixedEditorFeatures;
1954
+ // 0.84+ 的 tui 是惰性 Proxy:行级 patch(doRender/handleInput)无法安全捕获,
1955
+ // fixed-editor compositor 亦停用 —— 整个工具鼠标交互体系(hover/点击/滚动按钮)
1956
+ // 让位给官方,连 onTerminalInput 监听都不注册。
1957
+ let proxyTui = false;
1950
1958
  ctx.ui.setWidget(TOOL_MOUSE_WIDGET_KEY, (tui: any, theme: any) => {
1951
1959
  toolMouseTui = tui;
1960
+ if (isLazyProxyTui(tui)) {
1961
+ proxyTui = true;
1962
+ return { render: () => 0, invalidate() {} };
1963
+ }
1952
1964
  // Root input capture follows the live compositor; onRebuild re-applies it
1953
1965
  // once the fixed-editor compositor actually owns the terminal.
1954
1966
  if (useFixedEditorFeatures(tui)) patchToolMouseInputCapture(tui);
@@ -1967,6 +1979,10 @@ export function installToolMouseInteraction(
1967
1979
  scrollButtonWidget = widget;
1968
1980
  return widget;
1969
1981
  });
1982
+ if (proxyTui) {
1983
+ teardownToolMouseInteraction(fixedEditorFeatures);
1984
+ return;
1985
+ }
1970
1986
  toolMouseInputUnsubscribe = ctx.ui.onTerminalInput(handleToolMouseInput);
1971
1987
  }
1972
1988
 
@@ -2040,18 +2056,12 @@ function applyStyleMode(
2040
2056
 
2041
2057
  function modeSettingDescription(mode: CompactStyleMode): string {
2042
2058
  if (mode === "compact") {
2043
- return "Compact transcript summaries. Fixed editor and diff options below still apply independently.";
2059
+ return "Compact transcript summaries. Diff options below still apply independently.";
2044
2060
  }
2045
2061
  if (mode === "off") {
2046
- return "Pi native tool rendering. Fixed editor and diff options below still apply independently.";
2062
+ return "Pi native tool rendering. Diff options below still apply independently.";
2047
2063
  }
2048
- return "Claude Code style with rich edit/write diffs. Tune fixed editor and diff options below.";
2049
- }
2050
-
2051
- function fixedEditorSettingDescription(enabled: boolean): string {
2052
- return enabled
2053
- ? "Pinned editor via @tifan/pi-fixed-editor. Mouse capture, 5-row wheel, tool clicks, back-to-bottom button, message count, and Ctrl+End."
2054
- : "Native scrolling editor; mouse hover/click and wheel reporting off. Ctrl+End remains enabled.";
2064
+ return "Claude Code style with rich edit/write diffs. Tune diff options below.";
2055
2065
  }
2056
2066
 
2057
2067
  function excludeRenderersDescription(names: readonly string[]): string {
@@ -2162,7 +2172,6 @@ function renderSectionTabBar(
2162
2172
  async function showCcstylePanel(
2163
2173
  ctx: any,
2164
2174
  compactStyle: CompactStyleHooks,
2165
- fixedEditorController: FixedEditorController,
2166
2175
  toolGrouping?: ToolGroupingHooks,
2167
2176
  compactThinking?: CompactThinkingController,
2168
2177
  ): Promise<void> {
@@ -2179,13 +2188,6 @@ async function showCcstylePanel(
2179
2188
  currentValue: config.mode,
2180
2189
  values: ["on", "off", "compact"],
2181
2190
  };
2182
- const fixedEditorSetting = {
2183
- id: "fixedEditorFeatures",
2184
- label: "Fixed editor",
2185
- description: fixedEditorSettingDescription(config.fixedEditorFeatures),
2186
- currentValue: config.fixedEditorFeatures ? "on" : "off",
2187
- values: ["on", "off"],
2188
- };
2189
2191
  // Tracks whether the Exclude-tools submenu is open so Tab switches sections
2190
2192
  // only at the top level (mirrors Zentui settings: Tab = switch sections).
2191
2193
  let excludeSubmenuOpen = false;
@@ -2287,17 +2289,6 @@ async function showCcstylePanel(
2287
2289
  modeSetting.description = modeSettingDescription(value as CompactStyleMode);
2288
2290
  applyStyleMode(value as CompactStyleMode, ctx, compactStyle, toolGrouping);
2289
2291
  return;
2290
- case "fixedEditorFeatures":
2291
- config.fixedEditorFeatures = value === "on";
2292
- fixedEditorSetting.description = fixedEditorSettingDescription(
2293
- config.fixedEditorFeatures,
2294
- );
2295
- saveConfig();
2296
- fixedEditorController.setEnabled(config.fixedEditorFeatures);
2297
- installToolMouseInteraction(ctx);
2298
- refreshCurrentTranscript(compactStyle, ctx);
2299
- ctx.ui.notify(`Fixed editor: ${value}`, "info");
2300
- return;
2301
2292
  case "excludeRenderers":
2302
2293
  excludeSetting.currentValue = formatExcludeRenderers(config.excludeRenderers);
2303
2294
  excludeSetting.description = excludeRenderersDescription(config.excludeRenderers);
@@ -2367,11 +2358,6 @@ async function showCcstylePanel(
2367
2358
  label: "Style",
2368
2359
  items: [modeSetting, excludeSetting],
2369
2360
  },
2370
- {
2371
- id: "editor",
2372
- label: "Editor",
2373
- items: [fixedEditorSetting],
2374
- },
2375
2361
  {
2376
2362
  id: "diff",
2377
2363
  label: "Diff",
@@ -3189,6 +3175,8 @@ export default function (
3189
3175
  | undefined;
3190
3176
  const ensureTuiInstallation = (ctx: any) => {
3191
3177
  if (ctx?.mode !== "tui" || !ctx?.hasUI) return undefined;
3178
+ // fullscreen 复用官方布局,插件渲染层(工具样式/紧凑模式/分组)让位
3179
+ if (isFullscreenUi(ctx)) return undefined;
3192
3180
  if (installation) return installation;
3193
3181
  const globalToolRendering = installGlobalToolRendering(writeExecutionMetadata);
3194
3182
  const toolGrouping = installToolGrouping(() => config.mode === "on");
@@ -3215,23 +3203,25 @@ export default function (
3215
3203
  },
3216
3204
  handler: async (args, ctx) => {
3217
3205
  const arg = args.trim().toLowerCase();
3218
- if (!arg || arg === "panel") {
3219
- const hooks = ensureTuiInstallation(ctx);
3220
- if (hooks)
3221
- await showCcstylePanel(
3222
- ctx,
3223
- hooks.compactStyle,
3224
- fixedEditorController,
3225
- hooks.toolGrouping,
3226
- compactThinking,
3206
+ if (!arg || arg === "panel" || arg === "on" || arg === "off" || arg === "compact") {
3207
+ if (ctx?.mode !== "tui" || !ctx?.hasUI) {
3208
+ ctx.ui?.notify?.("/ccstyle requires TUI mode", "warning");
3209
+ return;
3210
+ }
3211
+ if (isFullscreenUi(ctx)) {
3212
+ ctx.ui.notify(
3213
+ "ccstyle 渲染已在 fullscreen 模式下让位给官方 TUI;请切换到 regular 模式后使用 /ccstyle",
3214
+ "warning",
3227
3215
  );
3228
- else ctx.ui?.notify?.("/ccstyle requires TUI mode", "warning");
3229
- return;
3230
- }
3231
- if (arg === "on" || arg === "off" || arg === "compact") {
3216
+ return;
3217
+ }
3232
3218
  const hooks = ensureTuiInstallation(ctx);
3233
- if (hooks) applyStyleMode(arg, ctx, hooks.compactStyle, hooks.toolGrouping);
3234
- else ctx.ui?.notify?.("/ccstyle requires TUI mode", "warning");
3219
+ if (!hooks) return;
3220
+ if (!arg || arg === "panel") {
3221
+ await showCcstylePanel(ctx, hooks.compactStyle, hooks.toolGrouping, compactThinking);
3222
+ } else {
3223
+ applyStyleMode(arg, ctx, hooks.compactStyle, hooks.toolGrouping);
3224
+ }
3235
3225
  return;
3236
3226
  }
3237
3227
  if (arg === "status") {
@@ -2,7 +2,7 @@ import * as piAi from "@earendil-works/pi-ai";
2
2
  import * as piCodingAgent from "@earendil-works/pi-coding-agent";
3
3
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
4
  import * as piTui from "@earendil-works/pi-tui";
5
- import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
+ import { readFileSync, rmSync } from "node:fs";
6
6
  import { createRequire } from "node:module";
7
7
  import { homedir } from "node:os";
8
8
  import { join } from "node:path";
@@ -188,20 +188,16 @@ export function installCompactThinking(
188
188
  host[COMPACT_THINKING_OWNER]?.stop(event, ctx);
189
189
  session = { event, ctx };
190
190
 
191
- // upstream lib/config.ts 在模块顶层读取 compact-thinking.json,缺失时
192
- // 会自建默认配置;预写我们的配置供其读取,加载完成后立即删除,
193
- // 不在 .pi/agent 下残留文件(文件原本存在时保持不动)。
191
+ // compact-thinking.json 已日落:不预写、不读取,配置统一由
192
+ // claude-code-style.json 管控(下方 Object.assign 覆盖库默认值)。
193
+ // 库首次加载会在 .pi/agent 下自建默认文件,加载完成后立即删除,不留残留。
194
194
  const agentDir = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
195
195
  const configPath = join(agentDir, "compact-thinking.json");
196
- const fileExisted = existsSync(configPath);
197
- if (!fileExisted) {
198
- writeFileSync(configPath, `${JSON.stringify(initialConfig, null, 2)}\n`, "utf8");
199
- }
200
196
  let loaded: ReturnType<typeof loadPatchedUpstream>;
201
197
  try {
202
198
  loaded = loadPatchedUpstream();
203
199
  } finally {
204
- if (!fileExisted) rmSync(configPath, { force: true });
200
+ rmSync(configPath, { force: true });
205
201
  }
206
202
  upstreamConfig = loaded.config;
207
203
  Object.assign(upstreamConfig, initialConfig);
@@ -2,6 +2,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import * as piCodingAgent from "@earendil-works/pi-coding-agent";
3
3
  import * as piTui from "@earendil-works/pi-tui";
4
4
  import { visibleWidth } from "@earendil-works/pi-tui";
5
+ import { isFullscreenUi, isLazyProxyTui } from "./fullscreen-detect.ts";
5
6
  import { createJiti } from "jiti";
6
7
 
7
8
  type LifecycleHandler = (event: any, ctx: any) => void;
@@ -196,8 +197,25 @@ export function installFixedEditorImePatch(compositorClass: {
196
197
  }
197
198
  }
198
199
 
200
+ const COMPOSITOR_PROXY_GUARD = Symbol.for("pi.ccstyle.compositor-proxy-guard");
201
+
199
202
  if (terminalSplit.TerminalSplitCompositor) {
200
203
  installFixedEditorImePatch(terminalSplit.TerminalSplitCompositor);
204
+ // 0.84+ 的 tui 是惰性 Proxy:compositor 构造时捕获的 doRender/render 是
205
+ // 每次重新解析的包装,install 后执行会解析到 compositor 自身(无限递归)。
206
+ // 检测到惰性 Proxy 时跳过安装,渲染管线完全交给官方。
207
+ installProxyGuard(terminalSplit.TerminalSplitCompositor);
208
+ }
209
+
210
+ function installProxyGuard(compositorClass: { prototype: Record<PropertyKey, any> }): void {
211
+ const prototype = compositorClass.prototype;
212
+ const originalInstall = prototype.install;
213
+ if (typeof originalInstall !== "function" || prototype[COMPOSITOR_PROXY_GUARD]) return;
214
+ prototype[COMPOSITOR_PROXY_GUARD] = true;
215
+ prototype.install = function (this: any, ...args: any[]) {
216
+ if (isLazyProxyTui(this.tui)) return;
217
+ return Reflect.apply(originalInstall, this, args);
218
+ };
201
219
  }
202
220
 
203
221
  export type FixedEditorController = {
@@ -320,6 +338,8 @@ export function installFixedEditor(
320
338
  }
321
339
  function activate() {
322
340
  if (!session || session.ctx?.mode !== "tui" || active) return;
341
+ // fullscreen 复用官方的 sticky editor,固定编辑器让位
342
+ if (isFullscreenUi(session.ctx)) return;
323
343
  const previous = (globalThis as any)[FIXED_EDITOR_OWNER] as FixedEditorOwner | undefined;
324
344
  if (previous?.owner !== owner) previous?.stop();
325
345
  const currentSession = session;
@@ -0,0 +1,38 @@
1
+ // fullscreen 模式探测:官方 fullscreen TUI(TuiAltScreen)由官方独占布局,
2
+ // 插件渲染层应让位。检测方式是瞬时 setWidget 拿 tui 实例检查 mode。
3
+ const PROBE_KEY = "cc-fullscreen-probe";
4
+
5
+ /**
6
+ * 官方 0.84+ 的 tui 引用是惰性 Proxy(createInteractiveTuiReference):
7
+ * 函数属性每次 get 都返回新包装,执行时才解析到当前实现。
8
+ * 通过它捕获 doRender/render/handleInput 会解析到自身形成无限递归,
9
+ * 因此检测到惰性 Proxy 时必须跳过所有"捕获后包装"类 patch。
10
+ */
11
+ export function isLazyProxyTui(tui: any): boolean {
12
+ if (!tui || typeof tui !== "object") return false;
13
+ const probe = tui.requestRender;
14
+ return typeof probe === "function" && probe !== tui.requestRender;
15
+ }
16
+
17
+ /**
18
+ * 探测当前会话的 TUI 是否运行在官方 fullscreen 模式。
19
+ * 无 setWidget 的环境(测试 mock)视为 regular。
20
+ */
21
+ export function isFullscreenUi(ctx: { ui?: any }): boolean {
22
+ if (typeof ctx?.ui?.setWidget !== "function") return false;
23
+ let fullscreen = false;
24
+ try {
25
+ ctx.ui.setWidget(PROBE_KEY, (tui: any) => {
26
+ fullscreen = tui?.mode === "fullscreen";
27
+ return { render: () => 0, invalidate() {} };
28
+ });
29
+ } catch {
30
+ // 探测失败按 regular 处理,避免误伤
31
+ }
32
+ try {
33
+ ctx.ui.setWidget(PROBE_KEY, undefined);
34
+ } catch {
35
+ // 忽略
36
+ }
37
+ return fullscreen;
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cc-extensions",
3
- "version": "0.8.42",
3
+ "version": "0.8.43",
4
4
  "license": "MIT",
5
5
  "description": "A Pi productivity suite with Claude Code-style UI, fixed editor, context inspection, and agent/session references",
6
6
  "type": "module",
@@ -55,9 +55,9 @@
55
55
  "@tifan/pi-fixed-editor"
56
56
  ],
57
57
  "peerDependencies": {
58
- "@earendil-works/pi-ai": "*",
59
- "@earendil-works/pi-coding-agent": "*",
60
- "@earendil-works/pi-tui": "*"
58
+ "@earendil-works/pi-ai": "^0.84.0",
59
+ "@earendil-works/pi-coding-agent": "^0.84.0",
60
+ "@earendil-works/pi-tui": "^0.84.0"
61
61
  },
62
62
  "engines": {
63
63
  "node": ">=22.19.0"