@selesai/code 0.13.29 → 0.13.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +8 -2
- package/dist/core/model-registry.d.ts +13 -1
- package/dist/core/model-registry.js +16 -0
- package/dist/defaults/settings.json +8 -13
- package/dist/extensions/capability-gateway/catalog.ts +2 -2
- package/dist/extensions/capability-gateway/index.ts +103 -18
- package/dist/extensions/capability-gateway/integration.test.ts +394 -8
- package/dist/extensions/capability-gateway/routing.test.ts +415 -0
- package/dist/extensions/capability-gateway/routing.ts +221 -0
- package/dist/extensions/grep-app/index.ts +10 -0
- package/dist/extensions/jev/decisions.test.ts +316 -0
- package/dist/extensions/jev/decisions.ts +527 -0
- package/dist/extensions/jev/test-support.ts +233 -0
- package/dist/extensions/jev-advisory-lifecycle.test.ts +206 -0
- package/dist/extensions/jev-advisory-memory.test.ts +191 -0
- package/dist/extensions/jev-advisory-recommendations.test.ts +240 -0
- package/dist/extensions/jev-advisory-routing.ts +539 -0
- package/dist/extensions/package.json +2 -2
- package/dist/extensions/pi-hermes-memory/src/memory-search-bridge.ts +40 -0
- package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +57 -46
- package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +17 -0
- package/dist/extensions/pi-hermes-memory/src/tools/session-search-tool.ts +10 -0
- package/dist/extensions/pi-hermes-memory/src/tools/skill-tool.ts +5 -0
- package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +25 -0
- package/dist/extensions/pi-intercom/index.ts +10 -0
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +5 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +5 -0
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +10 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +10 -0
- package/dist/extensions/pi-web-agent/src/extension.ts +5 -0
- package/dist/extensions/question/index.ts +5 -0
- package/dist/extensions/tokenin-onboarding.ts +185 -0
- package/dist/skills/code-review-and-quality/SKILL.md +396 -0
- package/dist/skills/code-simplification/SKILL.md +331 -0
- package/dist/skills/incremental-implementation/SKILL.md +249 -0
- package/dist/skills/planning-and-task-breakdown/SKILL.md +257 -0
- package/dist/skills/references/agent-skills-LICENSE +21 -0
- package/dist/skills/references/definition-of-done.md +67 -0
- package/dist/skills/references/performance-checklist.md +236 -0
- package/dist/skills/references/security-checklist.md +248 -0
- package/docs/settings.md +68 -31
- package/package.json +3 -3
- package/dist/extensions/auto-model.test.ts +0 -438
- package/dist/extensions/auto-model.ts +0 -357
|
@@ -2,16 +2,19 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
6
|
import { fauxProvider } from "@earendil-works/pi-ai/providers/faux";
|
|
7
7
|
import {
|
|
8
8
|
createAgentSession,
|
|
9
|
+
createEventBus,
|
|
9
10
|
DefaultResourceLoader,
|
|
10
11
|
ModelRuntime,
|
|
11
12
|
SessionManager,
|
|
12
13
|
SettingsManager,
|
|
13
14
|
type AgentSession,
|
|
15
|
+
type EventBus,
|
|
14
16
|
} from "@selesai/code";
|
|
17
|
+
import { allowNetwork } from "../../../test/test-network-env.ts";
|
|
15
18
|
|
|
16
19
|
const EXTENSIONS_DIR = fileURLToPath(new URL("../../", import.meta.url));
|
|
17
20
|
const GATEWAY_DIR = fileURLToPath(new URL(".", import.meta.url));
|
|
@@ -21,14 +24,34 @@ const INLINE_SKILLS_FILE = fileURLToPath(new URL("../inline-skills.ts", import.m
|
|
|
21
24
|
|
|
22
25
|
interface Harness {
|
|
23
26
|
session: AgentSession;
|
|
27
|
+
/** Everything the gateway emitted on its telemetry channel. */
|
|
28
|
+
telemetry: Array<Record<string, unknown>>;
|
|
24
29
|
dispose: () => Promise<void>;
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
|
|
32
|
+
interface HarnessOptions {
|
|
28
33
|
enabled: boolean;
|
|
29
34
|
withSkill?: boolean;
|
|
30
35
|
extensions?: string[];
|
|
31
|
-
|
|
36
|
+
/** Written to `capabilityGateway.routing.jev` in the session's settings.json. */
|
|
37
|
+
jev?: Record<string, unknown>;
|
|
38
|
+
/** Make the gateway's telemetry channel throw on every emit. */
|
|
39
|
+
telemetryDown?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A bus that fails only on the gateway's own telemetry channel. */
|
|
43
|
+
function telemetryDownBus(): EventBus {
|
|
44
|
+
const bus = createEventBus();
|
|
45
|
+
return {
|
|
46
|
+
emit: (channel, data) => {
|
|
47
|
+
if (channel === "capability-gateway") throw new Error("telemetry channel down");
|
|
48
|
+
bus.emit(channel, data);
|
|
49
|
+
},
|
|
50
|
+
on: bus.on,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function createGatewaySession(options: HarnessOptions): Promise<Harness> {
|
|
32
55
|
const extensionPaths = options.extensions ?? [EXTENSIONS_DIR];
|
|
33
56
|
const cwd = mkdtempSync(join(tmpdir(), "gw-cwd-"));
|
|
34
57
|
const home = mkdtempSync(join(tmpdir(), "gw-home-"));
|
|
@@ -45,6 +68,13 @@ async function createGatewaySession(options: {
|
|
|
45
68
|
if (options.enabled) delete process.env.SELESAI_CAPABILITY_GATEWAY;
|
|
46
69
|
else process.env.SELESAI_CAPABILITY_GATEWAY = "0";
|
|
47
70
|
|
|
71
|
+
if (options.jev) {
|
|
72
|
+
writeFileSync(
|
|
73
|
+
join(home, "settings.json"),
|
|
74
|
+
JSON.stringify({ capabilityGateway: { routing: { jev: options.jev } } }),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
48
78
|
if (options.withSkill) {
|
|
49
79
|
mkdirSync(join(home, "skills", "research"), { recursive: true });
|
|
50
80
|
writeFileSync(
|
|
@@ -71,11 +101,20 @@ async function createGatewaySession(options: {
|
|
|
71
101
|
if (!model) throw new Error("faux model not registered");
|
|
72
102
|
|
|
73
103
|
const settingsManager = SettingsManager.inMemory({ compaction: { enabled: false }, retry: { enabled: false } });
|
|
104
|
+
// Subscribe to the gateway's telemetry channel before the extension can emit.
|
|
105
|
+
const eventBus = options.telemetryDown ? telemetryDownBus() : createEventBus();
|
|
106
|
+
const telemetry: Array<Record<string, unknown>> = [];
|
|
107
|
+
if (!options.telemetryDown) {
|
|
108
|
+
eventBus.on("capability-gateway", (data) => {
|
|
109
|
+
telemetry.push(data as Record<string, unknown>);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
74
112
|
const loader = new DefaultResourceLoader({
|
|
75
113
|
cwd,
|
|
76
114
|
agentDir: home,
|
|
77
115
|
settingsManager,
|
|
78
116
|
additionalExtensionPaths: extensionPaths,
|
|
117
|
+
eventBus,
|
|
79
118
|
noPromptTemplates: true,
|
|
80
119
|
noThemes: true,
|
|
81
120
|
noContextFiles: true,
|
|
@@ -114,12 +153,13 @@ async function createGatewaySession(options: {
|
|
|
114
153
|
rmSync(home, { recursive: true, force: true });
|
|
115
154
|
};
|
|
116
155
|
|
|
117
|
-
return { session, dispose };
|
|
156
|
+
return { session, telemetry, dispose };
|
|
118
157
|
}
|
|
119
158
|
|
|
120
159
|
const harnesses: Harness[] = [];
|
|
121
160
|
afterEach(async () => {
|
|
122
161
|
for (const h of harnesses.splice(0)) await h.dispose();
|
|
162
|
+
vi.unstubAllGlobals();
|
|
123
163
|
});
|
|
124
164
|
|
|
125
165
|
describe("capability gateway integration", () => {
|
|
@@ -236,7 +276,7 @@ describe("capability gateway integration", () => {
|
|
|
236
276
|
h.session.systemPrompt,
|
|
237
277
|
{ cwd: process.cwd() } as never,
|
|
238
278
|
);
|
|
239
|
-
expect(result).
|
|
279
|
+
expect(result.messages).toEqual([]);
|
|
240
280
|
expect(h.session.getActiveToolNames()).toContain("grep_app_search");
|
|
241
281
|
});
|
|
242
282
|
|
|
@@ -253,7 +293,7 @@ describe("capability gateway integration", () => {
|
|
|
253
293
|
h.session.systemPrompt,
|
|
254
294
|
{ cwd: process.cwd() } as never,
|
|
255
295
|
);
|
|
256
|
-
expect(result).
|
|
296
|
+
expect(result.messages).toEqual([]);
|
|
257
297
|
expect(h.session.systemPrompt).not.toContain("Research instructions body.");
|
|
258
298
|
});
|
|
259
299
|
|
|
@@ -266,7 +306,7 @@ describe("capability gateway integration", () => {
|
|
|
266
306
|
h.session.systemPrompt,
|
|
267
307
|
{ cwd: process.cwd() } as never,
|
|
268
308
|
);
|
|
269
|
-
expect(result).
|
|
309
|
+
expect(result.messages).toEqual([]);
|
|
270
310
|
});
|
|
271
311
|
|
|
272
312
|
it("does not recommend capability_skill_show for an inline-loaded $skill", async () => {
|
|
@@ -288,6 +328,352 @@ describe("capability gateway integration", () => {
|
|
|
288
328
|
h.session.systemPrompt,
|
|
289
329
|
{ cwd: process.cwd() } as never,
|
|
290
330
|
);
|
|
291
|
-
expect(result).
|
|
331
|
+
expect(result.messages).toEqual([]);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
// Jev-assisted routing (opt-in): capabilityGateway.routing.jev.
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
|
|
339
|
+
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";
|
|
343
|
+
// No tool name, alias, or summary token matches: the router has no lexical signal and Jev
|
|
344
|
+
// must never be consulted.
|
|
345
|
+
const NO_SIGNAL_PROMPT = "continue where we left off last time";
|
|
346
|
+
|
|
347
|
+
const jevSettings = (overrides: Record<string, unknown> = {}): Record<string, unknown> => ({
|
|
348
|
+
enabled: true,
|
|
349
|
+
provider: "faux-gw",
|
|
350
|
+
baseUrl: "http://jev.test/v1",
|
|
351
|
+
minConfidence: 0.5,
|
|
352
|
+
timeoutMs: 2_000,
|
|
353
|
+
...overrides,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
function jevSseResponse(choice: string, confidence: number): Response {
|
|
357
|
+
const body = JSON.stringify({ answers: { capability: { choice, confidence } } });
|
|
358
|
+
const chunk = (delta: unknown, finish: string | null) =>
|
|
359
|
+
`data: ${JSON.stringify({
|
|
360
|
+
id: "1",
|
|
361
|
+
object: "chat.completion.chunk",
|
|
362
|
+
created: 0,
|
|
363
|
+
model: "jev-1.13",
|
|
364
|
+
choices: [{ index: 0, delta, finish_reason: finish }],
|
|
365
|
+
})}\n\n`;
|
|
366
|
+
return new Response(chunk({ role: "assistant", content: body }, null) + chunk({}, "stop") + "data: [DONE]\n\n", {
|
|
367
|
+
status: 200,
|
|
368
|
+
headers: { "content-type": "text/event-stream" },
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function stubJev(choice: string, confidence = 0.95): { requests: string[]; fetchMock: ReturnType<typeof vi.fn> } {
|
|
373
|
+
const requests: string[] = [];
|
|
374
|
+
const fetchMock = vi.fn(async (_url: unknown, init?: { body?: unknown }) => {
|
|
375
|
+
requests.push(typeof init?.body === "string" ? init.body : "");
|
|
376
|
+
return jevSseResponse(choice, confidence);
|
|
377
|
+
});
|
|
378
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
379
|
+
return { requests, fetchMock };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function route(h: Harness, prompt: string): Promise<void> {
|
|
383
|
+
const result = await h.session.extensionRunner.emitBeforeAgentStart(
|
|
384
|
+
prompt,
|
|
385
|
+
undefined,
|
|
386
|
+
h.session.systemPrompt,
|
|
387
|
+
{ cwd: process.cwd() } as never,
|
|
388
|
+
);
|
|
389
|
+
expect(result.messages).toEqual([]);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function routeEvents(h: Harness): Array<Record<string, unknown>> {
|
|
393
|
+
return h.telemetry.filter((event) => event.event === "route");
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Telemetry may carry route metadata and canonical tool names, never prompt
|
|
397
|
+
// text, conversation turns, credentials, raw Jev output, or tool arguments.
|
|
398
|
+
const TELEMETRY_KEYS = new Set([
|
|
399
|
+
"event",
|
|
400
|
+
"source",
|
|
401
|
+
"outcome",
|
|
402
|
+
"reason",
|
|
403
|
+
"tool",
|
|
404
|
+
"confidence",
|
|
405
|
+
"candidates",
|
|
406
|
+
"durationMs",
|
|
407
|
+
"baselineCount",
|
|
408
|
+
"dormantCount",
|
|
409
|
+
"activeCount",
|
|
410
|
+
"activated",
|
|
411
|
+
"used",
|
|
412
|
+
"results",
|
|
413
|
+
"skill",
|
|
414
|
+
]);
|
|
415
|
+
|
|
416
|
+
it("activates the tool Jev selects for the run, then restores the baseline", async () => {
|
|
417
|
+
allowNetwork();
|
|
418
|
+
const { requests } = stubJev("grep_app_search", 0.91);
|
|
419
|
+
const h = await createGatewaySession({
|
|
420
|
+
enabled: true,
|
|
421
|
+
extensions: [GATEWAY_DIR, GREP_APP_DIR, GRAFT_DIR],
|
|
422
|
+
jev: jevSettings(),
|
|
423
|
+
});
|
|
424
|
+
harnesses.push(h);
|
|
425
|
+
const baseline = h.session.getActiveToolNames();
|
|
426
|
+
expect(baseline).not.toContain("grep_app_search");
|
|
427
|
+
|
|
428
|
+
await route(h, HINT_PROMPT);
|
|
429
|
+
|
|
430
|
+
expect(h.session.getActiveToolNames()).toContain("grep_app_search");
|
|
431
|
+
// Built-ins and always-active code-context tools stay active.
|
|
432
|
+
for (const name of ["read", "bash", "capability_catalog", "graft_find_code"]) {
|
|
433
|
+
expect(h.session.getActiveToolNames()).toContain(name);
|
|
434
|
+
}
|
|
435
|
+
expect(routeEvents(h)).toContainEqual(
|
|
436
|
+
expect.objectContaining({ source: "jev", outcome: "attempt", candidates: 2 }),
|
|
437
|
+
);
|
|
438
|
+
expect(routeEvents(h)).toContainEqual(
|
|
439
|
+
expect.objectContaining({
|
|
440
|
+
source: "jev",
|
|
441
|
+
outcome: "activated",
|
|
442
|
+
tool: "grep_app_search",
|
|
443
|
+
confidence: "high",
|
|
444
|
+
candidates: 2,
|
|
445
|
+
durationMs: expect.any(Number),
|
|
446
|
+
}),
|
|
447
|
+
);
|
|
448
|
+
expect(requests).toHaveLength(1);
|
|
449
|
+
|
|
450
|
+
// The activation was temporary: settlement restores the baseline set.
|
|
451
|
+
await h.session.extensionRunner.emit({ type: "agent_settled" });
|
|
452
|
+
expect(h.session.getActiveToolNames()).toEqual(baseline);
|
|
453
|
+
expect(h.telemetry).toContainEqual(
|
|
454
|
+
expect.objectContaining({ event: "reset", activated: 1, used: 0 }),
|
|
455
|
+
);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("exposes the selected schema for the same turn and keeps the other hinted candidate dormant", async () => {
|
|
459
|
+
allowNetwork();
|
|
460
|
+
stubJev("grep_app_search", 0.9);
|
|
461
|
+
const h = await createGatewaySession({
|
|
462
|
+
enabled: true,
|
|
463
|
+
extensions: [GATEWAY_DIR, GREP_APP_DIR],
|
|
464
|
+
jev: jevSettings(),
|
|
465
|
+
});
|
|
466
|
+
harnesses.push(h);
|
|
467
|
+
|
|
468
|
+
await route(h, HINT_PROMPT);
|
|
469
|
+
|
|
470
|
+
// The live loadout is what the resulting turn uses, and the selected tool's
|
|
471
|
+
// schema is registered for it.
|
|
472
|
+
expect(h.session.getActiveToolNames()).toContain("grep_app_search");
|
|
473
|
+
const definition = h.session.getToolDefinition("grep_app_search");
|
|
474
|
+
expect(definition).toBeDefined();
|
|
475
|
+
expect(definition?.parameters).toBeDefined();
|
|
476
|
+
// The other hinted candidate (and every nonselected tool) stays dormant.
|
|
477
|
+
expect(h.session.getActiveToolNames()).not.toContain("grep_app_fetch");
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it("does not call Jev without a lexical tool signal or for a skill-only match", async () => {
|
|
481
|
+
allowNetwork();
|
|
482
|
+
const { fetchMock } = stubJev("grep_app_search");
|
|
483
|
+
const h = await createGatewaySession({
|
|
484
|
+
enabled: true,
|
|
485
|
+
withSkill: true,
|
|
486
|
+
extensions: [GATEWAY_DIR, GREP_APP_DIR],
|
|
487
|
+
jev: jevSettings(),
|
|
488
|
+
});
|
|
489
|
+
harnesses.push(h);
|
|
490
|
+
|
|
491
|
+
await route(h, NO_SIGNAL_PROMPT);
|
|
492
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
493
|
+
expect(routeEvents(h)).toEqual([]);
|
|
494
|
+
expect(h.session.getActiveToolNames()).not.toContain("grep_app_search");
|
|
495
|
+
|
|
496
|
+
// A unique skill match is a recommendation, never a Jev auto-selection.
|
|
497
|
+
await route(h, "do research on this topic");
|
|
498
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
499
|
+
expect(routeEvents(h)).toEqual([]);
|
|
500
|
+
expect(h.session.getActiveToolNames()).not.toContain("research");
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("does not call Jev when the deterministic router already activated a tool", async () => {
|
|
504
|
+
allowNetwork();
|
|
505
|
+
const deterministic = stubJev("grep_app_search");
|
|
506
|
+
const h = await createGatewaySession({
|
|
507
|
+
enabled: true,
|
|
508
|
+
extensions: [GATEWAY_DIR, GREP_APP_DIR],
|
|
509
|
+
jev: jevSettings(),
|
|
510
|
+
});
|
|
511
|
+
harnesses.push(h);
|
|
512
|
+
|
|
513
|
+
await route(h, "search github code with grep_app_search");
|
|
514
|
+
expect(h.session.getActiveToolNames()).toContain("grep_app_search");
|
|
515
|
+
expect(deterministic.fetchMock).not.toHaveBeenCalled();
|
|
516
|
+
expect(routeEvents(h)).toContainEqual(
|
|
517
|
+
expect.objectContaining({ source: "deterministic", outcome: "activated", tool: "grep_app_search" }),
|
|
518
|
+
);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it("leaves routing deterministic when the Jev route is not configured", async () => {
|
|
522
|
+
allowNetwork();
|
|
523
|
+
const disabled = stubJev("grep_app_search");
|
|
524
|
+
const h = await createGatewaySession({ enabled: true, extensions: [GATEWAY_DIR, GREP_APP_DIR] });
|
|
525
|
+
harnesses.push(h);
|
|
526
|
+
|
|
527
|
+
await route(h, HINT_PROMPT);
|
|
528
|
+
|
|
529
|
+
expect(disabled.fetchMock).not.toHaveBeenCalled();
|
|
530
|
+
expect(h.session.getActiveToolNames()).not.toContain("grep_app_search");
|
|
531
|
+
expect(routeEvents(h)).toEqual([]);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it("offers Jev only the current prompt and the hinted candidates, never the full catalog", async () => {
|
|
535
|
+
allowNetwork();
|
|
536
|
+
const { requests } = stubJev("none", 1);
|
|
537
|
+
const h = await createGatewaySession({
|
|
538
|
+
enabled: true,
|
|
539
|
+
withSkill: true,
|
|
540
|
+
extensions: [GATEWAY_DIR, GREP_APP_DIR, GRAFT_DIR],
|
|
541
|
+
jev: jevSettings(),
|
|
542
|
+
});
|
|
543
|
+
harnesses.push(h);
|
|
544
|
+
|
|
545
|
+
await route(h, HINT_PROMPT);
|
|
546
|
+
|
|
547
|
+
const sent = JSON.parse(requests[0]!) as { messages: Array<{ content: string }> };
|
|
548
|
+
const payload = JSON.parse(sent.messages[0]!.content) as {
|
|
549
|
+
state: { conversation: unknown[]; system_prompt?: string };
|
|
550
|
+
questions: Record<string, { criteria: Record<string, string> }>;
|
|
551
|
+
};
|
|
552
|
+
const criteria = Object.keys(payload.questions.capability!.criteria);
|
|
553
|
+
expect(criteria).toEqual(["none", "grep_app_search", "grep_app_fetch"]);
|
|
554
|
+
// Built-ins, gateway tools, always-active Graft tools, and skills are not selectable.
|
|
555
|
+
for (const name of ["read", "capability_catalog", "capability_skill_show", "graft_find_code", "research"]) {
|
|
556
|
+
expect(criteria).not.toContain(name);
|
|
557
|
+
}
|
|
558
|
+
// Only the current prompt travels; never conversation history or a system prompt.
|
|
559
|
+
expect(payload.state.conversation).toEqual([{ role: "user", text: HINT_PROMPT }]);
|
|
560
|
+
expect(payload.state.system_prompt).toBeUndefined();
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("abstains on `none` and stays non-blocking when Jev is unavailable", async () => {
|
|
564
|
+
allowNetwork();
|
|
565
|
+
const { requests } = stubJev("none", 1);
|
|
566
|
+
const h = await createGatewaySession({
|
|
567
|
+
enabled: true,
|
|
568
|
+
extensions: [GATEWAY_DIR, GREP_APP_DIR],
|
|
569
|
+
jev: jevSettings(),
|
|
570
|
+
});
|
|
571
|
+
harnesses.push(h);
|
|
572
|
+
|
|
573
|
+
await route(h, HINT_PROMPT);
|
|
574
|
+
expect(h.session.getActiveToolNames()).not.toContain("grep_app_search");
|
|
575
|
+
expect(routeEvents(h)).toContainEqual(
|
|
576
|
+
expect.objectContaining({ source: "jev", outcome: "abstained", reason: "none", candidates: 2 }),
|
|
577
|
+
);
|
|
578
|
+
expect(requests).toHaveLength(1);
|
|
579
|
+
|
|
580
|
+
// A provider outage is an ordinary fallback: nothing activates, and the
|
|
581
|
+
// telemetry reports the route without recording any content.
|
|
582
|
+
vi.stubGlobal(
|
|
583
|
+
"fetch",
|
|
584
|
+
vi.fn(async () => {
|
|
585
|
+
throw new Error("connection reset");
|
|
586
|
+
}),
|
|
587
|
+
);
|
|
588
|
+
await route(h, HINT_PROMPT);
|
|
589
|
+
expect(h.session.getActiveToolNames()).not.toContain("grep_app_search");
|
|
590
|
+
expect(routeEvents(h)).toContainEqual(
|
|
591
|
+
expect.objectContaining({ source: "jev", outcome: "unavailable", reason: "transport" }),
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
const serialized = JSON.stringify(h.telemetry);
|
|
595
|
+
expect(serialized).not.toContain(HINT_PROMPT);
|
|
596
|
+
expect(serialized).not.toContain(NO_SIGNAL_PROMPT);
|
|
597
|
+
expect(serialized).not.toContain("answers");
|
|
598
|
+
expect(serialized).not.toContain("faux");
|
|
599
|
+
for (const event of h.telemetry) {
|
|
600
|
+
for (const key of Object.keys(event)) expect(TELEMETRY_KEYS.has(key)).toBe(true);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it("is invisible without a Jev provider template or subscription", async () => {
|
|
605
|
+
allowNetwork();
|
|
606
|
+
const { fetchMock } = stubJev("grep_app_search");
|
|
607
|
+
const h = await createGatewaySession({
|
|
608
|
+
enabled: true,
|
|
609
|
+
extensions: [GATEWAY_DIR, GREP_APP_DIR],
|
|
610
|
+
jev: jevSettings({ provider: "no-such-provider", baseUrl: undefined }),
|
|
611
|
+
});
|
|
612
|
+
harnesses.push(h);
|
|
613
|
+
|
|
614
|
+
await route(h, HINT_PROMPT);
|
|
615
|
+
|
|
616
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
617
|
+
expect(h.session.getActiveToolNames()).not.toContain("grep_app_search");
|
|
618
|
+
expect(routeEvents(h)).toContainEqual(
|
|
619
|
+
expect.objectContaining({ source: "jev", outcome: "unavailable", reason: "no-template" }),
|
|
620
|
+
);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it("records whether an activated tool was actually used, then resets", async () => {
|
|
624
|
+
allowNetwork();
|
|
625
|
+
stubJev("grep_app_search", 0.9);
|
|
626
|
+
const h = await createGatewaySession({
|
|
627
|
+
enabled: true,
|
|
628
|
+
extensions: [GATEWAY_DIR, GREP_APP_DIR],
|
|
629
|
+
jev: jevSettings(),
|
|
630
|
+
});
|
|
631
|
+
harnesses.push(h);
|
|
632
|
+
|
|
633
|
+
await route(h, HINT_PROMPT);
|
|
634
|
+
expect(h.session.getActiveToolNames()).toContain("grep_app_search");
|
|
635
|
+
|
|
636
|
+
// An unrelated tool produces no use telemetry.
|
|
637
|
+
await h.session.extensionRunner.emit({
|
|
638
|
+
type: "tool_execution_start",
|
|
639
|
+
toolCallId: "t0",
|
|
640
|
+
toolName: "read",
|
|
641
|
+
args: {},
|
|
642
|
+
});
|
|
643
|
+
expect(h.telemetry.filter((event) => event.event === "use")).toEqual([]);
|
|
644
|
+
|
|
645
|
+
// The selected tool is called (twice): one use event per activation.
|
|
646
|
+
for (const toolCallId of ["t1", "t2"]) {
|
|
647
|
+
await h.session.extensionRunner.emit({
|
|
648
|
+
type: "tool_execution_start",
|
|
649
|
+
toolCallId,
|
|
650
|
+
toolName: "grep_app_search",
|
|
651
|
+
args: {},
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
const uses = h.telemetry.filter((event) => event.event === "use");
|
|
655
|
+
expect(uses).toEqual([{ event: "use", tool: "grep_app_search", source: "jev" }]);
|
|
656
|
+
|
|
657
|
+
await h.session.extensionRunner.emit({ type: "agent_settled" });
|
|
658
|
+
expect(h.telemetry).toContainEqual(
|
|
659
|
+
expect.objectContaining({ event: "reset", activated: 1, used: 1 }),
|
|
660
|
+
);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
it("completes the run when telemetry cannot be emitted", async () => {
|
|
664
|
+
allowNetwork();
|
|
665
|
+
stubJev("grep_app_search", 0.95);
|
|
666
|
+
const h = await createGatewaySession({
|
|
667
|
+
enabled: true,
|
|
668
|
+
extensions: [GATEWAY_DIR, GREP_APP_DIR],
|
|
669
|
+
jev: jevSettings(),
|
|
670
|
+
telemetryDown: true,
|
|
671
|
+
});
|
|
672
|
+
harnesses.push(h);
|
|
673
|
+
|
|
674
|
+
await route(h, HINT_PROMPT);
|
|
675
|
+
|
|
676
|
+
expect(h.telemetry).toEqual([]);
|
|
677
|
+
expect(h.session.getActiveToolNames()).toContain("grep_app_search");
|
|
292
678
|
});
|
|
293
679
|
});
|