paseo-plugin-chat-view 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/LICENSE.compact-agent-activity +21 -0
- package/LICENSE.mermaid +21 -0
- package/README.md +428 -2
- package/README_zh.md +429 -0
- package/client/activity/Activity.test.ts +220 -0
- package/client/activity/Activity.tsx +784 -0
- package/client/activity/detail/detail.test.ts +102 -0
- package/client/activity/detail/dispatch.tsx +94 -0
- package/client/activity/detail/file.tsx +93 -0
- package/client/activity/detail/index.tsx +39 -0
- package/client/activity/detail/parts.tsx +221 -0
- package/client/activity/detail/search.tsx +59 -0
- package/client/activity/detail/terminal.tsx +53 -0
- package/client/activity/detail/texts.tsx +42 -0
- package/client/activity/disclosure.ts +49 -0
- package/client/activity/dom.test.ts +52 -0
- package/client/activity/dom.ts +138 -0
- package/client/activity/group-store.test.ts +126 -0
- package/client/activity/group-store.ts +265 -0
- package/client/activity/highlight.test.ts +116 -0
- package/client/activity/highlight.ts +418 -0
- package/client/activity/history.test.ts +602 -0
- package/client/activity/history.ts +239 -0
- package/client/activity/index.ts +74 -0
- package/client/activity/latest-marker.test.ts +41 -0
- package/client/activity/latest-marker.ts +70 -0
- package/client/activity/styles.ts +535 -0
- package/client/activity/tools/child-agent.tsx +308 -0
- package/client/activity/tools/exa.tsx +99 -0
- package/client/activity/tools/github.tsx +501 -0
- package/client/activity/tools/list.tsx +102 -0
- package/client/activity/tools/lists.test.ts +193 -0
- package/client/activity/tools/paseo/agent.tsx +384 -0
- package/client/activity/tools/paseo/browser.tsx +322 -0
- package/client/activity/tools/paseo/index.tsx +79 -0
- package/client/activity/tools/paseo/parts.tsx +133 -0
- package/client/activity/tools/paseo/provider.tsx +220 -0
- package/client/activity/tools/paseo/schedule.tsx +246 -0
- package/client/activity/tools/paseo/speak.tsx +20 -0
- package/client/activity/tools/paseo/terminal.tsx +214 -0
- package/client/activity/tools/paseo/workspace.tsx +162 -0
- package/client/activity/tools/shared.tsx +431 -0
- package/client/activity/transform.test.ts +28 -0
- package/client/activity/transform.ts +43 -0
- package/client/agent-pills.ts +56 -0
- package/client/dom.ts +75 -0
- package/client/enhance/controller.ts +111 -0
- package/client/enhance/dom.test.ts +28 -0
- package/client/enhance/dom.ts +101 -0
- package/client/enhance/index.ts +95 -0
- package/client/enhance/pill.tsx +19 -0
- package/client/enhance/state.ts +58 -0
- package/client/feature.ts +22 -0
- package/client/features.test.ts +149 -0
- package/client/features.ts +83 -0
- package/client/locale.ts +45 -0
- package/client/mermaid/diagram.tsx +438 -0
- package/client/mermaid/index.ts +27 -0
- package/client/mermaid/item.tsx +28 -0
- package/client/mermaid/markdown.tsx +217 -0
- package/client/mermaid/registration.ts +32 -0
- package/client/mermaid/toolbar.tsx +175 -0
- package/client/mermaid/viewer.tsx +314 -0
- package/client/readonly/dom.test.ts +66 -0
- package/client/readonly/dom.ts +32 -0
- package/client/readonly/index.ts +27 -0
- package/client/registration.test.ts +243 -0
- package/client/settings.tsx +438 -0
- package/client/sticky/StickyBar.tsx +144 -0
- package/client/sticky/dom.test.ts +230 -0
- package/client/sticky/dom.ts +517 -0
- package/client/sticky/index.ts +38 -0
- package/client/sticky/pill.tsx +70 -0
- package/client/timeline-pager.test.ts +121 -0
- package/client/timeline-pager.ts +123 -0
- package/index.client.tsx +44 -0
- package/index.server.ts +12 -0
- package/package.json +42 -4
- package/paseo-plugin.json +6 -0
- package/server/enhance/index.ts +63 -0
- package/server/enhance/manifest.ts +176 -0
- package/server/enhance/probe.test.ts +215 -0
- package/server/enhance/probe.ts +118 -0
- package/server/enhance/provider.ts +149 -0
- package/shared/activity/child-agent.test.ts +27 -0
- package/shared/activity/child-agent.ts +26 -0
- package/shared/activity/diff.test.ts +74 -0
- package/shared/activity/diff.ts +207 -0
- package/shared/activity/exa.test.ts +66 -0
- package/shared/activity/exa.ts +183 -0
- package/shared/activity/file-kind.test.ts +13 -0
- package/shared/activity/file-kind.ts +112 -0
- package/shared/activity/github.test.ts +56 -0
- package/shared/activity/github.ts +174 -0
- package/shared/activity/palette.test.ts +26 -0
- package/shared/activity/palette.ts +106 -0
- package/shared/activity/parse.test.ts +39 -0
- package/shared/activity/parse.ts +115 -0
- package/shared/activity/paseo-tools.test.ts +163 -0
- package/shared/activity/paseo-tools.ts +497 -0
- package/shared/activity/text.test.ts +85 -0
- package/shared/activity/text.ts +202 -0
- package/shared/activity/timeline.test.ts +26 -0
- package/shared/activity/timeline.ts +108 -0
- package/shared/activity/tool-presentation.test.ts +154 -0
- package/shared/activity/tool-presentation.ts +352 -0
- package/shared/enhance/contract.ts +105 -0
- package/shared/enhance/decide.ts +97 -0
- package/shared/enhance/enhance.test.ts +188 -0
- package/shared/enhance/failure.ts +40 -0
- package/shared/enhance/index.ts +8 -0
- package/shared/enhance/normalize.ts +16 -0
- package/shared/enhance/parse.ts +79 -0
- package/shared/enhance/policy.ts +48 -0
- package/shared/enhance/prompt.ts +134 -0
- package/shared/enhance/run.ts +84 -0
- package/shared/i18n.test.ts +81 -0
- package/shared/i18n.ts +300 -0
- package/shared/mermaid/flowchart.test.ts +169 -0
- package/shared/mermaid/flowchart.ts +271 -0
- package/shared/mermaid/layout.test.ts +305 -0
- package/shared/mermaid/layout.ts +496 -0
- package/shared/mermaid/parse.test.ts +110 -0
- package/shared/mermaid/parse.ts +193 -0
- package/shared/mermaid/segment.test.ts +55 -0
- package/shared/mermaid/segment.ts +95 -0
- package/shared/mermaid/sequence.test.ts +68 -0
- package/shared/mermaid/sequence.ts +130 -0
- package/shared/mermaid/transform.test.ts +52 -0
- package/shared/mermaid/transform.ts +45 -0
- package/shared/mermaid/zoom.test.ts +134 -0
- package/shared/mermaid/zoom.ts +114 -0
- package/shared/settings.ts +41 -0
- package/shared/sticky/history.test.ts +91 -0
- package/shared/sticky/history.ts +86 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { JsonValue } from "@getpaseo/protocol/agent-types";
|
|
2
|
+
|
|
3
|
+
/** 文本预算(行数/字符数上限)、宽松 JSON 格式化,以及任意值转可读文本。 */
|
|
4
|
+
|
|
5
|
+
export function compactText(value: string, maxLength = 180): string | undefined {
|
|
6
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
7
|
+
if (!normalized) return undefined;
|
|
8
|
+
return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1)}…` : normalized;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const PREVIEW_LINES = 20;
|
|
12
|
+
|
|
13
|
+
export const PREVIEW_CHARS = 4_000;
|
|
14
|
+
|
|
15
|
+
export const MAX_FORMAT_CHARS = 100_000;
|
|
16
|
+
|
|
17
|
+
export interface TextPreview {
|
|
18
|
+
text: string;
|
|
19
|
+
truncated: boolean;
|
|
20
|
+
totalLines: number;
|
|
21
|
+
totalChars: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function previewText(
|
|
25
|
+
text: string,
|
|
26
|
+
maxLines = PREVIEW_LINES,
|
|
27
|
+
maxChars = PREVIEW_CHARS,
|
|
28
|
+
): TextPreview {
|
|
29
|
+
const lines = text.split("\n");
|
|
30
|
+
const totalLines = lines.length;
|
|
31
|
+
const totalChars = text.length;
|
|
32
|
+
|
|
33
|
+
if (totalLines <= maxLines && totalChars <= maxChars) {
|
|
34
|
+
return { text, truncated: false, totalLines, totalChars };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const candidate = text.slice(0, maxChars + 1);
|
|
38
|
+
const candidateLines = candidate.split("\n");
|
|
39
|
+
let preview = candidateLines.slice(0, maxLines).join("\n").slice(0, maxChars);
|
|
40
|
+
|
|
41
|
+
// Guard against splitting a UTF-16 surrogate pair
|
|
42
|
+
const last = preview.charCodeAt(preview.length - 1);
|
|
43
|
+
const next = text.charCodeAt(preview.length);
|
|
44
|
+
if (
|
|
45
|
+
preview.length < text.length &&
|
|
46
|
+
last >= 0xd800 &&
|
|
47
|
+
last <= 0xdbff &&
|
|
48
|
+
next >= 0xdc00 &&
|
|
49
|
+
next <= 0xdfff
|
|
50
|
+
) {
|
|
51
|
+
preview = preview.slice(0, -1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
text: preview,
|
|
56
|
+
truncated: preview.length < text.length,
|
|
57
|
+
totalLines,
|
|
58
|
+
totalChars,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function looksLikeJson(source: string): boolean {
|
|
63
|
+
return /^\s*[[{"]/.test(source.slice(0, 256));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface JsonFormat {
|
|
67
|
+
text: string | null;
|
|
68
|
+
limited: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Change whitespace only. Preserve large numbers, key order, duplicate keys and escapes. */
|
|
72
|
+
|
|
73
|
+
export function formatJson(source: string): JsonFormat {
|
|
74
|
+
if (source.length > MAX_FORMAT_CHARS || !source.trim()) {
|
|
75
|
+
return { text: null, limited: source.length > MAX_FORMAT_CHARS && looksLikeJson(source) };
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
JSON.parse(source);
|
|
79
|
+
} catch {
|
|
80
|
+
return { text: null, limited: false };
|
|
81
|
+
}
|
|
82
|
+
const tokens = source.match(/"(?:\\[\s\S]|[^"\\])*"|[{}[\],:]|[^\s{}[\],:]+/g) ?? [];
|
|
83
|
+
let depth = 0;
|
|
84
|
+
const out: string[] = [];
|
|
85
|
+
let length = 0;
|
|
86
|
+
const push = (value: string) => {
|
|
87
|
+
out.push(value);
|
|
88
|
+
length += value.length;
|
|
89
|
+
};
|
|
90
|
+
const line = () => push("\n" + " ".repeat(Math.min(depth, 40)));
|
|
91
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
92
|
+
const token = tokens[index];
|
|
93
|
+
if (token === "{" || token === "[") {
|
|
94
|
+
push(token);
|
|
95
|
+
depth += 1;
|
|
96
|
+
if (tokens[index + 1] !== "}" && tokens[index + 1] !== "]") line();
|
|
97
|
+
} else if (token === "}" || token === "]") {
|
|
98
|
+
depth -= 1;
|
|
99
|
+
if (tokens[index - 1] !== "{" && tokens[index - 1] !== "[") line();
|
|
100
|
+
push(token);
|
|
101
|
+
} else if (token === ",") {
|
|
102
|
+
push(token);
|
|
103
|
+
line();
|
|
104
|
+
} else if (token === ":") {
|
|
105
|
+
push(": ");
|
|
106
|
+
} else {
|
|
107
|
+
push(token);
|
|
108
|
+
}
|
|
109
|
+
if (length > MAX_FORMAT_CHARS) return { text: null, limited: true };
|
|
110
|
+
}
|
|
111
|
+
return { text: out.join(""), limited: false };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function prettyJson(source: string): string | null {
|
|
115
|
+
return formatJson(source).text;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function extractCodeInput(
|
|
119
|
+
toolName: string,
|
|
120
|
+
value: unknown,
|
|
121
|
+
): { code: string; language: string } | undefined {
|
|
122
|
+
let decoded = value;
|
|
123
|
+
if (typeof value === "string" && value.length <= 1_000_000) {
|
|
124
|
+
try {
|
|
125
|
+
decoded = JSON.parse(value);
|
|
126
|
+
} catch {
|
|
127
|
+
// may be literal JS
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const record =
|
|
131
|
+
decoded !== null && typeof decoded === "object" && !Array.isArray(decoded)
|
|
132
|
+
? (decoded as Record<string, unknown>)
|
|
133
|
+
: undefined;
|
|
134
|
+
const name = toolName
|
|
135
|
+
.trim()
|
|
136
|
+
.toLowerCase()
|
|
137
|
+
.replace(/^(?:functions|tools)\./, "")
|
|
138
|
+
.replace(/^mcp__.*?__/, "")
|
|
139
|
+
.replace(/^mcp_/, "");
|
|
140
|
+
|
|
141
|
+
const codeTool = ["exec", "mcpscript", "mcp_script", "run_script"].includes(name);
|
|
142
|
+
if (codeTool) {
|
|
143
|
+
const code =
|
|
144
|
+
record?.code ??
|
|
145
|
+
(typeof decoded === "string" && !decoded.slice(0, 256).trimStart().startsWith("{")
|
|
146
|
+
? decoded
|
|
147
|
+
: undefined);
|
|
148
|
+
if (typeof code === "string") {
|
|
149
|
+
return { code, language: "javascript" };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (name === "evaluate_browser" || name === "browser_evaluate") {
|
|
154
|
+
const code = record?.expression ?? record?.code;
|
|
155
|
+
if (typeof code === "string") {
|
|
156
|
+
return { code, language: "javascript" };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function toJsonValue(value: unknown): JsonValue {
|
|
164
|
+
try {
|
|
165
|
+
const serialized = JSON.stringify(value);
|
|
166
|
+
return serialized === undefined ? null : (JSON.parse(serialized) as JsonValue);
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function formatUnknownValue(value: unknown): string {
|
|
173
|
+
if (typeof value === "string") {
|
|
174
|
+
const formatted = prettyJson(value);
|
|
175
|
+
return formatted ?? value;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const raw = JSON.stringify(value);
|
|
179
|
+
if (raw === undefined) return String(value);
|
|
180
|
+
const formatted = prettyJson(raw);
|
|
181
|
+
return formatted ?? JSON.stringify(value, null, 2) ?? String(value);
|
|
182
|
+
} catch {
|
|
183
|
+
return String(value);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function formatError(error: unknown): string | undefined {
|
|
188
|
+
if (error === null || error === undefined) return undefined;
|
|
189
|
+
const text = typeof error === "string" ? error : formatUnknownValue(error);
|
|
190
|
+
return compactText(text, 400);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function formatReasoningText(text: string): string {
|
|
194
|
+
if (!text) return "";
|
|
195
|
+
const parts = text.split(/(```[\s\S]*?(?:```|$)|`[^`\n]+`)/g);
|
|
196
|
+
return parts
|
|
197
|
+
.map((part, index) => {
|
|
198
|
+
if (index % 2 === 1) return part;
|
|
199
|
+
return part.replace(/(\*\*[^*\s\n](?:[^*\n]*?[^*\s\n])?\*\*)\s*(?=\*\*)/g, "$1\n\n");
|
|
200
|
+
})
|
|
201
|
+
.join("");
|
|
202
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { getActivityExpansionState, getReasoningExpansionState } from "./timeline";
|
|
3
|
+
|
|
4
|
+
describe("reasoning expansion", () => {
|
|
5
|
+
it("keeps the latest completed thinking block open", () => {
|
|
6
|
+
expect(getReasoningExpansionState(false, true, null)).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("collapses an older block when a newer block exists", () => {
|
|
10
|
+
expect(getReasoningExpansionState(false, false, null)).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("keeps streaming reasoning open regardless of tool activity", () => {
|
|
14
|
+
expect(getReasoningExpansionState(true, false, false)).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("respects an explicit user toggle", () => {
|
|
18
|
+
expect(getReasoningExpansionState(false, true, false)).toBe(false);
|
|
19
|
+
expect(getReasoningExpansionState(false, false, true)).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("uses the same latest-item rule for tool calls", () => {
|
|
23
|
+
expect(getActivityExpansionState(false, true, null)).toBe(true);
|
|
24
|
+
expect(getActivityExpansionState(false, false, null)).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { AgentTimelineItem } from "@getpaseo/protocol/agent-types";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { formatError, formatReasoningText, toJsonValue } from "./text";
|
|
4
|
+
import { resolveToolCallPresentation } from "./tool-presentation";
|
|
5
|
+
|
|
6
|
+
export const REASONING_RENDERER_KIND = "colorful-reasoning";
|
|
7
|
+
export const TOOL_CALL_RENDERER_KIND = "colorful-tool-call";
|
|
8
|
+
export const REASONING_RENDERER_VERSION = 1;
|
|
9
|
+
export const TOOL_CALL_RENDERER_VERSION = 1;
|
|
10
|
+
|
|
11
|
+
export const reasoningItemDataSchema = z.object({
|
|
12
|
+
callId: z.string().optional(),
|
|
13
|
+
turnIndex: z.number().int().optional(),
|
|
14
|
+
turnId: z.string().optional(),
|
|
15
|
+
text: z.string(),
|
|
16
|
+
phase: z.enum(["streaming", "complete"]),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const diffStatsSchema = z.object({
|
|
20
|
+
additions: z.number().int().nonnegative(),
|
|
21
|
+
deletions: z.number().int().nonnegative(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const toolCallPresentationSchema = z.object({
|
|
25
|
+
category: z.enum(["shell", "file", "search", "agent", "plan", "communication", "unknown"]),
|
|
26
|
+
icon: z.string(),
|
|
27
|
+
label: z.string(),
|
|
28
|
+
summary: z.string().optional(),
|
|
29
|
+
filePath: z.string().optional(),
|
|
30
|
+
fileIcon: z.string().optional(),
|
|
31
|
+
language: z.string().optional(),
|
|
32
|
+
diffStats: diffStatsSchema.optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const toolCallItemDataSchema = z.object({
|
|
36
|
+
callId: z.string().optional(),
|
|
37
|
+
turnIndex: z.number().int().optional(),
|
|
38
|
+
turnId: z.string().optional(),
|
|
39
|
+
name: z.string(),
|
|
40
|
+
status: z.enum(["running", "completed", "failed", "canceled"]),
|
|
41
|
+
detail: z.json(),
|
|
42
|
+
errorText: z.string().optional(),
|
|
43
|
+
presentation: toolCallPresentationSchema,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export type ReasoningItemData = z.output<typeof reasoningItemDataSchema>;
|
|
47
|
+
export type ToolCallItemData = z.output<typeof toolCallItemDataSchema>;
|
|
48
|
+
|
|
49
|
+
export function getActivityExpansionState(
|
|
50
|
+
isStreaming: boolean,
|
|
51
|
+
isLatest: boolean,
|
|
52
|
+
userExpanded: boolean | null,
|
|
53
|
+
): boolean {
|
|
54
|
+
return isStreaming || (userExpanded !== null ? userExpanded : isLatest);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getReasoningExpansionState(
|
|
58
|
+
isStreaming: boolean,
|
|
59
|
+
isLatest: boolean,
|
|
60
|
+
userExpanded: boolean | null,
|
|
61
|
+
): boolean {
|
|
62
|
+
return getActivityExpansionState(isStreaming, isLatest, userExpanded);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createReasoningData(
|
|
66
|
+
item: Extract<AgentTimelineItem, { type: "reasoning" }>,
|
|
67
|
+
phase: ReasoningItemData["phase"],
|
|
68
|
+
turnIndex?: number,
|
|
69
|
+
callId?: string,
|
|
70
|
+
turnId?: string,
|
|
71
|
+
): ReasoningItemData {
|
|
72
|
+
return {
|
|
73
|
+
...(callId ? { callId } : {}),
|
|
74
|
+
...(turnIndex !== undefined ? { turnIndex } : {}),
|
|
75
|
+
...(turnId ? { turnId } : {}),
|
|
76
|
+
text: formatReasoningText(item.text),
|
|
77
|
+
phase,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createToolCallData(
|
|
82
|
+
item: Extract<AgentTimelineItem, { type: "tool_call" }>,
|
|
83
|
+
turnIndex?: number,
|
|
84
|
+
callId?: string,
|
|
85
|
+
turnId?: string,
|
|
86
|
+
): ToolCallItemData {
|
|
87
|
+
const presentation = resolveToolCallPresentation(item);
|
|
88
|
+
const errorText = formatError(item.error);
|
|
89
|
+
return {
|
|
90
|
+
callId: callId ?? item.callId,
|
|
91
|
+
...(turnIndex !== undefined ? { turnIndex } : {}),
|
|
92
|
+
...(turnId ? { turnId } : {}),
|
|
93
|
+
name: item.name,
|
|
94
|
+
status: item.status,
|
|
95
|
+
detail: toJsonValue(item.detail),
|
|
96
|
+
...(errorText ? { errorText } : {}),
|
|
97
|
+
presentation: {
|
|
98
|
+
category: presentation.category,
|
|
99
|
+
icon: presentation.icon,
|
|
100
|
+
label: presentation.label,
|
|
101
|
+
...(presentation.summary ? { summary: presentation.summary } : {}),
|
|
102
|
+
...(presentation.filePath ? { filePath: presentation.filePath } : {}),
|
|
103
|
+
...(presentation.fileIcon ? { fileIcon: presentation.fileIcon } : {}),
|
|
104
|
+
...(presentation.language ? { language: presentation.language } : {}),
|
|
105
|
+
...(presentation.diffStats ? { diffStats: presentation.diffStats } : {}),
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
parseSubAgentActionLog,
|
|
4
|
+
paseoToolCategory,
|
|
5
|
+
paseoToolIcon,
|
|
6
|
+
paseoToolLabel,
|
|
7
|
+
paseoToolResult,
|
|
8
|
+
paseoToolSummary,
|
|
9
|
+
resolveSubAgentActionPresentation,
|
|
10
|
+
resolveToolCallPresentation,
|
|
11
|
+
unwrapPaseoToolOutput,
|
|
12
|
+
} from "./tool-presentation";
|
|
13
|
+
import { paseoToolLeafName } from "./paseo-tools";
|
|
14
|
+
|
|
15
|
+
describe("tool call presentation", () => {
|
|
16
|
+
it("maps known tool details to screenshot-style presentation", () => {
|
|
17
|
+
expect(
|
|
18
|
+
resolveToolCallPresentation({
|
|
19
|
+
name: "edit",
|
|
20
|
+
detail: { type: "edit", filePath: "src/web/main.tsx", oldString: "", newString: "x" },
|
|
21
|
+
}),
|
|
22
|
+
).toMatchObject({
|
|
23
|
+
category: "file",
|
|
24
|
+
icon: "FileCode2",
|
|
25
|
+
label: "Edit File",
|
|
26
|
+
summary: "src/web/main.tsx",
|
|
27
|
+
language: "typescript",
|
|
28
|
+
diffStats: { additions: 1, deletions: 0 },
|
|
29
|
+
});
|
|
30
|
+
expect(
|
|
31
|
+
resolveToolCallPresentation({
|
|
32
|
+
name: "bash",
|
|
33
|
+
detail: { type: "shell", command: "bun run typecheck && bun test" },
|
|
34
|
+
}),
|
|
35
|
+
).toEqual({
|
|
36
|
+
category: "shell",
|
|
37
|
+
icon: "SquareTerminal",
|
|
38
|
+
label: "Shell Command",
|
|
39
|
+
summary: "bun run typecheck && bun test",
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("maps sub-agent actions to readable inline progress rows", () => {
|
|
44
|
+
expect(resolveSubAgentActionPresentation("read", "README.md")).toEqual({
|
|
45
|
+
icon: "FileText",
|
|
46
|
+
label: "Read File",
|
|
47
|
+
summaryIcon: "FileText",
|
|
48
|
+
});
|
|
49
|
+
expect(resolveSubAgentActionPresentation("find_files")).toEqual({
|
|
50
|
+
icon: "Search",
|
|
51
|
+
label: "Find Files",
|
|
52
|
+
});
|
|
53
|
+
expect(resolveSubAgentActionPresentation("shell", "git status")).toEqual({
|
|
54
|
+
icon: "SquareTerminal",
|
|
55
|
+
label: "Shell Command",
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("recovers Claude-style sub-agent actions from the provider log", () => {
|
|
60
|
+
expect(parseSubAgentActionLog("[Read] README.md\n[Shell] git status\nnot an action")).toEqual([
|
|
61
|
+
{ index: 0, toolName: "Read", summary: "README.md" },
|
|
62
|
+
{ index: 1, toolName: "Shell", summary: "git status" },
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("gives namespaced Paseo tools a specialized title, icon, and summary", () => {
|
|
67
|
+
const input = {
|
|
68
|
+
title: "Random Number Agent 3",
|
|
69
|
+
provider: "pi/plexus/gpt-5.6-luna",
|
|
70
|
+
};
|
|
71
|
+
expect(paseoToolLabel("mcp__paseo__create_agent")).toBe("Create Agent");
|
|
72
|
+
expect(paseoToolLabel("mcp_paseo_create_agent")).toBe("Create Agent");
|
|
73
|
+
expect(paseoToolIcon("paseo.create_agent")).toBe("Bot");
|
|
74
|
+
expect(paseoToolLeafName("paseo_create_agent")).toBe("create_agent");
|
|
75
|
+
expect(paseoToolLeafName("mcp_paseo_create_agent")).toBe("create_agent");
|
|
76
|
+
expect(paseoToolLeafName("mcp__paseo__future_tool")).toBeNull();
|
|
77
|
+
expect(paseoToolCategory("paseo_remote.create_agent")).toBe("agent");
|
|
78
|
+
expect(paseoToolSummary("mcp__paseo__create_agent", input)).toBe(
|
|
79
|
+
"Random Number Agent 3 · pi/plexus/gpt-5.6-luna",
|
|
80
|
+
);
|
|
81
|
+
expect(paseoToolLabel("mcp__github__create_issue")).toBeNull();
|
|
82
|
+
expect(
|
|
83
|
+
resolveToolCallPresentation({
|
|
84
|
+
name: "mcp_paseo_create_agent",
|
|
85
|
+
detail: { type: "unknown", input, output: { agentId: "agt_123" } },
|
|
86
|
+
}),
|
|
87
|
+
).toMatchObject({
|
|
88
|
+
category: "agent",
|
|
89
|
+
icon: "Bot",
|
|
90
|
+
label: "Paseo Create Agent",
|
|
91
|
+
summary: "Random Number Agent 3 · pi/plexus/gpt-5.6-luna",
|
|
92
|
+
});
|
|
93
|
+
expect(
|
|
94
|
+
resolveToolCallPresentation({
|
|
95
|
+
name: "mcp__github__search_repositories",
|
|
96
|
+
detail: { type: "unknown", input: { query: "paseo" }, output: {} },
|
|
97
|
+
}),
|
|
98
|
+
).toMatchObject({
|
|
99
|
+
category: "search",
|
|
100
|
+
icon: "BookMarked",
|
|
101
|
+
label: "GitHub Repository Search",
|
|
102
|
+
summary: "paseo",
|
|
103
|
+
});
|
|
104
|
+
expect(
|
|
105
|
+
resolveToolCallPresentation({
|
|
106
|
+
name: "mcp__paseo__create_agent",
|
|
107
|
+
detail: { type: "unknown", input, output: { agentId: "agt_123" } },
|
|
108
|
+
}),
|
|
109
|
+
).toMatchObject({
|
|
110
|
+
category: "agent",
|
|
111
|
+
icon: "Bot",
|
|
112
|
+
label: "Paseo Create Agent",
|
|
113
|
+
summary: "Random Number Agent 3 · pi/plexus/gpt-5.6-luna",
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("unwraps MCP text envelopes with diagnostic prefixes", () => {
|
|
118
|
+
const output = {
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: "text",
|
|
122
|
+
text: 'availableModes_count=0\\n\\n{"agentId":"agt_123","status":"running"}',
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
expect(unwrapPaseoToolOutput(output)).toEqual({
|
|
127
|
+
agentId: "agt_123",
|
|
128
|
+
status: "running",
|
|
129
|
+
});
|
|
130
|
+
expect(paseoToolResult({ ok: true, result: { browserId: "tab-1" } })).toEqual({
|
|
131
|
+
browserId: "tab-1",
|
|
132
|
+
});
|
|
133
|
+
expect(
|
|
134
|
+
paseoToolResult({
|
|
135
|
+
ok: false,
|
|
136
|
+
error: { code: "browser_timeout", message: "Timed out" },
|
|
137
|
+
}),
|
|
138
|
+
).toEqual({
|
|
139
|
+
ok: false,
|
|
140
|
+
error: { code: "browser_timeout", message: "Timed out" },
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("uses valid Activity icon for get_agent_activity", () => {
|
|
145
|
+
expect(paseoToolIcon("paseo_get_agent_activity")).toBe("Activity");
|
|
146
|
+
expect(paseoToolIcon("mcp_paseo_get_agent_activity")).toBe("Activity");
|
|
147
|
+
expect(
|
|
148
|
+
resolveToolCallPresentation({
|
|
149
|
+
name: "mcp_paseo_get_agent_activity",
|
|
150
|
+
detail: { type: "unknown", input: { agentId: "agt_1" }, output: {} },
|
|
151
|
+
}).icon,
|
|
152
|
+
).toBe("Activity");
|
|
153
|
+
});
|
|
154
|
+
});
|