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.
Files changed (61) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/CODEBASE.md +393 -0
  3. package/README.md +1 -1
  4. package/bin/capman.js +11 -724
  5. package/bin/lib/cmd-demo.js +180 -0
  6. package/bin/lib/cmd-explain.js +72 -0
  7. package/bin/lib/cmd-generate.js +280 -0
  8. package/bin/lib/cmd-help.js +26 -0
  9. package/bin/lib/cmd-init.js +19 -0
  10. package/bin/lib/cmd-inspect.js +33 -0
  11. package/bin/lib/cmd-run.js +71 -0
  12. package/bin/lib/cmd-validate.js +32 -0
  13. package/bin/lib/shared.js +77 -0
  14. package/dist/cjs/cache.d.ts.map +1 -1
  15. package/dist/cjs/cache.js +8 -2
  16. package/dist/cjs/cache.js.map +1 -1
  17. package/dist/cjs/engine.d.ts +58 -1
  18. package/dist/cjs/engine.d.ts.map +1 -1
  19. package/dist/cjs/engine.js +312 -12
  20. package/dist/cjs/engine.js.map +1 -1
  21. package/dist/cjs/generator.d.ts.map +1 -1
  22. package/dist/cjs/generator.js +4 -0
  23. package/dist/cjs/generator.js.map +1 -1
  24. package/dist/cjs/index.d.ts +1 -1
  25. package/dist/cjs/index.d.ts.map +1 -1
  26. package/dist/cjs/index.js.map +1 -1
  27. package/dist/cjs/learning.d.ts.map +1 -1
  28. package/dist/cjs/learning.js +7 -2
  29. package/dist/cjs/learning.js.map +1 -1
  30. package/dist/cjs/matcher.d.ts.map +1 -1
  31. package/dist/cjs/matcher.js +23 -27
  32. package/dist/cjs/matcher.js.map +1 -1
  33. package/dist/cjs/parser.js +2 -1
  34. package/dist/cjs/parser.js.map +1 -1
  35. package/dist/cjs/resolver.js +6 -2
  36. package/dist/cjs/resolver.js.map +1 -1
  37. package/dist/cjs/types.d.ts +27 -0
  38. package/dist/cjs/types.d.ts.map +1 -1
  39. package/dist/cjs/version.d.ts +1 -1
  40. package/dist/cjs/version.js +1 -1
  41. package/dist/esm/cache.d.ts +49 -0
  42. package/dist/esm/cache.js +8 -2
  43. package/dist/esm/engine.d.ts +138 -0
  44. package/dist/esm/engine.js +312 -12
  45. package/dist/esm/generator.d.ts +7 -0
  46. package/dist/esm/generator.js +4 -0
  47. package/dist/esm/index.d.ts +47 -0
  48. package/dist/esm/learning.d.ts +55 -0
  49. package/dist/esm/learning.js +7 -2
  50. package/dist/esm/logger.d.ts +21 -0
  51. package/dist/esm/matcher.d.ts +6 -0
  52. package/dist/esm/matcher.js +23 -27
  53. package/dist/esm/parser.d.ts +10 -0
  54. package/dist/esm/parser.js +2 -1
  55. package/dist/esm/resolver.d.ts +21 -0
  56. package/dist/esm/resolver.js +6 -2
  57. package/dist/esm/schema.d.ts +740 -0
  58. package/dist/esm/types.d.ts +136 -0
  59. package/dist/esm/version.d.ts +1 -0
  60. package/dist/esm/version.js +1 -1
  61. 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 }
@@ -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;YAYJ,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"}
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
- this.store = new Map(Object.entries(JSON.parse(raw)));
110
- logger_1.logger.debug(`File cache loaded: ${this.store.size} entries`);
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
@@ -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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"}
@@ -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
  }
@@ -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;AAC9F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACzC,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAA;AAK9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAKxC,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,QAAQ,EAAE,QAAQ,CAAA;IAClB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,kDAAkD;IAClD,GAAG,CAAC,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAA;IAC9B,0FAA0F;IAC1F,KAAK,CAAC,EAAE,UAAU,GAAG,KAAK,CAAA;IAC1B,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,aAAa,GAAG,KAAK,CAAA;IAChC,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,mDAAmD;IACnD,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAID,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,WAAW,CAAA;IAClB,UAAU,EAAE,aAAa,CAAA;IACzB,WAAW,EAAE,OAAO,GAAG,SAAS,GAAG,KAAK,CAAA;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB,4CAA4C;IAC5C,KAAK,EAAE,cAAc,CAAA;CACtB;AAID,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,IAAI,CAAgB;IAC5B,OAAO,CAAC,GAAG,CAAC,CAA+B;IAC3C,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,IAAI,CAAC,CAAiB;IAC9B,OAAO,CAAC,OAAO,CAAC,CAAyB;IACzC,OAAO,CAAC,SAAS,CAAQ;gBAEb,OAAO,EAAE,aAAa;IAwBlC;;;;;;;;;;OAUG;IACG,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,GAAE,OAAO,CAAC,cAAc,CAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAoKxF;;;OAGG;IACG,QAAQ;IAKd;;OAEG;IACG,kBAAkB,CAAC,KAAK,SAAI;;;;IAKlC;;OAEG;IACG,UAAU;IAMhB,OAAO,CAAC,cAAc;YASR,cAAc;CAgB7B"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/engine.ts"],"names":[],"mappings":"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"}
@@ -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 t = Date.now();
90
- matchResult = await (0, matcher_1.matchWithLLM)(query, this.manifest, { llm: this.llm });
91
- resolvedVia = 'llm';
92
- steps.push({ type: 'llm_match', status: 'pass', durationMs: Date.now() - t, detail: `confidence: ${matchResult.confidence}%` });
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
- logger_1.logger.info(`Low confidence (${keywordResult.confidence}%) — escalating to LLM`);
112
- const t2 = Date.now();
113
- matchResult = await (0, matcher_1.matchWithLLM)(query, this.manifest, { llm: this.llm });
114
- resolvedVia = 'llm';
115
- steps.push({ type: 'llm_match', status: 'pass', durationMs: Date.now() - t2, detail: `confidence: ${matchResult.confidence}%` });
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?.length) {
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 {