@selesai/code 0.13.21 → 0.13.22

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/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to `@selesai/code` will be documented in this file.
4
4
 
5
+ ## [0.13.22] - 2026-09-12
6
+
7
+ ### Fixed
8
+ - **Live TPS gate.** The live tokens-per-second status no longer reports implausible rates: a live reading now needs at least a 200 ms sample window and is dropped when the rate exceeds the plausible maximum, matching the gates the final measurement already applied.
9
+ - **Capability gateway hint noise.** The gateway no longer injects fuzzy skill recommendations or ambiguous-match catalog hints into the model context. A uniquely matched tool is still auto-activated, and optional capabilities remain discoverable through `capability_catalog`.
10
+
11
+ ### Changed
12
+ - **Capability catalog tool rows.** `capability_catalog` calls now render the search query and optional kind in the TUI instead of the bare tool name.
13
+
5
14
  ## [0.13.21] - 2026-09-11
6
15
 
7
16
  ### Fixed
@@ -13,10 +13,9 @@
13
13
  * - capability_discover validates one catalogued tool and activates its native
14
14
  * definition for the current agent run; capability_skill_show loads exactly
15
15
  * the selected skill instructions.
16
- * - A deterministic router inspects each user prompt and, for a unique
17
- * high-confidence tool match, activates it before the run; for a unique
18
- * high-confidence skill match it adds a concise recommendation; ambiguous
19
- * matches add a catalog hint; unrelated prompts are untouched.
16
+ * - A deterministic router activates a uniquely matched tool before the run.
17
+ * Skills and ambiguous matches remain discoverable through the catalog
18
+ * without injecting fuzzy hints into the model context.
20
19
  * - Temporary activations reset at agent_settled, restoring the baseline
21
20
  * active-tool set.
22
21
  * - The system-prompt skill index is replaced by a compact capability
@@ -28,6 +27,7 @@ import { readFileSync } from "node:fs";
28
27
  import { dirname } from "node:path";
29
28
  import { stripFrontmatter, type ExtensionAPI, type ToolInfo } from "@selesai/code";
30
29
  import { StringEnum } from "@earendil-works/pi-ai";
30
+ import { Text } from "@earendil-works/pi-tui";
31
31
  import { Type } from "typebox";
32
32
  import {
33
33
  buildSkillCatalog,
@@ -177,6 +177,13 @@ export default function capabilityGatewayExtension(pi: ExtensionAPI): void {
177
177
  details: { count: shown.length, total: filtered.length },
178
178
  };
179
179
  },
180
+ renderCall(args, theme) {
181
+ const query = typeof args.query === "string" && args.query.length > 0 ? args.query : "(all)";
182
+ let text = theme.fg("toolTitle", theme.bold("capability_catalog "));
183
+ text += theme.fg("accent", `"${query}"`);
184
+ if (args.kind) text += " " + theme.fg("muted", args.kind);
185
+ return new Text(text, 0, 0);
186
+ },
180
187
  });
181
188
 
182
189
  pi.registerTool({
@@ -276,27 +283,8 @@ export default function capabilityGatewayExtension(pi: ExtensionAPI): void {
276
283
  emitTelemetry(pi, "route_activate", { tool: result.entry.name });
277
284
  return undefined;
278
285
  }
279
- if (result.action === "recommend" && result.entry) {
280
- emitTelemetry(pi, "route_recommend", { skill: result.entry.name });
281
- return {
282
- message: {
283
- customType: "capability-gateway-hint",
284
- content: `The request matches the optional skill "${result.entry.name}". Load it with capability_skill_show before applying it.`,
285
- display: false,
286
- },
287
- };
288
- }
289
- if (result.action === "hint" && result.candidates) {
290
- const names = result.candidates.map((c) => c.name).join(", ");
291
- emitTelemetry(pi, "route_hint", { candidates: result.candidates.map((c) => c.name) });
292
- return {
293
- message: {
294
- customType: "capability-gateway-hint",
295
- content: `The request may match optional capabilities: ${names}. Search capability_catalog to confirm before selecting.`,
296
- display: false,
297
- },
298
- };
299
- }
286
+ // Do not inject fuzzy recommendations or catalog hints into the model
287
+ // context. It can discover capabilities when it actually needs one.
300
288
  return undefined;
301
289
  });
302
290
 
@@ -175,6 +175,19 @@ describe("capability gateway integration", () => {
175
175
  expect(result.details).toMatchObject({ count: 1 });
176
176
  });
177
177
 
178
+ it("renders the query and kind in the tool call line", async () => {
179
+ const h = await createGatewaySession({ enabled: true });
180
+ harnesses.push(h);
181
+ const catalog = h.session.getToolDefinition("capability_catalog");
182
+ const theme = { fg: (_name: string, text: string) => text, bold: (text: string) => text } as never;
183
+ const draw = (args: unknown) =>
184
+ (catalog!.renderCall!(args as never, theme, {} as never) as { render(width: number): string[] }).render(120).join("\n");
185
+
186
+ expect(draw({ query: "ponytail-debt", kind: "skill" })).toContain('"ponytail-debt"');
187
+ expect(draw({ query: "ponytail-debt", kind: "skill" })).toContain("skill");
188
+ expect(draw({})).toContain("(all)");
189
+ });
190
+
178
191
  it("activates a discovered tool for the run and resets after agent_settled", async () => {
179
192
  const h = await createGatewaySession({ enabled: true });
180
193
  harnesses.push(h);
@@ -212,26 +225,35 @@ describe("capability gateway integration", () => {
212
225
  expect(h.session.getActiveToolNames()).toContain("grep_app_search");
213
226
  });
214
227
 
215
- it("does not auto-load skills from fuzzy matching", async () => {
228
+ it("does not inject fuzzy skill recommendations", async () => {
216
229
  const h = await createGatewaySession({
217
230
  enabled: true,
218
231
  withSkill: true,
219
232
  extensions: [GATEWAY_DIR, GREP_APP_DIR],
220
233
  });
221
234
  harnesses.push(h);
222
- const runner = h.session.extensionRunner;
223
- const result = await runner.emitBeforeAgentStart(
235
+ const result = await h.session.extensionRunner.emitBeforeAgentStart(
224
236
  "do research on this topic",
225
237
  undefined,
226
238
  h.session.systemPrompt,
227
239
  { cwd: process.cwd() } as never,
228
240
  );
229
- // A recommendation message is injected, but the skill body is not loaded
230
- // into the prompt and the skill is not auto-activated.
231
- expect(result?.messages?.[0]?.content).toContain("research");
241
+ expect(result).toBeUndefined();
232
242
  expect(h.session.systemPrompt).not.toContain("Research instructions body.");
233
243
  });
234
244
 
245
+ it("does not inject catalog hints for ambiguous tool matches", async () => {
246
+ const h = await createGatewaySession({ enabled: true, extensions: [GATEWAY_DIR, GREP_APP_DIR] });
247
+ harnesses.push(h);
248
+ const result = await h.session.extensionRunner.emitBeforeAgentStart(
249
+ "grep app",
250
+ undefined,
251
+ h.session.systemPrompt,
252
+ { cwd: process.cwd() } as never,
253
+ );
254
+ expect(result).toBeUndefined();
255
+ });
256
+
235
257
  it("does not recommend capability_skill_show for an inline-loaded $skill", async () => {
236
258
  const h = await createGatewaySession({
237
259
  enabled: true,
@@ -10,6 +10,7 @@
10
10
  "peerDependencies": {
11
11
  "@selesai/code": "*",
12
12
  "@earendil-works/pi-ai": "*",
13
+ "@earendil-works/pi-tui": "*",
13
14
  "typebox": "*"
14
15
  }
15
16
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { calculateReliableTps, type TpsTiming } from "./tps.ts";
2
+ import { calculateLiveTps, calculateReliableTps, type TpsTiming } from "./tps.ts";
3
3
 
4
4
  function timing(overrides: Partial<TpsTiming> = {}): TpsTiming {
5
5
  return {
@@ -15,6 +15,14 @@ function timing(overrides: Partial<TpsTiming> = {}): TpsTiming {
15
15
  };
16
16
  }
17
17
 
18
+ describe("calculateLiveTps", () => {
19
+ it("waits for a usable sample and rejects implausible rates", () => {
20
+ expect(calculateLiveTps(60, 0.2)).toBeNull();
21
+ expect(calculateLiveTps(100, 200)).toBe(500);
22
+ expect(calculateLiveTps(10_001, 1_000)).toBeNull();
23
+ });
24
+ });
25
+
18
26
  describe("calculateReliableTps", () => {
19
27
  it("uses active stream time for sufficiently sampled output", () => {
20
28
  expect(calculateReliableTps(100, timing())).toEqual({
@@ -75,6 +75,12 @@ export function calculateReliableTps(outputTokens: number, timing: TpsTiming): R
75
75
  return { tps, effectiveMs, isPrimary };
76
76
  }
77
77
 
78
+ export function calculateLiveTps(tokens: number, elapsedMs: number): number | null {
79
+ if (tokens <= 0 || elapsedMs < MIN_GENERATION_MS) return null;
80
+ const tps = Math.round(tokens / (elapsedMs / 1000));
81
+ return tps <= MAX_PLAUSIBLE_TPS ? tps : null;
82
+ }
83
+
78
84
  export default function tpsExtension(pi: ExtensionAPI): void {
79
85
  setupTpsTracker(pi);
80
86
  }
@@ -223,11 +229,11 @@ export function setupTpsTracker(pi: ExtensionAPI): void {
223
229
 
224
230
  streamStart ??= now;
225
231
  estimatedStreamedTokens += Math.max(0, streamEvent.delta.length / 4);
226
- const elapsed = (now - streamStart) / 1000;
227
232
  const officialTokens = generatedTokensFromUsage(asRecord(event.message.usage));
228
233
  const currentTokens = officialTokens > 0 ? officialTokens : estimatedStreamedTokens;
229
- if (elapsed > 0 && currentTokens > 0) {
230
- ctx.ui.setStatus("tps", ctx.ui.theme.fg("accent", `${Math.round(currentTokens / elapsed)} tok/s`));
234
+ const tps = calculateLiveTps(currentTokens, now - streamStart);
235
+ if (tps !== null) {
236
+ ctx.ui.setStatus("tps", ctx.ui.theme.fg("accent", `${tps} tok/s`));
231
237
  }
232
238
  });
233
239
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selesai/code",
3
- "version": "0.13.21",
3
+ "version": "0.13.22",
4
4
  "description": "Maintained, extension-first Pi coding agent with built-in workflows, subagents, web research, questions, skills, and an enhanced terminal UI.",
5
5
  "type": "module",
6
6
  "repository": {