@rlabs-inc/memory 0.5.3 → 0.5.8
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/dist/index.js +5115 -41504
- package/dist/index.mjs +1425 -10549
- package/dist/server/index.js +3779 -12801
- package/dist/server/index.mjs +3474 -12498
- package/package.json +1 -1
- package/src/core/curator.ts +140 -448
- package/src/core/manager.ts +321 -546
- package/src/server/index.ts +2 -1
- package/src/utils/paths.ts +191 -0
package/src/core/manager.ts
CHANGED
|
@@ -4,34 +4,19 @@
|
|
|
4
4
|
// Mirrors Curator pattern exactly
|
|
5
5
|
// ============================================================================
|
|
6
6
|
|
|
7
|
-
import { join } from
|
|
8
|
-
import { homedir } from
|
|
9
|
-
import { existsSync } from
|
|
10
|
-
import type { CurationResult } from
|
|
11
|
-
import { logger } from
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (envCommand) return envCommand
|
|
21
|
-
|
|
22
|
-
// 2. Use `which` to find claude in PATH (universal - works with native, homebrew, npm, etc.)
|
|
23
|
-
const result = Bun.spawnSync(['which', 'claude'])
|
|
24
|
-
if (result.exitCode === 0) {
|
|
25
|
-
return result.stdout.toString().trim()
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// 3. Legacy fallback - hardcoded native install path
|
|
29
|
-
const claudeLocal = join(homedir(), '.claude', 'local', 'claude')
|
|
30
|
-
if (existsSync(claudeLocal)) return claudeLocal
|
|
31
|
-
|
|
32
|
-
// 4. Last resort
|
|
33
|
-
return 'claude'
|
|
34
|
-
}
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { existsSync } from "fs";
|
|
10
|
+
import type { CurationResult } from "../types/memory.ts";
|
|
11
|
+
import { logger } from "../utils/logger.ts";
|
|
12
|
+
import {
|
|
13
|
+
getClaudeCommand,
|
|
14
|
+
getManagerPromptPath,
|
|
15
|
+
getManagerCwd,
|
|
16
|
+
getCentralStoragePath,
|
|
17
|
+
getStorageMode,
|
|
18
|
+
type StoragePaths,
|
|
19
|
+
} from "../utils/paths.ts";
|
|
35
20
|
|
|
36
21
|
/**
|
|
37
22
|
* Manager configuration
|
|
@@ -42,80 +27,40 @@ export interface ManagerConfig {
|
|
|
42
27
|
* When disabled, memories are stored but not organized/linked
|
|
43
28
|
* Default: true
|
|
44
29
|
*/
|
|
45
|
-
enabled?: boolean
|
|
30
|
+
enabled?: boolean;
|
|
46
31
|
|
|
47
32
|
/**
|
|
48
33
|
* CLI command to use (for subprocess mode)
|
|
49
34
|
* Default: auto-detected (~/.claude/local/claude or 'claude')
|
|
50
35
|
*/
|
|
51
|
-
cliCommand?: string
|
|
36
|
+
cliCommand?: string;
|
|
52
37
|
|
|
53
38
|
/**
|
|
54
39
|
* Maximum turns for the management agent
|
|
55
40
|
* Set to undefined for unlimited turns
|
|
56
41
|
* Default: undefined (unlimited)
|
|
57
42
|
*/
|
|
58
|
-
maxTurns?: number
|
|
43
|
+
maxTurns?: number;
|
|
59
44
|
}
|
|
60
45
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* These are resolved at runtime from the server's actual configuration
|
|
64
|
-
*/
|
|
65
|
-
export interface StoragePaths {
|
|
66
|
-
/**
|
|
67
|
-
* Root path to project storage directory (NOT the memories subdirectory)
|
|
68
|
-
* e.g., ~/.local/share/memory/memory-ts/ (central)
|
|
69
|
-
* or /path/to/project/.memory/ (local)
|
|
70
|
-
*/
|
|
71
|
-
projectPath: string
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Root path to global storage directory (NOT the memories subdirectory)
|
|
75
|
-
* Always ~/.local/share/memory/global/
|
|
76
|
-
*/
|
|
77
|
-
globalPath: string
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Full path to project memories directory
|
|
81
|
-
* e.g., ~/.local/share/memory/memory-ts/memories/ (central)
|
|
82
|
-
* or /path/to/project/.memory/memories/ (local)
|
|
83
|
-
*/
|
|
84
|
-
projectMemoriesPath: string
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Full path to global memories directory
|
|
88
|
-
* Always ~/.local/share/memory/global/memories/
|
|
89
|
-
*/
|
|
90
|
-
globalMemoriesPath: string
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Full path to personal primer file
|
|
94
|
-
* Always ~/.local/share/memory/global/memories/personal-primer.md
|
|
95
|
-
*/
|
|
96
|
-
personalPrimerPath: string
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Storage mode for context
|
|
100
|
-
*/
|
|
101
|
-
storageMode: 'central' | 'local'
|
|
102
|
-
}
|
|
46
|
+
// Re-export StoragePaths for backwards compatibility
|
|
47
|
+
export type { StoragePaths } from "../utils/paths.ts";
|
|
103
48
|
|
|
104
49
|
/**
|
|
105
50
|
* Management result - what the agent did
|
|
106
51
|
*/
|
|
107
52
|
export interface ManagementResult {
|
|
108
|
-
success: boolean
|
|
109
|
-
superseded: number
|
|
110
|
-
resolved: number
|
|
111
|
-
linked: number
|
|
112
|
-
filesRead: number
|
|
113
|
-
filesWritten: number
|
|
114
|
-
primerUpdated: boolean
|
|
115
|
-
actions: string[]
|
|
116
|
-
summary: string
|
|
117
|
-
fullReport: string // Complete management report (ACTIONS + SUMMARY sections)
|
|
118
|
-
error?: string
|
|
53
|
+
success: boolean;
|
|
54
|
+
superseded: number;
|
|
55
|
+
resolved: number;
|
|
56
|
+
linked: number;
|
|
57
|
+
filesRead: number;
|
|
58
|
+
filesWritten: number;
|
|
59
|
+
primerUpdated: boolean;
|
|
60
|
+
actions: string[]; // Detailed action log lines
|
|
61
|
+
summary: string; // Brief summary for storage
|
|
62
|
+
fullReport: string; // Complete management report (ACTIONS + SUMMARY sections)
|
|
63
|
+
error?: string;
|
|
119
64
|
}
|
|
120
65
|
|
|
121
66
|
/**
|
|
@@ -124,17 +69,17 @@ export interface ManagementResult {
|
|
|
124
69
|
*/
|
|
125
70
|
export class Manager {
|
|
126
71
|
private _config: {
|
|
127
|
-
enabled: boolean
|
|
128
|
-
cliCommand: string
|
|
129
|
-
maxTurns?: number
|
|
130
|
-
}
|
|
72
|
+
enabled: boolean;
|
|
73
|
+
cliCommand: string;
|
|
74
|
+
maxTurns?: number; // undefined = unlimited
|
|
75
|
+
};
|
|
131
76
|
|
|
132
77
|
constructor(config: ManagerConfig = {}) {
|
|
133
78
|
this._config = {
|
|
134
79
|
enabled: config.enabled ?? true,
|
|
135
80
|
cliCommand: config.cliCommand ?? getClaudeCommand(),
|
|
136
|
-
maxTurns: config.maxTurns,
|
|
137
|
-
}
|
|
81
|
+
maxTurns: config.maxTurns, // undefined = unlimited turns
|
|
82
|
+
};
|
|
138
83
|
}
|
|
139
84
|
|
|
140
85
|
/**
|
|
@@ -144,25 +89,34 @@ export class Manager {
|
|
|
144
89
|
async buildManagementPrompt(): Promise<string | null> {
|
|
145
90
|
const skillPaths = [
|
|
146
91
|
// Development - relative to src/core
|
|
147
|
-
join(import.meta.dir,
|
|
92
|
+
join(import.meta.dir, "../../skills/memory-management.md"),
|
|
148
93
|
// Installed via bun global
|
|
149
|
-
join(
|
|
94
|
+
join(
|
|
95
|
+
homedir(),
|
|
96
|
+
".bun/install/global/node_modules/@rlabs-inc/memory/skills/memory-management.md",
|
|
97
|
+
),
|
|
150
98
|
// Installed via npm global
|
|
151
|
-
join(
|
|
99
|
+
join(
|
|
100
|
+
homedir(),
|
|
101
|
+
".npm/global/node_modules/@rlabs-inc/memory/skills/memory-management.md",
|
|
102
|
+
),
|
|
152
103
|
// Local node_modules
|
|
153
|
-
join(
|
|
154
|
-
|
|
104
|
+
join(
|
|
105
|
+
process.cwd(),
|
|
106
|
+
"node_modules/@rlabs-inc/memory/skills/memory-management.md",
|
|
107
|
+
),
|
|
108
|
+
];
|
|
155
109
|
|
|
156
110
|
for (const path of skillPaths) {
|
|
157
111
|
try {
|
|
158
|
-
const content = await Bun.file(path).text()
|
|
159
|
-
if (content) return content
|
|
112
|
+
const content = await Bun.file(path).text();
|
|
113
|
+
if (content) return content;
|
|
160
114
|
} catch {
|
|
161
|
-
continue
|
|
115
|
+
continue;
|
|
162
116
|
}
|
|
163
117
|
}
|
|
164
118
|
|
|
165
|
-
return null
|
|
119
|
+
return null;
|
|
166
120
|
}
|
|
167
121
|
|
|
168
122
|
/**
|
|
@@ -173,13 +127,14 @@ export class Manager {
|
|
|
173
127
|
projectId: string,
|
|
174
128
|
sessionNumber: number,
|
|
175
129
|
result: CurationResult,
|
|
176
|
-
storagePaths?: StoragePaths
|
|
130
|
+
storagePaths?: StoragePaths,
|
|
177
131
|
): string {
|
|
178
|
-
const today = new Date().toISOString().split(
|
|
132
|
+
const today = new Date().toISOString().split("T")[0];
|
|
179
133
|
|
|
180
134
|
// Build storage paths section if provided
|
|
181
135
|
// Includes both root paths (for permissions context) and memories paths (for file operations)
|
|
182
|
-
const pathsSection = storagePaths
|
|
136
|
+
const pathsSection = storagePaths
|
|
137
|
+
? `
|
|
183
138
|
## Storage Paths (ACTUAL - use these exact paths)
|
|
184
139
|
|
|
185
140
|
**Storage Mode:** ${storagePaths.storageMode}
|
|
@@ -195,7 +150,8 @@ export class Manager {
|
|
|
195
150
|
|
|
196
151
|
> ⚠️ These paths are resolved from the running server configuration. Use them exactly as provided.
|
|
197
152
|
> Memories are stored as individual markdown files in the memories directories.
|
|
198
|
-
`
|
|
153
|
+
`
|
|
154
|
+
: "";
|
|
199
155
|
|
|
200
156
|
return `## Curation Data
|
|
201
157
|
|
|
@@ -204,183 +160,38 @@ export class Manager {
|
|
|
204
160
|
**Date:** ${today}
|
|
205
161
|
${pathsSection}
|
|
206
162
|
### Session Summary
|
|
207
|
-
${result.session_summary ||
|
|
163
|
+
${result.session_summary || "No summary provided"}
|
|
208
164
|
|
|
209
165
|
### Project Snapshot
|
|
210
|
-
${
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
-
|
|
214
|
-
-
|
|
215
|
-
|
|
166
|
+
${
|
|
167
|
+
result.project_snapshot
|
|
168
|
+
? `
|
|
169
|
+
- Current Phase: ${result.project_snapshot.current_phase || "N/A"}
|
|
170
|
+
- Recent Achievements: ${result.project_snapshot.recent_achievements?.join(", ") || "None"}
|
|
171
|
+
- Active Challenges: ${result.project_snapshot.active_challenges?.join(", ") || "None"}
|
|
172
|
+
- Next Steps: ${result.project_snapshot.next_steps?.join(", ") || "None"}
|
|
173
|
+
`
|
|
174
|
+
: "No snapshot provided"
|
|
175
|
+
}
|
|
216
176
|
|
|
217
177
|
### New Memories (${result.memories.length})
|
|
218
|
-
${result.memories
|
|
178
|
+
${result.memories
|
|
179
|
+
.map(
|
|
180
|
+
(m, i) => `
|
|
219
181
|
#### Memory ${i + 1}
|
|
220
182
|
- **Content:** ${m.content}
|
|
221
183
|
- **Type:** ${m.context_type}
|
|
222
|
-
- **Scope:** ${m.scope ||
|
|
223
|
-
- **Domain:** ${m.domain ||
|
|
184
|
+
- **Scope:** ${m.scope || "project"}
|
|
185
|
+
- **Domain:** ${m.domain || "N/A"}
|
|
224
186
|
- **Importance:** ${m.importance_weight}
|
|
225
|
-
- **Tags:** ${m.semantic_tags?.join(
|
|
226
|
-
|
|
187
|
+
- **Tags:** ${m.semantic_tags?.join(", ") || "None"}
|
|
188
|
+
`,
|
|
189
|
+
)
|
|
190
|
+
.join("\n")}
|
|
227
191
|
|
|
228
192
|
---
|
|
229
193
|
|
|
230
|
-
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
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Parse management response from Claude
|
|
235
|
-
*/
|
|
236
|
-
parseManagementResponse(responseJson: string): ManagementResult {
|
|
237
|
-
const emptyResult = (error?: string): ManagementResult => ({
|
|
238
|
-
success: !error,
|
|
239
|
-
superseded: 0,
|
|
240
|
-
resolved: 0,
|
|
241
|
-
linked: 0,
|
|
242
|
-
filesRead: 0,
|
|
243
|
-
filesWritten: 0,
|
|
244
|
-
primerUpdated: false,
|
|
245
|
-
actions: [],
|
|
246
|
-
summary: error ? '' : 'No actions taken',
|
|
247
|
-
fullReport: error ? `Error: ${error}` : '',
|
|
248
|
-
error,
|
|
249
|
-
})
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
// First, parse the CLI JSON wrapper
|
|
253
|
-
const cliOutput = JSON.parse(responseJson)
|
|
254
|
-
|
|
255
|
-
// Claude Code now returns an array of events - find the result object
|
|
256
|
-
let resultObj: any
|
|
257
|
-
if (Array.isArray(cliOutput)) {
|
|
258
|
-
// New format: array of events, find the one with type="result"
|
|
259
|
-
resultObj = cliOutput.find((item: any) => item.type === 'result')
|
|
260
|
-
if (!resultObj) {
|
|
261
|
-
return emptyResult('No result found in response')
|
|
262
|
-
}
|
|
263
|
-
} else {
|
|
264
|
-
// Old format: single object (backwards compatibility)
|
|
265
|
-
resultObj = cliOutput
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Check for error response
|
|
269
|
-
if (resultObj.type === 'error' || resultObj.is_error === true) {
|
|
270
|
-
return emptyResult(resultObj.error || 'Unknown error')
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Extract the "result" field (AI's response text)
|
|
274
|
-
const resultText = typeof resultObj.result === 'string' ? resultObj.result : ''
|
|
275
|
-
|
|
276
|
-
// Extract the full report (everything from === MANAGEMENT ACTIONS === onwards)
|
|
277
|
-
const reportMatch = resultText.match(/(=== MANAGEMENT ACTIONS ===[\s\S]*)/)
|
|
278
|
-
const fullReport = reportMatch ? reportMatch[1].trim() : resultText
|
|
279
|
-
|
|
280
|
-
// Extract actions section
|
|
281
|
-
const actionsMatch = resultText.match(/=== MANAGEMENT ACTIONS ===([\s\S]*?)(?:=== SUMMARY ===|$)/)
|
|
282
|
-
const actions: string[] = []
|
|
283
|
-
if (actionsMatch) {
|
|
284
|
-
const actionsText = actionsMatch[1]
|
|
285
|
-
// Extract lines that look like actions (TYPE: description or TYPE OK/FAILED: path)
|
|
286
|
-
// Note: RECEIVED replaced CREATED - manager receives new memories from curator, doesn't create them
|
|
287
|
-
const actionLines = actionsText.split('\n')
|
|
288
|
-
.map((line: string) => line.trim())
|
|
289
|
-
.filter((line: string) => /^(READ|WRITE|RECEIVED|CREATED|UPDATED|SUPERSEDED|RESOLVED|LINKED|PRIMER|SKIPPED|NO_ACTION)/.test(line))
|
|
290
|
-
actions.push(...actionLines)
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Extract stats from result text
|
|
294
|
-
const supersededMatch = resultText.match(/memories_superseded[:\s]+(\d+)/i) || resultText.match(/superseded[:\s]+(\d+)/i)
|
|
295
|
-
const resolvedMatch = resultText.match(/memories_resolved[:\s]+(\d+)/i) || resultText.match(/resolved[:\s]+(\d+)/i)
|
|
296
|
-
const linkedMatch = resultText.match(/memories_linked[:\s]+(\d+)/i) || resultText.match(/linked[:\s]+(\d+)/i)
|
|
297
|
-
const filesReadMatch = resultText.match(/files_read[:\s]+(\d+)/i)
|
|
298
|
-
const filesWrittenMatch = resultText.match(/files_written[:\s]+(\d+)/i)
|
|
299
|
-
const primerUpdated = /primer_updated[:\s]+true/i.test(resultText) || /PRIMER\s+OK/i.test(resultText)
|
|
300
|
-
|
|
301
|
-
// Count file operations from actions if not in summary
|
|
302
|
-
const readActions = actions.filter((a: string) => a.startsWith('READ OK')).length
|
|
303
|
-
const writeActions = actions.filter((a: string) => a.startsWith('WRITE OK')).length
|
|
304
|
-
|
|
305
|
-
return {
|
|
306
|
-
success: true,
|
|
307
|
-
superseded: supersededMatch ? parseInt(supersededMatch[1]) : 0,
|
|
308
|
-
resolved: resolvedMatch ? parseInt(resolvedMatch[1]) : 0,
|
|
309
|
-
linked: linkedMatch ? parseInt(linkedMatch[1]) : 0,
|
|
310
|
-
filesRead: filesReadMatch ? parseInt(filesReadMatch[1]) : readActions,
|
|
311
|
-
filesWritten: filesWrittenMatch ? parseInt(filesWrittenMatch[1]) : writeActions,
|
|
312
|
-
primerUpdated,
|
|
313
|
-
actions,
|
|
314
|
-
summary: resultText.slice(0, 500), // Brief summary for storage
|
|
315
|
-
fullReport, // Complete report for logging
|
|
316
|
-
}
|
|
317
|
-
} catch {
|
|
318
|
-
return emptyResult('Failed to parse response')
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Build a temporary settings file with path-based permissions
|
|
324
|
-
*
|
|
325
|
-
* Claude CLI supports path restrictions via settings.json permissions, NOT via --allowedTools.
|
|
326
|
-
* Syntax: Read(//absolute/path/**) where // means absolute path
|
|
327
|
-
*
|
|
328
|
-
* This provides real security - the agent can ONLY access memory storage paths.
|
|
329
|
-
*/
|
|
330
|
-
private async _buildSettingsFile(storagePaths?: StoragePaths): Promise<string> {
|
|
331
|
-
// Build allow list with path restrictions
|
|
332
|
-
const allowRules: string[] = []
|
|
333
|
-
|
|
334
|
-
// Global path - always central at ~/.local/share/memory/global
|
|
335
|
-
const globalPath = storagePaths?.globalPath
|
|
336
|
-
?? join(homedir(), '.local', 'share', 'memory', 'global')
|
|
337
|
-
|
|
338
|
-
// Project path - depends on storage mode
|
|
339
|
-
const projectPath = storagePaths?.projectPath
|
|
340
|
-
?? join(homedir(), '.local', 'share', 'memory')
|
|
341
|
-
|
|
342
|
-
// Glob and Grep - tool names only (no path syntax in Claude CLI for these)
|
|
343
|
-
allowRules.push('Glob')
|
|
344
|
-
allowRules.push('Grep')
|
|
345
|
-
|
|
346
|
-
// Helper to format path for Claude CLI permissions
|
|
347
|
-
// // means "absolute path from filesystem root"
|
|
348
|
-
// If path already starts with /, replace it with //
|
|
349
|
-
const formatPath = (p: string) => p.startsWith('/') ? '/' + p : '//' + p
|
|
350
|
-
|
|
351
|
-
// Read, Write, Edit - use path patterns with // for absolute paths
|
|
352
|
-
// Global path (always ~/.local/share/memory/global/)
|
|
353
|
-
allowRules.push(`Read(${formatPath(globalPath)}/**)`)
|
|
354
|
-
allowRules.push(`Write(${formatPath(globalPath)}/**)`)
|
|
355
|
-
allowRules.push(`Edit(${formatPath(globalPath)}/**)`)
|
|
356
|
-
|
|
357
|
-
// Project path (always different from global, even in central mode)
|
|
358
|
-
// Central: ~/.local/share/memory/{project_id}/
|
|
359
|
-
// Local: {project_path}/.memory/{project_id}/
|
|
360
|
-
allowRules.push(`Read(${formatPath(projectPath)}/**)`)
|
|
361
|
-
allowRules.push(`Write(${formatPath(projectPath)}/**)`)
|
|
362
|
-
allowRules.push(`Edit(${formatPath(projectPath)}/**)`)
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const settings = {
|
|
366
|
-
permissions: {
|
|
367
|
-
allow: allowRules,
|
|
368
|
-
deny: [
|
|
369
|
-
// Explicitly deny sensitive paths for Read/Write/Edit
|
|
370
|
-
'Read(/etc/**)',
|
|
371
|
-
'Read(~/.ssh/**)',
|
|
372
|
-
'Read(~/.aws/**)',
|
|
373
|
-
'Read(~/.gnupg/**)',
|
|
374
|
-
'Read(.env)',
|
|
375
|
-
'Read(.env.*)',
|
|
376
|
-
]
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Write to temp file
|
|
381
|
-
const tempPath = join(homedir(), '.local', 'share', 'memory', '.manager-settings.json')
|
|
382
|
-
await Bun.write(tempPath, JSON.stringify(settings, null, 2))
|
|
383
|
-
return tempPath
|
|
194
|
+
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.`;
|
|
384
195
|
}
|
|
385
196
|
|
|
386
197
|
/**
|
|
@@ -391,10 +202,10 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
391
202
|
projectId: string,
|
|
392
203
|
sessionNumber: number,
|
|
393
204
|
result: CurationResult,
|
|
394
|
-
storagePaths?: StoragePaths
|
|
205
|
+
storagePaths?: StoragePaths,
|
|
395
206
|
): Promise<ManagementResult> {
|
|
396
207
|
// Skip if disabled via config or env var
|
|
397
|
-
if (!this._config.enabled || process.env.MEMORY_MANAGER_DISABLED ===
|
|
208
|
+
if (!this._config.enabled || process.env.MEMORY_MANAGER_DISABLED === "1") {
|
|
398
209
|
return {
|
|
399
210
|
success: true,
|
|
400
211
|
superseded: 0,
|
|
@@ -404,9 +215,9 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
404
215
|
filesWritten: 0,
|
|
405
216
|
primerUpdated: false,
|
|
406
217
|
actions: [],
|
|
407
|
-
summary:
|
|
408
|
-
fullReport:
|
|
409
|
-
}
|
|
218
|
+
summary: "Management agent disabled",
|
|
219
|
+
fullReport: "Management agent disabled via configuration",
|
|
220
|
+
};
|
|
410
221
|
}
|
|
411
222
|
|
|
412
223
|
// Skip if no memories
|
|
@@ -420,13 +231,13 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
420
231
|
filesWritten: 0,
|
|
421
232
|
primerUpdated: false,
|
|
422
233
|
actions: [],
|
|
423
|
-
summary:
|
|
424
|
-
fullReport:
|
|
425
|
-
}
|
|
234
|
+
summary: "No memories to process",
|
|
235
|
+
fullReport: "No memories to process - skipped",
|
|
236
|
+
};
|
|
426
237
|
}
|
|
427
238
|
|
|
428
239
|
// Load skill file
|
|
429
|
-
const systemPrompt = await this.buildManagementPrompt()
|
|
240
|
+
const systemPrompt = await this.buildManagementPrompt();
|
|
430
241
|
if (!systemPrompt) {
|
|
431
242
|
return {
|
|
432
243
|
success: false,
|
|
@@ -437,44 +248,50 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
437
248
|
filesWritten: 0,
|
|
438
249
|
primerUpdated: false,
|
|
439
250
|
actions: [],
|
|
440
|
-
summary:
|
|
441
|
-
fullReport:
|
|
442
|
-
error:
|
|
443
|
-
}
|
|
251
|
+
summary: "",
|
|
252
|
+
fullReport: "Error: Management skill file not found",
|
|
253
|
+
error: "Management skill not found",
|
|
254
|
+
};
|
|
444
255
|
}
|
|
445
256
|
|
|
446
|
-
const userMessage = this.buildUserMessage(
|
|
257
|
+
const userMessage = this.buildUserMessage(
|
|
258
|
+
projectId,
|
|
259
|
+
sessionNumber,
|
|
260
|
+
result,
|
|
261
|
+
storagePaths,
|
|
262
|
+
);
|
|
447
263
|
|
|
448
264
|
try {
|
|
449
265
|
// Dynamic import to make Agent SDK optional
|
|
450
|
-
const { query } = await import(
|
|
266
|
+
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
451
267
|
|
|
452
268
|
// Build allowed directories for file access
|
|
453
|
-
const globalPath =
|
|
454
|
-
|
|
269
|
+
const globalPath =
|
|
270
|
+
storagePaths?.globalPath ?? join(getCentralStoragePath(), "global");
|
|
271
|
+
const projectPath = storagePaths?.projectPath ?? getCentralStoragePath();
|
|
455
272
|
|
|
456
273
|
// Use Agent SDK with file tools
|
|
457
274
|
const q = query({
|
|
458
275
|
prompt: userMessage,
|
|
459
276
|
options: {
|
|
460
277
|
systemPrompt,
|
|
461
|
-
permissionMode:
|
|
462
|
-
model:
|
|
278
|
+
permissionMode: "bypassPermissions",
|
|
279
|
+
model: "claude-opus-4-5-20251101",
|
|
463
280
|
// Only allow file tools - no Bash, no web
|
|
464
|
-
allowedTools: [
|
|
281
|
+
allowedTools: ["Read", "Write", "Edit", "Glob", "Grep"],
|
|
465
282
|
// Allow access to memory directories
|
|
466
283
|
additionalDirectories: [globalPath, projectPath],
|
|
467
284
|
// Limit turns if configured
|
|
468
285
|
maxTurns: this._config.maxTurns,
|
|
469
286
|
},
|
|
470
|
-
})
|
|
287
|
+
});
|
|
471
288
|
|
|
472
289
|
// Iterate through the async generator to get the result
|
|
473
|
-
let resultText =
|
|
290
|
+
let resultText = "";
|
|
474
291
|
for await (const msg of q) {
|
|
475
|
-
if (msg.type ===
|
|
476
|
-
resultText = msg.result
|
|
477
|
-
break
|
|
292
|
+
if (msg.type === "result" && "result" in msg) {
|
|
293
|
+
resultText = msg.result;
|
|
294
|
+
break;
|
|
478
295
|
}
|
|
479
296
|
}
|
|
480
297
|
|
|
@@ -488,12 +305,12 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
488
305
|
filesWritten: 0,
|
|
489
306
|
primerUpdated: false,
|
|
490
307
|
actions: [],
|
|
491
|
-
summary:
|
|
492
|
-
fullReport:
|
|
493
|
-
}
|
|
308
|
+
summary: "No result from management agent",
|
|
309
|
+
fullReport: "Management agent completed but returned no result",
|
|
310
|
+
};
|
|
494
311
|
}
|
|
495
312
|
|
|
496
|
-
return this._parseSDKManagementResult(resultText)
|
|
313
|
+
return this._parseSDKManagementResult(resultText);
|
|
497
314
|
} catch (error: any) {
|
|
498
315
|
return {
|
|
499
316
|
success: false,
|
|
@@ -504,159 +321,63 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
504
321
|
filesWritten: 0,
|
|
505
322
|
primerUpdated: false,
|
|
506
323
|
actions: [],
|
|
507
|
-
summary:
|
|
508
|
-
fullReport: `Error: Agent SDK failed: ${error.message}`,
|
|
324
|
+
summary: "",
|
|
325
|
+
fullReport: `Manager Claude - Error: Agent SDK failed: ${error.message}`,
|
|
509
326
|
error: error.message,
|
|
510
|
-
}
|
|
327
|
+
};
|
|
511
328
|
}
|
|
512
329
|
}
|
|
513
330
|
|
|
514
|
-
/**
|
|
515
|
-
* Manage using CLI subprocess (for hooks - keeps working while we migrate)
|
|
516
|
-
* Similar to Curator.curateWithCLI
|
|
517
|
-
*/
|
|
518
|
-
async manageWithCLI(
|
|
519
|
-
projectId: string,
|
|
520
|
-
sessionNumber: number,
|
|
521
|
-
result: CurationResult,
|
|
522
|
-
storagePaths?: StoragePaths
|
|
523
|
-
): Promise<ManagementResult> {
|
|
524
|
-
// Skip if disabled via config or env var
|
|
525
|
-
if (!this._config.enabled || process.env.MEMORY_MANAGER_DISABLED === '1') {
|
|
526
|
-
return {
|
|
527
|
-
success: true,
|
|
528
|
-
superseded: 0,
|
|
529
|
-
resolved: 0,
|
|
530
|
-
linked: 0,
|
|
531
|
-
filesRead: 0,
|
|
532
|
-
filesWritten: 0,
|
|
533
|
-
primerUpdated: false,
|
|
534
|
-
actions: [],
|
|
535
|
-
summary: 'Management agent disabled',
|
|
536
|
-
fullReport: 'Management agent disabled via configuration',
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Skip if no memories
|
|
541
|
-
if (result.memories.length === 0) {
|
|
542
|
-
return {
|
|
543
|
-
success: true,
|
|
544
|
-
superseded: 0,
|
|
545
|
-
resolved: 0,
|
|
546
|
-
linked: 0,
|
|
547
|
-
filesRead: 0,
|
|
548
|
-
filesWritten: 0,
|
|
549
|
-
primerUpdated: false,
|
|
550
|
-
actions: [],
|
|
551
|
-
summary: 'No memories to process',
|
|
552
|
-
fullReport: 'No memories to process - skipped',
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// Load skill file
|
|
557
|
-
const systemPrompt = await this.buildManagementPrompt()
|
|
558
|
-
if (!systemPrompt) {
|
|
559
|
-
return {
|
|
560
|
-
success: false,
|
|
561
|
-
superseded: 0,
|
|
562
|
-
resolved: 0,
|
|
563
|
-
linked: 0,
|
|
564
|
-
filesRead: 0,
|
|
565
|
-
filesWritten: 0,
|
|
566
|
-
primerUpdated: false,
|
|
567
|
-
actions: [],
|
|
568
|
-
summary: '',
|
|
569
|
-
fullReport: 'Error: Management skill file not found',
|
|
570
|
-
error: 'Management skill not found',
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
const userMessage = this.buildUserMessage(projectId, sessionNumber, result, storagePaths)
|
|
575
|
-
|
|
576
|
-
// Build settings file with path-based permissions
|
|
577
|
-
// This provides real security - the agent can ONLY access memory storage paths
|
|
578
|
-
const settingsPath = await this._buildSettingsFile(storagePaths)
|
|
579
|
-
|
|
580
|
-
// Build CLI command with settings file for path restrictions
|
|
581
|
-
const args = [
|
|
582
|
-
'-p', userMessage,
|
|
583
|
-
'--append-system-prompt', systemPrompt,
|
|
584
|
-
'--output-format', 'json',
|
|
585
|
-
'--settings', settingsPath,
|
|
586
|
-
]
|
|
587
|
-
|
|
588
|
-
// Add max-turns only if configured (undefined = unlimited)
|
|
589
|
-
if (this._config.maxTurns !== undefined) {
|
|
590
|
-
args.push('--max-turns', String(this._config.maxTurns))
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// Execute CLI
|
|
594
|
-
const proc = Bun.spawn([this._config.cliCommand, ...args], {
|
|
595
|
-
env: {
|
|
596
|
-
...process.env,
|
|
597
|
-
MEMORY_CURATOR_ACTIVE: '1', // Prevent recursive hook triggering
|
|
598
|
-
},
|
|
599
|
-
stderr: 'pipe',
|
|
600
|
-
})
|
|
601
|
-
|
|
602
|
-
// Capture output
|
|
603
|
-
const [stdout, stderr] = await Promise.all([
|
|
604
|
-
new Response(proc.stdout).text(),
|
|
605
|
-
new Response(proc.stderr).text(),
|
|
606
|
-
])
|
|
607
|
-
const exitCode = await proc.exited
|
|
608
|
-
|
|
609
|
-
if (exitCode !== 0) {
|
|
610
|
-
const errorMsg = stderr || `Exit code ${exitCode}`
|
|
611
|
-
return {
|
|
612
|
-
success: false,
|
|
613
|
-
superseded: 0,
|
|
614
|
-
resolved: 0,
|
|
615
|
-
linked: 0,
|
|
616
|
-
filesRead: 0,
|
|
617
|
-
filesWritten: 0,
|
|
618
|
-
primerUpdated: false,
|
|
619
|
-
actions: [],
|
|
620
|
-
summary: '',
|
|
621
|
-
fullReport: `Error: CLI failed with exit code ${exitCode}\n${stderr}`,
|
|
622
|
-
error: errorMsg,
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
return this.parseManagementResponse(stdout)
|
|
627
|
-
}
|
|
628
|
-
|
|
629
331
|
/**
|
|
630
332
|
* Parse management result from Agent SDK response
|
|
631
333
|
* Similar to parseManagementResponse but for SDK output format
|
|
632
334
|
*/
|
|
633
335
|
private _parseSDKManagementResult(resultText: string): ManagementResult {
|
|
634
336
|
// Extract actions section
|
|
635
|
-
const actionsMatch = resultText.match(
|
|
636
|
-
|
|
337
|
+
const actionsMatch = resultText.match(
|
|
338
|
+
/=== MANAGEMENT ACTIONS ===([\s\S]*?)(?:=== SUMMARY ===|$)/,
|
|
339
|
+
);
|
|
340
|
+
const actions: string[] = [];
|
|
637
341
|
if (actionsMatch) {
|
|
638
|
-
const actionsText = actionsMatch[1]
|
|
639
|
-
const actionLines = actionsText
|
|
342
|
+
const actionsText = actionsMatch[1];
|
|
343
|
+
const actionLines = actionsText
|
|
344
|
+
.split("\n")
|
|
640
345
|
.map((line: string) => line.trim())
|
|
641
|
-
.filter((line: string) =>
|
|
642
|
-
|
|
346
|
+
.filter((line: string) =>
|
|
347
|
+
/^(READ|WRITE|RECEIVED|CREATED|UPDATED|SUPERSEDED|RESOLVED|LINKED|PRIMER|SKIPPED|NO_ACTION)/.test(
|
|
348
|
+
line,
|
|
349
|
+
),
|
|
350
|
+
);
|
|
351
|
+
actions.push(...actionLines);
|
|
643
352
|
}
|
|
644
353
|
|
|
645
354
|
// Extract the full report
|
|
646
|
-
const reportMatch = resultText.match(/(=== MANAGEMENT ACTIONS ===[\s\S]*)/)
|
|
647
|
-
const fullReport = reportMatch ? reportMatch[1].trim() : resultText
|
|
355
|
+
const reportMatch = resultText.match(/(=== MANAGEMENT ACTIONS ===[\s\S]*)/);
|
|
356
|
+
const fullReport = reportMatch ? reportMatch[1].trim() : resultText;
|
|
648
357
|
|
|
649
358
|
// Extract stats from result text
|
|
650
|
-
const supersededMatch =
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
359
|
+
const supersededMatch =
|
|
360
|
+
resultText.match(/memories_superseded[:\s]+(\d+)/i) ||
|
|
361
|
+
resultText.match(/superseded[:\s]+(\d+)/i);
|
|
362
|
+
const resolvedMatch =
|
|
363
|
+
resultText.match(/memories_resolved[:\s]+(\d+)/i) ||
|
|
364
|
+
resultText.match(/resolved[:\s]+(\d+)/i);
|
|
365
|
+
const linkedMatch =
|
|
366
|
+
resultText.match(/memories_linked[:\s]+(\d+)/i) ||
|
|
367
|
+
resultText.match(/linked[:\s]+(\d+)/i);
|
|
368
|
+
const filesReadMatch = resultText.match(/files_read[:\s]+(\d+)/i);
|
|
369
|
+
const filesWrittenMatch = resultText.match(/files_written[:\s]+(\d+)/i);
|
|
370
|
+
const primerUpdated =
|
|
371
|
+
/primer_updated[:\s]+true/i.test(resultText) ||
|
|
372
|
+
/PRIMER\s+OK/i.test(resultText);
|
|
656
373
|
|
|
657
374
|
// Count file operations from actions if not in summary
|
|
658
|
-
const readActions = actions.filter((a: string) =>
|
|
659
|
-
|
|
375
|
+
const readActions = actions.filter((a: string) =>
|
|
376
|
+
a.startsWith("READ OK"),
|
|
377
|
+
).length;
|
|
378
|
+
const writeActions = actions.filter((a: string) =>
|
|
379
|
+
a.startsWith("WRITE OK"),
|
|
380
|
+
).length;
|
|
660
381
|
|
|
661
382
|
return {
|
|
662
383
|
success: true,
|
|
@@ -664,12 +385,14 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
664
385
|
resolved: resolvedMatch ? parseInt(resolvedMatch[1]) : 0,
|
|
665
386
|
linked: linkedMatch ? parseInt(linkedMatch[1]) : 0,
|
|
666
387
|
filesRead: filesReadMatch ? parseInt(filesReadMatch[1]) : readActions,
|
|
667
|
-
filesWritten: filesWrittenMatch
|
|
388
|
+
filesWritten: filesWrittenMatch
|
|
389
|
+
? parseInt(filesWrittenMatch[1])
|
|
390
|
+
: writeActions,
|
|
668
391
|
primerUpdated,
|
|
669
392
|
actions,
|
|
670
393
|
summary: resultText.slice(0, 500),
|
|
671
394
|
fullReport,
|
|
672
|
-
}
|
|
395
|
+
};
|
|
673
396
|
}
|
|
674
397
|
|
|
675
398
|
/**
|
|
@@ -681,10 +404,10 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
681
404
|
projectId: string,
|
|
682
405
|
sessionNumber: number,
|
|
683
406
|
result: CurationResult,
|
|
684
|
-
storagePaths?: StoragePaths
|
|
407
|
+
storagePaths?: StoragePaths,
|
|
685
408
|
): Promise<ManagementResult> {
|
|
686
409
|
// Skip if disabled via config or env var
|
|
687
|
-
if (!this._config.enabled || process.env.MEMORY_MANAGER_DISABLED ===
|
|
410
|
+
if (!this._config.enabled || process.env.MEMORY_MANAGER_DISABLED === "1") {
|
|
688
411
|
return {
|
|
689
412
|
success: true,
|
|
690
413
|
superseded: 0,
|
|
@@ -694,9 +417,9 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
694
417
|
filesWritten: 0,
|
|
695
418
|
primerUpdated: false,
|
|
696
419
|
actions: [],
|
|
697
|
-
summary:
|
|
698
|
-
fullReport:
|
|
699
|
-
}
|
|
420
|
+
summary: "Manager Gemini - Management agent disabled",
|
|
421
|
+
fullReport: "Management agent disabled via configuration",
|
|
422
|
+
};
|
|
700
423
|
}
|
|
701
424
|
|
|
702
425
|
// Skip if no memories
|
|
@@ -710,13 +433,13 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
710
433
|
filesWritten: 0,
|
|
711
434
|
primerUpdated: false,
|
|
712
435
|
actions: [],
|
|
713
|
-
summary:
|
|
714
|
-
fullReport:
|
|
715
|
-
}
|
|
436
|
+
summary: "Manager Gemini - No memories to process",
|
|
437
|
+
fullReport: "No memories to process - skipped",
|
|
438
|
+
};
|
|
716
439
|
}
|
|
717
440
|
|
|
718
441
|
// Load skill file
|
|
719
|
-
const systemPrompt = await this.buildManagementPrompt()
|
|
442
|
+
const systemPrompt = await this.buildManagementPrompt();
|
|
720
443
|
if (!systemPrompt) {
|
|
721
444
|
return {
|
|
722
445
|
success: false,
|
|
@@ -727,26 +450,28 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
727
450
|
filesWritten: 0,
|
|
728
451
|
primerUpdated: false,
|
|
729
452
|
actions: [],
|
|
730
|
-
summary:
|
|
731
|
-
fullReport:
|
|
732
|
-
|
|
733
|
-
|
|
453
|
+
summary: "",
|
|
454
|
+
fullReport:
|
|
455
|
+
"Manager Gemini - Error: Management skill not file not found",
|
|
456
|
+
error: "Management skill not found",
|
|
457
|
+
};
|
|
734
458
|
}
|
|
735
459
|
|
|
736
|
-
const userMessage = this.buildUserMessage(
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
460
|
+
const userMessage = this.buildUserMessage(
|
|
461
|
+
projectId,
|
|
462
|
+
sessionNumber,
|
|
463
|
+
result,
|
|
464
|
+
storagePaths,
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
// Resolve paths using centralized utilities
|
|
468
|
+
const managerCwd = getManagerCwd(storagePaths);
|
|
469
|
+
const projectPath =
|
|
470
|
+
storagePaths?.projectPath ?? join(getCentralStoragePath(), projectId);
|
|
471
|
+
const globalPath =
|
|
472
|
+
storagePaths?.globalPath ?? join(getCentralStoragePath(), "global");
|
|
473
|
+
|
|
474
|
+
// Write system prompt to temp file (tmpdir always exists)
|
|
750
475
|
const geminiSystemPrompt = `${systemPrompt}
|
|
751
476
|
|
|
752
477
|
## Available Tools
|
|
@@ -756,88 +481,112 @@ You have access to the following tools to manage memory files:
|
|
|
756
481
|
\${AvailableTools}
|
|
757
482
|
|
|
758
483
|
Use these tools to read existing memories, write updates, and manage the memory filesystem.
|
|
759
|
-
|
|
760
|
-
const tempPromptPath =
|
|
761
|
-
await Bun.write(tempPromptPath, geminiSystemPrompt)
|
|
484
|
+
`;
|
|
485
|
+
const tempPromptPath = getManagerPromptPath();
|
|
486
|
+
await Bun.write(tempPromptPath, geminiSystemPrompt);
|
|
762
487
|
|
|
763
488
|
// Copy user's Gemini settings to managerCwd with hooks disabled
|
|
764
489
|
// This prevents the manager's session from triggering hooks recursively
|
|
765
|
-
const userSettingsPath = join(homedir(),
|
|
766
|
-
const managerSettingsDir = join(managerCwd,
|
|
767
|
-
const managerSettingsPath = join(managerSettingsDir,
|
|
490
|
+
const userSettingsPath = join(homedir(), ".gemini", "settings.json");
|
|
491
|
+
const managerSettingsDir = join(managerCwd, ".gemini");
|
|
492
|
+
const managerSettingsPath = join(managerSettingsDir, "settings.json");
|
|
768
493
|
|
|
769
494
|
try {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
782
|
-
settings.hooks.enabled = false
|
|
495
|
+
// Disable memory hooks to prevent recursive curation
|
|
496
|
+
// Format: hooks.disabled array with hook command names
|
|
497
|
+
const settings = {
|
|
498
|
+
hooks: {
|
|
499
|
+
disabled: [
|
|
500
|
+
"inject-memories",
|
|
501
|
+
"load-session-primer",
|
|
502
|
+
"curate-memories",
|
|
503
|
+
"curate-memories",
|
|
504
|
+
],
|
|
505
|
+
},
|
|
506
|
+
};
|
|
783
507
|
|
|
784
508
|
// Ensure .gemini directory exists in managerCwd
|
|
785
509
|
if (!existsSync(managerSettingsDir)) {
|
|
786
|
-
const { mkdirSync } = await import(
|
|
787
|
-
mkdirSync(managerSettingsDir, { recursive: true })
|
|
510
|
+
const { mkdirSync } = await import("fs");
|
|
511
|
+
mkdirSync(managerSettingsDir, { recursive: true });
|
|
788
512
|
}
|
|
789
513
|
|
|
790
|
-
await Bun.write(managerSettingsPath, JSON.stringify(settings, null, 2))
|
|
791
|
-
logger.debug(
|
|
514
|
+
await Bun.write(managerSettingsPath, JSON.stringify(settings, null, 2));
|
|
515
|
+
logger.debug(
|
|
516
|
+
`Manager Gemini: Created settings with hooks disabled at ${managerSettingsPath}`,
|
|
517
|
+
"manager",
|
|
518
|
+
);
|
|
792
519
|
} catch (err: any) {
|
|
793
|
-
logger.debug(
|
|
520
|
+
logger.debug(
|
|
521
|
+
`Manager Gemini: Could not create settings file: ${err.message}`,
|
|
522
|
+
"manager",
|
|
523
|
+
);
|
|
794
524
|
}
|
|
795
525
|
|
|
796
|
-
logger.debug(
|
|
797
|
-
|
|
798
|
-
|
|
526
|
+
logger.debug(
|
|
527
|
+
`Manager Gemini - Starting management for project ${projectId}`,
|
|
528
|
+
"manager",
|
|
529
|
+
);
|
|
530
|
+
logger.debug(
|
|
531
|
+
`Manager Gemini - Processing ${result.memories.length} memories`,
|
|
532
|
+
"manager",
|
|
533
|
+
);
|
|
534
|
+
logger.debug(
|
|
535
|
+
`Manager Gemini - Storage mode: ${getStorageMode(storagePaths)}, cwd: ${managerCwd}`,
|
|
536
|
+
"manager",
|
|
537
|
+
);
|
|
799
538
|
|
|
800
539
|
// Build CLI command
|
|
801
540
|
// - cwd gives write access to that tree
|
|
802
541
|
// - --include-directories adds read access to other paths
|
|
803
542
|
const args = [
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
543
|
+
"-p",
|
|
544
|
+
userMessage,
|
|
545
|
+
"--output-format",
|
|
546
|
+
"json",
|
|
547
|
+
"--yolo", // Auto-approve file operations
|
|
548
|
+
"--include-directories",
|
|
549
|
+
projectPath,
|
|
550
|
+
"--include-directories",
|
|
551
|
+
globalPath,
|
|
552
|
+
];
|
|
553
|
+
|
|
554
|
+
logger.debug(
|
|
555
|
+
`Manager Gemini: Spawning gemini CLI from ${managerCwd}`,
|
|
556
|
+
"manager",
|
|
557
|
+
);
|
|
812
558
|
|
|
813
559
|
// Execute CLI with system prompt via environment variable
|
|
814
560
|
// cwd determines write access, --include-directories adds read access
|
|
815
|
-
const proc = Bun.spawn([
|
|
561
|
+
const proc = Bun.spawn(["gemini", ...args], {
|
|
816
562
|
cwd: managerCwd,
|
|
817
563
|
env: {
|
|
818
564
|
...process.env,
|
|
819
|
-
MEMORY_CURATOR_ACTIVE:
|
|
820
|
-
GEMINI_SYSTEM_MD: tempPromptPath,
|
|
565
|
+
MEMORY_CURATOR_ACTIVE: "1", // Prevent recursive hook triggering
|
|
566
|
+
GEMINI_SYSTEM_MD: tempPromptPath, // Inject our management prompt
|
|
821
567
|
},
|
|
822
|
-
stdout:
|
|
823
|
-
stderr:
|
|
824
|
-
})
|
|
568
|
+
stdout: "pipe",
|
|
569
|
+
stderr: "pipe",
|
|
570
|
+
});
|
|
825
571
|
|
|
826
572
|
// Capture output
|
|
827
573
|
const [stdout, stderr] = await Promise.all([
|
|
828
574
|
new Response(proc.stdout).text(),
|
|
829
575
|
new Response(proc.stderr).text(),
|
|
830
|
-
])
|
|
831
|
-
const exitCode = await proc.exited
|
|
576
|
+
]);
|
|
577
|
+
const exitCode = await proc.exited;
|
|
832
578
|
|
|
833
|
-
logger.debug(`Manager Gemini
|
|
579
|
+
logger.debug(`Manager Gemini - Exit code ${exitCode}`, "manager");
|
|
834
580
|
if (stderr && stderr.trim()) {
|
|
835
|
-
logger.debug(`Manager Gemini stderr: ${stderr}`,
|
|
581
|
+
logger.debug(`Manager Gemini - stderr: ${stderr}`, "manager");
|
|
836
582
|
}
|
|
837
583
|
|
|
838
584
|
if (exitCode !== 0) {
|
|
839
|
-
logger.debug(
|
|
840
|
-
|
|
585
|
+
logger.debug(
|
|
586
|
+
`Manager Gemini - Failed with exit code ${exitCode}`,
|
|
587
|
+
"manager",
|
|
588
|
+
);
|
|
589
|
+
const errorMsg = stderr || `Exit code ${exitCode}`;
|
|
841
590
|
return {
|
|
842
591
|
success: false,
|
|
843
592
|
superseded: 0,
|
|
@@ -847,22 +596,31 @@ Use these tools to read existing memories, write updates, and manage the memory
|
|
|
847
596
|
filesWritten: 0,
|
|
848
597
|
primerUpdated: false,
|
|
849
598
|
actions: [],
|
|
850
|
-
summary:
|
|
851
|
-
fullReport: `Error: Gemini CLI failed with exit code ${exitCode}\n${stderr}`,
|
|
599
|
+
summary: "",
|
|
600
|
+
fullReport: `Manager Gemini - Error: Gemini CLI failed with exit code ${exitCode}\n${stderr}`,
|
|
852
601
|
error: errorMsg,
|
|
853
|
-
}
|
|
602
|
+
};
|
|
854
603
|
}
|
|
855
604
|
|
|
856
605
|
// Parse Gemini JSON output
|
|
857
606
|
// Note: Gemini CLI outputs log messages before AND after the JSON
|
|
858
607
|
// We need to extract just the JSON object
|
|
859
|
-
logger.debug(
|
|
608
|
+
logger.debug(
|
|
609
|
+
`Manager Gemini - Parsing response (${stdout.length} chars)`,
|
|
610
|
+
"manager",
|
|
611
|
+
);
|
|
860
612
|
try {
|
|
861
613
|
// Find the JSON object - it starts with { and we need to find the matching }
|
|
862
|
-
const jsonStart = stdout.indexOf(
|
|
614
|
+
const jsonStart = stdout.indexOf("{");
|
|
863
615
|
if (jsonStart === -1) {
|
|
864
|
-
logger.debug(
|
|
865
|
-
|
|
616
|
+
logger.debug(
|
|
617
|
+
"Manager Gemini - No JSON object found in output",
|
|
618
|
+
"manager",
|
|
619
|
+
);
|
|
620
|
+
// logger.debug(
|
|
621
|
+
// `Manager Gemini - Raw stdout: ${stdout.slice(0, 500)}`,
|
|
622
|
+
// "manager",
|
|
623
|
+
// );
|
|
866
624
|
return {
|
|
867
625
|
success: false,
|
|
868
626
|
superseded: 0,
|
|
@@ -872,25 +630,29 @@ Use these tools to read existing memories, write updates, and manage the memory
|
|
|
872
630
|
filesWritten: 0,
|
|
873
631
|
primerUpdated: false,
|
|
874
632
|
actions: [],
|
|
875
|
-
summary:
|
|
876
|
-
fullReport:
|
|
877
|
-
|
|
633
|
+
summary: "Manager Gemini - No JSON in Gemini response",
|
|
634
|
+
fullReport:
|
|
635
|
+
"Manager Gemini - Failed: No JSON object in Gemini CLI output",
|
|
636
|
+
};
|
|
878
637
|
}
|
|
879
638
|
|
|
880
639
|
// Find the matching closing brace by counting braces
|
|
881
|
-
let braceCount = 0
|
|
882
|
-
let jsonEnd = -1
|
|
640
|
+
let braceCount = 0;
|
|
641
|
+
let jsonEnd = -1;
|
|
883
642
|
for (let i = jsonStart; i < stdout.length; i++) {
|
|
884
|
-
if (stdout[i] ===
|
|
885
|
-
if (stdout[i] ===
|
|
643
|
+
if (stdout[i] === "{") braceCount++;
|
|
644
|
+
if (stdout[i] === "}") braceCount--;
|
|
886
645
|
if (braceCount === 0) {
|
|
887
|
-
jsonEnd = i + 1
|
|
888
|
-
break
|
|
646
|
+
jsonEnd = i + 1;
|
|
647
|
+
break;
|
|
889
648
|
}
|
|
890
649
|
}
|
|
891
650
|
|
|
892
651
|
if (jsonEnd === -1) {
|
|
893
|
-
logger.debug(
|
|
652
|
+
logger.debug(
|
|
653
|
+
"Manager Gemini - Could not find matching closing brace",
|
|
654
|
+
"manager",
|
|
655
|
+
);
|
|
894
656
|
return {
|
|
895
657
|
success: false,
|
|
896
658
|
superseded: 0,
|
|
@@ -900,21 +662,25 @@ Use these tools to read existing memories, write updates, and manage the memory
|
|
|
900
662
|
filesWritten: 0,
|
|
901
663
|
primerUpdated: false,
|
|
902
664
|
actions: [],
|
|
903
|
-
summary:
|
|
904
|
-
fullReport:
|
|
905
|
-
|
|
665
|
+
summary: "Manager Gemini - Incomplete JSON in Gemini response",
|
|
666
|
+
fullReport:
|
|
667
|
+
"Manager Gemini - Failed: Could not find complete JSON object",
|
|
668
|
+
};
|
|
906
669
|
}
|
|
907
670
|
|
|
908
|
-
const jsonStr = stdout.slice(jsonStart, jsonEnd)
|
|
909
|
-
logger.debug(
|
|
671
|
+
const jsonStr = stdout.slice(jsonStart, jsonEnd);
|
|
672
|
+
logger.debug(
|
|
673
|
+
`Manager Gemini - Extracted JSON (${jsonStr.length} chars)`,
|
|
674
|
+
"manager",
|
|
675
|
+
);
|
|
910
676
|
|
|
911
|
-
const geminiOutput = JSON.parse(jsonStr)
|
|
677
|
+
const geminiOutput = JSON.parse(jsonStr);
|
|
912
678
|
|
|
913
679
|
// Gemini returns { response: "...", stats: {...} }
|
|
914
|
-
const aiResponse = geminiOutput.response ||
|
|
680
|
+
const aiResponse = geminiOutput.response || "";
|
|
915
681
|
|
|
916
682
|
if (!aiResponse) {
|
|
917
|
-
logger.debug(
|
|
683
|
+
logger.debug("Manager Gemini - No response field in output", "manager");
|
|
918
684
|
return {
|
|
919
685
|
success: true,
|
|
920
686
|
superseded: 0,
|
|
@@ -924,20 +690,29 @@ Use these tools to read existing memories, write updates, and manage the memory
|
|
|
924
690
|
filesWritten: 0,
|
|
925
691
|
primerUpdated: false,
|
|
926
692
|
actions: [],
|
|
927
|
-
summary:
|
|
928
|
-
fullReport:
|
|
929
|
-
}
|
|
693
|
+
summary: "Managetr Gemini - No response from Gemini",
|
|
694
|
+
fullReport: "Management completed but no response returned",
|
|
695
|
+
};
|
|
930
696
|
}
|
|
931
697
|
|
|
932
|
-
logger.debug(
|
|
698
|
+
logger.debug(
|
|
699
|
+
`Manager Gemini - Got response (${aiResponse.length} chars)`,
|
|
700
|
+
"manager",
|
|
701
|
+
);
|
|
933
702
|
|
|
934
703
|
// Parse using our existing SDK parser (same format expected)
|
|
935
|
-
const result = this._parseSDKManagementResult(aiResponse)
|
|
936
|
-
logger.debug(
|
|
937
|
-
|
|
704
|
+
const result = this._parseSDKManagementResult(aiResponse);
|
|
705
|
+
logger.debug(
|
|
706
|
+
`Manager Gemini - Parsed result - superseded: ${result.superseded}, resolved: ${result.resolved}, linked: ${result.linked}`,
|
|
707
|
+
"manager",
|
|
708
|
+
);
|
|
709
|
+
return result;
|
|
938
710
|
} catch (error: any) {
|
|
939
|
-
logger.debug(`Manager Gemini
|
|
940
|
-
logger.debug(
|
|
711
|
+
logger.debug(`Manager Gemini - Parse error: ${error.message}`, "manager");
|
|
712
|
+
logger.debug(
|
|
713
|
+
`Manager Gemini - Raw stdout (first 500 chars): ${stdout.slice(0, 500)}`,
|
|
714
|
+
"manager",
|
|
715
|
+
);
|
|
941
716
|
return {
|
|
942
717
|
success: false,
|
|
943
718
|
superseded: 0,
|
|
@@ -947,10 +722,10 @@ Use these tools to read existing memories, write updates, and manage the memory
|
|
|
947
722
|
filesWritten: 0,
|
|
948
723
|
primerUpdated: false,
|
|
949
724
|
actions: [],
|
|
950
|
-
summary:
|
|
951
|
-
fullReport: `Error: Failed to parse Gemini response: ${error.message}`,
|
|
725
|
+
summary: "",
|
|
726
|
+
fullReport: `Manager Gemini - Error: Failed to parse Gemini response: ${error.message}`,
|
|
952
727
|
error: error.message,
|
|
953
|
-
}
|
|
728
|
+
};
|
|
954
729
|
}
|
|
955
730
|
}
|
|
956
731
|
}
|
|
@@ -959,5 +734,5 @@ Use these tools to read existing memories, write updates, and manage the memory
|
|
|
959
734
|
* Create a new manager
|
|
960
735
|
*/
|
|
961
736
|
export function createManager(config?: ManagerConfig): Manager {
|
|
962
|
-
return new Manager(config)
|
|
737
|
+
return new Manager(config);
|
|
963
738
|
}
|