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