@ephemushroom/opencode-claude-mem 0.6.2 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -562
- package/dist/capture-status.d.ts +26 -0
- package/dist/cli.js +370 -352
- package/dist/official-sync.d.ts +29 -0
- package/dist/server.d.ts +70 -0
- package/dist/server.js +1299 -0
- package/dist/sidebar-model.d.ts +33 -5
- package/dist/tui.js +280 -352
- package/dist/worker-client.d.ts +52 -90
- package/dist/worker-start.d.ts +46 -0
- package/dist/worker-tools.d.ts +33 -0
- package/package.json +10 -23
- package/dist/file-context.d.ts +0 -17
- package/dist/index.d.ts +0 -20
- package/dist/index.js +0 -13844
- package/dist/memory-options.d.ts +0 -12
- package/dist/memory-session.d.ts +0 -22
- package/dist/memory-turn.d.ts +0 -10
- package/dist/memory-worker.d.ts +0 -21
- package/dist/package-server.d.ts +0 -7
- package/dist/package-server.js +0 -10
- package/dist/shared.d.ts +0 -14
- package/dist/tui-registration.d.ts +0 -16
- package/dist/v2.d.ts +0 -22
- package/dist/v2.js +0 -5133
package/dist/cli.js
CHANGED
|
@@ -1,57 +1,88 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// src/
|
|
3
|
-
import {
|
|
2
|
+
// src/capture-status.ts
|
|
3
|
+
import { createHash, randomUUID } from "crypto";
|
|
4
|
+
import { join, resolve } from "path";
|
|
5
|
+
import {
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
realpathSync,
|
|
10
|
+
renameSync,
|
|
11
|
+
rmSync,
|
|
12
|
+
statSync,
|
|
13
|
+
writeFileSync
|
|
14
|
+
} from "fs";
|
|
4
15
|
import { homedir } from "os";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
result += text.slice(offset, match.index);
|
|
16
|
+
function cacheDirectory(env = process.env, home = homedir()) {
|
|
17
|
+
return join(env["XDG_CACHE_HOME"]?.trim() || join(home, ".cache"), "opencode-claude-mem");
|
|
18
|
+
}
|
|
19
|
+
function statusKey(directory) {
|
|
20
|
+
let resolved = resolve(directory);
|
|
21
|
+
try {
|
|
22
|
+
resolved = realpathSync(directory);
|
|
23
|
+
} catch {}
|
|
24
|
+
return createHash("sha256").update(resolved).digest("hex").slice(0, 16);
|
|
25
|
+
}
|
|
26
|
+
function isAlive(pid) {
|
|
27
|
+
try {
|
|
28
|
+
process.kill(pid, 0);
|
|
29
|
+
return true;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
return error.code === "EPERM";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function parseStatus(file) {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
37
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
38
|
+
return null;
|
|
29
39
|
}
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
const { state, message, pid } = parsed;
|
|
41
|
+
return (state === "ok" || state === "problem") && typeof message === "string" && typeof pid === "number" && Number.isInteger(pid) && pid > 0 ? { state, message, pid } : null;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function readCaptureStatus(directory, dir = cacheDirectory()) {
|
|
47
|
+
const folder = join(dir, "status");
|
|
48
|
+
const prefix = `${statusKey(directory)}.`;
|
|
49
|
+
let names = [];
|
|
50
|
+
try {
|
|
51
|
+
names = readdirSync(folder).filter((name) => name.startsWith(prefix) && name.endsWith(".json"));
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
let best = null;
|
|
56
|
+
for (const name of names) {
|
|
57
|
+
const file = join(folder, name);
|
|
58
|
+
const parsed = parseStatus(file);
|
|
59
|
+
if (!parsed || !isAlive(parsed.pid)) {
|
|
60
|
+
rmSync(file, { force: true });
|
|
35
61
|
} else {
|
|
36
|
-
|
|
62
|
+
const own = parsed.pid === process.pid;
|
|
63
|
+
const at = statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? 0;
|
|
64
|
+
if (!best || own && !best.own || own === best.own && at > best.at) {
|
|
65
|
+
best = { status: { state: parsed.state, message: parsed.message }, own, at };
|
|
66
|
+
}
|
|
37
67
|
}
|
|
38
|
-
offset = match.index + match[0].length;
|
|
39
68
|
}
|
|
40
|
-
|
|
41
|
-
result += text.slice(offset);
|
|
42
|
-
}
|
|
43
|
-
return result.trim();
|
|
69
|
+
return best?.status ?? null;
|
|
44
70
|
}
|
|
45
71
|
|
|
46
72
|
// src/worker-client.ts
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
73
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
74
|
+
import { join as join2, resolve as resolve2, win32 } from "path";
|
|
75
|
+
import { createHash as createHash2 } from "crypto";
|
|
76
|
+
import { homedir as homedir2 } from "os";
|
|
51
77
|
var DEFAULT_WORKER_HOST = "127.0.0.1";
|
|
52
|
-
var
|
|
53
|
-
var
|
|
54
|
-
var
|
|
78
|
+
var WORKER_PORT_BASE = 37700;
|
|
79
|
+
var TOKEN_HEADER = "x-claude-mem-token";
|
|
80
|
+
var PROJECT_COUNTS_SQL = `SELECT
|
|
81
|
+
(SELECT COUNT(*) FROM observations
|
|
82
|
+
WHERE project = ?1 COLLATE NOCASE OR merged_into_project = ?1 COLLATE NOCASE) AS observations,
|
|
83
|
+
(SELECT COUNT(*) FROM session_summaries
|
|
84
|
+
WHERE project = ?1 COLLATE NOCASE OR merged_into_project = ?1 COLLATE NOCASE) AS summaries,
|
|
85
|
+
(SELECT COUNT(*) FROM sdk_sessions WHERE project = ?1 COLLATE NOCASE) AS sessions`;
|
|
55
86
|
function isRecord(value) {
|
|
56
87
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
57
88
|
}
|
|
@@ -75,303 +106,134 @@ function parseSettings(rawSettings) {
|
|
|
75
106
|
return {};
|
|
76
107
|
}
|
|
77
108
|
try {
|
|
78
|
-
const parsed = JSON.parse(rawSettings);
|
|
79
|
-
|
|
109
|
+
const parsed = JSON.parse(rawSettings.replace(/^\uFEFF/, ""));
|
|
110
|
+
if (!isRecord(parsed)) {
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
const nested = parsed["env"];
|
|
114
|
+
const source = isRecord(nested) && Object.keys(nested).some((key) => key.startsWith("CLAUDE_MEM_")) ? nested : parsed;
|
|
115
|
+
return Object.fromEntries(Object.entries(source).map(([key, value]) => [
|
|
116
|
+
key,
|
|
117
|
+
typeof value === "number" || typeof value === "boolean" ? String(value) : value
|
|
118
|
+
]));
|
|
80
119
|
} catch {
|
|
81
120
|
return {};
|
|
82
121
|
}
|
|
83
122
|
}
|
|
84
|
-
function
|
|
123
|
+
function dataDirectory(env, home) {
|
|
124
|
+
const dir = readString(env["CLAUDE_MEM_DATA_DIR"]) ?? join2(home, ".claude-mem");
|
|
125
|
+
return dir.replace(/^~(?=$|[\\/])/, home);
|
|
126
|
+
}
|
|
127
|
+
function defaultWorkerPort(dataDir, platform = process.platform, uid = typeof process.getuid === "function" ? process.getuid() : null) {
|
|
128
|
+
if (uid !== null) {
|
|
129
|
+
return WORKER_PORT_BASE + (uid % 100 + 100) % 100;
|
|
130
|
+
}
|
|
131
|
+
const normalized = platform === "win32" ? win32.resolve(dataDir).toLowerCase() : resolve2(dataDir);
|
|
132
|
+
const digest = createHash2("sha256").update(normalized, "utf8").digest();
|
|
133
|
+
return WORKER_PORT_BASE + digest.readUInt32BE(0) % 100;
|
|
134
|
+
}
|
|
135
|
+
function parseWorkerEndpoint(env, rawSettings, fallbackPort = defaultWorkerPort(dataDirectory(env, homedir2()))) {
|
|
85
136
|
const settings = parseSettings(rawSettings);
|
|
86
|
-
const
|
|
87
|
-
const
|
|
137
|
+
const configured = readString(env["CLAUDE_MEM_WORKER_HOST"]) ?? readString(settings["CLAUDE_MEM_WORKER_HOST"]) ?? DEFAULT_WORKER_HOST;
|
|
138
|
+
const host = configured === "localhost" ? DEFAULT_WORKER_HOST : configured;
|
|
139
|
+
const port = readPort(env["CLAUDE_MEM_WORKER_PORT"]) ?? readPort(settings["CLAUDE_MEM_WORKER_PORT"]) ?? fallbackPort;
|
|
88
140
|
return { host, port };
|
|
89
141
|
}
|
|
90
|
-
function
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
["query", options.query ?? ""],
|
|
94
|
-
["limit", options.limit],
|
|
95
|
-
["project", options.project],
|
|
96
|
-
["platformSource", options.platformSource],
|
|
97
|
-
["type", options.type],
|
|
98
|
-
["obs_type", options.obs_type],
|
|
99
|
-
["dateStart", options.dateStart],
|
|
100
|
-
["dateEnd", options.dateEnd],
|
|
101
|
-
["offset", options.offset],
|
|
102
|
-
["orderBy", options.orderBy]
|
|
103
|
-
];
|
|
104
|
-
for (const [key, value] of entries) {
|
|
105
|
-
if (value !== undefined) {
|
|
106
|
-
params.set(key, String(value));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return params;
|
|
142
|
+
function isLoopbackHost(host) {
|
|
143
|
+
const name = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
144
|
+
return name === "localhost" || name.endsWith(".localhost") || name === "::1" || /^127(?:\.\d{1,3}){1,3}$/.test(name);
|
|
110
145
|
}
|
|
111
|
-
function
|
|
146
|
+
function workerUrl(endpoint) {
|
|
147
|
+
const host = endpoint.host.includes(":") && !endpoint.host.startsWith("[") ? `[${endpoint.host}]` : endpoint.host;
|
|
148
|
+
return `http://${host}:${endpoint.port}`;
|
|
149
|
+
}
|
|
150
|
+
function tokenHeaders(baseUrl, rawSettings) {
|
|
151
|
+
const token = readString(parseSettings(rawSettings)["CLAUDE_MEM_LOCAL_API_TOKEN"]);
|
|
112
152
|
try {
|
|
113
|
-
return
|
|
153
|
+
return token && isLoopbackHost(new URL(baseUrl).hostname) ? { [TOKEN_HEADER]: token } : {};
|
|
114
154
|
} catch {
|
|
115
|
-
return
|
|
155
|
+
return {};
|
|
116
156
|
}
|
|
117
157
|
}
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
return `http://${endpoint.host}:${endpoint.port}`;
|
|
121
|
-
}
|
|
122
|
-
function extractTextContent(value) {
|
|
123
|
-
if (typeof value === "string") {
|
|
124
|
-
return value.trim() || null;
|
|
125
|
-
}
|
|
126
|
-
if (!isRecord(value)) {
|
|
158
|
+
function resolveDatabasePath(env, rawSettings, home) {
|
|
159
|
+
if (!isLoopbackHost(parseWorkerEndpoint(env, rawSettings).host)) {
|
|
127
160
|
return null;
|
|
128
161
|
}
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
if (Array.isArray(content)) {
|
|
134
|
-
const text = content.map((part) => {
|
|
135
|
-
if (!isRecord(part) || part["type"] !== "text" || typeof part["text"] !== "string") {
|
|
136
|
-
return "";
|
|
137
|
-
}
|
|
138
|
-
return part["text"];
|
|
139
|
-
}).filter(Boolean).join(`
|
|
140
|
-
`).trim();
|
|
141
|
-
return text || null;
|
|
142
|
-
}
|
|
143
|
-
const text = JSON.stringify(value, null, 2);
|
|
144
|
-
return text === "{}" || text === "null" ? null : text;
|
|
162
|
+
const settings = parseSettings(rawSettings);
|
|
163
|
+
const dir = readString(env["CLAUDE_MEM_DATA_DIR"]) ?? readString(settings["CLAUDE_MEM_DATA_DIR"]) ?? join2(home, ".claude-mem");
|
|
164
|
+
return join2(dir.replace(/^~(?=$|[\\/])/, home), "claude-mem.db");
|
|
145
165
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
const response = await fetch(`${this.BASE_URL}/api/health`, {
|
|
153
|
-
signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS)
|
|
154
|
-
});
|
|
155
|
-
return response.ok;
|
|
156
|
-
} catch {
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
static async ensureRunning() {
|
|
161
|
-
await this.tryAutoStart();
|
|
162
|
-
return this.isHealthy();
|
|
163
|
-
}
|
|
164
|
-
static tryAutoStart() {
|
|
165
|
-
if (!this.autoStartPromise) {
|
|
166
|
-
this.autoStartPromise = this.runAutoStart().catch(() => "spawn-failed");
|
|
167
|
-
}
|
|
168
|
-
return this.autoStartPromise;
|
|
169
|
-
}
|
|
170
|
-
static async runAutoStart() {
|
|
171
|
-
if (await this.isHealthy()) {
|
|
172
|
-
return "already-running";
|
|
173
|
-
}
|
|
174
|
-
const bunPath = this.findBun();
|
|
175
|
-
if (!bunPath) {
|
|
176
|
-
return "no-bun";
|
|
177
|
-
}
|
|
178
|
-
try {
|
|
179
|
-
const child = spawn(bunPath, ["x", "claude-mem", "start"], {
|
|
180
|
-
detached: true,
|
|
181
|
-
stdio: "ignore",
|
|
182
|
-
windowsHide: true,
|
|
183
|
-
env: process.env
|
|
184
|
-
});
|
|
185
|
-
child.on("error", () => {});
|
|
186
|
-
child.unref();
|
|
187
|
-
} catch {
|
|
188
|
-
return "spawn-failed";
|
|
189
|
-
}
|
|
190
|
-
for (let i = 0;i < AUTOSTART_POLL_MAX_ATTEMPTS; i++) {
|
|
191
|
-
await new Promise((resolve) => setTimeout(resolve, AUTOSTART_POLL_INTERVAL_MS));
|
|
192
|
-
if (await this.isHealthy()) {
|
|
193
|
-
return "started";
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return "timeout";
|
|
197
|
-
}
|
|
198
|
-
static findBun() {
|
|
199
|
-
const bunGlobal = globalThis.Bun;
|
|
200
|
-
if (bunGlobal && typeof bunGlobal.which === "function") {
|
|
201
|
-
try {
|
|
202
|
-
const found = bunGlobal.which("bun");
|
|
203
|
-
if (typeof found === "string" && found) {
|
|
204
|
-
return found;
|
|
205
|
-
}
|
|
206
|
-
} catch {}
|
|
207
|
-
}
|
|
208
|
-
const path = process.env.PATH || "";
|
|
209
|
-
const sep = process.platform === "win32" ? ";" : ":";
|
|
210
|
-
const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""];
|
|
211
|
-
const slash = process.platform === "win32" ? "\\" : "/";
|
|
212
|
-
const dirs = path.split(sep).filter(Boolean);
|
|
213
|
-
for (const dir of dirs) {
|
|
214
|
-
const trailing = dir.endsWith("/") || dir.endsWith("\\") ? "" : slash;
|
|
215
|
-
for (const ext of exts) {
|
|
216
|
-
const candidate = `${dir}${trailing}bun${ext}`;
|
|
217
|
-
try {
|
|
218
|
-
if (existsSync(candidate)) {
|
|
219
|
-
return candidate;
|
|
220
|
-
}
|
|
221
|
-
} catch {}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
166
|
+
function readCount(value) {
|
|
167
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null;
|
|
168
|
+
}
|
|
169
|
+
async function readProjectCounts(path, project) {
|
|
170
|
+
if (!existsSync(path)) {
|
|
224
171
|
return null;
|
|
225
172
|
}
|
|
226
|
-
|
|
173
|
+
try {
|
|
174
|
+
const { Database } = await import("bun:sqlite");
|
|
175
|
+
const db = new Database(path, { readonly: true });
|
|
227
176
|
try {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
signal: AbortSignal.timeout(WRITE_TIMEOUT_MS),
|
|
231
|
-
headers: { "Content-Type": "application/json" },
|
|
232
|
-
body: JSON.stringify({
|
|
233
|
-
contentSessionId,
|
|
234
|
-
project,
|
|
235
|
-
prompt,
|
|
236
|
-
platformSource: PLATFORM_SOURCE
|
|
237
|
-
})
|
|
238
|
-
});
|
|
239
|
-
if (!response.ok) {
|
|
177
|
+
const row = db.query(PROJECT_COUNTS_SQL).get(project);
|
|
178
|
+
if (!isRecord(row)) {
|
|
240
179
|
return null;
|
|
241
180
|
}
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
promptNumber: value.promptNumber,
|
|
249
|
-
skipped: value.skipped === true,
|
|
250
|
-
reason: typeof value.reason === "string" ? value.reason : undefined
|
|
251
|
-
};
|
|
252
|
-
} catch {
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
static async sendObservation(contentSessionId, toolName, toolInput, toolResponse, cwd, toolUseId) {
|
|
257
|
-
try {
|
|
258
|
-
const response = await fetch(`${this.BASE_URL}/api/sessions/observations`, {
|
|
259
|
-
method: "POST",
|
|
260
|
-
signal: AbortSignal.timeout(WRITE_TIMEOUT_MS),
|
|
261
|
-
headers: { "Content-Type": "application/json" },
|
|
262
|
-
body: JSON.stringify({
|
|
263
|
-
contentSessionId,
|
|
264
|
-
tool_name: toolName,
|
|
265
|
-
tool_input: toolInput,
|
|
266
|
-
tool_response: toolResponse,
|
|
267
|
-
cwd,
|
|
268
|
-
platformSource: PLATFORM_SOURCE,
|
|
269
|
-
tool_use_id: toolUseId
|
|
270
|
-
})
|
|
271
|
-
});
|
|
272
|
-
return response.ok;
|
|
273
|
-
} catch {
|
|
274
|
-
return false;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
static async summarize(contentSessionId, lastUserMessage, lastAssistantMessage, metadata = {}) {
|
|
278
|
-
try {
|
|
279
|
-
const response = await fetch(`${this.BASE_URL}/api/sessions/summarize`, {
|
|
280
|
-
method: "POST",
|
|
281
|
-
signal: AbortSignal.timeout(WRITE_TIMEOUT_MS),
|
|
282
|
-
headers: { "Content-Type": "application/json" },
|
|
283
|
-
body: JSON.stringify({
|
|
284
|
-
contentSessionId,
|
|
285
|
-
last_user_message: stripTaggedContent(lastUserMessage),
|
|
286
|
-
last_assistant_message: stripTaggedContent(lastAssistantMessage),
|
|
287
|
-
platformSource: PLATFORM_SOURCE,
|
|
288
|
-
observedModel: metadata.observedModel,
|
|
289
|
-
observedBilling: metadata.observedBilling
|
|
290
|
-
})
|
|
291
|
-
});
|
|
292
|
-
return response.ok;
|
|
293
|
-
} catch {
|
|
294
|
-
return false;
|
|
181
|
+
const observations = readCount(row["observations"]);
|
|
182
|
+
const summaries = readCount(row["summaries"]);
|
|
183
|
+
const sessions = readCount(row["sessions"]);
|
|
184
|
+
return observations === null || summaries === null || sessions === null ? null : { observations, summaries, sessions };
|
|
185
|
+
} finally {
|
|
186
|
+
db.close();
|
|
295
187
|
}
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
296
190
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const text = await response.text();
|
|
308
|
-
return text.trim() || null;
|
|
309
|
-
} catch {
|
|
310
|
-
return null;
|
|
311
|
-
}
|
|
191
|
+
}
|
|
192
|
+
function readEpoch(item) {
|
|
193
|
+
const at = item["created_at_epoch"];
|
|
194
|
+
return typeof at === "number" && Number.isFinite(at) ? at : null;
|
|
195
|
+
}
|
|
196
|
+
function readSettingsRaw() {
|
|
197
|
+
try {
|
|
198
|
+
return readFileSync2(join2(dataDirectory(process.env, homedir2()), "settings.json"), "utf8");
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
312
201
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
return extractTextContent(await response.json()) || "";
|
|
335
|
-
}
|
|
336
|
-
const text = await response.text();
|
|
337
|
-
return text.trim();
|
|
338
|
-
} catch {
|
|
339
|
-
return "";
|
|
202
|
+
}
|
|
203
|
+
function readWorkerSetting(key) {
|
|
204
|
+
return readString(process.env[key]) ?? readString(parseSettings(readSettingsRaw())[key]);
|
|
205
|
+
}
|
|
206
|
+
function getWorkerEndpoint() {
|
|
207
|
+
return parseWorkerEndpoint(process.env, readSettingsRaw());
|
|
208
|
+
}
|
|
209
|
+
function getWorkerBaseUrl() {
|
|
210
|
+
return workerUrl(getWorkerEndpoint());
|
|
211
|
+
}
|
|
212
|
+
function workerHeaders(baseUrl = getWorkerBaseUrl()) {
|
|
213
|
+
return tokenHeaders(baseUrl, readSettingsRaw());
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
class WorkerClient {
|
|
217
|
+
static BASE_URL = getWorkerBaseUrl();
|
|
218
|
+
static SUPPORTED = !readWorkerSetting("CLAUDE_MEM_WORKER_URL");
|
|
219
|
+
static DATABASE_PATH = resolveDatabasePath(process.env, readSettingsRaw(), homedir2());
|
|
220
|
+
static get(path) {
|
|
221
|
+
if (!this.SUPPORTED) {
|
|
222
|
+
return Promise.reject(new Error("CLAUDE_MEM_WORKER_URL is not supported"));
|
|
340
223
|
}
|
|
224
|
+
return fetch(`${this.BASE_URL}${path}`, {
|
|
225
|
+
headers: workerHeaders(this.BASE_URL),
|
|
226
|
+
redirect: "error"
|
|
227
|
+
});
|
|
341
228
|
}
|
|
342
|
-
static
|
|
343
|
-
|
|
344
|
-
const body = { ids };
|
|
345
|
-
if (project) {
|
|
346
|
-
body["project"] = project;
|
|
347
|
-
}
|
|
348
|
-
const response = await fetch(`${this.BASE_URL}/api/observations/batch`, {
|
|
349
|
-
method: "POST",
|
|
350
|
-
headers: { "Content-Type": "application/json" },
|
|
351
|
-
body: JSON.stringify(body)
|
|
352
|
-
});
|
|
353
|
-
if (!response.ok) {
|
|
354
|
-
return "";
|
|
355
|
-
}
|
|
356
|
-
const contentType = response.headers.get("content-type") || "";
|
|
357
|
-
if (contentType.includes("application/json")) {
|
|
358
|
-
const payload = await response.json();
|
|
359
|
-
if (Array.isArray(payload)) {
|
|
360
|
-
return payload.length ? JSON.stringify(payload, null, 2) : "";
|
|
361
|
-
}
|
|
362
|
-
return extractTextContent(payload) || "";
|
|
363
|
-
}
|
|
364
|
-
const text = await response.text();
|
|
365
|
-
return text.trim();
|
|
366
|
-
} catch {
|
|
367
|
-
return "";
|
|
368
|
-
}
|
|
229
|
+
static getProjectCounts(project) {
|
|
230
|
+
return this.DATABASE_PATH && this.SUPPORTED ? readProjectCounts(this.DATABASE_PATH, project) : Promise.resolve(null);
|
|
369
231
|
}
|
|
370
232
|
static async getStats() {
|
|
371
233
|
try {
|
|
372
234
|
const [statsRes, processingRes] = await Promise.all([
|
|
373
|
-
|
|
374
|
-
|
|
235
|
+
this.get(`/api/stats`),
|
|
236
|
+
this.get(`/api/processing-status`)
|
|
375
237
|
]);
|
|
376
238
|
if (!statsRes.ok) {
|
|
377
239
|
return null;
|
|
@@ -386,7 +248,7 @@ class WorkerClient {
|
|
|
386
248
|
static async getRecentSummaries(project, limit) {
|
|
387
249
|
try {
|
|
388
250
|
const params = new URLSearchParams({ project, limit: String(limit) });
|
|
389
|
-
const response = await
|
|
251
|
+
const response = await this.get(`/api/summaries?${params.toString()}`);
|
|
390
252
|
if (!response.ok) {
|
|
391
253
|
return [];
|
|
392
254
|
}
|
|
@@ -399,7 +261,7 @@ class WorkerClient {
|
|
|
399
261
|
return [];
|
|
400
262
|
}
|
|
401
263
|
const request = typeof item["request"] === "string" ? item["request"].trim() : "";
|
|
402
|
-
return request ? [{ id: item["id"], request }] : [];
|
|
264
|
+
return request ? [{ id: item["id"], request, at: readEpoch(item) }] : [];
|
|
403
265
|
});
|
|
404
266
|
} catch {
|
|
405
267
|
return [];
|
|
@@ -408,7 +270,7 @@ class WorkerClient {
|
|
|
408
270
|
static async getRecentObservations(project, limit) {
|
|
409
271
|
try {
|
|
410
272
|
const params = new URLSearchParams({ project, limit: String(limit) });
|
|
411
|
-
const response = await
|
|
273
|
+
const response = await this.get(`/api/observations?${params.toString()}`);
|
|
412
274
|
if (!response.ok) {
|
|
413
275
|
return [];
|
|
414
276
|
}
|
|
@@ -422,34 +284,29 @@ class WorkerClient {
|
|
|
422
284
|
}
|
|
423
285
|
const title = typeof item["title"] === "string" ? item["title"].trim() : "";
|
|
424
286
|
const type = typeof item["type"] === "string" ? item["type"] : "";
|
|
425
|
-
return title ? [{ id: item["id"], type, title }] : [];
|
|
287
|
+
return title ? [{ id: item["id"], type, title, at: readEpoch(item) }] : [];
|
|
426
288
|
});
|
|
427
289
|
} catch {
|
|
428
290
|
return [];
|
|
429
291
|
}
|
|
430
292
|
}
|
|
431
|
-
static async
|
|
293
|
+
static async getDetail(kind, id) {
|
|
432
294
|
try {
|
|
433
|
-
const
|
|
434
|
-
const response = await
|
|
295
|
+
const path = kind === "observation" ? "observation" : "session";
|
|
296
|
+
const response = await this.get(`/api/${path}/${id}`);
|
|
435
297
|
if (!response.ok) {
|
|
436
|
-
return
|
|
437
|
-
}
|
|
438
|
-
const contentType = response.headers.get("content-type") || "";
|
|
439
|
-
if (contentType.includes("application/json")) {
|
|
440
|
-
return extractTextContent(await response.json()) || "";
|
|
298
|
+
return null;
|
|
441
299
|
}
|
|
442
|
-
const
|
|
443
|
-
return
|
|
300
|
+
const payload = await response.json();
|
|
301
|
+
return isRecord(payload) ? payload : null;
|
|
444
302
|
} catch {
|
|
445
|
-
return
|
|
303
|
+
return null;
|
|
446
304
|
}
|
|
447
305
|
}
|
|
448
306
|
}
|
|
449
307
|
|
|
450
308
|
// src/sidebar-model.ts
|
|
451
309
|
var POLL_INTERVAL_MS = 5000;
|
|
452
|
-
var LABEL_MAX = 40;
|
|
453
310
|
var RECENT_SUMMARIES = 3;
|
|
454
311
|
var RECENT_OBSERVATIONS = 3;
|
|
455
312
|
var OBSERVATION_TYPE_ICONS = {
|
|
@@ -478,9 +335,37 @@ var HTML_ENTITIES = {
|
|
|
478
335
|
function decodeEntities(value) {
|
|
479
336
|
return value.replace(/&(?:lt|gt|quot|#39|amp);/g, (entity) => HTML_ENTITIES[entity] ?? entity);
|
|
480
337
|
}
|
|
481
|
-
function
|
|
338
|
+
function stripProjectName(value, project) {
|
|
482
339
|
const decoded = decodeEntities(value);
|
|
483
|
-
|
|
340
|
+
const name = project.trim();
|
|
341
|
+
if (!name) {
|
|
342
|
+
return decoded;
|
|
343
|
+
}
|
|
344
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
345
|
+
const stripped = decoded.replace(new RegExp(`(?<![\\w.@-])(?:[\\w.@-]+/)?${escaped}(?![\\w.-])`, "giu"), " ").replaceAll(/\s{2,}/g, " ").replaceAll(/\s+([\uFF0C\u3002\u3001\uFF1B\uFF1A,.:;])/g, "$1").replace(/^[\s:\uFF1A,\uFF0C\u3001;\uFF1B\u00B7|\u2013\u2014-]+/u, "").trim();
|
|
346
|
+
return stripped || decoded;
|
|
347
|
+
}
|
|
348
|
+
function formatAge(at, now) {
|
|
349
|
+
if (at === null || !Number.isFinite(at)) {
|
|
350
|
+
return "";
|
|
351
|
+
}
|
|
352
|
+
const minutes = Math.floor(Math.max(0, now - at) / 60000);
|
|
353
|
+
if (minutes < 1) {
|
|
354
|
+
return "just now";
|
|
355
|
+
}
|
|
356
|
+
if (minutes < 60) {
|
|
357
|
+
return `${minutes}m ago`;
|
|
358
|
+
}
|
|
359
|
+
const hours = Math.floor(minutes / 60);
|
|
360
|
+
if (hours < 24) {
|
|
361
|
+
return `${hours}h ago`;
|
|
362
|
+
}
|
|
363
|
+
const days = Math.floor(hours / 24);
|
|
364
|
+
if (days < 7) {
|
|
365
|
+
return `${days}d ago`;
|
|
366
|
+
}
|
|
367
|
+
const date = new Date(at);
|
|
368
|
+
return `${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
484
369
|
}
|
|
485
370
|
function formatCount(value) {
|
|
486
371
|
if (value === null) {
|
|
@@ -492,6 +377,9 @@ function summaryLine(view) {
|
|
|
492
377
|
if (!view.healthy) {
|
|
493
378
|
return { label: "(offline)", tone: "err" };
|
|
494
379
|
}
|
|
380
|
+
if (view.capture?.state === "problem") {
|
|
381
|
+
return { label: "(capture off)", tone: "err" };
|
|
382
|
+
}
|
|
495
383
|
if (view.isProcessing || (view.queueDepth ?? 0) > 0) {
|
|
496
384
|
return { label: `(queue ${view.queueDepth ?? 0})`, tone: "warn" };
|
|
497
385
|
}
|
|
@@ -503,68 +391,102 @@ function toneColor(tone, theme) {
|
|
|
503
391
|
}
|
|
504
392
|
return tone === "warn" ? theme.warning : theme.textMuted;
|
|
505
393
|
}
|
|
506
|
-
function
|
|
394
|
+
function label(color, value) {
|
|
395
|
+
return text({ fg: color, wrapMode: "none", truncate: true }, value);
|
|
396
|
+
}
|
|
397
|
+
function bulletRow(icon, iconColor, value, theme, labelColor = theme.text) {
|
|
507
398
|
return box({ flexDirection: "row", gap: 1 }, [
|
|
508
399
|
text({ fg: iconColor, flexShrink: 0 }, icon),
|
|
509
|
-
|
|
400
|
+
label(labelColor, decodeEntities(value))
|
|
401
|
+
]);
|
|
402
|
+
}
|
|
403
|
+
function recentRow(icon, iconColor, title, age, theme, onSelect) {
|
|
404
|
+
return box({ flexDirection: "column", ...onSelect ? { onMouseDown: onSelect } : {} }, [
|
|
405
|
+
bulletRow(icon, iconColor, title, theme),
|
|
406
|
+
...age ? [box({ paddingLeft: 2 }, [label(theme.textMuted, age)])] : []
|
|
510
407
|
]);
|
|
511
408
|
}
|
|
512
|
-
function expandedRows(view, theme) {
|
|
409
|
+
function expandedRows(view, theme, onSelect) {
|
|
410
|
+
const notice = view.capture?.message ? [
|
|
411
|
+
box({ flexDirection: "row", gap: 1 }, [
|
|
412
|
+
text({ fg: theme.warning, flexShrink: 0 }, "!"),
|
|
413
|
+
text({
|
|
414
|
+
fg: view.capture.state === "problem" ? theme.error : theme.warning,
|
|
415
|
+
wrapMode: "word"
|
|
416
|
+
}, view.capture.message)
|
|
417
|
+
])
|
|
418
|
+
] : [];
|
|
513
419
|
if (!view.healthy) {
|
|
514
|
-
return [bulletRow("\u2022", theme.error, "worker offline", theme)];
|
|
420
|
+
return [bulletRow("\u2022", theme.error, "worker offline", theme), ...notice];
|
|
515
421
|
}
|
|
422
|
+
const counts = `obs ${formatCount(view.observations)} \xB7 sum ${formatCount(view.summaries)} \xB7 ses ${formatCount(view.sessions)}`;
|
|
516
423
|
const rows = [
|
|
517
|
-
|
|
424
|
+
...notice,
|
|
425
|
+
bulletRow("\u2022", theme.success, view.scope === "all" ? `all \xB7 ${counts}` : counts, theme)
|
|
518
426
|
];
|
|
427
|
+
if (view.scope === "project") {
|
|
428
|
+
rows.push(bulletRow("\u2022", theme.textMuted, `all ${formatCount(view.totalObservations)} obs`, theme, theme.textMuted));
|
|
429
|
+
}
|
|
519
430
|
if (view.isProcessing || (view.queueDepth ?? 0) > 0) {
|
|
520
431
|
rows.push(bulletRow("\u2022", theme.warning, `processing \xB7 queue ${view.queueDepth ?? 0}`, theme));
|
|
521
432
|
}
|
|
522
433
|
if (view.recentSummaries.length > 0) {
|
|
523
434
|
rows.push(text({ fg: theme.textMuted }, "Recent sessions"));
|
|
524
435
|
for (const summary of view.recentSummaries) {
|
|
525
|
-
rows.push(
|
|
436
|
+
rows.push(recentRow("\u2022", theme.textMuted, stripProjectName(summary.request, view.project), formatAge(summary.at, view.asOf), theme, onSelect && (() => onSelect("summary", summary.id))));
|
|
526
437
|
}
|
|
527
438
|
}
|
|
528
439
|
if (view.recentObservations.length > 0) {
|
|
529
440
|
rows.push(text({ fg: theme.textMuted }, "Latest"));
|
|
530
441
|
for (const observation of view.recentObservations) {
|
|
531
442
|
const icon = OBSERVATION_TYPE_ICONS[observation.type] ?? "\u25CB";
|
|
532
|
-
rows.push(
|
|
443
|
+
rows.push(recentRow(icon, theme.info, stripProjectName(observation.title, view.project), [formatAge(observation.at, view.asOf), `#${observation.id}`].filter(Boolean).join(" \xB7 "), theme, onSelect && (() => onSelect("observation", observation.id))));
|
|
533
444
|
}
|
|
534
445
|
}
|
|
446
|
+
if (onSelect && view.recentSummaries.length + view.recentObservations.length > 0) {
|
|
447
|
+
rows.push(label(theme.textMuted, "Click an item for details"));
|
|
448
|
+
}
|
|
535
449
|
return rows;
|
|
536
450
|
}
|
|
537
|
-
function buildMemNodes(view, theme, collapsed, onToggle) {
|
|
451
|
+
function buildMemNodes(view, theme, collapsed, onToggle, onSelect) {
|
|
538
452
|
const summary = summaryLine(view);
|
|
539
453
|
const header = box({ flexDirection: "row", gap: 1, onMouseDown: onToggle }, [
|
|
540
|
-
text({ fg: theme.text }, collapsed ? "\u25B6" : "\u25BC"),
|
|
541
|
-
text({ fg: theme.info, attributes: 1 }, "Memory"),
|
|
542
|
-
|
|
454
|
+
text({ fg: theme.text, flexShrink: 0 }, collapsed ? "\u25B6" : "\u25BC"),
|
|
455
|
+
text({ fg: theme.info, attributes: 1, flexShrink: 0 }, "Memory"),
|
|
456
|
+
collapsed ? text({ fg: toneColor(summary.tone, theme) }, summary.label) : label(theme.textMuted, view.project)
|
|
543
457
|
]);
|
|
544
458
|
return [
|
|
545
459
|
box({ flexDirection: "column", paddingTop: 1 }, [
|
|
546
460
|
header,
|
|
547
|
-
...collapsed ? [] : [box({ flexDirection: "column", paddingLeft: 2 }, expandedRows(view, theme))]
|
|
461
|
+
...collapsed ? [] : [box({ flexDirection: "column", paddingLeft: 2 }, expandedRows(view, theme, onSelect))]
|
|
548
462
|
])
|
|
549
463
|
];
|
|
550
464
|
}
|
|
551
465
|
function viewKey(view) {
|
|
552
466
|
return JSON.stringify(view);
|
|
553
467
|
}
|
|
554
|
-
function
|
|
468
|
+
function readCount2(value) {
|
|
555
469
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
556
470
|
}
|
|
557
|
-
async function readMemView(project, collapsed) {
|
|
558
|
-
const stats = await
|
|
471
|
+
async function readMemView(project, collapsed, directory = "") {
|
|
472
|
+
const [stats, counts] = await Promise.all([
|
|
473
|
+
WorkerClient.getStats(),
|
|
474
|
+
WorkerClient.getProjectCounts(project)
|
|
475
|
+
]);
|
|
476
|
+
const capture = directory ? readCaptureStatus(directory) : null;
|
|
559
477
|
if (!stats) {
|
|
560
478
|
return {
|
|
561
479
|
healthy: false,
|
|
480
|
+
capture,
|
|
562
481
|
project,
|
|
482
|
+
scope: "all",
|
|
483
|
+
totalObservations: null,
|
|
563
484
|
observations: null,
|
|
564
485
|
sessions: null,
|
|
565
486
|
summaries: null,
|
|
566
487
|
queueDepth: null,
|
|
567
488
|
isProcessing: false,
|
|
489
|
+
asOf: 0,
|
|
568
490
|
recentSummaries: [],
|
|
569
491
|
recentObservations: []
|
|
570
492
|
};
|
|
@@ -573,18 +495,85 @@ async function readMemView(project, collapsed) {
|
|
|
573
495
|
WorkerClient.getRecentSummaries(project, RECENT_SUMMARIES),
|
|
574
496
|
WorkerClient.getRecentObservations(project, RECENT_OBSERVATIONS)
|
|
575
497
|
]);
|
|
498
|
+
const totalObservations = readCount2(stats.database?.observations);
|
|
576
499
|
return {
|
|
577
500
|
healthy: true,
|
|
501
|
+
capture,
|
|
578
502
|
project,
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
503
|
+
scope: counts ? "project" : "all",
|
|
504
|
+
totalObservations,
|
|
505
|
+
observations: counts ? counts.observations : totalObservations,
|
|
506
|
+
sessions: counts ? counts.sessions : readCount2(stats.database?.sessions),
|
|
507
|
+
summaries: counts ? counts.summaries : readCount2(stats.database?.summaries),
|
|
508
|
+
queueDepth: readCount2(stats.processing?.queueDepth),
|
|
583
509
|
isProcessing: stats.processing?.isProcessing === true,
|
|
510
|
+
asOf: Math.floor(Date.now() / 60000) * 60000,
|
|
584
511
|
recentSummaries,
|
|
585
512
|
recentObservations
|
|
586
513
|
};
|
|
587
514
|
}
|
|
515
|
+
function field(record, key) {
|
|
516
|
+
const value = record[key];
|
|
517
|
+
return typeof value === "string" ? decodeEntities(value).trim() : "";
|
|
518
|
+
}
|
|
519
|
+
function listLines(value) {
|
|
520
|
+
const items = [...value.matchAll(/<item>([\s\S]*?)<\/item>/g)].map((match) => match[1] ?? "");
|
|
521
|
+
if (items.length > 0) {
|
|
522
|
+
return items.map((item) => `\u2022 ${item.trim()}`);
|
|
523
|
+
}
|
|
524
|
+
if (value.startsWith("[")) {
|
|
525
|
+
try {
|
|
526
|
+
const parsed = JSON.parse(value);
|
|
527
|
+
if (Array.isArray(parsed)) {
|
|
528
|
+
return parsed.filter((item) => typeof item === "string").map((item) => `\u2022 ${item}`);
|
|
529
|
+
}
|
|
530
|
+
} catch {}
|
|
531
|
+
}
|
|
532
|
+
return value ? [value] : [];
|
|
533
|
+
}
|
|
534
|
+
function section(heading, lines) {
|
|
535
|
+
return lines.length > 0 ? ["", heading, ...lines] : [];
|
|
536
|
+
}
|
|
537
|
+
function formatTime(record) {
|
|
538
|
+
const at = record["created_at_epoch"];
|
|
539
|
+
return typeof at === "number" && Number.isFinite(at) ? new Date(at).toLocaleString() : "";
|
|
540
|
+
}
|
|
541
|
+
function describeDetail(kind, id, record) {
|
|
542
|
+
if (!record) {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
const meta = [kind === "observation" ? field(record, "type") : "session", formatTime(record)];
|
|
546
|
+
if (kind === "observation") {
|
|
547
|
+
return {
|
|
548
|
+
title: `#${id} ${field(record, "title")}`.trim(),
|
|
549
|
+
message: [
|
|
550
|
+
field(record, "subtitle"),
|
|
551
|
+
...section("Narrative", listLines(field(record, "narrative"))),
|
|
552
|
+
...section("Facts", listLines(field(record, "facts"))),
|
|
553
|
+
...section("Files modified", listLines(field(record, "files_modified"))),
|
|
554
|
+
"",
|
|
555
|
+
[...meta, `get_observations([${id}])`].filter(Boolean).join(" \xB7 ")
|
|
556
|
+
].join(`
|
|
557
|
+
`).trim()
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
title: field(record, "request") || `Session #${id}`,
|
|
562
|
+
message: [
|
|
563
|
+
...section("Investigated", listLines(field(record, "investigated"))),
|
|
564
|
+
...section("Learned", listLines(field(record, "learned"))),
|
|
565
|
+
...section("Completed", listLines(field(record, "completed"))),
|
|
566
|
+
...section("Next steps", listLines(field(record, "next_steps"))),
|
|
567
|
+
...section("Notes", listLines(field(record, "notes"))),
|
|
568
|
+
"",
|
|
569
|
+
[...meta, `#${id}`].filter(Boolean).join(" \xB7 ")
|
|
570
|
+
].join(`
|
|
571
|
+
`).trim()
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
async function readDetail(kind, id) {
|
|
575
|
+
return describeDetail(kind, id, await WorkerClient.getDetail(kind, id));
|
|
576
|
+
}
|
|
588
577
|
|
|
589
578
|
// node_modules/@opencode/plugin/dist/tui/plugin.js
|
|
590
579
|
function define(plugin) {
|
|
@@ -598,12 +587,16 @@ var REFRESH_EVENTS = ["session.created", "session.execution.succeeded"];
|
|
|
598
587
|
function emptyView(project) {
|
|
599
588
|
return {
|
|
600
589
|
healthy: false,
|
|
590
|
+
capture: null,
|
|
601
591
|
project,
|
|
592
|
+
scope: "all",
|
|
593
|
+
totalObservations: null,
|
|
602
594
|
observations: null,
|
|
603
595
|
sessions: null,
|
|
604
596
|
summaries: null,
|
|
605
597
|
queueDepth: null,
|
|
606
598
|
isProcessing: false,
|
|
599
|
+
asOf: 0,
|
|
607
600
|
recentSummaries: [],
|
|
608
601
|
recentObservations: []
|
|
609
602
|
};
|
|
@@ -615,7 +608,8 @@ var cli_default = define({
|
|
|
615
608
|
if (!solid) {
|
|
616
609
|
return;
|
|
617
610
|
}
|
|
618
|
-
const
|
|
611
|
+
const directory = String(ctx.location?.directory ?? process.cwd());
|
|
612
|
+
const project = directory.split(/[\\/]/).filter(Boolean).findLast(Boolean) ?? "unknown-project";
|
|
619
613
|
const [state, setState] = ctx.storage.memory("claude-mem.sidebar", {
|
|
620
614
|
initial: { collapsed: true, view: emptyView(project) }
|
|
621
615
|
});
|
|
@@ -636,7 +630,7 @@ var cli_default = define({
|
|
|
636
630
|
do {
|
|
637
631
|
refreshPending = false;
|
|
638
632
|
const { collapsed } = state;
|
|
639
|
-
const next = await readMemView(project, collapsed);
|
|
633
|
+
const next = await readMemView(project, collapsed, directory);
|
|
640
634
|
if (disposed) {
|
|
641
635
|
return;
|
|
642
636
|
}
|
|
@@ -661,9 +655,33 @@ var cli_default = define({
|
|
|
661
655
|
});
|
|
662
656
|
refresh().catch(() => {});
|
|
663
657
|
};
|
|
658
|
+
let opening = false;
|
|
659
|
+
const onSelect = (kind, id) => {
|
|
660
|
+
if (disposed || opening) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
opening = true;
|
|
664
|
+
readDetail(kind, id).then((detail) => {
|
|
665
|
+
if (disposed) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
if (!detail) {
|
|
669
|
+
ctx.ui.toast.show({
|
|
670
|
+
title: "Claude-Mem",
|
|
671
|
+
message: `Could not load ${kind} #${id} from the Worker`,
|
|
672
|
+
variant: "warning",
|
|
673
|
+
duration: 3000
|
|
674
|
+
});
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
return ctx.ui.dialog.alert(detail);
|
|
678
|
+
}).catch(() => {}).finally(() => {
|
|
679
|
+
opening = false;
|
|
680
|
+
});
|
|
681
|
+
};
|
|
664
682
|
const disposeSlot = ctx.ui.slot({
|
|
665
683
|
append: "sidebar.content",
|
|
666
|
-
render: () => materialize(() => buildMemNodes(state.view, themeFromResolved(ctx.theme), state.collapsed, onToggle), solid)
|
|
684
|
+
render: () => materialize(() => buildMemNodes(state.view, themeFromResolved(ctx.theme), state.collapsed, onToggle, onSelect), solid)
|
|
667
685
|
});
|
|
668
686
|
const disposeEvents = REFRESH_EVENTS.map((type) => ctx.data.on(type, () => {
|
|
669
687
|
refresh().catch(() => {});
|