@selesai/code 0.13.19 → 0.13.21
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 +12 -0
- package/dist/defaults/models.json +22 -3
- package/dist/extensions/capability-gateway/catalog.ts +26 -2
- package/dist/extensions/capability-gateway/index.test.ts +9 -0
- package/dist/extensions/capability-gateway/index.ts +12 -10
- package/dist/extensions/capability-gateway/integration.test.ts +18 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@selesai/code` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.13.21] - 2026-09-11
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **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.
|
|
9
|
+
|
|
10
|
+
## [0.13.20] - 2026-09-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **DeepSeek V4.1 Flash in default catalog.** Added `deepseek-v4.1-flash` (text+image, 512K context, 128K max tokens, deepseek thinking format).
|
|
14
|
+
- **Vision for DeepSeek V4 Pro.** `deepseek-v4-pro` now accepts text+image input with 128K max tokens.
|
|
15
|
+
- **Typo-tolerant capability routing.** The capability gateway now scores one-character-off name/alias matches as exact, so a misspelled tool name (e.g. `inercom` → `intercom`) auto-activates when uniquely identifiable.
|
|
16
|
+
|
|
5
17
|
## [0.13.19] - 2026-09-09
|
|
6
18
|
|
|
7
19
|
### Changed
|
|
@@ -105,16 +105,35 @@
|
|
|
105
105
|
"requiresReasoningContentOnAssistantMessages": true
|
|
106
106
|
}
|
|
107
107
|
},
|
|
108
|
+
{
|
|
109
|
+
"id": "deepseek-v4.1-flash",
|
|
110
|
+
"name": "DeepSeek V4.1 Flash",
|
|
111
|
+
"reasoning": true,
|
|
112
|
+
"input": ["text", "image"],
|
|
113
|
+
"contextWindow": 512000,
|
|
114
|
+
"maxTokens": 128000,
|
|
115
|
+
"thinkingLevelMap": {
|
|
116
|
+
"minimal": null,
|
|
117
|
+
"low": null,
|
|
118
|
+
"medium": null,
|
|
119
|
+
"high": "high",
|
|
120
|
+
"xhigh": null,
|
|
121
|
+
"max": "max"
|
|
122
|
+
},
|
|
123
|
+
"compat": {
|
|
124
|
+
"requiresReasoningContentOnAssistantMessages": true
|
|
125
|
+
}
|
|
126
|
+
},
|
|
108
127
|
{
|
|
109
128
|
"id": "deepseek-v4-pro",
|
|
110
129
|
"name": "DeepSeek V4 pro",
|
|
111
130
|
"reasoning": true,
|
|
112
|
-
"input": ["text"],
|
|
131
|
+
"input": ["text", "image"],
|
|
113
132
|
"contextWindow": 512000,
|
|
114
|
-
"maxTokens":
|
|
133
|
+
"maxTokens": 128000,
|
|
115
134
|
"thinkingLevelMap": {
|
|
116
135
|
"minimal": null,
|
|
117
|
-
"low":
|
|
136
|
+
"low": null,
|
|
118
137
|
"medium": null,
|
|
119
138
|
"high": "high",
|
|
120
139
|
"xhigh": null,
|
|
@@ -86,6 +86,29 @@ export function tokenize(text: string): string[] {
|
|
|
86
86
|
return normalizeQuery(text).split(/\s+/).filter(Boolean);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** True when two words differ by at most one insertion, deletion, or substitution. */
|
|
90
|
+
function differsByOne(left: string, right: string): boolean {
|
|
91
|
+
if (Math.abs(left.length - right.length) > 1) return false;
|
|
92
|
+
let leftIndex = 0;
|
|
93
|
+
let rightIndex = 0;
|
|
94
|
+
let differences = 0;
|
|
95
|
+
while (leftIndex < left.length && rightIndex < right.length) {
|
|
96
|
+
if (left[leftIndex] === right[rightIndex]) {
|
|
97
|
+
leftIndex++;
|
|
98
|
+
rightIndex++;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (++differences > 1) return false;
|
|
102
|
+
if (left.length > right.length) leftIndex++;
|
|
103
|
+
else if (right.length > left.length) rightIndex++;
|
|
104
|
+
else {
|
|
105
|
+
leftIndex++;
|
|
106
|
+
rightIndex++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
89
112
|
/** Words that carry no routing signal. */
|
|
90
113
|
const STOPWORDS = new Set([
|
|
91
114
|
"the", "a", "an", "to", "of", "for", "with", "and", "or", "use", "using", "me", "my", "i",
|
|
@@ -103,8 +126,8 @@ export interface RouteResult {
|
|
|
103
126
|
/**
|
|
104
127
|
* Deterministic routing over the compact catalog.
|
|
105
128
|
*
|
|
106
|
-
* - Exact name/alias
|
|
107
|
-
* shared with the name/alias or summary adds 1.
|
|
129
|
+
* - Exact or one-character-off name/alias matches score 3; prefix matches score 2;
|
|
130
|
+
* each content token shared with the name/alias or summary adds 1.
|
|
108
131
|
* - A unique tool with score >= 3 is auto-activated.
|
|
109
132
|
* - A unique skill with score >= 3 is recommended (never auto-loaded).
|
|
110
133
|
* - Score 2 or a tie at the top produces a catalog-discovery hint.
|
|
@@ -127,6 +150,7 @@ export function route(query: string, catalog: CatalogEntry[]): RouteResult {
|
|
|
127
150
|
}
|
|
128
151
|
for (const token of contentTokens) {
|
|
129
152
|
if (nameTokens.has(token)) score += 3;
|
|
153
|
+
else if (token.length >= 5 && [...nameTokens].some((nameToken) => nameToken.length >= 5 && differsByOne(token, nameToken))) score += 3;
|
|
130
154
|
else if (token.length >= 3 && [...nameTokens].some((nameToken) => nameToken.startsWith(token))) score += 2;
|
|
131
155
|
if (summaryTokens.has(token)) score += 1;
|
|
132
156
|
}
|
|
@@ -102,6 +102,15 @@ describe("deterministic routing", () => {
|
|
|
102
102
|
expect(result.entry?.name).toBe("grep_app_search");
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
+
it("auto-activates a uniquely identifiable one-character tool typo", () => {
|
|
106
|
+
const result = route("activate inercom", [
|
|
107
|
+
...catalog,
|
|
108
|
+
{ name: "intercom", kind: "tool", summary: "Coordinate with other local sessions", aliases: [], category: "coordination" },
|
|
109
|
+
]);
|
|
110
|
+
expect(result.action).toBe("activate");
|
|
111
|
+
expect(result.entry?.name).toBe("intercom");
|
|
112
|
+
});
|
|
113
|
+
|
|
105
114
|
it("recommends a unique high-confidence skill without auto-loading", () => {
|
|
106
115
|
const result = route("do research on this topic", catalog);
|
|
107
116
|
expect(result.action).toBe("recommend");
|
|
@@ -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 {
|
|
@@ -157,6 +157,24 @@ 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
|
+
|
|
160
178
|
it("activates a discovered tool for the run and resets after agent_settled", async () => {
|
|
161
179
|
const h = await createGatewaySession({ enabled: true });
|
|
162
180
|
harnesses.push(h);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selesai/code",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.21",
|
|
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": {
|