@selesai/code 0.13.20 → 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,20 @@
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
+
14
+ ## [0.13.21] - 2026-09-11
15
+
16
+ ### Fixed
17
+ - **Capability catalog unique-match results.** `capability_catalog` now returns a uniquely routed natural-language tool or skill match instead of falling back to a literal full-query search and incorrectly reporting no matching capabilities.
18
+
5
19
  ## [0.13.20] - 2026-09-10
6
20
 
7
21
  ### Added
@@ -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,
@@ -158,16 +158,18 @@ export default function capabilityGatewayExtension(pi: ExtensionAPI): void {
158
158
  const shown =
159
159
  params.query && matched.candidates
160
160
  ? matched.candidates
161
- : params.query
162
- ? filtered.filter((entry) => {
163
- const q = params.query!.toLowerCase();
164
- return (
165
- entry.name.toLowerCase().includes(q) ||
166
- entry.summary.toLowerCase().includes(q) ||
167
- entry.aliases.some((alias) => alias.toLowerCase().includes(q))
168
- );
169
- })
170
- : filtered;
161
+ : params.query && matched.entry
162
+ ? [matched.entry]
163
+ : params.query
164
+ ? filtered.filter((entry) => {
165
+ const q = params.query!.toLowerCase();
166
+ return (
167
+ entry.name.toLowerCase().includes(q) ||
168
+ entry.summary.toLowerCase().includes(q) ||
169
+ entry.aliases.some((alias) => alias.toLowerCase().includes(q))
170
+ );
171
+ })
172
+ : filtered;
171
173
  const text = formatCatalog(shown);
172
174
  emitTelemetry(pi, "catalog", { query: params.query ?? "", kind: params.kind ?? "", results: shown.length });
173
175
  return {
@@ -175,6 +177,13 @@ export default function capabilityGatewayExtension(pi: ExtensionAPI): void {
175
177
  details: { count: shown.length, total: filtered.length },
176
178
  };
177
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
+ },
178
187
  });
179
188
 
180
189
  pi.registerTool({
@@ -274,27 +283,8 @@ export default function capabilityGatewayExtension(pi: ExtensionAPI): void {
274
283
  emitTelemetry(pi, "route_activate", { tool: result.entry.name });
275
284
  return undefined;
276
285
  }
277
- if (result.action === "recommend" && result.entry) {
278
- emitTelemetry(pi, "route_recommend", { skill: result.entry.name });
279
- return {
280
- message: {
281
- customType: "capability-gateway-hint",
282
- content: `The request matches the optional skill "${result.entry.name}". Load it with capability_skill_show before applying it.`,
283
- display: false,
284
- },
285
- };
286
- }
287
- if (result.action === "hint" && result.candidates) {
288
- const names = result.candidates.map((c) => c.name).join(", ");
289
- emitTelemetry(pi, "route_hint", { candidates: result.candidates.map((c) => c.name) });
290
- return {
291
- message: {
292
- customType: "capability-gateway-hint",
293
- content: `The request may match optional capabilities: ${names}. Search capability_catalog to confirm before selecting.`,
294
- display: false,
295
- },
296
- };
297
- }
286
+ // Do not inject fuzzy recommendations or catalog hints into the model
287
+ // context. It can discover capabilities when it actually needs one.
298
288
  return undefined;
299
289
  });
300
290
 
@@ -157,6 +157,37 @@ describe("capability gateway integration", () => {
157
157
  expect(block).toContain("capability-gateway");
158
158
  });
159
159
 
160
+ it("returns a uniquely routed natural-language catalog match", async () => {
161
+ const h = await createGatewaySession({ enabled: true, extensions: [GATEWAY_DIR, GREP_APP_DIR] });
162
+ harnesses.push(h);
163
+ const catalog = h.session.getToolDefinition("capability_catalog");
164
+ expect(catalog).toBeDefined();
165
+
166
+ const result = await catalog!.execute(
167
+ "call-1",
168
+ { query: "search GitHub code with grep_app_search" },
169
+ undefined,
170
+ undefined,
171
+ {} as never,
172
+ );
173
+ expect(result.content[0]!.type).toBe("text");
174
+ expect(String(result.content[0]!.text)).toContain("grep_app_search");
175
+ expect(result.details).toMatchObject({ count: 1 });
176
+ });
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
+
160
191
  it("activates a discovered tool for the run and resets after agent_settled", async () => {
161
192
  const h = await createGatewaySession({ enabled: true });
162
193
  harnesses.push(h);
@@ -194,26 +225,35 @@ describe("capability gateway integration", () => {
194
225
  expect(h.session.getActiveToolNames()).toContain("grep_app_search");
195
226
  });
196
227
 
197
- it("does not auto-load skills from fuzzy matching", async () => {
228
+ it("does not inject fuzzy skill recommendations", async () => {
198
229
  const h = await createGatewaySession({
199
230
  enabled: true,
200
231
  withSkill: true,
201
232
  extensions: [GATEWAY_DIR, GREP_APP_DIR],
202
233
  });
203
234
  harnesses.push(h);
204
- const runner = h.session.extensionRunner;
205
- const result = await runner.emitBeforeAgentStart(
235
+ const result = await h.session.extensionRunner.emitBeforeAgentStart(
206
236
  "do research on this topic",
207
237
  undefined,
208
238
  h.session.systemPrompt,
209
239
  { cwd: process.cwd() } as never,
210
240
  );
211
- // A recommendation message is injected, but the skill body is not loaded
212
- // into the prompt and the skill is not auto-activated.
213
- expect(result?.messages?.[0]?.content).toContain("research");
241
+ expect(result).toBeUndefined();
214
242
  expect(h.session.systemPrompt).not.toContain("Research instructions body.");
215
243
  });
216
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
+
217
257
  it("does not recommend capability_skill_show for an inline-loaded $skill", async () => {
218
258
  const h = await createGatewaySession({
219
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.20",
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": {