capman 0.4.2 → 0.4.4
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 +153 -0
- package/CODEBASE.md +393 -0
- package/README.md +1 -1
- package/bin/capman.js +11 -724
- package/bin/lib/cmd-demo.js +180 -0
- package/bin/lib/cmd-explain.js +72 -0
- package/bin/lib/cmd-generate.js +280 -0
- package/bin/lib/cmd-help.js +26 -0
- package/bin/lib/cmd-init.js +19 -0
- package/bin/lib/cmd-inspect.js +33 -0
- package/bin/lib/cmd-run.js +71 -0
- package/bin/lib/cmd-validate.js +32 -0
- package/bin/lib/shared.js +77 -0
- package/dist/cjs/cache.d.ts.map +1 -1
- package/dist/cjs/cache.js +8 -2
- package/dist/cjs/cache.js.map +1 -1
- package/dist/cjs/engine.d.ts +58 -1
- package/dist/cjs/engine.d.ts.map +1 -1
- package/dist/cjs/engine.js +312 -12
- package/dist/cjs/engine.js.map +1 -1
- package/dist/cjs/generator.d.ts.map +1 -1
- package/dist/cjs/generator.js +4 -0
- package/dist/cjs/generator.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/learning.d.ts.map +1 -1
- package/dist/cjs/learning.js +7 -2
- package/dist/cjs/learning.js.map +1 -1
- package/dist/cjs/matcher.d.ts.map +1 -1
- package/dist/cjs/matcher.js +23 -27
- package/dist/cjs/matcher.js.map +1 -1
- package/dist/cjs/parser.js +2 -1
- package/dist/cjs/parser.js.map +1 -1
- package/dist/cjs/resolver.js +6 -2
- package/dist/cjs/resolver.js.map +1 -1
- package/dist/cjs/types.d.ts +27 -0
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/cache.d.ts +49 -0
- package/dist/esm/cache.js +8 -2
- package/dist/esm/engine.d.ts +138 -0
- package/dist/esm/engine.js +312 -12
- package/dist/esm/generator.d.ts +7 -0
- package/dist/esm/generator.js +4 -0
- package/dist/esm/index.d.ts +47 -0
- package/dist/esm/learning.d.ts +55 -0
- package/dist/esm/learning.js +7 -2
- package/dist/esm/logger.d.ts +21 -0
- package/dist/esm/matcher.d.ts +6 -0
- package/dist/esm/matcher.js +23 -27
- package/dist/esm/parser.d.ts +10 -0
- package/dist/esm/parser.js +2 -1
- package/dist/esm/resolver.d.ts +21 -0
- package/dist/esm/resolver.js +6 -2
- package/dist/esm/schema.d.ts +740 -0
- package/dist/esm/types.d.ts +136 -0
- package/dist/esm/version.d.ts +1 -0
- package/dist/esm/version.js +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { header, log, c, getFlag, requireSrc } = require('./shared')
|
|
4
|
+
|
|
5
|
+
module.exports = function cmdValidate() {
|
|
6
|
+
header()
|
|
7
|
+
const { readManifest, validate } = requireSrc()
|
|
8
|
+
|
|
9
|
+
const manifestPath = getFlag('--manifest') ?? 'manifest.json'
|
|
10
|
+
let manifest
|
|
11
|
+
try {
|
|
12
|
+
manifest = readManifest(manifestPath)
|
|
13
|
+
} catch (e) {
|
|
14
|
+
log.error(e.message)
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
log.info(`Validating ${c.bold}${manifestPath}${c.reset}...`)
|
|
19
|
+
const result = validate(manifest)
|
|
20
|
+
log.blank()
|
|
21
|
+
|
|
22
|
+
for (const w of result.warnings) log.warn(w)
|
|
23
|
+
for (const e of result.errors) log.error(e)
|
|
24
|
+
|
|
25
|
+
if (result.valid) {
|
|
26
|
+
log.success(`${manifest.capabilities.length} capabilities — all valid`)
|
|
27
|
+
} else {
|
|
28
|
+
log.error(`${result.errors.length} error(s) found.`)
|
|
29
|
+
process.exit(1)
|
|
30
|
+
}
|
|
31
|
+
console.log()
|
|
32
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
|
|
6
|
+
// ─── Args ─────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2)
|
|
9
|
+
const command = args[0]
|
|
10
|
+
const flags = args.slice(1)
|
|
11
|
+
|
|
12
|
+
const getFlag = (name) => {
|
|
13
|
+
const i = flags.indexOf(name)
|
|
14
|
+
if (i === -1) return undefined
|
|
15
|
+
const value = flags[i + 1]
|
|
16
|
+
// If next token is another flag or doesn't exist, the flag has no value
|
|
17
|
+
if (value === undefined || value.startsWith('--')) {
|
|
18
|
+
console.error(`${'\x1b[31m'}✗${'\x1b[0m'} Flag "${name}" requires a value. Example: ${name} <value>`)
|
|
19
|
+
process.exit(1)
|
|
20
|
+
}
|
|
21
|
+
return value
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ─── Colors ───────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const c = {
|
|
27
|
+
reset: '\x1b[0m',
|
|
28
|
+
bold: '\x1b[1m',
|
|
29
|
+
teal: '\x1b[36m',
|
|
30
|
+
yellow: '\x1b[33m',
|
|
31
|
+
red: '\x1b[31m',
|
|
32
|
+
green: '\x1b[32m',
|
|
33
|
+
gray: '\x1b[90m',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ─── Logger ───────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
const log = {
|
|
39
|
+
info: (...a) => console.log(`${c.teal}i${c.reset}`, ...a),
|
|
40
|
+
success: (...a) => console.log(`${c.green}✓${c.reset}`, ...a),
|
|
41
|
+
warn: (...a) => console.log(`${c.yellow}⚠${c.reset}`, ...a),
|
|
42
|
+
error: (...a) => console.error(`${c.red}✗${c.reset}`, ...a),
|
|
43
|
+
blank: () => console.log(),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── Header ───────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
function header() {
|
|
49
|
+
const pkg = require(path.join(__dirname, '..', '..', 'package.json'))
|
|
50
|
+
console.log()
|
|
51
|
+
console.log(`${c.bold}${c.teal} capman${c.reset} ${c.gray}v${pkg.version} — Capability Manifest Engine${c.reset}`)
|
|
52
|
+
console.log(`${c.gray} ─────────────────────────────────────────${c.reset}`)
|
|
53
|
+
console.log()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── requireSrc ───────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
function requireSrc() {
|
|
59
|
+
const distPath = path.join(__dirname, '..', '..', 'dist', 'cjs', 'index.js')
|
|
60
|
+
if (fs.existsSync(distPath)) return require(distPath)
|
|
61
|
+
|
|
62
|
+
log.info('dist/cjs not found — running build...')
|
|
63
|
+
try {
|
|
64
|
+
require('child_process').execSync('npm run build', {
|
|
65
|
+
cwd: path.join(__dirname, '..', '..'),
|
|
66
|
+
stdio: 'inherit',
|
|
67
|
+
})
|
|
68
|
+
if (fs.existsSync(distPath)) return require(distPath)
|
|
69
|
+
} catch {
|
|
70
|
+
// build failed
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
log.error('Cannot find dist/cjs/. Run: pnpm run build')
|
|
74
|
+
process.exit(1)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { args, command, flags, getFlag, c, log, header, requireSrc }
|
package/dist/cjs/cache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAK1C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAID,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IAC5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CACxB;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AAEH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAC7C,MAAM,CAQR;AAMD,qBAAa,WAAY,YAAW,UAAU;IAC5C,OAAO,CAAC,KAAK,CAAgC;IAEvC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAU5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAepD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAC9B;AAID,qBAAa,SAAU,YAAW,UAAU;IAC1C,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,MAAM,CAAQ;gBAEV,QAAQ,SAAuB;YAK7B,IAAI;
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAK1C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAID,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IAC5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CACxB;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AAEH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAC7C,MAAM,CAQR;AAMD,qBAAa,WAAY,YAAW,UAAU;IAC5C,OAAO,CAAC,KAAK,CAAgC;IAEvC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAU5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAepD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAC9B;AAID,qBAAa,SAAU,YAAW,UAAU;IAC1C,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,MAAM,CAAQ;gBAEV,QAAQ,SAAuB;YAK7B,IAAI;YAiBJ,IAAI;IAaZ,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAW5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAYpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAI9B;AAID,qBAAa,UAAW,YAAW,UAAU;IAC3C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,IAAI,CAAW;gBAEX,QAAQ,SAAuB;IAKrC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAY5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAG9B"}
|
package/dist/cjs/cache.js
CHANGED
|
@@ -106,8 +106,14 @@ class FileCache {
|
|
|
106
106
|
return;
|
|
107
107
|
try {
|
|
108
108
|
const raw = await fs.promises.readFile(this.filePath, 'utf-8');
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
const parsed = JSON.parse(raw);
|
|
110
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
111
|
+
this.store = new Map(Object.entries(parsed));
|
|
112
|
+
logger_1.logger.debug(`File cache loaded: ${this.store.size} entries`);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
logger_1.logger.warn(`File cache at ${this.filePath} contained unexpected format — starting fresh`);
|
|
116
|
+
}
|
|
111
117
|
}
|
|
112
118
|
catch {
|
|
113
119
|
// File doesn't exist yet — start fresh
|
package/dist/cjs/cache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/engine.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Manifest, MatchResult, ResolveResult, ExecutionTrace } from './types';
|
|
1
|
+
import type { Manifest, MatchResult, ResolveResult, ExecutionTrace, ExplainResult } from './types';
|
|
2
2
|
import type { LLMMatcherOptions } from './matcher';
|
|
3
3
|
import type { ResolveOptions, AuthContext } from './resolver';
|
|
4
4
|
import type { CacheStore } from './cache';
|
|
@@ -28,6 +28,29 @@ export interface EngineOptions {
|
|
|
28
28
|
headers?: Record<string, string>;
|
|
29
29
|
/** Confidence threshold for keyword matcher (default: 50) */
|
|
30
30
|
threshold?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Maximum LLM calls per minute in balanced/accurate mode.
|
|
33
|
+
* After limit is hit, falls back to keyword result.
|
|
34
|
+
* @default 60
|
|
35
|
+
*/
|
|
36
|
+
maxLLMCallsPerMinute?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Minimum milliseconds between consecutive LLM calls.
|
|
39
|
+
* Useful for free-tier models with burst limits.
|
|
40
|
+
* @default 0
|
|
41
|
+
*/
|
|
42
|
+
llmCooldownMs?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Maximum consecutive LLM failures before circuit breaker opens.
|
|
45
|
+
* When open, LLM calls are skipped for llmCircuitBreakerResetMs.
|
|
46
|
+
* @default 3
|
|
47
|
+
*/
|
|
48
|
+
llmCircuitBreakerThreshold?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Milliseconds to wait before retrying LLM after circuit breaker opens.
|
|
51
|
+
* @default 60000
|
|
52
|
+
*/
|
|
53
|
+
llmCircuitBreakerResetMs?: number;
|
|
31
54
|
}
|
|
32
55
|
export interface EngineResult {
|
|
33
56
|
match: MatchResult;
|
|
@@ -47,6 +70,15 @@ export declare class CapmanEngine {
|
|
|
47
70
|
private auth?;
|
|
48
71
|
private headers?;
|
|
49
72
|
private threshold;
|
|
73
|
+
private maxLLMCallsPerMinute;
|
|
74
|
+
private llmCooldownMs;
|
|
75
|
+
private llmCircuitBreakerThreshold;
|
|
76
|
+
private llmCircuitBreakerResetMs;
|
|
77
|
+
private llmCallsThisMinute;
|
|
78
|
+
private llmWindowStart;
|
|
79
|
+
private llmLastCallAt;
|
|
80
|
+
private llmConsecutiveFails;
|
|
81
|
+
private llmCircuitOpenAt;
|
|
50
82
|
constructor(options: EngineOptions);
|
|
51
83
|
/**
|
|
52
84
|
* Ask the engine a natural language query.
|
|
@@ -76,6 +108,31 @@ export declare class CapmanEngine {
|
|
|
76
108
|
* Clear the cache.
|
|
77
109
|
*/
|
|
78
110
|
clearCache(): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Explain what would happen for a query — without executing it.
|
|
113
|
+
* Shows matched capability, all candidate scores with reasoning,
|
|
114
|
+
* and what action would be taken.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* const explanation = await engine.explain('track order 1234')
|
|
118
|
+
* console.log(explanation.matched.reasoning)
|
|
119
|
+
* console.log(explanation.wouldExecute.action)
|
|
120
|
+
* console.log(explanation.candidates)
|
|
121
|
+
*/
|
|
122
|
+
explain(query: string): Promise<ExplainResult>;
|
|
123
|
+
/**
|
|
124
|
+
* Checks all rate limiting and circuit breaker conditions.
|
|
125
|
+
* Returns null if LLM call is allowed, or a skip reason string if it should be skipped.
|
|
126
|
+
*/
|
|
127
|
+
private checkLLMAllowed;
|
|
128
|
+
/**
|
|
129
|
+
* Records a successful LLM call — updates rate limit counters.
|
|
130
|
+
*/
|
|
131
|
+
private recordLLMSuccess;
|
|
132
|
+
/**
|
|
133
|
+
* Records a failed LLM call — may open the circuit breaker.
|
|
134
|
+
*/
|
|
135
|
+
private recordLLMFailure;
|
|
79
136
|
private resolveOptions;
|
|
80
137
|
private recordLearning;
|
|
81
138
|
}
|
package/dist/cjs/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAa,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/engine.js
CHANGED
|
@@ -9,6 +9,12 @@ const cache_1 = require("./cache");
|
|
|
9
9
|
// ─── CapmanEngine ─────────────────────────────────────────────────────────────
|
|
10
10
|
class CapmanEngine {
|
|
11
11
|
constructor(options) {
|
|
12
|
+
// ── LLM rate limiting state ────────────────────────────────────────────────
|
|
13
|
+
this.llmCallsThisMinute = 0;
|
|
14
|
+
this.llmWindowStart = Date.now();
|
|
15
|
+
this.llmLastCallAt = 0;
|
|
16
|
+
this.llmConsecutiveFails = 0;
|
|
17
|
+
this.llmCircuitOpenAt = 0;
|
|
12
18
|
this.manifest = options.manifest;
|
|
13
19
|
this.mode = options.mode ?? 'balanced';
|
|
14
20
|
this.llm = options.llm;
|
|
@@ -16,6 +22,10 @@ class CapmanEngine {
|
|
|
16
22
|
this.auth = options.auth;
|
|
17
23
|
this.headers = options.headers;
|
|
18
24
|
this.threshold = options.threshold ?? 50;
|
|
25
|
+
this.maxLLMCallsPerMinute = options.maxLLMCallsPerMinute ?? 60;
|
|
26
|
+
this.llmCooldownMs = options.llmCooldownMs ?? 0;
|
|
27
|
+
this.llmCircuitBreakerThreshold = options.llmCircuitBreakerThreshold ?? 3;
|
|
28
|
+
this.llmCircuitBreakerResetMs = options.llmCircuitBreakerResetMs ?? 60000;
|
|
19
29
|
// Cache — default MemoryCache (no filesystem writes), or disabled with false
|
|
20
30
|
// Use FileCache or ComboCache explicitly for persistence across restarts
|
|
21
31
|
this.cache = options.cache === false
|
|
@@ -54,7 +64,7 @@ class CapmanEngine {
|
|
|
54
64
|
const resolution = await (0, resolver_1.resolve)(cached.result, cached.result.extractedParams, this.resolveOptions(overrides));
|
|
55
65
|
const trace = {
|
|
56
66
|
query,
|
|
57
|
-
candidates: cached.result.candidates
|
|
67
|
+
candidates: cached.result.candidates,
|
|
58
68
|
reasoning: [`Served from cache (original: ${cached.result.reasoning})`],
|
|
59
69
|
steps,
|
|
60
70
|
resolvedVia: 'cache',
|
|
@@ -86,10 +96,30 @@ class CapmanEngine {
|
|
|
86
96
|
}
|
|
87
97
|
case 'accurate': {
|
|
88
98
|
if (this.llm) {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
const skipReason = this.checkLLMAllowed();
|
|
100
|
+
if (skipReason) {
|
|
101
|
+
logger_1.logger.warn(`LLM skipped — ${skipReason} — falling back to keyword`);
|
|
102
|
+
const t = Date.now();
|
|
103
|
+
matchResult = (0, matcher_1.match)(query, this.manifest);
|
|
104
|
+
steps.push({ type: 'keyword_match', status: 'pass', durationMs: Date.now() - t, detail: `llm skipped: ${skipReason}` });
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const t = Date.now();
|
|
108
|
+
try {
|
|
109
|
+
matchResult = await (0, matcher_1.matchWithLLM)(query, this.manifest, { llm: this.llm });
|
|
110
|
+
this.recordLLMSuccess();
|
|
111
|
+
resolvedVia = 'llm';
|
|
112
|
+
steps.push({ type: 'llm_match', status: 'pass', durationMs: Date.now() - t, detail: `confidence: ${matchResult.confidence}%` });
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
this.recordLLMFailure();
|
|
116
|
+
logger_1.logger.warn(`LLM call failed — falling back to keyword: ${err}`);
|
|
117
|
+
const t2 = Date.now();
|
|
118
|
+
matchResult = (0, matcher_1.match)(query, this.manifest);
|
|
119
|
+
steps.push({ type: 'llm_match', status: 'fail', durationMs: Date.now() - t, detail: String(err) });
|
|
120
|
+
steps.push({ type: 'keyword_match', status: 'pass', durationMs: Date.now() - t2, detail: 'fallback after llm failure' });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
93
123
|
}
|
|
94
124
|
else {
|
|
95
125
|
logger_1.logger.warn('accurate mode requires llm — falling back to keyword');
|
|
@@ -108,11 +138,28 @@ class CapmanEngine {
|
|
|
108
138
|
matchResult = keywordResult;
|
|
109
139
|
}
|
|
110
140
|
else {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
141
|
+
const skipReason = this.checkLLMAllowed();
|
|
142
|
+
if (skipReason) {
|
|
143
|
+
logger_1.logger.warn(`LLM skipped — ${skipReason}`);
|
|
144
|
+
steps.push({ type: 'llm_match', status: 'skip', durationMs: 0, detail: skipReason });
|
|
145
|
+
matchResult = keywordResult;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
logger_1.logger.info(`Low confidence (${keywordResult.confidence}%) — escalating to LLM`);
|
|
149
|
+
const t2 = Date.now();
|
|
150
|
+
try {
|
|
151
|
+
matchResult = await (0, matcher_1.matchWithLLM)(query, this.manifest, { llm: this.llm });
|
|
152
|
+
this.recordLLMSuccess();
|
|
153
|
+
resolvedVia = 'llm';
|
|
154
|
+
steps.push({ type: 'llm_match', status: 'pass', durationMs: Date.now() - t2, detail: `confidence: ${matchResult.confidence}%` });
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
this.recordLLMFailure();
|
|
158
|
+
logger_1.logger.warn(`LLM call failed — falling back to keyword: ${err}`);
|
|
159
|
+
steps.push({ type: 'llm_match', status: 'fail', durationMs: Date.now() - t2, detail: String(err) });
|
|
160
|
+
matchResult = keywordResult;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
116
163
|
}
|
|
117
164
|
break;
|
|
118
165
|
}
|
|
@@ -143,7 +190,7 @@ class CapmanEngine {
|
|
|
143
190
|
});
|
|
144
191
|
// ── Step 6: Build reasoning array ────────────────────────────────────────
|
|
145
192
|
const reasoning = [];
|
|
146
|
-
if (matchResult.candidates
|
|
193
|
+
if (matchResult.candidates.length) {
|
|
147
194
|
const winner = matchResult.candidates.find(c => c.matched);
|
|
148
195
|
const rejected = matchResult.candidates
|
|
149
196
|
.filter(c => !c.matched && c.score > 0)
|
|
@@ -170,7 +217,7 @@ class CapmanEngine {
|
|
|
170
217
|
await this.recordLearning(query, matchResult, resolvedVia);
|
|
171
218
|
const trace = {
|
|
172
219
|
query,
|
|
173
|
-
candidates: matchResult.candidates
|
|
220
|
+
candidates: matchResult.candidates,
|
|
174
221
|
reasoning,
|
|
175
222
|
steps,
|
|
176
223
|
resolvedVia,
|
|
@@ -208,6 +255,259 @@ class CapmanEngine {
|
|
|
208
255
|
if (this.cache)
|
|
209
256
|
await this.cache.clear();
|
|
210
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Explain what would happen for a query — without executing it.
|
|
260
|
+
* Shows matched capability, all candidate scores with reasoning,
|
|
261
|
+
* and what action would be taken.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* const explanation = await engine.explain('track order 1234')
|
|
265
|
+
* console.log(explanation.matched.reasoning)
|
|
266
|
+
* console.log(explanation.wouldExecute.action)
|
|
267
|
+
* console.log(explanation.candidates)
|
|
268
|
+
*/
|
|
269
|
+
async explain(query) {
|
|
270
|
+
const start = Date.now();
|
|
271
|
+
// ── Match — mirrors ask() logic including rate limiting ───────────────────
|
|
272
|
+
let matchResult;
|
|
273
|
+
let resolvedVia = 'keyword';
|
|
274
|
+
if (this.mode === 'accurate') {
|
|
275
|
+
if (this.llm) {
|
|
276
|
+
const skipReason = this.checkLLMAllowed();
|
|
277
|
+
if (skipReason) {
|
|
278
|
+
logger_1.logger.warn(`explain(): LLM skipped — ${skipReason} — falling back to keyword`);
|
|
279
|
+
matchResult = (0, matcher_1.match)(query, this.manifest);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
try {
|
|
283
|
+
matchResult = await (0, matcher_1.matchWithLLM)(query, this.manifest, { llm: this.llm });
|
|
284
|
+
this.recordLLMSuccess();
|
|
285
|
+
resolvedVia = 'llm';
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
this.recordLLMFailure();
|
|
289
|
+
logger_1.logger.warn(`explain(): LLM call failed — falling back to keyword: ${err}`);
|
|
290
|
+
matchResult = (0, matcher_1.match)(query, this.manifest);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
matchResult = (0, matcher_1.match)(query, this.manifest);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else if (this.mode === 'balanced' && this.llm) {
|
|
299
|
+
// Keyword first — escalate to LLM if low confidence (same as ask())
|
|
300
|
+
const keywordResult = (0, matcher_1.match)(query, this.manifest);
|
|
301
|
+
if (keywordResult.confidence >= this.threshold) {
|
|
302
|
+
matchResult = keywordResult;
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
const skipReason = this.checkLLMAllowed();
|
|
306
|
+
if (skipReason) {
|
|
307
|
+
logger_1.logger.warn(`explain(): LLM skipped — ${skipReason}`);
|
|
308
|
+
matchResult = keywordResult;
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
try {
|
|
312
|
+
matchResult = await (0, matcher_1.matchWithLLM)(query, this.manifest, { llm: this.llm });
|
|
313
|
+
this.recordLLMSuccess();
|
|
314
|
+
resolvedVia = 'llm';
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
this.recordLLMFailure();
|
|
318
|
+
logger_1.logger.warn(`explain(): LLM call failed — falling back to keyword: ${err}`);
|
|
319
|
+
matchResult = keywordResult;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
// cheap mode or no llm — keyword only
|
|
326
|
+
matchResult = (0, matcher_1.match)(query, this.manifest);
|
|
327
|
+
}
|
|
328
|
+
// ── Build candidate explanations ─────────────────────────────────────────
|
|
329
|
+
const candidates = matchResult.candidates
|
|
330
|
+
.sort((a, b) => b.score - a.score)
|
|
331
|
+
.map(c => {
|
|
332
|
+
const cap = this.manifest.capabilities.find(mc => mc.id === c.capabilityId);
|
|
333
|
+
let explanation = '';
|
|
334
|
+
if (c.score === 0) {
|
|
335
|
+
explanation = 'No keyword overlap with examples or description';
|
|
336
|
+
}
|
|
337
|
+
else if (c.score >= 90) {
|
|
338
|
+
explanation = `Strong match (${c.score}%) — query closely matches examples`;
|
|
339
|
+
}
|
|
340
|
+
else if (c.score >= 50) {
|
|
341
|
+
const qWords = query.toLowerCase().split(/\W+/).filter(Boolean);
|
|
342
|
+
const matchedWords = (cap?.examples ?? [])
|
|
343
|
+
.flatMap(e => e.toLowerCase().split(/\s+/))
|
|
344
|
+
.filter(w => qWords.includes(w) && w.length > 2);
|
|
345
|
+
const unique = [...new Set(matchedWords)].slice(0, 3);
|
|
346
|
+
explanation = unique.length
|
|
347
|
+
? `Matched keywords: ${unique.join(', ')} (${c.score}%)`
|
|
348
|
+
: `Partial match (${c.score}%) — some keyword overlap`;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
explanation = `Weak match (${c.score}%) — below 50% confidence threshold, rejected`;
|
|
352
|
+
}
|
|
353
|
+
return { capabilityId: c.capabilityId, score: c.score, matched: c.matched, explanation };
|
|
354
|
+
});
|
|
355
|
+
// ── Build reasoning array ────────────────────────────────────────────────
|
|
356
|
+
const reasoning = [];
|
|
357
|
+
const winner = candidates.find(c => c.matched);
|
|
358
|
+
const rejected = candidates.filter(c => !c.matched && c.score > 0).slice(0, 3);
|
|
359
|
+
if (winner) {
|
|
360
|
+
reasoning.push(`Matched "${winner.capabilityId}" with ${winner.score}% confidence`);
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
reasoning.push('No capability matched above the 50% confidence threshold');
|
|
364
|
+
}
|
|
365
|
+
if (rejected.length) {
|
|
366
|
+
reasoning.push(`Rejected: ${rejected.map(r => `${r.capabilityId} (${r.score}%)`).join(', ')}`);
|
|
367
|
+
}
|
|
368
|
+
reasoning.push(`Resolved via: ${resolvedVia}`);
|
|
369
|
+
if (matchResult.extractedParams && Object.keys(matchResult.extractedParams).length) {
|
|
370
|
+
const params = Object.entries(matchResult.extractedParams)
|
|
371
|
+
.filter(([, v]) => v !== null)
|
|
372
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
373
|
+
.join(', ');
|
|
374
|
+
if (params)
|
|
375
|
+
reasoning.push(`Would extract params: ${params}`);
|
|
376
|
+
}
|
|
377
|
+
// ── Build wouldExecute ───────────────────────────────────────────────────
|
|
378
|
+
const cap = matchResult.capability;
|
|
379
|
+
let action = null;
|
|
380
|
+
let blocked = null;
|
|
381
|
+
let privacy = null;
|
|
382
|
+
let resolverType = null;
|
|
383
|
+
if (cap) {
|
|
384
|
+
privacy = cap.privacy.level;
|
|
385
|
+
resolverType = cap.resolver.type;
|
|
386
|
+
// Check if privacy would block — mirrors checkPrivacy() in resolver.ts
|
|
387
|
+
if (cap.privacy.level === 'user_owned') {
|
|
388
|
+
if (!this.auth?.isAuthenticated) {
|
|
389
|
+
blocked = `Capability "${cap.id}" requires authentication (privacy: user_owned)`;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
else if (cap.privacy.level === 'admin') {
|
|
393
|
+
if (!this.auth?.isAuthenticated) {
|
|
394
|
+
blocked = `Capability "${cap.id}" requires authentication (privacy: admin)`;
|
|
395
|
+
}
|
|
396
|
+
else if (this.auth.role !== 'admin') {
|
|
397
|
+
blocked = `Capability "${cap.id}" requires admin role (current role: ${this.auth.role ?? 'none'})`;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (!blocked) {
|
|
401
|
+
// Build action string
|
|
402
|
+
const params = matchResult.extractedParams;
|
|
403
|
+
if (cap.resolver.type === 'api') {
|
|
404
|
+
const endpoint = cap.resolver.endpoints[0];
|
|
405
|
+
let path = endpoint.path;
|
|
406
|
+
for (const [k, v] of Object.entries(params)) {
|
|
407
|
+
if (v)
|
|
408
|
+
path = path.replace(`{${k}}`, v);
|
|
409
|
+
}
|
|
410
|
+
const base = this.baseUrl ?? '';
|
|
411
|
+
action = `${endpoint.method} ${base}${path}`;
|
|
412
|
+
}
|
|
413
|
+
else if (cap.resolver.type === 'nav') {
|
|
414
|
+
let dest = cap.resolver.destination;
|
|
415
|
+
for (const [k, v] of Object.entries(params)) {
|
|
416
|
+
if (v)
|
|
417
|
+
dest = dest.replace(`{${k}}`, v);
|
|
418
|
+
}
|
|
419
|
+
action = `navigate → ${dest}`;
|
|
420
|
+
}
|
|
421
|
+
else if (cap.resolver.type === 'hybrid') {
|
|
422
|
+
const hybrid = cap.resolver;
|
|
423
|
+
const endpoint = hybrid.api.endpoints[0];
|
|
424
|
+
let path = endpoint.path;
|
|
425
|
+
for (const [k, v] of Object.entries(params)) {
|
|
426
|
+
if (v)
|
|
427
|
+
path = path.replace(`{${k}}`, v);
|
|
428
|
+
}
|
|
429
|
+
let dest = hybrid.nav.destination;
|
|
430
|
+
for (const [k, v] of Object.entries(params)) {
|
|
431
|
+
if (v)
|
|
432
|
+
dest = dest.replace(`{${k}}`, v);
|
|
433
|
+
}
|
|
434
|
+
const base = this.baseUrl ?? '';
|
|
435
|
+
action = `${endpoint.method} ${base}${path} + navigate → ${dest}`;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
query,
|
|
441
|
+
matched: {
|
|
442
|
+
capability: matchResult.capability,
|
|
443
|
+
confidence: matchResult.confidence,
|
|
444
|
+
intent: matchResult.intent,
|
|
445
|
+
reasoning,
|
|
446
|
+
},
|
|
447
|
+
candidates,
|
|
448
|
+
wouldExecute: { resolverType, action, privacy, blocked },
|
|
449
|
+
resolvedVia,
|
|
450
|
+
durationMs: Date.now() - start,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Checks all rate limiting and circuit breaker conditions.
|
|
455
|
+
* Returns null if LLM call is allowed, or a skip reason string if it should be skipped.
|
|
456
|
+
*/
|
|
457
|
+
checkLLMAllowed() {
|
|
458
|
+
const now = Date.now();
|
|
459
|
+
// ── Circuit breaker ──────────────────────────────────────────────────────
|
|
460
|
+
if (this.llmCircuitOpenAt > 0) {
|
|
461
|
+
const elapsed = now - this.llmCircuitOpenAt;
|
|
462
|
+
if (elapsed < this.llmCircuitBreakerResetMs) {
|
|
463
|
+
const remainingSec = Math.ceil((this.llmCircuitBreakerResetMs - elapsed) / 1000);
|
|
464
|
+
return `circuit breaker open — ${remainingSec}s remaining`;
|
|
465
|
+
}
|
|
466
|
+
// Reset circuit breaker — try again
|
|
467
|
+
logger_1.logger.info('LLM circuit breaker reset — trying again');
|
|
468
|
+
this.llmCircuitOpenAt = 0;
|
|
469
|
+
this.llmConsecutiveFails = 0;
|
|
470
|
+
}
|
|
471
|
+
// ── Cooldown between calls ───────────────────────────────────────────────
|
|
472
|
+
if (this.llmCooldownMs > 0 && this.llmLastCallAt > 0) {
|
|
473
|
+
const elapsed = now - this.llmLastCallAt;
|
|
474
|
+
if (elapsed < this.llmCooldownMs) {
|
|
475
|
+
const remainingMs = this.llmCooldownMs - elapsed;
|
|
476
|
+
return `cooldown active — ${remainingMs}ms remaining`;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// ── Per-minute rate limit ────────────────────────────────────────────────
|
|
480
|
+
const windowElapsed = now - this.llmWindowStart;
|
|
481
|
+
if (windowElapsed >= 60000) {
|
|
482
|
+
this.llmCallsThisMinute = 0;
|
|
483
|
+
this.llmWindowStart = now;
|
|
484
|
+
}
|
|
485
|
+
if (this.llmCallsThisMinute >= this.maxLLMCallsPerMinute) {
|
|
486
|
+
// Recalculate elapsed after possible window reset above
|
|
487
|
+
const resetIn = Math.ceil((60000 - (now - this.llmWindowStart)) / 1000);
|
|
488
|
+
return `rate limit reached (${this.maxLLMCallsPerMinute}/min) — resets in ${Math.max(0, resetIn)}s`;
|
|
489
|
+
}
|
|
490
|
+
// Reserve the slot atomically before the call happens
|
|
491
|
+
this.llmCallsThisMinute++;
|
|
492
|
+
this.llmLastCallAt = Date.now();
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Records a successful LLM call — updates rate limit counters.
|
|
497
|
+
*/
|
|
498
|
+
recordLLMSuccess() {
|
|
499
|
+
this.llmConsecutiveFails = 0;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Records a failed LLM call — may open the circuit breaker.
|
|
503
|
+
*/
|
|
504
|
+
recordLLMFailure() {
|
|
505
|
+
this.llmConsecutiveFails++;
|
|
506
|
+
if (this.llmConsecutiveFails >= this.llmCircuitBreakerThreshold) {
|
|
507
|
+
this.llmCircuitOpenAt = Date.now();
|
|
508
|
+
logger_1.logger.warn(`LLM circuit breaker opened after ${this.llmConsecutiveFails} consecutive failures — pausing for ${this.llmCircuitBreakerResetMs / 1000}s`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
211
511
|
// ── Private helpers ────────────────────────────────────────────────────────
|
|
212
512
|
resolveOptions(overrides = {}) {
|
|
213
513
|
return {
|