@rlabs-inc/memory 0.3.5 → 0.3.6
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 +123 -30
- package/dist/index.js +803 -179
- package/dist/index.mjs +803 -179
- package/dist/server/index.js +36774 -2643
- package/dist/server/index.mjs +1034 -185
- package/package.json +3 -2
- package/skills/memory-management.md +686 -0
- package/src/cli/commands/migrate.ts +423 -0
- package/src/cli/commands/serve.ts +88 -0
- package/src/cli/index.ts +21 -0
- package/src/core/curator.ts +151 -17
- package/src/core/engine.ts +159 -11
- package/src/core/manager.ts +484 -0
- package/src/core/retrieval.ts +547 -420
- package/src/core/store.ts +383 -8
- package/src/server/index.ts +108 -8
- package/src/types/memory.ts +142 -0
- package/src/types/schema.ts +80 -7
- package/src/utils/logger.ts +310 -46
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// MEMORY MANAGER - Post-curation memory lifecycle management
|
|
3
|
+
// Spawns a management agent to update, supersede, and organize memories
|
|
4
|
+
// Mirrors Curator pattern exactly
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
import { join } from 'path'
|
|
8
|
+
import { homedir } from 'os'
|
|
9
|
+
import { existsSync } from 'fs'
|
|
10
|
+
import type { CurationResult } from '../types/memory.ts'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the Claude CLI command path
|
|
14
|
+
* Same logic as curator.ts
|
|
15
|
+
*/
|
|
16
|
+
function getClaudeCommand(): string {
|
|
17
|
+
const envCommand = process.env.CURATOR_COMMAND
|
|
18
|
+
if (envCommand) return envCommand
|
|
19
|
+
|
|
20
|
+
const claudeLocal = join(homedir(), '.claude', 'local', 'claude')
|
|
21
|
+
if (existsSync(claudeLocal)) return claudeLocal
|
|
22
|
+
|
|
23
|
+
return 'claude'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Manager configuration
|
|
28
|
+
*/
|
|
29
|
+
export interface ManagerConfig {
|
|
30
|
+
/**
|
|
31
|
+
* Enable the management agent
|
|
32
|
+
* When disabled, memories are stored but not organized/linked
|
|
33
|
+
* Default: true
|
|
34
|
+
*/
|
|
35
|
+
enabled?: boolean
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* CLI command to use (for subprocess mode)
|
|
39
|
+
* Default: auto-detected (~/.claude/local/claude or 'claude')
|
|
40
|
+
*/
|
|
41
|
+
cliCommand?: string
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Maximum turns for the management agent
|
|
45
|
+
* Set to undefined for unlimited turns
|
|
46
|
+
* Default: undefined (unlimited)
|
|
47
|
+
*/
|
|
48
|
+
maxTurns?: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Storage paths for the management agent
|
|
53
|
+
* These are resolved at runtime from the server's actual configuration
|
|
54
|
+
*/
|
|
55
|
+
export interface StoragePaths {
|
|
56
|
+
/**
|
|
57
|
+
* Root path to project storage directory (NOT the memories subdirectory)
|
|
58
|
+
* e.g., ~/.local/share/memory/memory-ts/ (central)
|
|
59
|
+
* or /path/to/project/.memory/ (local)
|
|
60
|
+
*/
|
|
61
|
+
projectPath: string
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Root path to global storage directory (NOT the memories subdirectory)
|
|
65
|
+
* Always ~/.local/share/memory/global/
|
|
66
|
+
*/
|
|
67
|
+
globalPath: string
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Full path to project memories directory
|
|
71
|
+
* e.g., ~/.local/share/memory/memory-ts/memories/ (central)
|
|
72
|
+
* or /path/to/project/.memory/memories/ (local)
|
|
73
|
+
*/
|
|
74
|
+
projectMemoriesPath: string
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Full path to global memories directory
|
|
78
|
+
* Always ~/.local/share/memory/global/memories/
|
|
79
|
+
*/
|
|
80
|
+
globalMemoriesPath: string
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Full path to personal primer file
|
|
84
|
+
* Always ~/.local/share/memory/global/memories/personal-primer.md
|
|
85
|
+
*/
|
|
86
|
+
personalPrimerPath: string
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Storage mode for context
|
|
90
|
+
*/
|
|
91
|
+
storageMode: 'central' | 'local'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Management result - what the agent did
|
|
96
|
+
*/
|
|
97
|
+
export interface ManagementResult {
|
|
98
|
+
success: boolean
|
|
99
|
+
superseded: number
|
|
100
|
+
resolved: number
|
|
101
|
+
linked: number
|
|
102
|
+
filesRead: number
|
|
103
|
+
filesWritten: number
|
|
104
|
+
primerUpdated: boolean
|
|
105
|
+
actions: string[] // Detailed action log lines
|
|
106
|
+
summary: string // Brief summary for storage
|
|
107
|
+
fullReport: string // Complete management report (ACTIONS + SUMMARY sections)
|
|
108
|
+
error?: string
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Memory Manager - Updates and organizes memories after curation
|
|
113
|
+
* Mirrors Curator class structure
|
|
114
|
+
*/
|
|
115
|
+
export class Manager {
|
|
116
|
+
private _config: {
|
|
117
|
+
enabled: boolean
|
|
118
|
+
cliCommand: string
|
|
119
|
+
maxTurns?: number // undefined = unlimited
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
constructor(config: ManagerConfig = {}) {
|
|
123
|
+
this._config = {
|
|
124
|
+
enabled: config.enabled ?? true,
|
|
125
|
+
cliCommand: config.cliCommand ?? getClaudeCommand(),
|
|
126
|
+
maxTurns: config.maxTurns, // undefined = unlimited turns
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Build the management prompt
|
|
132
|
+
* Loads from skills file
|
|
133
|
+
*/
|
|
134
|
+
async buildManagementPrompt(): Promise<string | null> {
|
|
135
|
+
const skillPaths = [
|
|
136
|
+
// Development - relative to src/core
|
|
137
|
+
join(import.meta.dir, '../../skills/memory-management.md'),
|
|
138
|
+
// Installed via bun global
|
|
139
|
+
join(homedir(), '.bun/install/global/node_modules/@rlabs-inc/memory/skills/memory-management.md'),
|
|
140
|
+
// Installed via npm global
|
|
141
|
+
join(homedir(), '.npm/global/node_modules/@rlabs-inc/memory/skills/memory-management.md'),
|
|
142
|
+
// Local node_modules
|
|
143
|
+
join(process.cwd(), 'node_modules/@rlabs-inc/memory/skills/memory-management.md'),
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
for (const path of skillPaths) {
|
|
147
|
+
try {
|
|
148
|
+
const content = await Bun.file(path).text()
|
|
149
|
+
if (content) return content
|
|
150
|
+
} catch {
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Build the user message with curation data
|
|
160
|
+
* Includes actual storage paths resolved at runtime
|
|
161
|
+
*/
|
|
162
|
+
buildUserMessage(
|
|
163
|
+
projectId: string,
|
|
164
|
+
sessionNumber: number,
|
|
165
|
+
result: CurationResult,
|
|
166
|
+
storagePaths?: StoragePaths
|
|
167
|
+
): string {
|
|
168
|
+
const today = new Date().toISOString().split('T')[0]
|
|
169
|
+
|
|
170
|
+
// Build storage paths section if provided
|
|
171
|
+
// Includes both root paths (for permissions context) and memories paths (for file operations)
|
|
172
|
+
const pathsSection = storagePaths ? `
|
|
173
|
+
## Storage Paths (ACTUAL - use these exact paths)
|
|
174
|
+
|
|
175
|
+
**Storage Mode:** ${storagePaths.storageMode}
|
|
176
|
+
|
|
177
|
+
### Project Storage
|
|
178
|
+
- **Project Root:** ${storagePaths.projectPath}
|
|
179
|
+
- **Project Memories:** ${storagePaths.projectMemoriesPath}
|
|
180
|
+
|
|
181
|
+
### Global Storage (shared across all projects)
|
|
182
|
+
- **Global Root:** ${storagePaths.globalPath}
|
|
183
|
+
- **Global Memories:** ${storagePaths.globalMemoriesPath}
|
|
184
|
+
- **Personal Primer:** ${storagePaths.personalPrimerPath}
|
|
185
|
+
|
|
186
|
+
> ⚠️ These paths are resolved from the running server configuration. Use them exactly as provided.
|
|
187
|
+
> Memories are stored as individual markdown files in the memories directories.
|
|
188
|
+
` : ''
|
|
189
|
+
|
|
190
|
+
return `## Curation Data
|
|
191
|
+
|
|
192
|
+
**Project ID:** ${projectId}
|
|
193
|
+
**Session Number:** ${sessionNumber}
|
|
194
|
+
**Date:** ${today}
|
|
195
|
+
${pathsSection}
|
|
196
|
+
### Session Summary
|
|
197
|
+
${result.session_summary || 'No summary provided'}
|
|
198
|
+
|
|
199
|
+
### Project Snapshot
|
|
200
|
+
${result.project_snapshot ? `
|
|
201
|
+
- Current Phase: ${result.project_snapshot.current_phase || 'N/A'}
|
|
202
|
+
- Recent Achievements: ${result.project_snapshot.recent_achievements?.join(', ') || 'None'}
|
|
203
|
+
- Active Challenges: ${result.project_snapshot.active_challenges?.join(', ') || 'None'}
|
|
204
|
+
- Next Steps: ${result.project_snapshot.next_steps?.join(', ') || 'None'}
|
|
205
|
+
` : 'No snapshot provided'}
|
|
206
|
+
|
|
207
|
+
### New Memories (${result.memories.length})
|
|
208
|
+
${result.memories.map((m, i) => `
|
|
209
|
+
#### Memory ${i + 1}
|
|
210
|
+
- **Content:** ${m.content}
|
|
211
|
+
- **Type:** ${m.context_type}
|
|
212
|
+
- **Scope:** ${m.scope || 'project'}
|
|
213
|
+
- **Domain:** ${m.domain || 'N/A'}
|
|
214
|
+
- **Importance:** ${m.importance_weight}
|
|
215
|
+
- **Tags:** ${m.semantic_tags?.join(', ') || 'None'}
|
|
216
|
+
`).join('\n')}
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
Please process these memories according to your management procedure. Use the exact storage paths provided above to read and write memory files. Update, supersede, or link existing memories as needed. Update the personal primer if any personal memories warrant it.`
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Parse management response from Claude
|
|
225
|
+
*/
|
|
226
|
+
parseManagementResponse(responseJson: string): ManagementResult {
|
|
227
|
+
const emptyResult = (error?: string): ManagementResult => ({
|
|
228
|
+
success: !error,
|
|
229
|
+
superseded: 0,
|
|
230
|
+
resolved: 0,
|
|
231
|
+
linked: 0,
|
|
232
|
+
filesRead: 0,
|
|
233
|
+
filesWritten: 0,
|
|
234
|
+
primerUpdated: false,
|
|
235
|
+
actions: [],
|
|
236
|
+
summary: error ? '' : 'No actions taken',
|
|
237
|
+
fullReport: error ? `Error: ${error}` : '',
|
|
238
|
+
error,
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
// First, parse the CLI JSON wrapper
|
|
243
|
+
const cliOutput = JSON.parse(responseJson)
|
|
244
|
+
|
|
245
|
+
// Check for error response
|
|
246
|
+
if (cliOutput.type === 'error' || cliOutput.is_error === true) {
|
|
247
|
+
return emptyResult(cliOutput.error || 'Unknown error')
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Extract the "result" field (AI's response text)
|
|
251
|
+
const resultText = typeof cliOutput.result === 'string' ? cliOutput.result : ''
|
|
252
|
+
|
|
253
|
+
// Extract the full report (everything from === MANAGEMENT ACTIONS === onwards)
|
|
254
|
+
const reportMatch = resultText.match(/(=== MANAGEMENT ACTIONS ===[\s\S]*)/)
|
|
255
|
+
const fullReport = reportMatch ? reportMatch[1].trim() : resultText
|
|
256
|
+
|
|
257
|
+
// Extract actions section
|
|
258
|
+
const actionsMatch = resultText.match(/=== MANAGEMENT ACTIONS ===([\s\S]*?)(?:=== SUMMARY ===|$)/)
|
|
259
|
+
const actions: string[] = []
|
|
260
|
+
if (actionsMatch) {
|
|
261
|
+
const actionsText = actionsMatch[1]
|
|
262
|
+
// Extract lines that look like actions (TYPE: description or TYPE OK/FAILED: path)
|
|
263
|
+
// Note: RECEIVED replaced CREATED - manager receives new memories from curator, doesn't create them
|
|
264
|
+
const actionLines = actionsText.split('\n')
|
|
265
|
+
.map((line: string) => line.trim())
|
|
266
|
+
.filter((line: string) => /^(READ|WRITE|RECEIVED|CREATED|UPDATED|SUPERSEDED|RESOLVED|LINKED|PRIMER|SKIPPED|NO_ACTION)/.test(line))
|
|
267
|
+
actions.push(...actionLines)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Extract stats from result text
|
|
271
|
+
const supersededMatch = resultText.match(/memories_superseded[:\s]+(\d+)/i) || resultText.match(/superseded[:\s]+(\d+)/i)
|
|
272
|
+
const resolvedMatch = resultText.match(/memories_resolved[:\s]+(\d+)/i) || resultText.match(/resolved[:\s]+(\d+)/i)
|
|
273
|
+
const linkedMatch = resultText.match(/memories_linked[:\s]+(\d+)/i) || resultText.match(/linked[:\s]+(\d+)/i)
|
|
274
|
+
const filesReadMatch = resultText.match(/files_read[:\s]+(\d+)/i)
|
|
275
|
+
const filesWrittenMatch = resultText.match(/files_written[:\s]+(\d+)/i)
|
|
276
|
+
const primerUpdated = /primer_updated[:\s]+true/i.test(resultText) || /PRIMER\s+OK/i.test(resultText)
|
|
277
|
+
|
|
278
|
+
// Count file operations from actions if not in summary
|
|
279
|
+
const readActions = actions.filter((a: string) => a.startsWith('READ OK')).length
|
|
280
|
+
const writeActions = actions.filter((a: string) => a.startsWith('WRITE OK')).length
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
success: true,
|
|
284
|
+
superseded: supersededMatch ? parseInt(supersededMatch[1]) : 0,
|
|
285
|
+
resolved: resolvedMatch ? parseInt(resolvedMatch[1]) : 0,
|
|
286
|
+
linked: linkedMatch ? parseInt(linkedMatch[1]) : 0,
|
|
287
|
+
filesRead: filesReadMatch ? parseInt(filesReadMatch[1]) : readActions,
|
|
288
|
+
filesWritten: filesWrittenMatch ? parseInt(filesWrittenMatch[1]) : writeActions,
|
|
289
|
+
primerUpdated,
|
|
290
|
+
actions,
|
|
291
|
+
summary: resultText.slice(0, 500), // Brief summary for storage
|
|
292
|
+
fullReport, // Complete report for logging
|
|
293
|
+
}
|
|
294
|
+
} catch {
|
|
295
|
+
return emptyResult('Failed to parse response')
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Build a temporary settings file with path-based permissions
|
|
301
|
+
*
|
|
302
|
+
* Claude CLI supports path restrictions via settings.json permissions, NOT via --allowedTools.
|
|
303
|
+
* Syntax: Read(//absolute/path/**) where // means absolute path
|
|
304
|
+
*
|
|
305
|
+
* This provides real security - the agent can ONLY access memory storage paths.
|
|
306
|
+
*/
|
|
307
|
+
private async _buildSettingsFile(storagePaths?: StoragePaths): Promise<string> {
|
|
308
|
+
// Build allow list with path restrictions
|
|
309
|
+
const allowRules: string[] = []
|
|
310
|
+
|
|
311
|
+
// Global path - always central at ~/.local/share/memory/global
|
|
312
|
+
const globalPath = storagePaths?.globalPath
|
|
313
|
+
?? join(homedir(), '.local', 'share', 'memory', 'global')
|
|
314
|
+
|
|
315
|
+
// Project path - depends on storage mode
|
|
316
|
+
const projectPath = storagePaths?.projectPath
|
|
317
|
+
?? join(homedir(), '.local', 'share', 'memory')
|
|
318
|
+
|
|
319
|
+
// Glob and Grep - tool names only (no path syntax in Claude CLI for these)
|
|
320
|
+
allowRules.push('Glob')
|
|
321
|
+
allowRules.push('Grep')
|
|
322
|
+
|
|
323
|
+
// Helper to format path for Claude CLI permissions
|
|
324
|
+
// // means "absolute path from filesystem root"
|
|
325
|
+
// If path already starts with /, replace it with //
|
|
326
|
+
const formatPath = (p: string) => p.startsWith('/') ? '/' + p : '//' + p
|
|
327
|
+
|
|
328
|
+
// Read, Write, Edit - use path patterns with // for absolute paths
|
|
329
|
+
// Global path (always ~/.local/share/memory/global/)
|
|
330
|
+
allowRules.push(`Read(${formatPath(globalPath)}/**)`)
|
|
331
|
+
allowRules.push(`Write(${formatPath(globalPath)}/**)`)
|
|
332
|
+
allowRules.push(`Edit(${formatPath(globalPath)}/**)`)
|
|
333
|
+
|
|
334
|
+
// Project path (always different from global, even in central mode)
|
|
335
|
+
// Central: ~/.local/share/memory/{project_id}/
|
|
336
|
+
// Local: {project_path}/.memory/{project_id}/
|
|
337
|
+
allowRules.push(`Read(${formatPath(projectPath)}/**)`)
|
|
338
|
+
allowRules.push(`Write(${formatPath(projectPath)}/**)`)
|
|
339
|
+
allowRules.push(`Edit(${formatPath(projectPath)}/**)`)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
const settings = {
|
|
343
|
+
permissions: {
|
|
344
|
+
allow: allowRules,
|
|
345
|
+
deny: [
|
|
346
|
+
// Explicitly deny sensitive paths for Read/Write/Edit
|
|
347
|
+
'Read(/etc/**)',
|
|
348
|
+
'Read(~/.ssh/**)',
|
|
349
|
+
'Read(~/.aws/**)',
|
|
350
|
+
'Read(~/.gnupg/**)',
|
|
351
|
+
'Read(.env)',
|
|
352
|
+
'Read(.env.*)',
|
|
353
|
+
]
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Write to temp file
|
|
358
|
+
const tempPath = join(homedir(), '.local', 'share', 'memory', '.manager-settings.json')
|
|
359
|
+
await Bun.write(tempPath, JSON.stringify(settings, null, 2))
|
|
360
|
+
return tempPath
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Manage using CLI subprocess
|
|
365
|
+
* Similar to Curator.curateWithCLI
|
|
366
|
+
*/
|
|
367
|
+
async manageWithCLI(
|
|
368
|
+
projectId: string,
|
|
369
|
+
sessionNumber: number,
|
|
370
|
+
result: CurationResult,
|
|
371
|
+
storagePaths?: StoragePaths
|
|
372
|
+
): Promise<ManagementResult> {
|
|
373
|
+
// Skip if disabled via config or env var
|
|
374
|
+
if (!this._config.enabled || process.env.MEMORY_MANAGER_DISABLED === '1') {
|
|
375
|
+
return {
|
|
376
|
+
success: true,
|
|
377
|
+
superseded: 0,
|
|
378
|
+
resolved: 0,
|
|
379
|
+
linked: 0,
|
|
380
|
+
filesRead: 0,
|
|
381
|
+
filesWritten: 0,
|
|
382
|
+
primerUpdated: false,
|
|
383
|
+
actions: [],
|
|
384
|
+
summary: 'Management agent disabled',
|
|
385
|
+
fullReport: 'Management agent disabled via configuration',
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Skip if no memories
|
|
390
|
+
if (result.memories.length === 0) {
|
|
391
|
+
return {
|
|
392
|
+
success: true,
|
|
393
|
+
superseded: 0,
|
|
394
|
+
resolved: 0,
|
|
395
|
+
linked: 0,
|
|
396
|
+
filesRead: 0,
|
|
397
|
+
filesWritten: 0,
|
|
398
|
+
primerUpdated: false,
|
|
399
|
+
actions: [],
|
|
400
|
+
summary: 'No memories to process',
|
|
401
|
+
fullReport: 'No memories to process - skipped',
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Load skill file
|
|
406
|
+
const systemPrompt = await this.buildManagementPrompt()
|
|
407
|
+
if (!systemPrompt) {
|
|
408
|
+
return {
|
|
409
|
+
success: false,
|
|
410
|
+
superseded: 0,
|
|
411
|
+
resolved: 0,
|
|
412
|
+
linked: 0,
|
|
413
|
+
filesRead: 0,
|
|
414
|
+
filesWritten: 0,
|
|
415
|
+
primerUpdated: false,
|
|
416
|
+
actions: [],
|
|
417
|
+
summary: '',
|
|
418
|
+
fullReport: 'Error: Management skill file not found',
|
|
419
|
+
error: 'Management skill not found',
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const userMessage = this.buildUserMessage(projectId, sessionNumber, result, storagePaths)
|
|
424
|
+
|
|
425
|
+
// Build settings file with path-based permissions
|
|
426
|
+
// This provides real security - the agent can ONLY access memory storage paths
|
|
427
|
+
const settingsPath = await this._buildSettingsFile(storagePaths)
|
|
428
|
+
|
|
429
|
+
// Build CLI command with settings file for path restrictions
|
|
430
|
+
const args = [
|
|
431
|
+
'-p', userMessage,
|
|
432
|
+
'--append-system-prompt', systemPrompt,
|
|
433
|
+
'--output-format', 'json',
|
|
434
|
+
'--settings', settingsPath,
|
|
435
|
+
]
|
|
436
|
+
|
|
437
|
+
// Add max-turns only if configured (undefined = unlimited)
|
|
438
|
+
if (this._config.maxTurns !== undefined) {
|
|
439
|
+
args.push('--max-turns', String(this._config.maxTurns))
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Execute CLI
|
|
443
|
+
const proc = Bun.spawn([this._config.cliCommand, ...args], {
|
|
444
|
+
env: {
|
|
445
|
+
...process.env,
|
|
446
|
+
MEMORY_CURATOR_ACTIVE: '1', // Prevent recursive hook triggering
|
|
447
|
+
},
|
|
448
|
+
stderr: 'pipe',
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
// Capture output
|
|
452
|
+
const [stdout, stderr] = await Promise.all([
|
|
453
|
+
new Response(proc.stdout).text(),
|
|
454
|
+
new Response(proc.stderr).text(),
|
|
455
|
+
])
|
|
456
|
+
const exitCode = await proc.exited
|
|
457
|
+
|
|
458
|
+
if (exitCode !== 0) {
|
|
459
|
+
const errorMsg = stderr || `Exit code ${exitCode}`
|
|
460
|
+
return {
|
|
461
|
+
success: false,
|
|
462
|
+
superseded: 0,
|
|
463
|
+
resolved: 0,
|
|
464
|
+
linked: 0,
|
|
465
|
+
filesRead: 0,
|
|
466
|
+
filesWritten: 0,
|
|
467
|
+
primerUpdated: false,
|
|
468
|
+
actions: [],
|
|
469
|
+
summary: '',
|
|
470
|
+
fullReport: `Error: CLI failed with exit code ${exitCode}\n${stderr}`,
|
|
471
|
+
error: errorMsg,
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return this.parseManagementResponse(stdout)
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Create a new manager
|
|
481
|
+
*/
|
|
482
|
+
export function createManager(config?: ManagerConfig): Manager {
|
|
483
|
+
return new Manager(config)
|
|
484
|
+
}
|