@patravishek/memex 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/LICENSE +21 -0
- package/README.md +399 -0
- package/dist/ai/provider.d.ts +6 -0
- package/dist/ai/provider.js +134 -0
- package/dist/ai/provider.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +475 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/pty-wrapper.d.ts +14 -0
- package/dist/core/pty-wrapper.js +233 -0
- package/dist/core/pty-wrapper.js.map +1 -0
- package/dist/core/session-logger.d.ts +31 -0
- package/dist/core/session-logger.js +124 -0
- package/dist/core/session-logger.js.map +1 -0
- package/dist/memory/compressor.d.ts +4 -0
- package/dist/memory/compressor.js +167 -0
- package/dist/memory/compressor.js.map +1 -0
- package/dist/memory/store.d.ts +42 -0
- package/dist/memory/store.js +145 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/storage/db.d.ts +7 -0
- package/dist/storage/db.js +148 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/storage/queries.d.ts +38 -0
- package/dist/storage/queries.js +271 -0
- package/dist/storage/queries.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.wrapProcess = wrapProcess;
|
|
37
|
+
const child_process = __importStar(require("child_process"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
// Common binary locations — checked in order when shell lookup fails
|
|
42
|
+
const COMMON_BIN_DIRS = [
|
|
43
|
+
"/opt/homebrew/bin",
|
|
44
|
+
"/usr/local/bin",
|
|
45
|
+
"/usr/bin",
|
|
46
|
+
"/bin",
|
|
47
|
+
process.env.HOME ? `${process.env.HOME}/.local/bin` : "",
|
|
48
|
+
].filter(Boolean);
|
|
49
|
+
function resolveCommandPath(command) {
|
|
50
|
+
if (command.startsWith("/"))
|
|
51
|
+
return command;
|
|
52
|
+
try {
|
|
53
|
+
const shell = process.env.SHELL ?? "/bin/zsh";
|
|
54
|
+
const result = child_process.spawnSync(shell, ["-lc", `which ${command}`], {
|
|
55
|
+
encoding: "utf-8",
|
|
56
|
+
timeout: 5000,
|
|
57
|
+
});
|
|
58
|
+
const resolved = result.stdout?.trim().split("\n")[0];
|
|
59
|
+
if (resolved && resolved.startsWith("/"))
|
|
60
|
+
return resolved;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// fall through
|
|
64
|
+
}
|
|
65
|
+
for (const dir of COMMON_BIN_DIRS) {
|
|
66
|
+
const candidate = path.join(dir, command);
|
|
67
|
+
try {
|
|
68
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
69
|
+
return candidate;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// keep scanning
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return command;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Primary strategy: record via macOS `script` command (no native bindings).
|
|
79
|
+
* `script -q <logfile> <cmd>` transparently records all terminal I/O.
|
|
80
|
+
*/
|
|
81
|
+
async function wrapWithScript(logger, options, resolvedCommand) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
// script records raw terminal output; we use a separate txt file for it
|
|
84
|
+
const rawLogPath = logger.getLogPath().replace(".jsonl", "-raw.txt");
|
|
85
|
+
// If we need to inject a resume prompt, write it to a temp file
|
|
86
|
+
// and prepend a note so the agent reads it on first turn
|
|
87
|
+
let injectFile = null;
|
|
88
|
+
if (options.injectOnReady) {
|
|
89
|
+
injectFile = path.join(os.tmpdir(), `memex-context-${Date.now()}.txt`);
|
|
90
|
+
fs.writeFileSync(injectFile, options.injectOnReady, "utf-8");
|
|
91
|
+
}
|
|
92
|
+
const args = options.args ?? [];
|
|
93
|
+
// macOS `script` syntax: script [-q] [-F pipe] [file [command [args]]]
|
|
94
|
+
// -q = quiet (no typescript start/stop messages)
|
|
95
|
+
const scriptArgs = ["-q", rawLogPath, resolvedCommand, ...args];
|
|
96
|
+
const proc = child_process.spawn("script", scriptArgs, {
|
|
97
|
+
stdio: "inherit",
|
|
98
|
+
cwd: options.cwd ?? process.cwd(),
|
|
99
|
+
env: {
|
|
100
|
+
...sanitizeEnv(process.env),
|
|
101
|
+
...(injectFile ? { MEMEX_CONTEXT_FILE: injectFile } : {}),
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
// If injecting a resume prompt, auto-type it via a stdin write after delay
|
|
105
|
+
if (injectFile && options.injectOnReady) {
|
|
106
|
+
const delay = options.injectDelayMs ?? 3000;
|
|
107
|
+
const text = options.injectOnReady;
|
|
108
|
+
setTimeout(() => {
|
|
109
|
+
try {
|
|
110
|
+
if (proc.stdin) {
|
|
111
|
+
proc.stdin.write(text + "\n");
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// stdin is inherited — write directly to process.stdin
|
|
115
|
+
process.stdin.write(text + "\n");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// ignore — session may have ended
|
|
120
|
+
}
|
|
121
|
+
}, delay);
|
|
122
|
+
}
|
|
123
|
+
proc.on("error", (err) => {
|
|
124
|
+
reject(new Error(`Failed to start session: ${err.message}`));
|
|
125
|
+
});
|
|
126
|
+
proc.on("close", (code) => {
|
|
127
|
+
// Parse the raw script log into a readable transcript
|
|
128
|
+
const transcript = parseScriptLog(rawLogPath);
|
|
129
|
+
// Feed into session logger so it's saved in structured format
|
|
130
|
+
transcript.split("\n").forEach((line) => {
|
|
131
|
+
if (line.trim())
|
|
132
|
+
logger.logOutput(line);
|
|
133
|
+
});
|
|
134
|
+
logger.close();
|
|
135
|
+
if (injectFile && fs.existsSync(injectFile)) {
|
|
136
|
+
fs.unlinkSync(injectFile);
|
|
137
|
+
}
|
|
138
|
+
resolve({
|
|
139
|
+
exitCode: code ?? 0,
|
|
140
|
+
transcript,
|
|
141
|
+
logPath: logger.getLogPath(),
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Fallback strategy: spawn directly with inherited stdio.
|
|
148
|
+
* No transcript capture — compression will work from minimal context.
|
|
149
|
+
*/
|
|
150
|
+
async function wrapWithSpawn(logger, options, resolvedCommand) {
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
const proc = child_process.spawn(resolvedCommand, options.args ?? [], {
|
|
153
|
+
stdio: "inherit",
|
|
154
|
+
cwd: options.cwd ?? process.cwd(),
|
|
155
|
+
env: sanitizeEnv(process.env),
|
|
156
|
+
});
|
|
157
|
+
proc.on("error", (err) => {
|
|
158
|
+
reject(new Error(`Could not start "${resolvedCommand}": ${err.message}\n` +
|
|
159
|
+
`Verify it is installed: which ${options.command}`));
|
|
160
|
+
});
|
|
161
|
+
proc.on("close", (code) => {
|
|
162
|
+
logger.close();
|
|
163
|
+
resolve({
|
|
164
|
+
exitCode: code ?? 0,
|
|
165
|
+
transcript: logger.getTranscript(),
|
|
166
|
+
logPath: logger.getLogPath(),
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Strip Memex-internal API keys from the environment before passing it to the
|
|
173
|
+
* wrapped agent. This prevents conflicts like Claude CLI warning about
|
|
174
|
+
* ANTHROPIC_API_KEY clashing with a claude.ai login session.
|
|
175
|
+
*/
|
|
176
|
+
function sanitizeEnv(env) {
|
|
177
|
+
const MEMEX_ONLY_KEYS = [
|
|
178
|
+
"ANTHROPIC_API_KEY",
|
|
179
|
+
"OPENAI_API_KEY",
|
|
180
|
+
"LITELLM_API_KEY",
|
|
181
|
+
"LITELLM_BASE_URL",
|
|
182
|
+
"LITELLM_MODEL",
|
|
183
|
+
"LITELLM_TEAM_ID",
|
|
184
|
+
"ANTHROPIC_MODEL",
|
|
185
|
+
"OPENAI_MODEL",
|
|
186
|
+
"AI_PROVIDER",
|
|
187
|
+
];
|
|
188
|
+
return Object.fromEntries(Object.entries(env)
|
|
189
|
+
.filter(([key]) => !MEMEX_ONLY_KEYS.includes(key))
|
|
190
|
+
.filter((entry) => entry[1] !== undefined));
|
|
191
|
+
}
|
|
192
|
+
async function wrapProcess(logger, options) {
|
|
193
|
+
const resolvedCommand = resolveCommandPath(options.command);
|
|
194
|
+
// Check the command actually exists before attempting anything
|
|
195
|
+
try {
|
|
196
|
+
fs.accessSync(resolvedCommand, fs.constants.X_OK);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
throw new Error(`Command "${options.command}" not found or not executable.\n` +
|
|
200
|
+
`Expected at: ${resolvedCommand}\n` +
|
|
201
|
+
`Run: which ${options.command}`);
|
|
202
|
+
}
|
|
203
|
+
// Try script-based recording first (macOS built-in, no native deps)
|
|
204
|
+
try {
|
|
205
|
+
return await wrapWithScript(logger, options, resolvedCommand);
|
|
206
|
+
}
|
|
207
|
+
catch (scriptErr) {
|
|
208
|
+
// Fall back to plain spawn with inherited stdio
|
|
209
|
+
console.error(`\n [memex] script recording unavailable, falling back to direct spawn`);
|
|
210
|
+
return await wrapWithSpawn(logger, options, resolvedCommand);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function parseScriptLog(logPath) {
|
|
214
|
+
if (!fs.existsSync(logPath))
|
|
215
|
+
return "";
|
|
216
|
+
try {
|
|
217
|
+
const raw = fs.readFileSync(logPath, "utf-8");
|
|
218
|
+
// Strip ANSI escape codes and carriage returns
|
|
219
|
+
const clean = raw
|
|
220
|
+
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "")
|
|
221
|
+
.replace(/\x1b\[[0-9;]*m/g, "")
|
|
222
|
+
.replace(/\r\n/g, "\n")
|
|
223
|
+
.replace(/\r/g, "\n")
|
|
224
|
+
.replace(/[^\x20-\x7E\n\t]/g, "")
|
|
225
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
226
|
+
.trim();
|
|
227
|
+
return clean;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return "";
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=pty-wrapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pty-wrapper.js","sourceRoot":"","sources":["../../src/core/pty-wrapper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface LogEntry {
|
|
2
|
+
ts: number;
|
|
3
|
+
source: "user" | "agent";
|
|
4
|
+
raw: string;
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ConversationTurn {
|
|
8
|
+
role: "user" | "assistant";
|
|
9
|
+
content: string;
|
|
10
|
+
ts: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class SessionLogger {
|
|
13
|
+
private logPath;
|
|
14
|
+
private buffer;
|
|
15
|
+
private writeStream;
|
|
16
|
+
private inputBuffer;
|
|
17
|
+
constructor(memexDir: string);
|
|
18
|
+
logOutput(raw: string): void;
|
|
19
|
+
logInput(raw: string): void;
|
|
20
|
+
getTranscript(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Collapse sequential entries from the same source into conversation turns.
|
|
23
|
+
* This simulates the message structure of the original session so it can be
|
|
24
|
+
* re-injected as context on resume — replicating Claude's --resume behaviour
|
|
25
|
+
* without depending on Anthropic's server-side session storage.
|
|
26
|
+
*/
|
|
27
|
+
getConversationTurns(): ConversationTurn[];
|
|
28
|
+
getLogPath(): string;
|
|
29
|
+
getEntryCount(): number;
|
|
30
|
+
close(): void;
|
|
31
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.SessionLogger = void 0;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
// @ts-ignore - strip-ansi v6 CJS has no types issue
|
|
43
|
+
const strip_ansi_1 = __importDefault(require("strip-ansi"));
|
|
44
|
+
class SessionLogger {
|
|
45
|
+
logPath;
|
|
46
|
+
buffer = [];
|
|
47
|
+
writeStream;
|
|
48
|
+
inputBuffer = "";
|
|
49
|
+
constructor(memexDir) {
|
|
50
|
+
const sessionsDir = path.join(memexDir, "sessions");
|
|
51
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
52
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
53
|
+
this.logPath = path.join(sessionsDir, `${timestamp}.jsonl`);
|
|
54
|
+
this.writeStream = fs.createWriteStream(this.logPath, { flags: "a" });
|
|
55
|
+
}
|
|
56
|
+
logOutput(raw) {
|
|
57
|
+
const text = (0, strip_ansi_1.default)(raw).trim();
|
|
58
|
+
if (!text)
|
|
59
|
+
return;
|
|
60
|
+
const entry = {
|
|
61
|
+
ts: Date.now(),
|
|
62
|
+
source: "agent",
|
|
63
|
+
raw,
|
|
64
|
+
text,
|
|
65
|
+
};
|
|
66
|
+
this.buffer.push(entry);
|
|
67
|
+
this.writeStream.write(JSON.stringify(entry) + "\n");
|
|
68
|
+
}
|
|
69
|
+
logInput(raw) {
|
|
70
|
+
// Buffer input chars and flush on newline
|
|
71
|
+
this.inputBuffer += raw;
|
|
72
|
+
if (this.inputBuffer.includes("\r") || this.inputBuffer.includes("\n")) {
|
|
73
|
+
const text = (0, strip_ansi_1.default)(this.inputBuffer).replace(/[\r\n]/g, "").trim();
|
|
74
|
+
this.inputBuffer = "";
|
|
75
|
+
if (!text)
|
|
76
|
+
return;
|
|
77
|
+
const entry = {
|
|
78
|
+
ts: Date.now(),
|
|
79
|
+
source: "user",
|
|
80
|
+
raw,
|
|
81
|
+
text,
|
|
82
|
+
};
|
|
83
|
+
this.buffer.push(entry);
|
|
84
|
+
this.writeStream.write(JSON.stringify(entry) + "\n");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
getTranscript() {
|
|
88
|
+
return this.buffer
|
|
89
|
+
.map((e) => `[${e.source.toUpperCase()}]: ${e.text}`)
|
|
90
|
+
.join("\n");
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Collapse sequential entries from the same source into conversation turns.
|
|
94
|
+
* This simulates the message structure of the original session so it can be
|
|
95
|
+
* re-injected as context on resume — replicating Claude's --resume behaviour
|
|
96
|
+
* without depending on Anthropic's server-side session storage.
|
|
97
|
+
*/
|
|
98
|
+
getConversationTurns() {
|
|
99
|
+
const turns = [];
|
|
100
|
+
for (const entry of this.buffer) {
|
|
101
|
+
const role = entry.source === "user" ? "user" : "assistant";
|
|
102
|
+
const last = turns[turns.length - 1];
|
|
103
|
+
if (last && last.role === role) {
|
|
104
|
+
// Merge consecutive messages from the same role into one turn
|
|
105
|
+
last.content += "\n" + entry.text;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
turns.push({ role, content: entry.text, ts: entry.ts });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return turns;
|
|
112
|
+
}
|
|
113
|
+
getLogPath() {
|
|
114
|
+
return this.logPath;
|
|
115
|
+
}
|
|
116
|
+
getEntryCount() {
|
|
117
|
+
return this.buffer.length;
|
|
118
|
+
}
|
|
119
|
+
close() {
|
|
120
|
+
this.writeStream.end();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.SessionLogger = SessionLogger;
|
|
124
|
+
//# sourceMappingURL=session-logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-logger.js","sourceRoot":"","sources":["../../src/core/session-logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ProjectMemory } from "./store.js";
|
|
2
|
+
export declare function compressSession(transcript: string, projectPath: string, logFile: string): Promise<ProjectMemory>;
|
|
3
|
+
export declare function buildResumePrompt(memory: ProjectMemory): string;
|
|
4
|
+
export declare function writeResumeFile(memory: ProjectMemory, filePath: string): void;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compressSession = compressSession;
|
|
4
|
+
exports.buildResumePrompt = buildResumePrompt;
|
|
5
|
+
exports.writeResumeFile = writeResumeFile;
|
|
6
|
+
const provider_js_1 = require("../ai/provider.js");
|
|
7
|
+
const store_js_1 = require("./store.js");
|
|
8
|
+
const SYSTEM_PROMPT = `You are a memory manager for an AI coding agent.
|
|
9
|
+
Your job is to extract and maintain a structured understanding of a software project
|
|
10
|
+
from conversation transcripts between a developer and an AI assistant.
|
|
11
|
+
|
|
12
|
+
Be concise, accurate, and practical. Focus on what would actually help the AI
|
|
13
|
+
resume work on this project without losing context. Output must always be valid JSON.`;
|
|
14
|
+
async function compressSession(transcript, projectPath, logFile) {
|
|
15
|
+
const existing = (0, store_js_1.loadMemory)(projectPath) ?? (0, store_js_1.initMemory)(projectPath);
|
|
16
|
+
const existingContext = existing.description
|
|
17
|
+
? `Existing project memory:\n${JSON.stringify(existing, null, 2)}`
|
|
18
|
+
: "No existing memory for this project.";
|
|
19
|
+
const response = await (0, provider_js_1.chat)([
|
|
20
|
+
{
|
|
21
|
+
role: "user",
|
|
22
|
+
content: `Analyze this conversation transcript and update the project memory.
|
|
23
|
+
|
|
24
|
+
${existingContext}
|
|
25
|
+
|
|
26
|
+
--- TRANSCRIPT ---
|
|
27
|
+
${transcript.slice(-12000)}
|
|
28
|
+
--- END TRANSCRIPT ---
|
|
29
|
+
|
|
30
|
+
Return an updated JSON object merging existing memory with new information from this session.
|
|
31
|
+
Schema:
|
|
32
|
+
{
|
|
33
|
+
"projectName": "string",
|
|
34
|
+
"projectPath": "${projectPath}",
|
|
35
|
+
"stack": ["array of tech stack items"],
|
|
36
|
+
"description": "string - what this project does",
|
|
37
|
+
"keyDecisions": [
|
|
38
|
+
{ "decision": "string", "reason": "string", "date": "ISO date string" }
|
|
39
|
+
],
|
|
40
|
+
"currentFocus": "string - what is being worked on right now",
|
|
41
|
+
"pendingTasks": ["string array - tasks mentioned but not completed"],
|
|
42
|
+
"importantFiles": [
|
|
43
|
+
{ "filePath": "string", "purpose": "string" }
|
|
44
|
+
],
|
|
45
|
+
"gotchas": ["string array - problems encountered, mistakes made, things to avoid"],
|
|
46
|
+
"recentSessions": ${JSON.stringify(existing.recentSessions ?? [])},
|
|
47
|
+
"lastUpdated": "${new Date().toISOString()}"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Add a new entry to recentSessions:
|
|
51
|
+
{ "date": "${new Date().toISOString()}", "summary": "2-3 sentence summary of this session", "logFile": "${logFile}" }
|
|
52
|
+
|
|
53
|
+
Keep recentSessions to the last 5 entries only.
|
|
54
|
+
Return ONLY the JSON, no markdown, no explanation.`,
|
|
55
|
+
},
|
|
56
|
+
], SYSTEM_PROMPT);
|
|
57
|
+
try {
|
|
58
|
+
// Strip markdown code fences if the AI wrapped the JSON in them
|
|
59
|
+
const cleaned = response
|
|
60
|
+
.trim()
|
|
61
|
+
.replace(/^```(?:json)?\s*/i, "")
|
|
62
|
+
.replace(/\s*```$/, "")
|
|
63
|
+
.trim();
|
|
64
|
+
const updated = JSON.parse(cleaned);
|
|
65
|
+
(0, store_js_1.saveMemory)(projectPath, updated);
|
|
66
|
+
return updated;
|
|
67
|
+
}
|
|
68
|
+
catch (parseErr) {
|
|
69
|
+
const reason = response.trim().length === 0
|
|
70
|
+
? "AI returned an empty response — is your API key set correctly?"
|
|
71
|
+
: `Could not parse AI response as JSON: ${parseErr.message}`;
|
|
72
|
+
existing.recentSessions.push({
|
|
73
|
+
date: new Date().toISOString(),
|
|
74
|
+
summary: `Session recorded but compression failed — ${reason}`,
|
|
75
|
+
logFile,
|
|
76
|
+
});
|
|
77
|
+
if (existing.recentSessions.length > 5) {
|
|
78
|
+
existing.recentSessions = existing.recentSessions.slice(-5);
|
|
79
|
+
}
|
|
80
|
+
(0, store_js_1.saveMemory)(projectPath, existing);
|
|
81
|
+
// Re-throw so the CLI can surface the real reason to the user
|
|
82
|
+
throw new Error(reason);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function buildResumePrompt(memory) {
|
|
86
|
+
const lastSession = memory.recentSessions.at(-1);
|
|
87
|
+
const lastDate = lastSession
|
|
88
|
+
? new Date(lastSession.date).toLocaleString()
|
|
89
|
+
: "unknown";
|
|
90
|
+
const sections = [
|
|
91
|
+
`Project: ${memory.projectName}`,
|
|
92
|
+
];
|
|
93
|
+
if (memory.description)
|
|
94
|
+
sections.push(`Description: ${memory.description}`);
|
|
95
|
+
if (memory.stack.length)
|
|
96
|
+
sections.push(`Stack: ${memory.stack.join(", ")}`);
|
|
97
|
+
if (memory.currentFocus)
|
|
98
|
+
sections.push(`Current focus: ${memory.currentFocus}`);
|
|
99
|
+
if (memory.pendingTasks.length) {
|
|
100
|
+
sections.push(`Pending tasks:\n${memory.pendingTasks.map((t) => `- ${t}`).join("\n")}`);
|
|
101
|
+
}
|
|
102
|
+
if (memory.keyDecisions.length) {
|
|
103
|
+
sections.push(`Key decisions:\n${memory.keyDecisions.map((d) => `- ${d.decision} (${d.reason})`).join("\n")}`);
|
|
104
|
+
}
|
|
105
|
+
if (memory.gotchas.length) {
|
|
106
|
+
sections.push(`Gotchas to avoid:\n${memory.gotchas.map((g) => `- ${g}`).join("\n")}`);
|
|
107
|
+
}
|
|
108
|
+
if (memory.importantFiles.length) {
|
|
109
|
+
sections.push(`Important files:\n${memory.importantFiles.map((f) => `- ${f.filePath}: ${f.purpose}`).join("\n")}`);
|
|
110
|
+
}
|
|
111
|
+
if (lastSession) {
|
|
112
|
+
sections.push(`Last session (${lastDate}):\n${lastSession.summary}`);
|
|
113
|
+
}
|
|
114
|
+
return [
|
|
115
|
+
"# Memex — Session Context",
|
|
116
|
+
"",
|
|
117
|
+
"> This file was written by Memex before this session started.",
|
|
118
|
+
"> **When you start, read this fully, then immediately say:**",
|
|
119
|
+
"> _\"Continuing from our last session — [one sentence on where we left off]. Here's what I have in memory: [brief summary]. What would you like to work on?\"_",
|
|
120
|
+
"",
|
|
121
|
+
"---",
|
|
122
|
+
"",
|
|
123
|
+
sections.join("\n\n"),
|
|
124
|
+
].join("\n");
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Write resume context to a markdown file so it can be read cleanly
|
|
128
|
+
* without being piped through stdin (which causes formatting issues).
|
|
129
|
+
* Includes the recent conversation history if available, simulating
|
|
130
|
+
* Claude's --resume behaviour without relying on server-side sessions.
|
|
131
|
+
*/
|
|
132
|
+
const CLAUDE_MD_CHAR_LIMIT = 35000; // Stay safely under Claude's 40k warning
|
|
133
|
+
const MAX_TURNS_IN_CLAUDE_MD = 10; // Last N turns of conversation to inject
|
|
134
|
+
const MAX_TURN_CHARS = 1500; // Truncate individual long turns
|
|
135
|
+
function writeResumeFile(memory, filePath) {
|
|
136
|
+
const fs = require("fs");
|
|
137
|
+
const lines = [buildResumePrompt(memory)];
|
|
138
|
+
if (memory.lastConversation && memory.lastConversation.length > 0) {
|
|
139
|
+
const recentTurns = memory.lastConversation.slice(-MAX_TURNS_IN_CLAUDE_MD);
|
|
140
|
+
lines.push("");
|
|
141
|
+
lines.push("---");
|
|
142
|
+
lines.push("");
|
|
143
|
+
lines.push(`Last ${recentTurns.length} conversation turns (use to understand where we left off):`);
|
|
144
|
+
lines.push("");
|
|
145
|
+
for (const turn of recentTurns) {
|
|
146
|
+
const label = turn.role === "user" ? "Human" : "Assistant";
|
|
147
|
+
const content = turn.content.length > MAX_TURN_CHARS
|
|
148
|
+
? turn.content.slice(0, MAX_TURN_CHARS) + "… [truncated]"
|
|
149
|
+
: turn.content;
|
|
150
|
+
lines.push(`**${label}:** ${content}`);
|
|
151
|
+
lines.push("");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Hard cap — truncate from the bottom (trim conversation history, keep memory)
|
|
155
|
+
let content = lines.join("\n");
|
|
156
|
+
if (content.length > CLAUDE_MD_CHAR_LIMIT) {
|
|
157
|
+
content = content.slice(0, CLAUDE_MD_CHAR_LIMIT);
|
|
158
|
+
// Ensure we don't cut mid-line
|
|
159
|
+
const lastNewline = content.lastIndexOf("\n");
|
|
160
|
+
if (lastNewline > CLAUDE_MD_CHAR_LIMIT * 0.8) {
|
|
161
|
+
content = content.slice(0, lastNewline);
|
|
162
|
+
}
|
|
163
|
+
content += "\n\n> [Memex: conversation history truncated to fit size limit]";
|
|
164
|
+
}
|
|
165
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=compressor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compressor.js","sourceRoot":"","sources":["../../src/memory/compressor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ConversationTurn } from "../core/session-logger.js";
|
|
2
|
+
export interface KeyDecision {
|
|
3
|
+
decision: string;
|
|
4
|
+
reason: string;
|
|
5
|
+
date: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ImportantFile {
|
|
8
|
+
filePath: string;
|
|
9
|
+
purpose: string;
|
|
10
|
+
}
|
|
11
|
+
export interface RecentSession {
|
|
12
|
+
date: string;
|
|
13
|
+
summary: string;
|
|
14
|
+
logFile: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ProjectMemory {
|
|
17
|
+
projectName: string;
|
|
18
|
+
projectPath: string;
|
|
19
|
+
stack: string[];
|
|
20
|
+
description: string;
|
|
21
|
+
keyDecisions: KeyDecision[];
|
|
22
|
+
currentFocus: string;
|
|
23
|
+
pendingTasks: string[];
|
|
24
|
+
importantFiles: ImportantFile[];
|
|
25
|
+
/** Things that went wrong or surprised us — prevents repeating mistakes */
|
|
26
|
+
gotchas: string[];
|
|
27
|
+
recentSessions: RecentSession[];
|
|
28
|
+
/**
|
|
29
|
+
* Last N conversation turns from the most recent session.
|
|
30
|
+
* Used to simulate Claude's --resume without relying on server-side
|
|
31
|
+
* session storage that expires. Never expires, works across machines.
|
|
32
|
+
*/
|
|
33
|
+
lastConversation: ConversationTurn[];
|
|
34
|
+
lastUpdated: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function getMemexDir(projectPath: string): string;
|
|
37
|
+
export declare function memoryExists(projectPath: string): boolean;
|
|
38
|
+
export declare function loadMemory(projectPath: string): ProjectMemory | null;
|
|
39
|
+
export declare function saveMemory(projectPath: string, memory: ProjectMemory): void;
|
|
40
|
+
export declare function initMemory(projectPath: string): ProjectMemory;
|
|
41
|
+
export declare function clearMemory(projectPath: string, keepSessions?: boolean): void;
|
|
42
|
+
export declare function formatMemoryForPrompt(memory: ProjectMemory): string;
|