@herbertgao/pi-extensions 2026.8.1 → 2026.8.2
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 +5 -2
- package/node_modules/@czottmann/pi-automode/README.md +16 -2
- package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +35 -0
- package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +18 -9
- package/node_modules/@czottmann/pi-automode/docs/defaults.md +6 -2
- package/node_modules/@czottmann/pi-automode/examples/automode.local.json +6 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +11 -7
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +100 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +32 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +78 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +16 -4
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +23 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +22 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +18 -2
- package/node_modules/@czottmann/pi-automode/package.json +4 -4
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail-frontmatter.cjs +23 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs +99 -0
- package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +30 -0
- package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +16 -0
- package/node_modules/@dietrichgebert/ponytail/AGENTS.md +32 -0
- package/node_modules/@dietrichgebert/ponytail/LICENSE +21 -0
- package/node_modules/@dietrichgebert/ponytail/README.es.md +298 -0
- package/node_modules/@dietrichgebert/ponytail/README.ko.md +315 -0
- package/node_modules/@dietrichgebert/ponytail/README.md +351 -0
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-3model.svg +21 -0
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-agentic.svg +62 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.svg +115 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt-dark.svg +27 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt.svg +27 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/social-preview.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-es.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-ko.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +44 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/copilot-hooks.json +21 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +96 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-config.js +169 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +98 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +130 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +85 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.ps1 +24 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.sh +18 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-subagent.js +77 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/qoder-hooks.json +26 -0
- package/node_modules/@dietrichgebert/ponytail/package.json +47 -0
- package/node_modules/@dietrichgebert/ponytail/pi-extension/index.js +211 -0
- package/node_modules/@dietrichgebert/ponytail/pi-extension/package.json +8 -0
- package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +60 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +120 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +41 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +44 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +50 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +71 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +57 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -3
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +10 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary.ts +6 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -83
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/markdown-enhance.ts +119 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +73 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-hover.ts +130 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-interaction.ts +65 -542
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-layout.ts +160 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-packets.ts +143 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-scroll.ts +184 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-diff/diff-renderer.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-grouping.ts +9 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +91 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/agent-summary.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-dark.json +1 -0
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-light.json +2 -2
- package/node_modules/@herbertgao/pi-stash/README.md +9 -9
- package/node_modules/@herbertgao/pi-stash/package.json +1 -1
- package/node_modules/@herbertgao/pi-stash/src/index.ts +2 -1
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +14 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +6 -2
- package/node_modules/@herbertgao/pi-subagents/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +85 -11
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +28 -4
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +12 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +2 -0
- package/node_modules/@narumitw/pi-btw/LICENSE +21 -0
- package/node_modules/@narumitw/pi-btw/README.md +210 -0
- package/node_modules/@narumitw/pi-btw/package.json +53 -0
- package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +632 -0
- package/node_modules/@narumitw/pi-btw/src/btw.ts +996 -0
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +339 -0
- package/node_modules/@narumitw/pi-btw/src/index.ts +1 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +296 -0
- package/node_modules/@narumitw/pi-btw/src/settings.ts +237 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +238 -0
- package/node_modules/@narumitw/pi-btw/src/text.ts +10 -0
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +620 -0
- package/node_modules/@pi-plugins/fast-mode/LICENSE +21 -0
- package/node_modules/@pi-plugins/fast-mode/README.md +92 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts +6 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +2026 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -0
- package/node_modules/@pi-plugins/fast-mode/package.json +73 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +59 -0
- package/node_modules/pi-mcp-adapter/README.md +62 -13
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +377 -0
- package/node_modules/pi-mcp-adapter/commands.ts +9 -7
- package/node_modules/pi-mcp-adapter/config.ts +110 -49
- package/node_modules/pi-mcp-adapter/direct-tools.ts +34 -19
- package/node_modules/pi-mcp-adapter/elicitation-handler.ts +37 -36
- package/node_modules/pi-mcp-adapter/errors.ts +21 -17
- package/node_modules/pi-mcp-adapter/host-html-template.ts +13 -7
- package/node_modules/pi-mcp-adapter/index.ts +37 -14
- package/node_modules/pi-mcp-adapter/init.ts +9 -7
- package/node_modules/pi-mcp-adapter/json-schema-validator.ts +2 -2
- package/node_modules/pi-mcp-adapter/lifecycle.ts +8 -8
- package/node_modules/pi-mcp-adapter/logger.ts +1 -1
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +36 -22
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +120 -2
- package/node_modules/pi-mcp-adapter/mcp-code.ts +13 -11
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +19 -13
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +15 -11
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +34 -15
- package/node_modules/pi-mcp-adapter/mcp-probe.ts +131 -34
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +2 -2
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +7 -4
- package/node_modules/pi-mcp-adapter/mcp-trace.ts +59 -64
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +34 -26
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +6 -4
- package/node_modules/pi-mcp-adapter/oauth-handler.ts +1 -1
- package/node_modules/pi-mcp-adapter/onboarding-state.ts +19 -11
- package/node_modules/pi-mcp-adapter/package.json +13 -8
- package/node_modules/pi-mcp-adapter/panel-keys.ts +20 -4
- package/node_modules/pi-mcp-adapter/prompts.ts +2 -2
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +48 -11
- package/node_modules/pi-mcp-adapter/sampling-handler.ts +22 -15
- package/node_modules/pi-mcp-adapter/search-ranking.ts +2 -1
- package/node_modules/pi-mcp-adapter/server-manager.ts +165 -113
- package/node_modules/pi-mcp-adapter/session-recovery.ts +11 -12
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +3 -3
- package/node_modules/pi-mcp-adapter/state.ts +3 -1
- package/node_modules/pi-mcp-adapter/tool-approval.ts +67 -4
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -5
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +14 -2
- package/node_modules/pi-mcp-adapter/ts-shape.ts +9 -2
- package/node_modules/pi-mcp-adapter/types.ts +109 -22
- package/node_modules/pi-mcp-adapter/ui-app-bridge-helpers.ts +40 -0
- package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +28 -19
- package/node_modules/pi-mcp-adapter/ui-server.ts +90 -58
- package/node_modules/pi-mcp-adapter/ui-session.ts +16 -14
- package/node_modules/pi-mcp-adapter/unix-socket-transport.ts +4 -4
- package/node_modules/pi-mcp-adapter/utils.ts +23 -9
- package/package.json +22 -7
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getKeybindings, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
1
|
import { hasActiveTextPreview, showTextPreview } from "../feature/context.ts";
|
|
3
2
|
import { ToolGroupComponent } from "./tool-grouping.ts";
|
|
4
3
|
import { isCompactAssistantComponent, setHoveredCompactAssistant } from "./compact-mode.ts";
|
|
@@ -8,19 +7,57 @@ import { setToolTuiFullscreen } from "./show-more-hint.ts";
|
|
|
8
7
|
import {
|
|
9
8
|
type ExpandedToolIoView,
|
|
10
9
|
getActiveIoViewFrame,
|
|
11
|
-
invalidateIoView,
|
|
12
10
|
isExpandedToolIoView,
|
|
13
11
|
type IoViewFrameState,
|
|
14
12
|
setActiveIoViewFrame,
|
|
15
13
|
type ToolIoSection,
|
|
16
14
|
} from "./tool-result.ts";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
import {
|
|
16
|
+
collectToolComponents,
|
|
17
|
+
extractToolFramePlacements,
|
|
18
|
+
isSgrLeftPress,
|
|
19
|
+
isToolExecutionComponent,
|
|
20
|
+
parseSgrMousePackets,
|
|
21
|
+
stripTerminalSequences,
|
|
22
|
+
stripTerminalSequencesPreservingLayout,
|
|
23
|
+
toolFrameMarker,
|
|
24
|
+
type FrameToolPlacement,
|
|
25
|
+
type SgrMousePacket,
|
|
26
|
+
} from "./mouse-packets.ts";
|
|
27
|
+
import {
|
|
28
|
+
collectFullscreenToolCards,
|
|
29
|
+
componentAtLocalRow,
|
|
30
|
+
collapsedHintHitbox,
|
|
31
|
+
fullscreenContentWidth,
|
|
32
|
+
fullscreenLeafAt,
|
|
33
|
+
isScrollbarColumnAt,
|
|
34
|
+
} from "./mouse-layout.ts";
|
|
35
|
+
import {
|
|
36
|
+
fullscreenLazyTui,
|
|
37
|
+
hideScrollButton,
|
|
38
|
+
isScrollBottomInput,
|
|
39
|
+
renderScrollButton,
|
|
40
|
+
resetScrollButtonState,
|
|
41
|
+
scheduleScrollButtonSync,
|
|
42
|
+
setScrollButtonHovered,
|
|
43
|
+
setScrollButtonVisible,
|
|
44
|
+
setScrollButtonWidget,
|
|
45
|
+
setToolMouseTui,
|
|
46
|
+
scrollButtonVisible,
|
|
47
|
+
scrollButtonWidget,
|
|
48
|
+
toolMouseInteractionActive,
|
|
49
|
+
toolMouseTui,
|
|
50
|
+
updateScrollButtonFromInput,
|
|
51
|
+
} from "./mouse-scroll.ts";
|
|
52
|
+
import {
|
|
53
|
+
applyFullscreenHover,
|
|
54
|
+
cachedFullscreenComponentAtRow,
|
|
55
|
+
sharedToolHoverState,
|
|
56
|
+
setHoveredToolCallId,
|
|
57
|
+
setHoveredToolGroup,
|
|
58
|
+
setHoveredToolIo,
|
|
59
|
+
type FullscreenHoverTarget,
|
|
60
|
+
} from "./mouse-hover.ts";
|
|
24
61
|
|
|
25
62
|
type FrameToolRender = {
|
|
26
63
|
component: any;
|
|
@@ -28,17 +65,6 @@ type FrameToolRender = {
|
|
|
28
65
|
contentBoxLines: number;
|
|
29
66
|
};
|
|
30
67
|
|
|
31
|
-
/** Final painted placement of one outermost tool/group row after parent layout. */
|
|
32
|
-
type FrameToolPlacement = {
|
|
33
|
-
component: any;
|
|
34
|
-
componentRow: number;
|
|
35
|
-
lineIndex: number;
|
|
36
|
-
/** Marker-stripped final line text as painted after parent layout. */
|
|
37
|
-
finalLine: string;
|
|
38
|
-
view?: ExpandedToolIoView;
|
|
39
|
-
section?: ToolIoSection;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
68
|
type InteractionRegion = {
|
|
43
69
|
kind: "collapsed-hint" | "expanded-card" | "show-more" | "scroll-bottom";
|
|
44
70
|
row: number;
|
|
@@ -51,23 +77,14 @@ type InteractionRegion = {
|
|
|
51
77
|
|
|
52
78
|
type InteractionFrame = { regions: InteractionRegion[] };
|
|
53
79
|
|
|
54
|
-
/** Zero-width APC row marker (like pi CURSOR_MARKER); stripped before terminal output. */
|
|
55
|
-
const TOOL_FRAME_MARKER_RE = /_cc:t(\d+):(\d+)/g;
|
|
56
|
-
const TOOL_VIEW_MARKER_RE = /_cc:v(\d+):([io])/g;
|
|
57
|
-
const toolFrameMarker = (id: number, row: number) => `_cc:t${id}:${row}`;
|
|
58
|
-
|
|
59
80
|
const TOOL_MOUSE_WIDGET_KEY = "ccstyle-tool-mouse";
|
|
60
81
|
const TOOL_MOUSE_MOTION_ENABLE = "\x1b[?1003h\x1b[?1006h";
|
|
61
82
|
const TOOL_MOUSE_MOTION_DISABLE = "\x1b[?1003l";
|
|
62
83
|
const FULLSCREEN_MOTION_ENABLED = Symbol("ccstyle.fullscreen-motion-enabled");
|
|
63
|
-
const TOOL_HOVER_STATE_KEY = Symbol.for("pi.ccstyle.tool-hover-state");
|
|
64
84
|
const TOOL_MOUSE_OWNER_KEY = Symbol.for("pi.ccstyle.tool-mouse-owner");
|
|
65
85
|
const DEFAULT_TOOL_MOUSE_OWNER = {};
|
|
66
86
|
export const TOOL_MOUSE_DISABLE = "\x1b[?1006l\x1b[?1003l\x1b[?1000l";
|
|
67
|
-
|
|
68
|
-
const ZENTUI_PAGE_DOWN_INPUT = /^\x1b\[6;9(?::[12])?~$|^\x1b\[57422;9(?::[12])?u$|^\x1b\[1;6B$/;
|
|
69
|
-
const SCROLL_BOTTOM_SHORTCUT = "ctrl+end";
|
|
70
|
-
export let toolMouseTui: any = null;
|
|
87
|
+
|
|
71
88
|
let toolMouseUi: any = null;
|
|
72
89
|
let toolMouseInputUnsubscribe: (() => void) | null = null;
|
|
73
90
|
let toolMouseRenderPatchTui: any = null;
|
|
@@ -78,268 +95,12 @@ let toolMouseRawWrite: ((data: string) => unknown) | null = null;
|
|
|
78
95
|
let toolMouseInstallationOwner: object | null = null;
|
|
79
96
|
let fullscreenMotionTerminal: any = null;
|
|
80
97
|
let ownsFullscreenMotion = false;
|
|
81
|
-
let scrollButtonVisible = false;
|
|
82
|
-
let scrollButtonHovered = false;
|
|
83
|
-
let scrollButtonWidget: any = null;
|
|
84
|
-
let scrollButtonSyncScheduled = false;
|
|
85
98
|
let sessionRenderTimer: ReturnType<typeof setTimeout> | null = null;
|
|
86
|
-
type SharedToolHoverState = { toolCallId: string | null };
|
|
87
|
-
function sharedToolHoverState(): SharedToolHoverState {
|
|
88
|
-
const host = globalThis as any;
|
|
89
|
-
return (host[TOOL_HOVER_STATE_KEY] ??= { toolCallId: null });
|
|
90
|
-
}
|
|
91
|
-
export let hoveredToolCallId: string | null = sharedToolHoverState().toolCallId;
|
|
92
|
-
function setHoveredToolCallId(toolCallId: string | null): void {
|
|
93
|
-
hoveredToolCallId = toolCallId;
|
|
94
|
-
sharedToolHoverState().toolCallId = toolCallId;
|
|
95
|
-
}
|
|
96
|
-
export function isToolCallHovered(toolCallId: string | null | undefined): boolean {
|
|
97
|
-
return Boolean(toolCallId && sharedToolHoverState().toolCallId === toolCallId);
|
|
98
|
-
}
|
|
99
|
-
let hoveredToolGroup: ToolGroupComponent | null = null;
|
|
100
99
|
let latestInteractionFrame: InteractionFrame = { regions: [] };
|
|
101
100
|
|
|
102
|
-
function parseSgrMousePackets(data: string): SgrMousePacket[] | null {
|
|
103
|
-
const pattern = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
104
|
-
const packets: SgrMousePacket[] = [];
|
|
105
|
-
let offset = 0;
|
|
106
|
-
|
|
107
|
-
for (const match of data.matchAll(pattern)) {
|
|
108
|
-
if (match.index !== offset) return null;
|
|
109
|
-
offset = match.index + match[0].length;
|
|
110
|
-
packets.push({
|
|
111
|
-
code: Number(match[1]),
|
|
112
|
-
col: Number(match[2]),
|
|
113
|
-
row: Number(match[3]),
|
|
114
|
-
final: match[4] as "M" | "m",
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return packets.length > 0 && offset === data.length ? packets : null;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function isSgrLeftPress(packet: SgrMousePacket): boolean {
|
|
122
|
-
const baseButton = packet.code & ~(4 | 8 | 16 | 32);
|
|
123
|
-
return packet.final === "M" && baseButton === 0 && (packet.code & 32) === 0;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function stripTerminalSequencesPreservingLayout(value: string): string {
|
|
127
|
-
return value
|
|
128
|
-
.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
|
|
129
|
-
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function stripTerminalSequences(value: string): string {
|
|
133
|
-
return stripTerminalSequencesPreservingLayout(value).replace(/\s+/g, " ").trim();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function isToolExecutionComponent(value: any): boolean {
|
|
137
|
-
return Boolean(
|
|
138
|
-
value &&
|
|
139
|
-
typeof value === "object" &&
|
|
140
|
-
typeof value.toolCallId === "string" &&
|
|
141
|
-
typeof value.setExpanded === "function" &&
|
|
142
|
-
typeof value.render === "function",
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function collectToolComponents(component: any, tools: any[], seen = new Set<any>()): void {
|
|
147
|
-
if (!component || typeof component !== "object" || seen.has(component)) return;
|
|
148
|
-
seen.add(component);
|
|
149
|
-
if (isToolExecutionComponent(component)) {
|
|
150
|
-
tools.push(component);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (Array.isArray(component.children)) {
|
|
154
|
-
for (const child of component.children) collectToolComponents(child, tools, seen);
|
|
155
|
-
}
|
|
156
|
-
try {
|
|
157
|
-
const mounted = component.getMountedRoots?.();
|
|
158
|
-
if (Array.isArray(mounted)) {
|
|
159
|
-
for (const root of mounted) collectToolComponents(root, tools, seen);
|
|
160
|
-
}
|
|
161
|
-
} catch {
|
|
162
|
-
// renderer 切换中的惰性 Proxy 可能暂时没有 mounted roots。
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function stripToolFrameMarkers(line: string): string {
|
|
167
|
-
return line.replace(TOOL_FRAME_MARKER_RE, "").replace(TOOL_VIEW_MARKER_RE, "");
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function extractToolFramePlacements(
|
|
171
|
-
lines: string[],
|
|
172
|
-
idToComponent: Map<number, any>,
|
|
173
|
-
idToView: Map<number, ExpandedToolIoView>,
|
|
174
|
-
): { lines: string[]; placements: FrameToolPlacement[] } {
|
|
175
|
-
const placements: FrameToolPlacement[] = [];
|
|
176
|
-
const cleaned = lines.map((line, lineIndex) => {
|
|
177
|
-
const toolMatches = [...line.matchAll(TOOL_FRAME_MARKER_RE)];
|
|
178
|
-
const viewMatches = [...line.matchAll(TOOL_VIEW_MARKER_RE)];
|
|
179
|
-
const finalLine = stripToolFrameMarkers(line);
|
|
180
|
-
let view: ExpandedToolIoView | undefined;
|
|
181
|
-
let section: ToolIoSection | undefined;
|
|
182
|
-
for (const match of viewMatches) {
|
|
183
|
-
const candidate = idToView.get(Number(match[1]));
|
|
184
|
-
if (!candidate) continue;
|
|
185
|
-
view = candidate;
|
|
186
|
-
section = match[2] === "i" ? "input" : "output";
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
for (const match of toolMatches) {
|
|
190
|
-
const component = idToComponent.get(Number(match[1]));
|
|
191
|
-
if (!component) continue;
|
|
192
|
-
placements.push({
|
|
193
|
-
component,
|
|
194
|
-
componentRow: Number(match[2]),
|
|
195
|
-
lineIndex,
|
|
196
|
-
finalLine,
|
|
197
|
-
view,
|
|
198
|
-
section,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
return finalLine;
|
|
202
|
-
});
|
|
203
|
-
return { lines: cleaned, placements };
|
|
204
|
-
}
|
|
205
|
-
|
|
206
101
|
/** Summary markers used by Pi and ccstyle; unlike the trailing hint, these survive truncation. */
|
|
207
102
|
const COLLAPSED_TOOL_SUMMARY = /^\s*(?:↳|└|⎿|●|✓|✗|…)/;
|
|
208
103
|
|
|
209
|
-
function toolMouseInteractionActive(): boolean {
|
|
210
|
-
if (config.mode === "off") return false;
|
|
211
|
-
if (isLazyProxyTui(toolMouseTui)) return true;
|
|
212
|
-
return true;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function formatShortcut(shortcut: string): string {
|
|
216
|
-
return shortcut
|
|
217
|
-
.split("+")
|
|
218
|
-
.map((part) =>
|
|
219
|
-
part.length <= 1 ? part.toUpperCase() : `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`,
|
|
220
|
-
)
|
|
221
|
-
.join("+");
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function isScrollBottomInput(data: string): boolean {
|
|
225
|
-
return matchesKey(data, SCROLL_BOTTOM_SHORTCUT);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function wheelDirection(data: string): "up" | "down" | null {
|
|
229
|
-
const packets = parseSgrMousePackets(data);
|
|
230
|
-
for (const packet of packets ?? []) {
|
|
231
|
-
if (packet.final !== "M") continue;
|
|
232
|
-
const baseButton = packet.code & ~(4 | 8 | 16 | 32);
|
|
233
|
-
if (baseButton === 64) return "up";
|
|
234
|
-
if (baseButton === 65) return "down";
|
|
235
|
-
}
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function isScrollNavigationInput(data: string): boolean {
|
|
240
|
-
if (
|
|
241
|
-
matchesKey(data, "pageUp") ||
|
|
242
|
-
matchesKey(data, "pageDown") ||
|
|
243
|
-
ZENTUI_PAGE_UP_INPUT.test(data) ||
|
|
244
|
-
ZENTUI_PAGE_DOWN_INPUT.test(data) ||
|
|
245
|
-
// 官方 fullscreen viewport 的可滚动键(half-page/prompt/top/bottom)。
|
|
246
|
-
[
|
|
247
|
-
"tui.altScreen.pageUp",
|
|
248
|
-
"tui.altScreen.pageDown",
|
|
249
|
-
"tui.altScreen.halfPageUp",
|
|
250
|
-
"tui.altScreen.halfPageDown",
|
|
251
|
-
"tui.altScreen.previousPrompt",
|
|
252
|
-
"tui.altScreen.nextPrompt",
|
|
253
|
-
"tui.altScreen.top",
|
|
254
|
-
"tui.altScreen.bottom",
|
|
255
|
-
].some((key) => getKeybindings().matches(data, key))
|
|
256
|
-
) {
|
|
257
|
-
return true;
|
|
258
|
-
}
|
|
259
|
-
const packets = parseSgrMousePackets(data);
|
|
260
|
-
return Boolean(
|
|
261
|
-
packets?.some((packet) => {
|
|
262
|
-
const baseButton = packet.code & ~(4 | 8 | 16 | 32);
|
|
263
|
-
return packet.final === "M" && (baseButton === 64 || baseButton === 65);
|
|
264
|
-
}),
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function isAtTranscriptBottom(tui: any): boolean {
|
|
269
|
-
// 惰性 Proxy fullscreen:官方 viewport 以 isFollowingOutput 判定是否在底部。
|
|
270
|
-
if (fullscreenLazyTui(tui)) return isFullscreenAtBottom(tui);
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function hideScrollButton(tui: any): void {
|
|
275
|
-
const changed = scrollButtonVisible || scrollButtonHovered;
|
|
276
|
-
scrollButtonVisible = false;
|
|
277
|
-
scrollButtonHovered = false;
|
|
278
|
-
if (changed) tui.requestRender?.();
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function scheduleScrollButtonSync(tui: any, data: string): void {
|
|
282
|
-
if (
|
|
283
|
-
!fullscreenLazyTui(tui) ||
|
|
284
|
-
!toolMouseInteractionActive() ||
|
|
285
|
-
!isScrollNavigationInput(data) ||
|
|
286
|
-
scrollButtonSyncScheduled
|
|
287
|
-
)
|
|
288
|
-
return;
|
|
289
|
-
scrollButtonSyncScheduled = true;
|
|
290
|
-
const previousLines = tui.previousLines;
|
|
291
|
-
const check = (attempt: number) => {
|
|
292
|
-
scrollButtonSyncScheduled = false;
|
|
293
|
-
if (toolMouseTui !== tui) return;
|
|
294
|
-
// Pi renders on its own frame timer. Inspect the resulting viewport before
|
|
295
|
-
// showing the button so empty or non-scrollable transcripts never flash it.
|
|
296
|
-
const rendered = tui.previousLines !== previousLines;
|
|
297
|
-
// fullscreen 下 isFollowingOutput 是即时状态,无需等待官方帧渲染。
|
|
298
|
-
if (!rendered && attempt < 4 && !fullscreenLazyTui(tui)) {
|
|
299
|
-
scrollButtonSyncScheduled = true;
|
|
300
|
-
const timer = setTimeout(() => check(attempt + 1), 16);
|
|
301
|
-
if (typeof timer === "object" && timer !== null && "unref" in timer) {
|
|
302
|
-
(timer as { unref: () => void }).unref();
|
|
303
|
-
}
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
const nextVisible = !isAtTranscriptBottom(tui);
|
|
307
|
-
if (nextVisible !== scrollButtonVisible) {
|
|
308
|
-
scrollButtonVisible = nextVisible;
|
|
309
|
-
tui.requestRender?.();
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
process.nextTick(() => check(0));
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function updateScrollButtonFromInput(tui: any, data: string): void {
|
|
316
|
-
if (!fullscreenLazyTui(tui) || !toolMouseInteractionActive()) return;
|
|
317
|
-
if (matchesKey(data, "enter") || matchesKey(data, "return")) hideScrollButton(tui);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function renderComponentTree(component: any, width: number): string[] {
|
|
321
|
-
if (!component || typeof component !== "object") return [];
|
|
322
|
-
try {
|
|
323
|
-
const lines = component.render?.(width);
|
|
324
|
-
if (Array.isArray(lines) && lines.length > 0) return lines;
|
|
325
|
-
} catch {
|
|
326
|
-
// Fall through to children for hidden container renderers.
|
|
327
|
-
}
|
|
328
|
-
if (!Array.isArray(component.children)) return [];
|
|
329
|
-
return component.children.flatMap((child: any) => renderComponentTree(child, width));
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
let hoveredToolIoView: ExpandedToolIoView | null = null;
|
|
333
|
-
let hoveredToolIoSection: ToolIoSection | null = null;
|
|
334
|
-
|
|
335
|
-
function collapsedHintHitbox(line: string): { startCol: number; endCol: number } | null {
|
|
336
|
-
const plain = stripTerminalSequencesPreservingLayout(line);
|
|
337
|
-
const match = /(\([^()\n]* \/ click\)|click to show more|to show more)(?=\)?\s*$)/.exec(plain);
|
|
338
|
-
if (!match?.[1]) return null;
|
|
339
|
-
const startCol = visibleWidth(plain.slice(0, match.index)) + 1;
|
|
340
|
-
return { startCol, endCol: startCol + visibleWidth(match[1]) - 1 };
|
|
341
|
-
}
|
|
342
|
-
|
|
343
104
|
function interactionRegionAt(packet: SgrMousePacket): InteractionRegion | null {
|
|
344
105
|
const matches = latestInteractionFrame.regions.filter(
|
|
345
106
|
(region) =>
|
|
@@ -369,41 +130,11 @@ function tryOpenToolIoShowMore(region: InteractionRegion): boolean {
|
|
|
369
130
|
return true;
|
|
370
131
|
}
|
|
371
132
|
|
|
372
|
-
export function setHoveredToolIo(
|
|
373
|
-
view: ExpandedToolIoView | null,
|
|
374
|
-
section: ToolIoSection | null,
|
|
375
|
-
): boolean {
|
|
376
|
-
// resultRendererComponent 可能是 Text/第三方 renderer;reload 后也可能残留旧实例。
|
|
377
|
-
const nextView = isExpandedToolIoView(view) ? view : null;
|
|
378
|
-
const nextSection = nextView ? section : null;
|
|
379
|
-
if (nextView === hoveredToolIoView && nextSection === hoveredToolIoSection) return false;
|
|
380
|
-
if (isExpandedToolIoView(hoveredToolIoView)) {
|
|
381
|
-
hoveredToolIoView.setHoveredSection(null);
|
|
382
|
-
invalidateIoView(hoveredToolIoView);
|
|
383
|
-
}
|
|
384
|
-
hoveredToolIoView = nextView;
|
|
385
|
-
hoveredToolIoSection = nextSection;
|
|
386
|
-
if (nextView) {
|
|
387
|
-
nextView.setHoveredSection(nextSection);
|
|
388
|
-
invalidateIoView(nextView);
|
|
389
|
-
}
|
|
390
|
-
return true;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
export function setHoveredToolGroup(group: ToolGroupComponent | null): boolean {
|
|
394
|
-
if (group === hoveredToolGroup) return false;
|
|
395
|
-
hoveredToolGroup?.setHintHovered(false);
|
|
396
|
-
hoveredToolGroup = group;
|
|
397
|
-
group?.setHintHovered(true);
|
|
398
|
-
return true;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
133
|
function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
|
|
402
134
|
if ((packet.code & 32) === 0 || packet.final !== "M") return;
|
|
403
135
|
const region = interactionRegionAt(packet);
|
|
404
136
|
const nextScrollButtonHovered = region?.kind === "scroll-bottom";
|
|
405
|
-
const scrollButtonChanged = nextScrollButtonHovered
|
|
406
|
-
scrollButtonHovered = nextScrollButtonHovered;
|
|
137
|
+
const scrollButtonChanged = setScrollButtonHovered(nextScrollButtonHovered);
|
|
407
138
|
const component = region?.component;
|
|
408
139
|
const nextToolCallId = region?.kind === "collapsed-hint" ? (component?.toolCallId ?? null) : null;
|
|
409
140
|
const nextGroup = component instanceof ToolGroupComponent ? component : null;
|
|
@@ -446,22 +177,6 @@ function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
446
177
|
return true;
|
|
447
178
|
}
|
|
448
179
|
|
|
449
|
-
function renderScrollButton(width: number, theme: any): string[] {
|
|
450
|
-
if (!scrollButtonVisible || !fullscreenLazyTui(toolMouseTui)) return [];
|
|
451
|
-
const shortcut = formatShortcut(SCROLL_BOTTOM_SHORTCUT);
|
|
452
|
-
const label = theme.fg(
|
|
453
|
-
scrollButtonHovered ? "text" : "accent",
|
|
454
|
-
`[ ↓ Back to bottom · ${shortcut} ]`,
|
|
455
|
-
);
|
|
456
|
-
const leftPad = Math.max(0, Math.floor((width - visibleWidth(label)) / 2));
|
|
457
|
-
return [`${" ".repeat(leftPad)}${truncateToWidth(label, width, "…")}`];
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/** 惰性 Proxy 官方 fullscreen(TuiAltScreen)判定。 */
|
|
461
|
-
function fullscreenLazyTui(tui: any): boolean {
|
|
462
|
-
return isLazyProxyTui(tui) && tui.mode === "fullscreen";
|
|
463
|
-
}
|
|
464
|
-
|
|
465
180
|
function officialFullscreenHasAllMotion(): boolean {
|
|
466
181
|
const term = process.env.TERM?.toLowerCase() ?? "";
|
|
467
182
|
return !(
|
|
@@ -522,141 +237,9 @@ function releaseFullscreenToolMouseMotion(tui?: any): void {
|
|
|
522
237
|
}
|
|
523
238
|
}
|
|
524
239
|
|
|
525
|
-
/** 官方 fullscreen:是否已跟随 transcript 底部(按钮隐藏条件)。 */
|
|
526
|
-
function isFullscreenAtBottom(tui: any): boolean {
|
|
527
|
-
const following = tui.isFollowingOutput ?? tui.getPrimaryScrollView?.()?.isFollowingEnd ?? true;
|
|
528
|
-
return Boolean(following);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
240
|
const FULLSCREEN_VIEWPORT_PATCH = Symbol("ccstyle.fullscreen-viewport-patch");
|
|
532
241
|
const FULLSCREEN_WHEEL_SCROLL_ORIGINAL = Symbol("ccstyle.fullscreen-wheel-scroll-original");
|
|
533
242
|
|
|
534
|
-
/** 布局树点查询:返回 (x,y) 处最深含行 leaf box(屏幕行 → 组件局部行)。 */
|
|
535
|
-
function fullscreenLeafAt(
|
|
536
|
-
layout: any,
|
|
537
|
-
x: number,
|
|
538
|
-
y: number,
|
|
539
|
-
): { box: any; localRow: number } | null {
|
|
540
|
-
const root = layout?.root;
|
|
541
|
-
if (!root) return null;
|
|
542
|
-
let best: { box: any; localRow: number } | null = null;
|
|
543
|
-
let bestDepth = -1;
|
|
544
|
-
const visit = (box: any, depth: number) => {
|
|
545
|
-
if (!box) return;
|
|
546
|
-
const clip = box.clip;
|
|
547
|
-
if (!clip || x < clip.x || x >= clip.x + clip.width || y < clip.y || y >= clip.y + clip.height)
|
|
548
|
-
return;
|
|
549
|
-
const isLeaf = !Array.isArray(box.children) || box.children.length === 0;
|
|
550
|
-
if (
|
|
551
|
-
isLeaf &&
|
|
552
|
-
y >= box.rect.y &&
|
|
553
|
-
y < box.rect.y + Math.max(1, box.rect.height) &&
|
|
554
|
-
depth > bestDepth
|
|
555
|
-
) {
|
|
556
|
-
best = { box, localRow: Math.max(0, y - box.rect.y) };
|
|
557
|
-
bestDepth = depth;
|
|
558
|
-
}
|
|
559
|
-
for (const child of box.children ?? []) visit(child, depth + 1);
|
|
560
|
-
};
|
|
561
|
-
visit(root, 0);
|
|
562
|
-
return best;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
/** leaf 自身不带 scrollView;内容宽度由最近的 scroll 祖先决定。 */
|
|
566
|
-
function fullscreenContentWidth(box: any, terminalWidth: number): number {
|
|
567
|
-
for (let current = box; current; current = current.parent) {
|
|
568
|
-
if (typeof current.scrollView?.getContentWidth === "function") {
|
|
569
|
-
return Math.max(1, current.scrollView.getContentWidth(terminalWidth));
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
return terminalWidth;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
/** 点击列是否为官方滚动条列(放行官方拖动)。 */
|
|
576
|
-
function isScrollbarColumnAt(layout: any, x: number): boolean {
|
|
577
|
-
let hit = false;
|
|
578
|
-
const visit = (box: any) => {
|
|
579
|
-
if (hit || !box) return;
|
|
580
|
-
if (box.scrollView?.isScrollbarVisible && x === box.rect.x + box.rect.width - 1) {
|
|
581
|
-
hit = true;
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
for (const child of box.children ?? []) visit(child);
|
|
585
|
-
};
|
|
586
|
-
visit(layout?.root);
|
|
587
|
-
return hit;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
type ComponentRowHit = {
|
|
591
|
-
component: any;
|
|
592
|
-
row: number;
|
|
593
|
-
/** 内部工具命中时所属的展开 group;普通卡点击仍折叠整个 group。 */
|
|
594
|
-
group?: ToolGroupComponent;
|
|
595
|
-
};
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
* 布局 leaf box 的组件通常是容器(documentContainer/dock 容器等),工具卡与
|
|
599
|
-
* widget 在其 children 内。按局部行遍历组件树,定位实际命中的子组件。
|
|
600
|
-
*/
|
|
601
|
-
function componentAtLocalRow(
|
|
602
|
-
component: any,
|
|
603
|
-
localRow: number,
|
|
604
|
-
width: number,
|
|
605
|
-
): ComponentRowHit | null {
|
|
606
|
-
if (component instanceof ToolGroupComponent) {
|
|
607
|
-
// 展开的 group:头两行(空行 + 头行)归 group,其余行映射到内部工具。
|
|
608
|
-
const child = component.childAtRow(localRow, width);
|
|
609
|
-
return child ? { ...child, group: component } : { component, row: localRow };
|
|
610
|
-
}
|
|
611
|
-
if (isToolExecutionComponent(component)) {
|
|
612
|
-
return { component, row: localRow };
|
|
613
|
-
}
|
|
614
|
-
if (isCompactAssistantComponent(component)) {
|
|
615
|
-
// compact 摘要行整体作为可展开卡片(折叠摘要 / 展开内容)。
|
|
616
|
-
return { component, row: localRow };
|
|
617
|
-
}
|
|
618
|
-
if (component === scrollButtonWidget) {
|
|
619
|
-
return { component, row: localRow };
|
|
620
|
-
}
|
|
621
|
-
if (!Array.isArray(component.children)) return null;
|
|
622
|
-
let offset = 0;
|
|
623
|
-
for (const child of component.children) {
|
|
624
|
-
let lines: string[] = [];
|
|
625
|
-
try {
|
|
626
|
-
const rendered = child.render?.(width);
|
|
627
|
-
if (Array.isArray(rendered)) lines = rendered.map((line) => String(line));
|
|
628
|
-
} catch {
|
|
629
|
-
lines = [];
|
|
630
|
-
}
|
|
631
|
-
if (localRow < offset + lines.length) {
|
|
632
|
-
return (
|
|
633
|
-
componentAtLocalRow(child, localRow - offset, width) ?? {
|
|
634
|
-
component: child,
|
|
635
|
-
row: localRow - offset,
|
|
636
|
-
}
|
|
637
|
-
);
|
|
638
|
-
}
|
|
639
|
-
offset += lines.length;
|
|
640
|
-
}
|
|
641
|
-
return null;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
/** fullscreen single-expand:group 作为整体,不继续递归其内部工具。 */
|
|
645
|
-
function collectFullscreenToolCards(component: any, out: any[], seen = new Set<any>()): void {
|
|
646
|
-
if (!component || typeof component !== "object" || seen.has(component)) return;
|
|
647
|
-
seen.add(component);
|
|
648
|
-
if (
|
|
649
|
-
isToolExecutionComponent(component) ||
|
|
650
|
-
component instanceof ToolGroupComponent ||
|
|
651
|
-
isCompactAssistantComponent(component)
|
|
652
|
-
) {
|
|
653
|
-
out.push(component);
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
if (!Array.isArray(component.children)) return;
|
|
657
|
-
for (const child of component.children) collectFullscreenToolCards(child, out, seen);
|
|
658
|
-
}
|
|
659
|
-
|
|
660
243
|
/**
|
|
661
244
|
* 官方 fullscreen 工具卡点击:collapsed hint 点击展开
|
|
662
245
|
* (有且仅保持一个展开:展开前收起其他工具卡),expanded 整卡二次点击收起,
|
|
@@ -740,47 +323,8 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
740
323
|
return true;
|
|
741
324
|
}
|
|
742
325
|
|
|
743
|
-
/** hover 与点击共用组件定位;同一布局下按容器/宽度/行缓存,避免 motion 重复渲染。 */
|
|
744
|
-
let fullscreenHoverCacheLayout: unknown = null;
|
|
745
|
-
let fullscreenHoverComponentCache = new WeakMap<object, Map<string, ComponentRowHit | null>>();
|
|
746
|
-
|
|
747
|
-
function cachedFullscreenComponentAtRow(
|
|
748
|
-
layout: any,
|
|
749
|
-
container: any,
|
|
750
|
-
row: number,
|
|
751
|
-
width: number,
|
|
752
|
-
): ComponentRowHit | null {
|
|
753
|
-
if (!container || typeof container !== "object") return null;
|
|
754
|
-
if (fullscreenHoverCacheLayout !== layout) {
|
|
755
|
-
fullscreenHoverCacheLayout = layout;
|
|
756
|
-
fullscreenHoverComponentCache = new WeakMap();
|
|
757
|
-
}
|
|
758
|
-
let rows = fullscreenHoverComponentCache.get(container);
|
|
759
|
-
if (!rows) {
|
|
760
|
-
rows = new Map();
|
|
761
|
-
fullscreenHoverComponentCache.set(container, rows);
|
|
762
|
-
}
|
|
763
|
-
const key = `${width}:${row}`;
|
|
764
|
-
if (rows.has(key)) return rows.get(key) ?? null;
|
|
765
|
-
const hit = componentAtLocalRow(container, row, width);
|
|
766
|
-
rows.set(key, hit);
|
|
767
|
-
return hit;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
/** fullscreen 鼠标悬停目标。 */
|
|
771
|
-
type FullscreenHoverTarget =
|
|
772
|
-
| { kind: "button" }
|
|
773
|
-
| { kind: "group"; component: ToolGroupComponent }
|
|
774
|
-
| { kind: "assistant"; component: any }
|
|
775
|
-
| {
|
|
776
|
-
kind: "tool";
|
|
777
|
-
component: any;
|
|
778
|
-
view: ExpandedToolIoView | null;
|
|
779
|
-
section: ToolIoSection | null;
|
|
780
|
-
};
|
|
781
|
-
|
|
782
326
|
/**
|
|
783
|
-
* fullscreen
|
|
327
|
+
* fullscreen 鼠标悬停:collapsed 卡 [click to show more] hint、
|
|
784
328
|
* expanded 卡截断头 show-more、回到底部按钮。motion 不 consume,官方链照常。
|
|
785
329
|
*/
|
|
786
330
|
function handleFullscreenToolHover(tui: any, packet: SgrMousePacket): void {
|
|
@@ -846,32 +390,6 @@ function handleFullscreenToolHover(tui: any, packet: SgrMousePacket): void {
|
|
|
846
390
|
applyFullscreenHover(tui, target);
|
|
847
391
|
}
|
|
848
392
|
|
|
849
|
-
/** 悬停状态变化才触发渲染(motion 事件密集,状态不变跳过)。 */
|
|
850
|
-
function applyFullscreenHover(tui: any, target: FullscreenHoverTarget | null): void {
|
|
851
|
-
let changed = false;
|
|
852
|
-
const nextCallId =
|
|
853
|
-
target?.kind === "tool" && !target.component.expanded
|
|
854
|
-
? (target.component.toolCallId ?? null)
|
|
855
|
-
: null;
|
|
856
|
-
if (nextCallId !== sharedToolHoverState().toolCallId) {
|
|
857
|
-
setHoveredToolCallId(nextCallId);
|
|
858
|
-
changed = true;
|
|
859
|
-
}
|
|
860
|
-
const nextGroup = target?.kind === "group" ? target.component : null;
|
|
861
|
-
if (setHoveredToolGroup(nextGroup)) changed = true;
|
|
862
|
-
const nextAssistant = target?.kind === "assistant" ? target.component : null;
|
|
863
|
-
if (setHoveredCompactAssistant(nextAssistant)) changed = true;
|
|
864
|
-
const nextView = target?.kind === "tool" ? target.view : null;
|
|
865
|
-
const nextSection = target?.kind === "tool" ? target.section : null;
|
|
866
|
-
if (setHoveredToolIo(nextView, nextSection)) changed = true;
|
|
867
|
-
const nextButton = target?.kind === "button";
|
|
868
|
-
if (nextButton !== scrollButtonHovered) {
|
|
869
|
-
scrollButtonHovered = nextButton;
|
|
870
|
-
changed = true;
|
|
871
|
-
}
|
|
872
|
-
if (changed) tui.requestRender?.();
|
|
873
|
-
}
|
|
874
|
-
|
|
875
393
|
/**
|
|
876
394
|
* 实例级包装 TuiAltScreen.handleViewportInput(惰性 Proxy 安全):
|
|
877
395
|
* 原型方法取 original(绕开 proxy 函数包装),实例 own property 装 wrapper
|
|
@@ -1276,11 +794,8 @@ export function teardownToolMouseInteraction(
|
|
|
1276
794
|
}
|
|
1277
795
|
restoreToolMouseRenderPatch();
|
|
1278
796
|
restoreFullscreenViewportInput(toolMouseTui);
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
scrollButtonWidget = null;
|
|
1282
|
-
scrollButtonSyncScheduled = false;
|
|
1283
|
-
toolMouseTui = null;
|
|
797
|
+
resetScrollButtonState();
|
|
798
|
+
setToolMouseTui(null);
|
|
1284
799
|
toolMouseUi = null;
|
|
1285
800
|
if (host[TOOL_MOUSE_OWNER_KEY] === owner) delete host[TOOL_MOUSE_OWNER_KEY];
|
|
1286
801
|
if (toolMouseInstallationOwner === owner) toolMouseInstallationOwner = null;
|
|
@@ -1290,8 +805,8 @@ export function teardownToolMouseInteraction(
|
|
|
1290
805
|
export function resetToolHoverState(): void {
|
|
1291
806
|
setHoveredToolCallId(null);
|
|
1292
807
|
setHoveredCompactAssistant(null);
|
|
1293
|
-
|
|
1294
|
-
|
|
808
|
+
setScrollButtonVisible(false);
|
|
809
|
+
setScrollButtonHovered(false);
|
|
1295
810
|
releaseFullscreenToolMouseMotion(toolMouseTui);
|
|
1296
811
|
}
|
|
1297
812
|
|
|
@@ -1311,19 +826,19 @@ export function installToolMouseInteraction(
|
|
|
1311
826
|
// 0.84+ 的 tui 是惰性 Proxy:regular 保留原生 scrollback;fullscreen
|
|
1312
827
|
// 由官方 LayoutFrame 命中,并由扩展补齐 hover 所需的 all-motion 上报。
|
|
1313
828
|
ctx.ui.setWidget(TOOL_MOUSE_WIDGET_KEY, (tui: any, theme: any) => {
|
|
1314
|
-
|
|
829
|
+
setToolMouseTui(tui);
|
|
1315
830
|
setToolTuiFullscreen(fullscreenLazyTui(tui));
|
|
1316
831
|
if (isLazyProxyTui(tui)) {
|
|
1317
832
|
patchFullscreenViewportInput(tui);
|
|
1318
833
|
ensureFullscreenToolMouseMotion(tui);
|
|
1319
|
-
|
|
834
|
+
setScrollButtonWidget({
|
|
1320
835
|
render: (width: number) => {
|
|
1321
836
|
patchFullscreenViewportInput(tui);
|
|
1322
837
|
ensureFullscreenToolMouseMotion(tui);
|
|
1323
838
|
return renderScrollButton(width, theme);
|
|
1324
839
|
},
|
|
1325
840
|
invalidate() {},
|
|
1326
|
-
};
|
|
841
|
+
});
|
|
1327
842
|
return scrollButtonWidget;
|
|
1328
843
|
}
|
|
1329
844
|
// Wrap doRender to capture the live frame for tool click/hover mapping.
|
|
@@ -1333,7 +848,7 @@ export function installToolMouseInteraction(
|
|
|
1333
848
|
render: (width: number) => renderScrollButton(width, theme),
|
|
1334
849
|
invalidate() {},
|
|
1335
850
|
};
|
|
1336
|
-
|
|
851
|
+
setScrollButtonWidget(widget);
|
|
1337
852
|
return widget;
|
|
1338
853
|
});
|
|
1339
854
|
toolMouseInputUnsubscribe = ctx.ui.onTerminalInput(handleToolMouseInput);
|
|
@@ -1361,3 +876,11 @@ export function scheduleSessionRender(refresh?: () => void): void {
|
|
|
1361
876
|
tui.requestRender(true);
|
|
1362
877
|
}, 0);
|
|
1363
878
|
}
|
|
879
|
+
|
|
880
|
+
export { toolMouseTui } from "./mouse-scroll.ts";
|
|
881
|
+
export {
|
|
882
|
+
hoveredToolCallId,
|
|
883
|
+
isToolCallHovered,
|
|
884
|
+
setHoveredToolGroup,
|
|
885
|
+
setHoveredToolIo,
|
|
886
|
+
} from "./mouse-hover.ts";
|