@winspan/claude-forge 8.13.1 → 8.16.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/dist/agents/definition.d.ts +53 -0
- package/dist/agents/definition.d.ts.map +1 -0
- package/dist/agents/definition.js +24 -0
- package/dist/agents/definition.js.map +1 -0
- package/dist/agents/distributor.d.ts +23 -0
- package/dist/agents/distributor.d.ts.map +1 -0
- package/dist/agents/distributor.js +85 -0
- package/dist/agents/distributor.js.map +1 -0
- package/dist/agents/index.d.ts +5 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +5 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/official-agents.d.ts +14 -0
- package/dist/agents/official-agents.d.ts.map +1 -0
- package/dist/agents/official-agents.js +510 -0
- package/dist/agents/official-agents.js.map +1 -0
- package/dist/agents/registry.d.ts +27 -0
- package/dist/agents/registry.d.ts.map +1 -0
- package/dist/agents/registry.js +105 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +17 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/menu.js +183 -0
- package/dist/cli/commands/menu.js.map +1 -1
- package/dist/core/constants.d.ts +1 -1
- package/dist/core/constants.js +1 -1
- package/dist/core/constants.js.map +1 -1
- package/dist/core/storage/schema.sql +60 -0
- package/dist/core/storage/sqlite.d.ts +73 -0
- package/dist/core/storage/sqlite.d.ts.map +1 -1
- package/dist/core/storage/sqlite.js +159 -0
- package/dist/core/storage/sqlite.js.map +1 -1
- package/dist/daemon/auto-disable-scheduler.d.ts +53 -0
- package/dist/daemon/auto-disable-scheduler.d.ts.map +1 -0
- package/dist/daemon/auto-disable-scheduler.js +114 -0
- package/dist/daemon/auto-disable-scheduler.js.map +1 -0
- package/dist/daemon/handlers/post-tool-use.d.ts +3 -1
- package/dist/daemon/handlers/post-tool-use.d.ts.map +1 -1
- package/dist/daemon/handlers/post-tool-use.js +14 -2
- package/dist/daemon/handlers/post-tool-use.js.map +1 -1
- package/dist/daemon/handlers/stop.d.ts +3 -1
- package/dist/daemon/handlers/stop.d.ts.map +1 -1
- package/dist/daemon/handlers/stop.js +14 -1
- package/dist/daemon/handlers/stop.js.map +1 -1
- package/dist/daemon/handlers/user-prompt.d.ts +18 -7
- package/dist/daemon/handlers/user-prompt.d.ts.map +1 -1
- package/dist/daemon/handlers/user-prompt.js +97 -23
- package/dist/daemon/handlers/user-prompt.js.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +53 -18
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/routing-observer.d.ts +39 -0
- package/dist/daemon/routing-observer.d.ts.map +1 -0
- package/dist/daemon/routing-observer.js +156 -0
- package/dist/daemon/routing-observer.js.map +1 -0
- package/dist/engine/agent-router.d.ts +99 -0
- package/dist/engine/agent-router.d.ts.map +1 -0
- package/dist/engine/agent-router.js +206 -0
- package/dist/engine/agent-router.js.map +1 -0
- package/dist/engine/conventions/routing.yaml +84 -0
- package/dist/engine/dsl/parser.d.ts +6 -0
- package/dist/engine/dsl/parser.d.ts.map +1 -1
- package/dist/engine/dsl/parser.js +19 -0
- package/dist/engine/dsl/parser.js.map +1 -1
- package/dist/engine/evidence-store.d.ts.map +1 -1
- package/dist/engine/evidence-store.js +3 -0
- package/dist/engine/evidence-store.js.map +1 -1
- package/dist/engine/experiment-router.d.ts +102 -0
- package/dist/engine/experiment-router.d.ts.map +1 -0
- package/dist/engine/experiment-router.js +289 -0
- package/dist/engine/experiment-router.js.map +1 -0
- package/dist/engine/recommender.d.ts +52 -0
- package/dist/engine/recommender.d.ts.map +1 -0
- package/dist/engine/recommender.js +150 -0
- package/dist/engine/recommender.js.map +1 -0
- package/dist/intelligence/classifier.d.ts +19 -5
- package/dist/intelligence/classifier.d.ts.map +1 -1
- package/dist/intelligence/classifier.js +98 -20
- package/dist/intelligence/classifier.js.map +1 -1
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +5 -2
- package/dist/skills/registry.js.map +1 -1
- package/dist/web/server.d.ts +4 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +551 -0
- package/dist/web/server.js.map +1 -1
- package/dist/web/static/index.html +940 -77
- package/package.json +1 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExperimentRouter — wraps AgentRouter with A/B-testing capability.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Load ~/.claude-forge/routing-experiments.yaml
|
|
6
|
+
* - Assign incoming requests to one of N experiment groups via a sticky,
|
|
7
|
+
* session-scoped consistent hash
|
|
8
|
+
* - Delegate the actual routing decision to a per-group AgentRouter built
|
|
9
|
+
* from the group's inline rules
|
|
10
|
+
* - Fall back to a provided defaultRouter when the experiment is disabled,
|
|
11
|
+
* concluded, or misconfigured
|
|
12
|
+
*
|
|
13
|
+
* Stickiness is persisted in the `experiment_assignments` table so group
|
|
14
|
+
* assignment stays stable across hot reloads and daemon restarts. The hash
|
|
15
|
+
* input mixes experiment.id so switching to a new experiment reshuffles.
|
|
16
|
+
*/
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
import yaml from 'js-yaml';
|
|
21
|
+
import { logger } from '../core/utils/logger.js';
|
|
22
|
+
import { AgentRouter } from './agent-router.js';
|
|
23
|
+
export class ExperimentRouter {
|
|
24
|
+
storage;
|
|
25
|
+
defaultRouter;
|
|
26
|
+
experimentsPath;
|
|
27
|
+
config = { enabled: false, experiment: null };
|
|
28
|
+
groupRouters = new Map();
|
|
29
|
+
watcher = null;
|
|
30
|
+
isRuleDisabled = null;
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
this.storage = opts.storage;
|
|
33
|
+
this.defaultRouter = opts.defaultRouter;
|
|
34
|
+
this.experimentsPath = opts.experimentsPath ?? path.join(homedir(), '.claude-forge', 'routing-experiments.yaml');
|
|
35
|
+
this.isRuleDisabled = opts.isRuleDisabled ?? null;
|
|
36
|
+
this.load();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Make a routing decision. If the experiment is active, assigns the session
|
|
40
|
+
* to a group (sticky) and uses that group's rules. Otherwise delegates to
|
|
41
|
+
* the default router.
|
|
42
|
+
*/
|
|
43
|
+
decide(input, sessionId) {
|
|
44
|
+
if (!this.isExperimentActive()) {
|
|
45
|
+
const decision = this.defaultRouter.decide(input);
|
|
46
|
+
return { ...decision, experimentId: null, groupId: null };
|
|
47
|
+
}
|
|
48
|
+
const exp = this.config.experiment;
|
|
49
|
+
const groupId = this.assignGroup(sessionId, exp);
|
|
50
|
+
const router = this.groupRouters.get(groupId) ?? this.defaultRouter;
|
|
51
|
+
const decision = router.decide(input);
|
|
52
|
+
return { ...decision, experimentId: exp.id, groupId };
|
|
53
|
+
}
|
|
54
|
+
getConfigSnapshot() {
|
|
55
|
+
return {
|
|
56
|
+
enabled: this.config.enabled,
|
|
57
|
+
experiment: this.config.experiment
|
|
58
|
+
? {
|
|
59
|
+
...this.config.experiment,
|
|
60
|
+
groups: this.config.experiment.groups.map(g => ({ ...g, rules: g.rules.slice() })),
|
|
61
|
+
}
|
|
62
|
+
: null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
enableHotReload() {
|
|
66
|
+
if (this.watcher)
|
|
67
|
+
return;
|
|
68
|
+
const dir = path.dirname(this.experimentsPath);
|
|
69
|
+
if (!fs.existsSync(dir)) {
|
|
70
|
+
logger.debug(`[ExperimentRouter] Hot-reload skipped: ${dir} does not exist`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
this.watcher = fs.watch(dir, (_eventType, filename) => {
|
|
75
|
+
if (filename === path.basename(this.experimentsPath)) {
|
|
76
|
+
logger.info(`[ExperimentRouter] Detected change in ${this.experimentsPath}, reloading...`);
|
|
77
|
+
this.load();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
logger.info(`[ExperimentRouter] Hot-reload enabled for ${this.experimentsPath}`);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
logger.warn(`[ExperimentRouter] Failed to enable hot-reload: ${err}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
disableHotReload() {
|
|
87
|
+
if (this.watcher) {
|
|
88
|
+
this.watcher.close();
|
|
89
|
+
this.watcher = null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Rebuild config and per-group routers from disk. Idempotent. */
|
|
93
|
+
load() {
|
|
94
|
+
const parsed = this.tryReadConfig();
|
|
95
|
+
if (!parsed) {
|
|
96
|
+
this.config = { enabled: false, experiment: null };
|
|
97
|
+
this.groupRouters = new Map();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.config = parsed;
|
|
101
|
+
// Rebuild group routers atomically — construct a new Map, then swap.
|
|
102
|
+
const next = new Map();
|
|
103
|
+
if (parsed.experiment) {
|
|
104
|
+
for (const group of parsed.experiment.groups) {
|
|
105
|
+
const router = new AgentRouter({ rules: group.rules });
|
|
106
|
+
if (this.isRuleDisabled)
|
|
107
|
+
router.setIsRuleDisabled(this.isRuleDisabled);
|
|
108
|
+
next.set(group.id, router);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
this.groupRouters = next;
|
|
112
|
+
logger.info(parsed.enabled && parsed.experiment
|
|
113
|
+
? `[ExperimentRouter] Loaded experiment '${parsed.experiment.id}' with ${parsed.experiment.groups.length} group(s)`
|
|
114
|
+
: `[ExperimentRouter] Loaded config (experiment disabled or absent)`);
|
|
115
|
+
}
|
|
116
|
+
tryReadConfig() {
|
|
117
|
+
if (!fs.existsSync(this.experimentsPath))
|
|
118
|
+
return null;
|
|
119
|
+
let raw;
|
|
120
|
+
try {
|
|
121
|
+
raw = fs.readFileSync(this.experimentsPath, 'utf-8');
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
logger.warn(`[ExperimentRouter] Failed to read ${this.experimentsPath}: ${err}`);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
// CORE_SCHEMA disallows !!js/function and other unsafe js-yaml tags.
|
|
129
|
+
const parsed = yaml.load(raw, { schema: yaml.CORE_SCHEMA });
|
|
130
|
+
return validateConfig(parsed);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
logger.warn(`[ExperimentRouter] Failed to parse experiments YAML: ${err}`);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
isExperimentActive() {
|
|
138
|
+
return Boolean(this.config.enabled &&
|
|
139
|
+
this.config.experiment &&
|
|
140
|
+
this.config.experiment.endedAt == null &&
|
|
141
|
+
this.config.experiment.groups.length > 0 &&
|
|
142
|
+
this.groupRouters.size > 0);
|
|
143
|
+
}
|
|
144
|
+
assignGroup(sessionId, exp) {
|
|
145
|
+
const existing = this.storage.getExperimentAssignment(sessionId, exp.id);
|
|
146
|
+
if (existing && this.groupRouters.has(existing)) {
|
|
147
|
+
return existing;
|
|
148
|
+
}
|
|
149
|
+
const groupId = pickGroup(sessionId, exp);
|
|
150
|
+
try {
|
|
151
|
+
this.storage.setExperimentAssignment(sessionId, exp.id, groupId);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
logger.warn(`[ExperimentRouter] Failed to persist assignment for ${sessionId}: ${err}`);
|
|
155
|
+
}
|
|
156
|
+
return groupId;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Deterministic bucket selection using murmur3_32 of `sessionId:experimentId`.
|
|
161
|
+
* Mixing the experiment id ensures that starting a new experiment does not
|
|
162
|
+
* leave a given session permanently stuck on the first bucket.
|
|
163
|
+
*/
|
|
164
|
+
export function pickGroup(sessionId, exp) {
|
|
165
|
+
const bucket = murmur3_32(`${sessionId}:${exp.id}`) % 100;
|
|
166
|
+
let acc = 0;
|
|
167
|
+
for (const group of exp.groups) {
|
|
168
|
+
acc += group.weight;
|
|
169
|
+
if (bucket < acc)
|
|
170
|
+
return group.id;
|
|
171
|
+
}
|
|
172
|
+
// Floating-point/rounding safety net: return last group.
|
|
173
|
+
return exp.groups[exp.groups.length - 1].id;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* murmur3_32 — a small, self-contained hash. Public-domain reference impl.
|
|
177
|
+
* We avoid adding a dependency for ~30 lines of code.
|
|
178
|
+
*/
|
|
179
|
+
export function murmur3_32(str, seed = 0) {
|
|
180
|
+
let h1 = seed >>> 0;
|
|
181
|
+
const c1 = 0xcc9e2d51;
|
|
182
|
+
const c2 = 0x1b873593;
|
|
183
|
+
const data = new TextEncoder().encode(str);
|
|
184
|
+
const len = data.length;
|
|
185
|
+
const blocks = Math.floor(len / 4);
|
|
186
|
+
for (let i = 0; i < blocks; i++) {
|
|
187
|
+
let k1 = data[i * 4] |
|
|
188
|
+
(data[i * 4 + 1] << 8) |
|
|
189
|
+
(data[i * 4 + 2] << 16) |
|
|
190
|
+
(data[i * 4 + 3] << 24);
|
|
191
|
+
k1 = Math.imul(k1, c1) >>> 0;
|
|
192
|
+
k1 = ((k1 << 15) | (k1 >>> 17)) >>> 0;
|
|
193
|
+
k1 = Math.imul(k1, c2) >>> 0;
|
|
194
|
+
h1 = (h1 ^ k1) >>> 0;
|
|
195
|
+
h1 = ((h1 << 13) | (h1 >>> 19)) >>> 0;
|
|
196
|
+
h1 = (Math.imul(h1, 5) + 0xe6546b64) >>> 0;
|
|
197
|
+
}
|
|
198
|
+
let k1 = 0;
|
|
199
|
+
const tail = len & 3;
|
|
200
|
+
const tailStart = blocks * 4;
|
|
201
|
+
if (tail === 3)
|
|
202
|
+
k1 ^= data[tailStart + 2] << 16;
|
|
203
|
+
if (tail >= 2)
|
|
204
|
+
k1 ^= data[tailStart + 1] << 8;
|
|
205
|
+
if (tail >= 1) {
|
|
206
|
+
k1 ^= data[tailStart];
|
|
207
|
+
k1 = Math.imul(k1, c1) >>> 0;
|
|
208
|
+
k1 = ((k1 << 15) | (k1 >>> 17)) >>> 0;
|
|
209
|
+
k1 = Math.imul(k1, c2) >>> 0;
|
|
210
|
+
h1 = (h1 ^ k1) >>> 0;
|
|
211
|
+
}
|
|
212
|
+
h1 = (h1 ^ len) >>> 0;
|
|
213
|
+
h1 ^= h1 >>> 16;
|
|
214
|
+
h1 = Math.imul(h1, 0x85ebca6b) >>> 0;
|
|
215
|
+
h1 ^= h1 >>> 13;
|
|
216
|
+
h1 = Math.imul(h1, 0xc2b2ae35) >>> 0;
|
|
217
|
+
h1 ^= h1 >>> 16;
|
|
218
|
+
return h1 >>> 0;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Validate raw YAML shape. Returns null on any structural error; logs details.
|
|
222
|
+
* Exported so the Web API can reject invalid PUTs with the same rules.
|
|
223
|
+
*/
|
|
224
|
+
export function validateConfig(raw) {
|
|
225
|
+
if (!raw || typeof raw !== 'object')
|
|
226
|
+
return null;
|
|
227
|
+
const enabled = raw.enabled === true;
|
|
228
|
+
const exp = raw.experiment;
|
|
229
|
+
if (!exp || typeof exp !== 'object') {
|
|
230
|
+
return { enabled: false, experiment: null };
|
|
231
|
+
}
|
|
232
|
+
if (typeof exp.id !== 'string' || exp.id.length === 0) {
|
|
233
|
+
logger.warn('[ExperimentRouter] experiment.id missing or empty');
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
if (!Array.isArray(exp.groups) || exp.groups.length === 0) {
|
|
237
|
+
logger.warn('[ExperimentRouter] experiment.groups must be a non-empty array');
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const groups = [];
|
|
241
|
+
const ids = new Set();
|
|
242
|
+
let totalWeight = 0;
|
|
243
|
+
for (const g of exp.groups) {
|
|
244
|
+
if (!g || typeof g !== 'object') {
|
|
245
|
+
logger.warn('[ExperimentRouter] invalid group entry');
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
if (typeof g.id !== 'string' || g.id.length === 0) {
|
|
249
|
+
logger.warn('[ExperimentRouter] group.id missing');
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
if (ids.has(g.id)) {
|
|
253
|
+
logger.warn(`[ExperimentRouter] duplicate group id '${g.id}'`);
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
ids.add(g.id);
|
|
257
|
+
if (typeof g.weight !== 'number' || !Number.isFinite(g.weight) || g.weight < 0) {
|
|
258
|
+
logger.warn(`[ExperimentRouter] group '${g.id}' has invalid weight`);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
if (!Array.isArray(g.rules) || g.rules.length === 0) {
|
|
262
|
+
logger.warn(`[ExperimentRouter] group '${g.id}' has no rules`);
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
totalWeight += g.weight;
|
|
266
|
+
groups.push({
|
|
267
|
+
id: g.id,
|
|
268
|
+
name: typeof g.name === 'string' ? g.name : g.id,
|
|
269
|
+
weight: g.weight,
|
|
270
|
+
rules: g.rules,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
if (totalWeight !== 100) {
|
|
274
|
+
logger.warn(`[ExperimentRouter] group weights sum to ${totalWeight}, expected 100`);
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
const endedAt = typeof exp.endedAt === 'string' && exp.endedAt.length > 0 ? exp.endedAt : null;
|
|
278
|
+
return {
|
|
279
|
+
enabled,
|
|
280
|
+
experiment: {
|
|
281
|
+
id: exp.id,
|
|
282
|
+
name: typeof exp.name === 'string' ? exp.name : exp.id,
|
|
283
|
+
startedAt: typeof exp.startedAt === 'string' ? exp.startedAt : new Date().toISOString(),
|
|
284
|
+
endedAt,
|
|
285
|
+
groups,
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=experiment-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"experiment-router.js","sourceRoot":"","sources":["../../src/engine/experiment-router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recommender — analyzes routing_events history to suggest rule changes.
|
|
3
|
+
*
|
|
4
|
+
* Heuristic (MVP):
|
|
5
|
+
* 1. For each (taskType, complexity?) slot seen in the last N days,
|
|
6
|
+
* aggregate how many times Claude actually invoked each agent
|
|
7
|
+
* (either as the first Task subagent_type, or anywhere in the
|
|
8
|
+
* downstream chain).
|
|
9
|
+
* 2. Compare against the current AgentRouter decision for that slot.
|
|
10
|
+
* 3. Emit a recommendation when:
|
|
11
|
+
* - A different agent was actually used > 50% of the time
|
|
12
|
+
* - Sample size >= 10
|
|
13
|
+
* - The recommended agent exists in AgentRegistry
|
|
14
|
+
* 4. Sort by confidence (sample size * rate delta).
|
|
15
|
+
*
|
|
16
|
+
* This is intentionally conservative — we do not attempt to learn rule
|
|
17
|
+
* ordering or compose new when-clauses. Recommendations are suggestions
|
|
18
|
+
* for the user to apply manually (or via a one-click button later).
|
|
19
|
+
*/
|
|
20
|
+
import type { SQLiteStorage } from '../core/storage/sqlite.js';
|
|
21
|
+
import type { AgentRouter, TaskType, Complexity } from './agent-router.js';
|
|
22
|
+
import type { AgentRegistry } from '../agents/registry.js';
|
|
23
|
+
export interface Recommendation {
|
|
24
|
+
taskType: TaskType;
|
|
25
|
+
complexity: Complexity | null;
|
|
26
|
+
currentAgent: string | null;
|
|
27
|
+
recommendedAgent: string;
|
|
28
|
+
sampleSize: number;
|
|
29
|
+
currentObeyedRate: number;
|
|
30
|
+
recommendedUsageRate: number;
|
|
31
|
+
confidence: number;
|
|
32
|
+
reason: string;
|
|
33
|
+
}
|
|
34
|
+
export interface RecommenderOptions {
|
|
35
|
+
/** Analysis window (ms). Default 7d. */
|
|
36
|
+
windowMs?: number;
|
|
37
|
+
/** Minimum attempts per slot before a recommendation is considered. */
|
|
38
|
+
minSampleSize?: number;
|
|
39
|
+
/** The actual-use rate threshold for a challenger agent. */
|
|
40
|
+
minChallengerRate?: number;
|
|
41
|
+
}
|
|
42
|
+
export declare class Recommender {
|
|
43
|
+
private storage;
|
|
44
|
+
private router;
|
|
45
|
+
private agents;
|
|
46
|
+
private windowMs;
|
|
47
|
+
private minSampleSize;
|
|
48
|
+
private minChallengerRate;
|
|
49
|
+
constructor(storage: SQLiteStorage, router: AgentRouter, agents: AgentRegistry, opts?: RecommenderOptions);
|
|
50
|
+
analyze(): Recommendation[];
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=recommender.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommender.d.ts","sourceRoot":"","sources":["../../src/engine/recommender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG3D,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAqBD,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,iBAAiB,CAAS;gBAGhC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,aAAa,EACrB,IAAI,GAAE,kBAAuB;IAU/B,OAAO,IAAI,cAAc,EAAE;CA+F5B"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recommender — analyzes routing_events history to suggest rule changes.
|
|
3
|
+
*
|
|
4
|
+
* Heuristic (MVP):
|
|
5
|
+
* 1. For each (taskType, complexity?) slot seen in the last N days,
|
|
6
|
+
* aggregate how many times Claude actually invoked each agent
|
|
7
|
+
* (either as the first Task subagent_type, or anywhere in the
|
|
8
|
+
* downstream chain).
|
|
9
|
+
* 2. Compare against the current AgentRouter decision for that slot.
|
|
10
|
+
* 3. Emit a recommendation when:
|
|
11
|
+
* - A different agent was actually used > 50% of the time
|
|
12
|
+
* - Sample size >= 10
|
|
13
|
+
* - The recommended agent exists in AgentRegistry
|
|
14
|
+
* 4. Sort by confidence (sample size * rate delta).
|
|
15
|
+
*
|
|
16
|
+
* This is intentionally conservative — we do not attempt to learn rule
|
|
17
|
+
* ordering or compose new when-clauses. Recommendations are suggestions
|
|
18
|
+
* for the user to apply manually (or via a one-click button later).
|
|
19
|
+
*/
|
|
20
|
+
import { logger } from '../core/utils/logger.js';
|
|
21
|
+
const TASK_TYPES = [
|
|
22
|
+
'build_system', 'add_feature', 'refactor', 'migrate', 'fix_bug',
|
|
23
|
+
'analyze_requirement', 'design_architecture', 'design_api', 'design_schema',
|
|
24
|
+
'write_code', 'write_test', 'review', 'audit_security', 'optimize_perf',
|
|
25
|
+
'write_spec', 'review_doc', 'create_pr', 'explain', 'other',
|
|
26
|
+
];
|
|
27
|
+
const COMPLEXITIES = ['simple', 'moderate', 'complex'];
|
|
28
|
+
export class Recommender {
|
|
29
|
+
storage;
|
|
30
|
+
router;
|
|
31
|
+
agents;
|
|
32
|
+
windowMs;
|
|
33
|
+
minSampleSize;
|
|
34
|
+
minChallengerRate;
|
|
35
|
+
constructor(storage, router, agents, opts = {}) {
|
|
36
|
+
this.storage = storage;
|
|
37
|
+
this.router = router;
|
|
38
|
+
this.agents = agents;
|
|
39
|
+
this.windowMs = opts.windowMs ?? 7 * 24 * 3600 * 1000;
|
|
40
|
+
this.minSampleSize = opts.minSampleSize ?? 10;
|
|
41
|
+
this.minChallengerRate = opts.minChallengerRate ?? 0.5;
|
|
42
|
+
}
|
|
43
|
+
analyze() {
|
|
44
|
+
const since = Date.now() - this.windowMs;
|
|
45
|
+
const events = this.storage.queryRoutingEvents({ since_ts: since, limit: 20000 });
|
|
46
|
+
const slots = new Map();
|
|
47
|
+
for (const e of events) {
|
|
48
|
+
if (!e.is_forced)
|
|
49
|
+
continue;
|
|
50
|
+
let taskType = null;
|
|
51
|
+
let complexity = null;
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(e.intent_json ?? '{}');
|
|
54
|
+
if (typeof parsed.taskType === 'string' && TASK_TYPES.includes(parsed.taskType)) {
|
|
55
|
+
taskType = parsed.taskType;
|
|
56
|
+
}
|
|
57
|
+
if (typeof parsed.complexity === 'string' && COMPLEXITIES.includes(parsed.complexity)) {
|
|
58
|
+
complexity = parsed.complexity;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch { /* ignore */ }
|
|
62
|
+
if (!taskType)
|
|
63
|
+
continue;
|
|
64
|
+
const key = `${taskType}__${complexity ?? 'any'}`;
|
|
65
|
+
const agg = slots.get(key) ?? {
|
|
66
|
+
taskType,
|
|
67
|
+
complexity,
|
|
68
|
+
attempts: 0,
|
|
69
|
+
obeyedCount: 0,
|
|
70
|
+
agentUsage: new Map(),
|
|
71
|
+
currentAgent: null,
|
|
72
|
+
};
|
|
73
|
+
agg.attempts++;
|
|
74
|
+
if (e.obeyed === 1)
|
|
75
|
+
agg.obeyedCount++;
|
|
76
|
+
// Count agent usage — the agent Claude actually invoked (if we know).
|
|
77
|
+
// Priority order: first_tool_name=Task's subagent (encoded indirectly
|
|
78
|
+
// via obeyed + routed_to_name), then the downstream chain.
|
|
79
|
+
const chain = parseChain(e.downstream_task_chain);
|
|
80
|
+
if (chain.length > 0) {
|
|
81
|
+
// The chain is the authoritative signal: which agents Claude engaged.
|
|
82
|
+
const first = chain[0];
|
|
83
|
+
agg.agentUsage.set(first, (agg.agentUsage.get(first) ?? 0) + 1);
|
|
84
|
+
}
|
|
85
|
+
else if (e.obeyed === 1 && e.routed_to_name) {
|
|
86
|
+
// Obeyed path with no chain recorded (non-Task first tool but match) —
|
|
87
|
+
// count the routed agent.
|
|
88
|
+
agg.agentUsage.set(e.routed_to_name, (agg.agentUsage.get(e.routed_to_name) ?? 0) + 1);
|
|
89
|
+
}
|
|
90
|
+
slots.set(key, agg);
|
|
91
|
+
}
|
|
92
|
+
// Fill in currentAgent via the router's live decision.
|
|
93
|
+
for (const agg of slots.values()) {
|
|
94
|
+
const decision = this.router.decide({
|
|
95
|
+
taskType: agg.taskType,
|
|
96
|
+
complexity: agg.complexity ?? 'moderate',
|
|
97
|
+
});
|
|
98
|
+
agg.currentAgent = decision.action.type === 'route_to_agent' ? decision.action.name : null;
|
|
99
|
+
}
|
|
100
|
+
const recommendations = [];
|
|
101
|
+
for (const agg of slots.values()) {
|
|
102
|
+
if (agg.attempts < this.minSampleSize)
|
|
103
|
+
continue;
|
|
104
|
+
if (agg.agentUsage.size === 0)
|
|
105
|
+
continue;
|
|
106
|
+
// Rank agents by usage count.
|
|
107
|
+
const sorted = [...agg.agentUsage.entries()].sort((a, b) => b[1] - a[1]);
|
|
108
|
+
const [topAgent, topCount] = sorted[0];
|
|
109
|
+
const topRate = topCount / agg.attempts;
|
|
110
|
+
if (topRate < this.minChallengerRate)
|
|
111
|
+
continue;
|
|
112
|
+
if (topAgent === agg.currentAgent)
|
|
113
|
+
continue; // Already routing here.
|
|
114
|
+
if (!this.agents.get(topAgent))
|
|
115
|
+
continue; // Unknown agent — skip.
|
|
116
|
+
const currentObeyedRate = agg.obeyedCount / agg.attempts;
|
|
117
|
+
const confidence = Math.min(1, (agg.attempts / 50) * Math.max(0, topRate - currentObeyedRate));
|
|
118
|
+
const reason = agg.currentAgent
|
|
119
|
+
? `当前路由到 ${agg.currentAgent} 听话率 ${(currentObeyedRate * 100).toFixed(0)}%,` +
|
|
120
|
+
`但 Claude 实际使用 ${topAgent} 达 ${(topRate * 100).toFixed(0)}%(${topCount}/${agg.attempts})`
|
|
121
|
+
: `当前无路由(走 skill 回退),但 Claude 实际使用 ${topAgent} 达 ${(topRate * 100).toFixed(0)}%`;
|
|
122
|
+
recommendations.push({
|
|
123
|
+
taskType: agg.taskType,
|
|
124
|
+
complexity: agg.complexity,
|
|
125
|
+
currentAgent: agg.currentAgent,
|
|
126
|
+
recommendedAgent: topAgent,
|
|
127
|
+
sampleSize: agg.attempts,
|
|
128
|
+
currentObeyedRate,
|
|
129
|
+
recommendedUsageRate: topRate,
|
|
130
|
+
confidence,
|
|
131
|
+
reason,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
recommendations.sort((a, b) => b.confidence - a.confidence);
|
|
135
|
+
logger.debug(`[Recommender] ${recommendations.length} recommendation(s) from ${slots.size} slot(s)`);
|
|
136
|
+
return recommendations;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function parseChain(raw) {
|
|
140
|
+
if (!raw)
|
|
141
|
+
return [];
|
|
142
|
+
try {
|
|
143
|
+
const arr = JSON.parse(raw);
|
|
144
|
+
return Array.isArray(arr) ? arr.filter(x => typeof x === 'string') : [];
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=recommender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommender.js","sourceRoot":"","sources":["../../src/engine/recommender.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,27 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* IntentClassifier —
|
|
2
|
+
* IntentClassifier — intent classification for UserPromptSubmit routing
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Output contract:
|
|
5
|
+
* { complexity, taskType, searchKeywords, reasoning }
|
|
6
|
+
*
|
|
7
|
+
* Layered strategy (fast → slow):
|
|
8
|
+
* 1. Regex ground-truth — explicit simple commands (ls/pwd/...) → skip AI
|
|
9
|
+
* 2. LRU cache — repeated prompts
|
|
10
|
+
* 3. AI classification — 3s hard timeout
|
|
11
|
+
* 4. Regex fallback — AI timeout/failure
|
|
7
12
|
*/
|
|
8
13
|
import type { AIProvider } from '../core/ai/types.js';
|
|
9
14
|
export type Complexity = 'simple' | 'moderate' | 'complex';
|
|
15
|
+
export type TaskType = 'build_system' | 'add_feature' | 'refactor' | 'migrate' | 'fix_bug' | 'analyze_requirement' | 'design_architecture' | 'design_api' | 'design_schema' | 'write_code' | 'write_test' | 'review' | 'audit_security' | 'optimize_perf' | 'write_spec' | 'review_doc' | 'create_pr' | 'explain' | 'other';
|
|
16
|
+
export declare const TASK_TYPES: readonly TaskType[];
|
|
10
17
|
export interface IntentAnalysis {
|
|
11
18
|
requirement: string;
|
|
12
19
|
complexity: Complexity;
|
|
20
|
+
taskType: TaskType;
|
|
13
21
|
searchKeywords: string[];
|
|
14
22
|
reasoning: string;
|
|
23
|
+
/** True when AI timed out or failed and regex fallback was used. */
|
|
24
|
+
fallbackUsed?: boolean;
|
|
25
|
+
/** AI classification latency in ms (undefined when fallback-only path). */
|
|
26
|
+
classificationMs?: number;
|
|
15
27
|
}
|
|
16
28
|
export declare class IntentClassifier {
|
|
17
29
|
private ai;
|
|
18
30
|
private cache;
|
|
19
|
-
constructor(ai: AIProvider);
|
|
31
|
+
constructor(ai: AIProvider | null);
|
|
20
32
|
classify(prompt: string, projectPath: string): Promise<IntentAnalysis>;
|
|
21
33
|
private matchGroundTruth;
|
|
34
|
+
private aiClassifyWithTimeout;
|
|
22
35
|
private aiClassify;
|
|
23
36
|
private parseAIResponse;
|
|
24
37
|
private fallback;
|
|
38
|
+
private guessTaskType;
|
|
25
39
|
private extractKeywords;
|
|
26
40
|
}
|
|
27
41
|
//# sourceMappingURL=classifier.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classifier.d.ts","sourceRoot":"","sources":["../../src/intelligence/classifier.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"classifier.d.ts","sourceRoot":"","sources":["../../src/intelligence/classifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAItD,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAE3D,MAAM,MAAM,QAAQ,GAChB,cAAc,GACd,aAAa,GACb,UAAU,GACV,SAAS,GACT,SAAS,GACT,qBAAqB,GACrB,qBAAqB,GACrB,YAAY,GACZ,eAAe,GACf,YAAY,GACZ,YAAY,GACZ,QAAQ,GACR,gBAAgB,GAChB,eAAe,GACf,YAAY,GACZ,YAAY,GACZ,WAAW,GACX,SAAS,GACT,OAAO,CAAC;AAEZ,eAAO,MAAM,UAAU,EAAE,SAAS,QAAQ,EAKhC,CAAC;AAEX,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,QAAQ,CAAC;IACnB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AA4CD,qBAAa,gBAAgB;IAGf,OAAO,CAAC,EAAE;IAFtB,OAAO,CAAC,KAAK,CAA4C;gBAErC,EAAE,EAAE,UAAU,GAAG,IAAI;IAEnC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAwC5E,OAAO,CAAC,gBAAgB;YA4BV,qBAAqB;YAYrB,UAAU;IAgCxB,OAAO,CAAC,eAAe;IA2BvB,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,eAAe;CAOxB"}
|