@rlabs-inc/memory 0.5.2 → 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 +336 -528
- 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,126 +321,11 @@ 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
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
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))
|
|
327
|
+
};
|
|
591
328
|
}
|
|
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
329
|
}
|
|
628
330
|
|
|
629
331
|
/**
|
|
@@ -632,31 +334,50 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
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,16 +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
|
-
|
|
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)
|
|
740
475
|
const geminiSystemPrompt = `${systemPrompt}
|
|
741
476
|
|
|
742
477
|
## Available Tools
|
|
@@ -746,65 +481,112 @@ You have access to the following tools to manage memory files:
|
|
|
746
481
|
\${AvailableTools}
|
|
747
482
|
|
|
748
483
|
Use these tools to read existing memories, write updates, and manage the memory filesystem.
|
|
749
|
-
|
|
750
|
-
const tempPromptPath =
|
|
484
|
+
`;
|
|
485
|
+
const tempPromptPath = getManagerPromptPath();
|
|
486
|
+
await Bun.write(tempPromptPath, geminiSystemPrompt);
|
|
751
487
|
|
|
752
|
-
//
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
}
|
|
488
|
+
// Copy user's Gemini settings to managerCwd with hooks disabled
|
|
489
|
+
// This prevents the manager's session from triggering hooks recursively
|
|
490
|
+
const userSettingsPath = join(homedir(), ".gemini", "settings.json");
|
|
491
|
+
const managerSettingsDir = join(managerCwd, ".gemini");
|
|
492
|
+
const managerSettingsPath = join(managerSettingsDir, "settings.json");
|
|
758
493
|
|
|
759
|
-
|
|
494
|
+
try {
|
|
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
|
+
};
|
|
760
507
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
508
|
+
// Ensure .gemini directory exists in managerCwd
|
|
509
|
+
if (!existsSync(managerSettingsDir)) {
|
|
510
|
+
const { mkdirSync } = await import("fs");
|
|
511
|
+
mkdirSync(managerSettingsDir, { recursive: true });
|
|
512
|
+
}
|
|
764
513
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
+
);
|
|
519
|
+
} catch (err: any) {
|
|
520
|
+
logger.debug(
|
|
521
|
+
`Manager Gemini: Could not create settings file: ${err.message}`,
|
|
522
|
+
"manager",
|
|
523
|
+
);
|
|
524
|
+
}
|
|
775
525
|
|
|
776
|
-
logger.debug(
|
|
777
|
-
|
|
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
|
+
);
|
|
538
|
+
|
|
539
|
+
// Build CLI command
|
|
540
|
+
// - cwd gives write access to that tree
|
|
541
|
+
// - --include-directories adds read access to other paths
|
|
542
|
+
const args = [
|
|
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
|
+
);
|
|
778
558
|
|
|
779
559
|
// Execute CLI with system prompt via environment variable
|
|
780
|
-
//
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
cwd: memoryStoragePath, // Run FROM memory directory to allow writes
|
|
560
|
+
// cwd determines write access, --include-directories adds read access
|
|
561
|
+
const proc = Bun.spawn(["gemini", ...args], {
|
|
562
|
+
cwd: managerCwd,
|
|
784
563
|
env: {
|
|
785
564
|
...process.env,
|
|
786
|
-
MEMORY_CURATOR_ACTIVE:
|
|
787
|
-
GEMINI_SYSTEM_MD: tempPromptPath,
|
|
565
|
+
MEMORY_CURATOR_ACTIVE: "1", // Prevent recursive hook triggering
|
|
566
|
+
GEMINI_SYSTEM_MD: tempPromptPath, // Inject our management prompt
|
|
788
567
|
},
|
|
789
|
-
stdout:
|
|
790
|
-
stderr:
|
|
791
|
-
})
|
|
568
|
+
stdout: "pipe",
|
|
569
|
+
stderr: "pipe",
|
|
570
|
+
});
|
|
792
571
|
|
|
793
572
|
// Capture output
|
|
794
573
|
const [stdout, stderr] = await Promise.all([
|
|
795
574
|
new Response(proc.stdout).text(),
|
|
796
575
|
new Response(proc.stderr).text(),
|
|
797
|
-
])
|
|
798
|
-
const exitCode = await proc.exited
|
|
576
|
+
]);
|
|
577
|
+
const exitCode = await proc.exited;
|
|
799
578
|
|
|
800
|
-
logger.debug(`Manager Gemini
|
|
579
|
+
logger.debug(`Manager Gemini - Exit code ${exitCode}`, "manager");
|
|
801
580
|
if (stderr && stderr.trim()) {
|
|
802
|
-
logger.debug(`Manager Gemini stderr: ${stderr}`,
|
|
581
|
+
logger.debug(`Manager Gemini - stderr: ${stderr}`, "manager");
|
|
803
582
|
}
|
|
804
583
|
|
|
805
584
|
if (exitCode !== 0) {
|
|
806
|
-
logger.debug(
|
|
807
|
-
|
|
585
|
+
logger.debug(
|
|
586
|
+
`Manager Gemini - Failed with exit code ${exitCode}`,
|
|
587
|
+
"manager",
|
|
588
|
+
);
|
|
589
|
+
const errorMsg = stderr || `Exit code ${exitCode}`;
|
|
808
590
|
return {
|
|
809
591
|
success: false,
|
|
810
592
|
superseded: 0,
|
|
@@ -814,22 +596,31 @@ Use these tools to read existing memories, write updates, and manage the memory
|
|
|
814
596
|
filesWritten: 0,
|
|
815
597
|
primerUpdated: false,
|
|
816
598
|
actions: [],
|
|
817
|
-
summary:
|
|
818
|
-
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}`,
|
|
819
601
|
error: errorMsg,
|
|
820
|
-
}
|
|
602
|
+
};
|
|
821
603
|
}
|
|
822
604
|
|
|
823
605
|
// Parse Gemini JSON output
|
|
824
606
|
// Note: Gemini CLI outputs log messages before AND after the JSON
|
|
825
607
|
// We need to extract just the JSON object
|
|
826
|
-
logger.debug(
|
|
608
|
+
logger.debug(
|
|
609
|
+
`Manager Gemini - Parsing response (${stdout.length} chars)`,
|
|
610
|
+
"manager",
|
|
611
|
+
);
|
|
827
612
|
try {
|
|
828
613
|
// Find the JSON object - it starts with { and we need to find the matching }
|
|
829
|
-
const jsonStart = stdout.indexOf(
|
|
614
|
+
const jsonStart = stdout.indexOf("{");
|
|
830
615
|
if (jsonStart === -1) {
|
|
831
|
-
logger.debug(
|
|
832
|
-
|
|
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
|
+
// );
|
|
833
624
|
return {
|
|
834
625
|
success: false,
|
|
835
626
|
superseded: 0,
|
|
@@ -839,25 +630,29 @@ Use these tools to read existing memories, write updates, and manage the memory
|
|
|
839
630
|
filesWritten: 0,
|
|
840
631
|
primerUpdated: false,
|
|
841
632
|
actions: [],
|
|
842
|
-
summary:
|
|
843
|
-
fullReport:
|
|
844
|
-
|
|
633
|
+
summary: "Manager Gemini - No JSON in Gemini response",
|
|
634
|
+
fullReport:
|
|
635
|
+
"Manager Gemini - Failed: No JSON object in Gemini CLI output",
|
|
636
|
+
};
|
|
845
637
|
}
|
|
846
638
|
|
|
847
639
|
// Find the matching closing brace by counting braces
|
|
848
|
-
let braceCount = 0
|
|
849
|
-
let jsonEnd = -1
|
|
640
|
+
let braceCount = 0;
|
|
641
|
+
let jsonEnd = -1;
|
|
850
642
|
for (let i = jsonStart; i < stdout.length; i++) {
|
|
851
|
-
if (stdout[i] ===
|
|
852
|
-
if (stdout[i] ===
|
|
643
|
+
if (stdout[i] === "{") braceCount++;
|
|
644
|
+
if (stdout[i] === "}") braceCount--;
|
|
853
645
|
if (braceCount === 0) {
|
|
854
|
-
jsonEnd = i + 1
|
|
855
|
-
break
|
|
646
|
+
jsonEnd = i + 1;
|
|
647
|
+
break;
|
|
856
648
|
}
|
|
857
649
|
}
|
|
858
650
|
|
|
859
651
|
if (jsonEnd === -1) {
|
|
860
|
-
logger.debug(
|
|
652
|
+
logger.debug(
|
|
653
|
+
"Manager Gemini - Could not find matching closing brace",
|
|
654
|
+
"manager",
|
|
655
|
+
);
|
|
861
656
|
return {
|
|
862
657
|
success: false,
|
|
863
658
|
superseded: 0,
|
|
@@ -867,21 +662,25 @@ Use these tools to read existing memories, write updates, and manage the memory
|
|
|
867
662
|
filesWritten: 0,
|
|
868
663
|
primerUpdated: false,
|
|
869
664
|
actions: [],
|
|
870
|
-
summary:
|
|
871
|
-
fullReport:
|
|
872
|
-
|
|
665
|
+
summary: "Manager Gemini - Incomplete JSON in Gemini response",
|
|
666
|
+
fullReport:
|
|
667
|
+
"Manager Gemini - Failed: Could not find complete JSON object",
|
|
668
|
+
};
|
|
873
669
|
}
|
|
874
670
|
|
|
875
|
-
const jsonStr = stdout.slice(jsonStart, jsonEnd)
|
|
876
|
-
logger.debug(
|
|
671
|
+
const jsonStr = stdout.slice(jsonStart, jsonEnd);
|
|
672
|
+
logger.debug(
|
|
673
|
+
`Manager Gemini - Extracted JSON (${jsonStr.length} chars)`,
|
|
674
|
+
"manager",
|
|
675
|
+
);
|
|
877
676
|
|
|
878
|
-
const geminiOutput = JSON.parse(jsonStr)
|
|
677
|
+
const geminiOutput = JSON.parse(jsonStr);
|
|
879
678
|
|
|
880
679
|
// Gemini returns { response: "...", stats: {...} }
|
|
881
|
-
const aiResponse = geminiOutput.response ||
|
|
680
|
+
const aiResponse = geminiOutput.response || "";
|
|
882
681
|
|
|
883
682
|
if (!aiResponse) {
|
|
884
|
-
logger.debug(
|
|
683
|
+
logger.debug("Manager Gemini - No response field in output", "manager");
|
|
885
684
|
return {
|
|
886
685
|
success: true,
|
|
887
686
|
superseded: 0,
|
|
@@ -891,20 +690,29 @@ Use these tools to read existing memories, write updates, and manage the memory
|
|
|
891
690
|
filesWritten: 0,
|
|
892
691
|
primerUpdated: false,
|
|
893
692
|
actions: [],
|
|
894
|
-
summary:
|
|
895
|
-
fullReport:
|
|
896
|
-
}
|
|
693
|
+
summary: "Managetr Gemini - No response from Gemini",
|
|
694
|
+
fullReport: "Management completed but no response returned",
|
|
695
|
+
};
|
|
897
696
|
}
|
|
898
697
|
|
|
899
|
-
logger.debug(
|
|
698
|
+
logger.debug(
|
|
699
|
+
`Manager Gemini - Got response (${aiResponse.length} chars)`,
|
|
700
|
+
"manager",
|
|
701
|
+
);
|
|
900
702
|
|
|
901
703
|
// Parse using our existing SDK parser (same format expected)
|
|
902
|
-
const result = this._parseSDKManagementResult(aiResponse)
|
|
903
|
-
logger.debug(
|
|
904
|
-
|
|
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;
|
|
905
710
|
} catch (error: any) {
|
|
906
|
-
logger.debug(`Manager Gemini
|
|
907
|
-
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
|
+
);
|
|
908
716
|
return {
|
|
909
717
|
success: false,
|
|
910
718
|
superseded: 0,
|
|
@@ -914,10 +722,10 @@ Use these tools to read existing memories, write updates, and manage the memory
|
|
|
914
722
|
filesWritten: 0,
|
|
915
723
|
primerUpdated: false,
|
|
916
724
|
actions: [],
|
|
917
|
-
summary:
|
|
918
|
-
fullReport: `Error: Failed to parse Gemini response: ${error.message}`,
|
|
725
|
+
summary: "",
|
|
726
|
+
fullReport: `Manager Gemini - Error: Failed to parse Gemini response: ${error.message}`,
|
|
919
727
|
error: error.message,
|
|
920
|
-
}
|
|
728
|
+
};
|
|
921
729
|
}
|
|
922
730
|
}
|
|
923
731
|
}
|
|
@@ -926,5 +734,5 @@ Use these tools to read existing memories, write updates, and manage the memory
|
|
|
926
734
|
* Create a new manager
|
|
927
735
|
*/
|
|
928
736
|
export function createManager(config?: ManagerConfig): Manager {
|
|
929
|
-
return new Manager(config)
|
|
737
|
+
return new Manager(config);
|
|
930
738
|
}
|