claude-brain 0.22.2 → 0.22.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/VERSION +1 -1
- package/package.json +1 -1
- package/src/memory/chroma/store.ts +6 -3
- package/src/memory/fts5-search.ts +9 -7
- package/src/memory/index.ts +16 -5
- package/src/retrieval/query/expander.ts +5 -0
- package/src/routing/response-filter.ts +3 -1
- package/src/routing/router.ts +48 -7
- package/src/server/handlers/tools/brain.ts +3 -2
- package/src/server/handlers/tools/schemas.ts +2 -1
- package/src/server/http-api.ts +20 -10
- package/src/server/utils/memory-indicator.ts +4 -3
- package/src/server/web-viewer.ts +1 -1
- package/src/tools/schemas.ts +5 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.22.
|
|
1
|
+
0.22.4
|
package/package.json
CHANGED
|
@@ -25,6 +25,7 @@ function sanitizeMetadata(metadata: Record<string, any>): Record<string, string
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface StoreDecisionInput {
|
|
28
|
+
id?: string
|
|
28
29
|
project: string
|
|
29
30
|
context: string
|
|
30
31
|
decision: string
|
|
@@ -141,7 +142,7 @@ export class ChromaMemoryStore {
|
|
|
141
142
|
return firstDuplicate.id
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
const id = randomUUID()
|
|
145
|
+
const id = input.id || randomUUID()
|
|
145
146
|
const now = new Date().toISOString()
|
|
146
147
|
|
|
147
148
|
const metadata: Record<string, any> = {
|
|
@@ -225,6 +226,7 @@ export class ChromaMemoryStore {
|
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
async storePattern(input: {
|
|
229
|
+
id?: string
|
|
228
230
|
project: string
|
|
229
231
|
pattern_type: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
|
|
230
232
|
description: string
|
|
@@ -233,7 +235,7 @@ export class ChromaMemoryStore {
|
|
|
233
235
|
context?: string
|
|
234
236
|
source?: string
|
|
235
237
|
}): Promise<string> {
|
|
236
|
-
const id = randomUUID()
|
|
238
|
+
const id = input.id || randomUUID()
|
|
237
239
|
const now = new Date().toISOString()
|
|
238
240
|
|
|
239
241
|
const metadata: Record<string, any> = {
|
|
@@ -300,6 +302,7 @@ export class ChromaMemoryStore {
|
|
|
300
302
|
}
|
|
301
303
|
|
|
302
304
|
async storeCorrection(input: {
|
|
305
|
+
id?: string
|
|
303
306
|
project: string
|
|
304
307
|
original: string
|
|
305
308
|
correction: string
|
|
@@ -307,7 +310,7 @@ export class ChromaMemoryStore {
|
|
|
307
310
|
context?: string
|
|
308
311
|
confidence: number
|
|
309
312
|
}): Promise<string> {
|
|
310
|
-
const id = randomUUID()
|
|
313
|
+
const id = input.id || randomUUID()
|
|
311
314
|
const now = new Date().toISOString()
|
|
312
315
|
|
|
313
316
|
const metadata: Record<string, any> = {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { randomUUID } from 'crypto'
|
|
8
8
|
import type { Database } from 'bun:sqlite'
|
|
9
9
|
import type { Logger } from 'pino'
|
|
10
|
+
import { expandQuery } from '@/retrieval/query/expander'
|
|
10
11
|
|
|
11
12
|
export type ObservationCategory = 'decision' | 'pattern' | 'correction' | 'insight' | 'preference'
|
|
12
13
|
|
|
@@ -174,8 +175,8 @@ export class FTS5Search {
|
|
|
174
175
|
/**
|
|
175
176
|
* Store a new observation. Returns the generated ID.
|
|
176
177
|
*/
|
|
177
|
-
store(observation: NewObservation): string {
|
|
178
|
-
const id = randomUUID()
|
|
178
|
+
store(observation: NewObservation, providedId?: string): string {
|
|
179
|
+
const id = providedId || randomUUID()
|
|
179
180
|
const now = new Date().toISOString()
|
|
180
181
|
|
|
181
182
|
const stmt = this.db.prepare(`
|
|
@@ -374,12 +375,13 @@ export class FTS5Search {
|
|
|
374
375
|
const cleaned = query.replace(/[":*^~(){}[\]]/g, ' ').trim()
|
|
375
376
|
if (!cleaned) return ''
|
|
376
377
|
|
|
377
|
-
//
|
|
378
|
-
const
|
|
379
|
-
|
|
378
|
+
// Expand query with synonyms (e.g., "database" → also search "storage", "persistence")
|
|
379
|
+
const expanded = expandQuery(cleaned, { useSynonyms: true, maxExpansions: 8 })
|
|
380
|
+
const allWords = expanded.combined.split(/\s+/).filter(w => w.length >= 2)
|
|
381
|
+
const unique = [...new Set(allWords)]
|
|
380
382
|
|
|
381
|
-
|
|
382
|
-
return
|
|
383
|
+
if (unique.length === 0) return ''
|
|
384
|
+
return unique.map(w => `"${w}"`).join(' OR ')
|
|
383
385
|
}
|
|
384
386
|
|
|
385
387
|
/**
|
package/src/memory/index.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Unified memory system manager that combines all components
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { randomUUID } from 'crypto'
|
|
8
9
|
import type { Logger } from 'pino'
|
|
9
10
|
import { MemoryDatabase } from './database'
|
|
10
11
|
import { EmbeddingService } from './embeddings'
|
|
@@ -244,6 +245,9 @@ export class MemoryManager {
|
|
|
244
245
|
reasoning: string,
|
|
245
246
|
options?: { alternatives?: string; tags?: string[] }
|
|
246
247
|
): Promise<string> {
|
|
248
|
+
// BUG-001 fix: Generate a single shared ID for all backends
|
|
249
|
+
const sharedId = randomUUID()
|
|
250
|
+
|
|
247
251
|
// Phase 26: Always store in FTS5 if available
|
|
248
252
|
let fts5Id: string | undefined
|
|
249
253
|
if (this._fts5) {
|
|
@@ -262,7 +266,7 @@ export class MemoryManager {
|
|
|
262
266
|
reasoning,
|
|
263
267
|
context,
|
|
264
268
|
tags: options?.tags
|
|
265
|
-
})
|
|
269
|
+
}, sharedId)
|
|
266
270
|
} catch (error) {
|
|
267
271
|
this.logger.warn({ error }, 'FTS5 store failed, continuing with other backends')
|
|
268
272
|
}
|
|
@@ -272,6 +276,7 @@ export class MemoryManager {
|
|
|
272
276
|
if (this.useChromaDB) {
|
|
273
277
|
try {
|
|
274
278
|
const chromaId = await this.chroma.store.storeDecision({
|
|
279
|
+
id: sharedId,
|
|
275
280
|
project,
|
|
276
281
|
context,
|
|
277
282
|
decision,
|
|
@@ -456,6 +461,9 @@ export class MemoryManager {
|
|
|
456
461
|
context?: string
|
|
457
462
|
source?: string
|
|
458
463
|
}): Promise<string> {
|
|
464
|
+
// BUG-001 fix: Generate a single shared ID for all backends
|
|
465
|
+
const sharedId = randomUUID()
|
|
466
|
+
|
|
459
467
|
// Phase 26: Dual-write to FTS5
|
|
460
468
|
let fts5Id: string | undefined
|
|
461
469
|
if (this._fts5) {
|
|
@@ -467,7 +475,7 @@ export class MemoryManager {
|
|
|
467
475
|
context: input.context,
|
|
468
476
|
confidence: input.confidence,
|
|
469
477
|
source: input.source
|
|
470
|
-
})
|
|
478
|
+
}, sharedId)
|
|
471
479
|
} catch (error) {
|
|
472
480
|
this.logger.warn({ error }, 'FTS5 pattern store failed')
|
|
473
481
|
}
|
|
@@ -475,7 +483,7 @@ export class MemoryManager {
|
|
|
475
483
|
|
|
476
484
|
if (this.useChromaDB) {
|
|
477
485
|
try {
|
|
478
|
-
const chromaId = await this.chroma.store.storePattern(input)
|
|
486
|
+
const chromaId = await this.chroma.store.storePattern({ ...input, id: sharedId })
|
|
479
487
|
return fts5Id || chromaId
|
|
480
488
|
} catch (error) {
|
|
481
489
|
this.logger.warn({ error }, 'ChromaDB pattern store failed')
|
|
@@ -500,6 +508,9 @@ export class MemoryManager {
|
|
|
500
508
|
context?: string
|
|
501
509
|
confidence: number
|
|
502
510
|
}): Promise<string> {
|
|
511
|
+
// BUG-001 fix: Generate a single shared ID for all backends
|
|
512
|
+
const sharedId = randomUUID()
|
|
513
|
+
|
|
503
514
|
// Phase 26: Dual-write to FTS5
|
|
504
515
|
let fts5Id: string | undefined
|
|
505
516
|
if (this._fts5) {
|
|
@@ -511,7 +522,7 @@ export class MemoryManager {
|
|
|
511
522
|
reasoning: input.reasoning,
|
|
512
523
|
context: input.context,
|
|
513
524
|
confidence: input.confidence
|
|
514
|
-
})
|
|
525
|
+
}, sharedId)
|
|
515
526
|
} catch (error) {
|
|
516
527
|
this.logger.warn({ error }, 'FTS5 correction store failed')
|
|
517
528
|
}
|
|
@@ -519,7 +530,7 @@ export class MemoryManager {
|
|
|
519
530
|
|
|
520
531
|
if (this.useChromaDB) {
|
|
521
532
|
try {
|
|
522
|
-
const chromaId = await this.chroma.store.storeCorrection(input)
|
|
533
|
+
const chromaId = await this.chroma.store.storeCorrection({ ...input, id: sharedId })
|
|
523
534
|
return fts5Id || chromaId
|
|
524
535
|
} catch (error) {
|
|
525
536
|
this.logger.warn({ error }, 'ChromaDB correction store failed')
|
|
@@ -59,11 +59,16 @@ const SYNONYMS: Record<string, string[]> = {
|
|
|
59
59
|
deploy: ['deployment', 'release', 'ship', 'publish'],
|
|
60
60
|
build: ['compile', 'bundle', 'package'],
|
|
61
61
|
|
|
62
|
+
// Memory terms
|
|
63
|
+
memory: ['storage', 'store', 'cache', 'array'],
|
|
64
|
+
|
|
62
65
|
// Common verbs
|
|
63
66
|
create: ['add', 'new', 'generate', 'make'],
|
|
64
67
|
update: ['modify', 'change', 'edit', 'patch'],
|
|
65
68
|
delete: ['remove', 'destroy', 'drop'],
|
|
66
69
|
get: ['fetch', 'retrieve', 'read', 'query'],
|
|
70
|
+
choose: ['select', 'pick', 'decide', 'opt', 'chose'],
|
|
71
|
+
chose: ['choose', 'select', 'pick', 'decide', 'opted'],
|
|
67
72
|
|
|
68
73
|
// Framework terms
|
|
69
74
|
react: ['reactjs', 'react.js', 'component'],
|
|
@@ -15,6 +15,7 @@ export interface FilterableResult {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export interface FilteredResult {
|
|
18
|
+
id?: string
|
|
18
19
|
content: string
|
|
19
20
|
score: number
|
|
20
21
|
source: string
|
|
@@ -74,8 +75,9 @@ export class ResponseFilter {
|
|
|
74
75
|
// 5. Limit to 5 results
|
|
75
76
|
filtered = filtered.slice(0, 5)
|
|
76
77
|
|
|
77
|
-
// 6. Add one-line relevance explanation per result
|
|
78
|
+
// 6. Add one-line relevance explanation per result (preserve id from metadata)
|
|
78
79
|
return filtered.map(r => ({
|
|
80
|
+
id: (r.metadata as any)?.id || (r.metadata as any)?.decision_id,
|
|
79
81
|
content: r.content,
|
|
80
82
|
score: r.score,
|
|
81
83
|
source: r.source,
|
package/src/routing/router.ts
CHANGED
|
@@ -39,6 +39,7 @@ const UPDATE_MIN_SIMILARITY = 0.3
|
|
|
39
39
|
export interface BrainInput {
|
|
40
40
|
message: string
|
|
41
41
|
project?: string
|
|
42
|
+
action?: 'auto' | 'store' | 'recall' | 'update' | 'delete'
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
/** Recently stored decision for recency fast path (Bug 4 fix) */
|
|
@@ -84,17 +85,44 @@ export class BrainRouter {
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
async route(input: BrainInput): Promise<BrainResponse> {
|
|
87
|
-
const { message, project: inputProject } = input
|
|
88
|
+
const { message, project: inputProject, action } = input
|
|
88
89
|
|
|
89
|
-
//
|
|
90
|
-
const classification = this.classifier.classify(message)
|
|
91
|
-
this.logger.debug({ intent: classification.primary, confidence: classification.confidence }, 'Intent classified')
|
|
92
|
-
|
|
93
|
-
// Extract entities
|
|
90
|
+
// Extract entities (needed for all paths)
|
|
94
91
|
const entities = await this.entityExtractor.extract(message, inputProject)
|
|
95
92
|
const project = entities.project || inputProject
|
|
96
93
|
this.logger.debug({ project, technologies: entities.technologies }, 'Entities extracted')
|
|
97
94
|
|
|
95
|
+
// P1: Explicit action override — bypass intent classifier entirely
|
|
96
|
+
if (action && action !== 'auto') {
|
|
97
|
+
this.logger.debug({ action }, 'Using explicit action override')
|
|
98
|
+
try {
|
|
99
|
+
switch (action) {
|
|
100
|
+
case 'store':
|
|
101
|
+
return this.handleStoreThis(message, project, entities)
|
|
102
|
+
case 'recall':
|
|
103
|
+
return this.handleContextNeeded(message, project, entities)
|
|
104
|
+
case 'update':
|
|
105
|
+
return this.handleUpdateMemory(message, project, entities)
|
|
106
|
+
case 'delete':
|
|
107
|
+
return this.handleDeleteMemory(message, project, entities)
|
|
108
|
+
default:
|
|
109
|
+
break
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
this.logger.error({ error, action }, 'Router action override error')
|
|
113
|
+
return {
|
|
114
|
+
action: 'none',
|
|
115
|
+
summary: `Error processing ${action} request`,
|
|
116
|
+
content: `Failed to process: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
117
|
+
relevantItems: 0
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Classify intent
|
|
123
|
+
const classification = this.classifier.classify(message)
|
|
124
|
+
this.logger.debug({ intent: classification.primary, confidence: classification.confidence }, 'Intent classified')
|
|
125
|
+
|
|
98
126
|
// Route to handler
|
|
99
127
|
try {
|
|
100
128
|
switch (classification.primary) {
|
|
@@ -383,7 +411,20 @@ export class BrainRouter {
|
|
|
383
411
|
})
|
|
384
412
|
}
|
|
385
413
|
|
|
386
|
-
|
|
414
|
+
const response = this.responseFilter.synthesize(tiers, message, displayProject)
|
|
415
|
+
|
|
416
|
+
// P0: When no results found and message looks like a plain statement (not a question),
|
|
417
|
+
// add a hint that the user may have intended to store it
|
|
418
|
+
if (response.action === 'none' && response.relevantItems === 0) {
|
|
419
|
+
const isPlainStatement = message.length > 15 &&
|
|
420
|
+
!message.trim().endsWith('?') &&
|
|
421
|
+
message.split(/\s+/).length > 3
|
|
422
|
+
if (isPlainStatement) {
|
|
423
|
+
response.content += `\n\n**Tip:** If you meant to save this, try:\n- \`brain("Remember: ${message.slice(0, 60)}")\`\n- Or use \`action: "store"\` to force storage: \`brain({ message: "...", action: "store" })\``
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return response
|
|
387
428
|
}
|
|
388
429
|
|
|
389
430
|
/**
|
|
@@ -22,19 +22,20 @@ export async function handleBrain(
|
|
|
22
22
|
): Promise<ToolResponse> {
|
|
23
23
|
try {
|
|
24
24
|
const input = ToolValidator.validate(args, BrainSchema)
|
|
25
|
-
const { message, project } = input
|
|
25
|
+
const { message, project, action } = input
|
|
26
26
|
|
|
27
27
|
logger.debug(
|
|
28
28
|
{
|
|
29
29
|
messageLength: message.length,
|
|
30
30
|
project,
|
|
31
|
+
action,
|
|
31
32
|
messagePreview: message.slice(0, 80)
|
|
32
33
|
},
|
|
33
34
|
'Brain tool called'
|
|
34
35
|
)
|
|
35
36
|
|
|
36
37
|
const router = getBrainRouter(logger)
|
|
37
|
-
const response = await router.route({ message, project })
|
|
38
|
+
const response = await router.route({ message, project, action })
|
|
38
39
|
|
|
39
40
|
logger.info(
|
|
40
41
|
{
|
|
@@ -137,7 +137,8 @@ export const GetPhase12StatusSchema = z.object({})
|
|
|
137
137
|
|
|
138
138
|
export const BrainSchema = z.object({
|
|
139
139
|
message: z.string().min(1, 'message is required'),
|
|
140
|
-
project: z.string().optional()
|
|
140
|
+
project: z.string().optional(),
|
|
141
|
+
action: z.enum(['auto', 'store', 'recall', 'update', 'delete']).optional()
|
|
141
142
|
})
|
|
142
143
|
|
|
143
144
|
// ============================================================================
|
package/src/server/http-api.ts
CHANGED
|
@@ -288,7 +288,7 @@ export class HttpApiServer {
|
|
|
288
288
|
|
|
289
289
|
private async handleRecallSimilar(c: any): Promise<Response> {
|
|
290
290
|
try {
|
|
291
|
-
const query = c.req.query('query') || ''
|
|
291
|
+
const query = c.req.query('query') || c.req.query('q') || ''
|
|
292
292
|
|
|
293
293
|
const memoryService = getMemoryService()
|
|
294
294
|
|
|
@@ -583,6 +583,16 @@ export class HttpApiServer {
|
|
|
583
583
|
}
|
|
584
584
|
}
|
|
585
585
|
|
|
586
|
+
/** Normalize project param — extract basename if full path provided */
|
|
587
|
+
private normalizeProject(raw: string): string {
|
|
588
|
+
if (!raw) return raw
|
|
589
|
+
// If it looks like a path (contains /), extract the last segment
|
|
590
|
+
if (raw.includes('/')) {
|
|
591
|
+
return raw.split('/').filter(Boolean).pop() || raw
|
|
592
|
+
}
|
|
593
|
+
return raw
|
|
594
|
+
}
|
|
595
|
+
|
|
586
596
|
// ─── Phase 28: Code Intelligence Endpoints ─────────────────
|
|
587
597
|
|
|
588
598
|
private async handleCodeIndex(c: any): Promise<Response> {
|
|
@@ -669,7 +679,7 @@ export class HttpApiServer {
|
|
|
669
679
|
}
|
|
670
680
|
|
|
671
681
|
const query = c.req.query('query') || c.req.query('q') || ''
|
|
672
|
-
const project = c.req.query('project') || ''
|
|
682
|
+
const project = this.normalizeProject(c.req.query('project') || '')
|
|
673
683
|
const limit = parseInt(c.req.query('limit') || '20', 10)
|
|
674
684
|
|
|
675
685
|
if (!query || !project) {
|
|
@@ -699,11 +709,11 @@ export class HttpApiServer {
|
|
|
699
709
|
)
|
|
700
710
|
}
|
|
701
711
|
|
|
702
|
-
const project = c.req.query('project') || ''
|
|
712
|
+
const project = this.normalizeProject(c.req.query('project') || '')
|
|
703
713
|
|
|
704
714
|
if (!project) {
|
|
705
715
|
return Response.json(
|
|
706
|
-
{ success: false, error: 'project is required' },
|
|
716
|
+
{ success: false, error: 'project query parameter is required (e.g., ?project=my-app)' },
|
|
707
717
|
{ status: 400 }
|
|
708
718
|
)
|
|
709
719
|
}
|
|
@@ -730,11 +740,11 @@ export class HttpApiServer {
|
|
|
730
740
|
}
|
|
731
741
|
|
|
732
742
|
const file = c.req.query('file') || ''
|
|
733
|
-
const project = c.req.query('project') || ''
|
|
743
|
+
const project = this.normalizeProject(c.req.query('project') || '')
|
|
734
744
|
|
|
735
745
|
if (!file || !project) {
|
|
736
746
|
return Response.json(
|
|
737
|
-
{ success: false, error: 'file and project are required' },
|
|
747
|
+
{ success: false, error: 'file and project query parameters are required (e.g., ?file=src/index.ts&project=my-app)' },
|
|
738
748
|
{ status: 400 }
|
|
739
749
|
)
|
|
740
750
|
}
|
|
@@ -759,11 +769,11 @@ export class HttpApiServer {
|
|
|
759
769
|
)
|
|
760
770
|
}
|
|
761
771
|
|
|
762
|
-
const project = c.req.query('project') || ''
|
|
772
|
+
const project = this.normalizeProject(c.req.query('project') || '')
|
|
763
773
|
|
|
764
774
|
if (!project) {
|
|
765
775
|
return Response.json(
|
|
766
|
-
{ success: false, error: 'project is required' },
|
|
776
|
+
{ success: false, error: 'project query parameter is required (e.g., ?project=my-app)' },
|
|
767
777
|
{ status: 400 }
|
|
768
778
|
)
|
|
769
779
|
}
|
|
@@ -783,7 +793,7 @@ export class HttpApiServer {
|
|
|
783
793
|
|
|
784
794
|
private async handleContextQuery(c: any): Promise<Response> {
|
|
785
795
|
try {
|
|
786
|
-
const query = c.req.query('query') || ''
|
|
796
|
+
const query = c.req.query('query') || c.req.query('q') || ''
|
|
787
797
|
const type = c.req.query('type') || ''
|
|
788
798
|
const cwd = c.req.query('cwd') || ''
|
|
789
799
|
const limit = parseInt(c.req.query('limit') || '5', 10)
|
|
@@ -804,7 +814,7 @@ export class HttpApiServer {
|
|
|
804
814
|
memoryService.recallSimilar(projectName || 'recent work', {
|
|
805
815
|
project: projectName,
|
|
806
816
|
limit: 5,
|
|
807
|
-
minSimilarity: 0.
|
|
817
|
+
minSimilarity: 0.65,
|
|
808
818
|
}),
|
|
809
819
|
memoryService.getPatterns(projectName, { limit: 5 }),
|
|
810
820
|
])
|
|
@@ -16,13 +16,14 @@ const ANSI = {
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Memory indicator banner shown when Claude Brain provides helpful data
|
|
19
|
+
* Note: No ANSI codes — MCP tool responses should be plain text/markdown
|
|
19
20
|
*/
|
|
20
|
-
export const MEMORY_HELPED_BANNER =
|
|
21
|
+
export const MEMORY_HELPED_BANNER = '🧠 MEMORY ASSISTED'
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Alternative shorter indicator
|
|
24
25
|
*/
|
|
25
|
-
export const MEMORY_HELPED_SHORT =
|
|
26
|
+
export const MEMORY_HELPED_SHORT = '[memory-assisted]'
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Wrap content with the memory helped indicator
|
|
@@ -63,7 +64,7 @@ export function formatMemoryStats(stats: {
|
|
|
63
64
|
|
|
64
65
|
if (parts.length === 0) return ''
|
|
65
66
|
|
|
66
|
-
return
|
|
67
|
+
return `📊 Retrieved: ${parts.join(', ')}`
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
/**
|
package/src/server/web-viewer.ts
CHANGED
|
@@ -30,7 +30,7 @@ export function setupWebViewer(app: Hono): void {
|
|
|
30
30
|
// Search endpoint — queries FTS5
|
|
31
31
|
app.get('/api/memory/search', async (c) => {
|
|
32
32
|
try {
|
|
33
|
-
const query = c.req.query('q') || ''
|
|
33
|
+
const query = c.req.query('query') || c.req.query('q') || ''
|
|
34
34
|
const project = c.req.query('project') || undefined
|
|
35
35
|
const category = c.req.query('category') || undefined
|
|
36
36
|
const limit = parseInt(c.req.query('limit') || '20', 10)
|
package/src/tools/schemas.ts
CHANGED
|
@@ -601,6 +601,11 @@ export const TOOLS = {
|
|
|
601
601
|
project: {
|
|
602
602
|
type: 'string',
|
|
603
603
|
description: 'Project name (IMPORTANT: pass this to scope memories correctly, e.g. "my-app"). Auto-detected from message if omitted, defaults to "general".'
|
|
604
|
+
},
|
|
605
|
+
action: {
|
|
606
|
+
type: 'string',
|
|
607
|
+
enum: ['auto', 'store', 'recall', 'update', 'delete'],
|
|
608
|
+
description: 'Force a specific action instead of auto-detecting intent. "store" = always save, "recall" = always search, "update" = modify last stored, "delete" = remove. Default: auto (use intent classifier).'
|
|
604
609
|
}
|
|
605
610
|
},
|
|
606
611
|
required: ['message']
|