@selesai/code 0.14.0 → 0.14.3

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.
@@ -85,17 +85,17 @@ afterEach(() => {
85
85
  });
86
86
 
87
87
  describe("session start", () => {
88
- it("resolves the Git working tree, probes the executable, and publishes a status line", async () => {
88
+ it("resolves the Git working tree, probes the executable, and writes no status line", async () => {
89
89
  const session = await bootSession();
90
90
  expect(session.commandsRun()).toEqual(["git rev-parse --show-toplevel", "graft --version"]);
91
- expect(session.ctx.statuses.get("graft")).toBe("graft: ● structural v0.21.1");
91
+ expect(session.ctx.ui.setStatus).not.toHaveBeenCalled();
92
92
  await session.reset();
93
93
  });
94
94
 
95
95
  it("does nothing at all when the extension is disabled by settings", async () => {
96
96
  const session = await bootSession({ settings: { enabled: false } });
97
97
  expect(session.exec).not.toHaveBeenCalled();
98
- expect(session.ctx.statuses.get("graft")).toBeUndefined();
98
+ expect(session.ctx.ui.setStatus).not.toHaveBeenCalled();
99
99
  await session.reset();
100
100
  });
101
101
 
@@ -133,7 +133,9 @@ describe("session start", () => {
133
133
  const session = await bootSession({
134
134
  branch: [{ type: "custom", customType: MODE_ENTRY_TYPE, data: { mode: "hybrid" } }],
135
135
  });
136
- expect(session.ctx.statuses.get("graft-mode")).toBe("graft mode: hybrid");
136
+ const status = session.commands.get("graft")!.handler as (args: string, ctx: unknown) => Promise<void>;
137
+ await status("status", session.ctx);
138
+ expect(notified(session.ctx)).toContain("hybrid —");
137
139
  await session.reset();
138
140
  });
139
141
 
@@ -171,8 +173,8 @@ describe("session start", () => {
171
173
  const ctx = makeCtx({ cwd: repo });
172
174
  const start = handlerFor(harness, "session_start") as (e: unknown, c: unknown) => Promise<void>;
173
175
  await start({ type: "session_start", reason: "startup" }, ctx);
174
- await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● structural v0.21.1"));
175
- expect(harness.commandsRun()).toContain("npm install -g @nanonets/graft@^0.21");
176
+ await vi.waitFor(() => expect(harness.commandsRun()).toContain("npm install -g @nanonets/graft@^0.21"));
177
+ await vi.waitFor(() => expect(harness.commandsRun().filter((c) => c.includes("--version"))).toHaveLength(2));
176
178
 
177
179
  await start({ type: "session_start", reason: "reload" }, ctx);
178
180
  expect(harness.commandsRun().filter((command) => command.startsWith("npm install"))).toHaveLength(1);
@@ -194,7 +196,7 @@ describe("session start", () => {
194
196
  ctx,
195
197
  );
196
198
  await vi.waitFor(() => expect(notified(ctx)).toContain("npm access denied"));
197
- expect(ctx.statuses.get("graft")).toBe("graft: ✕ no cli");
199
+ expect(ctx.ui.setStatus).not.toHaveBeenCalled();
198
200
  });
199
201
 
200
202
  it("reports that Graft needs a Git working tree without probing anything else", async () => {
@@ -210,7 +212,7 @@ describe("session start", () => {
210
212
  { type: "session_start", reason: "startup" },
211
213
  ctx,
212
214
  );
213
- expect(ctx.statuses.get("graft")).toBe("graft: ✕ n/a");
215
+ expect(ctx.ui.setStatus).not.toHaveBeenCalled();
214
216
  expect(ctx.ui.notify).not.toHaveBeenCalled();
215
217
  });
216
218
  });
@@ -224,6 +226,12 @@ describe("mutation awareness", () => {
224
226
  content: [],
225
227
  isError,
226
228
  });
229
+ /** Whether the graph counts as changed shows up as a rebuild when the turn ends. */
230
+ const expectRefreshOnAgentEnd = async (session: Booted, expected: boolean) => {
231
+ await (handlerFor(session, "agent_end") as (e: unknown, c: unknown) => Promise<void>)({}, session.ctx);
232
+ if (expected) await vi.waitFor(() => expect(session.commandsRun()).toContain("graft build"));
233
+ else expect(session.commandsRun()).not.toContain("graft build");
234
+ };
227
235
  const onToolResult = (session: Booted, toolName: string, input: Record<string, unknown>, isError = false) =>
228
236
  (handlerFor(session, "tool_result") as (e: unknown, c: unknown) => Promise<void>)(
229
237
  toolResultEvent(toolName, input, isError),
@@ -233,14 +241,14 @@ describe("mutation awareness", () => {
233
241
  it("marks the graph changed after a successful edit", async () => {
234
242
  const session = await bootSession();
235
243
  await onToolResult(session, "edit", { path: "src/a.ts" });
236
- expect(session.ctx.statuses.get("graft")).toBe("graft: ◐ changed v0.21.1");
244
+ await expectRefreshOnAgentEnd(session, true);
237
245
  await session.reset();
238
246
  });
239
247
 
240
248
  it("marks the graph changed after a clearly mutating bash command", async () => {
241
249
  const session = await bootSession();
242
250
  await onToolResult(session, "bash", { command: "rm -rf dist" });
243
- expect(session.ctx.statuses.get("graft")).toBe("graft: ◐ changed v0.21.1");
251
+ await expectRefreshOnAgentEnd(session, true);
244
252
  await session.reset();
245
253
  });
246
254
 
@@ -249,7 +257,7 @@ describe("mutation awareness", () => {
249
257
  await onToolResult(session, "read", { path: "src/a.ts" });
250
258
  await onToolResult(session, "bash", { command: "git status" });
251
259
  await onToolResult(session, "edit", { path: "src/a.ts" }, true);
252
- expect(session.ctx.statuses.get("graft")).toBe("graft: ● structural v0.21.1");
260
+ await expectRefreshOnAgentEnd(session, false);
253
261
  await session.reset();
254
262
  });
255
263
  });
@@ -283,8 +291,7 @@ describe("proactive refresh", () => {
283
291
  { type: "session_start", reason: "startup" },
284
292
  ctx,
285
293
  );
286
- await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● structural v0.21.1"));
287
- expect(harness.commandsRun()).toContain("graft build");
294
+ await vi.waitFor(() => expect(harness.commandsRun()).toContain("graft build"));
288
295
  });
289
296
 
290
297
  it("uses the active TokenIn model for the default deep build", async () => {
@@ -314,7 +321,7 @@ describe("proactive refresh", () => {
314
321
  { type: "session_start", reason: "startup" },
315
322
  ctx,
316
323
  );
317
- await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● deep v0.21.1"));
324
+ await vi.waitFor(() => expect(harness.commandsRun().some((c) => c.startsWith("graft build"))).toBe(true));
318
325
  const build = harness.exec.mock.calls.find((call) => (call[1] as string[]).includes("build"))!;
319
326
  expect(build[1]).toEqual(["build", "--deep"]);
320
327
  expect(build[2]).toMatchObject({
@@ -359,7 +366,8 @@ describe("proactive refresh", () => {
359
366
  { type: "session_start", reason: "startup" },
360
367
  ctx,
361
368
  );
362
- await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● deep v0.21.1"));
369
+ await vi.waitFor(() => expect(notified(ctx)).toContain("retrying the deep pass"));
370
+ await vi.waitFor(() => expect(models).toEqual(["deepseek-v4.1-flash", "glm-5.3-flash"]));
363
371
  expect(models).toEqual(["deepseek-v4.1-flash", "glm-5.3-flash"]);
364
372
  expect(notified(ctx)).toContain("retrying the deep pass");
365
373
  });
@@ -388,12 +396,12 @@ describe("proactive refresh", () => {
388
396
  { type: "session_start", reason: "startup" },
389
397
  ctx,
390
398
  );
391
- await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● structural v0.21.1"));
399
+ await vi.waitFor(() => expect(harness.commandsRun().some((c) => c.startsWith("graft build"))).toBe(true));
392
400
  const build = harness.exec.mock.calls.find((call) => (call[1] as string[]).includes("build"))!;
393
401
  expect(build[1]).toEqual(["build"]);
394
402
  });
395
403
 
396
- it("re-indexes after edits, shows the sync phase, and returns to fresh", async () => {
404
+ it("re-indexes after edits without touching the status line", async () => {
397
405
  const session = await bootSession({ settings: { refreshDebounceSeconds: 0 } });
398
406
  await (handlerFor(session, "tool_result") as (e: unknown, c: unknown) => Promise<void>)(
399
407
  { type: "tool_result", toolName: "read", toolCallId: "c", input: {}, content: [], isError: false },
@@ -403,12 +411,10 @@ describe("proactive refresh", () => {
403
411
  { type: "tool_result", toolName: "write", toolCallId: "c1", input: {}, content: [], isError: false },
404
412
  session.ctx,
405
413
  );
406
- expect(session.ctx.statuses.get("graft")).toBe("graft: ◐ changed v0.21.1");
407
414
 
408
415
  await (handlerFor(session, "agent_end") as (e: unknown, c: unknown) => Promise<void>)({}, session.ctx);
409
- expect(session.ctx.statuses.get("graft")).toBe("graft: ⚙ syncing v0.21.1");
410
- await vi.waitFor(() => expect(session.ctx.statuses.get("graft")).toBe("graft: ● structural v0.21.1"));
411
- expect(session.commandsRun()).toContain("graft build");
416
+ await vi.waitFor(() => expect(session.commandsRun()).toContain("graft build"));
417
+ expect(session.ctx.ui.setStatus).not.toHaveBeenCalled();
412
418
  await session.reset();
413
419
  });
414
420
 
@@ -449,7 +455,8 @@ describe("proactive refresh", () => {
449
455
  ctx,
450
456
  );
451
457
  await (handlerFor(harness, "agent_end") as (e: unknown, c: unknown) => Promise<void>)({}, ctx);
452
- await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ✕ failed v0.21.1"));
458
+ await vi.waitFor(() => expect(harness.commandsRun()).toContain("graft build"));
459
+ expect(ctx.ui.setStatus).not.toHaveBeenCalled();
453
460
  });
454
461
  });
455
462
 
@@ -592,8 +599,7 @@ describe("session shutdown", () => {
592
599
  const session = await bootSession({ settings: { mode: "push" } });
593
600
  await session.reset();
594
601
 
595
- expect(session.ctx.statuses.get("graft")).toBeUndefined();
596
- expect(session.ctx.statuses.get("graft-mode")).toBeUndefined();
602
+ expect(session.ctx.ui.setStatus).not.toHaveBeenCalled();
597
603
  expect(process.env[TELEMETRY_ENV]).toBeUndefined();
598
604
 
599
605
  const before = session.exec.mock.calls.length;
@@ -714,7 +720,8 @@ describe("mode selection through commands", () => {
714
720
  const handler = session.commands.get("graft")!.handler as (args: string, ctx: unknown) => Promise<void>;
715
721
  await handler("mode hybrid", session.ctx);
716
722
  expect(session.entries).toContainEqual({ customType: MODE_ENTRY_TYPE, data: { mode: "hybrid" as RetrievalMode } });
717
- expect(session.ctx.statuses.get("graft-mode")).toBe("graft mode: hybrid");
723
+ await handler("status", session.ctx);
724
+ expect(notified(session.ctx)).toContain("hybrid —");
718
725
 
719
726
  const result = await (handlerFor(session, "before_agent_start") as (e: unknown, c: unknown) => Promise<unknown>)(
720
727
  { type: "before_agent_start", prompt: "Refactor src/auth.ts" },
@@ -282,7 +282,7 @@ export function registerGraftTools(pi: ExtensionAPI, runtime: GraftToolRuntime):
282
282
  });
283
283
 
284
284
  pi.registerTool({
285
- exposure: "direct",
285
+ exposure: "deferred",
286
286
  name: "graft_repo_map",
287
287
  label: "Graft: repo map",
288
288
  description:
@@ -308,7 +308,7 @@ export function registerGraftTools(pi: ExtensionAPI, runtime: GraftToolRuntime):
308
308
  });
309
309
 
310
310
  pi.registerTool({
311
- exposure: "direct",
311
+ exposure: "deferred",
312
312
  name: "graft_check_freshness",
313
313
  label: "Graft: check freshness",
314
314
  description:
@@ -95,7 +95,7 @@ export function registerSkillTool(pi: ExtensionAPI, store: SkillStore): void {
95
95
  label: "Skill Manager",
96
96
  description: SKILL_TOOL_DESCRIPTION,
97
97
  promptSnippet: "Create, inspect, and update reusable procedures and patterns",
98
- exposure: "direct",
98
+ exposure: "deferred",
99
99
  promptGuidelines: [
100
100
  "Use the skill_manage tool after completing complex tasks that required trial and error or multiple tool calls.",
101
101
  "Use 'create' to save a new reusable procedure, 'patch' to update a section of an existing skill by skill_id, and 'update' for a full rewrite.",
@@ -616,7 +616,7 @@ export default function questionExtension(pi: ExtensionAPI) {
616
616
  description:
617
617
  "Ask one or more typed questions in a terminal wizard. Supports select, multiselect, text, stable option values, Other answers, and partial atomic submission.",
618
618
  promptSnippet: "Ask the user one or more typed questions when a decision is required.",
619
- exposure: "direct",
619
+ exposure: "model-only",
620
620
  promptGuidelines: [
621
621
  "Use question only for decisions the user must make; inspect facts yourself.",
622
622
  "Send one questions array containing independent decisions that can be answered together.",
@@ -24,7 +24,7 @@ import {
24
24
  } from "@earendil-works/pi-ai";
25
25
  import { getApiProvider } from "@earendil-works/pi-ai/compat";
26
26
  import { normalizeContext } from "@earendil-works/pi-ai/utils/transcript";
27
- import { classifyTokenIn, TOKENIN_JEV_CLASSIFIER } from "./jev/classifier.ts";
27
+ import { classifyTokenIn, TOKENIN_CLASSIFIERS } from "./jev/classifier.ts";
28
28
 
29
29
  type TokenInCredentialStore = ExtensionContext["modelRegistry"]["authStorage"];
30
30
 
@@ -1000,8 +1000,8 @@ export default function tokenInOnboardingExtension(pi: ExtensionAPI): void {
1000
1000
  ...ctx.modelRegistry.getModelsOfType("image", "tokenin"),
1001
1001
  ...ctx.modelRegistry.getModelsOfType("classifier", "tokenin"),
1002
1002
  ];
1003
- if (!models.some((model) => model.type === "classifier" && model.id === TOKENIN_JEV_CLASSIFIER.id)) {
1004
- models.push(TOKENIN_JEV_CLASSIFIER);
1003
+ for (const classifier of TOKENIN_CLASSIFIERS) {
1004
+ if (!models.some((model) => model.type === "classifier" && model.id === classifier.id)) models.push(classifier);
1005
1005
  }
1006
1006
  pi.registerProvider("tokenin", { ...provider, models });
1007
1007
  if (event.reason !== "startup") return;
@@ -8,21 +8,33 @@
8
8
  * like any other tool change and survives `/tree`, resume, and fork on that branch.
9
9
  */
10
10
  import { type Static, Type } from "typebox";
11
- import type { ExtensionAPI, ToolDefinition, ToolInfo, ToolNamespace } from "../../core/extensions/types.ts";
11
+ import type { ExtensionAPI, ExtensionContext, ToolDefinition, ToolInfo, ToolNamespace } from "../../core/extensions/types.ts";
12
12
  export declare const TOOL_SEARCH_TOOL_NAME = "tool_search";
13
13
  export declare const DEFAULT_TOOL_SEARCH_LIMIT = 8;
14
14
  /** A tool as the ranker sees it: its name and the text built by {@link createToolSearchDocument}. */
15
15
  export interface ToolSearchDocument {
16
16
  name: string;
17
17
  text: string;
18
+ /** Short human-readable form for rankers that read tools as text (name, first paragraph of the description). */
19
+ summary?: string;
18
20
  }
19
21
  export interface ToolSearchMatch {
20
22
  name: string;
21
23
  score: number;
22
24
  }
23
- /** Ranks tools for a query. BM25 today; a hybrid ranker with embeddings can replace it. */
25
+ /** What a ranker may use besides the query: the caller's cancellation and session (model access). */
26
+ export interface ToolRankOptions {
27
+ signal?: AbortSignal;
28
+ /** Absent when the caller has no session. A ranker that needs one falls back to its own default. */
29
+ ctx?: ExtensionContext;
30
+ }
31
+ /**
32
+ * Ranks tools for a query. BM25 is the built-in; an extension can replace it with
33
+ * {@link registerToolRanker}, for example a model-backed ranker. Rankers may be asynchronous and
34
+ * should degrade to a local ranking rather than throw.
35
+ */
24
36
  export interface ToolRanker {
25
- rank(query: string, documents: readonly ToolSearchDocument[], limit: number): ToolSearchMatch[];
37
+ rank(query: string, documents: readonly ToolSearchDocument[], limit: number, options?: ToolRankOptions): ToolSearchMatch[] | Promise<ToolSearchMatch[]>;
26
38
  }
27
39
  /** Lowercase terms, split at camelCase boundaries and non-alphanumerics, without stop words. */
28
40
  export declare function tokenize(text: string): string[];
@@ -42,6 +54,13 @@ export declare class Bm25Ranker implements ToolRanker {
42
54
  });
43
55
  rank(query: string, documents: readonly ToolSearchDocument[], limit: number): ToolSearchMatch[];
44
56
  }
57
+ /**
58
+ * Make `ranker` the one `tool_search` and codemode's `searchTools()` use, until the returned function
59
+ * is called. Kept on `globalThis` so every copy of this module sees the same ranker.
60
+ */
61
+ export declare function registerToolRanker(ranker: ToolRanker): () => void;
62
+ /** The registered ranker, or BM25. */
63
+ export declare function getToolRanker(): ToolRanker;
45
64
  export declare const toolSearchSchema: Type.TObject<{
46
65
  query: Type.TString;
47
66
  limit: Type.TOptional<Type.TNumber>;
@@ -68,5 +87,5 @@ export interface ToolSearchToolOptions {
68
87
  * The `tool_search` description. It does not list the searchable tools or their namespaces, so it
69
88
  * stays the same while tools are registered, for example when MCP servers connect.
70
89
  */
71
- export declare const TOOL_SEARCH_DESCRIPTION = "# Tool discovery\n\nSearches over deferred tool metadata with BM25 and exposes matching tools for the next model call.\n\nSome of the tools, such as tools of MCP servers, may not have been provided to you upfront, and you should use this tool (`tool_search`) to search for the required tools. For MCP tool discovery, always use `tool_search`.";
90
+ export declare const TOOL_SEARCH_DESCRIPTION = "# Tool discovery\n\nSearches over deferred tool metadata and exposes matching tools for the next model call.\n\nSome of the tools, such as tools of MCP servers, may not have been provided to you upfront, and you should use this tool (`tool_search`) to search for the required tools. For MCP tool discovery, always use `tool_search`.";
72
91
  export declare function createToolSearchToolDefinition(options?: ToolSearchToolOptions): ToolDefinition<typeof toolSearchSchema, ToolSearchToolDetails>;
@@ -86,7 +86,9 @@ export function createToolSearchDocument(tool, namespace) {
86
86
  schemaText(tool.parameters, parts);
87
87
  if (namespace)
88
88
  parts.push(namespace.name, namespace.description ?? "", namespace.instructions ?? "");
89
- return { name: tool.name, text: parts.filter((part) => part.trim()).join(" ") };
89
+ const lead = tool.description.trim().split(/\n\s*\n/)[0] ?? "";
90
+ const summary = [`${tool.name}: ${lead}`, namespace?.description].filter((part) => part?.trim()).join(" - ");
91
+ return { name: tool.name, text: parts.filter((part) => part.trim()).join(" "), summary };
90
92
  }
91
93
  /** Okapi BM25 with the usual parameters. Ties keep document order. */
92
94
  export class Bm25Ranker {
@@ -128,6 +130,24 @@ export class Bm25Ranker {
128
130
  return matches.sort((a, b) => b.score - a.score).slice(0, limit);
129
131
  }
130
132
  }
133
+ const TOOL_RANKER_SLOT = Symbol.for("selesai.toolRanker");
134
+ const bm25Ranker = new Bm25Ranker();
135
+ /**
136
+ * Make `ranker` the one `tool_search` and codemode's `searchTools()` use, until the returned function
137
+ * is called. Kept on `globalThis` so every copy of this module sees the same ranker.
138
+ */
139
+ export function registerToolRanker(ranker) {
140
+ globalThis[TOOL_RANKER_SLOT] = ranker;
141
+ return () => {
142
+ if (globalThis[TOOL_RANKER_SLOT] === ranker) {
143
+ delete globalThis[TOOL_RANKER_SLOT];
144
+ }
145
+ };
146
+ }
147
+ /** The registered ranker, or BM25. */
148
+ export function getToolRanker() {
149
+ return globalThis[TOOL_RANKER_SLOT] ?? bm25Ranker;
150
+ }
131
151
  export const toolSearchSchema = Type.Object({
132
152
  query: Type.String({ description: "Search query for deferred tools." }),
133
153
  limit: Type.Optional(Type.Number({ description: `Maximum number of tools to return. Defaults to ${DEFAULT_TOOL_SEARCH_LIMIT}.` })),
@@ -144,13 +164,19 @@ function isSearchable(exposure) {
144
164
  * Rank the searchable tools that are not active yet and activate the matches, so the next model
145
165
  * call declares them. Activation is recorded in the transcript like any tool change.
146
166
  */
147
- function searchAndLoad(tools, query, limit) {
148
- const active = tools.getActiveTools();
149
- const candidates = tools.getAllTools().filter((tool) => isSearchable(tool.exposure) && !active.includes(tool.name));
167
+ async function searchAndLoad(tools, query, limit, rankOptions) {
168
+ const candidates = tools
169
+ .getAllTools()
170
+ .filter((tool) => isSearchable(tool.exposure) && !tools.getActiveTools().includes(tool.name));
150
171
  const documents = candidates.map((tool) => createToolSearchDocument(tool, tool.namespace));
151
- const matches = new Bm25Ranker().rank(query, documents, limit);
172
+ const ranked = await getToolRanker().rank(query, documents, limit, rankOptions);
173
+ // A registered ranker is third-party code: keep only known, distinct candidates, within the limit.
174
+ const known = new Set(candidates.map((tool) => tool.name));
175
+ const matches = [...new Map(ranked.filter((match) => known.has(match.name)).map((match) => [match.name, match])).values()].slice(0, limit);
176
+ // Read the active set after the await: it may have changed while the ranker ran.
177
+ const active = tools.getActiveTools();
152
178
  if (matches.length > 0)
153
- tools.setActiveTools([...active, ...matches.map((match) => match.name)]);
179
+ tools.setActiveTools([...new Set([...active, ...matches.map((match) => match.name)])]);
154
180
  return matches.map((match) => ({
155
181
  name: match.name,
156
182
  description: candidates.find((tool) => tool.name === match.name)?.description ?? "",
@@ -160,7 +186,7 @@ function searchAndLoad(tools, query, limit) {
160
186
  * The `tool_search` description. It does not list the searchable tools or their namespaces, so it
161
187
  * stays the same while tools are registered, for example when MCP servers connect.
162
188
  */
163
- export const TOOL_SEARCH_DESCRIPTION = `# Tool discovery\n\nSearches over deferred tool metadata with BM25 and exposes matching tools for the next model call.\n\nSome of the tools, such as tools of MCP servers, may not have been provided to you upfront, and you should use this tool (\`${TOOL_SEARCH_TOOL_NAME}\`) to search for the required tools. For MCP tool discovery, always use \`${TOOL_SEARCH_TOOL_NAME}\`.`;
189
+ export const TOOL_SEARCH_DESCRIPTION = `# Tool discovery\n\nSearches over deferred tool metadata and exposes matching tools for the next model call.\n\nSome of the tools, such as tools of MCP servers, may not have been provided to you upfront, and you should use this tool (\`${TOOL_SEARCH_TOOL_NAME}\`) to search for the required tools. For MCP tool discovery, always use \`${TOOL_SEARCH_TOOL_NAME}\`.`;
164
190
  export function createToolSearchToolDefinition(options = {}) {
165
191
  return {
166
192
  name: TOOL_SEARCH_TOOL_NAME,
@@ -170,13 +196,13 @@ export function createToolSearchToolDefinition(options = {}) {
170
196
  parameters: toolSearchSchema,
171
197
  // Searching is not something scripts need; it changes what the model sees.
172
198
  exposure: "model-only",
173
- async execute(_toolCallId, { query, limit }) {
199
+ async execute(_toolCallId, { query, limit }, signal, _onUpdate, ctx) {
174
200
  if (query.trim() === "")
175
201
  throw new Error("query must not be empty");
176
202
  const max = limit ?? DEFAULT_TOOL_SEARCH_LIMIT;
177
203
  if (!Number.isInteger(max) || max <= 0)
178
204
  throw new Error("limit must be a positive integer");
179
- const tools = options.tools ? searchAndLoad(options.tools, query, max) : [];
205
+ const tools = options.tools ? await searchAndLoad(options.tools, query, max, { signal, ctx }) : [];
180
206
  const text = tools.length === 0
181
207
  ? "No matching tools found."
182
208
  : `Loaded ${tools.length} tool${tools.length === 1 ? "" : "s"}. They are available from your next call:\n${tools
package/dist/index.d.ts CHANGED
@@ -32,6 +32,7 @@ export type { CodemodeStoreEntryData, CodemodeToolDetails } from "./extensions/c
32
32
  export type { LoadedMcpConfig, McpExposure, McpServerConfig, McpServerEntry } from "./extensions/mcp/config.ts";
33
33
  export { createMcpExtension, type McpExtensionOptions, type McpTransportFactory } from "./extensions/mcp/index.ts";
34
34
  export { createToolSearchExtension } from "./extensions/tool-search/index.ts";
35
+ export { Bm25Ranker, getToolRanker, registerToolRanker, type ToolRankOptions, type ToolRanker, type ToolSearchDocument, type ToolSearchMatch, } from "./extensions/tool-search/tool.ts";
35
36
  export { type MainOptions, main } from "./main.ts";
36
37
  export { InteractiveMode, type InteractiveModeOptions, type JsonAgentSessionEvent, type ModelInfo, type PrintModeOptions, RpcClient, type RpcClientOptions, type RpcCommand, type RpcEvent, type SubagentRpcCommand, type SubagentRpcEvent, type SubagentRpcResponse, type SubagentRpcSnapshot, type SubagentRpcNode, type SubagentRpcFleet, type SubagentRpcTokens, type SubagentRpcLifecycle, type RpcObservationScope, type ReleaseCommand, type ReleaseResponse, type ReleaseResult, type ReleaseReadinessCommand, type ReleaseReadinessResponse, type ReleaseReadiness, type StopAllBackgroundCommand, type StopAllBackgroundResponse, type StopAllBackgroundResult, type StopAllBackgroundItem, type IntercomRpcCommand, type IntercomRpcResponse, type IntercomRpcEvent, type IntercomRpcStatus, type IntercomRpcDelivery, type IntercomRpcMessageOptions, type IntercomRpcDataMap, type RpcEventListener, type RpcExtensionUIRequest, type RpcExtensionUIResponse, type RpcResponse, type RpcSessionState, runPrintMode, runRpcMode, } from "./modes/index.ts";
37
38
  export { ArminComponent, AssistantMessageComponent, BashExecutionComponent, BorderedLoader, BranchSummaryMessageComponent, CompactionSummaryMessageComponent, CustomEditor, type CustomEditorOptions, CustomMessageComponent, DynamicBorder, ExtensionEditorComponent, ExtensionInputComponent, ExtensionSelectorComponent, FooterComponent, keyHint, keyText, LoginDialogComponent, ModelSelectorComponent, OAuthSelectorComponent, type RenderDiffOptions, rawKeyHint, renderDiff, SessionSelectorComponent, type SettingsCallbacks, type SettingsConfig, SettingsSelectorComponent, ShowImagesSelectorComponent, SkillInvocationMessageComponent, ThemeSelectorComponent, ThinkingSelectorComponent, ToolExecutionComponent, type ToolExecutionOptions, TreeSelectorComponent, truncateToVisualLines, UserMessageComponent, UserMessageSelectorComponent, type VisualTruncateResult, } from "./modes/interactive/components/index.ts";
package/dist/index.js CHANGED
@@ -35,6 +35,7 @@ export { VIRTUAL_MODEL_STATE_ENTRY, } from "./core/virtual-models.js";
35
35
  export { createCodemodeExtension } from "./extensions/codemode/index.js";
36
36
  export { createMcpExtension } from "./extensions/mcp/index.js";
37
37
  export { createToolSearchExtension } from "./extensions/tool-search/index.js";
38
+ export { Bm25Ranker, getToolRanker, registerToolRanker, } from "./extensions/tool-search/tool.js";
38
39
  // Main entry point
39
40
  export { main } from "./main.js";
40
41
  // Run modes for programmatic SDK usage
package/docs/codemode.md CHANGED
@@ -29,7 +29,7 @@ The result starts with `Script completed` or `Script failed`, the wall time, and
29
29
  | `exit()` | End the script successfully. |
30
30
  | `store(key, value)` / `load(key)` | Keep small JSON values across `codemode` calls. See [Store values](#store-values). |
31
31
  | `ALL_TOOLS` | Every callable tool as `{ name, description }`, including tools the description does not list. |
32
- | `searchTools(query, { limit?, namespace? })` | Rank callable tools by relevance (BM25, default limit 8). Resolves to `{ name, description }[]`. |
32
+ | `searchTools(query, { limit?, namespace? })` | Rank callable tools by relevance (default limit 8). Asks Jev (`jevAdvisory.routes.toolSearch`) when a Token-In account is available, for the first 8 searches of a script; otherwise BM25. Resolves to `{ name, description }[]`. |
33
33
  | `describeTool(name)` | Resolves to a tool's description and TypeScript declaration, or `undefined`. |
34
34
  | `describeNamespace(name)` | Resolves to `{ name, description?, instructions?, tools }` for a namespace such as an MCP server, or `undefined`. |
35
35
  | `models` | List and run non-LLM models. See [Models](#models). |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selesai/code",
3
- "version": "0.14.0",
3
+ "version": "0.14.3",
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 test/auto-handoff-retirement.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-ask-tool.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-tool-orchestration.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 test/mermaid.test.ts test/settings-manager-mermaid.test.ts test/syntax-highlight.test.ts test/rpc-prompt-response-semantics.test.ts test/rpc-client-clear-queue.test.ts test/rpc-client-process-exit.test.ts test/rpc-client-prompt-disposition.test.ts test/keybindings.test.ts test/user-message.test.ts src/extensions/jev-find-source.test.ts src/extensions/skills-index/index.test.ts test/gateway-retirement.test.ts test/config-selector-builtins.test.ts test/mcp-fork-safety.test.ts test/mcp-cli-real.test.ts test/mcp-prompt-contract.test.ts test/shell-structured-output.test.ts test/rpc-real-bridge.test.ts test/rpc-caption-handoff.test.ts test/native-runtime-packages.test.ts test/rtk-scope-lifecycle.test.ts src/extensions/jev/classifier.test.ts test/tokenin-classifier-registration.test.ts test/program-status-reporter.test.ts test/output-pad.test.ts test/mcp-manager-view.test.ts test/suite/regressions/7187-malformed-package-manifest.test.ts test/suite/regressions/7193-event-bus-lifecycle.test.ts test/suite/regressions/7209-model-selector-filter-resets-selection.test.ts test/suite/regressions/7253-manual-compact-during-response.test.ts test/suite/regressions/7269-cli-end-of-options.test.ts test/suite/regressions/7290-json-stream-linear.test.ts test/suite/regressions/7497-session-discovery-symlink.test.ts test/suite/regressions/7731-tui-method-wrapping.test.ts test/suite/regressions/7829-invalid-settings-warning.test.ts test/suite/regressions/7911-json-stream-usage.test.ts test/suite/regressions/7925-toolcall-start-metadata.test.ts test/suite/regressions/8328-zero-usage-auto-compaction.test.ts test/suite/regressions/8337-utf8-bom-parsing.test.ts test/suite/regressions/8423-extension-factory-failure.test.ts test/suite/regressions/8964-extension-provider-streaming.test.ts test/suite/regressions/8989-fork-compaction-label-boundary.test.ts test/git-update.test.ts test/skills.test.ts test/settings-diagnostics.test.ts test/auth-check.test.ts test/credential-print.test.ts src/core/agent-session-custom-tools.test.ts src/core/agent-session-length-continuation.test.ts src/core/package-manager.test.ts",
59
- "test:coverage": "vitest run --coverage src/__tests__/model-registry-completion.test.ts src/extensions/jev/decisions.test.ts test/auto-handoff-retirement.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-ask-tool.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-tool-orchestration.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 test/mermaid.test.ts test/settings-manager-mermaid.test.ts test/syntax-highlight.test.ts test/rpc-prompt-response-semantics.test.ts test/rpc-client-clear-queue.test.ts test/rpc-client-process-exit.test.ts test/rpc-client-prompt-disposition.test.ts test/keybindings.test.ts test/user-message.test.ts src/extensions/jev-find-source.test.ts src/extensions/skills-index/index.test.ts test/gateway-retirement.test.ts test/config-selector-builtins.test.ts test/mcp-fork-safety.test.ts test/mcp-cli-real.test.ts test/mcp-prompt-contract.test.ts test/shell-structured-output.test.ts test/rpc-real-bridge.test.ts test/rpc-caption-handoff.test.ts test/native-runtime-packages.test.ts test/rtk-scope-lifecycle.test.ts src/extensions/jev/classifier.test.ts test/tokenin-classifier-registration.test.ts test/program-status-reporter.test.ts test/output-pad.test.ts test/mcp-manager-view.test.ts test/suite/regressions/7187-malformed-package-manifest.test.ts test/suite/regressions/7193-event-bus-lifecycle.test.ts test/suite/regressions/7209-model-selector-filter-resets-selection.test.ts test/suite/regressions/7253-manual-compact-during-response.test.ts test/suite/regressions/7269-cli-end-of-options.test.ts test/suite/regressions/7290-json-stream-linear.test.ts test/suite/regressions/7497-session-discovery-symlink.test.ts test/suite/regressions/7731-tui-method-wrapping.test.ts test/suite/regressions/7829-invalid-settings-warning.test.ts test/suite/regressions/7911-json-stream-usage.test.ts test/suite/regressions/7925-toolcall-start-metadata.test.ts test/suite/regressions/8328-zero-usage-auto-compaction.test.ts test/suite/regressions/8337-utf8-bom-parsing.test.ts test/suite/regressions/8423-extension-factory-failure.test.ts test/suite/regressions/8964-extension-provider-streaming.test.ts test/suite/regressions/8989-fork-compaction-label-boundary.test.ts test/git-update.test.ts test/skills.test.ts test/settings-diagnostics.test.ts test/auth-check.test.ts test/credential-print.test.ts src/core/agent-session-custom-tools.test.ts src/core/agent-session-length-continuation.test.ts src/core/package-manager.test.ts",
58
+ "test": "vitest run src/__tests__/model-registry-completion.test.ts src/extensions/jev/decisions.test.ts test/auto-handoff-retirement.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-ask-tool.test.ts src/extensions/jev-tool-ranker.test.ts src/extensions/tool-search/tool.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-tool-orchestration.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 test/mermaid.test.ts test/settings-manager-mermaid.test.ts test/syntax-highlight.test.ts test/rpc-prompt-response-semantics.test.ts test/rpc-client-clear-queue.test.ts test/rpc-client-process-exit.test.ts test/rpc-client-prompt-disposition.test.ts test/keybindings.test.ts test/user-message.test.ts src/extensions/jev-find-source.test.ts src/extensions/skills-index/index.test.ts test/gateway-retirement.test.ts test/config-selector-builtins.test.ts test/mcp-fork-safety.test.ts test/mcp-cli-real.test.ts test/mcp-prompt-contract.test.ts test/shell-structured-output.test.ts test/rpc-real-bridge.test.ts test/rpc-caption-handoff.test.ts test/native-runtime-packages.test.ts test/rtk-scope-lifecycle.test.ts src/extensions/jev/classifier.test.ts test/tokenin-classifier-registration.test.ts test/program-status-reporter.test.ts test/output-pad.test.ts test/mcp-manager-view.test.ts test/suite/regressions/7187-malformed-package-manifest.test.ts test/suite/regressions/7193-event-bus-lifecycle.test.ts test/suite/regressions/7209-model-selector-filter-resets-selection.test.ts test/suite/regressions/7253-manual-compact-during-response.test.ts test/suite/regressions/7269-cli-end-of-options.test.ts test/suite/regressions/7290-json-stream-linear.test.ts test/suite/regressions/7497-session-discovery-symlink.test.ts test/suite/regressions/7731-tui-method-wrapping.test.ts test/suite/regressions/7829-invalid-settings-warning.test.ts test/suite/regressions/7911-json-stream-usage.test.ts test/suite/regressions/7925-toolcall-start-metadata.test.ts test/suite/regressions/8328-zero-usage-auto-compaction.test.ts test/suite/regressions/8337-utf8-bom-parsing.test.ts test/suite/regressions/8423-extension-factory-failure.test.ts test/suite/regressions/8964-extension-provider-streaming.test.ts test/suite/regressions/8989-fork-compaction-label-boundary.test.ts test/git-update.test.ts test/skills.test.ts test/settings-diagnostics.test.ts test/auth-check.test.ts test/credential-print.test.ts src/core/agent-session-custom-tools.test.ts src/core/agent-session-length-continuation.test.ts src/core/package-manager.test.ts",
59
+ "test:coverage": "vitest run --coverage src/__tests__/model-registry-completion.test.ts src/extensions/jev/decisions.test.ts test/auto-handoff-retirement.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-ask-tool.test.ts src/extensions/jev-tool-ranker.test.ts src/extensions/tool-search/tool.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-tool-orchestration.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 test/mermaid.test.ts test/settings-manager-mermaid.test.ts test/syntax-highlight.test.ts test/rpc-prompt-response-semantics.test.ts test/rpc-client-clear-queue.test.ts test/rpc-client-process-exit.test.ts test/rpc-client-prompt-disposition.test.ts test/keybindings.test.ts test/user-message.test.ts src/extensions/jev-find-source.test.ts src/extensions/skills-index/index.test.ts test/gateway-retirement.test.ts test/config-selector-builtins.test.ts test/mcp-fork-safety.test.ts test/mcp-cli-real.test.ts test/mcp-prompt-contract.test.ts test/shell-structured-output.test.ts test/rpc-real-bridge.test.ts test/rpc-caption-handoff.test.ts test/native-runtime-packages.test.ts test/rtk-scope-lifecycle.test.ts src/extensions/jev/classifier.test.ts test/tokenin-classifier-registration.test.ts test/program-status-reporter.test.ts test/output-pad.test.ts test/mcp-manager-view.test.ts test/suite/regressions/7187-malformed-package-manifest.test.ts test/suite/regressions/7193-event-bus-lifecycle.test.ts test/suite/regressions/7209-model-selector-filter-resets-selection.test.ts test/suite/regressions/7253-manual-compact-during-response.test.ts test/suite/regressions/7269-cli-end-of-options.test.ts test/suite/regressions/7290-json-stream-linear.test.ts test/suite/regressions/7497-session-discovery-symlink.test.ts test/suite/regressions/7731-tui-method-wrapping.test.ts test/suite/regressions/7829-invalid-settings-warning.test.ts test/suite/regressions/7911-json-stream-usage.test.ts test/suite/regressions/7925-toolcall-start-metadata.test.ts test/suite/regressions/8328-zero-usage-auto-compaction.test.ts test/suite/regressions/8337-utf8-bom-parsing.test.ts test/suite/regressions/8423-extension-factory-failure.test.ts test/suite/regressions/8964-extension-provider-streaming.test.ts test/suite/regressions/8989-fork-compaction-label-boundary.test.ts test/git-update.test.ts test/skills.test.ts test/settings-diagnostics.test.ts test/auth-check.test.ts test/credential-print.test.ts src/core/agent-session-custom-tools.test.ts src/core/agent-session-length-continuation.test.ts src/core/package-manager.test.ts",
60
60
  "prepare": "npm run build",
61
61
  "build:native": "bun scripts/build-native.ts",
62
62
  "build": "npm run clean && tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js dist/rpc-entry.js && npm run copy-assets",