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,352 @@
|
|
|
1
|
+
import type { ToolCallDetail, ToolCallTimelineItem } from "@getpaseo/protocol/agent-types";
|
|
2
|
+
import { MAX_DIFF_CHARS, diffStatsForDetail, type DiffStats } from "./diff";
|
|
3
|
+
import { exaToolIcon, exaToolKind, exaToolLabel, exaToolSummary } from "./exa";
|
|
4
|
+
import { fileIconForPath, languageForFilePath } from "./file-kind";
|
|
5
|
+
import { githubToolIcon, githubToolKind, githubToolLabel, githubToolSummary } from "./github";
|
|
6
|
+
import { prettyToolName, resolvePaseoToolRow, type ToolCategory } from "./paseo-tools";
|
|
7
|
+
import { compactText } from "./text";
|
|
8
|
+
|
|
9
|
+
/** 时间线条目 → 标题、图标、分类、摘要;也负责子代理动作行与 MCP 信封解包。 */
|
|
10
|
+
|
|
11
|
+
export interface ToolCallPresentation {
|
|
12
|
+
category: ToolCategory;
|
|
13
|
+
icon: string;
|
|
14
|
+
label: string;
|
|
15
|
+
summary?: string;
|
|
16
|
+
filePath?: string;
|
|
17
|
+
fileIcon?: string;
|
|
18
|
+
language?: string;
|
|
19
|
+
diffStats?: DiffStats;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SubAgentActionPresentation {
|
|
23
|
+
icon: string;
|
|
24
|
+
label: string;
|
|
25
|
+
summaryIcon?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SubAgentAction {
|
|
29
|
+
index: number;
|
|
30
|
+
toolName: string;
|
|
31
|
+
summary?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const TOOL_ICON_NAMES: Record<string, string> = {
|
|
35
|
+
bot: "Bot",
|
|
36
|
+
brain: "Brain",
|
|
37
|
+
eye: "Eye",
|
|
38
|
+
mic_vocal: "MicVocal",
|
|
39
|
+
pencil: "Pencil",
|
|
40
|
+
paseo: "Sparkles",
|
|
41
|
+
search: "Search",
|
|
42
|
+
sparkles: "Sparkles",
|
|
43
|
+
square_terminal: "SquareTerminal",
|
|
44
|
+
wrench: "Wrench",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function iconNameFromProtocol(value: string | undefined): string | undefined {
|
|
48
|
+
if (!value) return undefined;
|
|
49
|
+
return TOOL_ICON_NAMES[value] ?? value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
53
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function stringField(value: unknown, key: string): string | undefined {
|
|
57
|
+
if (!isRecord(value)) return undefined;
|
|
58
|
+
const field = value[key];
|
|
59
|
+
return typeof field === "string" && field.trim() ? field : undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function paseoToolLabel(toolName: string): string | null {
|
|
63
|
+
return resolvePaseoToolRow(toolName)?.row.label ?? null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function paseoToolIcon(toolName: string): string | null {
|
|
67
|
+
return resolvePaseoToolRow(toolName)?.row.icon ?? null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function paseoToolCategory(toolName: string): ToolCategory | null {
|
|
71
|
+
return resolvePaseoToolRow(toolName)?.row.category ?? null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function summaryFieldValue(input: unknown, field: string): string | undefined {
|
|
75
|
+
if (field === "prompt")
|
|
76
|
+
return stringField(input, "prompt") ?? stringField(input, "initialPrompt");
|
|
77
|
+
return stringField(input, field);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function paseoToolSummary(toolName: string, input: unknown): string | undefined {
|
|
81
|
+
const row = resolvePaseoToolRow(toolName)?.row;
|
|
82
|
+
if (!row?.summary) return undefined;
|
|
83
|
+
for (const group of row.summary) {
|
|
84
|
+
const values = group.map((field) => summaryFieldValue(input, field));
|
|
85
|
+
if (values.some((value) => value === undefined)) continue;
|
|
86
|
+
const joined = values.join(" · ");
|
|
87
|
+
return row.compactSummary ? compactText(joined) : joined;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseEmbeddedJson(value: string): unknown {
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(value) as unknown;
|
|
95
|
+
} catch {
|
|
96
|
+
for (const match of value.matchAll(/\{|\[/g)) {
|
|
97
|
+
const offset = match.index;
|
|
98
|
+
if (offset === undefined) continue;
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(value.slice(offset)) as unknown;
|
|
101
|
+
} catch {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function unwrapPaseoToolOutput(value: unknown): unknown {
|
|
110
|
+
const record = isRecord(value) ? value : null;
|
|
111
|
+
if (!record) return value;
|
|
112
|
+
if (record.structuredContent !== undefined)
|
|
113
|
+
return unwrapPaseoToolOutput(record.structuredContent);
|
|
114
|
+
if (Array.isArray(record.content)) {
|
|
115
|
+
const content = record.content.find((item) => isRecord(item) && item.type === "text");
|
|
116
|
+
if (isRecord(content)) {
|
|
117
|
+
const parsed = typeof content.text === "string" ? parseEmbeddedJson(content.text) : undefined;
|
|
118
|
+
return parsed === undefined ? content.text : unwrapPaseoToolOutput(parsed);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function paseoToolResult(value: unknown): unknown {
|
|
125
|
+
const unwrapped = unwrapPaseoToolOutput(value);
|
|
126
|
+
const record = isRecord(unwrapped) ? unwrapped : null;
|
|
127
|
+
return record?.ok === true && record.result !== undefined ? record.result : unwrapped;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function shellSummary(detail: Extract<ToolCallDetail, { type: "shell" }>): string | undefined {
|
|
131
|
+
return compactText(detail.command);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function detailFilePath(detail: ToolCallDetail): string | undefined {
|
|
135
|
+
if (detail.type === "read" || detail.type === "edit" || detail.type === "write") {
|
|
136
|
+
return detail.filePath || undefined;
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function resolveToolCallPresentation(
|
|
142
|
+
item: Pick<ToolCallTimelineItem, "name" | "detail">,
|
|
143
|
+
): ToolCallPresentation {
|
|
144
|
+
const name = item.name.trim().toLowerCase();
|
|
145
|
+
const detail = item.detail;
|
|
146
|
+
const filePath = detailFilePath(detail);
|
|
147
|
+
const commonFileFields = filePath
|
|
148
|
+
? {
|
|
149
|
+
filePath,
|
|
150
|
+
fileIcon: fileIconForPath(filePath),
|
|
151
|
+
language: languageForFilePath(filePath),
|
|
152
|
+
}
|
|
153
|
+
: {};
|
|
154
|
+
|
|
155
|
+
switch (detail.type) {
|
|
156
|
+
case "shell":
|
|
157
|
+
return {
|
|
158
|
+
category: "shell",
|
|
159
|
+
icon: "SquareTerminal",
|
|
160
|
+
label: "Shell Command",
|
|
161
|
+
summary: shellSummary(detail),
|
|
162
|
+
};
|
|
163
|
+
case "worktree_setup":
|
|
164
|
+
return {
|
|
165
|
+
category: "shell",
|
|
166
|
+
icon: "GitBranch",
|
|
167
|
+
label: "Worktree Setup",
|
|
168
|
+
summary: compactText(detail.branchName || detail.worktreePath),
|
|
169
|
+
};
|
|
170
|
+
case "read":
|
|
171
|
+
return {
|
|
172
|
+
category: "file",
|
|
173
|
+
icon: commonFileFields.fileIcon ?? "Eye",
|
|
174
|
+
label: "Read File",
|
|
175
|
+
summary: compactText(detail.filePath),
|
|
176
|
+
...commonFileFields,
|
|
177
|
+
};
|
|
178
|
+
case "edit": {
|
|
179
|
+
const editSize =
|
|
180
|
+
detail.unifiedDiff?.length ??
|
|
181
|
+
(detail.oldString?.length ?? 0) + (detail.newString?.length ?? 0);
|
|
182
|
+
return {
|
|
183
|
+
category: "file",
|
|
184
|
+
icon: commonFileFields.fileIcon ?? "Pencil",
|
|
185
|
+
label: "Edit File",
|
|
186
|
+
summary: compactText(detail.filePath),
|
|
187
|
+
...(editSize <= MAX_DIFF_CHARS ? { diffStats: diffStatsForDetail(detail) } : {}),
|
|
188
|
+
...commonFileFields,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
case "write":
|
|
192
|
+
return {
|
|
193
|
+
category: "file",
|
|
194
|
+
icon: commonFileFields.fileIcon ?? "Pencil",
|
|
195
|
+
label: "Write File",
|
|
196
|
+
summary: compactText(detail.filePath),
|
|
197
|
+
...commonFileFields,
|
|
198
|
+
};
|
|
199
|
+
case "search":
|
|
200
|
+
return {
|
|
201
|
+
category: "search",
|
|
202
|
+
icon: "Search",
|
|
203
|
+
label: "Search",
|
|
204
|
+
summary: compactText(detail.query),
|
|
205
|
+
};
|
|
206
|
+
case "fetch":
|
|
207
|
+
return {
|
|
208
|
+
category: "search",
|
|
209
|
+
icon: "Globe",
|
|
210
|
+
label: "Fetch URL",
|
|
211
|
+
summary: compactText(detail.url),
|
|
212
|
+
};
|
|
213
|
+
case "sub_agent":
|
|
214
|
+
return {
|
|
215
|
+
category: "agent",
|
|
216
|
+
icon: "Bot",
|
|
217
|
+
label: "Agent Task",
|
|
218
|
+
summary: detail.description
|
|
219
|
+
? compactText(detail.description)
|
|
220
|
+
: compactText(detail.subAgentType ?? ""),
|
|
221
|
+
};
|
|
222
|
+
case "plain_text":
|
|
223
|
+
return {
|
|
224
|
+
category: "communication",
|
|
225
|
+
icon: iconNameFromProtocol(detail.icon) ?? "Wrench",
|
|
226
|
+
label: detail.label || prettyToolName(item.name),
|
|
227
|
+
summary: detail.text ? compactText(detail.text) : undefined,
|
|
228
|
+
};
|
|
229
|
+
case "plan":
|
|
230
|
+
return {
|
|
231
|
+
category: "plan",
|
|
232
|
+
icon: "ListChecks",
|
|
233
|
+
label: "Plan",
|
|
234
|
+
};
|
|
235
|
+
case "unknown": {
|
|
236
|
+
if (name === "thinking") {
|
|
237
|
+
return { category: "plan", icon: "Brain", label: "Thinking" };
|
|
238
|
+
}
|
|
239
|
+
if (name === "task") {
|
|
240
|
+
return { category: "agent", icon: "Bot", label: "Task", summary: compactText(item.name) };
|
|
241
|
+
}
|
|
242
|
+
if (name === "speak") {
|
|
243
|
+
return { category: "communication", icon: "MicVocal", label: "Speak" };
|
|
244
|
+
}
|
|
245
|
+
const githubKind = githubToolKind(item.name);
|
|
246
|
+
if (githubKind) {
|
|
247
|
+
return {
|
|
248
|
+
category:
|
|
249
|
+
githubKind === "pull-request" || githubKind === "actions-run" ? "agent" : "search",
|
|
250
|
+
icon: githubToolIcon(githubKind),
|
|
251
|
+
label: githubToolLabel(githubKind),
|
|
252
|
+
summary: githubToolSummary(githubKind, detail.input),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const exaKind = exaToolKind(item.name);
|
|
256
|
+
if (exaKind) {
|
|
257
|
+
return {
|
|
258
|
+
category: exaKind === "agent" ? "agent" : "search",
|
|
259
|
+
icon: exaToolIcon(exaKind),
|
|
260
|
+
label: exaToolLabel(exaKind),
|
|
261
|
+
summary: exaToolSummary(exaKind, detail.input),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const paseoLabel = paseoToolLabel(item.name);
|
|
265
|
+
if (paseoLabel) {
|
|
266
|
+
return {
|
|
267
|
+
category: paseoToolCategory(item.name) ?? "unknown",
|
|
268
|
+
icon: paseoToolIcon(item.name) ?? "Sparkles",
|
|
269
|
+
label: `Paseo ${paseoLabel}`,
|
|
270
|
+
summary: paseoToolSummary(item.name, detail.input),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
category: "unknown",
|
|
275
|
+
icon: "Wrench",
|
|
276
|
+
label: prettyToolName(item.name),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function resolveSubAgentActionPresentation(
|
|
283
|
+
toolName: string,
|
|
284
|
+
summary?: string,
|
|
285
|
+
): SubAgentActionPresentation {
|
|
286
|
+
const normalized = toolName
|
|
287
|
+
.trim()
|
|
288
|
+
.toLowerCase()
|
|
289
|
+
.replace(/[\s.-]+/g, "_");
|
|
290
|
+
if (
|
|
291
|
+
normalized === "read" ||
|
|
292
|
+
normalized.includes("read_file") ||
|
|
293
|
+
normalized.includes("readfile")
|
|
294
|
+
) {
|
|
295
|
+
return { icon: "FileText", label: "Read File", summaryIcon: fileIconForPath(summary) };
|
|
296
|
+
}
|
|
297
|
+
if (
|
|
298
|
+
normalized === "glob" ||
|
|
299
|
+
normalized === "find" ||
|
|
300
|
+
normalized.includes("find_file") ||
|
|
301
|
+
normalized.includes("list_file")
|
|
302
|
+
) {
|
|
303
|
+
return { icon: "Search", label: "Find Files" };
|
|
304
|
+
}
|
|
305
|
+
if (normalized === "grep" || normalized === "search" || normalized.includes("search")) {
|
|
306
|
+
return { icon: "Search", label: "Search" };
|
|
307
|
+
}
|
|
308
|
+
if (
|
|
309
|
+
normalized === "bash" ||
|
|
310
|
+
normalized === "sh" ||
|
|
311
|
+
normalized === "run" ||
|
|
312
|
+
normalized.includes("shell") ||
|
|
313
|
+
normalized.includes("command")
|
|
314
|
+
) {
|
|
315
|
+
return { icon: "SquareTerminal", label: "Shell Command" };
|
|
316
|
+
}
|
|
317
|
+
if (normalized === "edit" || normalized.includes("edit_file") || normalized.includes("patch")) {
|
|
318
|
+
return { icon: "Pencil", label: "Edit File", summaryIcon: fileIconForPath(summary) };
|
|
319
|
+
}
|
|
320
|
+
if (
|
|
321
|
+
normalized === "write" ||
|
|
322
|
+
normalized.includes("write_file") ||
|
|
323
|
+
normalized.includes("writefile")
|
|
324
|
+
) {
|
|
325
|
+
return { icon: "Pencil", label: "Write File", summaryIcon: fileIconForPath(summary) };
|
|
326
|
+
}
|
|
327
|
+
if (
|
|
328
|
+
normalized === "task" ||
|
|
329
|
+
normalized.includes("sub_agent") ||
|
|
330
|
+
normalized.includes("subagent")
|
|
331
|
+
) {
|
|
332
|
+
return { icon: "Bot", label: "Agent Task" };
|
|
333
|
+
}
|
|
334
|
+
return { icon: "Wrench", label: toolName.trim() || "Tool" };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function parseSubAgentActionLog(log: string): readonly SubAgentAction[] {
|
|
338
|
+
const actions: SubAgentAction[] = [];
|
|
339
|
+
for (const line of log.split(/\r?\n/)) {
|
|
340
|
+
const match = line.trim().match(/^\[([^\]]+)\]\s*(.*)$/);
|
|
341
|
+
if (!match?.[1]) continue;
|
|
342
|
+
const toolName = match[1].trim();
|
|
343
|
+
if (!toolName) continue;
|
|
344
|
+
const summary = match[2]?.trim();
|
|
345
|
+
actions.push({
|
|
346
|
+
index: actions.length,
|
|
347
|
+
toolName,
|
|
348
|
+
...(summary ? { summary } : {}),
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
return actions;
|
|
352
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { defineRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { localeSchema } from "../i18n";
|
|
4
|
+
|
|
5
|
+
export const PROJECT_KINDS = [
|
|
6
|
+
"frontend",
|
|
7
|
+
"backend",
|
|
8
|
+
"cli",
|
|
9
|
+
"library",
|
|
10
|
+
"monorepo",
|
|
11
|
+
"docs",
|
|
12
|
+
"unknown",
|
|
13
|
+
] as const;
|
|
14
|
+
export const projectKindSchema = z.enum(PROJECT_KINDS);
|
|
15
|
+
export type ProjectKind = z.output<typeof projectKindSchema>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Facts read from disk. The probe never records a conclusion it could not read: a missing
|
|
19
|
+
* manifest means "unknown", not a guess. The model draws conclusions; this carries evidence.
|
|
20
|
+
*/
|
|
21
|
+
export const environmentProfileSchema = z.object({
|
|
22
|
+
root: z.string(),
|
|
23
|
+
projectKind: projectKindSchema,
|
|
24
|
+
languages: z.array(z.string()),
|
|
25
|
+
packageManagers: z.array(z.string()),
|
|
26
|
+
frameworks: z.array(z.string()),
|
|
27
|
+
commands: z.object({
|
|
28
|
+
build: z.string().optional(),
|
|
29
|
+
test: z.string().optional(),
|
|
30
|
+
lint: z.string().optional(),
|
|
31
|
+
typecheck: z.string().optional(),
|
|
32
|
+
}),
|
|
33
|
+
conventions: z.array(z.string()),
|
|
34
|
+
constraints: z.array(z.string()),
|
|
35
|
+
hotspots: z.array(
|
|
36
|
+
z.object({
|
|
37
|
+
path: z.string(),
|
|
38
|
+
reason: z.string(),
|
|
39
|
+
}),
|
|
40
|
+
),
|
|
41
|
+
references: z.array(
|
|
42
|
+
z.object({
|
|
43
|
+
path: z.string(),
|
|
44
|
+
excerpt: z.string(),
|
|
45
|
+
}),
|
|
46
|
+
),
|
|
47
|
+
});
|
|
48
|
+
export type EnvironmentProfile = z.output<typeof environmentProfileSchema>;
|
|
49
|
+
|
|
50
|
+
export const MAX_RAW_CHARS = 20_000;
|
|
51
|
+
/**
|
|
52
|
+
* A hard ceiling on the enhanced body, deliberately not a multiple of the input. Scaling the cap by
|
|
53
|
+
* input length was what made short prompts come back as stubs — a four-character request was held to
|
|
54
|
+
* twelve characters — and a floor would fight this ceiling, so the body is simply held to one size.
|
|
55
|
+
* Four dimensions fit in 200 characters if the model is told to spend them on substance.
|
|
56
|
+
*/
|
|
57
|
+
export const MAX_ENHANCED_CHARS = 200;
|
|
58
|
+
|
|
59
|
+
export const enhanceRequestSchema = z.object({
|
|
60
|
+
raw: z.string().min(1).max(MAX_RAW_CHARS),
|
|
61
|
+
agentId: z.string().min(1),
|
|
62
|
+
workspaceId: z.string().min(1),
|
|
63
|
+
/** The caller's UI language; plugin-authored text in the result comes back in it. */
|
|
64
|
+
locale: localeSchema.optional(),
|
|
65
|
+
});
|
|
66
|
+
export type EnhanceRequest = z.output<typeof enhanceRequestSchema>;
|
|
67
|
+
|
|
68
|
+
export const enhanceResultSchema = z.object({
|
|
69
|
+
enhanced: z.string(),
|
|
70
|
+
deltaSummary: z.string(),
|
|
71
|
+
openQuestions: z.array(z.string()),
|
|
72
|
+
applied: z.boolean(),
|
|
73
|
+
degraded: z.boolean(),
|
|
74
|
+
degradedReason: z.string(),
|
|
75
|
+
/** The body was shortened to `MAX_ENHANCED_CHARS`, so `deltaSummary` says less than it wanted to. */
|
|
76
|
+
truncated: z.boolean(),
|
|
77
|
+
});
|
|
78
|
+
export type EnhanceResult = z.output<typeof enhanceResultSchema>;
|
|
79
|
+
|
|
80
|
+
export const enhancePromptRpc = defineRpc({
|
|
81
|
+
name: "enhance.prompt",
|
|
82
|
+
input: enhanceRequestSchema,
|
|
83
|
+
output: enhanceResultSchema,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Empty fields fall back to the stored settings, so the settings screen can test what the user
|
|
88
|
+
* just typed before the debounced save lands.
|
|
89
|
+
*/
|
|
90
|
+
export const testEnhanceConnectionRpc = defineRpc({
|
|
91
|
+
name: "enhance.test",
|
|
92
|
+
input: z.object({
|
|
93
|
+
baseUrl: z.string().max(500),
|
|
94
|
+
apiKey: z.string().max(500),
|
|
95
|
+
model: z.string().max(200),
|
|
96
|
+
locale: localeSchema.optional(),
|
|
97
|
+
}),
|
|
98
|
+
output: z.object({
|
|
99
|
+
ok: z.boolean(),
|
|
100
|
+
message: z.string(),
|
|
101
|
+
latencyMs: z.number(),
|
|
102
|
+
endpoint: z.string(),
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
export type TestConnectionResult = z.output<typeof testEnhanceConnectionRpc.output>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { t, type TranslationKey } from "../i18n";
|
|
2
|
+
import { MAX_ENHANCED_CHARS } from "./contract";
|
|
3
|
+
import { normalizeRaw } from "./normalize";
|
|
4
|
+
|
|
5
|
+
/** The four elements an executable prompt needs; the product definition of "enhanced". */
|
|
6
|
+
export const ENHANCE_DIMENSIONS = ["goal", "scope", "constraints", "acceptance"] as const;
|
|
7
|
+
export type EnhanceDimension = (typeof ENHANCE_DIMENSIONS)[number];
|
|
8
|
+
|
|
9
|
+
/** Where each dimension's name lives in the dictionary, so a reason can be worded in any language. */
|
|
10
|
+
export const DIMENSION_KEYS: Record<EnhanceDimension, TranslationKey> = {
|
|
11
|
+
goal: "enhance.dimension.goal",
|
|
12
|
+
scope: "enhance.dimension.scope",
|
|
13
|
+
constraints: "enhance.dimension.constraints",
|
|
14
|
+
acceptance: "enhance.dimension.acceptance",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The dimension names the model is given. The system prompt is Chinese, so these stay Chinese
|
|
19
|
+
* whatever language the interface is in; only the reasons the reader sees are translated.
|
|
20
|
+
*/
|
|
21
|
+
export const DIMENSION_LABELS: Record<EnhanceDimension, string> = {
|
|
22
|
+
goal: t("zh-CN", DIMENSION_KEYS.goal),
|
|
23
|
+
scope: t("zh-CN", DIMENSION_KEYS.scope),
|
|
24
|
+
constraints: t("zh-CN", DIMENSION_KEYS.constraints),
|
|
25
|
+
acceptance: t("zh-CN", DIMENSION_KEYS.acceptance),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Which check stopped the rewrite; the wording is chosen where the language is known. */
|
|
29
|
+
export type EnhanceSkipReason = "tooShort" | "alreadyStructured" | "missingDimensions" | "tooLong";
|
|
30
|
+
|
|
31
|
+
export interface EnhanceDecision {
|
|
32
|
+
enhance: boolean;
|
|
33
|
+
present: EnhanceDimension[];
|
|
34
|
+
missing: EnhanceDimension[];
|
|
35
|
+
/** Set whenever `enhance` is false. */
|
|
36
|
+
skip?: EnhanceSkipReason;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ACTION_VERBS =
|
|
40
|
+
/实现|开发|重构|修复|优化|新增|添加|删除|移除|支持|接入|迁移|编写|生成|构建|部署|测试|梳理|整理|清理|排查|设计|封装|升级|拆分|合并/;
|
|
41
|
+
const ENGLISH_ACTION_VERBS =
|
|
42
|
+
/\b(implement|fix|refactor|add|remove|support|migrate|write|build|create|update|handle|extract|rename|document|test)\b/i;
|
|
43
|
+
const SCOPE_HINT =
|
|
44
|
+
/范围|边界|不要|仅|只改|不涉及|排除|src\/|client\/|shared\/|server\/|\.ts\b|\.tsx\b|\.js\b|\.py\b|\.go\b|\.rs\b|模块|目录|文件/;
|
|
45
|
+
const ENGLISH_SCOPE_HINT =
|
|
46
|
+
/\b(scope|without|only|exclude|don't|do not)\b|\.\w{1,4}\b|\b(module|directory|file)\b/i;
|
|
47
|
+
const CONSTRAINT_HINT =
|
|
48
|
+
/约束|兼容|性能|不能|必须|禁止|保持|不要改|版本|依赖|并发|超时|安全|隐私|向后兼容|不破坏|沿用|遵循/;
|
|
49
|
+
const ENGLISH_CONSTRAINT_HINT =
|
|
50
|
+
/\b(must|must not|without breaking|backward|compatible|performance|constraint|keep|preserve|follow)\b/i;
|
|
51
|
+
const ACCEPTANCE_HINT =
|
|
52
|
+
/验收|测试|怎么算完成|完成标准|成功标准|期望|预期|验证|通过|用例|断言|benchmark/;
|
|
53
|
+
const ENGLISH_ACCEPTANCE_HINT =
|
|
54
|
+
/\b(acceptance|test|verify|expected|success criteria|definition of done|assert)\b/i;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Deterministic pre-check. A vague one-liner needs expansion; an already structured prompt must
|
|
58
|
+
* come back untouched rather than paraphrased into different words.
|
|
59
|
+
*/
|
|
60
|
+
export function decideEnhancement(raw: string): EnhanceDecision {
|
|
61
|
+
const text = normalizeRaw(raw);
|
|
62
|
+
const present = new Set<EnhanceDimension>();
|
|
63
|
+
if (ACTION_VERBS.test(text) || ENGLISH_ACTION_VERBS.test(text)) present.add("goal");
|
|
64
|
+
if (SCOPE_HINT.test(text) || ENGLISH_SCOPE_HINT.test(text)) present.add("scope");
|
|
65
|
+
if (CONSTRAINT_HINT.test(text) || ENGLISH_CONSTRAINT_HINT.test(text)) present.add("constraints");
|
|
66
|
+
if (ACCEPTANCE_HINT.test(text) || ENGLISH_ACCEPTANCE_HINT.test(text)) present.add("acceptance");
|
|
67
|
+
const missing = ENHANCE_DIMENSIONS.filter((dimension) => !present.has(dimension));
|
|
68
|
+
// Four CJK characters already carry a full request ("加个缓存"), so the floor stays low.
|
|
69
|
+
if (text.length < 4)
|
|
70
|
+
return {
|
|
71
|
+
enhance: false,
|
|
72
|
+
present: [...present],
|
|
73
|
+
missing,
|
|
74
|
+
skip: "tooShort",
|
|
75
|
+
};
|
|
76
|
+
if (present.has("goal") && missing.length <= 1)
|
|
77
|
+
return {
|
|
78
|
+
enhance: false,
|
|
79
|
+
present: [...present],
|
|
80
|
+
missing,
|
|
81
|
+
skip: missing.length ? "missingDimensions" : "alreadyStructured",
|
|
82
|
+
};
|
|
83
|
+
// The rewrite is capped well below this, so enhancing a prompt this long could only return
|
|
84
|
+
// something shorter than what the user already wrote. That is a deletion, not an enhancement.
|
|
85
|
+
if (text.length > MAX_ENHANCED_CHARS)
|
|
86
|
+
return {
|
|
87
|
+
enhance: false,
|
|
88
|
+
present: [...present],
|
|
89
|
+
missing,
|
|
90
|
+
skip: "tooLong",
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
enhance: true,
|
|
94
|
+
present: [...present],
|
|
95
|
+
missing,
|
|
96
|
+
};
|
|
97
|
+
}
|