@esso0428/pi-subagents 0.15.6 → 0.15.7
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/CHANGELOG.md +9 -0
- package/dist/agent-history-list.d.ts +1 -0
- package/dist/agent-history-list.d.ts.map +1 -0
- package/dist/agent-history-list.js +1 -0
- package/dist/agent-history-list.js.map +1 -0
- package/dist/agent-history.d.ts +1 -0
- package/dist/agent-history.d.ts.map +1 -0
- package/dist/agent-history.js +1 -0
- package/dist/agent-history.js.map +1 -0
- package/dist/agent-manager.d.ts +2 -0
- package/dist/agent-manager.d.ts.map +1 -0
- package/dist/agent-manager.js +71 -0
- package/dist/agent-manager.js.map +1 -0
- package/dist/agent-runner.d.ts +1 -0
- package/dist/agent-runner.d.ts.map +1 -0
- package/dist/agent-runner.js +8 -0
- package/dist/agent-runner.js.map +1 -0
- package/dist/agent-types.d.ts +1 -0
- package/dist/agent-types.d.ts.map +1 -0
- package/dist/agent-types.js +1 -0
- package/dist/agent-types.js.map +1 -0
- package/dist/context.d.ts +1 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +1 -0
- package/dist/context.js.map +1 -0
- package/dist/cross-extension-rpc.d.ts +1 -0
- package/dist/cross-extension-rpc.d.ts.map +1 -0
- package/dist/cross-extension-rpc.js +1 -0
- package/dist/cross-extension-rpc.js.map +1 -0
- package/dist/custom-agents.d.ts +1 -0
- package/dist/custom-agents.d.ts.map +1 -0
- package/dist/custom-agents.js +1 -0
- package/dist/custom-agents.js.map +1 -0
- package/dist/default-agents.d.ts +1 -0
- package/dist/default-agents.d.ts.map +1 -0
- package/dist/default-agents.js +1 -0
- package/dist/default-agents.js.map +1 -0
- package/dist/enabled-models.d.ts +1 -0
- package/dist/enabled-models.d.ts.map +1 -0
- package/dist/enabled-models.js +1 -0
- package/dist/enabled-models.js.map +1 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +1 -0
- package/dist/env.js.map +1 -0
- package/dist/group-join.d.ts +1 -0
- package/dist/group-join.d.ts.map +1 -0
- package/dist/group-join.js +1 -0
- package/dist/group-join.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -10
- package/dist/index.js.map +1 -0
- package/dist/invocation-config.d.ts +1 -0
- package/dist/invocation-config.d.ts.map +1 -0
- package/dist/invocation-config.js +1 -0
- package/dist/invocation-config.js.map +1 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +1 -0
- package/dist/memory.js.map +1 -0
- package/dist/model-resolver.d.ts +1 -0
- package/dist/model-resolver.d.ts.map +1 -0
- package/dist/model-resolver.js +1 -0
- package/dist/model-resolver.js.map +1 -0
- package/dist/nico-overrides.d.ts +1 -0
- package/dist/nico-overrides.d.ts.map +1 -0
- package/dist/nico-overrides.js +1 -0
- package/dist/nico-overrides.js.map +1 -0
- package/dist/output-file.d.ts +1 -0
- package/dist/output-file.d.ts.map +1 -0
- package/dist/output-file.js +1 -0
- package/dist/output-file.js.map +1 -0
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +1 -0
- package/dist/prompts.js.map +1 -0
- package/dist/schedule-store.d.ts +1 -0
- package/dist/schedule-store.d.ts.map +1 -0
- package/dist/schedule-store.js +1 -0
- package/dist/schedule-store.js.map +1 -0
- package/dist/schedule.d.ts +1 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +1 -0
- package/dist/schedule.js.map +1 -0
- package/dist/settings.d.ts +1 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +1 -0
- package/dist/settings.js.map +1 -0
- package/dist/skill-loader.d.ts +1 -0
- package/dist/skill-loader.d.ts.map +1 -0
- package/dist/skill-loader.js +1 -0
- package/dist/skill-loader.js.map +1 -0
- package/dist/status-note.d.ts +1 -0
- package/dist/status-note.d.ts.map +1 -0
- package/dist/status-note.js +1 -0
- package/dist/status-note.js.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/agent-widget.d.ts +1 -0
- package/dist/ui/agent-widget.d.ts.map +1 -0
- package/dist/ui/agent-widget.js +4 -2
- package/dist/ui/agent-widget.js.map +1 -0
- package/dist/ui/ccstyle/diff/ansi-utils.d.ts +11 -0
- package/dist/ui/ccstyle/diff/ansi-utils.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/ansi-utils.js +145 -0
- package/dist/ui/ccstyle/diff/ansi-utils.js.map +1 -0
- package/dist/ui/ccstyle/diff/diff-presentation.d.ts +12 -0
- package/dist/ui/ccstyle/diff/diff-presentation.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/diff-presentation.js +49 -0
- package/dist/ui/ccstyle/diff/diff-presentation.js.map +1 -0
- package/dist/ui/ccstyle/diff/diff-renderer.d.ts +46 -0
- package/dist/ui/ccstyle/diff/diff-renderer.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/diff-renderer.js +2044 -0
- package/dist/ui/ccstyle/diff/diff-renderer.js.map +1 -0
- package/dist/ui/ccstyle/diff/line-width-safety.d.ts +12 -0
- package/dist/ui/ccstyle/diff/line-width-safety.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/line-width-safety.js +59 -0
- package/dist/ui/ccstyle/diff/line-width-safety.js.map +1 -0
- package/dist/ui/ccstyle/diff/render-utils.d.ts +6 -0
- package/dist/ui/ccstyle/diff/render-utils.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/render-utils.js +25 -0
- package/dist/ui/ccstyle/diff/render-utils.js.map +1 -0
- package/dist/ui/ccstyle/diff/types.d.ts +22 -0
- package/dist/ui/ccstyle/diff/types.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/types.js +10 -0
- package/dist/ui/ccstyle/diff/types.js.map +1 -0
- package/dist/ui/ccstyle/diff/write-display-utils.d.ts +2 -0
- package/dist/ui/ccstyle/diff/write-display-utils.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/write-display-utils.js +12 -0
- package/dist/ui/ccstyle/diff/write-display-utils.js.map +1 -0
- package/dist/ui/ccstyle/tool-renderer.d.ts +17 -0
- package/dist/ui/ccstyle/tool-renderer.d.ts.map +1 -0
- package/dist/ui/ccstyle/tool-renderer.js +58 -0
- package/dist/ui/ccstyle/tool-renderer.js.map +1 -0
- package/dist/ui/ccstyle/tool-result.d.ts +24 -0
- package/dist/ui/ccstyle/tool-result.d.ts.map +1 -0
- package/dist/ui/ccstyle/tool-result.js +193 -0
- package/dist/ui/ccstyle/tool-result.js.map +1 -0
- package/dist/ui/conversation-blocks.d.ts +28 -0
- package/dist/ui/conversation-blocks.d.ts.map +1 -0
- package/dist/ui/conversation-blocks.js +119 -27
- package/dist/ui/conversation-blocks.js.map +1 -0
- package/dist/ui/conversation-nvim.d.ts +1 -0
- package/dist/ui/conversation-nvim.d.ts.map +1 -0
- package/dist/ui/conversation-nvim.js +1 -0
- package/dist/ui/conversation-nvim.js.map +1 -0
- package/dist/ui/conversation-role.d.ts +13 -0
- package/dist/ui/conversation-role.d.ts.map +1 -0
- package/dist/ui/conversation-role.js +39 -0
- package/dist/ui/conversation-role.js.map +1 -0
- package/dist/ui/conversation-search.d.ts +1 -0
- package/dist/ui/conversation-search.d.ts.map +1 -0
- package/dist/ui/conversation-search.js +1 -0
- package/dist/ui/conversation-search.js.map +1 -0
- package/dist/ui/conversation-timeline.d.ts +62 -0
- package/dist/ui/conversation-timeline.d.ts.map +1 -0
- package/dist/ui/conversation-timeline.js +263 -0
- package/dist/ui/conversation-timeline.js.map +1 -0
- package/dist/ui/conversation-viewer.d.ts +7 -1
- package/dist/ui/conversation-viewer.d.ts.map +1 -0
- package/dist/ui/conversation-viewer.js +96 -54
- package/dist/ui/conversation-viewer.js.map +1 -0
- package/dist/ui/fleet-list.d.ts +1 -0
- package/dist/ui/fleet-list.d.ts.map +1 -0
- package/dist/ui/fleet-list.js +1 -0
- package/dist/ui/fleet-list.js.map +1 -0
- package/dist/ui/schedule-menu.d.ts +1 -0
- package/dist/ui/schedule-menu.d.ts.map +1 -0
- package/dist/ui/schedule-menu.js +1 -0
- package/dist/ui/schedule-menu.js.map +1 -0
- package/dist/ui/viewer-keys.d.ts +1 -0
- package/dist/ui/viewer-keys.d.ts.map +1 -0
- package/dist/ui/viewer-keys.js +1 -0
- package/dist/ui/viewer-keys.js.map +1 -0
- package/dist/usage.d.ts +1 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +1 -0
- package/dist/usage.js.map +1 -0
- package/dist/worktree.d.ts +1 -0
- package/dist/worktree.d.ts.map +1 -0
- package/dist/worktree.js +1 -0
- package/dist/worktree.js.map +1 -0
- package/dist/write-execution.d.ts +42 -0
- package/dist/write-execution.d.ts.map +1 -0
- package/dist/write-execution.js +138 -0
- package/dist/write-execution.js.map +1 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +61 -0
- package/src/agent-runner.ts +7 -0
- package/src/index.ts +35 -11
- package/src/types.ts +4 -0
- package/src/ui/agent-widget.ts +2 -1
- package/src/ui/ccstyle/ATTRIBUTION.md +37 -0
- package/src/ui/ccstyle/diff/ansi-utils.ts +168 -0
- package/src/ui/ccstyle/diff/diff-presentation.ts +70 -0
- package/src/ui/ccstyle/diff/diff-renderer.ts +3148 -0
- package/src/ui/ccstyle/diff/line-width-safety.ts +92 -0
- package/src/ui/ccstyle/diff/render-utils.ts +32 -0
- package/src/ui/ccstyle/diff/types.ts +33 -0
- package/src/ui/ccstyle/diff/write-display-utils.ts +12 -0
- package/src/ui/ccstyle/tool-renderer.ts +71 -0
- package/src/ui/ccstyle/tool-result.ts +238 -0
- package/src/ui/conversation-blocks.ts +160 -26
- package/src/ui/conversation-role.ts +45 -0
- package/src/ui/conversation-timeline.ts +315 -0
- package/src/ui/conversation-viewer.ts +91 -54
- package/src/write-execution.ts +180 -0
- package/test/output-file-history.test.ts +50 -0
- package/test/ui/conversation-blocks.test.ts +31 -0
- package/test/ui/conversation-diff.test.ts +91 -0
- package/test/ui/conversation-viewer.test.ts +50 -0
- package/test/write-execution.test.ts +83 -0
- package/tsconfig.json +16 -0
- package/vitest.temp.config.mts +6 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { normalizeDiffRenderWidth } from "./diff-presentation.js";
|
|
2
|
+
import { pluralize } from "./render-utils.js";
|
|
3
|
+
import { showMoreHintText } from "./render-utils.js";
|
|
4
|
+
|
|
5
|
+
export interface WidthMeasurementOps {
|
|
6
|
+
measure(text: string): number;
|
|
7
|
+
truncate(text: string, maxWidth: number): string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CollapsedDiffHintOptions {
|
|
11
|
+
remainingLines: number;
|
|
12
|
+
hiddenHunks: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function guardSafeWidth(width: number): number | undefined {
|
|
16
|
+
const safe = normalizeDiffRenderWidth(width);
|
|
17
|
+
return safe === 0 ? undefined : safe;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function renderWithSafeWidth<T>(width: number, fallback: T, render: (safeWidth: number) => T): T {
|
|
21
|
+
const safeWidth = guardSafeWidth(width);
|
|
22
|
+
if (safeWidth === undefined) {
|
|
23
|
+
return fallback;
|
|
24
|
+
}
|
|
25
|
+
return render(safeWidth);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function clampRenderedLineToWidth(
|
|
29
|
+
text: string,
|
|
30
|
+
width: number,
|
|
31
|
+
ops: WidthMeasurementOps,
|
|
32
|
+
): string {
|
|
33
|
+
return renderWithSafeWidth(width, "", (safeWidth) => {
|
|
34
|
+
if (ops.measure(text) <= safeWidth) {
|
|
35
|
+
return text;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (let targetWidth = safeWidth; targetWidth >= 0; targetWidth--) {
|
|
39
|
+
const candidate = ops.truncate(text, targetWidth);
|
|
40
|
+
if (ops.measure(candidate) <= safeWidth) {
|
|
41
|
+
return candidate;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return "";
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function clampRenderedLinesToWidth(
|
|
50
|
+
lines: string[],
|
|
51
|
+
width: number,
|
|
52
|
+
ops: WidthMeasurementOps,
|
|
53
|
+
): string[] {
|
|
54
|
+
return lines.map((line) => clampRenderedLineToWidth(line, width, ops));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function buildCollapsedDiffHintText(
|
|
58
|
+
options: CollapsedDiffHintOptions,
|
|
59
|
+
width: number,
|
|
60
|
+
ops: WidthMeasurementOps,
|
|
61
|
+
): string {
|
|
62
|
+
return renderWithSafeWidth(width, "", (safeWidth) => {
|
|
63
|
+
const remainingText = `${options.remainingLines} more ${pluralize(options.remainingLines, "diff line")}`;
|
|
64
|
+
const hiddenHunksText =
|
|
65
|
+
options.hiddenHunks > 0
|
|
66
|
+
? `${options.hiddenHunks} more ${pluralize(options.hiddenHunks, "hunk")}`
|
|
67
|
+
: undefined;
|
|
68
|
+
const shortRemainingText = `${options.remainingLines} more ${pluralize(options.remainingLines, "line")}`;
|
|
69
|
+
const shortHiddenHunksText =
|
|
70
|
+
options.hiddenHunks > 0
|
|
71
|
+
? `${options.hiddenHunks} ${pluralize(options.hiddenHunks, "hunk")}`
|
|
72
|
+
: undefined;
|
|
73
|
+
|
|
74
|
+
const candidates = [
|
|
75
|
+
`… (${[remainingText, hiddenHunksText, showMoreHintText()].filter(Boolean).join(" • ")})`,
|
|
76
|
+
`… (${[remainingText, hiddenHunksText].filter(Boolean).join(" • ")})`,
|
|
77
|
+
`… (${[shortRemainingText, shortHiddenHunksText].filter(Boolean).join(" • ")})`,
|
|
78
|
+
options.hiddenHunks > 0
|
|
79
|
+
? `… (+${options.remainingLines} • +${options.hiddenHunks}h)`
|
|
80
|
+
: `… (+${options.remainingLines})`,
|
|
81
|
+
"…",
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
for (const candidate of candidates) {
|
|
85
|
+
if (ops.measure(candidate) <= safeWidth) {
|
|
86
|
+
return candidate;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return clampRenderedLineToWidth(candidates[candidates.length - 1] ?? "", safeWidth, ops);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export { sanitizeAnsiForThemedOutput } from "./ansi-utils.js";
|
|
2
|
+
|
|
3
|
+
export function pluralize(count: number, singular: string, plural = `${singular}s`): string {
|
|
4
|
+
return count === 1 ? singular : plural;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Strip terminal control sequences before captured tool text is rendered again. */
|
|
8
|
+
const UNSAFE_TERMINAL_ESCAPE = new RegExp(
|
|
9
|
+
"(?:\\u001B\\]|\\u009D)[\\s\\S]*?(?:\\u0007|\\u001B\\x5C|\\u009C)" +
|
|
10
|
+
"|(?:\\u001B[PX^_]|\\u0090)[\\s\\S]*?(?:\\u001B\\x5C|\\u009C)" +
|
|
11
|
+
"|(?:\\u001B\\[|\\u009B)[0-?]*[ -/]*[@-~]" +
|
|
12
|
+
"|\\u001B[@-_]",
|
|
13
|
+
"g",
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export function sanitizeToolResultText(value: string, maxChars?: number): string {
|
|
17
|
+
const source =
|
|
18
|
+
typeof maxChars === "number" && maxChars >= 0 && value.length > maxChars
|
|
19
|
+
? value.slice(0, maxChars)
|
|
20
|
+
: value;
|
|
21
|
+
return source
|
|
22
|
+
.replace(UNSAFE_TERMINAL_ESCAPE, "")
|
|
23
|
+
.replace(/\x1B/g, "")
|
|
24
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, "")
|
|
25
|
+
.replace(/\r\n/g, "\n")
|
|
26
|
+
.replace(/\r/g, "\n");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Kept local so renderer modules do not depend on a package-global hint setting. */
|
|
30
|
+
export function showMoreHintText(): string {
|
|
31
|
+
return "click to show more";
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type DiffViewMode = "auto" | "split" | "unified";
|
|
2
|
+
export type DiffIndicatorMode = "bars" | "classic" | "none";
|
|
3
|
+
|
|
4
|
+
/** Immutable viewer-local configuration; no global ccstyle config is consulted. */
|
|
5
|
+
export interface ViewerDiffConfig {
|
|
6
|
+
diffWordWrap: boolean;
|
|
7
|
+
diffViewMode: DiffViewMode;
|
|
8
|
+
diffIndicatorMode: DiffIndicatorMode;
|
|
9
|
+
editDiffCollapsedLines: number;
|
|
10
|
+
expandedPreviewMaxLines: number;
|
|
11
|
+
/** Optional compatibility knobs for callers that want split/write tuning. */
|
|
12
|
+
diffSplitMinWidth?: number;
|
|
13
|
+
writeDiffCollapsedLines?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** @deprecated Use ViewerDiffConfig. Kept as a local alias for renderer callers. */
|
|
17
|
+
export type ToolDisplayConfig = ViewerDiffConfig;
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_TOOL_DISPLAY_CONFIG: ViewerDiffConfig = Object.freeze({
|
|
20
|
+
diffWordWrap: true,
|
|
21
|
+
diffViewMode: "auto",
|
|
22
|
+
diffIndicatorMode: "bars",
|
|
23
|
+
editDiffCollapsedLines: 24,
|
|
24
|
+
expandedPreviewMaxLines: 40,
|
|
25
|
+
diffSplitMinWidth: 120,
|
|
26
|
+
writeDiffCollapsedLines: 0,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export interface PersistedDiff {
|
|
30
|
+
diff?: string;
|
|
31
|
+
patch?: string;
|
|
32
|
+
diffUnavailableReason?: string;
|
|
33
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function splitWriteContentLines(content: string): string[] {
|
|
2
|
+
if (!content) {
|
|
3
|
+
return [];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const normalized = content.replace(/\r\n?/g, "\n");
|
|
7
|
+
const lines = normalized.split("\n");
|
|
8
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
9
|
+
lines.pop();
|
|
10
|
+
}
|
|
11
|
+
return lines;
|
|
12
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { truncateToWidth, type Component } from "@earendil-works/pi-tui";
|
|
2
|
+
import { createViewerCcstyleResult, oneLine } from "./tool-result.js";
|
|
3
|
+
|
|
4
|
+
export interface ViewerToolRenderers {
|
|
5
|
+
name: string;
|
|
6
|
+
label: string;
|
|
7
|
+
renderShell: "self";
|
|
8
|
+
renderCall?: (args: unknown, theme: any, context: any) => Component;
|
|
9
|
+
renderResult?: (result: any, options: any, theme: any, context: any) => Component;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function titleForTool(toolName: string): string {
|
|
13
|
+
return toolName
|
|
14
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
15
|
+
.replace(/[_-]+/g, " ")
|
|
16
|
+
.replace(/\b\w/g, (character) => character.toUpperCase());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function summaryForCall(toolName: string, args: any): string {
|
|
20
|
+
if (!args || typeof args !== "object") return titleForTool(toolName);
|
|
21
|
+
const value =
|
|
22
|
+
args.path ?? args.file_path ?? args.command ?? args.query ?? args.pattern ?? args.url ?? args.name;
|
|
23
|
+
return value === undefined
|
|
24
|
+
? titleForTool(toolName)
|
|
25
|
+
: `${titleForTool(toolName)} ${oneLine(value)}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function renderCallComponent(toolName: string, args: any, theme: any, context: any): Component {
|
|
29
|
+
const pending = context?.isPartial || context?.executionStarted;
|
|
30
|
+
const icon = pending ? "●" : context?.isError ? "✗" : "✓";
|
|
31
|
+
const color = pending ? "accent" : context?.isError ? "error" : "success";
|
|
32
|
+
return {
|
|
33
|
+
render(width: number): string[] {
|
|
34
|
+
const safeWidth = Math.max(1, Math.floor(Number.isFinite(width) ? width : 1));
|
|
35
|
+
const prefix = ` ${theme.fg(color, icon)} `;
|
|
36
|
+
return [truncateToWidth(prefix + theme.fg("toolTitle", summaryForCall(toolName, args)), safeWidth, "")];
|
|
37
|
+
},
|
|
38
|
+
invalidate() {},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create a renderer definition consumed by the public ToolExecutionComponent
|
|
44
|
+
* renderer hooks. It is viewer-local: no Pi prototype or terminal listener is
|
|
45
|
+
* installed, and no global ccstyle configuration is read.
|
|
46
|
+
*/
|
|
47
|
+
export function createViewerCcstyleTool(toolName: string): ViewerToolRenderers {
|
|
48
|
+
const name = String(toolName || "tool");
|
|
49
|
+
// ToolExecutionComponent does not expose its result to renderCall. Keep a
|
|
50
|
+
// renderer-local settled bit so a rebuilt history card can show ✓/✗ rather
|
|
51
|
+
// than treating every reconstructed call as still running.
|
|
52
|
+
let settled = false;
|
|
53
|
+
return {
|
|
54
|
+
name,
|
|
55
|
+
label: name,
|
|
56
|
+
renderShell: "self",
|
|
57
|
+
renderCall(args, theme, context) {
|
|
58
|
+
return renderCallComponent(name, args, theme, {
|
|
59
|
+
...context,
|
|
60
|
+
executionStarted: settled ? false : context?.executionStarted,
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
renderResult(result, options, theme, context) {
|
|
64
|
+
settled = true;
|
|
65
|
+
return createViewerCcstyleResult(name, result, options, theme, context);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { renderRichToolResult } from "./tool-result.js";
|
|
71
|
+
export type { PersistedDiff, ViewerDiffConfig } from "./diff/types.js";
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
|
|
2
|
+
import {
|
|
3
|
+
renderEditDiffResult,
|
|
4
|
+
renderWriteDiffResult,
|
|
5
|
+
type DisplayConfigInput,
|
|
6
|
+
} from "./diff/diff-renderer.js";
|
|
7
|
+
import { DEFAULT_TOOL_DISPLAY_CONFIG, type PersistedDiff, type ViewerDiffConfig } from "./diff/types.js";
|
|
8
|
+
import { sanitizeToolResultText, showMoreHintText } from "./diff/render-utils.js";
|
|
9
|
+
|
|
10
|
+
export function toolViewportWidth(width: number): number {
|
|
11
|
+
return Math.max(1, Math.floor(Number.isFinite(width) ? width : 1));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function oneLine(value: unknown, max = 4096): string {
|
|
15
|
+
const text = sanitizeToolResultText(String(value ?? "")).replace(/\s+/g, " ").trim();
|
|
16
|
+
return text.length > max ? `${text.slice(0, Math.max(0, max - 1))}…` : text;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function rawTextFromResult(result: any): string {
|
|
20
|
+
return Array.isArray(result?.content)
|
|
21
|
+
? result.content
|
|
22
|
+
.filter((item: any) => item?.type === "text")
|
|
23
|
+
.map((item: any) => String(item.text ?? ""))
|
|
24
|
+
.join("\n")
|
|
25
|
+
: "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function textFromResult(result: any, expanded = false): string {
|
|
29
|
+
const content = sanitizeToolResultText(rawTextFromResult(result), 16_384);
|
|
30
|
+
const detail = result?.details;
|
|
31
|
+
if (!expanded || detail === undefined) return content;
|
|
32
|
+
const details = sanitizeToolResultText(
|
|
33
|
+
typeof detail === "string" ? detail : safeJson(detail),
|
|
34
|
+
16_384,
|
|
35
|
+
);
|
|
36
|
+
if (!details || details === content) return content;
|
|
37
|
+
return content ? `${content}\nDetails:\n${details}` : details;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function safeJson(value: unknown): string {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.stringify(value, null, 2) ?? "";
|
|
43
|
+
} catch {
|
|
44
|
+
return String(value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function outputLineCount(result: any): number {
|
|
49
|
+
const text = rawTextFromResult(result).replace(/\r\n?/g, "\n").replace(/\n+$/, "");
|
|
50
|
+
return text ? text.split("\n").length : 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function countLines(text: string): number {
|
|
54
|
+
return text.trim() ? text.trim().split("\n").length : 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function formatToolInputArgs(args: unknown, maxChars = 8_000): string {
|
|
58
|
+
if (args === undefined || args === null) return "";
|
|
59
|
+
const rendered =
|
|
60
|
+
typeof args === "object" ? safeJson(args) : sanitizeToolResultText(String(args));
|
|
61
|
+
const bounded = sanitizeToolResultText(rendered, maxChars);
|
|
62
|
+
return rendered.length > maxChars ? `${bounded}…` : bounded;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function hasExpandableDetail(text: string, args: unknown): boolean {
|
|
66
|
+
return countLines(text) > 1 || formatToolInputArgs(args).trim().length > 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Small Component used for the viewer-local Input/Output card. */
|
|
70
|
+
class ExpandedToolIoView implements Component {
|
|
71
|
+
private readonly input: string;
|
|
72
|
+
private readonly output: string;
|
|
73
|
+
private readonly isError: boolean;
|
|
74
|
+
|
|
75
|
+
constructor(input: string, output: string, isError: boolean) {
|
|
76
|
+
this.input = input;
|
|
77
|
+
this.output = output || "Done";
|
|
78
|
+
this.isError = isError;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
render(width: number): string[] {
|
|
82
|
+
const safeWidth = toolViewportWidth(width);
|
|
83
|
+
const rows: string[] = [];
|
|
84
|
+
const addSection = (title: string, body: string, last: boolean) => {
|
|
85
|
+
rows.push(truncateToWidth(`${last ? "└" : "├"} ${title}`, safeWidth, ""));
|
|
86
|
+
const source = body.replace(/\t/g, " ").replace(/\n+$/, "") || "(empty)";
|
|
87
|
+
for (const line of source.split("\n")) {
|
|
88
|
+
for (const wrapped of wrapTextWithAnsi(line, Math.max(1, safeWidth - 3))) {
|
|
89
|
+
const prefix = last ? " " : "│ ";
|
|
90
|
+
rows.push(truncateToWidth(`${prefix}${wrapped}`, safeWidth, ""));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
if (this.input.trim()) {
|
|
95
|
+
addSection("Input", this.input, false);
|
|
96
|
+
addSection("Output", this.output, true);
|
|
97
|
+
} else {
|
|
98
|
+
addSection("Output", this.output, true);
|
|
99
|
+
}
|
|
100
|
+
return rows.map((row) => (this.isError ? row : row));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
invalidate(): void {}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function unavailableComponent(reason: string, theme: any): Component {
|
|
107
|
+
return {
|
|
108
|
+
render(width: number): string[] {
|
|
109
|
+
return [truncateToWidth(theme.fg("warning", `↳ diff unavailable: ${oneLine(reason, 512)}`), toolViewportWidth(width), "")];
|
|
110
|
+
},
|
|
111
|
+
invalidate() {},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function resultText(result: any): string {
|
|
116
|
+
return rawTextFromResult(result);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function persistedFromDetails(details: any): PersistedDiff | undefined {
|
|
120
|
+
if (!details || typeof details !== "object") return undefined;
|
|
121
|
+
const persisted: PersistedDiff = {};
|
|
122
|
+
if (typeof details.diff === "string") persisted.diff = details.diff;
|
|
123
|
+
if (typeof details.patch === "string") persisted.patch = details.patch;
|
|
124
|
+
if (typeof details.diffUnavailableReason === "string") {
|
|
125
|
+
persisted.diffUnavailableReason = details.diffUnavailableReason;
|
|
126
|
+
}
|
|
127
|
+
return Object.keys(persisted).length ? persisted : undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function viewerConfig(context: any): ViewerDiffConfig {
|
|
131
|
+
const candidate = context?.diffConfig;
|
|
132
|
+
if (!candidate || typeof candidate !== "object") return DEFAULT_TOOL_DISPLAY_CONFIG;
|
|
133
|
+
return {
|
|
134
|
+
...DEFAULT_TOOL_DISPLAY_CONFIG,
|
|
135
|
+
...candidate,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Rich edit/write rendering for the conversation viewer. Durable details are
|
|
141
|
+
* passed through before any live/ephemeral context so history is deterministic.
|
|
142
|
+
*/
|
|
143
|
+
export function renderRichToolResult(
|
|
144
|
+
toolName: string,
|
|
145
|
+
result: any,
|
|
146
|
+
options: any,
|
|
147
|
+
theme: any,
|
|
148
|
+
context: any,
|
|
149
|
+
): Component | undefined {
|
|
150
|
+
if (options?.isPartial || options?.isError || context?.isError) return undefined;
|
|
151
|
+
const expanded = options?.expanded === true || context?.expanded === true;
|
|
152
|
+
const args = context?.args ?? options?.args;
|
|
153
|
+
const filePath = args?.file_path ?? args?.path;
|
|
154
|
+
const details = result?.details;
|
|
155
|
+
const persistedDiff = context?.persistedDiff ?? persistedFromDetails(details);
|
|
156
|
+
|
|
157
|
+
if (toolName === "edit") {
|
|
158
|
+
return renderEditDiffResult(
|
|
159
|
+
details,
|
|
160
|
+
{ expanded, filePath, persistedDiff, isHovered: options?.isHovered, invalidate: context?.invalidate },
|
|
161
|
+
viewerConfig(context),
|
|
162
|
+
theme,
|
|
163
|
+
resultText(result),
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
if (toolName !== "write") return undefined;
|
|
167
|
+
if (persistedDiff?.diffUnavailableReason && !persistedDiff.diff && !persistedDiff.patch) {
|
|
168
|
+
return unavailableComponent(persistedDiff.diffUnavailableReason, theme);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const content = typeof args?.content === "string" ? args.content : undefined;
|
|
172
|
+
if (!content && !persistedDiff?.diff && !persistedDiff?.patch) {
|
|
173
|
+
return unavailableComponent("execution metadata is unavailable", theme);
|
|
174
|
+
}
|
|
175
|
+
return renderWriteDiffResult(
|
|
176
|
+
content,
|
|
177
|
+
{
|
|
178
|
+
expanded,
|
|
179
|
+
filePath,
|
|
180
|
+
persistedDiff,
|
|
181
|
+
previousContent: context?.previousContent,
|
|
182
|
+
fileExistedBeforeWrite: context?.fileExistedBeforeWrite,
|
|
183
|
+
isHovered: options?.isHovered,
|
|
184
|
+
invalidate: context?.invalidate,
|
|
185
|
+
},
|
|
186
|
+
viewerConfig(context),
|
|
187
|
+
theme,
|
|
188
|
+
resultText(result),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function renderCallSummary(toolName: string, args: any, theme: any): string {
|
|
193
|
+
const title = toolName.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ");
|
|
194
|
+
const value = args?.path ?? args?.file_path ?? args?.command ?? args?.query ?? args?.name;
|
|
195
|
+
const summary = value === undefined ? title : `${title} ${oneLine(value)}`;
|
|
196
|
+
return theme.fg("toolTitle", summary);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function settledIcon(context: any): string {
|
|
200
|
+
if (context?.isError) return "✗";
|
|
201
|
+
if (context?.isPartial || context?.executionStarted) return "●";
|
|
202
|
+
return "✓";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function renderFallbackResult(result: any, options: any, theme: any, context: any): Component {
|
|
206
|
+
const error = options?.isError || context?.isError;
|
|
207
|
+
const expanded = options?.expanded === true || context?.expanded === true;
|
|
208
|
+
const text = textFromResult(result, expanded);
|
|
209
|
+
if (expanded && (text.trim() || formatToolInputArgs(context?.args).trim())) {
|
|
210
|
+
return new ExpandedToolIoView(formatToolInputArgs(context?.args), text, Boolean(error));
|
|
211
|
+
}
|
|
212
|
+
const lines = text ? text.split("\n") : [];
|
|
213
|
+
const count = lines.length;
|
|
214
|
+
const summary = error ? oneLine(text) || "Failed" : count ? `${count} ${count === 1 ? "line" : "lines"} returned` : "Done";
|
|
215
|
+
const expandable = !expanded && hasExpandableDetail(text, context?.args);
|
|
216
|
+
return {
|
|
217
|
+
render(width: number): string[] {
|
|
218
|
+
const hint = expandable ? ` • ${showMoreHintText()}` : "";
|
|
219
|
+
return [truncateToWidth(theme.fg(error ? "error" : "muted", ` ↳ ${summary}${hint}`), toolViewportWidth(width), "")];
|
|
220
|
+
},
|
|
221
|
+
invalidate() {},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function createViewerCcstyleResult(
|
|
226
|
+
toolName: string,
|
|
227
|
+
result: any,
|
|
228
|
+
options: any,
|
|
229
|
+
theme: any,
|
|
230
|
+
context: any,
|
|
231
|
+
): Component {
|
|
232
|
+
return (
|
|
233
|
+
renderRichToolResult(toolName, result, options, theme, context) ??
|
|
234
|
+
renderFallbackResult(result, options, theme, context)
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export { ExpandedToolIoView };
|