@0xmaxma/claude-gateway 1.6.9 → 1.7.1

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 (216) hide show
  1. package/README.md +149 -20
  2. package/config.template.json +59 -1
  3. package/dist/agent/dreaming/accept.d.ts +43 -0
  4. package/dist/agent/dreaming/accept.d.ts.map +1 -0
  5. package/dist/agent/dreaming/accept.js +230 -0
  6. package/dist/agent/dreaming/accept.js.map +1 -0
  7. package/dist/agent/dreaming/applier.d.ts +77 -0
  8. package/dist/agent/dreaming/applier.d.ts.map +1 -0
  9. package/dist/agent/dreaming/applier.js +406 -0
  10. package/dist/agent/dreaming/applier.js.map +1 -0
  11. package/dist/agent/dreaming/audit.d.ts +27 -0
  12. package/dist/agent/dreaming/audit.d.ts.map +1 -0
  13. package/dist/agent/dreaming/audit.js +100 -0
  14. package/dist/agent/dreaming/audit.js.map +1 -0
  15. package/dist/agent/dreaming/compactor.d.ts +66 -0
  16. package/dist/agent/dreaming/compactor.d.ts.map +1 -0
  17. package/dist/agent/dreaming/compactor.js +306 -0
  18. package/dist/agent/dreaming/compactor.js.map +1 -0
  19. package/dist/agent/dreaming/config.d.ts +19 -0
  20. package/dist/agent/dreaming/config.d.ts.map +1 -0
  21. package/dist/agent/dreaming/config.js +107 -0
  22. package/dist/agent/dreaming/config.js.map +1 -0
  23. package/dist/agent/dreaming/episodic.d.ts +42 -0
  24. package/dist/agent/dreaming/episodic.d.ts.map +1 -0
  25. package/dist/agent/dreaming/episodic.js +116 -0
  26. package/dist/agent/dreaming/episodic.js.map +1 -0
  27. package/dist/agent/dreaming/gather.d.ts +31 -0
  28. package/dist/agent/dreaming/gather.d.ts.map +1 -0
  29. package/dist/agent/dreaming/gather.js +55 -0
  30. package/dist/agent/dreaming/gather.js.map +1 -0
  31. package/dist/agent/dreaming/index.d.ts +87 -0
  32. package/dist/agent/dreaming/index.d.ts.map +1 -0
  33. package/dist/agent/dreaming/index.js +377 -0
  34. package/dist/agent/dreaming/index.js.map +1 -0
  35. package/dist/agent/dreaming/migrate-cli.d.ts +17 -0
  36. package/dist/agent/dreaming/migrate-cli.d.ts.map +1 -0
  37. package/dist/agent/dreaming/migrate-cli.js +42 -0
  38. package/dist/agent/dreaming/migrate-cli.js.map +1 -0
  39. package/dist/agent/dreaming/migrate.d.ts +72 -0
  40. package/dist/agent/dreaming/migrate.d.ts.map +1 -0
  41. package/dist/agent/dreaming/migrate.js +374 -0
  42. package/dist/agent/dreaming/migrate.js.map +1 -0
  43. package/dist/agent/dreaming/report.d.ts +39 -0
  44. package/dist/agent/dreaming/report.d.ts.map +1 -0
  45. package/dist/agent/dreaming/report.js +121 -0
  46. package/dist/agent/dreaming/report.js.map +1 -0
  47. package/dist/agent/dreaming/reviewer.d.ts +43 -0
  48. package/dist/agent/dreaming/reviewer.d.ts.map +1 -0
  49. package/dist/agent/dreaming/reviewer.js +249 -0
  50. package/dist/agent/dreaming/reviewer.js.map +1 -0
  51. package/dist/agent/dreaming/staleness.d.ts +68 -0
  52. package/dist/agent/dreaming/staleness.d.ts.map +1 -0
  53. package/dist/agent/dreaming/staleness.js +281 -0
  54. package/dist/agent/dreaming/staleness.js.map +1 -0
  55. package/dist/agent/dreaming/types.d.ts +130 -0
  56. package/dist/agent/dreaming/types.d.ts.map +1 -0
  57. package/dist/agent/dreaming/types.js +10 -0
  58. package/dist/agent/dreaming/types.js.map +1 -0
  59. package/dist/agent/knowledge/archive-db.d.ts +114 -0
  60. package/dist/agent/knowledge/archive-db.d.ts.map +1 -0
  61. package/dist/agent/knowledge/archive-db.js +474 -0
  62. package/dist/agent/knowledge/archive-db.js.map +1 -0
  63. package/dist/agent/knowledge/chunk.d.ts +21 -0
  64. package/dist/agent/knowledge/chunk.d.ts.map +1 -0
  65. package/dist/agent/knowledge/chunk.js +67 -0
  66. package/dist/agent/knowledge/chunk.js.map +1 -0
  67. package/dist/agent/knowledge/config.d.ts +20 -0
  68. package/dist/agent/knowledge/config.d.ts.map +1 -0
  69. package/dist/agent/knowledge/config.js +143 -0
  70. package/dist/agent/knowledge/config.js.map +1 -0
  71. package/dist/agent/knowledge/graph-demo.d.ts +25 -0
  72. package/dist/agent/knowledge/graph-demo.d.ts.map +1 -0
  73. package/dist/agent/knowledge/graph-demo.js +132 -0
  74. package/dist/agent/knowledge/graph-demo.js.map +1 -0
  75. package/dist/agent/knowledge/index.d.ts +24 -0
  76. package/dist/agent/knowledge/index.d.ts.map +1 -0
  77. package/dist/agent/knowledge/index.js +50 -0
  78. package/dist/agent/knowledge/index.js.map +1 -0
  79. package/dist/agent/knowledge/indexer.d.ts +34 -0
  80. package/dist/agent/knowledge/indexer.d.ts.map +1 -0
  81. package/dist/agent/knowledge/indexer.js +271 -0
  82. package/dist/agent/knowledge/indexer.js.map +1 -0
  83. package/dist/agent/knowledge/lifecycle.d.ts +91 -0
  84. package/dist/agent/knowledge/lifecycle.d.ts.map +1 -0
  85. package/dist/agent/knowledge/lifecycle.js +205 -0
  86. package/dist/agent/knowledge/lifecycle.js.map +1 -0
  87. package/dist/agent/knowledge/provenance.d.ts +15 -0
  88. package/dist/agent/knowledge/provenance.d.ts.map +1 -0
  89. package/dist/agent/knowledge/provenance.js +37 -0
  90. package/dist/agent/knowledge/provenance.js.map +1 -0
  91. package/dist/agent/knowledge/reindex-cli.d.ts +13 -0
  92. package/dist/agent/knowledge/reindex-cli.d.ts.map +1 -0
  93. package/dist/agent/knowledge/reindex-cli.js +53 -0
  94. package/dist/agent/knowledge/reindex-cli.js.map +1 -0
  95. package/dist/agent/knowledge/reindex-spawn.d.ts +30 -0
  96. package/dist/agent/knowledge/reindex-spawn.d.ts.map +1 -0
  97. package/dist/agent/knowledge/reindex-spawn.js +97 -0
  98. package/dist/agent/knowledge/reindex-spawn.js.map +1 -0
  99. package/dist/agent/knowledge/shared-promote.d.ts +18 -0
  100. package/dist/agent/knowledge/shared-promote.d.ts.map +1 -0
  101. package/dist/agent/knowledge/shared-promote.js +29 -0
  102. package/dist/agent/knowledge/shared-promote.js.map +1 -0
  103. package/dist/agent/knowledge/shared-writer.d.ts +24 -0
  104. package/dist/agent/knowledge/shared-writer.d.ts.map +1 -0
  105. package/dist/agent/knowledge/shared-writer.js +97 -0
  106. package/dist/agent/knowledge/shared-writer.js.map +1 -0
  107. package/dist/agent/knowledge/types.d.ts +83 -0
  108. package/dist/agent/knowledge/types.d.ts.map +1 -0
  109. package/dist/agent/knowledge/types.js +12 -0
  110. package/dist/agent/knowledge/types.js.map +1 -0
  111. package/dist/agent/knowledge/wiki.d.ts +107 -0
  112. package/dist/agent/knowledge/wiki.d.ts.map +1 -0
  113. package/dist/agent/knowledge/wiki.js +415 -0
  114. package/dist/agent/knowledge/wiki.js.map +1 -0
  115. package/dist/agent/memory-metrics.d.ts +48 -0
  116. package/dist/agent/memory-metrics.d.ts.map +1 -0
  117. package/dist/agent/memory-metrics.js +156 -0
  118. package/dist/agent/memory-metrics.js.map +1 -0
  119. package/dist/agent/runner.d.ts +34 -1
  120. package/dist/agent/runner.d.ts.map +1 -1
  121. package/dist/agent/runner.js +223 -19
  122. package/dist/agent/runner.js.map +1 -1
  123. package/dist/agent/skill-learning/config.d.ts +18 -0
  124. package/dist/agent/skill-learning/config.d.ts.map +1 -0
  125. package/dist/agent/skill-learning/config.js +71 -0
  126. package/dist/agent/skill-learning/config.js.map +1 -0
  127. package/dist/agent/skill-learning/curator.d.ts +45 -0
  128. package/dist/agent/skill-learning/curator.d.ts.map +1 -0
  129. package/dist/agent/skill-learning/curator.js +171 -0
  130. package/dist/agent/skill-learning/curator.js.map +1 -0
  131. package/dist/agent/skill-learning/index.d.ts +79 -0
  132. package/dist/agent/skill-learning/index.d.ts.map +1 -0
  133. package/dist/agent/skill-learning/index.js +308 -0
  134. package/dist/agent/skill-learning/index.js.map +1 -0
  135. package/dist/agent/skill-learning/metrics.d.ts +13 -0
  136. package/dist/agent/skill-learning/metrics.d.ts.map +1 -0
  137. package/dist/agent/skill-learning/metrics.js +109 -0
  138. package/dist/agent/skill-learning/metrics.js.map +1 -0
  139. package/dist/agent/skill-learning/notifier.d.ts +111 -0
  140. package/dist/agent/skill-learning/notifier.d.ts.map +1 -0
  141. package/dist/agent/skill-learning/notifier.js +398 -0
  142. package/dist/agent/skill-learning/notifier.js.map +1 -0
  143. package/dist/agent/skill-learning/reviewer.d.ts +61 -0
  144. package/dist/agent/skill-learning/reviewer.d.ts.map +1 -0
  145. package/dist/agent/skill-learning/reviewer.js +271 -0
  146. package/dist/agent/skill-learning/reviewer.js.map +1 -0
  147. package/dist/agent/skill-learning/telemetry.d.ts +22 -0
  148. package/dist/agent/skill-learning/telemetry.d.ts.map +1 -0
  149. package/dist/agent/skill-learning/telemetry.js +90 -0
  150. package/dist/agent/skill-learning/telemetry.js.map +1 -0
  151. package/dist/agent/skill-learning/trigger.d.ts +23 -0
  152. package/dist/agent/skill-learning/trigger.d.ts.map +1 -0
  153. package/dist/agent/skill-learning/trigger.js +44 -0
  154. package/dist/agent/skill-learning/trigger.js.map +1 -0
  155. package/dist/agent/skill-learning/types.d.ts +165 -0
  156. package/dist/agent/skill-learning/types.d.ts.map +1 -0
  157. package/dist/agent/skill-learning/types.js +11 -0
  158. package/dist/agent/skill-learning/types.js.map +1 -0
  159. package/dist/agent/skill-learning/writer.d.ts +37 -0
  160. package/dist/agent/skill-learning/writer.d.ts.map +1 -0
  161. package/dist/agent/skill-learning/writer.js +223 -0
  162. package/dist/agent/skill-learning/writer.js.map +1 -0
  163. package/dist/agent/workspace-loader.d.ts +83 -6
  164. package/dist/agent/workspace-loader.d.ts.map +1 -1
  165. package/dist/agent/workspace-loader.js +207 -14
  166. package/dist/agent/workspace-loader.js.map +1 -1
  167. package/dist/api/gateway-router.d.ts +2 -0
  168. package/dist/api/gateway-router.d.ts.map +1 -1
  169. package/dist/api/gateway-router.js +293 -6
  170. package/dist/api/gateway-router.js.map +1 -1
  171. package/dist/api/skills-router.d.ts +2 -2
  172. package/dist/api/skills-router.d.ts.map +1 -1
  173. package/dist/api/skills-router.js +61 -1
  174. package/dist/api/skills-router.js.map +1 -1
  175. package/dist/apps/agent-manager.d.ts +7 -0
  176. package/dist/apps/agent-manager.d.ts.map +1 -1
  177. package/dist/apps/agent-manager.js +13 -1
  178. package/dist/apps/agent-manager.js.map +1 -1
  179. package/dist/config/migrator.d.ts +2 -0
  180. package/dist/config/migrator.d.ts.map +1 -1
  181. package/dist/config/migrator.js +72 -1
  182. package/dist/config/migrator.js.map +1 -1
  183. package/dist/history/cleanup.d.ts +7 -0
  184. package/dist/history/cleanup.d.ts.map +1 -1
  185. package/dist/history/cleanup.js +27 -0
  186. package/dist/history/cleanup.js.map +1 -1
  187. package/dist/history/db.d.ts +65 -0
  188. package/dist/history/db.d.ts.map +1 -1
  189. package/dist/history/db.js +261 -0
  190. package/dist/history/db.js.map +1 -1
  191. package/dist/index.js +122 -20
  192. package/dist/index.js.map +1 -1
  193. package/dist/session/process.d.ts +20 -0
  194. package/dist/session/process.d.ts.map +1 -1
  195. package/dist/session/process.js +128 -4
  196. package/dist/session/process.js.map +1 -1
  197. package/dist/skills/loader.d.ts +8 -0
  198. package/dist/skills/loader.d.ts.map +1 -1
  199. package/dist/skills/loader.js +29 -1
  200. package/dist/skills/loader.js.map +1 -1
  201. package/dist/types.d.ts +116 -0
  202. package/dist/types.d.ts.map +1 -1
  203. package/dist/ui/web-ui.d.ts.map +1 -1
  204. package/dist/ui/web-ui.js +1126 -3
  205. package/dist/ui/web-ui.js.map +1 -1
  206. package/mcp/package.json +2 -1
  207. package/mcp/server.ts +8 -2
  208. package/mcp/tools/image/module.ts +58 -7
  209. package/mcp/tools/memory/archive-reader.test.ts +212 -0
  210. package/mcp/tools/memory/archive-reader.ts +253 -0
  211. package/mcp/tools/memory/module.ts +152 -0
  212. package/mcp/tools/memory/retrieval-recorder.ts +66 -0
  213. package/mcp/tools/skills/module.ts +28 -0
  214. package/mcp/types.ts +9 -3
  215. package/package.json +1 -1
  216. package/resource/claude_gateway.svg +1038 -112
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Memory tool module (planning-64 K1) — implements ToolModule.
3
+ *
4
+ * Read-only, on-demand retrieval over the per-agent knowledge archive
5
+ * (`kb.sqlite`, built by src/ under Node). Two lanes of memory: the small core
6
+ * (MEMORY.md/USER.md) is injected every turn; everything else lives in the
7
+ * searchable archive and is reached here via `memory_search` / `memory_get` —
8
+ * so the bulk never has to sit in the prompt. Runs under Bun; reads via
9
+ * `bun:sqlite` (see archive-reader.ts).
10
+ */
11
+
12
+ import type { ToolModule, McpToolDefinition, McpToolResult, ToolVisibility } from '../../types';
13
+ import { searchArchive, getExcerpt, archiveDbPath, sharedDbPathFromEnv, mergeHits } from './archive-reader';
14
+
15
+ /** Corpora the tool can serve. */
16
+ const SUPPORTED_CORPORA = new Set(['memory', 'shared', 'all']);
17
+
18
+ export class MemoryModule implements ToolModule {
19
+ id = 'memory';
20
+ toolVisibility: ToolVisibility = 'all-configured';
21
+
22
+ isEnabled(): boolean {
23
+ // Available whenever the agent's workspace dir is known (same gate as skills).
24
+ return Boolean(process.env.GATEWAY_WORKSPACE_DIR);
25
+ }
26
+
27
+ getTools(): McpToolDefinition[] {
28
+ return [
29
+ {
30
+ name: 'memory_search',
31
+ description:
32
+ 'Search your long-term memory archive (past notes under memory/, plus MEMORY.md/USER.md) by keyword and get the most relevant snippets with their file + line range. Use this to recall details that are NOT in the memory currently loaded into your context — the archive holds far more than fits in the prompt. Read-only.',
33
+ inputSchema: {
34
+ type: 'object',
35
+ properties: {
36
+ query: { type: 'string', description: 'Keywords to search for.' },
37
+ maxResults: {
38
+ type: 'number',
39
+ description: 'Max snippets to return (default 6, max 20).',
40
+ },
41
+ corpus: {
42
+ type: 'string',
43
+ enum: ['memory', 'shared', 'all'],
44
+ description:
45
+ 'Which corpus to search: "memory" (this agent, default), "shared" (the cross-agent shared KB), or "all" (both).',
46
+ },
47
+ },
48
+ required: ['query'],
49
+ additionalProperties: false,
50
+ },
51
+ },
52
+ {
53
+ name: 'memory_get',
54
+ description:
55
+ 'Read an exact line range from one of your memory files (a path returned by memory_search, e.g. "memory/foo.md", or "MEMORY.md"/"USER.md"). Use after memory_search to pull the full context around a snippet. Read-only.',
56
+ inputSchema: {
57
+ type: 'object',
58
+ properties: {
59
+ path: { type: 'string', description: 'Memory-scoped path (memory/*.md, MEMORY.md, USER.md).' },
60
+ from: { type: 'number', description: '1-indexed start line (default 1).' },
61
+ lines: { type: 'number', description: 'Number of lines to return (default 200).' },
62
+ },
63
+ required: ['path'],
64
+ additionalProperties: false,
65
+ },
66
+ },
67
+ ];
68
+ }
69
+
70
+ async handleTool(name: string, args: Record<string, unknown>): Promise<McpToolResult> {
71
+ const workspaceDir = process.env.GATEWAY_WORKSPACE_DIR;
72
+ if (!workspaceDir) {
73
+ return this.err('Memory archive unavailable: no workspace directory configured.');
74
+ }
75
+
76
+ try {
77
+ switch (name) {
78
+ case 'memory_search': {
79
+ const query = typeof args.query === 'string' ? args.query.trim() : '';
80
+ if (!query) return this.err('memory_search requires a non-empty "query".');
81
+
82
+ // Fail closed on an unsupported corpus rather than silently widening scope.
83
+ const corpus = typeof args.corpus === 'string' ? args.corpus : 'memory';
84
+ if (!SUPPORTED_CORPORA.has(corpus)) {
85
+ return this.json({
86
+ results: [],
87
+ unavailable: true,
88
+ warning: `corpus "${corpus}" is not available yet (shared/all arrive in a later phase). Use "memory".`,
89
+ });
90
+ }
91
+
92
+ const rawMax = Number.isFinite(args.maxResults as number) ? (args.maxResults as number) : 6;
93
+ const maxResults = Math.max(1, Math.min(20, Math.floor(rawMax)));
94
+
95
+ const personalDb = archiveDbPath(workspaceDir);
96
+ const sharedDb = sharedDbPathFromEnv();
97
+ // Recall counter (planning-66): record retrievals ONLY against the
98
+ // per-agent archive (the GC tier). Gated by the gateway via env, mirroring
99
+ // GATEWAY_SHARED_KB_DIR — off unless dreaming.staleness.recordRetrievals.
100
+ const rec = { recordRetrievals: process.env.GATEWAY_RECORD_RETRIEVALS === '1' };
101
+ let results;
102
+ if (corpus === 'memory') {
103
+ results = searchArchive(personalDb, query, maxResults, rec).map((h) => ({ ...h, corpus: 'memory' }));
104
+ } else if (corpus === 'shared') {
105
+ if (!sharedDb) return this.json({ results: [], unavailable: true, warning: 'shared KB is not enabled.' });
106
+ results = searchArchive(sharedDb, query, maxResults).map((h) => ({ ...h, corpus: 'shared' }));
107
+ } else {
108
+ // "all": merge personal + shared, keep the best by bm25.
109
+ type Tagged = ReturnType<typeof searchArchive>[number] & { corpus: string };
110
+ const mine: Tagged[] = searchArchive(personalDb, query, maxResults, rec).map((h) => ({ ...h, corpus: 'memory' }));
111
+ const shared: Tagged[] = sharedDb
112
+ ? searchArchive(sharedDb, query, maxResults).map((h) => ({ ...h, corpus: 'shared' }))
113
+ : [];
114
+ results = mergeHits(mine, shared, maxResults);
115
+ }
116
+
117
+ if (results.length === 0) {
118
+ return this.json({
119
+ results: [],
120
+ note: 'No matches (or the archive has not been indexed yet).',
121
+ });
122
+ }
123
+ return this.json({ results, corpus });
124
+ }
125
+
126
+ case 'memory_get': {
127
+ const p = typeof args.path === 'string' ? args.path : '';
128
+ const from = Number.isFinite(args.from as number) ? (args.from as number) : 1;
129
+ const lines = Number.isFinite(args.lines as number) ? (args.lines as number) : 200;
130
+ const excerpt = getExcerpt(workspaceDir, p, from, lines);
131
+ if (!excerpt) {
132
+ return this.err(`memory_get: "${p}" is not a readable memory-scoped file (memory/*.md, MEMORY.md, USER.md).`);
133
+ }
134
+ return this.json(excerpt);
135
+ }
136
+
137
+ default:
138
+ return this.err(`Unknown memory tool: ${name}`);
139
+ }
140
+ } catch (e) {
141
+ return this.err(`memory tool error: ${e instanceof Error ? e.message : String(e)}`);
142
+ }
143
+ }
144
+
145
+ private json(obj: unknown): McpToolResult {
146
+ return { content: [{ type: 'text', text: JSON.stringify(obj, null, 2) }] };
147
+ }
148
+
149
+ private err(text: string): McpToolResult {
150
+ return { content: [{ type: 'text', text }], isError: true };
151
+ }
152
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Runtime-agnostic append-only retrieval recorder (planning-66).
3
+ *
4
+ * The read path (`archive-reader.ts`) runs under Bun (`bun:sqlite`); the tests and
5
+ * the src/ aggregator run under Node (`node:sqlite`). Both need to write the SAME
6
+ * append-only `kb_retrieval_log` table, so the actual SQL lives here behind a tiny
7
+ * `SqlRunner` seam that each runtime adapts to its own driver. This file imports
8
+ * NO sqlite driver, so it is safe to import from a Node/jest test (importing
9
+ * `archive-reader.ts` directly is not — it top-level-imports `bun:sqlite`).
10
+ *
11
+ * The recall counter is deliberately APPEND-ONLY and fire-and-forget: the Bun read
12
+ * path must never read-modify-write chunk rows (that would break the "mcp reads
13
+ * only / src writes" invariant and risk locking the hot search path). It only ever
14
+ * INSERTs into a dedicated log table; the Node side aggregates that log into
15
+ * `kb_entry_lifecycle.last_retrieved`/`retrieval_count` at GC time.
16
+ */
17
+
18
+ /** Minimal write surface both `bun:sqlite` and `node:sqlite` can satisfy. */
19
+ export interface SqlRunner {
20
+ run(sql: string, params?: unknown[]): void;
21
+ }
22
+
23
+ /** DDL for the append-only retrieval log. Idempotent; safe to call every write. */
24
+ export const RETRIEVAL_LOG_DDL =
25
+ 'CREATE TABLE IF NOT EXISTS kb_retrieval_log (' +
26
+ 'rowid_id INTEGER PRIMARY KEY AUTOINCREMENT, entry_hash TEXT NOT NULL, retrieved_at INTEGER NOT NULL)';
27
+
28
+ /** Ensure the log table exists (the Bun side may run before any Node index pass). */
29
+ export function ensureRetrievalLog(db: SqlRunner): void {
30
+ db.run(RETRIEVAL_LOG_DDL);
31
+ }
32
+
33
+ /**
34
+ * Append one retrieval event, keyed by the STABLE `entry_hash` (never the line-
35
+ * based chunk id). Best-effort: a null/empty hash is ignored (a chunk outside any
36
+ * entry block carries no lifecycle identity). Callers wrap this in try/catch and
37
+ * never block their response on it.
38
+ */
39
+ export function appendRetrieval(db: SqlRunner, entryHash: string | null | undefined, retrievedAt: number): void {
40
+ if (!entryHash) return;
41
+ ensureRetrievalLog(db);
42
+ db.run('INSERT INTO kb_retrieval_log (entry_hash, retrieved_at) VALUES (?, ?)', [entryHash, retrievedAt]);
43
+ }
44
+
45
+ /**
46
+ * Record a batch of retrieval hits (the entry_hashes of one search's results),
47
+ * de-duplicated so a single search that returns two chunks of the same entry
48
+ * counts once. Fire-and-forget; never throws out (errors are swallowed so a
49
+ * telemetry write can never fail a search response).
50
+ */
51
+ export function recordRetrievalHits(
52
+ db: SqlRunner,
53
+ entryHashes: Array<string | null | undefined>,
54
+ retrievedAt: number,
55
+ ): void {
56
+ try {
57
+ const seen = new Set<string>();
58
+ for (const h of entryHashes) {
59
+ if (!h || seen.has(h)) continue;
60
+ seen.add(h);
61
+ appendRetrieval(db, h, retrievedAt);
62
+ }
63
+ } catch {
64
+ /* best-effort telemetry — never affect the search response */
65
+ }
66
+ }
@@ -108,6 +108,15 @@ export class SkillsModule implements ToolModule {
108
108
  additionalProperties: false,
109
109
  },
110
110
  },
111
+ {
112
+ name: 'skill_metrics',
113
+ description: 'Read the skill self-improvement effectiveness rollup for this agent: adoption funnel (auto-skill stickiness), cost-to-complete deltas, recovery-rate trend, enabled/disabled cohort, and the net-token ledger (tokens saved by reuse minus tokens spent reviewing). Read-only.',
114
+ inputSchema: {
115
+ type: 'object',
116
+ properties: {},
117
+ additionalProperties: false,
118
+ },
119
+ },
111
120
  ];
112
121
  }
113
122
 
@@ -152,6 +161,25 @@ export class SkillsModule implements ToolModule {
152
161
  const result = await installSkill(params);
153
162
  return { content: [{ type: 'text', text: result }] };
154
163
  }
164
+ case 'skill_metrics': {
165
+ const apiUrl = (process.env.GATEWAY_API_URL || '').replace(/\/+$/, '');
166
+ const apiKey = process.env.GATEWAY_API_KEY;
167
+ const agentId = process.env.GATEWAY_AGENT_ID;
168
+ if (!apiUrl || !apiKey || !agentId) {
169
+ return {
170
+ content: [{ type: 'text', text: 'skill_metrics unavailable: gateway API env not configured' }],
171
+ isError: true,
172
+ };
173
+ }
174
+ const res = await fetch(`${apiUrl}/api/v1/agents/${encodeURIComponent(agentId)}/skill-metrics`, {
175
+ headers: { Authorization: `Bearer ${apiKey}` },
176
+ });
177
+ const body = await res.text();
178
+ if (!res.ok) {
179
+ return { content: [{ type: 'text', text: `skill_metrics failed (${res.status}): ${body}` }], isError: true };
180
+ }
181
+ return { content: [{ type: 'text', text: body }] };
182
+ }
155
183
  default:
156
184
  return { content: [{ type: 'text', text: `unknown tool: ${name}` }], isError: true };
157
185
  }
package/mcp/types.ts CHANGED
@@ -77,8 +77,13 @@ export interface ChannelModule {
77
77
  /** MCP tools exposed by this module */
78
78
  getTools(): McpToolDefinition[];
79
79
 
80
- /** Execute a tool call */
81
- handleTool(name: string, args: Record<string, unknown>): Promise<McpToolResult>;
80
+ /**
81
+ * Execute a tool call. `signal` fires when the MCP client cancels this specific
82
+ * call (e.g. the CLI sends notifications/cancelled after a user Stop/Ctrl-C
83
+ * interrupts an in-flight tool call) — optional so existing modules that don't
84
+ * need it are unaffected.
85
+ */
86
+ handleTool(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<McpToolResult>;
82
87
 
83
88
  /** Initialize bot client for tool calls (non-blocking) */
84
89
  initBot?(): Promise<void>;
@@ -99,7 +104,8 @@ export interface ToolModule {
99
104
  toolVisibility: ToolVisibility;
100
105
  isEnabled(): boolean;
101
106
  getTools(): McpToolDefinition[];
102
- handleTool(name: string, args: Record<string, unknown>): Promise<McpToolResult>;
107
+ /** See ChannelModule.handleTool's `signal` doc — same optional-cancellation contract. */
108
+ handleTool(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<McpToolResult>;
103
109
  skillsDir?: string;
104
110
  }
105
111
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xmaxma/claude-gateway",
3
- "version": "1.6.9",
3
+ "version": "1.7.1",
4
4
  "description": "Multi-agent gateway for Claude",
5
5
  "repository": {
6
6
  "type": "git",