@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.
- package/README.md +42 -7
- package/dist/index.js +37279 -10012
- package/hooks/gemini/curation.ts +8 -3
- package/package.json +2 -1
- package/src/cli/commands/install.ts +6 -8
- package/src/core/curator.ts +137 -0
- package/src/core/manager.ts +250 -0
- package/src/server/index.ts +56 -28
package/hooks/gemini/curation.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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: '
|
|
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
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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 {
|
package/src/core/curator.ts
CHANGED
|
@@ -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
|
package/src/core/manager.ts
CHANGED
|
@@ -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
|
/**
|
package/src/server/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
logger.debug('
|
|
229
|
-
result = await curator.
|
|
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
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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,
|