@trim21/personal-pi-extensions 0.1.500 → 0.1.501

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.1.500",
3
+ "version": "0.1.501",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
package/src/aft/bridge.ts CHANGED
@@ -150,6 +150,9 @@ export async function createAftPool(
150
150
  * 调用 AFT 工具命令并返回 Rust 格式化好的文本。
151
151
  * 只读感知工具统一走 toolCall(server 侧 tool_call 分派)。`softCodes` 里的
152
152
  * 错误码(如 symbol_not_found)是合法否定答案,不抛错、按文本返回。
153
+ *
154
+ * `signal`:宿主取消信号,透传给 bridge 的 abortSignal——standalone transport
155
+ * 会在 abort 时向 Rust 发 cancel_request(subc route 忽略,靠 route 关闭取消)。
153
156
  */
154
157
  export async function callAftTool(
155
158
  bridge: AftProjectTransport,
@@ -158,12 +161,14 @@ export async function callAftTool(
158
161
  extCtx: ExtensionContext,
159
162
  options?: BridgeRequestOptions & { preview?: boolean },
160
163
  softCodes?: ReadonlySet<string>,
164
+ signal?: AbortSignal,
161
165
  ): Promise<{ text: string; response: Record<string, unknown> }> {
162
166
  const timeoutMs = timeoutForCommand(command);
163
167
  const sessionId = resolveSessionId(extCtx);
164
168
  const sendOptions = {
165
169
  ...(timeoutMs !== undefined && { timeoutMs }),
166
170
  ...options,
171
+ ...(signal !== undefined && !options?.abortSignal && { abortSignal: signal }),
167
172
  };
168
173
  const response = await bridge.toolCall(
169
174
  sessionId,
package/src/aft/tools.ts CHANGED
@@ -126,7 +126,7 @@ export function registerOutlineTool(pi: ExtensionAPI, ctx: AftToolContext): void
126
126
  promptSnippet: "Output structural outline of a file/directory",
127
127
  promptGuidelines: [OUTLINE_PROMPT],
128
128
  parameters: OutlineParams,
129
- async execute(_id, params, _signal, _onUpdate, extCtx) {
129
+ async execute(_id, params, signal, _onUpdate, extCtx) {
130
130
  const target = coerceTargetParam(params.target);
131
131
  if (typeof target !== "string" || target.length === 0) {
132
132
  throw new Error("'target' must be a single path (array targets are not supported)");
@@ -145,7 +145,15 @@ export function registerOutlineTool(pi: ExtensionAPI, ctx: AftToolContext): void
145
145
 
146
146
  const subtitle = buildOutlineSubtitle(extCtx.cwd, target);
147
147
 
148
- const { text, response } = await callAftTool(bridgeFor(ctx), "outline", rawArgs, extCtx);
148
+ const { text, response } = await callAftTool(
149
+ bridgeFor(ctx),
150
+ "outline",
151
+ rawArgs,
152
+ extCtx,
153
+ undefined,
154
+ undefined,
155
+ signal,
156
+ );
149
157
  const truncated = response.truncated === true;
150
158
  return {
151
159
  content: [{ type: "text", text }],
@@ -201,7 +209,7 @@ export function registerZoomTool(pi: ExtensionAPI, ctx: AftToolContext): void {
201
209
  promptSnippet: "Inspect the full source of a named symbol",
202
210
  promptGuidelines: [ZOOM_PROMPT],
203
211
  parameters: ZoomParams,
204
- async execute(_id, params, _signal, _onUpdate, extCtx) {
212
+ async execute(_id, params, signal, _onUpdate, extCtx) {
205
213
  const rawArgs = compactArgs({
206
214
  filePath: resolvePathArg(extCtx.cwd, params.path),
207
215
  symbols: params.symbols,
@@ -216,7 +224,15 @@ export function registerZoomTool(pi: ExtensionAPI, ctx: AftToolContext): void {
216
224
 
217
225
  const subtitle = buildZoomSubtitle(extCtx.cwd, params);
218
226
 
219
- const { text, response } = await callAftTool(bridgeFor(ctx), "zoom", rawArgs, extCtx);
227
+ const { text, response } = await callAftTool(
228
+ bridgeFor(ctx),
229
+ "zoom",
230
+ rawArgs,
231
+ extCtx,
232
+ undefined,
233
+ undefined,
234
+ signal,
235
+ );
220
236
  const truncated = response.truncated === true;
221
237
  return {
222
238
  content: [{ type: "text", text }],
@@ -305,12 +321,16 @@ export async function callCallgraphWithBuildRetry(
305
321
  bridge: AftProjectTransport,
306
322
  rawArgs: Record<string, unknown>,
307
323
  extCtx: ExtensionContext,
324
+ signal?: AbortSignal,
308
325
  timing?: { budgetMs: number; intervalMs: number },
309
326
  ): Promise<{ text: string; response: Record<string, unknown> }> {
310
327
  const budgetMs = timing?.budgetMs ?? CALLGRAPH_BUILD_RETRY_BUDGET_MS;
311
328
  const intervalMs = timing?.intervalMs ?? CALLGRAPH_BUILD_RETRY_INTERVAL_MS;
312
329
  const deadline = Date.now() + budgetMs;
313
330
  for (;;) {
331
+ if (signal?.aborted) {
332
+ throw new Error("callgraph request aborted");
333
+ }
314
334
  const { text, response } = await callAftTool(
315
335
  bridge,
316
336
  "callgraph",
@@ -318,6 +338,7 @@ export async function callCallgraphWithBuildRetry(
318
338
  extCtx,
319
339
  undefined,
320
340
  CALLGRAPH_SOFT_CODES,
341
+ signal,
321
342
  );
322
343
  const code = typeof response.code === "string" ? response.code : "";
323
344
  if (code !== "callgraph_building" || Date.now() >= deadline) {
@@ -341,7 +362,7 @@ export function registerCallgraphTool(pi: ExtensionAPI, ctx: AftToolContext): vo
341
362
  promptSnippet: "Call graph and data-flow navigation",
342
363
  promptGuidelines: [CALLGRAPH_PROMPT],
343
364
  parameters: CallgraphParams,
344
- async execute(_id, params, _signal, _onUpdate, extCtx) {
365
+ async execute(_id, params, signal, _onUpdate, extCtx) {
345
366
  const rawArgs = compactArgs({
346
367
  op: params.op,
347
368
  filePath: resolvePathArg(extCtx.cwd, params.path),
@@ -354,7 +375,12 @@ export function registerCallgraphTool(pi: ExtensionAPI, ctx: AftToolContext): vo
354
375
  includeUnresolved: params.includeUnresolved,
355
376
  });
356
377
 
357
- const { text, response } = await callCallgraphWithBuildRetry(bridgeFor(ctx), rawArgs, extCtx);
378
+ const { text, response } = await callCallgraphWithBuildRetry(
379
+ bridgeFor(ctx),
380
+ rawArgs,
381
+ extCtx,
382
+ signal,
383
+ );
358
384
  const out =
359
385
  text ||
360
386
  formatCallgraphSections(params.op, response, PLAIN_CALLGRAPH_THEME, {
@@ -483,7 +509,7 @@ export function registerSearchTool(pi: ExtensionAPI, ctx: AftToolContext): void
483
509
  promptSnippet: "Search code by meaning or exact text",
484
510
  promptGuidelines: [SEARCH_PROMPT],
485
511
  parameters: SearchParams,
486
- async execute(_id, params, _signal, onUpdate, extCtx) {
512
+ async execute(_id, params, signal, onUpdate, extCtx) {
487
513
  if (typeof params.query !== "string" || params.query.trim().length === 0) {
488
514
  throw new Error("'query' must be a non-empty string");
489
515
  }
@@ -506,13 +532,21 @@ export function registerSearchTool(pi: ExtensionAPI, ctx: AftToolContext): void
506
532
  let response: Record<string, unknown>;
507
533
  let text: string;
508
534
  try {
509
- ({ text, response } = await callAftTool(bridge, "search", rawArgs, extCtx, {
510
- // 默认 search 传输超时仅 60s,会早于索引等待(600s)触发;覆盖为等待
511
- // 上限 + 常规执行预算。超时只说明响应被挤掉而非 bridge 挂死,保留
512
- // 常驻的语义索引/LSP 状态。
513
- transportTimeoutMs: SEMANTIC_INDEX_WAIT_TIMEOUT_MS + 60_000,
514
- keepBridgeOnTimeout: true,
515
- }));
535
+ ({ text, response } = await callAftTool(
536
+ bridge,
537
+ "search",
538
+ rawArgs,
539
+ extCtx,
540
+ {
541
+ // 默认 search 传输超时仅 60s,会早于索引等待(600s)触发;覆盖为等待
542
+ // 上限 + 常规执行预算。超时只说明响应被挤掉而非 bridge 挂死,保留
543
+ // 常驻的语义索引/LSP 状态。
544
+ transportTimeoutMs: SEMANTIC_INDEX_WAIT_TIMEOUT_MS + 60_000,
545
+ keepBridgeOnTimeout: true,
546
+ },
547
+ undefined,
548
+ signal,
549
+ ));
516
550
  } finally {
517
551
  stopProgress?.();
518
552
  }