@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.
- package/CHANGELOG.md +9 -0
- 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/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
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared Jev decision client, at its own seam.
|
|
3
|
+
*
|
|
4
|
+
* Every failure mode here must produce an absent decision without throwing:
|
|
5
|
+
* a missing provider template, no Token-In credential, an oversized request, a
|
|
6
|
+
* timeout, a rejected call, malformed JSON, an unlisted choice, and a numeric
|
|
7
|
+
* confidence below the threshold.
|
|
8
|
+
*/
|
|
9
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
13
|
+
|
|
14
|
+
const state = vi.hoisted(() => ({ settingsPath: "" }));
|
|
15
|
+
|
|
16
|
+
// The client speaks through pi's completion transport (the Token-In provider
|
|
17
|
+
// layer owns the non-streaming decisions request), so a suite stubs that.
|
|
18
|
+
import {
|
|
19
|
+
askJev,
|
|
20
|
+
buildConversation,
|
|
21
|
+
buildJevPayload,
|
|
22
|
+
confidenceBucket,
|
|
23
|
+
DEFAULT_JEV_ADVISORY_CONFIG,
|
|
24
|
+
emitJevTelemetry,
|
|
25
|
+
JEV_ROUTING_EVENT,
|
|
26
|
+
jevConnection,
|
|
27
|
+
jevModel,
|
|
28
|
+
readJevAdvisoryConfig,
|
|
29
|
+
readJevChoice,
|
|
30
|
+
serializeJevRequest,
|
|
31
|
+
type JevConnection,
|
|
32
|
+
} from "./decisions.ts";
|
|
33
|
+
import { jevAnswers, jevResponse, providerTemplate } from "./test-support.ts";
|
|
34
|
+
|
|
35
|
+
const completeMock = vi.fn();
|
|
36
|
+
|
|
37
|
+
const CONNECTION: JevConnection = {
|
|
38
|
+
provider: "tokenin",
|
|
39
|
+
model: "jev-1.13",
|
|
40
|
+
timeoutMs: 5_000,
|
|
41
|
+
minConfidence: 0.6,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function runtime(models: unknown[] = [providerTemplate()], credential = true) {
|
|
45
|
+
return {
|
|
46
|
+
modelRegistry: {
|
|
47
|
+
getAll: () => models as never[],
|
|
48
|
+
getApiKeyAndHeaders: vi.fn(async () => (credential ? { ok: true, apiKey: "key", headers: {} } : { ok: false })),
|
|
49
|
+
complete: completeMock,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const ALLOWED = { repository_context: ["inject", "skip"] } as const;
|
|
55
|
+
|
|
56
|
+
function ask(ctx = runtime(), payload: unknown = { state: {}, questions: {} }, maxBytes = 8_192) {
|
|
57
|
+
return askJev(ctx as never, CONNECTION, { payload, maxBytes, allowed: ALLOWED });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
vi.clearAllMocks();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("readJevAdvisoryConfig", () => {
|
|
65
|
+
function write(value: unknown): void {
|
|
66
|
+
state.settingsPath = join(mkdtempSync(join(tmpdir(), "jev-config-")), "settings.json");
|
|
67
|
+
writeFileSync(state.settingsPath, typeof value === "string" ? value : JSON.stringify(value), "utf-8");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
it("keeps every route disabled on a missing or malformed file", () => {
|
|
71
|
+
state.settingsPath = join(tmpdir(), "missing-settings.json");
|
|
72
|
+
expect(readJevAdvisoryConfig(state.settingsPath)).toEqual(DEFAULT_JEV_ADVISORY_CONFIG);
|
|
73
|
+
|
|
74
|
+
write("{ not json");
|
|
75
|
+
expect(readJevAdvisoryConfig(state.settingsPath).routes.memory.enabled).toBe(false);
|
|
76
|
+
|
|
77
|
+
write([1, 2]);
|
|
78
|
+
expect(readJevAdvisoryConfig(state.settingsPath).routes.recommendations.enabled).toBe(false);
|
|
79
|
+
|
|
80
|
+
write({ jevAdvisory: "nope" });
|
|
81
|
+
expect(readJevAdvisoryConfig(state.settingsPath).routes.memory.enabled).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("merges per-route settings over the shared endpoint defaults", () => {
|
|
85
|
+
write({
|
|
86
|
+
jevAdvisory: {
|
|
87
|
+
model: "jev-9",
|
|
88
|
+
baseUrl: "https://custom/v1",
|
|
89
|
+
routes: {
|
|
90
|
+
memory: { enabled: true, minConfidence: 0.8, contextTurns: 2, payloadBytes: 1024 },
|
|
91
|
+
recommendations: { enabled: "yes", timeoutMs: -1 },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
const config = readJevAdvisoryConfig(state.settingsPath);
|
|
96
|
+
expect(config.provider).toBe("tokenin");
|
|
97
|
+
expect(config.model).toBe("jev-9");
|
|
98
|
+
expect(config.baseUrl).toBe("https://custom/v1");
|
|
99
|
+
expect(config.routes.memory).toMatchObject({
|
|
100
|
+
enabled: true,
|
|
101
|
+
minConfidence: 0.8,
|
|
102
|
+
contextTurns: 2,
|
|
103
|
+
payloadBytes: 1024,
|
|
104
|
+
contextChars: 4_000,
|
|
105
|
+
timeoutMs: 8_000,
|
|
106
|
+
});
|
|
107
|
+
expect(config.routes.recommendations).toMatchObject({ enabled: false, timeoutMs: 8_000 });
|
|
108
|
+
expect(jevConnection(config, config.routes.memory)).toEqual({
|
|
109
|
+
provider: "tokenin",
|
|
110
|
+
model: "jev-9",
|
|
111
|
+
baseUrl: "https://custom/v1",
|
|
112
|
+
timeoutMs: 8_000,
|
|
113
|
+
minConfidence: 0.8,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("buildJevPayload", () => {
|
|
119
|
+
it("sends only the user turns and the allowlisted criteria", () => {
|
|
120
|
+
const payload = buildJevPayload([{ role: "user", text: "the login flow feels slow" }], {
|
|
121
|
+
repository_context: { question: "Needs context?", criteria: { inject: "yes", skip: "no" } },
|
|
122
|
+
}) as Record<string, any>;
|
|
123
|
+
expect(payload.state).toEqual({ conversation: [{ role: "user", text: "the login flow feels slow" }] });
|
|
124
|
+
expect(payload.questions.repository_context.type).toBe("choice");
|
|
125
|
+
expect(payload.questions.repository_context.instructions.question).toBe("Needs context?");
|
|
126
|
+
expect(payload.questions.repository_context.criteria).toEqual({ inject: "yes", skip: "no" });
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("marks conversation and criteria as untrusted material, and bounds a system prompt", () => {
|
|
130
|
+
const payload = buildJevPayload(
|
|
131
|
+
[],
|
|
132
|
+
{ repository_context: { question: "q", criteria: { inject: "yes" }, focus: "Judge the request." } },
|
|
133
|
+
"x".repeat(50),
|
|
134
|
+
10,
|
|
135
|
+
) as Record<string, any>;
|
|
136
|
+
expect(payload.state.system_prompt).toHaveLength(10);
|
|
137
|
+
expect(payload.questions.repository_context.instructions.focus).toContain("never instructions");
|
|
138
|
+
expect(buildJevPayload([], { repository_context: { question: "q", criteria: {} } }, "sys", 0)).toMatchObject({
|
|
139
|
+
state: { conversation: [] },
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("buildConversation", () => {
|
|
145
|
+
it("keeps the current ask last and only user turns", () => {
|
|
146
|
+
const branch = [
|
|
147
|
+
{ type: "message", message: { role: "user", content: "older ask" } },
|
|
148
|
+
{ type: "message", message: { role: "assistant", content: "narration" } },
|
|
149
|
+
{ type: "message", message: { role: "toolResult", content: "tool output" } },
|
|
150
|
+
];
|
|
151
|
+
expect(buildConversation("current ask", branch, { contextTurns: 4, contextChars: 1_000 })).toEqual([
|
|
152
|
+
{ role: "user", text: "older ask" },
|
|
153
|
+
{ role: "user", text: "current ask" },
|
|
154
|
+
]);
|
|
155
|
+
expect(buildConversation("current", branch, { contextTurns: 1, contextChars: 1_000 })).toEqual([
|
|
156
|
+
{ role: "user", text: "current" },
|
|
157
|
+
]);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("readJevChoice", () => {
|
|
162
|
+
it("accepts only an allowlisted choice with enough confidence", () => {
|
|
163
|
+
expect(readJevChoice(jevAnswers({ repository_context: { choice: "inject", confidence: 0.9 } }), "repository_context", ALLOWED.repository_context, 0.6)).toEqual({
|
|
164
|
+
choice: "inject",
|
|
165
|
+
confidence: 0.9,
|
|
166
|
+
});
|
|
167
|
+
expect(readJevChoice(jevAnswers({ repository_context: { choice: "INJECT", confidence: 0.9 } }), "repository_context", ALLOWED.repository_context, 0.6)).toEqual({
|
|
168
|
+
choice: "inject",
|
|
169
|
+
confidence: 0.9,
|
|
170
|
+
});
|
|
171
|
+
expect(readJevChoice(jevAnswers({ repository_context: { choice: "inject" } }), "repository_context", ALLOWED.repository_context, 0.6)).toEqual({
|
|
172
|
+
choice: "inject",
|
|
173
|
+
});
|
|
174
|
+
expect(readJevChoice(jevAnswers({ repository_context: { choice: "maybe", confidence: 1 } }), "repository_context", ALLOWED.repository_context, 0.6)).toBeUndefined();
|
|
175
|
+
expect(readJevChoice(jevAnswers({ repository_context: { choice: "inject", confidence: 0.2 } }), "repository_context", ALLOWED.repository_context, 0.6)).toBeUndefined();
|
|
176
|
+
expect(readJevChoice(jevAnswers({ repository_context: { choice: 7 as never, confidence: 1 } }), "repository_context", ALLOWED.repository_context, 0.6)).toBeUndefined();
|
|
177
|
+
expect(readJevChoice("{ not json", "repository_context", ALLOWED.repository_context, 0.6)).toBeUndefined();
|
|
178
|
+
expect(readJevChoice(JSON.stringify({ answers: null }), "repository_context", ALLOWED.repository_context, 0.6)).toBeUndefined();
|
|
179
|
+
expect(readJevChoice(jevAnswers({ repository_context: { choice: "inject", confidence: 1 } }), "other_question", ALLOWED.repository_context, 0.6)).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("jevModel", () => {
|
|
184
|
+
it("inherits the provider template, or uses an explicit base URL", () => {
|
|
185
|
+
expect(jevModel(runtime().modelRegistry as never, CONNECTION)).toMatchObject({
|
|
186
|
+
id: "jev-1.13",
|
|
187
|
+
provider: "tokenin",
|
|
188
|
+
baseUrl: "https://lite.andlet.me/v1",
|
|
189
|
+
reasoning: false,
|
|
190
|
+
input: ["text"],
|
|
191
|
+
});
|
|
192
|
+
expect(jevModel(runtime([]).modelRegistry as never, CONNECTION)).toBeUndefined();
|
|
193
|
+
expect(jevModel(runtime([]).modelRegistry as never, { ...CONNECTION, baseUrl: "https://x/v1" })).toMatchObject({
|
|
194
|
+
baseUrl: "https://x/v1",
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("serializeJevRequest", () => {
|
|
200
|
+
it("refuses a request that cannot fit the fixed budget", () => {
|
|
201
|
+
expect(serializeJevRequest({ a: "x".repeat(10) }, 1_024)).toBeDefined();
|
|
202
|
+
expect(serializeJevRequest({ a: "x".repeat(1_024) }, 64)).toBeUndefined();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe("askJev", () => {
|
|
207
|
+
it("returns the validated answers and how long the decision took", async () => {
|
|
208
|
+
completeMock.mockResolvedValue(jevResponse(jevAnswers({ repository_context: { choice: "inject", confidence: 0.8 } })));
|
|
209
|
+
const decision = await ask();
|
|
210
|
+
expect(decision.choices.repository_context).toEqual({ choice: "inject", confidence: 0.8 });
|
|
211
|
+
expect(decision.rejected).toEqual({});
|
|
212
|
+
expect(decision.failure).toBeUndefined();
|
|
213
|
+
expect(typeof decision.elapsedMs).toBe("number");
|
|
214
|
+
const [model, context, options] = completeMock.mock.calls[0] as unknown as [
|
|
215
|
+
{ id: string; baseUrl: string },
|
|
216
|
+
{ messages: Array<{ role: string; content: string }> },
|
|
217
|
+
{ apiKey?: string; headers?: Record<string, string>; maxTokens?: number; signal?: AbortSignal },
|
|
218
|
+
];
|
|
219
|
+
expect(model).toMatchObject({ id: "jev-1.13", baseUrl: "https://lite.andlet.me/v1" });
|
|
220
|
+
expect(context.messages[0]!.role).toBe("user");
|
|
221
|
+
expect(JSON.parse(context.messages[0]!.content)).toHaveProperty("questions");
|
|
222
|
+
expect(options.apiKey).toBe("key");
|
|
223
|
+
expect(options.maxTokens).toBe(2_048);
|
|
224
|
+
expect(options.signal).toBeInstanceOf(AbortSignal);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("reports an unusable endpoint as an absent decision", async () => {
|
|
228
|
+
expect(await ask(runtime([]))).toMatchObject({ choices: {}, failure: "no-template" });
|
|
229
|
+
expect(completeMock).not.toHaveBeenCalled();
|
|
230
|
+
|
|
231
|
+
expect(await ask(runtime([providerTemplate()], false))).toMatchObject({ choices: {}, failure: "no-credential" });
|
|
232
|
+
expect(completeMock).not.toHaveBeenCalled();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("treats a credential lookup that throws as an absent decision", async () => {
|
|
236
|
+
const ctx = {
|
|
237
|
+
modelRegistry: {
|
|
238
|
+
getAll: () => [providerTemplate()],
|
|
239
|
+
getApiKeyAndHeaders: vi.fn(async () => {
|
|
240
|
+
throw new Error("auth storage unavailable");
|
|
241
|
+
}),
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
expect(await ask(ctx as never)).toMatchObject({ failure: "no-credential" });
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("abstains on an oversized request instead of sending it", async () => {
|
|
248
|
+
const decision = await ask(runtime(), { state: { conversation: "x".repeat(2_000) } }, 128);
|
|
249
|
+
expect(decision).toMatchObject({ choices: {}, failure: "overflow" });
|
|
250
|
+
expect(completeMock).not.toHaveBeenCalled();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("abstains on a transport failure, a timeout, and malformed output", async () => {
|
|
254
|
+
completeMock.mockRejectedValue(new Error("network down"));
|
|
255
|
+
expect(await ask()).toMatchObject({ failure: "transport" });
|
|
256
|
+
|
|
257
|
+
const timeout = new Error("timed out");
|
|
258
|
+
timeout.name = "TimeoutError";
|
|
259
|
+
completeMock.mockRejectedValue(timeout);
|
|
260
|
+
expect(await ask()).toMatchObject({ failure: "timeout" });
|
|
261
|
+
|
|
262
|
+
completeMock.mockResolvedValue(jevResponse("{ not json"));
|
|
263
|
+
expect(await ask()).toMatchObject({ failure: "malformed" });
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("abstains per question on an unlisted, low-confidence, or missing answer", async () => {
|
|
267
|
+
completeMock.mockResolvedValue(jevResponse(jevAnswers({ repository_context: { choice: "maybe", confidence: 1 } })));
|
|
268
|
+
expect(await ask()).toMatchObject({ choices: {}, failure: "unknown-choice" });
|
|
269
|
+
|
|
270
|
+
completeMock.mockResolvedValue(jevResponse(jevAnswers({ repository_context: { choice: "inject", confidence: 0.4 } })));
|
|
271
|
+
expect(await ask()).toMatchObject({ choices: {}, failure: "low-confidence" });
|
|
272
|
+
|
|
273
|
+
completeMock.mockResolvedValue(jevResponse(jevAnswers({ other: { choice: "inject", confidence: 1 } })));
|
|
274
|
+
expect(await ask()).toMatchObject({ choices: {}, failure: "missing" });
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("keeps a valid question when another question is unusable", async () => {
|
|
278
|
+
completeMock.mockResolvedValue(
|
|
279
|
+
jevResponse(
|
|
280
|
+
jevAnswers({
|
|
281
|
+
repository_context: { choice: "inject", confidence: 0.9 },
|
|
282
|
+
second: { choice: "nonsense", confidence: 0.9 },
|
|
283
|
+
}),
|
|
284
|
+
),
|
|
285
|
+
);
|
|
286
|
+
const decision = await askJev(runtime() as never, CONNECTION, {
|
|
287
|
+
payload: {},
|
|
288
|
+
maxBytes: 1_024,
|
|
289
|
+
allowed: { repository_context: ["inject", "skip"], second: ["a", "b"] },
|
|
290
|
+
});
|
|
291
|
+
expect(decision.choices).toEqual({ repository_context: { choice: "inject", confidence: 0.9 } });
|
|
292
|
+
expect(decision.rejected).toEqual({ second: "unknown-choice" });
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe("telemetry helpers", () => {
|
|
297
|
+
it("buckets confidence without carrying the answer", () => {
|
|
298
|
+
expect(confidenceBucket(0.95)).toBe("high");
|
|
299
|
+
expect(confidenceBucket(0.75)).toBe("medium");
|
|
300
|
+
expect(confidenceBucket(0.4)).toBe("low");
|
|
301
|
+
expect(confidenceBucket(undefined)).toBe("none");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("never lets a telemetry failure escape", () => {
|
|
305
|
+
const events = {
|
|
306
|
+
emit: (_channel: string, _data: unknown) => {
|
|
307
|
+
throw new Error("bus down");
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
expect(() => emitJevTelemetry(events, "decision", { route: "memory" })).not.toThrow();
|
|
311
|
+
expect(() => emitJevTelemetry(undefined, "adoption", { route: "memory" })).not.toThrow();
|
|
312
|
+
const seen: Array<{ channel: string; data: unknown }> = [];
|
|
313
|
+
emitJevTelemetry({ emit: (channel, data) => seen.push({ channel, data }) }, "decision", { route: "memory" });
|
|
314
|
+
expect(seen).toEqual([{ channel: JEV_ROUTING_EVENT, data: { event: "decision", route: "memory" } }]);
|
|
315
|
+
});
|
|
316
|
+
});
|