@selesai/code 0.13.30 → 0.13.32
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 +10 -0
- package/dist/core/remote-catalog-provider.js +5 -1
- package/dist/core/remote-catalog-provider.test.d.ts +1 -0
- package/dist/core/remote-catalog-provider.test.js +29 -0
- package/dist/core/settings-manager-auto-handoff.test.js +11 -0
- package/dist/core/settings-manager.d.ts +10 -0
- package/dist/core/settings-manager.js +6 -0
- package/dist/defaults/settings.json +5 -0
- package/dist/extensions/capability-gateway/index.ts +26 -8
- package/dist/extensions/capability-gateway/integration.test.ts +59 -6
- package/dist/extensions/capability-gateway/routing.test.ts +11 -5
- package/dist/extensions/capability-gateway/routing.ts +6 -6
- package/dist/extensions/pi-web-agent/src/extension.ts +2 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.js +12 -0
- package/dist/modes/interactive/interactive-mode.js +18 -0
- package/docs/settings.md +8 -7
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@selesai/code` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.13.32] - 2026-09-24
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Cached provider model catalogs are no longer discarded when `Last-Modified` is missing.** Models such as `openai-codex/gpt-6-sol` and `openai-codex/gpt-6-luna` remain available after a catalog refresh when the cached catalog is newer than the bundled metadata.
|
|
9
|
+
|
|
10
|
+
## [0.13.31] - 2026-09-23
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- **Jev capability tie-breaking is now on by default.** Toggle ambiguous-tool tie-breaking in `/settings`. It requires Token-In credentials; without them, Jev is not called and Selesai prompts you to run `/tokenin add`. Deterministic routing remains the fallback.
|
|
14
|
+
|
|
5
15
|
## [0.13.30] - 2026-09-23
|
|
6
16
|
|
|
7
17
|
### Added
|
|
@@ -30,7 +30,11 @@ function parseCatalog(providerId, value) {
|
|
|
30
30
|
function remoteModels(entry, localGeneratedAt) {
|
|
31
31
|
if (!entry)
|
|
32
32
|
return [];
|
|
33
|
-
|
|
33
|
+
// Some catalog responses (and the 404/501 fallback) have no usable
|
|
34
|
+
// Last-Modified value. Use the completed check time instead so a cached
|
|
35
|
+
// catalog is not discarded merely because that header was absent.
|
|
36
|
+
const remoteGeneratedAt = entry.lastModified && entry.lastModified > 0 ? entry.lastModified : entry.checkedAt;
|
|
37
|
+
if (localGeneratedAt !== undefined && (remoteGeneratedAt === undefined || remoteGeneratedAt <= localGeneratedAt)) {
|
|
34
38
|
return [];
|
|
35
39
|
}
|
|
36
40
|
return entry.models;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { withRemoteCatalog } from "./remote-catalog-provider.js";
|
|
3
|
+
const cachedModel = {
|
|
4
|
+
id: "gpt-6-sol",
|
|
5
|
+
provider: "openai-codex",
|
|
6
|
+
};
|
|
7
|
+
function refreshContext(stored) {
|
|
8
|
+
return {
|
|
9
|
+
stored,
|
|
10
|
+
allowNetwork: false,
|
|
11
|
+
signal: new AbortController().signal,
|
|
12
|
+
publish: async ({ update }) => {
|
|
13
|
+
update?.();
|
|
14
|
+
return true;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
describe("remote model catalog cache", () => {
|
|
19
|
+
it("keeps a newer cached catalog when Last-Modified is unavailable", async () => {
|
|
20
|
+
const localGeneratedAt = 100;
|
|
21
|
+
const provider = {
|
|
22
|
+
id: "openai-codex",
|
|
23
|
+
getModels: () => [],
|
|
24
|
+
};
|
|
25
|
+
const wrapped = withRemoteCatalog(provider, "https://example.test", localGeneratedAt);
|
|
26
|
+
await wrapped.refreshModels?.(refreshContext({ models: [cachedModel], checkedAt: localGeneratedAt + 1, lastModified: 0 }));
|
|
27
|
+
expect(wrapped.getModels().map((model) => model.id)).toEqual(["gpt-6-sol"]);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -39,4 +39,15 @@ describe("auto handoff settings", () => {
|
|
|
39
39
|
await manager.flush();
|
|
40
40
|
expect(manager.getAutoHandoffThresholdTokens()).toBe(128_500);
|
|
41
41
|
});
|
|
42
|
+
it("defaults Jev capability routing on and persists the /settings toggle", async () => {
|
|
43
|
+
const manager = SettingsManager.inMemory();
|
|
44
|
+
expect(manager.getCapabilityGatewayJevEnabled()).toBe(true);
|
|
45
|
+
manager.setCapabilityGatewayJevEnabled(false);
|
|
46
|
+
await manager.flush();
|
|
47
|
+
expect(manager.getCapabilityGatewayJevEnabled()).toBe(false);
|
|
48
|
+
expect(manager.getGlobalSettings().capabilityGateway?.routing?.jev?.enabled).toBe(false);
|
|
49
|
+
manager.setCapabilityGatewayJevEnabled(true);
|
|
50
|
+
await manager.flush();
|
|
51
|
+
expect(manager.getCapabilityGatewayJevEnabled()).toBe(true);
|
|
52
|
+
});
|
|
42
53
|
});
|
|
@@ -64,6 +64,13 @@ export interface AutoHandoffSettings {
|
|
|
64
64
|
enabled?: boolean;
|
|
65
65
|
thresholdTokens?: number;
|
|
66
66
|
}
|
|
67
|
+
export interface CapabilityGatewaySettings {
|
|
68
|
+
routing?: {
|
|
69
|
+
jev?: {
|
|
70
|
+
enabled?: boolean;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
}
|
|
67
74
|
export type DefaultProjectTrust = "ask" | "always" | "never";
|
|
68
75
|
export type TransportSetting = Transport;
|
|
69
76
|
/**
|
|
@@ -130,6 +137,7 @@ export interface Settings {
|
|
|
130
137
|
markdown?: MarkdownSettings;
|
|
131
138
|
warnings?: WarningSettings;
|
|
132
139
|
autoHandoff?: AutoHandoffSettings;
|
|
140
|
+
capabilityGateway?: CapabilityGatewaySettings;
|
|
133
141
|
sessionDir?: string;
|
|
134
142
|
extensionHost?: Record<string, "selesai" | "pi">;
|
|
135
143
|
httpProxy?: string;
|
|
@@ -361,6 +369,8 @@ export declare class SettingsManager {
|
|
|
361
369
|
getWarnings(): WarningSettings;
|
|
362
370
|
setWarnings(warnings: WarningSettings): void;
|
|
363
371
|
getAutoHandoffEnabled(): boolean;
|
|
372
|
+
getCapabilityGatewayJevEnabled(): boolean;
|
|
373
|
+
setCapabilityGatewayJevEnabled(enabled: boolean): void;
|
|
364
374
|
setAutoHandoffEnabled(enabled: boolean): void;
|
|
365
375
|
getAutoHandoffThresholdTokens(): number;
|
|
366
376
|
setAutoHandoffThresholdTokens(tokens: number): void;
|
|
@@ -1087,6 +1087,12 @@ export class SettingsManager {
|
|
|
1087
1087
|
getAutoHandoffEnabled() {
|
|
1088
1088
|
return this.settings.autoHandoff?.enabled ?? DEFAULT_AUTO_HANDOFF_SETTINGS.enabled;
|
|
1089
1089
|
}
|
|
1090
|
+
getCapabilityGatewayJevEnabled() {
|
|
1091
|
+
return this.settings.capabilityGateway?.routing?.jev?.enabled ?? true;
|
|
1092
|
+
}
|
|
1093
|
+
setCapabilityGatewayJevEnabled(enabled) {
|
|
1094
|
+
this.setGlobalSettings({ capabilityGateway: { routing: { jev: { enabled } } } });
|
|
1095
|
+
}
|
|
1090
1096
|
setAutoHandoffEnabled(enabled) {
|
|
1091
1097
|
if (!this.globalSettings.autoHandoff) {
|
|
1092
1098
|
this.globalSettings.autoHandoff = {};
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* - A deterministic router activates a uniquely matched tool before the run.
|
|
17
17
|
* Skills and ambiguous matches remain discoverable through the catalog
|
|
18
18
|
* without injecting fuzzy hints into the model context.
|
|
19
|
-
* -
|
|
19
|
+
* - Default-on Jev-assisted routing (capabilityGateway.routing.jev in settings.json)
|
|
20
20
|
* is only a bounded tie-breaker: when the deterministic router returns an
|
|
21
21
|
* ambiguous lexical hint among optional tools, the gateway offers just those
|
|
22
22
|
* hinted tools (two or three) and the current prompt to the Jev decisions
|
|
@@ -24,8 +24,9 @@
|
|
|
24
24
|
* hinted canonical tool name; the gateway revalidates the choice against the
|
|
25
25
|
* live catalog and activates it for the current run only. No hint, a unique
|
|
26
26
|
* activation, a skill match, or an already-activated tool never reaches Jev.
|
|
27
|
-
* Every Jev failure is an ordinary abstention that leaves
|
|
28
|
-
* behavior in place
|
|
27
|
+
* Every Jev failure is an ordinary abstention that leaves deterministic
|
|
28
|
+
* behavior in place. Without Token-In credentials, no Jev request is sent and
|
|
29
|
+
* the user is prompted to add an account with `/tokenin add`.
|
|
29
30
|
* - Temporary activations reset at agent_settled, restoring the baseline
|
|
30
31
|
* active-tool set; a tool_execution_start for one of them records whether the
|
|
31
32
|
* activation was actually used (content-free tool name + source only).
|
|
@@ -93,14 +94,19 @@ function catalogEntries(pi: ExtensionAPI): CatalogEntry[] {
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
const EMBEDDED_SKILL_BLOCK = /<skill\s+name="([^"]+)"[^>]*>[\s\S]*?<\/skill>/gi;
|
|
97
|
+
const GITHUB_OR_OPEN_SOURCE_QUERY = /\b(?:github|open[\s-]*source)\b/i;
|
|
96
98
|
|
|
97
99
|
function routePrompt(prompt: string, entries: CatalogEntry[]): ReturnType<typeof route> {
|
|
98
100
|
const loadedSkills = new Set([...prompt.matchAll(EMBEDDED_SKILL_BLOCK)].map((match) => match[1]!.toLowerCase()));
|
|
99
101
|
const query = prompt.replace(EMBEDDED_SKILL_BLOCK, " ");
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
const candidates = entries.filter((entry) => entry.kind !== "skill" || !loadedSkills.has(entry.name.toLowerCase()));
|
|
103
|
+
if (GITHUB_OR_OPEN_SOURCE_QUERY.test(query)) {
|
|
104
|
+
const grepAppSearch = candidates.find(
|
|
105
|
+
(entry) => entry.kind === "tool" && entry.eligible && entry.name === "grep_app_search",
|
|
106
|
+
);
|
|
107
|
+
if (grepAppSearch) return { action: "activate", entry: grepAppSearch };
|
|
108
|
+
}
|
|
109
|
+
return route(query, candidates);
|
|
104
110
|
}
|
|
105
111
|
|
|
106
112
|
function formatCatalog(entries: CatalogEntry[]): string {
|
|
@@ -146,6 +152,7 @@ export default function capabilityGatewayExtension(pi: ExtensionAPI): void {
|
|
|
146
152
|
// Tools this gateway activated for the current run, and whether they were invoked.
|
|
147
153
|
// Cleared at agent_settled with the activations themselves.
|
|
148
154
|
const activations = new Map<string, { source: ActivationSource; used: boolean }>();
|
|
155
|
+
let tokenInSetupPrompted = false;
|
|
149
156
|
|
|
150
157
|
/** Activate one tool for the current run, keeping the rest of the loadout untouched. */
|
|
151
158
|
function activateTool(name: string, source: ActivationSource): void {
|
|
@@ -313,7 +320,7 @@ export default function capabilityGatewayExtension(pi: ExtensionAPI): void {
|
|
|
313
320
|
});
|
|
314
321
|
|
|
315
322
|
// ------------------------------------------------------------------
|
|
316
|
-
// Routing: the deterministic catalog router first;
|
|
323
|
+
// Routing: the deterministic catalog router first; default-on Jev routing is
|
|
317
324
|
// only a bounded tie-breaker for its ambiguous/hint result.
|
|
318
325
|
// ------------------------------------------------------------------
|
|
319
326
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
@@ -344,6 +351,17 @@ export default function capabilityGatewayExtension(pi: ExtensionAPI): void {
|
|
|
344
351
|
emitTelemetry(pi, "route", { source: "jev", outcome: "attempt", candidates: candidates.length });
|
|
345
352
|
const jevRoute = await routeToJevTool(candidates, event.prompt, ctx, config);
|
|
346
353
|
if (!jevRoute.selected) {
|
|
354
|
+
if (
|
|
355
|
+
!tokenInSetupPrompted &&
|
|
356
|
+
config.provider === "tokenin" &&
|
|
357
|
+
(jevRoute.reason === "no-credential" || jevRoute.reason === "no-template")
|
|
358
|
+
) {
|
|
359
|
+
tokenInSetupPrompted = true;
|
|
360
|
+
ctx.ui.notify(
|
|
361
|
+
"Jev tool tie-breaking needs a Token-In account. Add one with /tokenin add; deterministic routing will keep working meanwhile.",
|
|
362
|
+
"warning",
|
|
363
|
+
);
|
|
364
|
+
}
|
|
347
365
|
emitTelemetry(pi, "route", {
|
|
348
366
|
source: "jev",
|
|
349
367
|
outcome: JEV_UNAVAILABLE_REASONS.has(jevRoute.reason) ? "unavailable" : "abstained",
|
|
@@ -19,6 +19,7 @@ import { allowNetwork } from "../../../test/test-network-env.ts";
|
|
|
19
19
|
const EXTENSIONS_DIR = fileURLToPath(new URL("../../", import.meta.url));
|
|
20
20
|
const GATEWAY_DIR = fileURLToPath(new URL(".", import.meta.url));
|
|
21
21
|
const GREP_APP_DIR = fileURLToPath(new URL("../grep-app", import.meta.url));
|
|
22
|
+
const WEB_AGENT_DIR = fileURLToPath(new URL("../pi-web-agent", import.meta.url));
|
|
22
23
|
const GRAFT_DIR = fileURLToPath(new URL("../pi-graft", import.meta.url));
|
|
23
24
|
const INLINE_SKILLS_FILE = fileURLToPath(new URL("../inline-skills.ts", import.meta.url));
|
|
24
25
|
|
|
@@ -333,13 +334,13 @@ describe("capability gateway integration", () => {
|
|
|
333
334
|
});
|
|
334
335
|
|
|
335
336
|
// ---------------------------------------------------------------------------
|
|
336
|
-
// Jev-assisted routing
|
|
337
|
+
// Default-on Jev-assisted routing: capabilityGateway.routing.jev.
|
|
337
338
|
// ---------------------------------------------------------------------------
|
|
338
339
|
|
|
339
340
|
describe("capability gateway Jev routing", () => {
|
|
340
|
-
// "
|
|
341
|
-
// two-candidate hint, which is the only Jev trigger.
|
|
342
|
-
const HINT_PROMPT = "
|
|
341
|
+
// "search fetch" weakly suggests both grep-app tools: the deterministic router returns a
|
|
342
|
+
// two-candidate hint, which is the only Jev trigger. GitHub/open-source mentions route directly.
|
|
343
|
+
const HINT_PROMPT = "search fetch";
|
|
343
344
|
// No tool name, alias, or summary token matches: the router has no lexical signal and Jev
|
|
344
345
|
// must never be consulted.
|
|
345
346
|
const NO_SIGNAL_PROMPT = "continue where we left off last time";
|
|
@@ -393,6 +394,35 @@ describe("capability gateway Jev routing", () => {
|
|
|
393
394
|
return h.telemetry.filter((event) => event.event === "route");
|
|
394
395
|
}
|
|
395
396
|
|
|
397
|
+
it("routes only GitHub/open-source mentions to grep.app; other web searches stay on web_explore", async () => {
|
|
398
|
+
allowNetwork();
|
|
399
|
+
const { fetchMock } = stubJev("none", 1);
|
|
400
|
+
const h = await createGatewaySession({
|
|
401
|
+
enabled: true,
|
|
402
|
+
extensions: [GATEWAY_DIR, GREP_APP_DIR, WEB_AGENT_DIR],
|
|
403
|
+
jev: jevSettings(),
|
|
404
|
+
});
|
|
405
|
+
harnesses.push(h);
|
|
406
|
+
|
|
407
|
+
for (const prompt of [
|
|
408
|
+
"search GitHub code",
|
|
409
|
+
"find open-source implementations",
|
|
410
|
+
"find open source implementations",
|
|
411
|
+
"find opensource examples",
|
|
412
|
+
]) {
|
|
413
|
+
await route(h, prompt);
|
|
414
|
+
expect(h.session.getActiveToolNames()).toContain("grep_app_search");
|
|
415
|
+
expect(h.session.getActiveToolNames()).not.toContain("web_explore");
|
|
416
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
417
|
+
await h.session.extensionRunner.emit({ type: "agent_settled" });
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
await route(h, "web search for release notes");
|
|
421
|
+
expect(h.session.getActiveToolNames()).toContain("web_explore");
|
|
422
|
+
expect(h.session.getActiveToolNames()).not.toContain("grep_app_search");
|
|
423
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
424
|
+
});
|
|
425
|
+
|
|
396
426
|
// Telemetry may carry route metadata and canonical tool names, never prompt
|
|
397
427
|
// text, conversation turns, credentials, raw Jev output, or tool arguments.
|
|
398
428
|
const TELEMETRY_KEYS = new Set([
|
|
@@ -518,14 +548,37 @@ describe("capability gateway Jev routing", () => {
|
|
|
518
548
|
);
|
|
519
549
|
});
|
|
520
550
|
|
|
521
|
-
it("
|
|
551
|
+
it("tries Jev by default but abstains safely without Token-In", async () => {
|
|
522
552
|
allowNetwork();
|
|
523
|
-
const
|
|
553
|
+
const unavailable = stubJev("grep_app_search");
|
|
524
554
|
const h = await createGatewaySession({ enabled: true, extensions: [GATEWAY_DIR, GREP_APP_DIR] });
|
|
525
555
|
harnesses.push(h);
|
|
526
556
|
|
|
527
557
|
await route(h, HINT_PROMPT);
|
|
528
558
|
|
|
559
|
+
expect(unavailable.fetchMock).not.toHaveBeenCalled();
|
|
560
|
+
expect(h.session.getActiveToolNames()).not.toContain("grep_app_search");
|
|
561
|
+
expect(routeEvents(h)).toContainEqual(expect.objectContaining({ source: "jev", outcome: "attempt" }));
|
|
562
|
+
expect(routeEvents(h)).toContainEqual(
|
|
563
|
+
expect.objectContaining({
|
|
564
|
+
source: "jev",
|
|
565
|
+
outcome: "unavailable",
|
|
566
|
+
reason: expect.stringMatching(/^no-(template|credential)$/),
|
|
567
|
+
}),
|
|
568
|
+
);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it("does not attempt Jev when explicitly disabled", async () => {
|
|
572
|
+
const disabled = stubJev("grep_app_search");
|
|
573
|
+
const h = await createGatewaySession({
|
|
574
|
+
enabled: true,
|
|
575
|
+
extensions: [GATEWAY_DIR, GREP_APP_DIR],
|
|
576
|
+
jev: { enabled: false },
|
|
577
|
+
});
|
|
578
|
+
harnesses.push(h);
|
|
579
|
+
|
|
580
|
+
await route(h, HINT_PROMPT);
|
|
581
|
+
|
|
529
582
|
expect(disabled.fetchMock).not.toHaveBeenCalled();
|
|
530
583
|
expect(h.session.getActiveToolNames()).not.toContain("grep_app_search");
|
|
531
584
|
expect(routeEvents(h)).toEqual([]);
|
|
@@ -115,10 +115,10 @@ describe("gateway Jev configuration", () => {
|
|
|
115
115
|
return path;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
it("
|
|
118
|
+
it("defaults Jev routing on with short pre-turn settings when settings are missing or malformed", () => {
|
|
119
119
|
expect(readGatewayJevConfig(join(settingsDir, "absent.json"))).toEqual(DEFAULT_GATEWAY_JEV_CONFIG);
|
|
120
120
|
expect(DEFAULT_GATEWAY_JEV_CONFIG).toMatchObject({
|
|
121
|
-
enabled:
|
|
121
|
+
enabled: true,
|
|
122
122
|
provider: "tokenin",
|
|
123
123
|
model: "jev-1.13",
|
|
124
124
|
timeoutMs: DEFAULT_GATEWAY_JEV_TIMEOUT_MS,
|
|
@@ -133,7 +133,7 @@ describe("gateway Jev configuration", () => {
|
|
|
133
133
|
);
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
-
it("reads
|
|
136
|
+
it("reads explicit route settings over the default-on gateway config", () => {
|
|
137
137
|
const path = write({
|
|
138
138
|
capabilityGateway: {
|
|
139
139
|
routing: {
|
|
@@ -174,7 +174,7 @@ describe("gateway Jev configuration", () => {
|
|
|
174
174
|
expect(read).not.toHaveProperty("contextChars");
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
-
it("rejects wrong types
|
|
177
|
+
it("rejects wrong types while allowing an omitted enabled flag to inherit true", () => {
|
|
178
178
|
const path = write({
|
|
179
179
|
capabilityGateway: {
|
|
180
180
|
routing: {
|
|
@@ -190,7 +190,13 @@ describe("gateway Jev configuration", () => {
|
|
|
190
190
|
},
|
|
191
191
|
},
|
|
192
192
|
});
|
|
193
|
-
expect(readGatewayJevConfig(path)).toEqual(DEFAULT_GATEWAY_JEV_CONFIG);
|
|
193
|
+
expect(readGatewayJevConfig(path)).toEqual({ ...DEFAULT_GATEWAY_JEV_CONFIG, enabled: false });
|
|
194
|
+
|
|
195
|
+
const omittedEnabled = write({ capabilityGateway: { routing: { jev: { timeoutMs: 500 } } } });
|
|
196
|
+
expect(readGatewayJevConfig(omittedEnabled).enabled).toBe(true);
|
|
197
|
+
|
|
198
|
+
const disabled = write({ capabilityGateway: { routing: { jev: { enabled: false } } } });
|
|
199
|
+
expect(readGatewayJevConfig(disabled).enabled).toBe(false);
|
|
194
200
|
});
|
|
195
201
|
|
|
196
202
|
it("derives the shared connection settings and hard-caps the pre-turn timeout", () => {
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* an abstention that leaves the deterministic behavior in place. The transport and validation half
|
|
13
13
|
* lives in the shared `../jev/decisions.ts` client.
|
|
14
14
|
*
|
|
15
|
-
* Configuration (
|
|
16
|
-
* not inherit `jevAdvisory` route policy; only the Jev provider/model
|
|
17
|
-
* Jev consumer defaults to the same deployment:
|
|
15
|
+
* Configuration (enabled by default, but requires Token-In credentials). The gateway reads its
|
|
16
|
+
* own settings area — it does not inherit `jevAdvisory` route policy; only the Jev provider/model
|
|
17
|
+
* identity is shared so every Jev consumer defaults to the same deployment:
|
|
18
18
|
*
|
|
19
19
|
* "capabilityGateway": {
|
|
20
20
|
* "routing": { "jev": { "enabled": true, "timeoutMs": 1000, "minConfidence": 0.6 } }
|
|
@@ -68,7 +68,7 @@ export interface GatewayJevConfig {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export const DEFAULT_GATEWAY_JEV_CONFIG: GatewayJevConfig = {
|
|
71
|
-
enabled:
|
|
71
|
+
enabled: true,
|
|
72
72
|
// The Jev deployment identity, shared with every other Jev consumer.
|
|
73
73
|
provider: DEFAULT_JEV_ADVISORY_CONFIG.provider,
|
|
74
74
|
model: DEFAULT_JEV_ADVISORY_CONFIG.model,
|
|
@@ -99,13 +99,13 @@ export function readGatewayJevConfig(settingsPath: string = getSettingsPath()):
|
|
|
99
99
|
if (isRecord(jev)) raw = jev;
|
|
100
100
|
}
|
|
101
101
|
} catch {
|
|
102
|
-
// Missing or malformed settings: the
|
|
102
|
+
// Missing or malformed settings: use the default-on route preference.
|
|
103
103
|
}
|
|
104
104
|
if (!raw) return DEFAULT_GATEWAY_JEV_CONFIG;
|
|
105
105
|
|
|
106
106
|
const baseUrl = raw.baseUrl;
|
|
107
107
|
return {
|
|
108
|
-
enabled: raw.enabled === true,
|
|
108
|
+
enabled: raw.enabled === undefined ? DEFAULT_GATEWAY_JEV_CONFIG.enabled : raw.enabled === true,
|
|
109
109
|
provider: stringOr(raw.provider, DEFAULT_GATEWAY_JEV_CONFIG.provider),
|
|
110
110
|
model: stringOr(raw.model, DEFAULT_GATEWAY_JEV_CONFIG.model),
|
|
111
111
|
baseUrl: typeof baseUrl === "string" && baseUrl.trim() !== "" ? baseUrl : undefined,
|
|
@@ -104,7 +104,8 @@ export default function extension(pi: ExtensionAPI) {
|
|
|
104
104
|
pi.on('before_agent_start', async (event) => ({
|
|
105
105
|
systemPrompt:
|
|
106
106
|
`${event.systemPrompt}\n\n` +
|
|
107
|
-
'For
|
|
107
|
+
'For requests explicitly mentioning GitHub or open source, use grep_app_search instead of web_explore. ' +
|
|
108
|
+
'For all other web research questions that require finding and comparing sources, use web_explore. ' +
|
|
108
109
|
'web_explore handles search, fetch, source ranking, and headless escalation internally. ' +
|
|
109
110
|
'If more web evidence is needed after web_explore, call web_explore again with a narrower query; do not use shell/network commands such as curl, Invoke-WebRequest, npm view/search/pack, or direct HTTP URLs for web research.'
|
|
110
111
|
}));
|
|
@@ -15,6 +15,7 @@ export interface SettingsConfig {
|
|
|
15
15
|
autoCompact: boolean;
|
|
16
16
|
autoHandoffEnabled: boolean;
|
|
17
17
|
autoHandoffThresholdTokens: number;
|
|
18
|
+
capabilityGatewayJevEnabled: boolean;
|
|
18
19
|
showImages: boolean;
|
|
19
20
|
imageWidthCells: number;
|
|
20
21
|
autoResizeImages: boolean;
|
|
@@ -62,6 +63,7 @@ export interface SettingsCallbacks {
|
|
|
62
63
|
onAutoCompactChange: (enabled: boolean) => void;
|
|
63
64
|
onAutoHandoffEnabledChange: (enabled: boolean) => void;
|
|
64
65
|
onAutoHandoffThresholdTokensChange: (tokens: number) => void;
|
|
66
|
+
onCapabilityGatewayJevChange: (enabled: boolean) => boolean;
|
|
65
67
|
onShowImagesChange: (enabled: boolean) => void;
|
|
66
68
|
onImageWidthCellsChange: (width: number) => void;
|
|
67
69
|
onAutoResizeImagesChange: (enabled: boolean) => void;
|
|
@@ -363,6 +363,13 @@ export class SettingsSelectorComponent extends Container {
|
|
|
363
363
|
currentValue: String(config.autoHandoffThresholdTokens),
|
|
364
364
|
values: ["64000", "96000", "128000", "160000", "192000", "256000", "512000"],
|
|
365
365
|
},
|
|
366
|
+
{
|
|
367
|
+
id: "capability-gateway-jev",
|
|
368
|
+
label: "Jev tool tie-breaking",
|
|
369
|
+
description: "Use Token-In Jev only for ambiguous optional-tool matches. Requires Token-In credentials; add one with /tokenin add.",
|
|
370
|
+
currentValue: config.capabilityGatewayJevEnabled ? "true" : "false",
|
|
371
|
+
values: ["true", "false"],
|
|
372
|
+
},
|
|
366
373
|
{
|
|
367
374
|
id: "steering-mode",
|
|
368
375
|
label: "Steering mode",
|
|
@@ -731,6 +738,11 @@ export class SettingsSelectorComponent extends Container {
|
|
|
731
738
|
case "auto-handoff-threshold":
|
|
732
739
|
callbacks.onAutoHandoffThresholdTokensChange(parseInt(newValue, 10));
|
|
733
740
|
break;
|
|
741
|
+
case "capability-gateway-jev":
|
|
742
|
+
if (!callbacks.onCapabilityGatewayJevChange(newValue === "true")) {
|
|
743
|
+
this.settingsList.updateValue(id, config.capabilityGatewayJevEnabled ? "true" : "false");
|
|
744
|
+
}
|
|
745
|
+
break;
|
|
734
746
|
case "show-images":
|
|
735
747
|
callbacks.onShowImagesChange(newValue === "true");
|
|
736
748
|
break;
|
|
@@ -3785,6 +3785,7 @@ export class InteractiveMode {
|
|
|
3785
3785
|
autoCompact: this.session.autoCompactionEnabled,
|
|
3786
3786
|
autoHandoffEnabled: this.settingsManager.getAutoHandoffEnabled(),
|
|
3787
3787
|
autoHandoffThresholdTokens: this.settingsManager.getAutoHandoffThresholdTokens(),
|
|
3788
|
+
capabilityGatewayJevEnabled: this.settingsManager.getCapabilityGatewayJevEnabled(),
|
|
3788
3789
|
defaultModel: (() => {
|
|
3789
3790
|
const defaultProvider = this.settingsManager.getDefaultProvider();
|
|
3790
3791
|
const defaultModelId = this.settingsManager.getDefaultModel();
|
|
@@ -3850,6 +3851,23 @@ export class InteractiveMode {
|
|
|
3850
3851
|
onAutoHandoffThresholdTokensChange: (tokens) => {
|
|
3851
3852
|
this.settingsManager.setAutoHandoffThresholdTokens(tokens);
|
|
3852
3853
|
},
|
|
3854
|
+
onCapabilityGatewayJevChange: (enabled) => {
|
|
3855
|
+
if (enabled && !this.session.modelRuntime.getProviderAuthStatus("tokenin").configured) {
|
|
3856
|
+
this.showStatus("Token-In is required for Jev routing; starting /tokenin add");
|
|
3857
|
+
done();
|
|
3858
|
+
void this.session
|
|
3859
|
+
.prompt("/tokenin add")
|
|
3860
|
+
.then(() => {
|
|
3861
|
+
if (this.session.modelRuntime.getProviderAuthStatus("tokenin").configured) {
|
|
3862
|
+
this.settingsManager.setCapabilityGatewayJevEnabled(true);
|
|
3863
|
+
}
|
|
3864
|
+
})
|
|
3865
|
+
.catch((error) => this.showError(`Could not start Token-In setup: ${String(error)}`));
|
|
3866
|
+
return false;
|
|
3867
|
+
}
|
|
3868
|
+
this.settingsManager.setCapabilityGatewayJevEnabled(enabled);
|
|
3869
|
+
return true;
|
|
3870
|
+
},
|
|
3853
3871
|
onShowImagesChange: (enabled) => {
|
|
3854
3872
|
this.settingsManager.setShowImages(enabled);
|
|
3855
3873
|
for (const child of this.chatContainer.children) {
|
package/docs/settings.md
CHANGED
|
@@ -103,15 +103,16 @@ Routing has two rungs:
|
|
|
103
103
|
|
|
104
104
|
1. A deterministic router activates a tool when the prompt uniquely matches its name, alias, or
|
|
105
105
|
discovery summary. Skills are never auto-loaded or auto-selected.
|
|
106
|
-
2.
|
|
107
|
-
optional tools, the Jev decisions model is asked which of two or three hinted tools (or
|
|
108
|
-
should be exposed. Jev sees only the bounded current prompt and each hinted tool's compact
|
|
106
|
+
2. Default-on Jev tie-breaking: when the deterministic router returns an ambiguous lexical hint
|
|
107
|
+
among optional tools, the Jev decisions model is asked which of two or three hinted tools (or
|
|
108
|
+
`none`) should be exposed. Jev sees only the bounded current prompt and each hinted tool's compact
|
|
109
109
|
discovery line — never conversation history, tool schemas, or the full catalog. A prompt with no
|
|
110
|
-
lexical signal, a unique activation, and a skill-only match never reach Jev.
|
|
110
|
+
lexical signal, a unique activation, and a skill-only match never reach Jev. Token-In credentials
|
|
111
|
+
are required; without them, no Jev request is sent and Selesai prompts you to run `/tokenin add`.
|
|
111
112
|
|
|
112
113
|
| Setting | Type | Default | Description |
|
|
113
114
|
|---------|------|---------|-------------|
|
|
114
|
-
| `capabilityGateway.routing.jev.enabled` | boolean | `
|
|
115
|
+
| `capabilityGateway.routing.jev.enabled` | boolean | `true` | Enable Jev tie-breaking for ambiguous tool hints; toggle it in `/settings` (requires Token-In credentials) |
|
|
115
116
|
| `capabilityGateway.routing.jev.provider` | string | `"tokenin"` | Provider serving the Jev decisions deployment |
|
|
116
117
|
| `capabilityGateway.routing.jev.model` | string | `"jev-1.13"` | Decisions model the tie-breaker calls |
|
|
117
118
|
| `capabilityGateway.routing.jev.baseUrl` | string | inherited | Base URL override; defaults to any registered model of `provider` |
|
|
@@ -121,8 +122,8 @@ Routing has two rungs:
|
|
|
121
122
|
|
|
122
123
|
This area is independent of `jevAdvisory`: gateway routing reads only
|
|
123
124
|
`capabilityGateway.routing.jev` and shares just the Jev provider/model deployment identity. A
|
|
124
|
-
failure, timeout, invalid answer, low confidence, or `none` is an
|
|
125
|
-
|
|
125
|
+
failure, timeout, invalid answer, low confidence, missing Token-In credentials, or `none` is an
|
|
126
|
+
ordinary abstention that leaves deterministic behavior in place. Temporary activations reset when the run settles, and
|
|
126
127
|
content-free telemetry on the `capability-gateway` event channel records the route outcome and
|
|
127
128
|
whether an activated tool was actually invoked.
|
|
128
129
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selesai/code",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.32",
|
|
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
|
"engines": {
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"clean": "shx rm -rf dist",
|
|
56
56
|
"dev": "tsx src/cli.ts",
|
|
57
57
|
"dev:print": "tsx src/cli.ts --print",
|
|
58
|
-
"test": "vitest run src/__tests__/model-registry-completion.test.ts src/extensions/jev/decisions.test.ts src/extensions/capability-gateway/routing.test.ts src/extensions/jev-advisory-memory.test.ts src/extensions/jev-advisory-recommendations.test.ts src/extensions/jev-advisory-lifecycle.test.ts src/extensions/undo.test.ts src/extensions/tps.test.ts src/extensions/pi-graft src/extensions/agent-browser.test.ts src/extensions/auto-session-name.test.ts src/extensions/context-compaction-reminder.test.ts src/extensions/copy-turn.test.ts src/extensions/grep-app/index.test.ts src/extensions/inline-skills.test.ts src/extensions/rtk.test.ts src/extensions/handoff-new.test.ts src/extensions/question/tests src/extensions/ponytail/test src/__tests__/tokenin-search.test.ts src/__tests__/tokenin-onboarding.test.ts src/__tests__/model-registry-defaults.test.ts src/core/system-prompt.test.ts src/core/session-format.test.ts test/settings-manager-compaction.test.ts test/suite/agent-session-runtime.test.ts test/suite/agent-session-prompt.test.ts test/suite/agent-session-queue.test.ts test/suite/agent-session-retry-events.test.ts test/suite/agent-session-compaction.test.ts test/suite/agent-session-compaction-model-overrides.test.ts test/suite/agent-session-bash-persistence.test.ts test/suite/agent-session-model-extension.test.ts test/clipboard.test.ts test/clipboard-command.test.ts test/clipboard-image.test.ts test/clipboard-image-bmp-conversion.test.ts test/clipboard-image-native-errors.test.ts src/cli/args.test.ts",
|
|
59
|
-
"test:coverage": "vitest run --coverage src/__tests__/model-registry-completion.test.ts src/extensions/jev/decisions.test.ts src/extensions/capability-gateway/routing.test.ts src/extensions/jev-advisory-memory.test.ts src/extensions/jev-advisory-recommendations.test.ts src/extensions/jev-advisory-lifecycle.test.ts src/extensions/undo.test.ts src/extensions/tps.test.ts src/extensions/agent-browser.test.ts src/extensions/auto-session-name.test.ts src/extensions/context-compaction-reminder.test.ts src/extensions/copy-turn.test.ts src/extensions/grep-app/index.test.ts src/extensions/inline-skills.test.ts src/extensions/rtk.test.ts src/extensions/handoff-new.test.ts src/extensions/question/tests src/extensions/ponytail/test src/__tests__/tokenin-search.test.ts src/__tests__/tokenin-onboarding.test.ts src/__tests__/model-registry-defaults.test.ts src/core/system-prompt.test.ts src/core/session-format.test.ts test/settings-manager-compaction.test.ts test/suite/agent-session-runtime.test.ts test/suite/agent-session-prompt.test.ts test/suite/agent-session-queue.test.ts test/suite/agent-session-retry-events.test.ts test/suite/agent-session-compaction.test.ts test/suite/agent-session-compaction-model-overrides.test.ts test/suite/agent-session-bash-persistence.test.ts test/suite/agent-session-model-extension.test.ts test/clipboard.test.ts test/clipboard-command.test.ts test/clipboard-image.test.ts test/clipboard-image-bmp-conversion.test.ts test/clipboard-image-native-errors.test.ts src/cli/args.test.ts",
|
|
58
|
+
"test": "vitest run src/__tests__/model-registry-completion.test.ts src/extensions/jev/decisions.test.ts src/extensions/capability-gateway/routing.test.ts src/core/settings-manager-auto-handoff.test.ts src/core/remote-catalog-provider.test.ts src/extensions/jev-advisory-memory.test.ts src/extensions/jev-advisory-recommendations.test.ts src/extensions/jev-advisory-lifecycle.test.ts src/extensions/undo.test.ts src/extensions/tps.test.ts src/extensions/pi-graft src/extensions/agent-browser.test.ts src/extensions/auto-session-name.test.ts src/extensions/context-compaction-reminder.test.ts src/extensions/copy-turn.test.ts src/extensions/grep-app/index.test.ts src/extensions/inline-skills.test.ts src/extensions/rtk.test.ts src/extensions/handoff-new.test.ts src/extensions/question/tests src/extensions/ponytail/test src/__tests__/tokenin-search.test.ts src/__tests__/tokenin-onboarding.test.ts src/__tests__/model-registry-defaults.test.ts src/core/system-prompt.test.ts src/core/session-format.test.ts test/settings-manager-compaction.test.ts test/suite/agent-session-runtime.test.ts test/suite/agent-session-prompt.test.ts test/suite/agent-session-queue.test.ts test/suite/agent-session-retry-events.test.ts test/suite/agent-session-compaction.test.ts test/suite/agent-session-compaction-model-overrides.test.ts test/suite/agent-session-bash-persistence.test.ts test/suite/agent-session-model-extension.test.ts test/clipboard.test.ts test/clipboard-command.test.ts test/clipboard-image.test.ts test/clipboard-image-bmp-conversion.test.ts test/clipboard-image-native-errors.test.ts src/cli/args.test.ts",
|
|
59
|
+
"test:coverage": "vitest run --coverage src/__tests__/model-registry-completion.test.ts src/extensions/jev/decisions.test.ts src/extensions/capability-gateway/routing.test.ts src/core/settings-manager-auto-handoff.test.ts src/core/remote-catalog-provider.test.ts src/extensions/jev-advisory-memory.test.ts src/extensions/jev-advisory-recommendations.test.ts src/extensions/jev-advisory-lifecycle.test.ts src/extensions/undo.test.ts src/extensions/tps.test.ts src/extensions/agent-browser.test.ts src/extensions/auto-session-name.test.ts src/extensions/context-compaction-reminder.test.ts src/extensions/copy-turn.test.ts src/extensions/grep-app/index.test.ts src/extensions/inline-skills.test.ts src/extensions/rtk.test.ts src/extensions/handoff-new.test.ts src/extensions/question/tests src/extensions/ponytail/test src/__tests__/tokenin-search.test.ts src/__tests__/tokenin-onboarding.test.ts src/__tests__/model-registry-defaults.test.ts src/core/system-prompt.test.ts src/core/session-format.test.ts test/settings-manager-compaction.test.ts test/suite/agent-session-runtime.test.ts test/suite/agent-session-prompt.test.ts test/suite/agent-session-queue.test.ts test/suite/agent-session-retry-events.test.ts test/suite/agent-session-compaction.test.ts test/suite/agent-session-compaction-model-overrides.test.ts test/suite/agent-session-bash-persistence.test.ts test/suite/agent-session-model-extension.test.ts test/clipboard.test.ts test/clipboard-command.test.ts test/clipboard-image.test.ts test/clipboard-image-bmp-conversion.test.ts test/clipboard-image-native-errors.test.ts src/cli/args.test.ts",
|
|
60
60
|
"prepare": "npm run build",
|
|
61
61
|
"build": "npm run clean && tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js dist/rpc-entry.js && npm run copy-assets",
|
|
62
62
|
"copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/ && shx mkdir -p dist/defaults && shx cp src/defaults/* dist/defaults/ && shx mkdir -p dist/extensions && node scripts/copy-extensions.mjs && shx mkdir -p dist/themes && shx cp -r src/themes/. dist/themes/ && shx mkdir -p dist/skills && shx cp -r src/skills/. dist/skills/"
|