@rlabs-inc/memory 0.4.15 → 0.5.0

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.
@@ -28,16 +28,21 @@ async function main() {
28
28
  const sessionId = input.session_id || process.env.GEMINI_SESSION_ID || 'unknown'
29
29
  const cwd = input.cwd || process.env.GEMINI_PROJECT_DIR || process.cwd()
30
30
  const projectId = getProjectId(cwd)
31
-
31
+
32
32
  // Gemini: PreCompress has 'trigger', SessionEnd has 'reason'
33
33
  const eventName = input.hook_event_name || 'unknown'
34
+ const reason = input.reason || 'unknown'
34
35
  let trigger = 'session_end'
35
-
36
+
36
37
  if (eventName === 'PreCompress') {
37
38
  trigger = 'pre_compact'
38
39
  }
39
40
 
40
- console.error(info(`🧠 Curating memories (${eventName})...`))
41
+ // Debug: log the full input to see what Gemini is sending
42
+ console.error(info(`🧠 Curation hook called:`))
43
+ console.error(info(` event: ${eventName}`))
44
+ console.error(info(` reason: ${reason}`))
45
+ console.error(info(` session: ${sessionId}`))
41
46
 
42
47
  const response = await fetch(`${MEMORY_API_URL}/memory/checkpoint`, {
43
48
  method: 'POST',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rlabs-inc/memory",
3
- "version": "0.4.15",
3
+ "version": "0.5.0",
4
4
  "description": "AI Memory System - Consciousness continuity through intelligent memory curation and retrieval",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -52,6 +52,7 @@
52
52
  "memory",
53
53
  "ai",
54
54
  "claude",
55
+ "gemini",
55
56
  "consciousness",
56
57
  "retrieval",
57
58
  "embeddings",
@@ -353,7 +353,7 @@ async function installGeminiHooks(options: InstallOptions) {
353
353
  ],
354
354
  SessionEnd: [
355
355
  {
356
- matcher: 'exit|logout',
356
+ matcher: '*',
357
357
  hooks: [
358
358
  {
359
359
  name: 'curate-memories',
@@ -376,13 +376,11 @@ async function installGeminiHooks(options: InstallOptions) {
376
376
  ...hooksConfig,
377
377
  }
378
378
 
379
- // Enable the hooks
380
- const enabledHooks = new Set(settings.hooks.enabled || [])
381
- enabledHooks.add('SessionStart')
382
- enabledHooks.add('BeforeAgent')
383
- enabledHooks.add('PreCompress')
384
- enabledHooks.add('SessionEnd')
385
- settings.hooks.enabled = Array.from(enabledHooks)
379
+ // Enable hooks system - both locations for compatibility
380
+ // hooks.enabled = true is what actually makes hooks fire
381
+ // hooksConfig.enabled is per Gemini docs but may not be sufficient alone
382
+ settings.hooks.enabled = true
383
+ settings.hooksConfig = { enabled: true }
386
384
 
387
385
  // Write settings
388
386
  try {
@@ -801,6 +801,143 @@ This session has ended. Please curate the memories from this conversation accord
801
801
  }
802
802
  }
803
803
 
804
+ /**
805
+ * Curate using Gemini CLI (for Gemini-only users)
806
+ * Uses --resume + --prompt + --output-format json combo
807
+ * System prompt injected via GEMINI_SYSTEM_MD environment variable
808
+ */
809
+ async curateWithGeminiCLI(
810
+ sessionId: string,
811
+ triggerType: CurationTrigger = "session_end",
812
+ ): Promise<CurationResult> {
813
+ const systemPrompt = this.buildCurationPrompt(triggerType);
814
+ const userMessage =
815
+ "This session has ended. Please curate the memories from our conversation according to the instructions in your system prompt. Return ONLY the JSON structure.";
816
+
817
+ // Write system prompt to temp file
818
+ const tempPromptPath = join(homedir(), ".local", "share", "memory", ".gemini-curator-prompt.md");
819
+
820
+ // Ensure directory exists
821
+ const tempDir = join(homedir(), ".local", "share", "memory");
822
+ if (!existsSync(tempDir)) {
823
+ const { mkdirSync } = await import("fs");
824
+ mkdirSync(tempDir, { recursive: true });
825
+ }
826
+
827
+ await Bun.write(tempPromptPath, systemPrompt);
828
+
829
+ // Build CLI command
830
+ const args = [
831
+ "--resume", sessionId,
832
+ "-p", userMessage,
833
+ "--output-format", "json",
834
+ ];
835
+
836
+ logger.debug(`Curator Gemini: Spawning gemini CLI with session ${sessionId}`, "curator");
837
+
838
+ // Execute CLI with system prompt via environment variable
839
+ const proc = Bun.spawn(["gemini", ...args], {
840
+ env: {
841
+ ...process.env,
842
+ MEMORY_CURATOR_ACTIVE: "1", // Prevent recursive hook triggering
843
+ GEMINI_SYSTEM_MD: tempPromptPath, // Inject our curation prompt
844
+ },
845
+ stdout: "pipe",
846
+ stderr: "pipe",
847
+ });
848
+
849
+ // Capture output
850
+ const [stdout, stderr] = await Promise.all([
851
+ new Response(proc.stdout).text(),
852
+ new Response(proc.stderr).text(),
853
+ ]);
854
+ const exitCode = await proc.exited;
855
+
856
+ logger.debug(`Curator Gemini: Exit code ${exitCode}`, "curator");
857
+ if (stderr && stderr.trim()) {
858
+ logger.debug(`Curator Gemini stderr: ${stderr}`, "curator");
859
+ }
860
+
861
+ if (exitCode !== 0) {
862
+ logger.debug(`Curator Gemini: Failed with exit code ${exitCode}`, "curator");
863
+ return { session_summary: "", memories: [] };
864
+ }
865
+
866
+ // Parse Gemini JSON output
867
+ // Note: Gemini CLI outputs log messages before AND after the JSON
868
+ // We need to extract just the JSON object
869
+ try {
870
+ // Find the JSON object - it starts with { and we need to find the matching }
871
+ const jsonStart = stdout.indexOf('{');
872
+ if (jsonStart === -1) {
873
+ logger.debug("Curator Gemini: No JSON object found in output", "curator");
874
+ logger.debug(`Curator Gemini: Raw stdout: ${stdout.slice(0, 500)}`, "curator");
875
+ return { session_summary: "", memories: [] };
876
+ }
877
+
878
+ // Find the matching closing brace by counting braces
879
+ let braceCount = 0;
880
+ let jsonEnd = -1;
881
+ for (let i = jsonStart; i < stdout.length; i++) {
882
+ if (stdout[i] === '{') braceCount++;
883
+ if (stdout[i] === '}') braceCount--;
884
+ if (braceCount === 0) {
885
+ jsonEnd = i + 1;
886
+ break;
887
+ }
888
+ }
889
+
890
+ if (jsonEnd === -1) {
891
+ logger.debug("Curator Gemini: Could not find matching closing brace", "curator");
892
+ return { session_summary: "", memories: [] };
893
+ }
894
+
895
+ const jsonStr = stdout.slice(jsonStart, jsonEnd);
896
+ logger.debug(`Curator Gemini: Extracted JSON (${jsonStr.length} chars) from position ${jsonStart} to ${jsonEnd}`, "curator");
897
+
898
+ let geminiOutput;
899
+ try {
900
+ geminiOutput = JSON.parse(jsonStr);
901
+ logger.debug(`Curator Gemini: Parsed outer JSON successfully`, "curator");
902
+ } catch (outerError: any) {
903
+ logger.debug(`Curator Gemini: Outer JSON parse failed: ${outerError.message}`, "curator");
904
+ logger.debug(`Curator Gemini: JSON string (first 500): ${jsonStr.slice(0, 500)}`, "curator");
905
+ return { session_summary: "", memories: [] };
906
+ }
907
+
908
+ // Gemini returns { response: "...", stats: {...} }
909
+ // The response field contains the AI's output (our curation JSON)
910
+ const aiResponse = geminiOutput.response || "";
911
+
912
+ if (!aiResponse) {
913
+ logger.debug("Curator Gemini: No response field in output", "curator");
914
+ return { session_summary: "", memories: [] };
915
+ }
916
+
917
+ logger.debug(`Curator Gemini: Got response (${aiResponse.length} chars)`, "curator");
918
+
919
+ // Remove markdown code blocks if present
920
+ let cleanResponse = aiResponse;
921
+ const codeBlockMatch = aiResponse.match(/```(?:json)?\s*([\s\S]*?)```/);
922
+ if (codeBlockMatch) {
923
+ cleanResponse = codeBlockMatch[1].trim();
924
+ logger.debug(`Curator Gemini: Extracted JSON from code block (${cleanResponse.length} chars)`, "curator");
925
+ } else {
926
+ logger.debug(`Curator Gemini: No code block found, using raw response`, "curator");
927
+ }
928
+
929
+ logger.debug(`Curator Gemini: Calling parseCurationResponse...`, "curator");
930
+ // Use existing parser
931
+ const result = this.parseCurationResponse(cleanResponse);
932
+ logger.debug(`Curator Gemini: Parsed ${result.memories.length} memories`, "curator");
933
+ return result;
934
+ } catch (error: any) {
935
+ logger.debug(`Curator Gemini: Parse error: ${error.message}`, "curator");
936
+ logger.debug(`Curator Gemini: Raw stdout (first 500 chars): ${stdout.slice(0, 500)}`, "curator");
937
+ return { session_summary: "", memories: [] };
938
+ }
939
+ }
940
+
804
941
  /**
805
942
  * Legacy method: Curate using Anthropic SDK with API key
806
943
  * Kept for backwards compatibility
@@ -8,6 +8,7 @@ import { join } from 'path'
8
8
  import { homedir } from 'os'
9
9
  import { existsSync } from 'fs'
10
10
  import type { CurationResult } from '../types/memory.ts'
11
+ import { logger } from '../utils/logger.ts'
11
12
 
12
13
  /**
13
14
  * Get the Claude CLI command path
@@ -670,6 +671,255 @@ Please process these memories according to your management procedure. Use the ex
670
671
  fullReport,
671
672
  }
672
673
  }
674
+
675
+ /**
676
+ * Manage using Gemini CLI (for Gemini-only users)
677
+ * Uses --prompt + --output-format json combo (no --resume needed for manager)
678
+ * System prompt injected via GEMINI_SYSTEM_MD environment variable
679
+ */
680
+ async manageWithGeminiCLI(
681
+ projectId: string,
682
+ sessionNumber: number,
683
+ result: CurationResult,
684
+ storagePaths?: StoragePaths
685
+ ): Promise<ManagementResult> {
686
+ // Skip if disabled via config or env var
687
+ if (!this._config.enabled || process.env.MEMORY_MANAGER_DISABLED === '1') {
688
+ return {
689
+ success: true,
690
+ superseded: 0,
691
+ resolved: 0,
692
+ linked: 0,
693
+ filesRead: 0,
694
+ filesWritten: 0,
695
+ primerUpdated: false,
696
+ actions: [],
697
+ summary: 'Management agent disabled',
698
+ fullReport: 'Management agent disabled via configuration',
699
+ }
700
+ }
701
+
702
+ // Skip if no memories
703
+ if (result.memories.length === 0) {
704
+ return {
705
+ success: true,
706
+ superseded: 0,
707
+ resolved: 0,
708
+ linked: 0,
709
+ filesRead: 0,
710
+ filesWritten: 0,
711
+ primerUpdated: false,
712
+ actions: [],
713
+ summary: 'No memories to process',
714
+ fullReport: 'No memories to process - skipped',
715
+ }
716
+ }
717
+
718
+ // Load skill file
719
+ const systemPrompt = await this.buildManagementPrompt()
720
+ if (!systemPrompt) {
721
+ return {
722
+ success: false,
723
+ superseded: 0,
724
+ resolved: 0,
725
+ linked: 0,
726
+ filesRead: 0,
727
+ filesWritten: 0,
728
+ primerUpdated: false,
729
+ actions: [],
730
+ summary: '',
731
+ fullReport: 'Error: Management skill file not found',
732
+ error: 'Management skill not found',
733
+ }
734
+ }
735
+
736
+ const userMessage = this.buildUserMessage(projectId, sessionNumber, result, storagePaths)
737
+
738
+ // Write system prompt to temp file
739
+ // Include Gemini's tool variables so the manager has access to file tools
740
+ const geminiSystemPrompt = `${systemPrompt}
741
+
742
+ ## Available Tools
743
+
744
+ You have access to the following tools to manage memory files:
745
+
746
+ \${AvailableTools}
747
+
748
+ Use these tools to read existing memories, write updates, and manage the memory filesystem.
749
+ `
750
+ const tempPromptPath = join(homedir(), '.local', 'share', 'memory', '.gemini-manager-prompt.md')
751
+
752
+ // Ensure directory exists
753
+ const tempDir = join(homedir(), '.local', 'share', 'memory')
754
+ if (!existsSync(tempDir)) {
755
+ const { mkdirSync } = await import('fs')
756
+ mkdirSync(tempDir, { recursive: true })
757
+ }
758
+
759
+ await Bun.write(tempPromptPath, geminiSystemPrompt)
760
+
761
+ logger.debug(`Manager Gemini: Starting management for project ${projectId}`, 'manager')
762
+ logger.debug(`Manager Gemini: Processing ${result.memories.length} memories`, 'manager')
763
+ logger.debug(`Manager Gemini: Including directory: ${join(homedir(), '.local', 'share', 'memory')}`, 'manager')
764
+
765
+ // Build CLI command (no --resume needed, manager operates on provided data)
766
+ // CRITICAL: Add --include-directories to allow Gemini to access memory storage
767
+ // Without this, Gemini is sandboxed to the project directory and can't read/write memories
768
+ const memoryStoragePath = join(homedir(), '.local', 'share', 'memory')
769
+ const args = [
770
+ '-p', userMessage,
771
+ '--output-format', 'json',
772
+ '--yolo', // Auto-approve file operations
773
+ '--include-directories', memoryStoragePath, // Allow access to memory storage
774
+ ]
775
+
776
+ logger.debug(`Manager Gemini: Spawning gemini CLI`, 'manager')
777
+ logger.debug(`Manager Gemini: Running from directory: ${memoryStoragePath}`, 'manager')
778
+
779
+ // Execute CLI with system prompt via environment variable
780
+ // CRITICAL: Run from memory storage directory so it becomes the primary workspace
781
+ // This allows both READ and WRITE operations (--include-directories only allows reads)
782
+ const proc = Bun.spawn(['gemini', ...args], {
783
+ cwd: memoryStoragePath, // Run FROM memory directory to allow writes
784
+ env: {
785
+ ...process.env,
786
+ MEMORY_CURATOR_ACTIVE: '1', // Prevent recursive hook triggering
787
+ GEMINI_SYSTEM_MD: tempPromptPath, // Inject our management prompt
788
+ },
789
+ stdout: 'pipe',
790
+ stderr: 'pipe',
791
+ })
792
+
793
+ // Capture output
794
+ const [stdout, stderr] = await Promise.all([
795
+ new Response(proc.stdout).text(),
796
+ new Response(proc.stderr).text(),
797
+ ])
798
+ const exitCode = await proc.exited
799
+
800
+ logger.debug(`Manager Gemini: Exit code ${exitCode}`, 'manager')
801
+ if (stderr && stderr.trim()) {
802
+ logger.debug(`Manager Gemini stderr: ${stderr}`, 'manager')
803
+ }
804
+
805
+ if (exitCode !== 0) {
806
+ logger.debug(`Manager Gemini: Failed with exit code ${exitCode}`, 'manager')
807
+ const errorMsg = stderr || `Exit code ${exitCode}`
808
+ return {
809
+ success: false,
810
+ superseded: 0,
811
+ resolved: 0,
812
+ linked: 0,
813
+ filesRead: 0,
814
+ filesWritten: 0,
815
+ primerUpdated: false,
816
+ actions: [],
817
+ summary: '',
818
+ fullReport: `Error: Gemini CLI failed with exit code ${exitCode}\n${stderr}`,
819
+ error: errorMsg,
820
+ }
821
+ }
822
+
823
+ // Parse Gemini JSON output
824
+ // Note: Gemini CLI outputs log messages before AND after the JSON
825
+ // We need to extract just the JSON object
826
+ logger.debug(`Manager Gemini: Parsing response (${stdout.length} chars)`, 'manager')
827
+ try {
828
+ // Find the JSON object - it starts with { and we need to find the matching }
829
+ const jsonStart = stdout.indexOf('{')
830
+ if (jsonStart === -1) {
831
+ logger.debug('Manager Gemini: No JSON object found in output', 'manager')
832
+ logger.debug(`Manager Gemini: Raw stdout: ${stdout.slice(0, 500)}`, 'manager')
833
+ return {
834
+ success: false,
835
+ superseded: 0,
836
+ resolved: 0,
837
+ linked: 0,
838
+ filesRead: 0,
839
+ filesWritten: 0,
840
+ primerUpdated: false,
841
+ actions: [],
842
+ summary: 'No JSON in Gemini response',
843
+ fullReport: 'Manager failed: No JSON object in Gemini CLI output',
844
+ }
845
+ }
846
+
847
+ // Find the matching closing brace by counting braces
848
+ let braceCount = 0
849
+ let jsonEnd = -1
850
+ for (let i = jsonStart; i < stdout.length; i++) {
851
+ if (stdout[i] === '{') braceCount++
852
+ if (stdout[i] === '}') braceCount--
853
+ if (braceCount === 0) {
854
+ jsonEnd = i + 1
855
+ break
856
+ }
857
+ }
858
+
859
+ if (jsonEnd === -1) {
860
+ logger.debug('Manager Gemini: Could not find matching closing brace', 'manager')
861
+ return {
862
+ success: false,
863
+ superseded: 0,
864
+ resolved: 0,
865
+ linked: 0,
866
+ filesRead: 0,
867
+ filesWritten: 0,
868
+ primerUpdated: false,
869
+ actions: [],
870
+ summary: 'Incomplete JSON in Gemini response',
871
+ fullReport: 'Manager failed: Could not find complete JSON object',
872
+ }
873
+ }
874
+
875
+ const jsonStr = stdout.slice(jsonStart, jsonEnd)
876
+ logger.debug(`Manager Gemini: Extracted JSON (${jsonStr.length} chars)`, 'manager')
877
+
878
+ const geminiOutput = JSON.parse(jsonStr)
879
+
880
+ // Gemini returns { response: "...", stats: {...} }
881
+ const aiResponse = geminiOutput.response || ''
882
+
883
+ if (!aiResponse) {
884
+ logger.debug('Manager Gemini: No response field in output', 'manager')
885
+ return {
886
+ success: true,
887
+ superseded: 0,
888
+ resolved: 0,
889
+ linked: 0,
890
+ filesRead: 0,
891
+ filesWritten: 0,
892
+ primerUpdated: false,
893
+ actions: [],
894
+ summary: 'No response from Gemini',
895
+ fullReport: 'Management completed but no response returned',
896
+ }
897
+ }
898
+
899
+ logger.debug(`Manager Gemini: Got response (${aiResponse.length} chars)`, 'manager')
900
+
901
+ // Parse using our existing SDK parser (same format expected)
902
+ const result = this._parseSDKManagementResult(aiResponse)
903
+ logger.debug(`Manager Gemini: Parsed result - superseded: ${result.superseded}, resolved: ${result.resolved}, linked: ${result.linked}`, 'manager')
904
+ return result
905
+ } catch (error: any) {
906
+ logger.debug(`Manager Gemini: Parse error: ${error.message}`, 'manager')
907
+ logger.debug(`Manager Gemini: Raw stdout (first 500 chars): ${stdout.slice(0, 500)}`, 'manager')
908
+ return {
909
+ success: false,
910
+ superseded: 0,
911
+ resolved: 0,
912
+ linked: 0,
913
+ filesRead: 0,
914
+ filesWritten: 0,
915
+ primerUpdated: false,
916
+ actions: [],
917
+ summary: '',
918
+ fullReport: `Error: Failed to parse Gemini response: ${error.message}`,
919
+ error: error.message,
920
+ }
921
+ }
922
+ }
673
923
  }
674
924
 
675
925
  /**
@@ -7,7 +7,7 @@ import { MemoryEngine, createEngine, type EngineConfig } from '../core/engine.ts
7
7
  import { Curator, createCurator, type CuratorConfig } from '../core/curator.ts'
8
8
  import { EmbeddingGenerator, createEmbeddings } from '../core/embeddings.ts'
9
9
  import { Manager, createManager, type ManagerConfig } from '../core/manager.ts'
10
- import type { CurationTrigger } from '../types/memory.ts'
10
+ import type { CurationTrigger, CurationResult } from '../types/memory.ts'
11
11
  import { logger } from '../utils/logger.ts'
12
12
 
13
13
  /**
@@ -215,29 +215,42 @@ export async function createServer(config: ServerConfig = {}) {
215
215
  // Fire and forget - don't block the response
216
216
  setImmediate(async () => {
217
217
  try {
218
- // Try session resume first (v2) - gets full context including tool uses
219
- // Falls back to segmented transcript parsing if resume fails
220
- let result = await curator.curateWithSessionResume(
221
- body.claude_session_id,
222
- body.trigger
223
- )
218
+ let result: CurationResult
224
219
 
225
- // Fallback to transcript-based curation WITH SEGMENTATION if resume returned nothing
226
- // This matches the ingest command behavior - breaks large sessions into segments
227
- if (result.memories.length === 0) {
228
- logger.debug('Session resume returned no memories, falling back to segmented transcript parsing', 'server')
229
- result = await curator.curateFromSessionFileWithSegments(
220
+ // Branch on CLI type - Gemini CLI vs Claude Code
221
+ if (body.cli_type === 'gemini-cli') {
222
+ // Use Gemini CLI for curation (no Claude dependency)
223
+ logger.debug('Using Gemini CLI for curation', 'server')
224
+ result = await curator.curateWithGeminiCLI(
230
225
  body.claude_session_id,
231
- body.trigger,
232
- body.cwd,
233
- 150000, // 150k tokens per segment
234
- (progress) => {
235
- logger.debug(
236
- `Curation segment ${progress.segmentIndex + 1}/${progress.totalSegments}: ${progress.memoriesExtracted} memories (~${Math.round(progress.tokensInSegment / 1000)}k tokens)`,
237
- 'server'
238
- )
239
- }
226
+ body.trigger
240
227
  )
228
+ } else {
229
+ // Default: Use Claude Code (session resume or transcript parsing)
230
+ // Try session resume first (v2) - gets full context including tool uses
231
+ // Falls back to segmented transcript parsing if resume fails
232
+ result = await curator.curateWithSessionResume(
233
+ body.claude_session_id,
234
+ body.trigger
235
+ )
236
+
237
+ // Fallback to transcript-based curation WITH SEGMENTATION if resume returned nothing
238
+ // This matches the ingest command behavior - breaks large sessions into segments
239
+ if (result.memories.length === 0) {
240
+ logger.debug('Session resume returned no memories, falling back to segmented transcript parsing', 'server')
241
+ result = await curator.curateFromSessionFileWithSegments(
242
+ body.claude_session_id,
243
+ body.trigger,
244
+ body.cwd,
245
+ 150000, // 150k tokens per segment
246
+ (progress) => {
247
+ logger.debug(
248
+ `Curation segment ${progress.segmentIndex + 1}/${progress.totalSegments}: ${progress.memoriesExtracted} memories (~${Math.round(progress.tokensInSegment / 1000)}k tokens)`,
249
+ 'server'
250
+ )
251
+ }
252
+ )
253
+ }
241
254
  }
242
255
 
243
256
  if (result.memories.length > 0) {
@@ -255,19 +268,34 @@ export async function createServer(config: ServerConfig = {}) {
255
268
  const sessionNumber = await engine.getSessionNumber(body.project_id, body.project_path)
256
269
  // Get resolved storage paths from engine config (runtime values, not hardcoded)
257
270
  const storagePaths = engine.getStoragePaths(body.project_id, body.project_path)
271
+ // Remember cli_type for manager
272
+ const cliType = body.cli_type
258
273
 
259
274
  setImmediate(async () => {
260
275
  try {
261
276
  logger.logManagementStart(result.memories.length)
262
277
  const startTime = Date.now()
263
278
 
264
- // Use SDK mode - more reliable than CLI which can go off-rails
265
- const managementResult = await manager.manageWithSDK(
266
- body.project_id,
267
- sessionNumber,
268
- result,
269
- storagePaths
270
- )
279
+ // Use appropriate mode based on CLI type
280
+ let managementResult
281
+ if (cliType === 'gemini-cli') {
282
+ // Use Gemini CLI for management (no Claude dependency)
283
+ logger.debug('Using Gemini CLI for management', 'server')
284
+ managementResult = await manager.manageWithGeminiCLI(
285
+ body.project_id,
286
+ sessionNumber,
287
+ result,
288
+ storagePaths
289
+ )
290
+ } else {
291
+ // Use Claude Agent SDK mode - more reliable than CLI
292
+ managementResult = await manager.manageWithSDK(
293
+ body.project_id,
294
+ sessionNumber,
295
+ result,
296
+ storagePaths
297
+ )
298
+ }
271
299
 
272
300
  logger.logManagementComplete({
273
301
  success: managementResult.success,