pi-one-ui 0.8.0 → 0.9.1

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/LICENSE +1 -1
  3. package/README.en.md +12 -10
  4. package/README.md +12 -10
  5. package/THIRD-PARTY-NOTICES.md +98 -0
  6. package/extensions/app/config/renderer.ts +1 -12
  7. package/extensions/app/config/shell.ts +5 -5
  8. package/extensions/app/runtime/tui-runtime.ts +6 -6
  9. package/extensions/app/settings/controller.ts +1 -1
  10. package/extensions/layouts/overlay/settings-panel.ts +9 -8
  11. package/extensions/layouts/overlay/settings-previews.ts +1 -1
  12. package/extensions/layouts/{context → transcript}/index.ts +8 -8
  13. package/extensions/layouts/{context → transcript}/renderer/compact-mode.ts +4 -4
  14. package/extensions/layouts/{context → transcript}/renderer/default-mode.ts +37 -7
  15. package/extensions/layouts/{context → transcript}/renderer/index.ts +8 -8
  16. package/extensions/layouts/{context → transcript}/renderer/mouse/hover.ts +0 -4
  17. package/extensions/layouts/{context → transcript}/renderer/mouse/interaction.ts +104 -93
  18. package/extensions/layouts/{context → transcript}/renderer/mouse/layout.ts +0 -4
  19. package/extensions/layouts/transcript/renderer/mouse/scroll.ts +35 -0
  20. package/extensions/layouts/{context → transcript}/renderer/tool/message-display.ts +1 -1
  21. package/extensions/layouts/{context → transcript}/renderer/tool/names.ts +19 -2
  22. package/extensions/layouts/{context/renderer/context-refresh.ts → transcript/renderer/transcript-refresh.ts} +6 -6
  23. package/extensions/layouts/{context → transcript}/thinking/compact-thinking.ts +4 -4
  24. package/extensions/tools/component-tree.ts +1 -1
  25. package/extensions/tools/patch-keys.ts +0 -3
  26. package/package.json +3 -2
  27. package/extensions/layouts/context/renderer/mouse/scroll.ts +0 -216
  28. /package/extensions/app/config/{context.ts → user-messages.ts} +0 -0
  29. /package/extensions/layouts/{context → transcript}/message/user-message-osc.ts +0 -0
  30. /package/extensions/layouts/{context → transcript}/message/user-message-styles.ts +0 -0
  31. /package/extensions/layouts/{context → transcript}/message/user-message.ts +0 -0
  32. /package/extensions/layouts/{context → transcript}/renderer/markdown-enhance.ts +0 -0
  33. /package/extensions/layouts/{context → transcript}/renderer/mouse/packets.ts +0 -0
  34. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/ATTRIBUTION.md +0 -0
  35. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/ansi-utils.ts +0 -0
  36. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-component.ts +0 -0
  37. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-edit-render.ts +0 -0
  38. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-header.ts +0 -0
  39. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-highlight.ts +0 -0
  40. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-inline.ts +0 -0
  41. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-layout.ts +0 -0
  42. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-limits.ts +0 -0
  43. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-palette.ts +0 -0
  44. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-parse.ts +0 -0
  45. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-presentation.ts +0 -0
  46. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-renderer.ts +0 -0
  47. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-text.ts +0 -0
  48. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-write-render.ts +0 -0
  49. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/index.ts +0 -0
  50. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/line-width-safety.ts +0 -0
  51. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/shiki-highlight.ts +0 -0
  52. /package/extensions/layouts/{context → transcript}/renderer/tool/diff/write-execution.ts +0 -0
  53. /package/extensions/layouts/{context → transcript}/renderer/tool/grouping.ts +0 -0
  54. /package/extensions/layouts/{context → transcript}/renderer/tool/result.ts +0 -0
  55. /package/extensions/layouts/{context → transcript}/renderer/tool/show-more-hint.ts +0 -0
  56. /package/extensions/layouts/{context → transcript}/renderer/tool/toggle-render-cache.ts +0 -0
  57. /package/extensions/layouts/{context → transcript}/summary/core.ts +0 -0
  58. /package/extensions/layouts/{context → transcript}/summary/index.ts +0 -0
@@ -11,22 +11,22 @@ import {
11
11
 
12
12
  // pi-coding-agent 类型声明中 AssistantMessageComponent 仅能以 value 形式使用,
13
13
  // updateContent/lastMessage 用结构化类型访问。
14
- type ContextComponentInternals = {
14
+ type TranscriptComponentInternals = {
15
15
  lastMessage?: AssistantMessage;
16
16
  updateContent(message: AssistantMessage): void;
17
17
  invalidate?(): void;
18
18
  };
19
19
 
20
20
  /**
21
- * Refreshes one constructed context component.
21
+ * Refreshes one constructed transcript component.
22
22
  *
23
23
  * Assistant components receive their latest message, while tool components
24
24
  * refresh their display so resumed content returns to the current shell.
25
25
  */
26
- export function refreshContextComponent(value: unknown): boolean {
26
+ export function refreshTranscriptComponent(value: unknown): boolean {
27
27
  if (!value || typeof value !== "object") return false;
28
28
  if (value instanceof AssistantMessageComponent) {
29
- const self = value as unknown as ContextComponentInternals;
29
+ const self = value as unknown as TranscriptComponentInternals;
30
30
  if (self.lastMessage) self.updateContent(self.lastMessage);
31
31
  else self.invalidate?.();
32
32
  return true;
@@ -65,13 +65,13 @@ function assertCompactModeOutermost(): void {
65
65
  * This covers reload, resume and compaction paths where Pi rebuilds components
66
66
  * before the renderer patches are installed.
67
67
  */
68
- export function refreshMountedContext(tui?: unknown): void {
68
+ export function refreshMountedTranscript(tui?: unknown): void {
69
69
  if (!tui || typeof (tui as any).getMountedRoots !== "function") return;
70
70
  // Fix patch order before refreshing so updateContent runs through the outer patch.
71
71
  assertCompactModeOutermost();
72
72
  walkComponentTree(tui, (value: any) => {
73
73
  try {
74
- refreshContextComponent(value);
74
+ refreshTranscriptComponent(value);
75
75
  } catch {
76
76
  // Isolate one component failure; a later tree rebuild can retry it.
77
77
  }
@@ -33,8 +33,8 @@ import {
33
33
  formatThoughtDuration,
34
34
  styleCompactThinkingText,
35
35
  } from "../renderer/compact-mode.ts";
36
- import { refreshMountedContext } from "../renderer/context-refresh.ts";
37
36
  import { isToolTuiFullscreen } from "../renderer/tool/show-more-hint.ts";
37
+ import { refreshMountedTranscript } from "../renderer/transcript-refresh.ts";
38
38
 
39
39
  // 保持导出兼容:渲染函数已并入 renderer/compact-mode.ts,这里 re-export。
40
40
  export {
@@ -1088,15 +1088,15 @@ function compactThinking(pi: ExtensionAPI) {
1088
1088
 
1089
1089
  // pi 在 reload/resume 时先于 session_start 用原始原型重建聊天组件
1090
1090
  // (rebuildChatFromMessages / renderCurrentSessionState)。由共享的
1091
- // refreshMountedContext 扫描挂载树重绘这些组件,恢复 compact 渲染、
1091
+ // refreshMountedTranscript 扫描挂载树重绘这些组件,恢复 compact 渲染、
1092
1092
  // 持久化时长与工具调用显示。
1093
- refreshMountedContext(activeTui);
1093
+ refreshMountedTranscript(activeTui);
1094
1094
  activeTui?.requestRender(true);
1095
1095
  });
1096
1096
 
1097
1097
  pi.on("session_tree", (_event, ctx) => {
1098
1098
  restoreDurationEntries(ctx.sessionManager.getBranch(), completedDurations);
1099
- refreshMountedContext(activeTui);
1099
+ refreshMountedTranscript(activeTui);
1100
1100
  activeTui?.requestRender(true);
1101
1101
  });
1102
1102
 
@@ -2,7 +2,7 @@
2
2
  * Shared recursive traversal for component trees.
3
3
  *
4
4
  * The implementation covers the children/getMountedRoots/seen traversal used by
5
- * context rendering, grouping and mouse packet handling.
5
+ * transcript rendering, grouping and mouse packet handling.
6
6
  *
7
7
  * The visitor runs once for every non-array object. Returning `false` stops
8
8
  * traversal into that node's children and mounted roots.
@@ -81,9 +81,6 @@ export const TOOL_GROUPING_GENERATION_KEY = Symbol.for(
81
81
  // ── 鼠标交互 ──
82
82
  export const TOOL_MOUSE_OWNER_KEY = Symbol.for("pi-one-ui.tool-mouse-owner");
83
83
  export const TOOL_MOUSE_TUI_SLOT = Symbol.for("pi-one-ui.tool-mouse-tui");
84
- export const SCROLL_BUTTON_STATE_SLOT = Symbol.for(
85
- "pi-one-ui.scroll-button-state",
86
- );
87
84
  export const FLUSH_DOCKED_BASH_PATCH = Symbol.for(
88
85
  "pi-one-ui.flush-docked-bash-patch",
89
86
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-one-ui",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "A single, customizable Pi UI package with layout-oriented Context and Shell rendering for Pi.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,7 +25,8 @@
25
25
  "README.md",
26
26
  "README.en.md",
27
27
  "CHANGELOG.md",
28
- "LICENSE"
28
+ "LICENSE",
29
+ "THIRD-PARTY-NOTICES.md"
29
30
  ],
30
31
  "scripts": {
31
32
  "format": "biome format --write extensions tests package.json tsconfig.json vitest.config.ts biome.json",
@@ -1,216 +0,0 @@
1
- import {
2
- getKeybindings,
3
- type Keybinding,
4
- matchesKey,
5
- truncateToWidth,
6
- visibleWidth,
7
- } from "@earendil-works/pi-tui";
8
- import { config } from "../../../../app/config/renderer.ts";
9
- import { isLazyProxyTui } from "../../../../tools/fullscreen-detect.ts";
10
- import {
11
- patchRegistry,
12
- SCROLL_BUTTON_STATE_SLOT,
13
- TOOL_MOUSE_TUI_SLOT,
14
- } from "../../../../tools/patch-keys.ts";
15
- import { parseSgrMousePackets } from "./packets.ts";
16
-
17
- const PAGE_UP_INPUT =
18
- /^\x1b\[5;9(?::[12])?~$|^\x1b\[57421;9(?::[12])?u$|^\x1b\[1;6A$/;
19
- const PAGE_DOWN_INPUT =
20
- /^\x1b\[6;9(?::[12])?~$|^\x1b\[57422;9(?::[12])?u$|^\x1b\[1;6B$/;
21
- const SCROLL_BOTTOM_SHORTCUT = "ctrl+end";
22
-
23
- /**
24
- * 当前安装的 tui 宿主。宿主放本模块(滚动按钮/调度依赖它),
25
- * 由 mouse-interaction 经 setToolMouseTui 维护;跨模块一律经绑定/setter 访问。
26
- *
27
- * 状态镜像到 globalThis(Symbol 槽):jiti 转译下经 re-export 链读取的
28
- * 模块级 let 绑定是初始值快照(死绑定,实测恒 null),函数调用才是活引用。
29
- * 跨模块读取一律用 getToolMouseTui(),避免拿到加载时的快照。
30
- */
31
- patchRegistry.ensure(TOOL_MOUSE_TUI_SLOT, () => null);
32
- export let toolMouseTui: any = null;
33
- export function getToolMouseTui(): any {
34
- return patchRegistry.get(TOOL_MOUSE_TUI_SLOT);
35
- }
36
- export function setToolMouseTui(tui: any): void {
37
- toolMouseTui = tui;
38
- patchRegistry.install(TOOL_MOUSE_TUI_SLOT, tui);
39
- }
40
-
41
- // 滚动按钮状态同 toolMouseTui:镜像到 globalThis(Symbol 槽),跨模块读取
42
- // 一律用 getter(jiti 转译下模块级 let 绑定是初始值快照)。
43
- type ScrollButtonState = { visible: boolean; hovered: boolean; widget: any };
44
- function scrollButtonState(): ScrollButtonState {
45
- return patchRegistry.ensure(SCROLL_BUTTON_STATE_SLOT, () => ({
46
- visible: false,
47
- hovered: false,
48
- widget: null,
49
- }));
50
- }
51
- export function getScrollButtonVisible(): boolean {
52
- return scrollButtonState().visible;
53
- }
54
- export function getScrollButtonHovered(): boolean {
55
- return scrollButtonState().hovered;
56
- }
57
- export function getScrollButtonWidget(): any {
58
- return scrollButtonState().widget;
59
- }
60
- export function setScrollButtonVisible(visible: boolean): void {
61
- scrollButtonState().visible = visible;
62
- }
63
-
64
- /** 返回是否发生变化(调用方据此决定是否需要重渲染)。 */
65
- export function setScrollButtonHovered(hovered: boolean): boolean {
66
- if (hovered === scrollButtonState().hovered) return false;
67
- scrollButtonState().hovered = hovered;
68
- return true;
69
- }
70
-
71
- export function setScrollButtonWidget(widget: any): void {
72
- scrollButtonState().widget = widget;
73
- }
74
-
75
- /** teardown 全量清零(visible/hovered/widget/sync 调度)。 */
76
- export function resetScrollButtonState(): void {
77
- scrollButtonState().visible = false;
78
- scrollButtonState().hovered = false;
79
- scrollButtonState().widget = null;
80
- scrollButtonSyncScheduled = false;
81
- }
82
-
83
- let scrollButtonSyncScheduled = false;
84
-
85
- // 交互开关只取决于配置模式:原实现按 isLazyProxyTui(toolMouseTui) 分两分支,
86
- // 两分支恒真(0.84+ 惰性 Proxy 下判定不再影响开关),折叠为单条件。
87
- export function toolMouseInteractionActive(): boolean {
88
- return config.mode !== "off";
89
- }
90
-
91
- /** 惰性 Proxy 官方 fullscreen(TuiAltScreen)判定。 */
92
- export function fullscreenLazyTui(tui: any): boolean {
93
- return isLazyProxyTui(tui) && tui.mode === "fullscreen";
94
- }
95
-
96
- /**
97
- * Reports whether fullscreen output is following the bottom of the context.
98
- */
99
- export function isFullscreenAtBottom(tui: any): boolean {
100
- const following =
101
- tui.isFollowingOutput ??
102
- tui.getPrimaryScrollView?.()?.isFollowingEnd ??
103
- true;
104
- return Boolean(following);
105
- }
106
-
107
- function formatShortcut(shortcut: string): string {
108
- return shortcut
109
- .split("+")
110
- .map((part) =>
111
- part.length <= 1
112
- ? part.toUpperCase()
113
- : `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`,
114
- )
115
- .join("+");
116
- }
117
-
118
- export function isScrollBottomInput(data: string): boolean {
119
- return matchesKey(data, SCROLL_BOTTOM_SHORTCUT);
120
- }
121
-
122
- function isScrollNavigationInput(data: string): boolean {
123
- if (
124
- matchesKey(data, "pageUp") ||
125
- matchesKey(data, "pageDown") ||
126
- PAGE_UP_INPUT.test(data) ||
127
- PAGE_DOWN_INPUT.test(data) ||
128
- // 官方 fullscreen viewport 的可滚动键(half-page/prompt/top/bottom)。
129
- [
130
- "tui.altScreen.pageUp",
131
- "tui.altScreen.pageDown",
132
- "tui.altScreen.halfPageUp",
133
- "tui.altScreen.halfPageDown",
134
- "tui.altScreen.previousPrompt",
135
- "tui.altScreen.nextPrompt",
136
- "tui.altScreen.top",
137
- "tui.altScreen.bottom",
138
- ].some((key) => getKeybindings().matches(data, key as Keybinding))
139
- ) {
140
- return true;
141
- }
142
- const packets = parseSgrMousePackets(data);
143
- return Boolean(
144
- packets?.some((packet) => {
145
- const baseButton = packet.code & ~(4 | 8 | 16 | 32);
146
- return packet.final === "M" && (baseButton === 64 || baseButton === 65);
147
- }),
148
- );
149
- }
150
-
151
- /**
152
- * Reports whether the context is at the bottom for the detected TUI mode.
153
- */
154
- function isAtContextBottom(tui: any): boolean {
155
- // 惰性 Proxy fullscreen:官方 viewport 以 isFollowingOutput 判定是否在底部。
156
- if (fullscreenLazyTui(tui)) return isFullscreenAtBottom(tui);
157
- return true;
158
- }
159
-
160
- export function hideScrollButton(tui: any): void {
161
- const changed = getScrollButtonVisible() || getScrollButtonHovered();
162
- setScrollButtonVisible(false);
163
- setScrollButtonHovered(false);
164
- if (changed) tui.requestRender?.();
165
- }
166
-
167
- export function scheduleScrollButtonSync(tui: any, data: string): void {
168
- if (
169
- !fullscreenLazyTui(tui) ||
170
- !toolMouseInteractionActive() ||
171
- !isScrollNavigationInput(data) ||
172
- scrollButtonSyncScheduled
173
- )
174
- return;
175
- scrollButtonSyncScheduled = true;
176
- const previousLines = tui.previousLines;
177
- const check = (attempt: number) => {
178
- scrollButtonSyncScheduled = false;
179
- if (toolMouseTui !== tui) return;
180
- // Pi renders on its own frame timer. Inspect the resulting viewport before
181
- // showing the button so empty or non-scrollable contexts never flash it.
182
- const rendered = tui.previousLines !== previousLines;
183
- // fullscreen 下 isFollowingOutput 是即时状态,无需等待官方帧渲染。
184
- if (!rendered && attempt < 4 && !fullscreenLazyTui(tui)) {
185
- scrollButtonSyncScheduled = true;
186
- const timer = setTimeout(() => check(attempt + 1), 16);
187
- if (typeof timer === "object" && timer !== null && "unref" in timer) {
188
- (timer as { unref: () => void }).unref();
189
- }
190
- return;
191
- }
192
- const nextVisible = !isAtContextBottom(tui);
193
- if (nextVisible !== getScrollButtonVisible()) {
194
- setScrollButtonVisible(nextVisible);
195
- tui.requestRender?.();
196
- }
197
- };
198
- process.nextTick(() => check(0));
199
- }
200
-
201
- export function updateScrollButtonFromInput(tui: any, data: string): void {
202
- if (!fullscreenLazyTui(tui) || !toolMouseInteractionActive()) return;
203
- if (matchesKey(data, "enter") || matchesKey(data, "return"))
204
- hideScrollButton(tui);
205
- }
206
-
207
- export function renderScrollButton(width: number, theme: any): string[] {
208
- if (!getScrollButtonVisible() || !fullscreenLazyTui(toolMouseTui)) return [];
209
- const shortcut = formatShortcut(SCROLL_BOTTOM_SHORTCUT);
210
- const label = theme.fg(
211
- getScrollButtonHovered() ? "text" : "accent",
212
- `[ ↓ Back to bottom · ${shortcut} ]`,
213
- );
214
- const leftPad = Math.max(0, Math.floor((width - visibleWidth(label)) / 2));
215
- return [`${" ".repeat(leftPad)}${truncateToWidth(label, width, "…")}`];
216
- }