@trim21/personal-pi-extensions 0.1.683 → 0.1.684
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/package.json +1 -1
- package/src/aft/schemas.ts +246 -0
- package/src/aft/tools.ts +275 -235
package/package.json
CHANGED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AFT 感知工具的结构化结果 schema。
|
|
3
|
+
*
|
|
4
|
+
* 载荷就是引擎自己的响应字段(去掉 envelope 的 request id),外加一个 `text`——我们自己
|
|
5
|
+
* 渲染(或引擎渲染)的文本,与工具输出一致。字段名与类型来自三处证据:
|
|
6
|
+
*
|
|
7
|
+
* - 本仓库对真引擎的实测响应(outline 两种模式、zoom,见 `test/fixtures/aft/`);
|
|
8
|
+
* - `@cortexkit/aft-bridge` 自己的格式化器读哪些字段(callgraph 各 op、zoom 的注解);
|
|
9
|
+
* - 引擎的 `ListEnvelope` / 截断字段(walk_truncated、collection_truncated、truncated)。
|
|
10
|
+
*
|
|
11
|
+
* 两条刻意的宽松:
|
|
12
|
+
*
|
|
13
|
+
* 1. 所有引擎字段都是可选、且允许额外字段(`additionalProperties: true`)。引擎不归我们
|
|
14
|
+
* 管,它的响应会加字段、换字段名;声明成必需会让版本一升级就把工具调用变成失败。
|
|
15
|
+
* 2. 嵌套条目(callgraph 的调用点、搜索命中等)把已知字段列全但全部可选——引擎自己也用
|
|
16
|
+
* `?? "(unknown)"` 兜底,说明这些字段并非永远在场。
|
|
17
|
+
*
|
|
18
|
+
* 载荷只经总线 `Value.Parse` 复核一次(见 `src/lib/tool-bus.ts`),因此「宽松」不等于
|
|
19
|
+
* 不校验:`text` 必需,列出的字段类型必须对得上。
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { Type } from "typebox";
|
|
23
|
+
|
|
24
|
+
/** 引擎渲染的文本,与工具输出一致。 */
|
|
25
|
+
const text = Type.String({ description: "引擎渲染的文本,与工具输出一致" });
|
|
26
|
+
|
|
27
|
+
/** 行号区间(zoom):引擎的 Range 序列化时行号从 1 开始。 */
|
|
28
|
+
const rangeSchema = Type.Object(
|
|
29
|
+
{
|
|
30
|
+
start_line: Type.Number(),
|
|
31
|
+
end_line: Type.Number(),
|
|
32
|
+
start_col: Type.Optional(Type.Number()),
|
|
33
|
+
end_col: Type.Optional(Type.Number()),
|
|
34
|
+
},
|
|
35
|
+
{ additionalProperties: true },
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/** 调用图注解里的一条引用(zoom 的 calls_out / called_by)。 */
|
|
39
|
+
const callRefSchema = Type.Object(
|
|
40
|
+
{
|
|
41
|
+
name: Type.String(),
|
|
42
|
+
line: Type.Number(),
|
|
43
|
+
/** 同一行上还有多少条同类引用被折叠。 */
|
|
44
|
+
extra_count: Type.Optional(Type.Number()),
|
|
45
|
+
},
|
|
46
|
+
{ additionalProperties: true },
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
/** `aft_outline`:单文件模式只有 text(引擎不返回符号树),files 模式带文件条目。 */
|
|
50
|
+
export const aftOutlineStructuredSchema = Type.Object(
|
|
51
|
+
{
|
|
52
|
+
text,
|
|
53
|
+
/** 引擎是否确定给全了结果。 */
|
|
54
|
+
complete: Type.Optional(Type.Boolean()),
|
|
55
|
+
/** files 模式(目录 + files: true)的文件条目。 */
|
|
56
|
+
files: Type.Optional(
|
|
57
|
+
Type.Array(
|
|
58
|
+
Type.Object(
|
|
59
|
+
{
|
|
60
|
+
path: Type.String(),
|
|
61
|
+
language: Type.String(),
|
|
62
|
+
/** 顶层符号数;未知时引擎省略该字段。 */
|
|
63
|
+
symbols: Type.Optional(Type.Number()),
|
|
64
|
+
/** 行数;null 表示不在索引里(line_count_gaps 列出这些文件)。 */
|
|
65
|
+
lines: Type.Union([Type.Number(), Type.Null()]),
|
|
66
|
+
},
|
|
67
|
+
{ additionalProperties: true },
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
),
|
|
71
|
+
/** 目录遍历触到上限。 */
|
|
72
|
+
walk_truncated: Type.Optional(Type.Boolean()),
|
|
73
|
+
/** 条目收集触到上限。 */
|
|
74
|
+
collection_truncated: Type.Optional(Type.Boolean()),
|
|
75
|
+
},
|
|
76
|
+
{ additionalProperties: true },
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
/** `aft_zoom`:单符号源码 + 行号区间 + 同文件调用注解。 */
|
|
80
|
+
export const aftZoomStructuredSchema = Type.Object(
|
|
81
|
+
{
|
|
82
|
+
text,
|
|
83
|
+
/** 符号名;行范围模式是 "lines X-Y",符号歧义时缺省。 */
|
|
84
|
+
name: Type.Optional(Type.String()),
|
|
85
|
+
/** function / class / method / lines / ambiguous_symbol … */
|
|
86
|
+
kind: Type.Optional(Type.String()),
|
|
87
|
+
range: Type.Optional(rangeSchema),
|
|
88
|
+
/** 符号源码(不含上下文行)。 */
|
|
89
|
+
content: Type.Optional(Type.String()),
|
|
90
|
+
context_before: Type.Optional(Type.Array(Type.String())),
|
|
91
|
+
context_after: Type.Optional(Type.Array(Type.String())),
|
|
92
|
+
annotations: Type.Optional(
|
|
93
|
+
Type.Object(
|
|
94
|
+
{
|
|
95
|
+
calls_out: Type.Optional(Type.Array(callRefSchema)),
|
|
96
|
+
called_by: Type.Optional(Type.Array(callRefSchema)),
|
|
97
|
+
},
|
|
98
|
+
{ additionalProperties: true },
|
|
99
|
+
),
|
|
100
|
+
),
|
|
101
|
+
},
|
|
102
|
+
{ additionalProperties: true },
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
/** 调用图上的一条位置(callers 的调用点、impact 的受害点)。 */
|
|
106
|
+
const callgraphSiteSchema = Type.Object(
|
|
107
|
+
{
|
|
108
|
+
file: Type.Optional(Type.String()),
|
|
109
|
+
line: Type.Optional(Type.Number()),
|
|
110
|
+
/** callers 分组的调用符号名。 */
|
|
111
|
+
symbol: Type.Optional(Type.String()),
|
|
112
|
+
/** impact 的调用点符号名(与 caller_file 成对)。 */
|
|
113
|
+
caller_symbol: Type.Optional(Type.String()),
|
|
114
|
+
caller_file: Type.Optional(Type.String()),
|
|
115
|
+
/** impact:该调用点自身是入口(脚本常据此过滤)。 */
|
|
116
|
+
is_entry_point: Type.Optional(Type.Boolean()),
|
|
117
|
+
/** 仅按名字解析出来的边(可能指向同名符号)。 */
|
|
118
|
+
approximate: Type.Optional(Type.Boolean()),
|
|
119
|
+
/** "name_match" 等解析来源。 */
|
|
120
|
+
resolved_by: Type.Optional(Type.String()),
|
|
121
|
+
},
|
|
122
|
+
{ additionalProperties: true },
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
/** 调用链上的一跳(trace_to / trace_to_symbol)。 */
|
|
126
|
+
const callgraphHopSchema = Type.Object(
|
|
127
|
+
{
|
|
128
|
+
symbol: Type.Optional(Type.String()),
|
|
129
|
+
file: Type.Optional(Type.String()),
|
|
130
|
+
line: Type.Optional(Type.Number()),
|
|
131
|
+
is_entry_point: Type.Optional(Type.Boolean()),
|
|
132
|
+
/** trace_data:该跳流转的变量名与流向。 */
|
|
133
|
+
variable: Type.Optional(Type.String()),
|
|
134
|
+
flow_type: Type.Optional(Type.String()),
|
|
135
|
+
approximate: Type.Optional(Type.Boolean()),
|
|
136
|
+
resolved_by: Type.Optional(Type.String()),
|
|
137
|
+
},
|
|
138
|
+
{ additionalProperties: true },
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* `aft_callgraph`:一个工具名下 6 个 op,响应形状各不同(callers 是分组列表、call_tree 是
|
|
143
|
+
* 树、impact 是扁平调用点、trace_to 是路径列表、trace_to_symbol 是单条路径、trace_data 是
|
|
144
|
+
* 数据流跳)。这里把各 op 的锚点与列表都列为可选字段,脚本按 op 取用。
|
|
145
|
+
*/
|
|
146
|
+
export const aftCallgraphStructuredSchema = Type.Object(
|
|
147
|
+
{
|
|
148
|
+
text,
|
|
149
|
+
/** 被查询的符号(callers / impact 的锚点)。 */
|
|
150
|
+
symbol: Type.Optional(Type.String()),
|
|
151
|
+
file: Type.Optional(Type.String()),
|
|
152
|
+
/** call_tree 根节点的锚点。 */
|
|
153
|
+
name: Type.Optional(Type.String()),
|
|
154
|
+
line: Type.Optional(Type.Number()),
|
|
155
|
+
signature: Type.Optional(Type.String()),
|
|
156
|
+
/** callers:按文件分组的调用点。 */
|
|
157
|
+
callers: Type.Optional(
|
|
158
|
+
Type.Array(
|
|
159
|
+
Type.Object(
|
|
160
|
+
{
|
|
161
|
+
file: Type.Optional(Type.String()),
|
|
162
|
+
callers: Type.Optional(Type.Array(callgraphSiteSchema)),
|
|
163
|
+
},
|
|
164
|
+
{ additionalProperties: true },
|
|
165
|
+
),
|
|
166
|
+
),
|
|
167
|
+
),
|
|
168
|
+
/** call_tree 根节点的直接子节点(整棵树在 payload 里,这里只标出形状)。 */
|
|
169
|
+
children: Type.Optional(Type.Array(Type.Unknown())),
|
|
170
|
+
/** call_tree 节点是否解析到定义;false 且无子节点 = 未解析的叶子。 */
|
|
171
|
+
resolved: Type.Optional(Type.Boolean()),
|
|
172
|
+
total_callers: Type.Optional(Type.Number()),
|
|
173
|
+
total_affected: Type.Optional(Type.Number()),
|
|
174
|
+
affected_files: Type.Optional(Type.Number()),
|
|
175
|
+
/** impact 的参数表(改签名时要知道)。 */
|
|
176
|
+
parameters: Type.Optional(Type.Array(Type.String())),
|
|
177
|
+
/** trace_to:到目标符号的路径,每条路径是一串跳。 */
|
|
178
|
+
paths: Type.Optional(
|
|
179
|
+
Type.Array(
|
|
180
|
+
Type.Object(
|
|
181
|
+
{ hops: Type.Optional(Type.Array(callgraphHopSchema)) },
|
|
182
|
+
{ additionalProperties: true },
|
|
183
|
+
),
|
|
184
|
+
),
|
|
185
|
+
),
|
|
186
|
+
total_paths: Type.Optional(Type.Number()),
|
|
187
|
+
/** trace_to_symbol:单条最短路径(没有路径时为 null)。 */
|
|
188
|
+
path: Type.Optional(Type.Union([Type.Array(callgraphHopSchema), Type.Null()])),
|
|
189
|
+
/** trace_data:值在赋值/参数/返回间的流转。 */
|
|
190
|
+
hops: Type.Optional(Type.Array(callgraphHopSchema)),
|
|
191
|
+
depth_limited: Type.Optional(Type.Boolean()),
|
|
192
|
+
/** 因结果上限被折叠的条目数(引擎给的是数字,不是布尔)。 */
|
|
193
|
+
truncated: Type.Optional(Type.Number()),
|
|
194
|
+
/** 软失败(symbol_not_found / callgraph_building …)的错误码。 */
|
|
195
|
+
code: Type.Optional(Type.String()),
|
|
196
|
+
},
|
|
197
|
+
{ additionalProperties: true },
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
/** 搜索命中:常规 ranking 条目(symbol)与降级时的 grep 行共用一份宽条目。 */
|
|
201
|
+
const searchHitSchema = Type.Object(
|
|
202
|
+
{
|
|
203
|
+
file: Type.Optional(Type.String()),
|
|
204
|
+
name: Type.Optional(Type.String()),
|
|
205
|
+
kind: Type.Optional(Type.String()),
|
|
206
|
+
start_line: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
|
|
207
|
+
end_line: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
|
|
208
|
+
/** "matching line" / "line range" / "[file summary]" / "GrepLine"。 */
|
|
209
|
+
location: Type.Optional(Type.String()),
|
|
210
|
+
score: Type.Optional(Type.Number()),
|
|
211
|
+
source: Type.Optional(Type.String()),
|
|
212
|
+
semantic_score: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
|
|
213
|
+
lexical_score: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
|
|
214
|
+
exact: Type.Optional(Type.Boolean()),
|
|
215
|
+
snippet: Type.Optional(Type.String()),
|
|
216
|
+
/** grep 行条目:行号、列号与命中文本。 */
|
|
217
|
+
line: Type.Optional(Type.Number()),
|
|
218
|
+
column: Type.Optional(Type.Number()),
|
|
219
|
+
line_text: Type.Optional(Type.String()),
|
|
220
|
+
match_text: Type.Optional(Type.String()),
|
|
221
|
+
},
|
|
222
|
+
{ additionalProperties: true },
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
/** `aft_search`:查询解释 + 命中列表 + 截断/降级状态。 */
|
|
226
|
+
export const aftSearchStructuredSchema = Type.Object(
|
|
227
|
+
{
|
|
228
|
+
text,
|
|
229
|
+
query: Type.Optional(Type.String()),
|
|
230
|
+
/** 引擎对查询的最终解释(regex / literal / semantic / hybrid)。 */
|
|
231
|
+
interpreted_as: Type.Optional(Type.String()),
|
|
232
|
+
result_count: Type.Optional(Type.Number()),
|
|
233
|
+
complete: Type.Optional(Type.Boolean()),
|
|
234
|
+
/** 还有更多命中没给(与分页上限的区别由引擎的 envelope 说明)。 */
|
|
235
|
+
more_available: Type.Optional(Type.Boolean()),
|
|
236
|
+
engine_capped: Type.Optional(Type.Boolean()),
|
|
237
|
+
/** 语义索引状态:ready / building / disabled / unavailable / external。 */
|
|
238
|
+
semantic_status: Type.Optional(Type.String()),
|
|
239
|
+
results: Type.Optional(Type.Array(searchHitSchema)),
|
|
240
|
+
/** 引擎给出的警告(索引降级等),原样转发。 */
|
|
241
|
+
warnings: Type.Optional(Type.Array(Type.String())),
|
|
242
|
+
/** 软失败(search_lanes_unavailable …)的错误码。 */
|
|
243
|
+
code: Type.Optional(Type.String()),
|
|
244
|
+
},
|
|
245
|
+
{ additionalProperties: true },
|
|
246
|
+
);
|
package/src/aft/tools.ts
CHANGED
|
@@ -20,18 +20,24 @@ import {
|
|
|
20
20
|
type StatusSnapshot,
|
|
21
21
|
} from "@cortexkit/aft-bridge";
|
|
22
22
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
23
|
-
import { type Static, Type } from "typebox";
|
|
23
|
+
import { type Static, type TSchema, Type } from "typebox";
|
|
24
24
|
import { Value } from "typebox/value";
|
|
25
25
|
|
|
26
26
|
import { formatDisplayPath, formatSubtitlePath, resolvePathArg } from "../lib/path.js";
|
|
27
27
|
import { type ToolPendant } from "../lib/pendant.js";
|
|
28
|
-
import type
|
|
28
|
+
import { defineStructuredTool, type StructuredResult, type ToolBus } from "../lib/tool-bus.js";
|
|
29
29
|
import {
|
|
30
30
|
type AftState,
|
|
31
31
|
callAftTool,
|
|
32
32
|
resolveSessionId,
|
|
33
33
|
SEMANTIC_INDEX_WAIT_TIMEOUT_MS,
|
|
34
34
|
} from "./bridge.js";
|
|
35
|
+
import {
|
|
36
|
+
aftCallgraphStructuredSchema,
|
|
37
|
+
aftOutlineStructuredSchema,
|
|
38
|
+
aftSearchStructuredSchema,
|
|
39
|
+
aftZoomStructuredSchema,
|
|
40
|
+
} from "./schemas.js";
|
|
35
41
|
|
|
36
42
|
/** 工具使用指南,以 markdown 形式维护,读起来像文档。 */
|
|
37
43
|
const OUTLINE_PROMPT = readFileSync(
|
|
@@ -79,6 +85,24 @@ export function compactArgs(args: Record<string, unknown>): Record<string, unkno
|
|
|
79
85
|
);
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
/**
|
|
89
|
+
* 结构化载荷:引擎自己的响应字段(去掉 envelope 的 request id)原样给脚本,外加我们
|
|
90
|
+
* 渲染的文本。引擎的 `success` / `code` 保留在载荷里——软失败(symbol_not_found、
|
|
91
|
+
* callgraph_building、search_lanes_unavailable)是「工具跑成了、引擎给出否定答案」,
|
|
92
|
+
* 脚本据 `code` 分支,而不是被当成调用异常。
|
|
93
|
+
*/
|
|
94
|
+
function aftPayload<TOutput extends TSchema>(
|
|
95
|
+
response: Record<string, unknown>,
|
|
96
|
+
text: string,
|
|
97
|
+
): StructuredResult<Static<TOutput>> {
|
|
98
|
+
const value: Record<string, unknown> = { ...response, text };
|
|
99
|
+
// envelope 的 request id 是传输层的事,不进载荷
|
|
100
|
+
delete value.id;
|
|
101
|
+
// 载荷是引擎响应的原样透传(schema 只声明我们确证过的字段,其余允许通过),所以
|
|
102
|
+
// 编译期只能断言;类型正确性由脚本侧的声明与总线运行期的 Value.Parse 复核共同保证。
|
|
103
|
+
return { ok: true, value: value as Static<TOutput> };
|
|
104
|
+
}
|
|
105
|
+
|
|
82
106
|
/** 人类视角的调用记录:input params + 与 LLM 相同的输出结果。 */
|
|
83
107
|
function buildPendantMarkdown(params: {
|
|
84
108
|
title: string;
|
|
@@ -127,60 +151,64 @@ const OutlineParams = Type.Object(
|
|
|
127
151
|
);
|
|
128
152
|
|
|
129
153
|
export function registerOutlineTool(bus: ToolBus, ctx: AftToolContext): void {
|
|
130
|
-
bus.register(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
154
|
+
bus.register(
|
|
155
|
+
defineStructuredTool({
|
|
156
|
+
name: "aft_outline",
|
|
157
|
+
label: "aft_outline",
|
|
158
|
+
description: [
|
|
159
|
+
"输出代码文件、目录的结构化大纲:函数/类/类型等符号及其行号范围;Markdown/HTML 返回标题层级。",
|
|
160
|
+
"用它在读取具体内容之前先了解文件结构。",
|
|
161
|
+
"深入了解某个符号用 aft_zoom;看跨文件调用关系用 aft_callgraph。",
|
|
162
|
+
"target 支持:文件路径(带签名的符号大纲)、目录路径(递归最多 200 文件)。只接受单个 target。",
|
|
163
|
+
"target 为目录时默认返回扁平文件树(语言、顶层符号数、字节大小);传 files: false 可改回符号大纲。",
|
|
164
|
+
].join("\n"),
|
|
165
|
+
promptSnippet: "Output structural outline of a file/directory",
|
|
166
|
+
promptGuidelines: [OUTLINE_PROMPT, AFT_GUIDELINES],
|
|
167
|
+
parameters: OutlineParams,
|
|
168
|
+
structuredSchema: aftOutlineStructuredSchema,
|
|
169
|
+
async execute(_id, params, signal, _onUpdate, extCtx) {
|
|
170
|
+
const target = coerceTargetParam(params.target);
|
|
171
|
+
if (typeof target !== "string" || target.length === 0) {
|
|
172
|
+
throw new Error("'target' must be a single path (array targets are not supported)");
|
|
173
|
+
}
|
|
174
|
+
const resolved = resolvePathArg(extCtx.cwd, target);
|
|
175
|
+
let filesMode = coerceBoolean(params.files);
|
|
176
|
+
if (params.files === undefined) {
|
|
177
|
+
const stats = await stat(resolved).catch(() => null);
|
|
178
|
+
filesMode = stats?.isDirectory() ?? false;
|
|
179
|
+
}
|
|
180
|
+
const rawArgs = compactArgs({
|
|
181
|
+
target: resolved,
|
|
182
|
+
files: filesMode || undefined,
|
|
183
|
+
includeTests: params.includeTests,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const subtitle = buildOutlineSubtitle(extCtx.cwd, target);
|
|
187
|
+
|
|
188
|
+
const { text, response } = await callAftTool(
|
|
189
|
+
bridgeFor(ctx),
|
|
190
|
+
"outline",
|
|
191
|
+
rawArgs,
|
|
192
|
+
resolveSessionId(extCtx),
|
|
193
|
+
undefined,
|
|
194
|
+
undefined,
|
|
195
|
+
signal,
|
|
196
|
+
);
|
|
197
|
+
const truncated = response.truncated === true;
|
|
198
|
+
return {
|
|
199
|
+
content: [{ type: "text", text }],
|
|
200
|
+
details: {
|
|
201
|
+
truncated,
|
|
202
|
+
params,
|
|
203
|
+
pendant: {
|
|
204
|
+
subtitle,
|
|
205
|
+
} satisfies ToolPendant,
|
|
206
|
+
},
|
|
207
|
+
structuredResult: aftPayload<typeof aftOutlineStructuredSchema>(response, text),
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
184
212
|
}
|
|
185
213
|
|
|
186
214
|
/** 构建 aft_outline pendant 的 subtitle:`target="…"`(路径过长时显示上一级目录加文件名)。 */
|
|
@@ -211,61 +239,65 @@ const ZoomParams = Type.Object(
|
|
|
211
239
|
);
|
|
212
240
|
|
|
213
241
|
export function registerZoomTool(bus: ToolBus, ctx: AftToolContext): void {
|
|
214
|
-
bus.register(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
params.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
242
|
+
bus.register(
|
|
243
|
+
defineStructuredTool({
|
|
244
|
+
name: "aft_zoom",
|
|
245
|
+
label: "aft_zoom",
|
|
246
|
+
description: [
|
|
247
|
+
"查看命名符号(函数/类/类型)的完整源码,或 Markdown/HTML 的标题段落内容。",
|
|
248
|
+
"需要理解某个具体符号时用它(读整个文件用 read)。",
|
|
249
|
+
"callgraph: true 时附带同文件内的调用关系标注。",
|
|
250
|
+
"同文件多符号用 `symbols` 数组。",
|
|
251
|
+
].join("\n"),
|
|
252
|
+
promptSnippet: "Inspect the full source of a named symbol",
|
|
253
|
+
promptGuidelines: [ZOOM_PROMPT],
|
|
254
|
+
parameters: ZoomParams,
|
|
255
|
+
structuredSchema: aftZoomStructuredSchema,
|
|
256
|
+
async execute(_id, params, signal, _onUpdate, extCtx) {
|
|
257
|
+
const rawArgs = compactArgs({
|
|
258
|
+
filePath: resolvePathArg(extCtx.cwd, params.path),
|
|
259
|
+
symbols: params.symbols,
|
|
260
|
+
contextLines: coerceOptionalInt(
|
|
261
|
+
params.contextLines,
|
|
262
|
+
"contextLines",
|
|
263
|
+
1,
|
|
264
|
+
Number.MAX_SAFE_INTEGER,
|
|
265
|
+
),
|
|
266
|
+
callgraph: coerceBoolean(params.callgraph) || undefined,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const subtitle = buildZoomSubtitle(extCtx.cwd, params);
|
|
270
|
+
|
|
271
|
+
const { text, response } = await callAftTool(
|
|
272
|
+
bridgeFor(ctx),
|
|
273
|
+
"zoom",
|
|
274
|
+
rawArgs,
|
|
275
|
+
resolveSessionId(extCtx),
|
|
276
|
+
undefined,
|
|
277
|
+
undefined,
|
|
278
|
+
signal,
|
|
279
|
+
);
|
|
280
|
+
const truncated = response.truncated === true;
|
|
281
|
+
return {
|
|
282
|
+
content: [{ type: "text", text }],
|
|
283
|
+
details: {
|
|
284
|
+
truncated,
|
|
285
|
+
params,
|
|
286
|
+
pendant: {
|
|
287
|
+
subtitle,
|
|
288
|
+
markdown: buildPendantMarkdown({
|
|
289
|
+
title: "aft_zoom",
|
|
290
|
+
input: params,
|
|
291
|
+
output: text,
|
|
292
|
+
truncated,
|
|
293
|
+
}),
|
|
294
|
+
} satisfies ToolPendant,
|
|
295
|
+
},
|
|
296
|
+
structuredResult: aftPayload<typeof aftZoomStructuredSchema>(response, text),
|
|
297
|
+
};
|
|
298
|
+
},
|
|
299
|
+
}),
|
|
300
|
+
);
|
|
269
301
|
}
|
|
270
302
|
|
|
271
303
|
/** 构建 aft_zoom pendant 的 subtitle:`path="…" symbol="…"`。 */
|
|
@@ -363,60 +395,64 @@ export async function callCallgraphWithBuildRetry(
|
|
|
363
395
|
}
|
|
364
396
|
|
|
365
397
|
export function registerCallgraphTool(bus: ToolBus, ctx: AftToolContext): void {
|
|
366
|
-
bus.register(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
398
|
+
bus.register(
|
|
399
|
+
defineStructuredTool({
|
|
400
|
+
name: "aft_callgraph",
|
|
401
|
+
label: "aft_callgraph",
|
|
402
|
+
description: [
|
|
403
|
+
"基于真实调用图回答代码关系问题(谁调用我、影响面、调用链),替代 grep + read 的链条式排查。",
|
|
404
|
+
"op 语义:callers=调用点(改名/改签名前用);impact=影响面(改一个符号会波及谁);",
|
|
405
|
+
"call_tree=该函数调用了什么;trace_to=从入口如何执行到某符号;",
|
|
406
|
+
"trace_to_symbol=两符号间最短路径(需 toSymbol,歧义时需 toPath);trace_data=追踪值在参数/赋值间的流转(需 expression)。",
|
|
407
|
+
"标记:~ = 仅按名字解析的边(可能指向同名符号);[unresolved] = 未解析到定义的调用点。",
|
|
408
|
+
].join("\n"),
|
|
409
|
+
promptSnippet: "Call graph and data-flow navigation",
|
|
410
|
+
promptGuidelines: [CALLGRAPH_PROMPT],
|
|
411
|
+
parameters: CallgraphParams,
|
|
412
|
+
structuredSchema: aftCallgraphStructuredSchema,
|
|
413
|
+
async execute(_id, params, signal, _onUpdate, extCtx) {
|
|
414
|
+
const rawArgs = compactArgs({
|
|
415
|
+
op: params.op,
|
|
416
|
+
filePath: resolvePathArg(extCtx.cwd, params.path),
|
|
417
|
+
symbol: params.symbol,
|
|
418
|
+
depth: coerceOptionalInt(params.depth, "depth", 1, Number.MAX_SAFE_INTEGER),
|
|
419
|
+
expression: params.expression,
|
|
420
|
+
toSymbol: params.toSymbol,
|
|
421
|
+
toFile: params.toPath ? resolvePathArg(extCtx.cwd, params.toPath) : undefined,
|
|
422
|
+
includeTests: params.includeTests,
|
|
423
|
+
includeUnresolved: params.includeUnresolved,
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const { text, response } = await callCallgraphWithBuildRetry(
|
|
427
|
+
bridgeFor(ctx),
|
|
428
|
+
rawArgs,
|
|
429
|
+
extCtx,
|
|
430
|
+
signal,
|
|
431
|
+
);
|
|
432
|
+
const out =
|
|
433
|
+
text ||
|
|
434
|
+
formatCallgraphSections(params.op, response, PLAIN_CALLGRAPH_THEME, {
|
|
435
|
+
includeUnresolved: coerceBoolean(params.includeUnresolved),
|
|
436
|
+
}).join("\n");
|
|
437
|
+
const truncated = response.truncated === true;
|
|
438
|
+
return {
|
|
439
|
+
content: [{ type: "text", text: out }],
|
|
440
|
+
details: {
|
|
441
|
+
truncated,
|
|
442
|
+
pendant: {
|
|
443
|
+
markdown: buildPendantMarkdown({
|
|
444
|
+
title: "aft_callgraph",
|
|
445
|
+
input: params,
|
|
446
|
+
output: out,
|
|
447
|
+
truncated,
|
|
448
|
+
}),
|
|
449
|
+
} satisfies ToolPendant,
|
|
450
|
+
},
|
|
451
|
+
structuredResult: aftPayload<typeof aftCallgraphStructuredSchema>(response, out),
|
|
452
|
+
};
|
|
453
|
+
},
|
|
454
|
+
}),
|
|
455
|
+
);
|
|
420
456
|
}
|
|
421
457
|
|
|
422
458
|
/**
|
|
@@ -612,75 +648,79 @@ const SearchParams = Type.Object(
|
|
|
612
648
|
);
|
|
613
649
|
|
|
614
650
|
export function registerSearchTool(bus: ToolBus, ctx: AftToolContext): void {
|
|
615
|
-
bus.register(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
651
|
+
bus.register(
|
|
652
|
+
defineStructuredTool({
|
|
653
|
+
name: "aft_search",
|
|
654
|
+
label: "aft_search",
|
|
655
|
+
description: [
|
|
656
|
+
"一个工具完成代码搜索:概念、标识符、错误串、正则、字面量、文件名自动路由到合适的引擎并按相关度排序。",
|
|
657
|
+
"概念类查询('ORM 如何构建并执行查询')用自然语言整句——语义通道理解意图并匹配 docstring 和注释;",
|
|
658
|
+
"精确名字、字符串、正则保持简短('^export'、'Cargo.lock')。",
|
|
659
|
+
"索引首次构建时本调用会阻塞到构建完成,避免返回部分结果。",
|
|
660
|
+
].join("\n"),
|
|
661
|
+
promptSnippet: "Search code by meaning or exact text",
|
|
662
|
+
promptGuidelines: [SEARCH_PROMPT],
|
|
663
|
+
parameters: SearchParams,
|
|
664
|
+
structuredSchema: aftSearchStructuredSchema,
|
|
665
|
+
async execute(_id, params, signal, onUpdate, extCtx) {
|
|
666
|
+
if (typeof params.query !== "string" || params.query.trim().length === 0) {
|
|
667
|
+
throw new Error("'query' must be a non-empty string");
|
|
668
|
+
}
|
|
669
|
+
const rawArgs = compactArgs({
|
|
670
|
+
query: params.query,
|
|
671
|
+
topK: params.topK,
|
|
672
|
+
includeTests: params.includeTests,
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
const bridge = bridgeFor(ctx);
|
|
676
|
+
const formatProgress = createSemanticIndexProgressFormatter();
|
|
677
|
+
const stopProgress =
|
|
678
|
+
onUpdate === undefined
|
|
679
|
+
? undefined
|
|
680
|
+
: subscribeBridgeStatus(bridge, (snapshot) => {
|
|
681
|
+
const text = formatProgress(snapshot);
|
|
682
|
+
if (text !== undefined) {
|
|
683
|
+
onUpdate({ content: [{ type: "text", text }], details: undefined });
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
let response: Record<string, unknown>;
|
|
687
|
+
let text: string;
|
|
688
|
+
try {
|
|
689
|
+
({ text, response } = await callAftTool(
|
|
690
|
+
bridge,
|
|
691
|
+
"search",
|
|
692
|
+
rawArgs,
|
|
693
|
+
resolveSessionId(extCtx),
|
|
694
|
+
{
|
|
695
|
+
// 默认 search 传输超时仅 60s,会早于索引等待(600s)触发;覆盖为等待
|
|
696
|
+
// 上限 + 常规执行预算。超时只说明响应被挤掉而非 bridge 挂死,保留
|
|
697
|
+
// 常驻的语义索引/LSP 状态。
|
|
698
|
+
transportTimeoutMs: SEMANTIC_INDEX_WAIT_TIMEOUT_MS + 60_000,
|
|
699
|
+
keepBridgeOnTimeout: true,
|
|
700
|
+
},
|
|
701
|
+
undefined,
|
|
702
|
+
signal,
|
|
703
|
+
));
|
|
704
|
+
} finally {
|
|
705
|
+
stopProgress?.();
|
|
706
|
+
}
|
|
707
|
+
const truncated = response.truncated === true;
|
|
708
|
+
return {
|
|
709
|
+
content: [{ type: "text", text }],
|
|
710
|
+
details: {
|
|
711
|
+
truncated,
|
|
712
|
+
pendant: {
|
|
713
|
+
markdown: buildPendantMarkdown({
|
|
714
|
+
title: "aft_search",
|
|
715
|
+
input: params,
|
|
716
|
+
output: text,
|
|
717
|
+
truncated,
|
|
718
|
+
}),
|
|
719
|
+
} satisfies ToolPendant,
|
|
662
720
|
},
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}
|
|
669
|
-
const truncated = response.truncated === true;
|
|
670
|
-
return {
|
|
671
|
-
content: [{ type: "text", text }],
|
|
672
|
-
details: {
|
|
673
|
-
truncated,
|
|
674
|
-
pendant: {
|
|
675
|
-
markdown: buildPendantMarkdown({
|
|
676
|
-
title: "aft_search",
|
|
677
|
-
input: params,
|
|
678
|
-
output: text,
|
|
679
|
-
truncated,
|
|
680
|
-
}),
|
|
681
|
-
} satisfies ToolPendant,
|
|
682
|
-
},
|
|
683
|
-
};
|
|
684
|
-
},
|
|
685
|
-
});
|
|
721
|
+
structuredResult: aftPayload<typeof aftSearchStructuredSchema>(response, text),
|
|
722
|
+
};
|
|
723
|
+
},
|
|
724
|
+
}),
|
|
725
|
+
);
|
|
686
726
|
}
|