@selesai/code 0.13.29 → 0.13.30-beta.0

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/core/model-registry.d.ts +13 -1
  3. package/dist/core/model-registry.js +16 -0
  4. package/dist/defaults/settings.json +8 -13
  5. package/dist/extensions/capability-gateway/catalog.ts +2 -2
  6. package/dist/extensions/capability-gateway/index.ts +103 -18
  7. package/dist/extensions/capability-gateway/integration.test.ts +394 -8
  8. package/dist/extensions/capability-gateway/routing.test.ts +415 -0
  9. package/dist/extensions/capability-gateway/routing.ts +221 -0
  10. package/dist/extensions/grep-app/index.ts +10 -0
  11. package/dist/extensions/jev/decisions.test.ts +316 -0
  12. package/dist/extensions/jev/decisions.ts +527 -0
  13. package/dist/extensions/jev/test-support.ts +233 -0
  14. package/dist/extensions/jev-advisory-lifecycle.test.ts +206 -0
  15. package/dist/extensions/jev-advisory-memory.test.ts +191 -0
  16. package/dist/extensions/jev-advisory-recommendations.test.ts +240 -0
  17. package/dist/extensions/jev-advisory-routing.ts +539 -0
  18. package/dist/extensions/package.json +2 -2
  19. package/dist/extensions/pi-hermes-memory/src/memory-search-bridge.ts +40 -0
  20. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +57 -46
  21. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +17 -0
  22. package/dist/extensions/pi-hermes-memory/src/tools/session-search-tool.ts +10 -0
  23. package/dist/extensions/pi-hermes-memory/src/tools/skill-tool.ts +5 -0
  24. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +25 -0
  25. package/dist/extensions/pi-intercom/index.ts +10 -0
  26. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +5 -0
  27. package/dist/extensions/pi-subagents/src/extension/index.ts +5 -0
  28. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +10 -0
  29. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +10 -0
  30. package/dist/extensions/pi-web-agent/src/extension.ts +5 -0
  31. package/dist/extensions/question/index.ts +5 -0
  32. package/dist/extensions/tokenin-onboarding.ts +185 -0
  33. package/docs/settings.md +68 -31
  34. package/package.json +3 -3
  35. package/dist/extensions/auto-model.test.ts +0 -438
  36. package/dist/extensions/auto-model.ts +0 -357
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to `@selesai/code` will be documented in this file.
4
4
 
5
+ ## [0.13.30-beta.0] - 2026-09-23
6
+
7
+ ### Added
8
+ - **Jev advisory routing (opt-in beta).** The new bundled `jev-advisory-routing` extension can make a bounded, read-only durable-memory lookup after an explicit memory cue, and suggest one discovered skill/workflow or a proportionate verification level. Both routes are disabled by default; neither writes memory, loads a skill, starts a workflow, runs a command, or weakens required checks.
9
+ - **Jev capability tie-breaking (opt-in beta).** The capability gateway can use Jev only to break an ambiguous lexical hint between two or three optional tools. It never receives the full catalog, tool schemas, or conversation history; unavailable, malformed, low-confidence, and `none` decisions preserve deterministic behavior.
10
+
11
+ ### Changed
12
+ - **One Jev transport for every consumer.** Advisory routing and capability tie-breaking share bounded request validation and the Token-In decisions-model path. `jev-*` calls use Token-In's non-streaming endpoint and retain its rotateable-account failover.
13
+
5
14
  ## [0.13.29] - 2026-09-22
6
15
 
7
16
  ### Changed
@@ -1,4 +1,4 @@
1
- import type { Api, AuthResult, Model, Provider } from "@earendil-works/pi-ai";
1
+ import type { Api, AssistantMessage, AssistantMessageEventStream, AuthResult, Context, Model, ModelsSimpleStreamOptions, Provider } from "@earendil-works/pi-ai";
2
2
  import type { AuthStorage } from "./auth-storage.ts";
3
3
  import type { ModelRuntime } from "./model-runtime.ts";
4
4
  import type { AuthStatus, ProviderConfigInput } from "./provider-composer.ts";
@@ -31,6 +31,18 @@ export declare class ModelRegistry {
31
31
  hasConfiguredAuth(model: Model<Api>): boolean;
32
32
  getApiKeyAndHeaders(model: Model<Api>): Promise<ResolvedRequestAuth>;
33
33
  getProviderAuthStatus(provider: string): AuthStatus;
34
+ /**
35
+ * Run a request through the composed provider layer — the same path the agent
36
+ * uses, so a provider's `streamSimple` override, auth resolution, base-URL
37
+ * overrides, and header transforms all apply.
38
+ *
39
+ * Extensions that call a model directly should use this instead of pi-ai's
40
+ * compat dispatch, which knows nothing about the providers this runtime
41
+ * composes (a provider override registered here is invisible to it).
42
+ */
43
+ streamSimple(model: Model<Api>, context: Context, options?: ModelsSimpleStreamOptions): AssistantMessageEventStream;
44
+ /** One non-streaming request through the composed provider layer. */
45
+ complete(model: Model<Api>, context: Context, options?: ModelsSimpleStreamOptions): Promise<AssistantMessage>;
34
46
  getProvider(provider: string): Provider | undefined;
35
47
  getProviderDisplayName(provider: string): string;
36
48
  getProviderAuth(provider: string): Promise<AuthResult | undefined>;
@@ -63,6 +63,22 @@ export class ModelRegistry {
63
63
  getProviderAuthStatus(provider) {
64
64
  return this.runtime.getProviderAuthStatus(provider);
65
65
  }
66
+ /**
67
+ * Run a request through the composed provider layer — the same path the agent
68
+ * uses, so a provider's `streamSimple` override, auth resolution, base-URL
69
+ * overrides, and header transforms all apply.
70
+ *
71
+ * Extensions that call a model directly should use this instead of pi-ai's
72
+ * compat dispatch, which knows nothing about the providers this runtime
73
+ * composes (a provider override registered here is invisible to it).
74
+ */
75
+ streamSimple(model, context, options) {
76
+ return this.runtime.streamSimple(model, context, options);
77
+ }
78
+ /** One non-streaming request through the composed provider layer. */
79
+ complete(model, context, options) {
80
+ return this.runtime.complete(model, context, options);
81
+ }
66
82
  getProvider(provider) {
67
83
  return this.runtime.getProvider(provider);
68
84
  }
@@ -1,21 +1,16 @@
1
1
  {
2
- "autoModel": {
3
- "enabled": false,
4
- "classifier": {
5
- "provider": "tokenin",
6
- "model": "jev-1.13"
7
- },
8
- "tiers": {
9
- "simple": "tokenin/deepseek-v4.1-flash",
10
- "medium": "tokenin/celestial-pro",
11
- "complex": "tokenin/celestial-max",
12
- "reasoning": "tokenin/celestial-ultra"
13
- }
14
- },
15
2
  "autoHandoff": {
16
3
  "enabled": true,
17
4
  "thresholdTokens": 256000
18
5
  },
6
+ "jevAdvisory": {
7
+ "provider": "tokenin",
8
+ "model": "jev-1.13",
9
+ "routes": {
10
+ "memory": { "enabled": false },
11
+ "recommendations": { "enabled": false }
12
+ }
13
+ },
19
14
  "collapseChangelog": true,
20
15
  "compaction": {
21
16
  "enabled": true,
@@ -29,13 +29,13 @@ export function firstSentence(text: string): string {
29
29
  return (end === -1 ? trimmed : trimmed.slice(0, end + 1)).trim();
30
30
  }
31
31
 
32
- /** Compact summary for a tool: explicit discovery metadata wins, then first sentence, then snippet. */
32
+ /** Compact summary for a tool: explicit discovery metadata wins, then the description's first sentence. */
33
33
  export function toolSummary(tool: ToolInfo): string {
34
34
  const explicit = tool.discovery?.summary?.trim();
35
35
  if (explicit) return explicit;
36
36
  const sentence = firstSentence(tool.description);
37
37
  if (sentence) return sentence;
38
- return tool.promptSnippet?.trim() || tool.name;
38
+ return tool.name;
39
39
  }
40
40
 
41
41
  export function buildToolCatalog(tools: ToolInfo[], gatewayToolNames: Set<string>): CatalogEntry[] {
@@ -16,8 +16,19 @@
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)
20
+ * is only a bounded tie-breaker: when the deterministic router returns an
21
+ * ambiguous lexical hint among optional tools, the gateway offers just those
22
+ * hinted tools (two or three) and the current prompt to the Jev decisions
23
+ * model as one constrained choice question. Jev may answer `none` or one
24
+ * hinted canonical tool name; the gateway revalidates the choice against the
25
+ * live catalog and activates it for the current run only. No hint, a unique
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.
19
29
  * - Temporary activations reset at agent_settled, restoring the baseline
20
- * active-tool set.
30
+ * active-tool set; a tool_execution_start for one of them records whether the
31
+ * activation was actually used (content-free tool name + source only).
21
32
  * - The system-prompt skill index is replaced by a compact capability
22
33
  * instruction; full skill instructions load only on explicit show/invoke.
23
34
  * - Telemetry events are emitted on the shared event bus.
@@ -36,6 +47,14 @@ import {
36
47
  route,
37
48
  type CatalogEntry,
38
49
  } from "./catalog.ts";
50
+ import {
51
+ hintedToolCandidates,
52
+ MIN_GATEWAY_JEV_CANDIDATES,
53
+ readGatewayJevConfig,
54
+ routeToJevTool,
55
+ JEV_UNAVAILABLE_REASONS,
56
+ } from "./routing.ts";
57
+ import { confidenceBucket } from "../jev/decisions.ts";
39
58
 
40
59
  export const GATEWAY_ENV = "SELESAI_CAPABILITY_GATEWAY";
41
60
  export const GATEWAY_TOOLS = new Set(["capability_catalog", "capability_discover", "capability_skill_show"]);
@@ -118,9 +137,25 @@ function emitTelemetry(pi: ExtensionAPI, event: string, data: Record<string, unk
118
137
  }
119
138
  }
120
139
 
140
+ /** Where a run-local tool activation came from; recorded so use telemetry can attribute it. */
141
+ type ActivationSource = "deterministic" | "jev" | "discover";
142
+
121
143
  export default function capabilityGatewayExtension(pi: ExtensionAPI): void {
122
144
  if (!isEnabled()) return;
123
145
 
146
+ // Tools this gateway activated for the current run, and whether they were invoked.
147
+ // Cleared at agent_settled with the activations themselves.
148
+ const activations = new Map<string, { source: ActivationSource; used: boolean }>();
149
+
150
+ /** Activate one tool for the current run, keeping the rest of the loadout untouched. */
151
+ function activateTool(name: string, source: ActivationSource): void {
152
+ const active = pi.getActiveTools();
153
+ if (!active.includes(name)) {
154
+ pi.setActiveTools([...active, name]);
155
+ }
156
+ activations.set(name, { source, used: false });
157
+ }
158
+
124
159
  // ------------------------------------------------------------------
125
160
  // Session start: snapshot baseline, make extension tools dormant, and
126
161
  // install the compact skill index (action methods are stubs until bind).
@@ -182,7 +217,8 @@ export default function capabilityGatewayExtension(pi: ExtensionAPI): void {
182
217
  })
183
218
  : filtered;
184
219
  const text = formatCatalog(shown);
185
- emitTelemetry(pi, "catalog", { query: params.query ?? "", kind: params.kind ?? "", results: shown.length });
220
+ // Content-free: catalog query and filter arguments never travel.
221
+ emitTelemetry(pi, "catalog", { results: shown.length });
186
222
  return {
187
223
  content: [{ type: "text", text }],
188
224
  details: { count: shown.length, total: filtered.length },
@@ -220,10 +256,7 @@ export default function capabilityGatewayExtension(pi: ExtensionAPI): void {
220
256
  details: { activated: false },
221
257
  };
222
258
  }
223
- const active = pi.getActiveTools();
224
- if (!active.includes(entry.name)) {
225
- pi.setActiveTools([...active, entry.name]);
226
- }
259
+ activateTool(entry.name, "discover");
227
260
  emitTelemetry(pi, "discover", { tool: entry.name });
228
261
  return {
229
262
  content: [
@@ -280,32 +313,84 @@ export default function capabilityGatewayExtension(pi: ExtensionAPI): void {
280
313
  });
281
314
 
282
315
  // ------------------------------------------------------------------
283
- // Deterministic routing: high-confidence activation/recommendation,
284
- // ambiguity hints, or nothing for unrelated prompts.
316
+ // Routing: the deterministic catalog router first; opt-in Jev routing is
317
+ // only a bounded tie-breaker for its ambiguous/hint result.
285
318
  // ------------------------------------------------------------------
286
- pi.on("before_agent_start", (event) => {
287
- const result = routePrompt(event.prompt, catalogEntries(pi));
288
- if (result.action === "none") return undefined;
319
+ pi.on("before_agent_start", async (event, ctx) => {
320
+ const entries = catalogEntries(pi);
321
+ const result = routePrompt(event.prompt, entries);
289
322
  if (result.action === "activate" && result.entry) {
290
- const active = pi.getActiveTools();
291
- if (!active.includes(result.entry.name)) {
292
- pi.setActiveTools([...active, result.entry.name]);
293
- }
294
- emitTelemetry(pi, "route_activate", { tool: result.entry.name });
323
+ activateTool(result.entry.name, "deterministic");
324
+ emitTelemetry(pi, "route", {
325
+ source: "deterministic",
326
+ outcome: "activated",
327
+ tool: result.entry.name,
328
+ candidates: 0,
329
+ durationMs: 0,
330
+ });
295
331
  return undefined;
296
332
  }
297
333
  // Do not inject fuzzy recommendations or catalog hints into the model
298
334
  // context. It can discover capabilities when it actually needs one.
335
+ // Jev is a tie-breaker, never a classifier: only the ambiguous/hint
336
+ // result counts. No lexical signal or unique skill recommendation
337
+ // reaches it, and skills are never offered as candidates.
338
+ if (result.action !== "hint") return undefined;
339
+ const config = readGatewayJevConfig();
340
+ if (!config.enabled) return undefined;
341
+ const candidates = hintedToolCandidates(result.candidates);
342
+ if (candidates.length < MIN_GATEWAY_JEV_CANDIDATES) return undefined;
343
+
344
+ emitTelemetry(pi, "route", { source: "jev", outcome: "attempt", candidates: candidates.length });
345
+ const jevRoute = await routeToJevTool(candidates, event.prompt, ctx, config);
346
+ if (!jevRoute.selected) {
347
+ emitTelemetry(pi, "route", {
348
+ source: "jev",
349
+ outcome: JEV_UNAVAILABLE_REASONS.has(jevRoute.reason) ? "unavailable" : "abstained",
350
+ reason: jevRoute.reason,
351
+ candidates: jevRoute.candidates,
352
+ durationMs: jevRoute.elapsedMs,
353
+ });
354
+ return undefined;
355
+ }
356
+
357
+ // Revalidate the accepted choice against the live catalog immediately
358
+ // before activation: a stale or no-longer-eligible name activates nothing.
359
+ const live = eligibleTools(pi).find((tool) => tool.name === jevRoute.tool);
360
+ if (live) activateTool(live.name, "jev");
361
+ emitTelemetry(pi, "route", {
362
+ source: "jev",
363
+ outcome: live ? "activated" : "abstained",
364
+ ...(live ? { tool: live.name } : { tool: jevRoute.tool }),
365
+ confidence: confidenceBucket(jevRoute.confidence),
366
+ candidates: jevRoute.candidates,
367
+ durationMs: jevRoute.elapsedMs,
368
+ });
299
369
  return undefined;
300
370
  });
301
371
 
302
372
  // ------------------------------------------------------------------
303
- // Reset: restore the baseline active-tool set after the run settles.
373
+ // Selected -> used: a run-local activation is "used" when its tool is
374
+ // actually executed before the run settles. Content-free: name + source.
375
+ // ------------------------------------------------------------------
376
+ pi.on("tool_execution_start", (event) => {
377
+ const activation = activations.get(event.toolName);
378
+ if (!activation || activation.used) return;
379
+ activation.used = true;
380
+ emitTelemetry(pi, "use", { tool: event.toolName, source: activation.source });
381
+ });
382
+
383
+ // ------------------------------------------------------------------
384
+ // Reset: restore the baseline active-tool set after the run settles and
385
+ // report how many run-local activations were actually used.
304
386
  // ------------------------------------------------------------------
305
387
  pi.on("agent_settled", () => {
306
388
  const baseline = pi.getActiveTools().filter((name) => !eligibleTools(pi).some((tool) => tool.name === name));
307
389
  pi.setActiveTools(baseline);
308
- emitTelemetry(pi, "reset", { activeCount: baseline.length });
390
+ let used = 0;
391
+ for (const activation of activations.values()) if (activation.used) used += 1;
392
+ emitTelemetry(pi, "reset", { activeCount: baseline.length, activated: activations.size, used });
393
+ activations.clear();
309
394
  });
310
395
 
311
396
  // ------------------------------------------------------------------