@trim21/personal-pi-extensions 0.1.482 → 0.1.487
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/tools.ts +99 -11
package/package.json
CHANGED
package/src/aft/tools.ts
CHANGED
|
@@ -16,9 +16,11 @@ import {
|
|
|
16
16
|
coerceTargetParam,
|
|
17
17
|
formatCallgraphSections,
|
|
18
18
|
PLAIN_CALLGRAPH_THEME,
|
|
19
|
+
type StatusSnapshot,
|
|
19
20
|
} from "@cortexkit/aft-bridge";
|
|
20
21
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
21
|
-
import { Type } from "typebox";
|
|
22
|
+
import { type Static, Type } from "typebox";
|
|
23
|
+
import { Value } from "typebox/value";
|
|
22
24
|
|
|
23
25
|
import { formatDisplayPath, formatSubtitlePath, resolvePathArg } from "../lib/path.js";
|
|
24
26
|
import { type ToolPendant } from "../lib/pendant.js";
|
|
@@ -150,7 +152,6 @@ export function registerOutlineTool(pi: ExtensionAPI, ctx: AftToolContext): void
|
|
|
150
152
|
truncated,
|
|
151
153
|
params,
|
|
152
154
|
pendant: {
|
|
153
|
-
title: "aft_outline",
|
|
154
155
|
subtitle,
|
|
155
156
|
} satisfies ToolPendant,
|
|
156
157
|
},
|
|
@@ -222,7 +223,6 @@ export function registerZoomTool(pi: ExtensionAPI, ctx: AftToolContext): void {
|
|
|
222
223
|
truncated,
|
|
223
224
|
params,
|
|
224
225
|
pendant: {
|
|
225
|
-
title: "aft_zoom",
|
|
226
226
|
subtitle,
|
|
227
227
|
markdown: buildPendantMarkdown({
|
|
228
228
|
title: "aft_zoom",
|
|
@@ -349,6 +349,78 @@ export function registerCallgraphTool(pi: ExtensionAPI, ctx: AftToolContext): vo
|
|
|
349
349
|
});
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
/**
|
|
353
|
+
* 语义索引进度订阅:索引冷构建期间 Rust 侧每秒推送一次 status_changed 帧
|
|
354
|
+
* (status_debounce_loop),aft-bridge 广播给 subscribeStatus 监听器。搜索
|
|
355
|
+
* 请求阻塞等待索引就绪(最长 SEMANTIC_INDEX_WAIT_TIMEOUT_MS)时,把进度
|
|
356
|
+
* 经 onUpdate 流式显示给用户,而不是无声干等。
|
|
357
|
+
*/
|
|
358
|
+
|
|
359
|
+
/** `BinaryBridge.subscribeStatus` 的能力探测类型:AftProjectTransport 接口上未暴露。 */
|
|
360
|
+
interface StatusSubscribableBridge {
|
|
361
|
+
subscribeStatus?(listener: (snapshot: StatusSnapshot) => void): () => void;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function subscribeBridgeStatus(
|
|
365
|
+
bridge: AftProjectTransport,
|
|
366
|
+
listener: (snapshot: StatusSnapshot) => void,
|
|
367
|
+
): (() => void) | undefined {
|
|
368
|
+
const subscribable = bridge as AftProjectTransport & StatusSubscribableBridge;
|
|
369
|
+
if (typeof subscribable.subscribeStatus !== "function") {
|
|
370
|
+
return undefined;
|
|
371
|
+
}
|
|
372
|
+
return subscribable.subscribeStatus(listener);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Rust 侧 status 快照里语义索引构建进度(SemanticHealthComponentSnapshot 的
|
|
377
|
+
* Building 分支)。上游控制的 schema:只声明真正读取的字段,未声明键忽略。
|
|
378
|
+
*/
|
|
379
|
+
const semanticIndexProgressSchema = Type.Object({
|
|
380
|
+
status: Type.Literal("building"),
|
|
381
|
+
stage: Type.Optional(Type.String()),
|
|
382
|
+
embedded_chunks: Type.Optional(Type.Integer()),
|
|
383
|
+
total_chunks: Type.Optional(Type.Integer()),
|
|
384
|
+
current_batch: Type.Optional(Type.Integer()),
|
|
385
|
+
total_batches: Type.Optional(Type.Integer()),
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
type SemanticIndexProgress = Static<typeof semanticIndexProgressSchema>;
|
|
389
|
+
|
|
390
|
+
/** status 快照 → 一行构建进度文本;非 Building 或形状不符返回 undefined(不显示)。 */
|
|
391
|
+
export function formatSemanticIndexProgress(snapshot: StatusSnapshot): string | undefined {
|
|
392
|
+
const raw = snapshot.semantic_index;
|
|
393
|
+
if (raw === undefined) {
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
let progress: SemanticIndexProgress;
|
|
397
|
+
try {
|
|
398
|
+
progress = Value.Parse(semanticIndexProgressSchema, raw);
|
|
399
|
+
} catch {
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
const parts = [`语义索引构建中${progress.stage === undefined ? "" : ` (${progress.stage})`}`];
|
|
403
|
+
if (
|
|
404
|
+
progress.embedded_chunks !== undefined &&
|
|
405
|
+
progress.total_chunks !== undefined &&
|
|
406
|
+
progress.total_chunks > 0
|
|
407
|
+
) {
|
|
408
|
+
const percent = Math.min(
|
|
409
|
+
100,
|
|
410
|
+
Math.round((progress.embedded_chunks / progress.total_chunks) * 100),
|
|
411
|
+
);
|
|
412
|
+
parts.push(`${progress.embedded_chunks}/${progress.total_chunks} chunks (${percent}%)`);
|
|
413
|
+
}
|
|
414
|
+
if (
|
|
415
|
+
progress.current_batch !== undefined &&
|
|
416
|
+
progress.total_batches !== undefined &&
|
|
417
|
+
progress.total_batches > 0
|
|
418
|
+
) {
|
|
419
|
+
parts.push(`batch ${progress.current_batch}/${progress.total_batches}`);
|
|
420
|
+
}
|
|
421
|
+
return parts.join(" · ");
|
|
422
|
+
}
|
|
423
|
+
|
|
352
424
|
const SearchParams = Type.Object(
|
|
353
425
|
{
|
|
354
426
|
query: Type.String({
|
|
@@ -380,7 +452,7 @@ export function registerSearchTool(pi: ExtensionAPI, ctx: AftToolContext): void
|
|
|
380
452
|
promptSnippet: "Search code by meaning or exact text",
|
|
381
453
|
promptGuidelines: [SEARCH_PROMPT],
|
|
382
454
|
parameters: SearchParams,
|
|
383
|
-
async execute(_id, params, _signal,
|
|
455
|
+
async execute(_id, params, _signal, onUpdate, extCtx) {
|
|
384
456
|
if (typeof params.query !== "string" || params.query.trim().length === 0) {
|
|
385
457
|
throw new Error("'query' must be a non-empty string");
|
|
386
458
|
}
|
|
@@ -390,13 +462,29 @@ export function registerSearchTool(pi: ExtensionAPI, ctx: AftToolContext): void
|
|
|
390
462
|
includeTests: params.includeTests,
|
|
391
463
|
});
|
|
392
464
|
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
465
|
+
const bridge = bridgeFor(ctx);
|
|
466
|
+
const stopProgress =
|
|
467
|
+
onUpdate === undefined
|
|
468
|
+
? undefined
|
|
469
|
+
: subscribeBridgeStatus(bridge, (snapshot) => {
|
|
470
|
+
const text = formatSemanticIndexProgress(snapshot);
|
|
471
|
+
if (text !== undefined) {
|
|
472
|
+
onUpdate({ content: [{ type: "text", text }], details: undefined });
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
let response: Record<string, unknown>;
|
|
476
|
+
let text: string;
|
|
477
|
+
try {
|
|
478
|
+
({ text, response } = await callAftTool(bridge, "search", rawArgs, extCtx, {
|
|
479
|
+
// 默认 search 传输超时仅 60s,会早于索引等待(600s)触发;覆盖为等待
|
|
480
|
+
// 上限 + 常规执行预算。超时只说明响应被挤掉而非 bridge 挂死,保留
|
|
481
|
+
// 常驻的语义索引/LSP 状态。
|
|
482
|
+
transportTimeoutMs: SEMANTIC_INDEX_WAIT_TIMEOUT_MS + 60_000,
|
|
483
|
+
keepBridgeOnTimeout: true,
|
|
484
|
+
}));
|
|
485
|
+
} finally {
|
|
486
|
+
stopProgress?.();
|
|
487
|
+
}
|
|
400
488
|
const truncated = response.truncated === true;
|
|
401
489
|
return {
|
|
402
490
|
content: [{ type: "text", text }],
|