@herbertgao/pi-extensions 2026.9.18 → 2026.9.20
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 +20 -28
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +76 -59
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +76 -59
- package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/preview.webp +0 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +93 -34
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +346 -117
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/core.ts +6 -36
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/index.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -29
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +330 -360
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/index.ts +56 -108
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/session.ts +97 -15
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/subagent.ts +80 -45
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/flush-docked-bash.ts +36 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer-layout.ts +229 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +479 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/startup-header.ts +92 -61
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +75 -40
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +19 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +1080 -229
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +232 -259
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +42 -41
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +8 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +2 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +277 -113
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +15 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/packets.ts +18 -72
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/scroll.ts +61 -26
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/ansi-utils.ts +25 -57
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-component.ts +125 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-edit-render.ts +227 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-header.ts +136 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-highlight.ts +115 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-inline.ts +324 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-layout.ts +762 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-limits.ts +152 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-palette.ts +410 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-parse.ts +535 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +21 -3219
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-text.ts +44 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +568 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +47 -10
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/line-width-safety.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-execution.ts +1 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +145 -190
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/mcp-title.ts +39 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{message-display.ts → tool/message-display.ts} +24 -90
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +426 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +90 -132
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{show-more-hint.ts → tool/show-more-hint.ts} +10 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +8 -26
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-color.ts +44 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-text.ts +76 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/component-tree.ts +34 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/format.ts +28 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/fullscreen-detect.ts +51 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/patch-keys.ts +124 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/sgr-mouse.ts +70 -0
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +8 -8
- package/node_modules/pi-antigravity/README.md +29 -6
- package/node_modules/pi-antigravity/package.json +12 -7
- package/node_modules/pi-antigravity/src/auth/accounts.ts +327 -0
- package/node_modules/pi-antigravity/src/auth/index.ts +9 -0
- package/node_modules/pi-antigravity/src/client/client.ts +1 -1
- package/node_modules/pi-antigravity/src/image/image.ts +4 -4
- package/node_modules/pi-antigravity/src/index.ts +203 -13
- package/node_modules/pi-antigravity/src/search/index.ts +1 -0
- package/node_modules/pi-antigravity/src/search/search.ts +325 -0
- package/node_modules/pi-antigravity/src/stream/stream.ts +191 -14
- package/node_modules/pi-antigravity/src/types/enums.ts +45 -36
- package/node_modules/pi-antigravity/src/types/types.ts +5 -12
- package/node_modules/pi-antigravity/src/utils/http.ts +7 -2
- package/node_modules/pi-antigravity/src/utils/util.ts +31 -3
- package/node_modules/pi-typesafe/README.md +2 -0
- package/node_modules/pi-typesafe/package.json +2 -1
- package/node_modules/pi-web-access/CHANGELOG.md +72 -0
- package/node_modules/pi-web-access/README.md +34 -13
- package/node_modules/pi-web-access/chrome-cookies.ts +79 -25
- package/node_modules/pi-web-access/curator-page.ts +5 -1
- package/node_modules/pi-web-access/curator-server.ts +2 -0
- package/node_modules/pi-web-access/dist/index.js +1337 -1009
- package/node_modules/pi-web-access/exa.ts +20 -52
- package/node_modules/pi-web-access/extract.ts +41 -4
- package/node_modules/pi-web-access/gemini-search.ts +12 -4
- package/node_modules/pi-web-access/index.ts +29 -37
- package/node_modules/pi-web-access/openai-search.ts +35 -7
- package/node_modules/pi-web-access/package.json +6 -5
- package/node_modules/pi-web-access/page-query.ts +2 -2
- package/node_modules/pi-web-access/query-rewrite.ts +2 -3
- package/node_modules/pi-web-access/ssrf-protection.ts +27 -10
- package/node_modules/pi-web-access/storage.ts +48 -77
- package/node_modules/pi-web-access/summary-model-scope.ts +6 -77
- package/node_modules/pi-web-access/summary-review.ts +3 -4
- package/node_modules/pi-web-access/tavily.ts +67 -20
- package/node_modules/pi-web-access/tool-activation.ts +53 -33
- package/node_modules/pi-web-access/utils.ts +1 -1
- package/node_modules/pi-web-access/you.ts +164 -0
- package/node_modules/pi-web-access/zai.ts +161 -0
- package/package.json +7 -11
- package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.en.svg +0 -101
- package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.svg +0 -101
- package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/welcome.webp +0 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/render-utils.ts +0 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +0 -30
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-display-utils.ts +0 -12
- package/node_modules/pi-footer/CHANGELOG.md +0 -145
- package/node_modules/pi-footer/LICENSE +0 -21
- package/node_modules/pi-footer/README.md +0 -354
- package/node_modules/pi-footer/package.json +0 -68
- package/node_modules/pi-footer/src/cache.ts +0 -106
- package/node_modules/pi-footer/src/colors.ts +0 -237
- package/node_modules/pi-footer/src/config.ts +0 -202
- package/node_modules/pi-footer/src/event-widgets.ts +0 -41
- package/node_modules/pi-footer/src/extension-statuses.ts +0 -102
- package/node_modules/pi-footer/src/git.ts +0 -135
- package/node_modules/pi-footer/src/index.ts +0 -268
- package/node_modules/pi-footer/src/metrics.ts +0 -141
- package/node_modules/pi-footer/src/presets.ts +0 -383
- package/node_modules/pi-footer/src/render.ts +0 -114
- package/node_modules/pi-footer/src/separators.ts +0 -52
- package/node_modules/pi-footer/src/types.ts +0 -122
- package/node_modules/pi-footer/src/ui/color-level-confirm.ts +0 -22
- package/node_modules/pi-footer/src/ui/color-options.ts +0 -195
- package/node_modules/pi-footer/src/ui/config-lifecycle.ts +0 -61
- package/node_modules/pi-footer/src/ui/edit-colors.ts +0 -13
- package/node_modules/pi-footer/src/ui/events.ts +0 -35
- package/node_modules/pi-footer/src/ui/extension-status-picker.ts +0 -60
- package/node_modules/pi-footer/src/ui/extension-statuses.ts +0 -73
- package/node_modules/pi-footer/src/ui/fields.ts +0 -252
- package/node_modules/pi-footer/src/ui/global-menu.ts +0 -126
- package/node_modules/pi-footer/src/ui/helpers.ts +0 -44
- package/node_modules/pi-footer/src/ui/layout.ts +0 -17
- package/node_modules/pi-footer/src/ui/line-list.ts +0 -19
- package/node_modules/pi-footer/src/ui/model.ts +0 -64
- package/node_modules/pi-footer/src/ui/navigation.ts +0 -29
- package/node_modules/pi-footer/src/ui/option-edit.ts +0 -93
- package/node_modules/pi-footer/src/ui/overlay-render.ts +0 -103
- package/node_modules/pi-footer/src/ui/screen-context.ts +0 -18
- package/node_modules/pi-footer/src/ui/screen-controller.ts +0 -27
- package/node_modules/pi-footer/src/ui/screen-render.ts +0 -32
- package/node_modules/pi-footer/src/ui/screen-state.ts +0 -20
- package/node_modules/pi-footer/src/ui/screen.ts +0 -243
- package/node_modules/pi-footer/src/ui/screens/add-widget.ts +0 -92
- package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +0 -52
- package/node_modules/pi-footer/src/ui/screens/controller.ts +0 -12
- package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +0 -68
- package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +0 -115
- package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +0 -61
- package/node_modules/pi-footer/src/ui/screens/global.ts +0 -75
- package/node_modules/pi-footer/src/ui/screens/line-list.ts +0 -108
- package/node_modules/pi-footer/src/ui/screens/main.ts +0 -99
- package/node_modules/pi-footer/src/ui/screens/terminal.ts +0 -121
- package/node_modules/pi-footer/src/ui/screens/widget-list.ts +0 -154
- package/node_modules/pi-footer/src/ui/terminal-menu.ts +0 -32
- package/node_modules/pi-footer/src/ui/theme.ts +0 -48
- package/node_modules/pi-footer/src/ui/title-bar.ts +0 -100
- package/node_modules/pi-footer/src/ui/widget-actions.ts +0 -76
- package/node_modules/pi-footer/src/ui.ts +0 -66
- package/node_modules/pi-footer/src/widgets/context.ts +0 -28
- package/node_modules/pi-footer/src/widgets/core/active-tools.ts +0 -17
- package/node_modules/pi-footer/src/widgets/core/context-window.ts +0 -24
- package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +0 -19
- package/node_modules/pi-footer/src/widgets/core/cwd.ts +0 -108
- package/node_modules/pi-footer/src/widgets/core/event.ts +0 -34
- package/node_modules/pi-footer/src/widgets/core/external-status.ts +0 -102
- package/node_modules/pi-footer/src/widgets/core/model-provider.ts +0 -20
- package/node_modules/pi-footer/src/widgets/core/model.ts +0 -35
- package/node_modules/pi-footer/src/widgets/core/provider.ts +0 -17
- package/node_modules/pi-footer/src/widgets/core/session-name.ts +0 -17
- package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +0 -17
- package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/branch.ts +0 -81
- package/node_modules/pi-footer/src/widgets/git/clean.ts +0 -19
- package/node_modules/pi-footer/src/widgets/git/deletions.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/diff.ts +0 -38
- package/node_modules/pi-footer/src/widgets/git/insertions.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/remote.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/root.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/sha.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/staged.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/status.ts +0 -19
- package/node_modules/pi-footer/src/widgets/git/unstaged.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/untracked.ts +0 -17
- package/node_modules/pi-footer/src/widgets/instance.ts +0 -118
- package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +0 -18
- package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +0 -32
- package/node_modules/pi-footer/src/widgets/layout/separator.ts +0 -57
- package/node_modules/pi-footer/src/widgets/layout/spacer.ts +0 -31
- package/node_modules/pi-footer/src/widgets/options.ts +0 -122
- package/node_modules/pi-footer/src/widgets/project/runtime.ts +0 -361
- package/node_modules/pi-footer/src/widgets/registry.ts +0 -223
- package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +0 -17
- package/node_modules/pi-footer/src/widgets/session/compactions.ts +0 -17
- package/node_modules/pi-footer/src/widgets/session/elapsed.ts +0 -18
- package/node_modules/pi-footer/src/widgets/session/last-activity.ts +0 -18
- package/node_modules/pi-footer/src/widgets/session/messages.ts +0 -19
- package/node_modules/pi-footer/src/widgets/session/session-id.ts +0 -17
- package/node_modules/pi-footer/src/widgets/session/session-start.ts +0 -18
- package/node_modules/pi-footer/src/widgets/session/tool-results.ts +0 -17
- package/node_modules/pi-footer/src/widgets/session/total-messages.ts +0 -19
- package/node_modules/pi-footer/src/widgets/session/total-time.ts +0 -18
- package/node_modules/pi-footer/src/widgets/session/user-messages.ts +0 -17
- package/node_modules/pi-footer/src/widgets/store.ts +0 -26
- package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +0 -55
- package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +0 -18
- package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +0 -18
- package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +0 -108
- package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +0 -26
- package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +0 -29
- package/node_modules/pi-footer/src/widgets/tokens/context.ts +0 -29
- package/node_modules/pi-footer/src/widgets/tokens/cost.ts +0 -51
- package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +0 -25
- package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +0 -18
- package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +0 -25
- package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +0 -18
- package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +0 -20
- package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +0 -25
- package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +0 -18
- package/node_modules/pi-footer/src/widgets/types.ts +0 -181
- package/node_modules/pi-footer/src/widgets/utils/colors.ts +0 -11
- package/node_modules/pi-footer/src/widgets/utils/context.ts +0 -112
- package/node_modules/pi-footer/src/widgets/utils/session.ts +0 -19
- package/node_modules/pi-footer/src/widgets/utils/token-format.ts +0 -78
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
import { Text, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
2
|
-
import { posix, win32 } from "node:path";
|
|
3
2
|
import { inspect } from "node:util";
|
|
4
3
|
import { config } from "../../config/config.ts";
|
|
5
|
-
import { showMoreHintText } from "
|
|
4
|
+
import { showMoreHintText } from "./show-more-hint.ts";
|
|
6
5
|
import { TOOL_LOADING_INTERVAL_MS, toolLoadingIcon } from "../../utils/tool-loading-icon.ts";
|
|
6
|
+
import { getToolMouseTui } from "../mouse/scroll.ts";
|
|
7
7
|
import { sanitizeToolResultText } from "../../utils/tool-result-sanitize.ts";
|
|
8
8
|
|
|
9
|
+
const TOOL_VIEWPORT_WIDTH_RATIO = 0.8;
|
|
10
|
+
/** 宽屏右侧留白上限:比例留白超过这么多列时改用固定留白。 */
|
|
11
|
+
const TOOL_VIEWPORT_MAX_GUTTER = 24;
|
|
12
|
+
|
|
9
13
|
export function toolViewportWidth(width: number): number {
|
|
10
|
-
return Math.max(
|
|
14
|
+
return Math.max(
|
|
15
|
+
1,
|
|
16
|
+
Math.floor(width * TOOL_VIEWPORT_WIDTH_RATIO),
|
|
17
|
+
Math.floor(width) - TOOL_VIEWPORT_MAX_GUTTER,
|
|
18
|
+
);
|
|
11
19
|
}
|
|
12
20
|
|
|
13
|
-
/**
|
|
21
|
+
/** 与默认工具结果相同的一级缩进包装。子组件只扣始终加上的 1 列;↳ 行多出的 2 列由 truncateToWidth 吃掉。 */
|
|
14
22
|
export function insetComponent(component: any): any {
|
|
15
|
-
|
|
23
|
+
const wrapped: Record<string, unknown> = {
|
|
16
24
|
render: (width: number) =>
|
|
17
25
|
component.render(Math.max(1, width - 1)).map((line: string) => {
|
|
18
26
|
const nestedMarker = line.replace(/^((?:\x1b\[[0-?]*[ -/]*[@-~])*)↳/, "$1 ↳");
|
|
19
|
-
return ` ${nestedMarker}
|
|
27
|
+
return truncateToWidth(` ${nestedMarker}`, Math.max(0, width), "");
|
|
20
28
|
}),
|
|
21
29
|
invalidate: () => component.invalidate?.(),
|
|
22
30
|
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
.trim();
|
|
29
|
-
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
31
|
+
// 内层 diff 声明的 remainder 行:鼠标层只能看到这个包装组件。
|
|
32
|
+
if (typeof component.isCollapsedHintLine === "function") {
|
|
33
|
+
wrapped.isCollapsedHintLine = (line: string) => component.isCollapsedHintLine(line);
|
|
34
|
+
}
|
|
35
|
+
return wrapped;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
function rawTextFromResult(result: any): string {
|
|
@@ -72,14 +78,11 @@ function hasExpandableResult(text: string): boolean {
|
|
|
72
78
|
return countLines(text) > 1;
|
|
73
79
|
}
|
|
74
80
|
|
|
75
|
-
function toolIcon(_name: string): string {
|
|
76
|
-
return "●";
|
|
77
|
-
}
|
|
78
|
-
|
|
79
81
|
const activeAnimationContexts = new Set<any>();
|
|
80
82
|
let sharedAnimationTimer: ReturnType<typeof setTimeout> | null = null;
|
|
81
83
|
|
|
82
84
|
function clearAnimation(context: any) {
|
|
85
|
+
if (context?.state) context.state.ccstyleAnimationLight = false;
|
|
83
86
|
if (!context?.state?.ccstyleAnimationScheduled) return;
|
|
84
87
|
context.state.ccstyleAnimationScheduled = false;
|
|
85
88
|
activeAnimationContexts.delete(context);
|
|
@@ -92,6 +95,7 @@ function clearAnimation(context: any) {
|
|
|
92
95
|
export function clearAllAnimations() {
|
|
93
96
|
for (const ctx of activeAnimationContexts) {
|
|
94
97
|
ctx.state.ccstyleAnimationScheduled = false;
|
|
98
|
+
ctx.state.ccstyleAnimationLight = false;
|
|
95
99
|
}
|
|
96
100
|
activeAnimationContexts.clear();
|
|
97
101
|
if (sharedAnimationTimer) {
|
|
@@ -100,8 +104,15 @@ export function clearAllAnimations() {
|
|
|
100
104
|
}
|
|
101
105
|
}
|
|
102
106
|
|
|
103
|
-
export function scheduleAnimation(
|
|
107
|
+
export function scheduleAnimation(
|
|
108
|
+
context: any,
|
|
109
|
+
options: { light?: boolean; intervalMs?: number } = {},
|
|
110
|
+
) {
|
|
104
111
|
const state = (context.state ??= {});
|
|
112
|
+
// light:调用方自己在 render() 内重取 loading 帧,定时器只需请求重绘;
|
|
113
|
+
// 否则定时器走 context.invalidate()(compact 摘要等靠它重建)。
|
|
114
|
+
// 每次调用都按本次 options 写入,避免上次 light 残留后非 light 调用仍跳过 invalidate。
|
|
115
|
+
state.ccstyleAnimationLight = Boolean(options.light);
|
|
105
116
|
if (state.ccstyleAnimationScheduled) return;
|
|
106
117
|
state.ccstyleAnimationScheduled = true;
|
|
107
118
|
activeAnimationContexts.add(context);
|
|
@@ -110,11 +121,16 @@ export function scheduleAnimation(context: any, intervalMs = TOOL_LOADING_INTERV
|
|
|
110
121
|
sharedAnimationTimer = null;
|
|
111
122
|
const contexts = Array.from(activeAnimationContexts);
|
|
112
123
|
activeAnimationContexts.clear();
|
|
124
|
+
const tui = getToolMouseTui();
|
|
125
|
+
const canRequestRender = typeof tui?.requestRender === "function";
|
|
126
|
+
let lightFrames = false;
|
|
113
127
|
for (const ctx of contexts) {
|
|
114
128
|
ctx.state.ccstyleAnimationScheduled = false;
|
|
115
|
-
ctx.
|
|
129
|
+
if (ctx.state.ccstyleAnimationLight && canRequestRender) lightFrames = true;
|
|
130
|
+
else ctx.invalidate?.();
|
|
116
131
|
}
|
|
117
|
-
|
|
132
|
+
if (lightFrames) tui.requestRender();
|
|
133
|
+
}, options.intervalMs ?? TOOL_LOADING_INTERVAL_MS);
|
|
118
134
|
}
|
|
119
135
|
}
|
|
120
136
|
|
|
@@ -127,7 +143,8 @@ type ToolVisualState = "pending" | "success" | "error";
|
|
|
127
143
|
export function settledIcon(name: string, state: ToolVisualState | undefined): string {
|
|
128
144
|
if (state === "success") return "✓";
|
|
129
145
|
if (state === "error") return "✗";
|
|
130
|
-
|
|
146
|
+
// toolIcon(name) 曾恒返回 "●",已内联;name 保留以稳定导出签名。
|
|
147
|
+
return "●";
|
|
131
148
|
}
|
|
132
149
|
|
|
133
150
|
export function setToolVisualState(context: any, visualState: ToolVisualState) {
|
|
@@ -213,7 +230,7 @@ export class ExpandedToolResultText {
|
|
|
213
230
|
}
|
|
214
231
|
}
|
|
215
232
|
|
|
216
|
-
/**
|
|
233
|
+
/** 截断体末行 `… +N more lines` 旁的展开提示,点击打开全量预览。 */
|
|
217
234
|
export const SHOW_MORE_LABEL = "• click to show more";
|
|
218
235
|
|
|
219
236
|
export type ToolIoSection = "input" | "output";
|
|
@@ -232,6 +249,8 @@ const EXPANDED_TOOL_IO_VIEW_GENERATION = Symbol("ccstyle-expanded-tool-io-view")
|
|
|
232
249
|
* └ Output click to show more
|
|
233
250
|
* result line…
|
|
234
251
|
*
|
|
252
|
+
* flushLeft=true(仅 mode=on 展开卡):去掉树线前导空格,由外层 Box(1,1) 提供 1 格 padding。
|
|
253
|
+
*
|
|
235
254
|
* Reused across re-renders via context.lastComponent when possible.
|
|
236
255
|
*/
|
|
237
256
|
export class ExpandedToolIoView {
|
|
@@ -247,16 +266,20 @@ export class ExpandedToolIoView {
|
|
|
247
266
|
private hoveredSection: ToolIoSection | null = null;
|
|
248
267
|
/** Which sections currently show the show-more affordance (after last render). */
|
|
249
268
|
private truncated: { input: boolean; output: boolean } = { input: false, output: false };
|
|
250
|
-
/** 0-based
|
|
269
|
+
/** 0-based header line indexes that carry show-more after last render. */
|
|
251
270
|
private showMoreHeaderRows: { input?: number; output?: number } = {};
|
|
252
271
|
|
|
272
|
+
/** flushLeft:贴左渲染(mode=on 展开卡);默认 false 保留前导空格(compact 等共用路径)。 */
|
|
273
|
+
private flushLeft: boolean;
|
|
274
|
+
|
|
253
275
|
constructor(
|
|
254
276
|
theme: any,
|
|
255
277
|
inputBody: string,
|
|
256
278
|
outputBody: string,
|
|
257
279
|
isError: boolean,
|
|
258
|
-
maxOutputLines = config.
|
|
259
|
-
maxInputLines = config.
|
|
280
|
+
maxOutputLines = config.expandedOutputMaxLines,
|
|
281
|
+
maxInputLines = config.expandedInputMaxLines,
|
|
282
|
+
flushLeft = false,
|
|
260
283
|
) {
|
|
261
284
|
this.theme = theme;
|
|
262
285
|
this.inputBody = inputBody;
|
|
@@ -264,6 +287,7 @@ export class ExpandedToolIoView {
|
|
|
264
287
|
this.isError = isError;
|
|
265
288
|
this.maxOutputLines = Math.max(1, maxOutputLines);
|
|
266
289
|
this.maxInputLines = Math.max(1, maxInputLines);
|
|
290
|
+
this.flushLeft = flushLeft;
|
|
267
291
|
}
|
|
268
292
|
|
|
269
293
|
setContent(
|
|
@@ -272,16 +296,19 @@ export class ExpandedToolIoView {
|
|
|
272
296
|
isError: boolean,
|
|
273
297
|
maxOutputLines?: number,
|
|
274
298
|
maxInputLines?: number,
|
|
299
|
+
flushLeft?: boolean,
|
|
275
300
|
): void {
|
|
276
301
|
const nextOut =
|
|
277
302
|
maxOutputLines !== undefined ? Math.max(1, maxOutputLines) : this.maxOutputLines;
|
|
278
303
|
const nextIn = maxInputLines !== undefined ? Math.max(1, maxInputLines) : this.maxInputLines;
|
|
304
|
+
const nextFlush = flushLeft !== undefined ? flushLeft : this.flushLeft;
|
|
279
305
|
if (
|
|
280
306
|
this.inputBody === inputBody &&
|
|
281
307
|
this.outputBody === outputBody &&
|
|
282
308
|
this.isError === isError &&
|
|
283
309
|
this.maxOutputLines === nextOut &&
|
|
284
|
-
this.maxInputLines === nextIn
|
|
310
|
+
this.maxInputLines === nextIn &&
|
|
311
|
+
this.flushLeft === nextFlush
|
|
285
312
|
) {
|
|
286
313
|
return;
|
|
287
314
|
}
|
|
@@ -290,6 +317,7 @@ export class ExpandedToolIoView {
|
|
|
290
317
|
this.isError = isError;
|
|
291
318
|
this.maxOutputLines = nextOut;
|
|
292
319
|
this.maxInputLines = nextIn;
|
|
320
|
+
this.flushLeft = nextFlush;
|
|
293
321
|
this.invalidate();
|
|
294
322
|
}
|
|
295
323
|
|
|
@@ -307,7 +335,11 @@ export class ExpandedToolIoView {
|
|
|
307
335
|
this.invalidate();
|
|
308
336
|
}
|
|
309
337
|
|
|
310
|
-
|
|
338
|
+
getHoveredSection(): ToolIoSection | null {
|
|
339
|
+
return this.hoveredSection;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** True when the plain truncation footer carries show-more. Input 续行带 │,Output 不带。 */
|
|
311
343
|
matchShowMoreLine(plainLine: string): ToolIoSection | null {
|
|
312
344
|
const line = plainLine.replace(/\x1b\[[0-9;]*m/g, "");
|
|
313
345
|
if (!line.includes(` • ${showMoreHintText()}`) || !/\+\d+ more lines/.test(line)) return null;
|
|
@@ -347,7 +379,9 @@ export class ExpandedToolIoView {
|
|
|
347
379
|
|
|
348
380
|
const theme = this.theme;
|
|
349
381
|
const safeWidth = Math.max(1, Math.floor(width));
|
|
350
|
-
|
|
382
|
+
// flushLeft:贴左(外层 Box 负责 1 格 pad);否则保留 1 格前导空格(compact 共用)
|
|
383
|
+
const lead = this.flushLeft ? "" : " ";
|
|
384
|
+
const rail = `${lead}│ `;
|
|
351
385
|
const railWidth = visibleWidth(rail);
|
|
352
386
|
const bodyWidth = toolViewportWidth(safeWidth);
|
|
353
387
|
const contentWidth = Math.max(1, bodyWidth - railWidth);
|
|
@@ -357,7 +391,7 @@ export class ExpandedToolIoView {
|
|
|
357
391
|
this.showMoreHeaderRows = {};
|
|
358
392
|
|
|
359
393
|
const pushHeader = (corner: "├" | "└", label: string) => {
|
|
360
|
-
const mark = theme.fg("dim",
|
|
394
|
+
const mark = theme.fg("dim", `${lead}${corner} `);
|
|
361
395
|
const title = theme.fg(
|
|
362
396
|
"accent",
|
|
363
397
|
typeof theme.bold === "function" ? theme.bold(label) : label,
|
|
@@ -366,12 +400,13 @@ export class ExpandedToolIoView {
|
|
|
366
400
|
};
|
|
367
401
|
|
|
368
402
|
const pushRailLine = (styledContent: string, continued = true) => {
|
|
369
|
-
|
|
403
|
+
// 续行 rail;Output 正文相对 └ 缩进 2 格(+ 可选 lead)
|
|
404
|
+
const prefix = continued ? rail : `${lead} `;
|
|
370
405
|
lines.push(truncateToWidth(theme.fg("dim", prefix) + styledContent, safeWidth, ""));
|
|
371
406
|
};
|
|
372
407
|
|
|
373
408
|
const pushBlankRail = () => {
|
|
374
|
-
lines.push(truncateToWidth(theme.fg("dim",
|
|
409
|
+
lines.push(truncateToWidth(theme.fg("dim", `${lead}│`), safeWidth, ""));
|
|
375
410
|
};
|
|
376
411
|
|
|
377
412
|
/** Style `key: value` input rows — dim keys, readable values. */
|
|
@@ -407,6 +442,7 @@ export class ExpandedToolIoView {
|
|
|
407
442
|
if (truncated) {
|
|
408
443
|
const hidden = Math.max(0, wrapped.length - visible.length);
|
|
409
444
|
if (hidden > 0) {
|
|
445
|
+
// hover 只高亮文字,圆点保持 dim(与 group hint 一致)。
|
|
410
446
|
const more =
|
|
411
447
|
theme.fg("dim", " •") +
|
|
412
448
|
theme.fg(
|
|
@@ -423,6 +459,7 @@ export class ExpandedToolIoView {
|
|
|
423
459
|
const hasInput = this.inputBody.trim().length > 0;
|
|
424
460
|
const outputText = this.getOutputBody();
|
|
425
461
|
|
|
462
|
+
// Decide show-more from the same truncation rules as pushBody.
|
|
426
463
|
const inputWouldTruncate =
|
|
427
464
|
hasInput &&
|
|
428
465
|
bodyExceedsLineLimit(this.inputBody, this.maxInputLines, contentWidth, true, theme);
|
|
@@ -447,11 +484,19 @@ export class ExpandedToolIoView {
|
|
|
447
484
|
pushBlankRail();
|
|
448
485
|
this.truncated.output = outputWouldTruncate;
|
|
449
486
|
pushHeader("└", "Output");
|
|
450
|
-
pushBody(outputText, {
|
|
487
|
+
pushBody(outputText, {
|
|
488
|
+
limit: this.maxOutputLines,
|
|
489
|
+
continued: false,
|
|
490
|
+
section: "output",
|
|
491
|
+
});
|
|
451
492
|
} else {
|
|
452
493
|
this.truncated.output = outputWouldTruncate;
|
|
453
494
|
pushHeader("└", "Output");
|
|
454
|
-
pushBody(outputText, {
|
|
495
|
+
pushBody(outputText, {
|
|
496
|
+
limit: this.maxOutputLines,
|
|
497
|
+
continued: false,
|
|
498
|
+
section: "output",
|
|
499
|
+
});
|
|
455
500
|
}
|
|
456
501
|
|
|
457
502
|
this.cachedWidth = width;
|
|
@@ -558,103 +603,6 @@ export function middleTruncateToWidth(text: string, width: number): string {
|
|
|
558
603
|
return `${left}…${right}`;
|
|
559
604
|
}
|
|
560
605
|
|
|
561
|
-
export type ToolCallSummary = {
|
|
562
|
-
main: string;
|
|
563
|
-
detail: string;
|
|
564
|
-
/** 路径摘要保留结构,供最终渲染按实际宽度优先保留文件名。 */
|
|
565
|
-
path?: { prefix: string; value: string };
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
function pathApi(value: string) {
|
|
569
|
-
if (win32.isAbsolute(value)) return win32;
|
|
570
|
-
if (posix.isAbsolute(value)) return posix;
|
|
571
|
-
return undefined;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/** cwd 内绝对路径转相对路径;cwd 外路径保持不变。 */
|
|
575
|
-
export function displayPath(value: unknown, cwd?: string): string {
|
|
576
|
-
const text = oneLine(value, 4096);
|
|
577
|
-
const base = cwd ? oneLine(cwd, 4096) : "";
|
|
578
|
-
const api = pathApi(text);
|
|
579
|
-
if (!api || !base || !api.isAbsolute(base)) return text;
|
|
580
|
-
const relative = api.relative(base, text);
|
|
581
|
-
if (!relative) return api.basename(text) || ".";
|
|
582
|
-
if (relative === ".." || relative.startsWith(`..${api.sep}`) || api.isAbsolute(relative)) {
|
|
583
|
-
return text;
|
|
584
|
-
}
|
|
585
|
-
return relative;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
function headToWidth(text: string, width: number, ellipsis = ""): string {
|
|
589
|
-
if (visibleWidth(text) <= width) return text;
|
|
590
|
-
if (width <= 0) return "";
|
|
591
|
-
const suffix = visibleWidth(ellipsis) <= width ? ellipsis : "";
|
|
592
|
-
const contentWidth = width - visibleWidth(suffix);
|
|
593
|
-
let head = "";
|
|
594
|
-
for (const char of Array.from(text)) {
|
|
595
|
-
if (visibleWidth(head + char) > contentWidth) break;
|
|
596
|
-
head += char;
|
|
597
|
-
}
|
|
598
|
-
return head + suffix;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
function tailToWidth(text: string, width: number): string {
|
|
602
|
-
if (width <= 0) return "";
|
|
603
|
-
let tail = "";
|
|
604
|
-
for (const char of Array.from(text).reverse()) {
|
|
605
|
-
if (visibleWidth(char + tail) > width) break;
|
|
606
|
-
tail = char + tail;
|
|
607
|
-
}
|
|
608
|
-
return tail;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
/** 中间截断路径:目录保留开头,末尾优先完整保留文件名。 */
|
|
612
|
-
export function truncatePathToWidth(path: string, width: number): string {
|
|
613
|
-
if (visibleWidth(path) <= width) return path;
|
|
614
|
-
if (width <= 1) return width === 1 ? "…" : "";
|
|
615
|
-
const separatorIndex = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
616
|
-
const filename = separatorIndex >= 0 ? path.slice(separatorIndex + 1) : path;
|
|
617
|
-
const filenameWidth = visibleWidth(filename);
|
|
618
|
-
if (separatorIndex >= 0 && filenameWidth + 1 <= width) {
|
|
619
|
-
const separator = path[separatorIndex]!;
|
|
620
|
-
const suffix = `${separator}${filename}`;
|
|
621
|
-
const prefixWidth = width - visibleWidth(suffix) - 1;
|
|
622
|
-
const prefix = headToWidth(path.slice(0, separatorIndex), Math.max(0, prefixWidth));
|
|
623
|
-
return `${prefix}…${prefix ? suffix : filename}`;
|
|
624
|
-
}
|
|
625
|
-
const leftWidth = Math.max(1, Math.floor((width - 1) * 0.45));
|
|
626
|
-
const rightWidth = Math.max(0, width - leftWidth - 1);
|
|
627
|
-
return `${headToWidth(filename, leftWidth)}…${tailToWidth(filename, rightWidth)}`;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
/** 路径展示的统一入口:相对化后按当前可用宽度中间截断。 */
|
|
631
|
-
export function formatDisplayPath(value: unknown, cwd: string | undefined, width: number): string {
|
|
632
|
-
return truncatePathToWidth(displayPath(value, cwd), Math.max(0, Math.floor(width)));
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
/** 按最终终端宽度渲染摘要;路径摘要不会再被整行头部截断。 */
|
|
636
|
-
export function fitToolCallSummary(summary: ToolCallSummary, width: number): string {
|
|
637
|
-
if (!summary.path) return headTruncateToWidth(summary.main, width);
|
|
638
|
-
const prefix = summary.path.prefix;
|
|
639
|
-
const pathWidth = Math.max(0, width - visibleWidth(prefix) - 1);
|
|
640
|
-
if (pathWidth <= 0) return headTruncateToWidth(prefix, width);
|
|
641
|
-
return `${prefix} ${truncatePathToWidth(summary.path.value, pathWidth)}`;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
export function pathSummary(
|
|
645
|
-
prefix: string,
|
|
646
|
-
value: unknown,
|
|
647
|
-
cwd: string | undefined,
|
|
648
|
-
detail = "",
|
|
649
|
-
): ToolCallSummary {
|
|
650
|
-
const path = displayPath(value, cwd);
|
|
651
|
-
return {
|
|
652
|
-
main: `${prefix} ${path}`,
|
|
653
|
-
detail,
|
|
654
|
-
path: { prefix, value: path },
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
|
|
658
606
|
/** Pretty-print tool call args for the expanded Input section. */
|
|
659
607
|
export function formatToolInputArgs(args: unknown, maxChars = 8_000): string {
|
|
660
608
|
if (args === undefined || args === null) return "";
|
|
@@ -746,6 +694,8 @@ export function renderExpandedToolResult(
|
|
|
746
694
|
lastComponent?: unknown,
|
|
747
695
|
args?: unknown,
|
|
748
696
|
context?: any,
|
|
697
|
+
/** mode=on 展开卡贴左;compact 等保持默认前导空格 */
|
|
698
|
+
flushLeft = false,
|
|
749
699
|
): ExpandedToolIoView | ExpandedToolResultText | Text {
|
|
750
700
|
const inputBody = formatToolInputArgs(args);
|
|
751
701
|
const outputBody = body;
|
|
@@ -756,7 +706,14 @@ export function renderExpandedToolResult(
|
|
|
756
706
|
if (inputBody.trim() || outputBody.trim()) {
|
|
757
707
|
let view: ExpandedToolIoView;
|
|
758
708
|
if (isExpandedToolIoView(lastComponent)) {
|
|
759
|
-
lastComponent.setContent(
|
|
709
|
+
lastComponent.setContent(
|
|
710
|
+
inputBody,
|
|
711
|
+
outputBody,
|
|
712
|
+
isError,
|
|
713
|
+
maxOutputLines,
|
|
714
|
+
maxInputLines,
|
|
715
|
+
flushLeft,
|
|
716
|
+
);
|
|
760
717
|
view = lastComponent;
|
|
761
718
|
} else {
|
|
762
719
|
view = new ExpandedToolIoView(
|
|
@@ -766,6 +723,7 @@ export function renderExpandedToolResult(
|
|
|
766
723
|
isError,
|
|
767
724
|
maxOutputLines,
|
|
768
725
|
maxInputLines,
|
|
726
|
+
flushLeft,
|
|
769
727
|
);
|
|
770
728
|
}
|
|
771
729
|
if (context) rememberIoView(context, view);
|
|
@@ -803,7 +761,7 @@ function frameViewId(view: ExpandedToolIoView): number | null {
|
|
|
803
761
|
function withIoViewMarkers(view: ExpandedToolIoView, lines: string[]): string[] {
|
|
804
762
|
const id = frameViewId(view);
|
|
805
763
|
if (id === null) return lines;
|
|
806
|
-
// Mark by exact
|
|
764
|
+
// Mark by exact header row from render — never scan body text for Input/Output labels.
|
|
807
765
|
const marked = lines.slice();
|
|
808
766
|
for (const { section, line } of view.showMoreHeaderLineIndexes()) {
|
|
809
767
|
if (line < 0 || line >= marked.length) continue;
|
|
@@ -36,3 +36,13 @@ export function expandShortcutText(): string {
|
|
|
36
36
|
export function showMoreHintText(): string {
|
|
37
37
|
return isToolTuiFullscreen() ? "click to show more" : `${expandShortcutText()} to show more`;
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
/** 展开卡的收起提示,与折叠态 showMoreHintText 同一位置对称。 */
|
|
41
|
+
export function collapseHintText(): string {
|
|
42
|
+
return isToolTuiFullscreen() ? "↑ Collapse" : `${expandShortcutText()} to collapse`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 该行是否带展开态收起提示(用于展开卡的 hint hover 命中)。 */
|
|
46
|
+
export function isCollapseHintLine(plainLine: string): boolean {
|
|
47
|
+
return plainLine.includes(collapseHintText());
|
|
48
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
2
2
|
import { AssistantMessageComponent, ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { walkComponentTree } from "../utils/component-tree.ts";
|
|
4
|
+
import { COMPACT_MODE_PATCH_KEY, patchRegistry } from "../utils/patch-keys.ts";
|
|
3
5
|
|
|
4
6
|
// pi-coding-agent 类型声明中 AssistantMessageComponent 仅能以 value 形式使用,
|
|
5
7
|
// updateContent/lastMessage 用结构化类型访问。
|
|
@@ -37,8 +39,6 @@ export function refreshTranscriptComponent(value: unknown): boolean {
|
|
|
37
39
|
* 遍历 value.children 与 value.getMountedRoots?.()(seen 集合防环),
|
|
38
40
|
* 单个组件失败用 try/catch 隔离,不影响其余组件。
|
|
39
41
|
*/
|
|
40
|
-
// compact-mode 把补丁暴露在此 Symbol 上(见 compact-mode.ts),避免循环依赖。
|
|
41
|
-
const COMPACT_MODE_PATCH_KEY = Symbol.for("pi.ccstyle.compact-mode-patch");
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* 补丁链序不变量:compact 模式下 compact-mode 必须位于 compact-thinking
|
|
@@ -48,8 +48,10 @@ const COMPACT_MODE_PATCH_KEY = Symbol.for("pi.ccstyle.compact-mode-patch");
|
|
|
48
48
|
*/
|
|
49
49
|
function assertCompactModeOutermost(): void {
|
|
50
50
|
try {
|
|
51
|
-
const patch = (
|
|
52
|
-
|
|
51
|
+
const patch = patchRegistry.get<{ assertAssistantOwnership?: () => void }>(
|
|
52
|
+
COMPACT_MODE_PATCH_KEY,
|
|
53
|
+
);
|
|
54
|
+
if (patch && typeof patch.assertAssistantOwnership === "function") {
|
|
53
55
|
patch.assertAssistantOwnership();
|
|
54
56
|
}
|
|
55
57
|
} catch {
|
|
@@ -61,31 +63,11 @@ export function refreshMountedTranscript(tui?: unknown): void {
|
|
|
61
63
|
if (!tui || typeof (tui as any).getMountedRoots !== "function") return;
|
|
62
64
|
// 先修正链序再重绘:扫描触发 updateContent 时 compact-mode 已在外层。
|
|
63
65
|
assertCompactModeOutermost();
|
|
64
|
-
|
|
65
|
-
const visit = (value: any): void => {
|
|
66
|
-
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
67
|
-
seen.add(value);
|
|
68
|
-
if (Array.isArray(value)) {
|
|
69
|
-
for (const child of value) visit(child);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
66
|
+
walkComponentTree(tui, (value: any) => {
|
|
72
67
|
try {
|
|
73
68
|
refreshTranscriptComponent(value);
|
|
74
69
|
} catch {
|
|
75
70
|
// 单个组件失败不阻断其余组件;后续 session_tree/重绘再试。
|
|
76
71
|
}
|
|
77
|
-
|
|
78
|
-
if (Array.isArray(children)) {
|
|
79
|
-
for (const child of children) visit(child);
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
const mounted = value.getMountedRoots?.();
|
|
83
|
-
if (Array.isArray(mounted)) {
|
|
84
|
-
for (const root of mounted) visit(root);
|
|
85
|
-
}
|
|
86
|
-
} catch {
|
|
87
|
-
// 惰性 Proxy / 未挂载容器没有 mounted roots。
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
visit(tui);
|
|
72
|
+
});
|
|
91
73
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** ANSI 16 色 → RGB(标准 xterm 色序)。 */
|
|
2
|
+
const ANSI_16_RGB: readonly [number, number, number][] = [
|
|
3
|
+
[0, 0, 0],
|
|
4
|
+
[128, 0, 0],
|
|
5
|
+
[0, 128, 0],
|
|
6
|
+
[128, 128, 0],
|
|
7
|
+
[0, 0, 128],
|
|
8
|
+
[128, 0, 128],
|
|
9
|
+
[0, 128, 128],
|
|
10
|
+
[192, 192, 192],
|
|
11
|
+
[128, 128, 128],
|
|
12
|
+
[255, 0, 0],
|
|
13
|
+
[0, 255, 0],
|
|
14
|
+
[255, 255, 0],
|
|
15
|
+
[0, 0, 255],
|
|
16
|
+
[255, 0, 255],
|
|
17
|
+
[0, 255, 255],
|
|
18
|
+
[255, 255, 255],
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/** ANSI 16 色码(0-15)→ RGB;越界回退白。 */
|
|
22
|
+
export function ansi16ToRgb(index: number): [number, number, number] {
|
|
23
|
+
return ANSI_16_RGB[index] ?? [255, 255, 255];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** ANSI 256 色码 → RGB(0-15 标准色 + 232-255 灰阶 + 16-231 六阶立方体)。 */
|
|
27
|
+
export function ansi256ToRgb(code: number): [number, number, number] {
|
|
28
|
+
// SGR 正则可解析任意位数字,入口先规范:取整并钳制到合法区间,
|
|
29
|
+
// 否则灰阶分支对 code > 255 会算出超出 0-255 的非法 RGB。
|
|
30
|
+
if (!Number.isFinite(code)) return [0, 0, 0];
|
|
31
|
+
code = Math.min(255, Math.max(0, Math.trunc(code)));
|
|
32
|
+
if (code < 16) return ansi16ToRgb(code);
|
|
33
|
+
if (code >= 232) {
|
|
34
|
+
const gray = 8 + (code - 232) * 10;
|
|
35
|
+
return [gray, gray, gray];
|
|
36
|
+
}
|
|
37
|
+
const cubeIndex = code - 16;
|
|
38
|
+
const levels = [0, 95, 135, 175, 215, 255];
|
|
39
|
+
return [
|
|
40
|
+
levels[Math.floor(cubeIndex / 36)]!,
|
|
41
|
+
levels[Math.floor((cubeIndex % 36) / 6)]!,
|
|
42
|
+
levels[cubeIndex % 6]!,
|
|
43
|
+
];
|
|
44
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 顶层共享的 ANSI/终端控制序列处理。
|
|
3
|
+
*
|
|
4
|
+
* 收敛原先散落在 renderer/grouping.ts / renderer/mouse/packets.ts /
|
|
5
|
+
* renderer/compact-mode.ts 的重复剥离逻辑,供 renderer 与 feature 共用。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** 单个 CSI 序列(颜色、光标等 SGR/CUP/ED 等)。 */
|
|
9
|
+
const CSI_SEQUENCE_RE = /\x1b\[[0-?]*[ -/]*[@-~]/g;
|
|
10
|
+
/** OSC 序列(如 \x1b]8;;url\x07 或 ST 结尾)。 */
|
|
11
|
+
const OSC_SEQUENCE_RE = /\x1b\][^\x07]*(?:\x07|\x1b\\)/g;
|
|
12
|
+
/** compact-mode 原有 hasVisibleText 用的更严格 OSC(内容不允许内嵌 ESC)。 */
|
|
13
|
+
const OSC_SEQUENCE_STRICT_RE = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
|
|
14
|
+
|
|
15
|
+
/** 剥离一行内所有 CSI 序列。 */
|
|
16
|
+
export function stripAnsi(line: string): string {
|
|
17
|
+
return line.replace(CSI_SEQUENCE_RE, "");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 仅剥离终端序列、保留原布局(换行/空白不动),用于命中区间计算。 */
|
|
21
|
+
export function stripTerminalSequencesPreservingLayout(value: string): string {
|
|
22
|
+
return value.replace(OSC_SEQUENCE_RE, "").replace(CSI_SEQUENCE_RE, "");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 剥离终端序列并折叠空白(用于纯文本比较)。 */
|
|
26
|
+
export function stripTerminalSequences(value: string): string {
|
|
27
|
+
return stripTerminalSequencesPreservingLayout(value).replace(/\s+/g, " ").trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 去掉行内所有 CSI/OSC 序列后是否仍有可见文本(判断工具卡首尾内容行)。 */
|
|
31
|
+
export function hasVisibleText(line: string): boolean {
|
|
32
|
+
// OSC 用更严格的变体:内容内嵌 ESC 时不吞并后续文本,保持 compact-mode 原有语义。
|
|
33
|
+
return line.replace(CSI_SEQUENCE_RE, "").replace(OSC_SEQUENCE_STRICT_RE, "").trim().length > 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 剥离背景色 ANSI(用于重新铺背景行)。 */
|
|
37
|
+
export function stripBackgroundAnsi(line: string): string {
|
|
38
|
+
return line.replace(/\x1b\[(?:4[0-9]|10[0-7]|48(?:(?:;|:)[0-9]+)+|49)m/g, "");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 剥离行首状态图标(展开组内工具首行复用)。 */
|
|
42
|
+
export function stripLeadingStatusIcon(line: string): string {
|
|
43
|
+
return line.replace(
|
|
44
|
+
/^((?:\x1b\[[0-9;]*m|[ \t]|[├└│─])*)(?:\x1b\[[0-9;]*m)*(?:[✓✗●○■⬤•·])(?:\x1b\[[0-9;]*m)*\s+/,
|
|
45
|
+
"$1",
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 终端序列零宽(不计入 plain 下标),与 stripAnsi/stripTerminalSequencesPreservingLayout 一致。 */
|
|
50
|
+
const ANY_SEQUENCE_RE = new RegExp(`${CSI_SEQUENCE_RE.source}|${OSC_SEQUENCE_RE.source}`, "g");
|
|
51
|
+
|
|
52
|
+
/** 只保留 [start, end) 之外的字符(base 为 text 首字符在 plain 串中的下标)。 */
|
|
53
|
+
function keepOutsideRange(text: string, base: number, start: number, end: number): string {
|
|
54
|
+
if (base + text.length <= start || base >= end) return text;
|
|
55
|
+
return text.slice(0, Math.max(0, start - base)) + text.slice(Math.max(0, end - base));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 删除 plain 区间 [start, end) 的字符,保留其余字符的 ANSI 样式。
|
|
60
|
+
* 区间内的终端序列原样保留,因此区间后的字符(如收尾括号)沿用前文颜色,
|
|
61
|
+
* 不会像截断后拼纯文本那样掉回终端默认前景色。
|
|
62
|
+
*/
|
|
63
|
+
export function removeStyledRange(styled: string, start: number, end: number): string {
|
|
64
|
+
if (end <= start) return styled;
|
|
65
|
+
let plain = 0;
|
|
66
|
+
let cursor = 0;
|
|
67
|
+
let out = "";
|
|
68
|
+
for (const match of styled.matchAll(ANY_SEQUENCE_RE)) {
|
|
69
|
+
const text = styled.slice(cursor, match.index);
|
|
70
|
+
out += keepOutsideRange(text, plain, start, end);
|
|
71
|
+
plain += text.length;
|
|
72
|
+
out += match[0];
|
|
73
|
+
cursor = match.index + match[0].length;
|
|
74
|
+
}
|
|
75
|
+
return out + keepOutsideRange(styled.slice(cursor), plain, start, end);
|
|
76
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组件树递归遍历共享实现。
|
|
3
|
+
*
|
|
4
|
+
* 收敛原先散落在 renderer 的 compact-mode / message-display / transcript-refresh /
|
|
5
|
+
* grouping / mouse/packets 的「children + getMountedRoots + seen 防环」同款遍历。
|
|
6
|
+
*
|
|
7
|
+
* visitor 在每个非数组对象节点上调用一次;返回 `false` 表示不再下钻该节点的
|
|
8
|
+
* children 与 mounted roots(对应各处原有的提前 return 语义)。
|
|
9
|
+
*/
|
|
10
|
+
export function walkComponentTree(root: any, visitor: (value: any) => boolean | void): void {
|
|
11
|
+
const seen = new Set<any>();
|
|
12
|
+
const visit = (value: any): void => {
|
|
13
|
+
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
14
|
+
seen.add(value);
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
for (const child of value) visit(child);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (visitor(value) === false) return;
|
|
20
|
+
const children = value.children;
|
|
21
|
+
if (Array.isArray(children)) {
|
|
22
|
+
for (const child of children) visit(child);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const mounted = value.getMountedRoots?.();
|
|
26
|
+
if (Array.isArray(mounted)) {
|
|
27
|
+
for (const root of mounted) visit(root);
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// renderer 切换中的惰性 Proxy 可能暂时没有 mounted roots。
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
visit(root);
|
|
34
|
+
}
|