@bubstack/moe-memory 0.1.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/.mcp.json +18 -0
- package/README.md +47 -0
- package/agents/search-conversations.md +171 -0
- package/dist/LICENSE +25 -0
- package/dist/cli.d.ts +25 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +125 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex-hook-trust.d.ts +4 -0
- package/dist/codex-hook-trust.d.ts.map +1 -0
- package/dist/codex-hook-trust.js +114 -0
- package/dist/codex-hook-trust.js.map +1 -0
- package/dist/codex-support.d.ts +6 -0
- package/dist/codex-support.d.ts.map +1 -0
- package/dist/codex-support.js +29 -0
- package/dist/codex-support.js.map +1 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +14 -0
- package/dist/constants.js.map +1 -0
- package/dist/db.d.ts +103 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +430 -0
- package/dist/db.js.map +1 -0
- package/dist/doctor-cli.d.ts +3 -0
- package/dist/doctor-cli.d.ts.map +1 -0
- package/dist/doctor-cli.js +43 -0
- package/dist/doctor-cli.js.map +1 -0
- package/dist/doctor.d.ts +17 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +98 -0
- package/dist/doctor.js.map +1 -0
- package/dist/embedding-migration.d.ts +73 -0
- package/dist/embedding-migration.d.ts.map +1 -0
- package/dist/embedding-migration.js +120 -0
- package/dist/embedding-migration.js.map +1 -0
- package/dist/embeddings.d.ts +78 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +180 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/file-lock.d.ts +39 -0
- package/dist/file-lock.d.ts.map +1 -0
- package/dist/file-lock.js +91 -0
- package/dist/file-lock.js.map +1 -0
- package/dist/index-cli.d.ts +10 -0
- package/dist/index-cli.d.ts.map +1 -0
- package/dist/index-cli.js +195 -0
- package/dist/index-cli.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer.d.ts +4 -0
- package/dist/indexer.d.ts.map +1 -0
- package/dist/indexer.js +318 -0
- package/dist/indexer.js.map +1 -0
- package/dist/install-check.d.ts +48 -0
- package/dist/install-check.d.ts.map +1 -0
- package/dist/install-check.js +82 -0
- package/dist/install-check.js.map +1 -0
- package/dist/journal/index.d.ts +5 -0
- package/dist/journal/index.d.ts.map +1 -0
- package/dist/journal/index.js +5 -0
- package/dist/journal/index.js.map +1 -0
- package/dist/journal/legacy-sidecars.d.ts +74 -0
- package/dist/journal/legacy-sidecars.d.ts.map +1 -0
- package/dist/journal/legacy-sidecars.js +151 -0
- package/dist/journal/legacy-sidecars.js.map +1 -0
- package/dist/journal/markdown.d.ts +91 -0
- package/dist/journal/markdown.d.ts.map +1 -0
- package/dist/journal/markdown.js +234 -0
- package/dist/journal/markdown.js.map +1 -0
- package/dist/journal/search.d.ts +91 -0
- package/dist/journal/search.d.ts.map +1 -0
- package/dist/journal/search.js +203 -0
- package/dist/journal/search.js.map +1 -0
- package/dist/journal/store.d.ts +96 -0
- package/dist/journal/store.d.ts.map +1 -0
- package/dist/journal/store.js +270 -0
- package/dist/journal/store.js.map +1 -0
- package/dist/journal-cli.d.ts +19 -0
- package/dist/journal-cli.d.ts.map +1 -0
- package/dist/journal-cli.js +153 -0
- package/dist/journal-cli.js.map +1 -0
- package/dist/logging.d.ts +6 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +20 -0
- package/dist/logging.js.map +1 -0
- package/dist/mcp-server.d.ts +34 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +607 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/parser.d.ts +11 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +445 -0
- package/dist/parser.js.map +1 -0
- package/dist/paths.d.ts +103 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +258 -0
- package/dist/paths.js.map +1 -0
- package/dist/search-cli.d.ts +3 -0
- package/dist/search-cli.d.ts.map +1 -0
- package/dist/search-cli.js +138 -0
- package/dist/search-cli.js.map +1 -0
- package/dist/search.d.ts +36 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +371 -0
- package/dist/search.js.map +1 -0
- package/dist/show-cli.d.ts +3 -0
- package/dist/show-cli.d.ts.map +1 -0
- package/dist/show-cli.js +60 -0
- package/dist/show-cli.js.map +1 -0
- package/dist/show.d.ts +3 -0
- package/dist/show.d.ts.map +1 -0
- package/dist/show.js +984 -0
- package/dist/show.js.map +1 -0
- package/dist/stats-cli.d.ts +3 -0
- package/dist/stats-cli.d.ts.map +1 -0
- package/dist/stats-cli.js +51 -0
- package/dist/stats-cli.js.map +1 -0
- package/dist/stats.d.ts +19 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +116 -0
- package/dist/stats.js.map +1 -0
- package/dist/summarizer.d.ts +83 -0
- package/dist/summarizer.d.ts.map +1 -0
- package/dist/summarizer.js +526 -0
- package/dist/summarizer.js.map +1 -0
- package/dist/summary-sentinel.d.ts +31 -0
- package/dist/summary-sentinel.d.ts.map +1 -0
- package/dist/summary-sentinel.js +80 -0
- package/dist/summary-sentinel.js.map +1 -0
- package/dist/sync-cli.d.ts +15 -0
- package/dist/sync-cli.d.ts.map +1 -0
- package/dist/sync-cli.js +202 -0
- package/dist/sync-cli.js.map +1 -0
- package/dist/sync.d.ts +35 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +204 -0
- package/dist/sync.js.map +1 -0
- package/dist/types.d.ts +125 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +32 -0
- package/dist/types.js.map +1 -0
- package/dist/verify.d.ts +30 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +174 -0
- package/dist/verify.js.map +1 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/hooks/hooks.json +15 -0
- package/package.json +71 -0
- package/prompts/search-agent.md +160 -0
- package/skills/remembering-conversations/MCP-TOOLS.md +147 -0
- package/skills/remembering-conversations/SKILL.md +97 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
4
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
5
|
+
import { codexVersionRequirementMessage, parseCodexCliVersion, versionMeetsMinimum, } from "./codex-support.js";
|
|
6
|
+
import { SUMMARIZER_CONTEXT_MARKER } from "./constants.js";
|
|
7
|
+
import { VERSION } from "./version.js";
|
|
8
|
+
/**
|
|
9
|
+
* Thrown by callClaude when the SDK yields an `is_error: true` result message.
|
|
10
|
+
* Carries the SDK's `subtype` and `session_id` as typed fields so callers can
|
|
11
|
+
* dispatch on structural metadata rather than parsing error message text.
|
|
12
|
+
*/
|
|
13
|
+
export class SummarizerSdkError extends Error {
|
|
14
|
+
subtype;
|
|
15
|
+
sessionId;
|
|
16
|
+
constructor(subtype, sessionId) {
|
|
17
|
+
super(`Summarizer SDK error: ${subtype}${sessionId ? ` (session ${sessionId})` : ""}`);
|
|
18
|
+
this.subtype = subtype;
|
|
19
|
+
this.sessionId = sessionId;
|
|
20
|
+
this.name = "SummarizerSdkError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* True when the SDK's reported failure subtype indicates resume couldn't find
|
|
25
|
+
* the session — the trigger for the non-resume fallback in summarizeConversation.
|
|
26
|
+
*/
|
|
27
|
+
export function isResumeFailure(error) {
|
|
28
|
+
return error instanceof SummarizerSdkError && error.subtype === "error_during_execution";
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get API environment overrides for summarization calls.
|
|
32
|
+
* Returns full env merged with process.env so subprocess inherits PATH, HOME, etc.
|
|
33
|
+
*
|
|
34
|
+
* Env vars (all optional):
|
|
35
|
+
* - MOE_MEMORY_API_MODEL: Model to use (default: haiku)
|
|
36
|
+
* - MOE_MEMORY_API_MODEL_FALLBACK: Fallback model on error (default: sonnet)
|
|
37
|
+
* - MOE_MEMORY_API_BASE_URL: Custom API endpoint
|
|
38
|
+
* - MOE_MEMORY_API_TOKEN: Auth token for custom endpoint
|
|
39
|
+
* - MOE_MEMORY_API_TIMEOUT_MS: Timeout for API calls (default: SDK default)
|
|
40
|
+
*/
|
|
41
|
+
export function getApiEnv() {
|
|
42
|
+
const baseUrl = process.env.MOE_MEMORY_API_BASE_URL;
|
|
43
|
+
const token = process.env.MOE_MEMORY_API_TOKEN;
|
|
44
|
+
const timeoutMs = process.env.MOE_MEMORY_API_TIMEOUT_MS;
|
|
45
|
+
// Always include the reentrancy guard so the SDK-spawned Claude subprocess
|
|
46
|
+
// (which inherits this env) marks itself as a reentrant context. The
|
|
47
|
+
// SessionStart hook checks the guard via shouldSkipReentrantSync() and
|
|
48
|
+
// exits before launching another sync, breaking the recursive cascade
|
|
49
|
+
// reported in #87.
|
|
50
|
+
return {
|
|
51
|
+
...process.env,
|
|
52
|
+
MOE_MEMORY_SUMMARIZER_GUARD: "1",
|
|
53
|
+
...(baseUrl && { ANTHROPIC_BASE_URL: baseUrl }),
|
|
54
|
+
...(token && { ANTHROPIC_AUTH_TOKEN: token }),
|
|
55
|
+
...(timeoutMs && { API_TIMEOUT_MS: timeoutMs }),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Detect whether the current process is running inside the Claude Agent SDK
|
|
60
|
+
* subprocess that the summarizer just spawned. The flag is set by getApiEnv()
|
|
61
|
+
* and inherited by the spawned subprocess. Used by sync entry points to bail
|
|
62
|
+
* out before re-entering the sync→summarizer→spawn cycle (#87).
|
|
63
|
+
*/
|
|
64
|
+
export function shouldSkipReentrantSync() {
|
|
65
|
+
return process.env.MOE_MEMORY_SUMMARIZER_GUARD === "1";
|
|
66
|
+
}
|
|
67
|
+
export function formatConversationText(exchanges) {
|
|
68
|
+
return exchanges
|
|
69
|
+
.map((ex) => {
|
|
70
|
+
return `User: ${ex.userMessage}\n\nAgent: ${ex.assistantMessage}`;
|
|
71
|
+
})
|
|
72
|
+
.join("\n\n---\n\n");
|
|
73
|
+
}
|
|
74
|
+
function extractSummary(text) {
|
|
75
|
+
const match = text.match(/<summary>(.*?)<\/summary>/s);
|
|
76
|
+
const captured = match?.[1];
|
|
77
|
+
if (captured !== undefined) {
|
|
78
|
+
return captured.trim();
|
|
79
|
+
}
|
|
80
|
+
// Fallback if no tags found
|
|
81
|
+
return text.trim();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Build the options object passed to the Claude Agent SDK's query() for a
|
|
85
|
+
* summarization call.
|
|
86
|
+
*
|
|
87
|
+
* persistSession: false keeps the SDK from writing its session transcript to
|
|
88
|
+
* ~/.claude/projects/ (#83). Without it, every summarization spawns a fake
|
|
89
|
+
* session JSONL that pollutes the IDE session sidebar. The option is honored
|
|
90
|
+
* by claude-agent-sdk >= 0.2.0.
|
|
91
|
+
*/
|
|
92
|
+
export function buildSummarizerQueryOptions(args) {
|
|
93
|
+
const { model, sessionId, cwd } = args;
|
|
94
|
+
return {
|
|
95
|
+
model,
|
|
96
|
+
max_tokens: 4096,
|
|
97
|
+
env: getApiEnv(),
|
|
98
|
+
resume: sessionId,
|
|
99
|
+
persistSession: false,
|
|
100
|
+
// Resume looks up the session under ~/.claude/projects/<encoded-cwd>/, so pass the recorded cwd when it still exists on disk.
|
|
101
|
+
...(cwd && fs.existsSync(cwd) ? { cwd } : {}),
|
|
102
|
+
// Don't override systemPrompt when resuming — the resumed session's prompt stays in effect.
|
|
103
|
+
...(sessionId
|
|
104
|
+
? {}
|
|
105
|
+
: {
|
|
106
|
+
systemPrompt: 'Write concise, factual summaries. Output ONLY the summary - no preamble, no "Here is", no "I will". Your output will be indexed directly.',
|
|
107
|
+
}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export function buildCodexSummaryPrompt() {
|
|
111
|
+
return `${SUMMARIZER_CONTEXT_MARKER}.
|
|
112
|
+
|
|
113
|
+
You are running in an ephemeral Codex fork of an existing session. Use the forked session context, including available reasoning summaries and thinking context, to write a concise, factual summary of the conversation.
|
|
114
|
+
|
|
115
|
+
Do not inspect files, run commands, search the web, or modify state. Use only the conversation context already available in this forked session.
|
|
116
|
+
|
|
117
|
+
Output ONLY a <summary></summary> block. Summarize what happened in 2-4 sentences.
|
|
118
|
+
|
|
119
|
+
Include:
|
|
120
|
+
- What was built/changed/discussed (be specific)
|
|
121
|
+
- Key technical decisions or approaches
|
|
122
|
+
- Problems solved or current state
|
|
123
|
+
|
|
124
|
+
Exclude:
|
|
125
|
+
- Apologies, meta-commentary, or your questions
|
|
126
|
+
- Raw logs or debug output
|
|
127
|
+
- Generic descriptions - focus on what makes THIS conversation unique
|
|
128
|
+
|
|
129
|
+
Good:
|
|
130
|
+
<summary>Built JWT authentication for React app with refresh tokens and protected routes. Fixed token expiration bug by implementing refresh-during-request logic.</summary>
|
|
131
|
+
|
|
132
|
+
Bad:
|
|
133
|
+
<summary>I apologize. The conversation discussed authentication and various approaches were considered...</summary>`;
|
|
134
|
+
}
|
|
135
|
+
export function buildCodexSummarizerCommand(args) {
|
|
136
|
+
const command = args.codexBin || process.env.MOE_MEMORY_CODEX_BIN || "codex";
|
|
137
|
+
return {
|
|
138
|
+
command,
|
|
139
|
+
args: ["app-server"],
|
|
140
|
+
prompt: args.prompt,
|
|
141
|
+
sessionId: args.sessionId,
|
|
142
|
+
model: args.model,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async function callClaude(prompt, sessionId, useFallback = false, cwd) {
|
|
146
|
+
const primaryModel = process.env.MOE_MEMORY_API_MODEL || "haiku";
|
|
147
|
+
const fallbackModel = process.env.MOE_MEMORY_API_MODEL_FALLBACK || "sonnet";
|
|
148
|
+
const model = useFallback ? fallbackModel : primaryModel;
|
|
149
|
+
for await (const message of query({
|
|
150
|
+
prompt,
|
|
151
|
+
options: buildSummarizerQueryOptions({ model, sessionId, cwd }),
|
|
152
|
+
})) {
|
|
153
|
+
if (message && typeof message === "object" && "type" in message && message.type === "result") {
|
|
154
|
+
// Throw on is_error — otherwise we return `message.result` (undefined) and the SDK's later iterator throw never fires.
|
|
155
|
+
if (message.is_error) {
|
|
156
|
+
throw new SummarizerSdkError(message.subtype || "unknown", message.session_id);
|
|
157
|
+
}
|
|
158
|
+
const result = message.result;
|
|
159
|
+
// Check if result is an API error (SDK returns errors as result strings)
|
|
160
|
+
if (typeof result === "string" &&
|
|
161
|
+
result.includes("API Error") &&
|
|
162
|
+
result.includes("thinking.budget_tokens")) {
|
|
163
|
+
if (!useFallback) {
|
|
164
|
+
console.log(` ${primaryModel} hit thinking budget error, retrying with ${fallbackModel}`);
|
|
165
|
+
return await callClaude(prompt, sessionId, true, cwd);
|
|
166
|
+
}
|
|
167
|
+
// If fallback also fails, return error message
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return "";
|
|
174
|
+
}
|
|
175
|
+
function appServerTimeoutMs() {
|
|
176
|
+
const configured = Number(process.env.MOE_MEMORY_CODEX_SUMMARY_TIMEOUT_MS);
|
|
177
|
+
return Number.isFinite(configured) && configured > 0 ? configured : 120000;
|
|
178
|
+
}
|
|
179
|
+
function readCommandOutput(command, args) {
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
const child = spawn(command, args, {
|
|
182
|
+
env: getApiEnv(),
|
|
183
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
184
|
+
});
|
|
185
|
+
let output = "";
|
|
186
|
+
child.stdout.on("data", (chunk) => {
|
|
187
|
+
output += chunk.toString();
|
|
188
|
+
});
|
|
189
|
+
child.stderr.on("data", (chunk) => {
|
|
190
|
+
output += chunk.toString();
|
|
191
|
+
});
|
|
192
|
+
child.on("error", reject);
|
|
193
|
+
child.on("exit", (code) => {
|
|
194
|
+
if (code === 0) {
|
|
195
|
+
resolve(output);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
reject(new Error(`${command} ${args.join(" ")} failed with exit code ${code}: ${output.trim()}`));
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async function assertSupportedCodexVersion(command) {
|
|
204
|
+
if (command.skipVersionCheck) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const output = await readCommandOutput(command.command, command.versionArgs || ["--version"]);
|
|
208
|
+
const version = parseCodexCliVersion(output);
|
|
209
|
+
if (!version || !versionMeetsMinimum(version)) {
|
|
210
|
+
throw new Error(codexVersionRequirementMessage(output));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function requireThreadId(result, method) {
|
|
214
|
+
const threadId = result?.thread?.id;
|
|
215
|
+
if (typeof threadId !== "string" || !threadId) {
|
|
216
|
+
throw new Error(`${method} returned unexpected response: ${JSON.stringify(result)}`);
|
|
217
|
+
}
|
|
218
|
+
return threadId;
|
|
219
|
+
}
|
|
220
|
+
function requireTurnId(result, method) {
|
|
221
|
+
const turnId = result?.turn?.id;
|
|
222
|
+
if (typeof turnId !== "string" || !turnId) {
|
|
223
|
+
throw new Error(`${method} returned unexpected response: ${JSON.stringify(result)}`);
|
|
224
|
+
}
|
|
225
|
+
return turnId;
|
|
226
|
+
}
|
|
227
|
+
export async function runCodexCommand(command) {
|
|
228
|
+
await assertSupportedCodexVersion(command);
|
|
229
|
+
return new Promise((resolve, reject) => {
|
|
230
|
+
const child = spawn(command.command, command.args, {
|
|
231
|
+
env: getApiEnv(),
|
|
232
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
233
|
+
});
|
|
234
|
+
let stderr = "";
|
|
235
|
+
let answer = "";
|
|
236
|
+
let nextRequestId = 1;
|
|
237
|
+
let targetTurnId;
|
|
238
|
+
let finished = false;
|
|
239
|
+
let timeout;
|
|
240
|
+
const pending = new Map();
|
|
241
|
+
const lines = createInterface({ input: child.stdout });
|
|
242
|
+
child.stderr.on("data", (chunk) => {
|
|
243
|
+
stderr += chunk.toString();
|
|
244
|
+
});
|
|
245
|
+
const cleanup = () => {
|
|
246
|
+
if (timeout) {
|
|
247
|
+
clearTimeout(timeout);
|
|
248
|
+
}
|
|
249
|
+
lines.close();
|
|
250
|
+
if (!child.killed) {
|
|
251
|
+
child.kill("SIGTERM");
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
const finish = (error, result = "") => {
|
|
255
|
+
if (finished)
|
|
256
|
+
return;
|
|
257
|
+
finished = true;
|
|
258
|
+
cleanup();
|
|
259
|
+
if (error) {
|
|
260
|
+
reject(error);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
resolve(result);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
timeout = setTimeout(() => {
|
|
267
|
+
finish(new Error(`Codex summarizer timed out after ${appServerTimeoutMs()}ms: ${stderr.trim()}`));
|
|
268
|
+
}, appServerTimeoutMs());
|
|
269
|
+
const send = (method, params) => {
|
|
270
|
+
const id = nextRequestId++;
|
|
271
|
+
child.stdin.write(`${JSON.stringify({ method, id, params })}\n`);
|
|
272
|
+
return new Promise((resolveRequest, rejectRequest) => {
|
|
273
|
+
pending.set(id, { method, resolve: resolveRequest, reject: rejectRequest });
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
const notify = (method, params) => {
|
|
277
|
+
const message = params === undefined ? { method } : { method, params };
|
|
278
|
+
child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
279
|
+
};
|
|
280
|
+
lines.on("line", (line) => {
|
|
281
|
+
if (!line.trim())
|
|
282
|
+
return;
|
|
283
|
+
let message;
|
|
284
|
+
try {
|
|
285
|
+
message = JSON.parse(line);
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
finish(new Error(`Codex app-server emitted invalid JSON: ${line}`));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (typeof message.id === "number" && pending.has(message.id)) {
|
|
292
|
+
const request = pending.get(message.id);
|
|
293
|
+
pending.delete(message.id);
|
|
294
|
+
if (message.error) {
|
|
295
|
+
request.reject(new Error(`${request.method} failed: ${JSON.stringify(message.error)}`));
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
request.resolve(message.result);
|
|
299
|
+
}
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (message.method === "item/agentMessage/delta") {
|
|
303
|
+
answer += message.params?.delta ?? "";
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (message.method === "item/completed" && message.params?.item?.type === "agentMessage") {
|
|
307
|
+
answer = message.params.item.text ?? answer;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (message.method === "turn/completed" &&
|
|
311
|
+
(!targetTurnId || message.params?.turn?.id === targetTurnId)) {
|
|
312
|
+
if (message.params.turn.status === "completed") {
|
|
313
|
+
finish(undefined, answer);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
const detail = message.params.turn.error?.message || message.params.turn.status;
|
|
317
|
+
finish(new Error(`Codex summarizer turn did not complete: ${detail}`));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
child.on("error", (error) => {
|
|
322
|
+
finish(error);
|
|
323
|
+
});
|
|
324
|
+
child.on("exit", (code) => {
|
|
325
|
+
if (!finished) {
|
|
326
|
+
const detail = code === 0
|
|
327
|
+
? "Codex app-server exited before the summary turn completed"
|
|
328
|
+
: `Codex summarizer failed with exit code ${code}: ${stderr.trim()}`;
|
|
329
|
+
finish(new Error(detail));
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
(async () => {
|
|
333
|
+
try {
|
|
334
|
+
await send("initialize", {
|
|
335
|
+
clientInfo: {
|
|
336
|
+
name: "moe-memory",
|
|
337
|
+
title: "Moe Memory",
|
|
338
|
+
version: VERSION,
|
|
339
|
+
},
|
|
340
|
+
capabilities: {
|
|
341
|
+
experimentalApi: true,
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
notify("initialized");
|
|
345
|
+
const fork = await send("thread/fork", {
|
|
346
|
+
threadId: command.sessionId,
|
|
347
|
+
ephemeral: true,
|
|
348
|
+
sandbox: "read-only",
|
|
349
|
+
approvalPolicy: "never",
|
|
350
|
+
...(command.model ? { model: command.model } : {}),
|
|
351
|
+
});
|
|
352
|
+
const forkThreadId = requireThreadId(fork, "thread/fork");
|
|
353
|
+
const turn = await send("turn/start", {
|
|
354
|
+
threadId: forkThreadId,
|
|
355
|
+
input: [
|
|
356
|
+
{
|
|
357
|
+
type: "text",
|
|
358
|
+
text: command.prompt,
|
|
359
|
+
textElements: [],
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
});
|
|
363
|
+
targetTurnId = requireTurnId(turn, "turn/start");
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
finish(error instanceof Error ? error : new Error(String(error)));
|
|
367
|
+
}
|
|
368
|
+
})();
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
async function callCodex(prompt, sessionId, model) {
|
|
372
|
+
const command = buildCodexSummarizerCommand({ sessionId, prompt, model });
|
|
373
|
+
return runCodexCommand(command);
|
|
374
|
+
}
|
|
375
|
+
function chunkExchanges(exchanges, chunkSize) {
|
|
376
|
+
const chunks = [];
|
|
377
|
+
for (let i = 0; i < exchanges.length; i += chunkSize) {
|
|
378
|
+
chunks.push(exchanges.slice(i, i + chunkSize));
|
|
379
|
+
}
|
|
380
|
+
return chunks;
|
|
381
|
+
}
|
|
382
|
+
function getCodexSessionId(exchanges, sessionId) {
|
|
383
|
+
if (!exchanges.some((exchange) => exchange.harness === "codex")) {
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
return sessionId || exchanges.find((exchange) => exchange.sessionId)?.sessionId;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Resolve the model to pass into Codex `thread/fork` for summarization.
|
|
390
|
+
*
|
|
391
|
+
* Historical exchanges may carry deprecated model ids (e.g. `gpt-5.2-codex`),
|
|
392
|
+
* and `-codex`-suffixed variants are API-key-only — ChatGPT-subscription users
|
|
393
|
+
* get a 400 from `app-server` regardless of the suffix used. Reading the model
|
|
394
|
+
* from history therefore breaks summarization for two large user populations.
|
|
395
|
+
*
|
|
396
|
+
* Default to `undefined` so `app-server` uses the current Codex config
|
|
397
|
+
* (`~/.codex/config.toml#model`). Operators can override via
|
|
398
|
+
* `MOE_MEMORY_CODEX_MODEL` if they need a specific model id (e.g. an
|
|
399
|
+
* API-key user wanting `gpt-5.5-codex`).
|
|
400
|
+
*/
|
|
401
|
+
export function getCodexModel(_exchanges) {
|
|
402
|
+
return process.env.MOE_MEMORY_CODEX_MODEL || undefined;
|
|
403
|
+
}
|
|
404
|
+
export async function summarizeConversation(exchanges, sessionId) {
|
|
405
|
+
// Handle trivial conversations
|
|
406
|
+
if (exchanges.length === 0) {
|
|
407
|
+
return "Trivial conversation with no substantive content.";
|
|
408
|
+
}
|
|
409
|
+
if (exchanges.length === 1) {
|
|
410
|
+
const text = formatConversationText(exchanges);
|
|
411
|
+
if (text.length < 100 || exchanges[0]?.userMessage.trim() === "/exit") {
|
|
412
|
+
return "Trivial conversation with no substantive content.";
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const codexSessionId = getCodexSessionId(exchanges, sessionId);
|
|
416
|
+
if (codexSessionId) {
|
|
417
|
+
try {
|
|
418
|
+
const result = await callCodex(buildCodexSummaryPrompt(), codexSessionId, getCodexModel(exchanges));
|
|
419
|
+
return extractSummary(result);
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
console.log(` Codex summarizer unavailable, falling back to transcript text: ${error instanceof Error ? error.message : String(error)}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// For short conversations (≤15 exchanges), summarize directly
|
|
426
|
+
if (exchanges.length <= 15) {
|
|
427
|
+
const claudeSessionId = codexSessionId ? undefined : sessionId;
|
|
428
|
+
const cwd = claudeSessionId ? exchanges.find((e) => e.cwd)?.cwd : undefined;
|
|
429
|
+
const conversationText = claudeSessionId
|
|
430
|
+
? "" // When resuming, no need to include conversation text - it's already in context
|
|
431
|
+
: formatConversationText(exchanges);
|
|
432
|
+
const prompt = `${SUMMARIZER_CONTEXT_MARKER}.
|
|
433
|
+
|
|
434
|
+
Please write a concise, factual summary of this conversation. Output ONLY the summary - no preamble. Claude will see this summary when searching previous conversations for useful memories and information.
|
|
435
|
+
|
|
436
|
+
Summarize what happened in 2-4 sentences. Be factual and specific. Output in <summary></summary> tags.
|
|
437
|
+
|
|
438
|
+
Include:
|
|
439
|
+
- What was built/changed/discussed (be specific)
|
|
440
|
+
- Key technical decisions or approaches
|
|
441
|
+
- Problems solved or current state
|
|
442
|
+
|
|
443
|
+
Exclude:
|
|
444
|
+
- Apologies, meta-commentary, or your questions
|
|
445
|
+
- Raw logs or debug output
|
|
446
|
+
- Generic descriptions - focus on what makes THIS conversation unique
|
|
447
|
+
|
|
448
|
+
Good:
|
|
449
|
+
<summary>Built JWT authentication for React app with refresh tokens and protected routes. Fixed token expiration bug by implementing refresh-during-request logic.</summary>
|
|
450
|
+
|
|
451
|
+
Bad:
|
|
452
|
+
<summary>I apologize. The conversation discussed authentication and various approaches were considered...</summary>
|
|
453
|
+
|
|
454
|
+
${conversationText}`;
|
|
455
|
+
try {
|
|
456
|
+
const result = await callClaude(prompt, claudeSessionId, false, cwd);
|
|
457
|
+
return extractSummary(result);
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
// Resume fails when the session's cwd doesn't exist on disk — retry without resume and feed the conversation text directly.
|
|
461
|
+
if (claudeSessionId && isResumeFailure(error)) {
|
|
462
|
+
console.log(` resume failed for ${claudeSessionId} (${error.message}); retrying without resume`);
|
|
463
|
+
const fullPrompt = `${prompt}\n\n${formatConversationText(exchanges)}`;
|
|
464
|
+
const result = await callClaude(fullPrompt);
|
|
465
|
+
return extractSummary(result);
|
|
466
|
+
}
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
// For long conversations, use hierarchical summarization
|
|
471
|
+
console.log(` Long conversation (${exchanges.length} exchanges) - using hierarchical summarization`);
|
|
472
|
+
// Note: Hierarchical summarization doesn't support resume mode (needs fresh session for each chunk)
|
|
473
|
+
// This is fine since we only use resume for the main session-end hook
|
|
474
|
+
// Chunk into groups of 8 exchanges
|
|
475
|
+
const chunks = chunkExchanges(exchanges, 8);
|
|
476
|
+
console.log(` Split into ${chunks.length} chunks`);
|
|
477
|
+
// Summarize each chunk
|
|
478
|
+
const chunkSummaries = [];
|
|
479
|
+
for (const [i, chunk] of chunks.entries()) {
|
|
480
|
+
const chunkText = formatConversationText(chunk);
|
|
481
|
+
const prompt = `${SUMMARIZER_CONTEXT_MARKER}.
|
|
482
|
+
|
|
483
|
+
Please write a concise summary of this part of a conversation in 2-3 sentences. What happened, what was built/discussed. Use <summary></summary> tags.
|
|
484
|
+
|
|
485
|
+
${chunkText}
|
|
486
|
+
|
|
487
|
+
Example: <summary>Implemented HID keyboard functionality for ESP32. Hit Bluetooth controller initialization error, fixed by adjusting memory allocation.</summary>`;
|
|
488
|
+
try {
|
|
489
|
+
const summary = await callClaude(prompt); // No sessionId for chunks
|
|
490
|
+
const extracted = extractSummary(summary);
|
|
491
|
+
chunkSummaries.push(extracted);
|
|
492
|
+
console.log(` Chunk ${i + 1}/${chunks.length}: ${extracted.split(/\s+/).length} words`);
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
console.log(` Chunk ${i + 1} failed, skipping`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (chunkSummaries.length === 0) {
|
|
499
|
+
return "Error: Unable to summarize conversation.";
|
|
500
|
+
}
|
|
501
|
+
// Synthesize chunks into final summary
|
|
502
|
+
const synthesisPrompt = `${SUMMARIZER_CONTEXT_MARKER}.
|
|
503
|
+
|
|
504
|
+
Please write a concise, factual summary that synthesizes these part-summaries into one cohesive paragraph. Focus on what was accomplished and any notable technical decisions or challenges. Output in <summary></summary> tags. Claude will see this summary when searching previous conversations for useful memories and information.
|
|
505
|
+
|
|
506
|
+
Part summaries:
|
|
507
|
+
${chunkSummaries.map((s, i) => `${i + 1}. ${s}`).join("\n")}
|
|
508
|
+
|
|
509
|
+
Good:
|
|
510
|
+
<summary>Built conversation search system with JavaScript, sqlite-vec, and local embeddings. Implemented hierarchical summarization for long conversations. System archives conversations permanently and provides semantic search via CLI.</summary>
|
|
511
|
+
|
|
512
|
+
Bad:
|
|
513
|
+
<summary>This conversation synthesizes several topics discussed across multiple parts...</summary>
|
|
514
|
+
|
|
515
|
+
Your summary (max 200 words):`;
|
|
516
|
+
console.log(` Synthesizing final summary...`);
|
|
517
|
+
try {
|
|
518
|
+
const result = await callClaude(synthesisPrompt); // No sessionId for synthesis
|
|
519
|
+
return extractSummary(result);
|
|
520
|
+
}
|
|
521
|
+
catch {
|
|
522
|
+
console.log(` Synthesis failed, using chunk summaries`);
|
|
523
|
+
return chunkSummaries.join(" ");
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
//# sourceMappingURL=summarizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarizer.js","sourceRoot":"","sources":["../src/summarizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel file format for `<archive>/<project>/<session>-summary.txt`:
|
|
3
|
+
*
|
|
4
|
+
* - File missing → conversation has not been processed; queue for summarization.
|
|
5
|
+
* - File empty → permanent skip (zero-exchange / metadata-only file; #91).
|
|
6
|
+
* - File starts with `__ERRORED__\n` → previous summarization failed.
|
|
7
|
+
* Skip-then-retry: if mtime is older than the retry threshold, the file is
|
|
8
|
+
* re-queued; otherwise it's treated as "recently failed, leave alone".
|
|
9
|
+
* - Anything else → real summary content.
|
|
10
|
+
*
|
|
11
|
+
* The error-marker path is what fixes #96: previously a failed summarization
|
|
12
|
+
* wrote no sentinel at all, so the file re-queued every sync run forever and
|
|
13
|
+
* could pin the head of the queue.
|
|
14
|
+
*/
|
|
15
|
+
export declare const ERROR_MARKER = "__ERRORED__";
|
|
16
|
+
export declare function formatErrorSentinel(error: unknown): string;
|
|
17
|
+
export declare function isErroredSentinel(content: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* True when the sentinel at `summaryPath` represents a real summary —
|
|
20
|
+
* a non-empty file that is not an error marker. Empty zero-exchange
|
|
21
|
+
* sentinels and error sentinels both return false. Use this for callers
|
|
22
|
+
* that care about "is this conversation summarized and useful" (stats,
|
|
23
|
+
* verify, search).
|
|
24
|
+
*/
|
|
25
|
+
export declare function hasRealSummary(summaryPath: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* True when the conversation at `summaryPath` should be (re-)summarized:
|
|
28
|
+
* no sentinel yet, or a stale error marker.
|
|
29
|
+
*/
|
|
30
|
+
export declare function shouldQueueForSummary(summaryPath: string): boolean;
|
|
31
|
+
//# sourceMappingURL=summary-sentinel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary-sentinel.d.ts","sourceRoot":"","sources":["../src/summary-sentinel.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,gBAAgB,CAAC;AAI1C,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAG1D;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE1D;AASD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAW3D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAelE"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* Sentinel file format for `<archive>/<project>/<session>-summary.txt`:
|
|
4
|
+
*
|
|
5
|
+
* - File missing → conversation has not been processed; queue for summarization.
|
|
6
|
+
* - File empty → permanent skip (zero-exchange / metadata-only file; #91).
|
|
7
|
+
* - File starts with `__ERRORED__\n` → previous summarization failed.
|
|
8
|
+
* Skip-then-retry: if mtime is older than the retry threshold, the file is
|
|
9
|
+
* re-queued; otherwise it's treated as "recently failed, leave alone".
|
|
10
|
+
* - Anything else → real summary content.
|
|
11
|
+
*
|
|
12
|
+
* The error-marker path is what fixes #96: previously a failed summarization
|
|
13
|
+
* wrote no sentinel at all, so the file re-queued every sync run forever and
|
|
14
|
+
* could pin the head of the queue.
|
|
15
|
+
*/
|
|
16
|
+
export const ERROR_MARKER = "__ERRORED__";
|
|
17
|
+
const ERROR_MARKER_PREFIX = `${ERROR_MARKER}\n`;
|
|
18
|
+
const DEFAULT_RETRY_MS = 3600_000; // 1 hour
|
|
19
|
+
export function formatErrorSentinel(error) {
|
|
20
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
21
|
+
return `${ERROR_MARKER}\n${new Date().toISOString()}\n${message}\n`;
|
|
22
|
+
}
|
|
23
|
+
export function isErroredSentinel(content) {
|
|
24
|
+
return content.startsWith(ERROR_MARKER_PREFIX);
|
|
25
|
+
}
|
|
26
|
+
function getErrorRetryMs() {
|
|
27
|
+
const raw = process.env.MOE_MEMORY_SUMMARY_ERROR_RETRY_HOURS;
|
|
28
|
+
if (!raw)
|
|
29
|
+
return DEFAULT_RETRY_MS;
|
|
30
|
+
const hours = parseFloat(raw);
|
|
31
|
+
return Number.isFinite(hours) && hours > 0 ? hours * 3600_000 : DEFAULT_RETRY_MS;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* True when the sentinel at `summaryPath` represents a real summary —
|
|
35
|
+
* a non-empty file that is not an error marker. Empty zero-exchange
|
|
36
|
+
* sentinels and error sentinels both return false. Use this for callers
|
|
37
|
+
* that care about "is this conversation summarized and useful" (stats,
|
|
38
|
+
* verify, search).
|
|
39
|
+
*/
|
|
40
|
+
export function hasRealSummary(summaryPath) {
|
|
41
|
+
if (!fs.existsSync(summaryPath))
|
|
42
|
+
return false;
|
|
43
|
+
let content;
|
|
44
|
+
try {
|
|
45
|
+
content = fs.readFileSync(summaryPath, "utf-8");
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (content.length === 0)
|
|
51
|
+
return false;
|
|
52
|
+
if (isErroredSentinel(content))
|
|
53
|
+
return false;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* True when the conversation at `summaryPath` should be (re-)summarized:
|
|
58
|
+
* no sentinel yet, or a stale error marker.
|
|
59
|
+
*/
|
|
60
|
+
export function shouldQueueForSummary(summaryPath) {
|
|
61
|
+
if (!fs.existsSync(summaryPath))
|
|
62
|
+
return true;
|
|
63
|
+
let content;
|
|
64
|
+
try {
|
|
65
|
+
content = fs.readFileSync(summaryPath, "utf-8");
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (!isErroredSentinel(content))
|
|
71
|
+
return false;
|
|
72
|
+
try {
|
|
73
|
+
const stat = fs.statSync(summaryPath);
|
|
74
|
+
return Date.now() - stat.mtimeMs >= getErrorRetryMs();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=summary-sentinel.js.map
|