@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 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
- if (localGeneratedAt !== undefined && (entry.lastModified === undefined || entry.lastModified <= localGeneratedAt)) {
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 = {};
@@ -11,6 +11,11 @@
11
11
  "recommendations": { "enabled": false }
12
12
  }
13
13
  },
14
+ "capabilityGateway": {
15
+ "routing": {
16
+ "jev": { "enabled": true }
17
+ }
18
+ },
14
19
  "collapseChangelog": true,
15
20
  "compaction": {
16
21
  "enabled": true,
@@ -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
- * - Opt-in Jev-assisted routing (capabilityGateway.routing.jev in settings.json)
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 the deterministic
28
- * behavior in place, so a missing Token-In subscription is invisible.
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
- return route(
101
- query,
102
- entries.filter((entry) => entry.kind !== "skill" || !loadedSkills.has(entry.name.toLowerCase())),
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; opt-in Jev routing is
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 (opt-in): capabilityGateway.routing.jev.
337
+ // Default-on Jev-assisted routing: capabilityGateway.routing.jev.
337
338
  // ---------------------------------------------------------------------------
338
339
 
339
340
  describe("capability gateway Jev routing", () => {
340
- // "...github..." only weakly suggests both grep-app tools: the deterministic router returns a
341
- // two-candidate hint, which is the only Jev trigger.
342
- const HINT_PROMPT = "look at this github repo";
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("leaves routing deterministic when the Jev route is not configured", async () => {
551
+ it("tries Jev by default but abstains safely without Token-In", async () => {
522
552
  allowNetwork();
523
- const disabled = stubJev("grep_app_search");
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("stays disabled with its own short pre-turn defaults when settings are missing or malformed", () => {
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: false,
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 the opt-in route settings over the gateway defaults", () => {
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 and never enables the route implicitly", () => {
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 (opt-in, disabled by default). The gateway reads its own settings area — it does
16
- * not inherit `jevAdvisory` route policy; only the Jev provider/model identity is shared so every
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: false,
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 feature stays disabled.
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 web research questions that require finding and comparing sources, use web_explore. ' +
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. Opt-in Jev tie-breaking: when the deterministic router returns an ambiguous lexical hint among
107
- optional tools, the Jev decisions model is asked which of two or three hinted tools (or `none`)
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 | `false` | Enable Jev tie-breaking for ambiguous tool hints |
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 ordinary abstention that leaves
125
- the deterministic behavior in place. Temporary activations reset when the run settles, and
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.30",
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/"