@rlabs-inc/memory 0.3.7 → 0.3.9
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/README.md +16 -0
- package/dist/index.js +809 -548
- package/dist/index.mjs +809 -548
- package/dist/server/index.js +961 -590
- package/dist/server/index.mjs +961 -590
- package/package.json +1 -1
- package/skills/memory-management.md +16 -3
- package/src/cli/commands/ingest.ts +214 -0
- package/src/cli/index.ts +20 -1
- package/src/core/curator.ts +46 -16
- package/src/core/engine.ts +3 -2
- package/src/core/manager.ts +16 -3
- package/src/core/session-parser.ts +955 -0
- package/src/core/store.ts +38 -29
- package/src/types/schema.ts +13 -0
package/package.json
CHANGED
|
@@ -363,11 +363,23 @@ When memory is retrieved and surfaced:
|
|
|
363
363
|
|
|
364
364
|
## Personal Primer Management
|
|
365
365
|
|
|
366
|
-
The personal primer is a special document in
|
|
366
|
+
The personal primer is a special document in its own dedicated collection (`primer/`) that provides relationship context at the START of EVERY session - not just the first session of a project.
|
|
367
367
|
|
|
368
368
|
**Why every session?** Without the primer, Claude would know more about the user in session #1 than in session #32. The relationship context is foundational and must always be present.
|
|
369
369
|
|
|
370
|
-
**Location:**
|
|
370
|
+
**Location:** `~/.local/share/memory/global/primer/personal-primer.md` - the primer has its own collection, separate from memories.
|
|
371
|
+
|
|
372
|
+
**Schema:** The primer uses a simple dedicated schema (not the full memory schema):
|
|
373
|
+
```yaml
|
|
374
|
+
---
|
|
375
|
+
id: personal-primer
|
|
376
|
+
created: {timestamp}
|
|
377
|
+
updated: {timestamp}
|
|
378
|
+
session_updated: {session_number}
|
|
379
|
+
updated_by: user|manager|curator
|
|
380
|
+
---
|
|
381
|
+
{markdown content}
|
|
382
|
+
```
|
|
371
383
|
|
|
372
384
|
**Injection:** The session primer generator reads this file and includes it BEFORE the project-specific content (previous session summary, project snapshot). On first sessions, there's no previous summary or snapshot, so only the personal primer appears. On subsequent sessions, personal primer + previous summary + snapshot all appear.
|
|
373
385
|
|
|
@@ -521,13 +533,14 @@ Your input includes these paths:
|
|
|
521
533
|
- **Project Memories**: Subdirectory for project memories (`{Project Root}/memories/`)
|
|
522
534
|
- **Global Root**: The global storage directory (`~/.local/share/memory/global/`)
|
|
523
535
|
- **Global Memories**: Subdirectory for global memories (`{Global Root}/memories/`)
|
|
524
|
-
- **Personal Primer**: Full path to the personal primer file
|
|
536
|
+
- **Personal Primer**: Full path to the personal primer file (`{Global Root}/primer/personal-primer.md`)
|
|
525
537
|
|
|
526
538
|
Other subdirectories you may find:
|
|
527
539
|
- `{Project Root}/sessions/` - Session tracking files
|
|
528
540
|
- `{Project Root}/summaries/` - Session summary files
|
|
529
541
|
- `{Project Root}/snapshots/` - Project snapshot files
|
|
530
542
|
- `{Global Root}/management-logs/` - Management agent logs
|
|
543
|
+
- `{Global Root}/primer/` - Personal primer collection (singleton record)
|
|
531
544
|
|
|
532
545
|
### Tool Usage
|
|
533
546
|
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// INGEST COMMAND - Batch ingest historical sessions into memory system
|
|
3
|
+
// Uses session parser + SDK curator to extract memories from past sessions
|
|
4
|
+
// ============================================================================
|
|
5
|
+
|
|
6
|
+
import { logger } from '../../utils/logger.ts'
|
|
7
|
+
import { styleText } from 'util'
|
|
8
|
+
import {
|
|
9
|
+
findAllSessions,
|
|
10
|
+
findProjectSessions,
|
|
11
|
+
parseSessionFileWithSegments,
|
|
12
|
+
getSessionSummary,
|
|
13
|
+
calculateStats,
|
|
14
|
+
type ParsedProject,
|
|
15
|
+
} from '../../core/session-parser.ts'
|
|
16
|
+
import { Curator } from '../../core/curator.ts'
|
|
17
|
+
import { MemoryStore } from '../../core/store.ts'
|
|
18
|
+
import { homedir } from 'os'
|
|
19
|
+
import { join } from 'path'
|
|
20
|
+
|
|
21
|
+
type Style = Parameters<typeof styleText>[0]
|
|
22
|
+
const style = (format: Style, text: string): string => styleText(format, text)
|
|
23
|
+
|
|
24
|
+
interface IngestOptions {
|
|
25
|
+
project?: string
|
|
26
|
+
all?: boolean
|
|
27
|
+
dryRun?: boolean
|
|
28
|
+
verbose?: boolean
|
|
29
|
+
limit?: number
|
|
30
|
+
maxTokens?: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function ingest(options: IngestOptions) {
|
|
34
|
+
logger.setVerbose(options.verbose ?? false)
|
|
35
|
+
|
|
36
|
+
// Header
|
|
37
|
+
console.log()
|
|
38
|
+
console.log(style(['bold', 'magenta'], '┌──────────────────────────────────────────────────────────┐'))
|
|
39
|
+
console.log(style(['bold', 'magenta'], '│') + style('bold', ' 🧠 MEMORY INGESTION ') + style(['bold', 'magenta'], '│'))
|
|
40
|
+
console.log(style(['bold', 'magenta'], '└──────────────────────────────────────────────────────────┘'))
|
|
41
|
+
console.log()
|
|
42
|
+
|
|
43
|
+
// Check for API key
|
|
44
|
+
const apiKey = process.env.ANTHROPIC_API_KEY
|
|
45
|
+
if (!apiKey && !options.dryRun) {
|
|
46
|
+
logger.error('ANTHROPIC_API_KEY not set')
|
|
47
|
+
console.log()
|
|
48
|
+
console.log(style('dim', ' Set your API key to use SDK curation:'))
|
|
49
|
+
console.log(style('dim', ' export ANTHROPIC_API_KEY=sk-...'))
|
|
50
|
+
console.log()
|
|
51
|
+
console.log(style('dim', ' Or use --dry-run to see what would be ingested'))
|
|
52
|
+
console.log()
|
|
53
|
+
process.exit(1)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const projectsFolder = join(homedir(), '.claude', 'projects')
|
|
57
|
+
const maxTokens = options.maxTokens ?? 150000
|
|
58
|
+
|
|
59
|
+
// Find sessions to ingest
|
|
60
|
+
let projects: ParsedProject[] = []
|
|
61
|
+
|
|
62
|
+
if (options.project) {
|
|
63
|
+
// Find specific project
|
|
64
|
+
const projectPath = join(projectsFolder, options.project)
|
|
65
|
+
const sessions = await findProjectSessions(projectPath, { limit: options.limit })
|
|
66
|
+
|
|
67
|
+
if (sessions.length === 0) {
|
|
68
|
+
logger.error(`No sessions found for project: ${options.project}`)
|
|
69
|
+
console.log()
|
|
70
|
+
process.exit(1)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
projects = [{
|
|
74
|
+
folderId: options.project,
|
|
75
|
+
name: sessions[0]?.projectName ?? options.project,
|
|
76
|
+
path: projectPath,
|
|
77
|
+
sessions
|
|
78
|
+
}]
|
|
79
|
+
} else if (options.all) {
|
|
80
|
+
// Find all projects
|
|
81
|
+
projects = await findAllSessions(projectsFolder, { limit: options.limit })
|
|
82
|
+
|
|
83
|
+
if (projects.length === 0) {
|
|
84
|
+
logger.error(`No sessions found in ${projectsFolder}`)
|
|
85
|
+
console.log()
|
|
86
|
+
process.exit(1)
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
logger.error('Specify --project <name> or --all')
|
|
90
|
+
console.log()
|
|
91
|
+
console.log(style('dim', ' Examples:'))
|
|
92
|
+
console.log(style('dim', ' memory ingest --project my-project'))
|
|
93
|
+
console.log(style('dim', ' memory ingest --all'))
|
|
94
|
+
console.log(style('dim', ' memory ingest --all --dry-run'))
|
|
95
|
+
console.log()
|
|
96
|
+
process.exit(1)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Calculate stats
|
|
100
|
+
const stats = calculateStats(projects)
|
|
101
|
+
|
|
102
|
+
logger.info('Discovery complete')
|
|
103
|
+
console.log(` ${style('dim', 'projects:')} ${stats.totalProjects}`)
|
|
104
|
+
console.log(` ${style('dim', 'sessions:')} ${stats.totalSessions}`)
|
|
105
|
+
console.log(` ${style('dim', 'messages:')} ${stats.totalMessages}`)
|
|
106
|
+
console.log(` ${style('dim', 'tool uses:')} ${stats.totalToolUses}`)
|
|
107
|
+
if (stats.oldestSession) {
|
|
108
|
+
console.log(` ${style('dim', 'range:')} ${stats.oldestSession.slice(0, 10)} → ${stats.newestSession?.slice(0, 10) ?? 'now'}`)
|
|
109
|
+
}
|
|
110
|
+
console.log()
|
|
111
|
+
|
|
112
|
+
if (options.dryRun) {
|
|
113
|
+
logger.info('Dry run - sessions to ingest:')
|
|
114
|
+
console.log()
|
|
115
|
+
|
|
116
|
+
for (const project of projects) {
|
|
117
|
+
console.log(` ${style('cyan', '📁')} ${style('bold', project.name)} ${style('dim', `(${project.sessions.length} sessions)`)}`)
|
|
118
|
+
|
|
119
|
+
for (const session of project.sessions.slice(0, 5)) {
|
|
120
|
+
const summary = getSessionSummary(session)
|
|
121
|
+
const truncated = summary.length > 55 ? summary.slice(0, 52) + '...' : summary
|
|
122
|
+
const tokens = session.metadata.estimatedTokens
|
|
123
|
+
const segments = Math.ceil(tokens / maxTokens)
|
|
124
|
+
|
|
125
|
+
console.log(` ${style('dim', '•')} ${session.id.slice(0, 8)}... ${style('dim', `(${tokens} tok, ${segments} seg)`)}`)
|
|
126
|
+
console.log(` ${style('dim', truncated)}`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (project.sessions.length > 5) {
|
|
130
|
+
console.log(` ${style('dim', `... and ${project.sessions.length - 5} more`)}`)
|
|
131
|
+
}
|
|
132
|
+
console.log()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
logger.success('Dry run complete. Remove --dry-run to ingest.')
|
|
136
|
+
console.log()
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Initialize curator and store
|
|
141
|
+
const curator = new Curator({ apiKey })
|
|
142
|
+
const store = new MemoryStore()
|
|
143
|
+
|
|
144
|
+
logger.divider()
|
|
145
|
+
logger.info('Starting ingestion...')
|
|
146
|
+
console.log()
|
|
147
|
+
|
|
148
|
+
let totalSegments = 0
|
|
149
|
+
let totalMemories = 0
|
|
150
|
+
let failedSegments = 0
|
|
151
|
+
|
|
152
|
+
for (const project of projects) {
|
|
153
|
+
console.log(` ${style('cyan', '📁')} ${style('bold', project.name)}`)
|
|
154
|
+
|
|
155
|
+
for (const session of project.sessions) {
|
|
156
|
+
const summary = getSessionSummary(session)
|
|
157
|
+
const truncated = summary.length > 45 ? summary.slice(0, 42) + '...' : summary
|
|
158
|
+
|
|
159
|
+
if (options.verbose) {
|
|
160
|
+
console.log(` ${style('dim', '•')} ${session.id.slice(0, 8)}... "${truncated}"`)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Parse into segments
|
|
164
|
+
const segments = await parseSessionFileWithSegments(session.filepath, maxTokens)
|
|
165
|
+
totalSegments += segments.length
|
|
166
|
+
|
|
167
|
+
for (const segment of segments) {
|
|
168
|
+
try {
|
|
169
|
+
if (options.verbose) {
|
|
170
|
+
console.log(` ${style('dim', `→ Segment ${segment.segmentIndex + 1}/${segment.totalSegments} (${segment.estimatedTokens} tokens)`)}`)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Curate the segment
|
|
174
|
+
const result = await curator.curateFromSegment(segment, 'historical')
|
|
175
|
+
|
|
176
|
+
// Store memories
|
|
177
|
+
for (const memory of result.memories) {
|
|
178
|
+
await store.storeMemory(project.folderId, session.id, memory)
|
|
179
|
+
totalMemories++
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (options.verbose && result.memories.length > 0) {
|
|
183
|
+
console.log(` ${style('green', '✓')} Extracted ${result.memories.length} memories`)
|
|
184
|
+
}
|
|
185
|
+
} catch (error: any) {
|
|
186
|
+
failedSegments++
|
|
187
|
+
if (options.verbose) {
|
|
188
|
+
console.log(` ${style('red', '✗')} Failed: ${error.message}`)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Summary
|
|
198
|
+
logger.divider()
|
|
199
|
+
console.log()
|
|
200
|
+
logger.info('Ingestion complete')
|
|
201
|
+
console.log(` ${style('dim', 'segments:')} ${totalSegments}`)
|
|
202
|
+
console.log(` ${style('dim', 'memories:')} ${style('green', String(totalMemories))}`)
|
|
203
|
+
if (failedSegments > 0) {
|
|
204
|
+
console.log(` ${style('dim', 'failed:')} ${style('yellow', String(failedSegments))}`)
|
|
205
|
+
}
|
|
206
|
+
console.log()
|
|
207
|
+
|
|
208
|
+
if (totalMemories > 0) {
|
|
209
|
+
logger.success(`Extracted ${totalMemories} memories from ${totalSegments} segments`)
|
|
210
|
+
} else {
|
|
211
|
+
logger.warn('No memories extracted. Try --verbose to see details.')
|
|
212
|
+
}
|
|
213
|
+
console.log()
|
|
214
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ ${c.bold('Commands:')}
|
|
|
23
23
|
${c.command('serve')} Start the memory server ${c.muted('(default)')}
|
|
24
24
|
${c.command('stats')} Show memory statistics
|
|
25
25
|
${c.command('install')} Set up hooks ${c.muted('(--claude or --gemini)')}
|
|
26
|
+
${c.command('ingest')} Ingest historical sessions into memory ${c.muted('(--project or --all)')}
|
|
26
27
|
${c.command('migrate')} Upgrade memories to latest schema version
|
|
27
28
|
${c.command('doctor')} Check system health
|
|
28
29
|
${c.command('help')} Show this help message
|
|
@@ -43,9 +44,10 @@ ${fmt.cmd('memory serve --port 9000')} ${c.muted('# Start on custom port')}
|
|
|
43
44
|
${fmt.cmd('memory stats')} ${c.muted('# Show memory statistics')}
|
|
44
45
|
${fmt.cmd('memory install')} ${c.muted('# Install Claude Code hooks (default)')}
|
|
45
46
|
${fmt.cmd('memory install --gemini')} ${c.muted('# Install Gemini CLI hooks')}
|
|
47
|
+
${fmt.cmd('memory ingest --project foo')} ${c.muted('# Ingest sessions from a project')}
|
|
48
|
+
${fmt.cmd('memory ingest --all --dry-run')} ${c.muted('# Preview all sessions to ingest')}
|
|
46
49
|
${fmt.cmd('memory migrate')} ${c.muted('# Upgrade memories to v2 schema')}
|
|
47
50
|
${fmt.cmd('memory migrate --dry-run')} ${c.muted('# Preview migration without changes')}
|
|
48
|
-
${fmt.cmd('memory migrate --embeddings')} ${c.muted('# Regenerate embeddings for all memories')}
|
|
49
51
|
|
|
50
52
|
${c.muted('Documentation: https://github.com/RLabs-Inc/memory')}
|
|
51
53
|
`)
|
|
@@ -76,6 +78,10 @@ async function main() {
|
|
|
76
78
|
'dry-run': { type: 'boolean', default: false },
|
|
77
79
|
embeddings: { type: 'boolean', default: false }, // Regenerate embeddings in migrate
|
|
78
80
|
path: { type: 'string' }, // Custom path for migrate
|
|
81
|
+
project: { type: 'string' }, // Project to ingest
|
|
82
|
+
all: { type: 'boolean', default: false }, // Ingest all projects
|
|
83
|
+
limit: { type: 'string' }, // Limit sessions per project
|
|
84
|
+
'max-tokens': { type: 'string' }, // Max tokens per segment
|
|
79
85
|
},
|
|
80
86
|
allowPositionals: true,
|
|
81
87
|
strict: false, // Allow unknown options for subcommands
|
|
@@ -137,6 +143,19 @@ async function main() {
|
|
|
137
143
|
break
|
|
138
144
|
}
|
|
139
145
|
|
|
146
|
+
case 'ingest': {
|
|
147
|
+
const { ingest } = await import('./commands/ingest.ts')
|
|
148
|
+
await ingest({
|
|
149
|
+
project: values.project,
|
|
150
|
+
all: values.all,
|
|
151
|
+
dryRun: values['dry-run'],
|
|
152
|
+
verbose: values.verbose,
|
|
153
|
+
limit: values.limit ? parseInt(values.limit, 10) : undefined,
|
|
154
|
+
maxTokens: values['max-tokens'] ? parseInt(values['max-tokens'], 10) : undefined,
|
|
155
|
+
})
|
|
156
|
+
break
|
|
157
|
+
}
|
|
158
|
+
|
|
140
159
|
case 'help':
|
|
141
160
|
showHelp()
|
|
142
161
|
break
|
package/src/core/curator.ts
CHANGED
|
@@ -392,42 +392,58 @@ Focus ONLY on technical, architectural, debugging, decision, workflow, and proje
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
/**
|
|
395
|
-
* Curate using Anthropic SDK
|
|
396
|
-
*
|
|
395
|
+
* Curate using Anthropic SDK with parsed session messages
|
|
396
|
+
* Takes the actual conversation messages in API format
|
|
397
397
|
*/
|
|
398
398
|
async curateWithSDK(
|
|
399
|
-
|
|
399
|
+
messages: Array<{ role: 'user' | 'assistant'; content: string | any[] }>,
|
|
400
400
|
triggerType: CurationTrigger = 'session_end'
|
|
401
401
|
): Promise<CurationResult> {
|
|
402
402
|
if (!this._config.apiKey) {
|
|
403
|
-
throw new Error('API key required for SDK mode')
|
|
403
|
+
throw new Error('API key required for SDK mode. Set ANTHROPIC_API_KEY environment variable.')
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
// Dynamic import to make SDK optional
|
|
407
407
|
const { default: Anthropic } = await import('@anthropic-ai/sdk')
|
|
408
408
|
const client = new Anthropic({ apiKey: this._config.apiKey })
|
|
409
409
|
|
|
410
|
-
const
|
|
410
|
+
const systemPrompt = this.buildCurationPrompt(triggerType)
|
|
411
|
+
|
|
412
|
+
// Build the conversation: original messages + curation request
|
|
413
|
+
const conversationMessages = [
|
|
414
|
+
...messages,
|
|
415
|
+
{
|
|
416
|
+
role: 'user' as const,
|
|
417
|
+
content: 'This session has ended. Please curate the memories from our conversation according to your system instructions. Return ONLY the JSON structure with no additional text.',
|
|
418
|
+
},
|
|
419
|
+
]
|
|
411
420
|
|
|
412
421
|
const response = await client.messages.create({
|
|
413
422
|
model: 'claude-sonnet-4-20250514',
|
|
414
423
|
max_tokens: 8192,
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
role: 'user',
|
|
418
|
-
content: `${conversationContext}\n\n---\n\n${prompt}`,
|
|
419
|
-
},
|
|
420
|
-
],
|
|
424
|
+
system: systemPrompt,
|
|
425
|
+
messages: conversationMessages,
|
|
421
426
|
})
|
|
422
427
|
|
|
423
428
|
const content = response.content[0]
|
|
424
429
|
if (content.type !== 'text') {
|
|
425
|
-
throw new Error('Unexpected response type')
|
|
430
|
+
throw new Error('Unexpected response type from Claude API')
|
|
426
431
|
}
|
|
427
432
|
|
|
428
433
|
return this.parseCurationResponse(content.text)
|
|
429
434
|
}
|
|
430
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Curate from a parsed session segment
|
|
438
|
+
* Convenience method that extracts messages from SessionSegment
|
|
439
|
+
*/
|
|
440
|
+
async curateFromSegment(
|
|
441
|
+
segment: { messages: Array<{ role: 'user' | 'assistant'; content: string | any[] }> },
|
|
442
|
+
triggerType: CurationTrigger = 'session_end'
|
|
443
|
+
): Promise<CurationResult> {
|
|
444
|
+
return this.curateWithSDK(segment.messages, triggerType)
|
|
445
|
+
}
|
|
446
|
+
|
|
431
447
|
/**
|
|
432
448
|
* Curate using CLI subprocess (for hook mode)
|
|
433
449
|
* Resumes a session and asks it to curate
|
|
@@ -471,7 +487,8 @@ Focus ONLY on technical, architectural, debugging, decision, workflow, and proje
|
|
|
471
487
|
...process.env,
|
|
472
488
|
MEMORY_CURATOR_ACTIVE: '1', // Prevent recursive hook triggering
|
|
473
489
|
},
|
|
474
|
-
|
|
490
|
+
stdout: 'pipe',
|
|
491
|
+
stderr: 'pipe',
|
|
475
492
|
})
|
|
476
493
|
|
|
477
494
|
// Capture both stdout and stderr
|
|
@@ -490,15 +507,28 @@ Focus ONLY on technical, architectural, debugging, decision, workflow, and proje
|
|
|
490
507
|
// First, parse the CLI JSON wrapper
|
|
491
508
|
const cliOutput = JSON.parse(stdout)
|
|
492
509
|
|
|
510
|
+
// Claude Code now returns an array of events - find the result object
|
|
511
|
+
let resultObj: any
|
|
512
|
+
if (Array.isArray(cliOutput)) {
|
|
513
|
+
// New format: array of events, find the one with type="result"
|
|
514
|
+
resultObj = cliOutput.find((item: any) => item.type === 'result')
|
|
515
|
+
if (!resultObj) {
|
|
516
|
+
return { session_summary: '', memories: [] }
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
// Old format: single object (backwards compatibility)
|
|
520
|
+
resultObj = cliOutput
|
|
521
|
+
}
|
|
522
|
+
|
|
493
523
|
// Check for error response FIRST (like Python does)
|
|
494
|
-
if (
|
|
524
|
+
if (resultObj.type === 'error' || resultObj.is_error === true) {
|
|
495
525
|
return { session_summary: '', memories: [] }
|
|
496
526
|
}
|
|
497
527
|
|
|
498
528
|
// Extract the "result" field (AI's response text)
|
|
499
529
|
let aiResponse = ''
|
|
500
|
-
if (typeof
|
|
501
|
-
aiResponse =
|
|
530
|
+
if (typeof resultObj.result === 'string') {
|
|
531
|
+
aiResponse = resultObj.result
|
|
502
532
|
} else {
|
|
503
533
|
return { session_summary: '', memories: [] }
|
|
504
534
|
}
|
package/src/core/engine.ts
CHANGED
|
@@ -379,7 +379,7 @@ export class MemoryEngine {
|
|
|
379
379
|
store: MemoryStore,
|
|
380
380
|
projectId: string
|
|
381
381
|
): Promise<SessionPrimer> {
|
|
382
|
-
// Fetch personal primer from
|
|
382
|
+
// Fetch personal primer from dedicated primer collection in global database
|
|
383
383
|
let personalContext: string | undefined
|
|
384
384
|
if (this._config.personalMemoriesEnabled) {
|
|
385
385
|
const personalPrimer = await store.getPersonalPrimer()
|
|
@@ -590,7 +590,8 @@ export class MemoryEngine {
|
|
|
590
590
|
// This is a constant: ~/.local/share/memory/global
|
|
591
591
|
const globalPath = join(homedir(), '.local', 'share', 'memory', 'global')
|
|
592
592
|
const globalMemoriesPath = join(globalPath, 'memories')
|
|
593
|
-
|
|
593
|
+
// Personal primer has its own dedicated collection (not in memories)
|
|
594
|
+
const personalPrimerPath = join(globalPath, 'primer', 'personal-primer.md')
|
|
594
595
|
|
|
595
596
|
// Project path depends on storage mode - mirrors _getStore() logic exactly
|
|
596
597
|
let storeBasePath: string
|
package/src/core/manager.ts
CHANGED
|
@@ -242,13 +242,26 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
242
242
|
// First, parse the CLI JSON wrapper
|
|
243
243
|
const cliOutput = JSON.parse(responseJson)
|
|
244
244
|
|
|
245
|
+
// Claude Code now returns an array of events - find the result object
|
|
246
|
+
let resultObj: any
|
|
247
|
+
if (Array.isArray(cliOutput)) {
|
|
248
|
+
// New format: array of events, find the one with type="result"
|
|
249
|
+
resultObj = cliOutput.find((item: any) => item.type === 'result')
|
|
250
|
+
if (!resultObj) {
|
|
251
|
+
return emptyResult('No result found in response')
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
// Old format: single object (backwards compatibility)
|
|
255
|
+
resultObj = cliOutput
|
|
256
|
+
}
|
|
257
|
+
|
|
245
258
|
// Check for error response
|
|
246
|
-
if (
|
|
247
|
-
return emptyResult(
|
|
259
|
+
if (resultObj.type === 'error' || resultObj.is_error === true) {
|
|
260
|
+
return emptyResult(resultObj.error || 'Unknown error')
|
|
248
261
|
}
|
|
249
262
|
|
|
250
263
|
// Extract the "result" field (AI's response text)
|
|
251
|
-
const resultText = typeof
|
|
264
|
+
const resultText = typeof resultObj.result === 'string' ? resultObj.result : ''
|
|
252
265
|
|
|
253
266
|
// Extract the full report (everything from === MANAGEMENT ACTIONS === onwards)
|
|
254
267
|
const reportMatch = resultText.match(/(=== MANAGEMENT ACTIONS ===[\s\S]*)/)
|