@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 +14 -0
- package/dist/extensions/capability-gateway/index.ts +25 -35
- package/dist/extensions/capability-gateway/integration.test.ts +46 -6
- package/dist/extensions/capability-gateway/package.json +1 -0
- package/dist/extensions/tps.test.ts +9 -1
- package/dist/extensions/tps.ts +9 -3
- package/package.json +1 -1
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
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
-
?
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
@@ -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({
|
package/dist/extensions/tps.ts
CHANGED
|
@@ -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
|
-
|
|
230
|
-
|
|
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.
|
|
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": {
|