@rlabs-inc/memory 0.1.0 → 0.2.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/dist/index.js +130 -18
- package/dist/index.mjs +130 -18
- package/dist/server/index.js +141 -18
- package/dist/server/index.mjs +141 -18
- package/hooks/curation.ts +89 -0
- package/hooks/session-start.ts +98 -0
- package/hooks/user-prompt.ts +97 -0
- package/package.json +14 -8
- package/src/cli/colors.ts +174 -0
- package/src/cli/commands/doctor.ts +143 -0
- package/src/cli/commands/install.ts +153 -0
- package/src/cli/commands/serve.ts +76 -0
- package/src/cli/commands/stats.ts +64 -0
- package/src/cli/index.ts +128 -0
- package/src/core/curator.ts +7 -48
- package/src/core/engine.test.ts +321 -0
- package/src/core/engine.ts +45 -8
- package/src/core/retrieval.ts +1 -1
- package/src/core/store.ts +109 -98
- package/src/server/index.ts +15 -40
- package/src/types/schema.ts +1 -1
- package/src/utils/logger.ts +158 -107
- package/bun.lock +0 -102
- package/test-retrieval.ts +0 -91
- package/tsconfig.json +0 -16
package/dist/server/index.js
CHANGED
|
@@ -10773,9 +10773,15 @@ class Database {
|
|
|
10773
10773
|
if (!id)
|
|
10774
10774
|
continue;
|
|
10775
10775
|
const record = this._columns.getRecord(index);
|
|
10776
|
-
const
|
|
10777
|
-
|
|
10778
|
-
|
|
10776
|
+
const withMeta = {
|
|
10777
|
+
id,
|
|
10778
|
+
...record,
|
|
10779
|
+
created: this._created[index] || 0,
|
|
10780
|
+
updated: this._updated[index] || 0,
|
|
10781
|
+
stale: this._staleFlags[index] || false
|
|
10782
|
+
};
|
|
10783
|
+
if (filter(withMeta)) {
|
|
10784
|
+
results.push(withMeta);
|
|
10779
10785
|
}
|
|
10780
10786
|
}
|
|
10781
10787
|
return results;
|
|
@@ -10786,9 +10792,15 @@ class Database {
|
|
|
10786
10792
|
if (!id)
|
|
10787
10793
|
continue;
|
|
10788
10794
|
const record = this._columns.getRecord(index);
|
|
10789
|
-
const
|
|
10790
|
-
|
|
10791
|
-
|
|
10795
|
+
const withMeta = {
|
|
10796
|
+
id,
|
|
10797
|
+
...record,
|
|
10798
|
+
created: this._created[index] || 0,
|
|
10799
|
+
updated: this._updated[index] || 0,
|
|
10800
|
+
stale: this._staleFlags[index] || false
|
|
10801
|
+
};
|
|
10802
|
+
if (filter(withMeta)) {
|
|
10803
|
+
return withMeta;
|
|
10792
10804
|
}
|
|
10793
10805
|
}
|
|
10794
10806
|
return null;
|
|
@@ -11142,9 +11154,12 @@ class MemoryStore {
|
|
|
11142
11154
|
}
|
|
11143
11155
|
async getProject(projectId) {
|
|
11144
11156
|
if (this._projects.has(projectId)) {
|
|
11157
|
+
console.log(`\uD83D\uDD04 [DEBUG] Returning cached databases for ${projectId}`);
|
|
11145
11158
|
return this._projects.get(projectId);
|
|
11146
11159
|
}
|
|
11160
|
+
console.log(`\uD83C\uDD95 [DEBUG] Creating NEW databases for ${projectId}`);
|
|
11147
11161
|
const projectPath = import_path2.join(this._config.basePath, projectId);
|
|
11162
|
+
console.log(` Path: ${projectPath}`);
|
|
11148
11163
|
const [memories, summaries, snapshots, sessions] = await Promise.all([
|
|
11149
11164
|
createDatabase({
|
|
11150
11165
|
path: import_path2.join(projectPath, "memories"),
|
|
@@ -11329,20 +11344,31 @@ class MemoryStore {
|
|
|
11329
11344
|
}
|
|
11330
11345
|
async storeSessionSummary(projectId, sessionId, summary, interactionTone = "") {
|
|
11331
11346
|
const { summaries } = await this.getProject(projectId);
|
|
11332
|
-
|
|
11347
|
+
console.log(`\uD83D\uDCDD [DEBUG] Storing summary for ${projectId}:`);
|
|
11348
|
+
console.log(` Summary length: ${summary.length} chars`);
|
|
11349
|
+
console.log(` Summaries count before: ${summaries.all().length}`);
|
|
11350
|
+
const id = await summaries.insert({
|
|
11333
11351
|
session_id: sessionId,
|
|
11334
11352
|
project_id: projectId,
|
|
11335
11353
|
summary,
|
|
11336
11354
|
interaction_tone: interactionTone
|
|
11337
11355
|
});
|
|
11356
|
+
console.log(` Summaries count after: ${summaries.all().length}`);
|
|
11357
|
+
console.log(` Inserted ID: ${id}`);
|
|
11358
|
+
return id;
|
|
11338
11359
|
}
|
|
11339
11360
|
async getLatestSummary(projectId) {
|
|
11340
11361
|
const { summaries } = await this.getProject(projectId);
|
|
11362
|
+
console.log(`\uD83D\uDCD6 [DEBUG] Getting latest summary for ${projectId}:`);
|
|
11341
11363
|
const all = summaries.all();
|
|
11342
|
-
|
|
11364
|
+
console.log(` Summaries found: ${all.length}`);
|
|
11365
|
+
if (!all.length) {
|
|
11366
|
+
console.log(` No summaries found!`);
|
|
11343
11367
|
return null;
|
|
11368
|
+
}
|
|
11344
11369
|
all.sort((a, b) => b.created - a.created);
|
|
11345
11370
|
const latest = all[0];
|
|
11371
|
+
console.log(` Latest summary: ${latest.summary.slice(0, 50)}...`);
|
|
11346
11372
|
return {
|
|
11347
11373
|
id: latest.id,
|
|
11348
11374
|
session_id: latest.session_id,
|
|
@@ -11727,6 +11753,12 @@ class MemoryEngine {
|
|
|
11727
11753
|
}
|
|
11728
11754
|
async _getStore(projectId, projectPath) {
|
|
11729
11755
|
const key = this._config.storageMode === "local" && projectPath ? projectPath : projectId;
|
|
11756
|
+
console.log(`\uD83C\uDFEA [DEBUG] _getStore called:`);
|
|
11757
|
+
console.log(` projectId: ${projectId}`);
|
|
11758
|
+
console.log(` projectPath: ${projectPath}`);
|
|
11759
|
+
console.log(` storageMode: ${this._config.storageMode}`);
|
|
11760
|
+
console.log(` cache key: ${key}`);
|
|
11761
|
+
console.log(` cached: ${this._stores.has(key)}`);
|
|
11730
11762
|
if (this._stores.has(key)) {
|
|
11731
11763
|
return this._stores.get(key);
|
|
11732
11764
|
}
|
|
@@ -11900,14 +11932,31 @@ function createEngine(config) {
|
|
|
11900
11932
|
}
|
|
11901
11933
|
|
|
11902
11934
|
// src/core/curator.ts
|
|
11935
|
+
var import_os3 = require("os");
|
|
11936
|
+
var import_path5 = require("path");
|
|
11937
|
+
var import_fs3 = require("fs");
|
|
11938
|
+
function getClaudeCommand() {
|
|
11939
|
+
const envCommand = process.env.CURATOR_COMMAND;
|
|
11940
|
+
if (envCommand) {
|
|
11941
|
+
return envCommand;
|
|
11942
|
+
}
|
|
11943
|
+
const claudeLocal = import_path5.join(import_os3.homedir(), ".claude", "local", "claude");
|
|
11944
|
+
if (import_fs3.existsSync(claudeLocal)) {
|
|
11945
|
+
return claudeLocal;
|
|
11946
|
+
}
|
|
11947
|
+
return "claude";
|
|
11948
|
+
}
|
|
11949
|
+
|
|
11903
11950
|
class Curator {
|
|
11904
11951
|
_config;
|
|
11905
11952
|
constructor(config = {}) {
|
|
11953
|
+
const cliCommand = config.cliCommand ?? getClaudeCommand();
|
|
11906
11954
|
this._config = {
|
|
11907
11955
|
apiKey: config.apiKey ?? "",
|
|
11908
|
-
cliCommand
|
|
11956
|
+
cliCommand,
|
|
11909
11957
|
cliType: config.cliType ?? "claude-code"
|
|
11910
11958
|
};
|
|
11959
|
+
console.log(`\uD83E\uDDE0 Curator initialized with CLI: ${cliCommand}`);
|
|
11911
11960
|
}
|
|
11912
11961
|
buildCurationPrompt(triggerType = "session_end") {
|
|
11913
11962
|
return `You have just had a conversation. As this session is ending (${triggerType}), please curate memories for the Claude Tools Memory System.
|
|
@@ -12101,33 +12150,96 @@ ${prompt}`
|
|
|
12101
12150
|
return this.parseCurationResponse(content.text);
|
|
12102
12151
|
}
|
|
12103
12152
|
async curateWithCLI(sessionId, triggerType = "session_end", cwd) {
|
|
12104
|
-
const
|
|
12153
|
+
const systemPrompt = this.buildCurationPrompt(triggerType);
|
|
12154
|
+
const userMessage = "This session has ended. Please curate the memories from our conversation according to the instructions in your system prompt. Return ONLY the JSON structure.";
|
|
12105
12155
|
const args = [];
|
|
12106
12156
|
if (this._config.cliType === "claude-code") {
|
|
12107
|
-
args.push("--resume", sessionId, "-p", prompt, "--output-format", "json", "--max-turns", "1");
|
|
12157
|
+
args.push("--resume", sessionId, "-p", userMessage, "--append-system-prompt", systemPrompt, "--output-format", "json", "--max-turns", "1");
|
|
12108
12158
|
} else {
|
|
12109
|
-
args.push("--resume", sessionId, "-p",
|
|
12159
|
+
args.push("--resume", sessionId, "-p", `${systemPrompt}
|
|
12160
|
+
|
|
12161
|
+
${userMessage}`, "--output-format", "json");
|
|
12110
12162
|
}
|
|
12163
|
+
console.log(`
|
|
12164
|
+
\uD83D\uDCCB Executing CLI command:`);
|
|
12165
|
+
console.log(` Command: ${this._config.cliCommand}`);
|
|
12166
|
+
console.log(` Args: --resume ${sessionId} -p [user_message] --append-system-prompt [curation_instructions] --output-format json --max-turns 1`);
|
|
12167
|
+
console.log(` CWD: ${cwd || "not set"}`);
|
|
12168
|
+
console.log(` User message: "${userMessage.slice(0, 50)}..."`);
|
|
12169
|
+
console.log(` System prompt length: ${systemPrompt.length} chars`);
|
|
12111
12170
|
const proc = Bun.spawn([this._config.cliCommand, ...args], {
|
|
12112
12171
|
cwd,
|
|
12113
12172
|
env: {
|
|
12114
12173
|
...process.env,
|
|
12115
12174
|
MEMORY_CURATOR_ACTIVE: "1"
|
|
12116
|
-
}
|
|
12175
|
+
},
|
|
12176
|
+
stderr: "pipe"
|
|
12117
12177
|
});
|
|
12118
|
-
const
|
|
12178
|
+
const [stdout, stderr] = await Promise.all([
|
|
12179
|
+
new Response(proc.stdout).text(),
|
|
12180
|
+
new Response(proc.stderr).text()
|
|
12181
|
+
]);
|
|
12119
12182
|
const exitCode = await proc.exited;
|
|
12183
|
+
console.log(`
|
|
12184
|
+
\uD83D\uDCE4 CLI Response:`);
|
|
12185
|
+
console.log(` Exit code: ${exitCode}`);
|
|
12186
|
+
console.log(` Stdout length: ${stdout.length} chars`);
|
|
12187
|
+
console.log(` Stderr length: ${stderr.length} chars`);
|
|
12188
|
+
if (stderr) {
|
|
12189
|
+
console.log(`
|
|
12190
|
+
⚠️ Stderr output:`);
|
|
12191
|
+
console.log(stderr.slice(0, 1000));
|
|
12192
|
+
}
|
|
12193
|
+
if (stdout) {
|
|
12194
|
+
console.log(`
|
|
12195
|
+
\uD83D\uDCE5 Stdout output (first 500 chars):`);
|
|
12196
|
+
console.log(stdout.slice(0, 500));
|
|
12197
|
+
}
|
|
12120
12198
|
if (exitCode !== 0) {
|
|
12121
|
-
console.error(`
|
|
12199
|
+
console.error(`
|
|
12200
|
+
❌ CLI exited with code ${exitCode}`);
|
|
12122
12201
|
return { session_summary: "", memories: [] };
|
|
12123
12202
|
}
|
|
12124
12203
|
try {
|
|
12125
|
-
const
|
|
12204
|
+
const cliOutput = JSON.parse(stdout);
|
|
12205
|
+
if (cliOutput.type === "error" || cliOutput.is_error === true) {
|
|
12206
|
+
console.log(`
|
|
12207
|
+
❌ CLI returned error:`);
|
|
12208
|
+
console.log(` Type: ${cliOutput.type}`);
|
|
12209
|
+
console.log(` Message: ${cliOutput.message || cliOutput.error || "Unknown error"}`);
|
|
12210
|
+
return { session_summary: "", memories: [] };
|
|
12211
|
+
}
|
|
12212
|
+
let aiResponse = "";
|
|
12213
|
+
if (typeof cliOutput.result === "string") {
|
|
12214
|
+
aiResponse = cliOutput.result;
|
|
12215
|
+
console.log(`
|
|
12216
|
+
\uD83D\uDCE6 Extracted result from CLI wrapper (${aiResponse.length} chars)`);
|
|
12217
|
+
} else {
|
|
12218
|
+
console.log(`
|
|
12219
|
+
⚠️ No result field in CLI output`);
|
|
12220
|
+
console.log(` Keys: ${Object.keys(cliOutput).join(", ")}`);
|
|
12221
|
+
return { session_summary: "", memories: [] };
|
|
12222
|
+
}
|
|
12223
|
+
const codeBlockMatch = aiResponse.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
12224
|
+
if (codeBlockMatch) {
|
|
12225
|
+
aiResponse = codeBlockMatch[1].trim();
|
|
12226
|
+
console.log(`\uD83D\uDCDD Extracted JSON from markdown code block`);
|
|
12227
|
+
}
|
|
12228
|
+
const jsonMatch = aiResponse.match(/\{[\s\S]*\}/)?.[0];
|
|
12126
12229
|
if (jsonMatch) {
|
|
12127
|
-
|
|
12230
|
+
console.log(`✅ Found JSON object (${jsonMatch.length} chars)`);
|
|
12231
|
+
const result = this.parseCurationResponse(jsonMatch);
|
|
12232
|
+
console.log(` Parsed ${result.memories.length} memories`);
|
|
12233
|
+
return result;
|
|
12234
|
+
} else {
|
|
12235
|
+
console.log(`
|
|
12236
|
+
⚠️ No JSON object found in AI response`);
|
|
12237
|
+
console.log(` Response preview: ${aiResponse.slice(0, 200)}...`);
|
|
12128
12238
|
}
|
|
12129
12239
|
} catch (error) {
|
|
12130
|
-
console.error(
|
|
12240
|
+
console.error(`
|
|
12241
|
+
❌ Failed to parse CLI output:`, error);
|
|
12242
|
+
console.log(` Raw stdout (first 500 chars): ${stdout.slice(0, 500)}`);
|
|
12131
12243
|
}
|
|
12132
12244
|
return { session_summary: "", memories: [] };
|
|
12133
12245
|
}
|
|
@@ -12283,6 +12395,17 @@ function createServer(config = {}) {
|
|
|
12283
12395
|
context_type: m2.context_type
|
|
12284
12396
|
})), body.current_message ?? "");
|
|
12285
12397
|
}
|
|
12398
|
+
console.log(`
|
|
12399
|
+
\uD83D\uDCE4 [DEBUG] Response to hook:`);
|
|
12400
|
+
console.log(` memories_count: ${result.memories.length}`);
|
|
12401
|
+
console.log(` has_primer: ${!!result.primer}`);
|
|
12402
|
+
console.log(` formatted length: ${result.formatted.length} chars`);
|
|
12403
|
+
if (result.formatted) {
|
|
12404
|
+
console.log(` formatted preview:
|
|
12405
|
+
${result.formatted.slice(0, 300)}...`);
|
|
12406
|
+
} else {
|
|
12407
|
+
console.log(` ⚠️ formatted is EMPTY!`);
|
|
12408
|
+
}
|
|
12286
12409
|
return Response.json({
|
|
12287
12410
|
success: true,
|
|
12288
12411
|
context: result.formatted,
|
package/dist/server/index.mjs
CHANGED
|
@@ -10753,9 +10753,15 @@ class Database {
|
|
|
10753
10753
|
if (!id)
|
|
10754
10754
|
continue;
|
|
10755
10755
|
const record = this._columns.getRecord(index);
|
|
10756
|
-
const
|
|
10757
|
-
|
|
10758
|
-
|
|
10756
|
+
const withMeta = {
|
|
10757
|
+
id,
|
|
10758
|
+
...record,
|
|
10759
|
+
created: this._created[index] || 0,
|
|
10760
|
+
updated: this._updated[index] || 0,
|
|
10761
|
+
stale: this._staleFlags[index] || false
|
|
10762
|
+
};
|
|
10763
|
+
if (filter(withMeta)) {
|
|
10764
|
+
results.push(withMeta);
|
|
10759
10765
|
}
|
|
10760
10766
|
}
|
|
10761
10767
|
return results;
|
|
@@ -10766,9 +10772,15 @@ class Database {
|
|
|
10766
10772
|
if (!id)
|
|
10767
10773
|
continue;
|
|
10768
10774
|
const record = this._columns.getRecord(index);
|
|
10769
|
-
const
|
|
10770
|
-
|
|
10771
|
-
|
|
10775
|
+
const withMeta = {
|
|
10776
|
+
id,
|
|
10777
|
+
...record,
|
|
10778
|
+
created: this._created[index] || 0,
|
|
10779
|
+
updated: this._updated[index] || 0,
|
|
10780
|
+
stale: this._staleFlags[index] || false
|
|
10781
|
+
};
|
|
10782
|
+
if (filter(withMeta)) {
|
|
10783
|
+
return withMeta;
|
|
10772
10784
|
}
|
|
10773
10785
|
}
|
|
10774
10786
|
return null;
|
|
@@ -11122,9 +11134,12 @@ class MemoryStore {
|
|
|
11122
11134
|
}
|
|
11123
11135
|
async getProject(projectId) {
|
|
11124
11136
|
if (this._projects.has(projectId)) {
|
|
11137
|
+
console.log(`\uD83D\uDD04 [DEBUG] Returning cached databases for ${projectId}`);
|
|
11125
11138
|
return this._projects.get(projectId);
|
|
11126
11139
|
}
|
|
11140
|
+
console.log(`\uD83C\uDD95 [DEBUG] Creating NEW databases for ${projectId}`);
|
|
11127
11141
|
const projectPath = join2(this._config.basePath, projectId);
|
|
11142
|
+
console.log(` Path: ${projectPath}`);
|
|
11128
11143
|
const [memories, summaries, snapshots, sessions] = await Promise.all([
|
|
11129
11144
|
createDatabase({
|
|
11130
11145
|
path: join2(projectPath, "memories"),
|
|
@@ -11309,20 +11324,31 @@ class MemoryStore {
|
|
|
11309
11324
|
}
|
|
11310
11325
|
async storeSessionSummary(projectId, sessionId, summary, interactionTone = "") {
|
|
11311
11326
|
const { summaries } = await this.getProject(projectId);
|
|
11312
|
-
|
|
11327
|
+
console.log(`\uD83D\uDCDD [DEBUG] Storing summary for ${projectId}:`);
|
|
11328
|
+
console.log(` Summary length: ${summary.length} chars`);
|
|
11329
|
+
console.log(` Summaries count before: ${summaries.all().length}`);
|
|
11330
|
+
const id = await summaries.insert({
|
|
11313
11331
|
session_id: sessionId,
|
|
11314
11332
|
project_id: projectId,
|
|
11315
11333
|
summary,
|
|
11316
11334
|
interaction_tone: interactionTone
|
|
11317
11335
|
});
|
|
11336
|
+
console.log(` Summaries count after: ${summaries.all().length}`);
|
|
11337
|
+
console.log(` Inserted ID: ${id}`);
|
|
11338
|
+
return id;
|
|
11318
11339
|
}
|
|
11319
11340
|
async getLatestSummary(projectId) {
|
|
11320
11341
|
const { summaries } = await this.getProject(projectId);
|
|
11342
|
+
console.log(`\uD83D\uDCD6 [DEBUG] Getting latest summary for ${projectId}:`);
|
|
11321
11343
|
const all = summaries.all();
|
|
11322
|
-
|
|
11344
|
+
console.log(` Summaries found: ${all.length}`);
|
|
11345
|
+
if (!all.length) {
|
|
11346
|
+
console.log(` No summaries found!`);
|
|
11323
11347
|
return null;
|
|
11348
|
+
}
|
|
11324
11349
|
all.sort((a, b) => b.created - a.created);
|
|
11325
11350
|
const latest = all[0];
|
|
11351
|
+
console.log(` Latest summary: ${latest.summary.slice(0, 50)}...`);
|
|
11326
11352
|
return {
|
|
11327
11353
|
id: latest.id,
|
|
11328
11354
|
session_id: latest.session_id,
|
|
@@ -11707,6 +11733,12 @@ class MemoryEngine {
|
|
|
11707
11733
|
}
|
|
11708
11734
|
async _getStore(projectId, projectPath) {
|
|
11709
11735
|
const key = this._config.storageMode === "local" && projectPath ? projectPath : projectId;
|
|
11736
|
+
console.log(`\uD83C\uDFEA [DEBUG] _getStore called:`);
|
|
11737
|
+
console.log(` projectId: ${projectId}`);
|
|
11738
|
+
console.log(` projectPath: ${projectPath}`);
|
|
11739
|
+
console.log(` storageMode: ${this._config.storageMode}`);
|
|
11740
|
+
console.log(` cache key: ${key}`);
|
|
11741
|
+
console.log(` cached: ${this._stores.has(key)}`);
|
|
11710
11742
|
if (this._stores.has(key)) {
|
|
11711
11743
|
return this._stores.get(key);
|
|
11712
11744
|
}
|
|
@@ -11880,14 +11912,31 @@ function createEngine(config) {
|
|
|
11880
11912
|
}
|
|
11881
11913
|
|
|
11882
11914
|
// src/core/curator.ts
|
|
11915
|
+
import { homedir as homedir3 } from "os";
|
|
11916
|
+
import { join as join4 } from "path";
|
|
11917
|
+
import { existsSync } from "fs";
|
|
11918
|
+
function getClaudeCommand() {
|
|
11919
|
+
const envCommand = process.env.CURATOR_COMMAND;
|
|
11920
|
+
if (envCommand) {
|
|
11921
|
+
return envCommand;
|
|
11922
|
+
}
|
|
11923
|
+
const claudeLocal = join4(homedir3(), ".claude", "local", "claude");
|
|
11924
|
+
if (existsSync(claudeLocal)) {
|
|
11925
|
+
return claudeLocal;
|
|
11926
|
+
}
|
|
11927
|
+
return "claude";
|
|
11928
|
+
}
|
|
11929
|
+
|
|
11883
11930
|
class Curator {
|
|
11884
11931
|
_config;
|
|
11885
11932
|
constructor(config = {}) {
|
|
11933
|
+
const cliCommand = config.cliCommand ?? getClaudeCommand();
|
|
11886
11934
|
this._config = {
|
|
11887
11935
|
apiKey: config.apiKey ?? "",
|
|
11888
|
-
cliCommand
|
|
11936
|
+
cliCommand,
|
|
11889
11937
|
cliType: config.cliType ?? "claude-code"
|
|
11890
11938
|
};
|
|
11939
|
+
console.log(`\uD83E\uDDE0 Curator initialized with CLI: ${cliCommand}`);
|
|
11891
11940
|
}
|
|
11892
11941
|
buildCurationPrompt(triggerType = "session_end") {
|
|
11893
11942
|
return `You have just had a conversation. As this session is ending (${triggerType}), please curate memories for the Claude Tools Memory System.
|
|
@@ -12081,33 +12130,96 @@ ${prompt}`
|
|
|
12081
12130
|
return this.parseCurationResponse(content.text);
|
|
12082
12131
|
}
|
|
12083
12132
|
async curateWithCLI(sessionId, triggerType = "session_end", cwd) {
|
|
12084
|
-
const
|
|
12133
|
+
const systemPrompt = this.buildCurationPrompt(triggerType);
|
|
12134
|
+
const userMessage = "This session has ended. Please curate the memories from our conversation according to the instructions in your system prompt. Return ONLY the JSON structure.";
|
|
12085
12135
|
const args = [];
|
|
12086
12136
|
if (this._config.cliType === "claude-code") {
|
|
12087
|
-
args.push("--resume", sessionId, "-p", prompt, "--output-format", "json", "--max-turns", "1");
|
|
12137
|
+
args.push("--resume", sessionId, "-p", userMessage, "--append-system-prompt", systemPrompt, "--output-format", "json", "--max-turns", "1");
|
|
12088
12138
|
} else {
|
|
12089
|
-
args.push("--resume", sessionId, "-p",
|
|
12139
|
+
args.push("--resume", sessionId, "-p", `${systemPrompt}
|
|
12140
|
+
|
|
12141
|
+
${userMessage}`, "--output-format", "json");
|
|
12090
12142
|
}
|
|
12143
|
+
console.log(`
|
|
12144
|
+
\uD83D\uDCCB Executing CLI command:`);
|
|
12145
|
+
console.log(` Command: ${this._config.cliCommand}`);
|
|
12146
|
+
console.log(` Args: --resume ${sessionId} -p [user_message] --append-system-prompt [curation_instructions] --output-format json --max-turns 1`);
|
|
12147
|
+
console.log(` CWD: ${cwd || "not set"}`);
|
|
12148
|
+
console.log(` User message: "${userMessage.slice(0, 50)}..."`);
|
|
12149
|
+
console.log(` System prompt length: ${systemPrompt.length} chars`);
|
|
12091
12150
|
const proc = Bun.spawn([this._config.cliCommand, ...args], {
|
|
12092
12151
|
cwd,
|
|
12093
12152
|
env: {
|
|
12094
12153
|
...process.env,
|
|
12095
12154
|
MEMORY_CURATOR_ACTIVE: "1"
|
|
12096
|
-
}
|
|
12155
|
+
},
|
|
12156
|
+
stderr: "pipe"
|
|
12097
12157
|
});
|
|
12098
|
-
const
|
|
12158
|
+
const [stdout, stderr] = await Promise.all([
|
|
12159
|
+
new Response(proc.stdout).text(),
|
|
12160
|
+
new Response(proc.stderr).text()
|
|
12161
|
+
]);
|
|
12099
12162
|
const exitCode = await proc.exited;
|
|
12163
|
+
console.log(`
|
|
12164
|
+
\uD83D\uDCE4 CLI Response:`);
|
|
12165
|
+
console.log(` Exit code: ${exitCode}`);
|
|
12166
|
+
console.log(` Stdout length: ${stdout.length} chars`);
|
|
12167
|
+
console.log(` Stderr length: ${stderr.length} chars`);
|
|
12168
|
+
if (stderr) {
|
|
12169
|
+
console.log(`
|
|
12170
|
+
⚠️ Stderr output:`);
|
|
12171
|
+
console.log(stderr.slice(0, 1000));
|
|
12172
|
+
}
|
|
12173
|
+
if (stdout) {
|
|
12174
|
+
console.log(`
|
|
12175
|
+
\uD83D\uDCE5 Stdout output (first 500 chars):`);
|
|
12176
|
+
console.log(stdout.slice(0, 500));
|
|
12177
|
+
}
|
|
12100
12178
|
if (exitCode !== 0) {
|
|
12101
|
-
console.error(`
|
|
12179
|
+
console.error(`
|
|
12180
|
+
❌ CLI exited with code ${exitCode}`);
|
|
12102
12181
|
return { session_summary: "", memories: [] };
|
|
12103
12182
|
}
|
|
12104
12183
|
try {
|
|
12105
|
-
const
|
|
12184
|
+
const cliOutput = JSON.parse(stdout);
|
|
12185
|
+
if (cliOutput.type === "error" || cliOutput.is_error === true) {
|
|
12186
|
+
console.log(`
|
|
12187
|
+
❌ CLI returned error:`);
|
|
12188
|
+
console.log(` Type: ${cliOutput.type}`);
|
|
12189
|
+
console.log(` Message: ${cliOutput.message || cliOutput.error || "Unknown error"}`);
|
|
12190
|
+
return { session_summary: "", memories: [] };
|
|
12191
|
+
}
|
|
12192
|
+
let aiResponse = "";
|
|
12193
|
+
if (typeof cliOutput.result === "string") {
|
|
12194
|
+
aiResponse = cliOutput.result;
|
|
12195
|
+
console.log(`
|
|
12196
|
+
\uD83D\uDCE6 Extracted result from CLI wrapper (${aiResponse.length} chars)`);
|
|
12197
|
+
} else {
|
|
12198
|
+
console.log(`
|
|
12199
|
+
⚠️ No result field in CLI output`);
|
|
12200
|
+
console.log(` Keys: ${Object.keys(cliOutput).join(", ")}`);
|
|
12201
|
+
return { session_summary: "", memories: [] };
|
|
12202
|
+
}
|
|
12203
|
+
const codeBlockMatch = aiResponse.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
12204
|
+
if (codeBlockMatch) {
|
|
12205
|
+
aiResponse = codeBlockMatch[1].trim();
|
|
12206
|
+
console.log(`\uD83D\uDCDD Extracted JSON from markdown code block`);
|
|
12207
|
+
}
|
|
12208
|
+
const jsonMatch = aiResponse.match(/\{[\s\S]*\}/)?.[0];
|
|
12106
12209
|
if (jsonMatch) {
|
|
12107
|
-
|
|
12210
|
+
console.log(`✅ Found JSON object (${jsonMatch.length} chars)`);
|
|
12211
|
+
const result = this.parseCurationResponse(jsonMatch);
|
|
12212
|
+
console.log(` Parsed ${result.memories.length} memories`);
|
|
12213
|
+
return result;
|
|
12214
|
+
} else {
|
|
12215
|
+
console.log(`
|
|
12216
|
+
⚠️ No JSON object found in AI response`);
|
|
12217
|
+
console.log(` Response preview: ${aiResponse.slice(0, 200)}...`);
|
|
12108
12218
|
}
|
|
12109
12219
|
} catch (error) {
|
|
12110
|
-
console.error(
|
|
12220
|
+
console.error(`
|
|
12221
|
+
❌ Failed to parse CLI output:`, error);
|
|
12222
|
+
console.log(` Raw stdout (first 500 chars): ${stdout.slice(0, 500)}`);
|
|
12111
12223
|
}
|
|
12112
12224
|
return { session_summary: "", memories: [] };
|
|
12113
12225
|
}
|
|
@@ -12263,6 +12375,17 @@ function createServer(config = {}) {
|
|
|
12263
12375
|
context_type: m2.context_type
|
|
12264
12376
|
})), body.current_message ?? "");
|
|
12265
12377
|
}
|
|
12378
|
+
console.log(`
|
|
12379
|
+
\uD83D\uDCE4 [DEBUG] Response to hook:`);
|
|
12380
|
+
console.log(` memories_count: ${result.memories.length}`);
|
|
12381
|
+
console.log(` has_primer: ${!!result.primer}`);
|
|
12382
|
+
console.log(` formatted length: ${result.formatted.length} chars`);
|
|
12383
|
+
if (result.formatted) {
|
|
12384
|
+
console.log(` formatted preview:
|
|
12385
|
+
${result.formatted.slice(0, 300)}...`);
|
|
12386
|
+
} else {
|
|
12387
|
+
console.log(` ⚠️ formatted is EMPTY!`);
|
|
12388
|
+
}
|
|
12266
12389
|
return Response.json({
|
|
12267
12390
|
success: true,
|
|
12268
12391
|
context: result.formatted,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// CURATION HOOK - Trigger memory curation
|
|
4
|
+
// Hook: PreCompact (auto|manual)
|
|
5
|
+
//
|
|
6
|
+
// Triggers memory curation when context is about to be compacted.
|
|
7
|
+
// This ensures memories are captured before context is lost.
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
import { join } from 'path'
|
|
11
|
+
import { styleText } from 'util'
|
|
12
|
+
|
|
13
|
+
// Configuration
|
|
14
|
+
const MEMORY_API_URL = process.env.MEMORY_API_URL || 'http://localhost:8765'
|
|
15
|
+
|
|
16
|
+
// Styled output helpers (for stderr feedback)
|
|
17
|
+
const info = (text: string) => styleText('cyan', text)
|
|
18
|
+
const success = (text: string) => styleText('green', text)
|
|
19
|
+
const warn = (text: string) => styleText('yellow', text)
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get project ID from working directory
|
|
23
|
+
*/
|
|
24
|
+
function getProjectId(cwd: string): string {
|
|
25
|
+
// Look for .memory-project.json in cwd or parents
|
|
26
|
+
let path = cwd
|
|
27
|
+
while (path !== '/') {
|
|
28
|
+
const configPath = join(path, '.memory-project.json')
|
|
29
|
+
try {
|
|
30
|
+
const config = JSON.parse(Bun.file(configPath).text() as any)
|
|
31
|
+
if (config.project_id) return config.project_id
|
|
32
|
+
} catch {
|
|
33
|
+
// Continue to parent
|
|
34
|
+
}
|
|
35
|
+
path = join(path, '..')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Fallback: use directory name
|
|
39
|
+
return cwd.split('/').pop() || 'default'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Main hook entry point
|
|
44
|
+
*/
|
|
45
|
+
async function main() {
|
|
46
|
+
// Skip if called from memory curator subprocess
|
|
47
|
+
if (process.env.MEMORY_CURATOR_ACTIVE === '1') return
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Read input from stdin
|
|
51
|
+
const inputText = await Bun.stdin.text()
|
|
52
|
+
const input = JSON.parse(inputText)
|
|
53
|
+
|
|
54
|
+
const sessionId = input.session_id || 'unknown'
|
|
55
|
+
const cwd = process.env.CLAUDE_PROJECT_DIR || input.cwd || process.cwd()
|
|
56
|
+
const trigger = input.trigger || 'pre_compact'
|
|
57
|
+
const hookEvent = input.hook_event_name || 'PreCompact'
|
|
58
|
+
|
|
59
|
+
const projectId = getProjectId(cwd)
|
|
60
|
+
|
|
61
|
+
console.error(info(`🧠 Curating memories (${hookEvent})...`))
|
|
62
|
+
|
|
63
|
+
// Fire and forget - trigger curation
|
|
64
|
+
// The server handles the actual curation asynchronously
|
|
65
|
+
const response = await fetch(`${MEMORY_API_URL}/memory/checkpoint`, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: { 'Content-Type': 'application/json' },
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
session_id: sessionId,
|
|
70
|
+
project_id: projectId,
|
|
71
|
+
claude_session_id: sessionId,
|
|
72
|
+
trigger: trigger === 'pre_compact' || trigger === 'manual' ? 'pre_compact' : 'session_end',
|
|
73
|
+
cwd,
|
|
74
|
+
}),
|
|
75
|
+
signal: AbortSignal.timeout(5000),
|
|
76
|
+
}).catch(() => null)
|
|
77
|
+
|
|
78
|
+
if (response?.ok) {
|
|
79
|
+
console.error(success('✨ Memory curation started'))
|
|
80
|
+
} else {
|
|
81
|
+
console.error(warn('⚠️ Memory server not available'))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
} catch (error: any) {
|
|
85
|
+
console.error(warn(`⚠️ Hook error: ${error.message}`))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
main()
|