claude-code-session-manager 0.113.0 → 0.115.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{AgentLibrary-wvrcLGGS.js → AgentLibrary-CjsZLl5e.js} +1 -1
- package/dist/assets/{Button-B6h0F0XT.js → Button-mj0vvcdc.js} +1 -1
- package/dist/assets/{DataModel-C5pW9E3u.js → DataModel-CG6ycpfG.js} +1 -1
- package/dist/assets/{History-BV4e8YOM.js → History-DDqLQiAN.js} +1 -1
- package/dist/assets/{Hooks-CrFSv4x1.js → Hooks-dRje24Nk.js} +1 -1
- package/dist/assets/{Library-D-GIt3EY.js → Library-Dl71nylN.js} +1 -1
- package/dist/assets/{ListDetail-Bco_wfs6.js → ListDetail-DcAD_cuA.js} +1 -1
- package/dist/assets/{MarkdownEditor-C-yHSXNV.js → MarkdownEditor-D7JTNOOp.js} +1 -1
- package/dist/assets/{McpServers-CfDekadG.js → McpServers-BbvbhemH.js} +1 -1
- package/dist/assets/Memory-dyz8ZU1e.js +8 -0
- package/dist/assets/{Panel-DGMEuYYG.js → Panel-BkfK-W74.js} +1 -1
- package/dist/assets/{Permissions-Cx2zjtYZ.js → Permissions-BnjsXblr.js} +1 -1
- package/dist/assets/{Plugins-Bc_W3IyR.js → Plugins-DSyTJtTp.js} +2 -2
- package/dist/assets/{ProvenanceBadge-DiKAUR1u.js → ProvenanceBadge-CQXSiPHq.js} +1 -1
- package/dist/assets/{RemoteMachines-8_77-WPD.js → RemoteMachines-li6EBPAt.js} +1 -1
- package/dist/assets/{SaveBar-Blpzdq1R.js → SaveBar-0aiuy1L2.js} +1 -1
- package/dist/assets/{Scheduler-Bd70AM4X.js → Scheduler-Ul4jiK7q.js} +1 -1
- package/dist/assets/{ScopeSwitcher-wxN0h4YW.js → ScopeSwitcher-DR262W_R.js} +1 -1
- package/dist/assets/{Settings-CQ7DA3zW.js → Settings-BwUcGuar.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-DH1NLOiS.js → SkillReferenceGraph-fv2FJUNK.js} +1 -1
- package/dist/assets/{Skills-CmyRyu4_.js → Skills-BA2I-iRE.js} +1 -1
- package/dist/assets/{SystemPrompt-KAc3UMCd.js → SystemPrompt-BgvyrAKx.js} +1 -1
- package/dist/assets/{TagLibrary-C8EvHnpg.js → TagLibrary-B5rKEDVT.js} +1 -1
- package/dist/assets/{TiptapBody-d423mH6D.js → TiptapBody-CBxxEulT.js} +1 -1
- package/dist/assets/{Toggle-D1wsGE9S.js → Toggle-W944m375.js} +1 -1
- package/dist/assets/{ViewTabs-CjHw3kGY.js → ViewTabs-BLQrBE2n.js} +1 -1
- package/dist/assets/{historyTotals-DFHzqLt7.js → historyTotals-BUeyjlNY.js} +1 -1
- package/dist/assets/{index-Dutcsly-.css → index-HpEbvDiE.css} +1 -1
- package/dist/assets/{index-Ci1u018F.js → index-t37UuEZu.js} +101 -101
- package/dist/assets/{settingsSchema-Ctf3pZVI.js → settingsSchema-HwiOXtC7.js} +1 -1
- package/dist/assets/{useAgentsDirWatch-B1NBmOAe.js → useAgentsDirWatch-D0rI9G_h.js} +1 -1
- package/dist/assets/{useRemotePeers-BHjSJs9D.js → useRemotePeers-BipAySOB.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +8 -2
- package/src/main/index.cjs +0 -2
- package/src/main/ipcSchemas.cjs +50 -35
- package/src/main/lib/memoryDirResolve.cjs +139 -0
- package/src/main/lib/memoryIndex.cjs +98 -0
- package/src/main/lib/remoteDrilldown.cjs +91 -0
- package/src/main/lib/remoteTranscriptTail.cjs +90 -0
- package/src/main/memoryAggregate.cjs +15 -8
- package/src/main/memoryTool.cjs +151 -22
- package/src/main/webRemote.cjs +90 -57
- package/src/preload/api.d.ts +44 -52
- package/src/preload/index.cjs +19 -21
- package/dist/assets/Memory-CGGLexkR.js +0 -8
- package/src/main/agentMemory.cjs +0 -246
package/src/main/webRemote.cjs
CHANGED
|
@@ -436,10 +436,6 @@ async function connect() {
|
|
|
436
436
|
broadcastStatus();
|
|
437
437
|
}).catch(() => {});
|
|
438
438
|
broadcastStatus();
|
|
439
|
-
// v2: begin pushing the live session list once connected.
|
|
440
|
-
// Reset diff-guard so the new client always receives a full session-list push.
|
|
441
|
-
for (const c of _clients.values()) c.lastSessionListJson = null;
|
|
442
|
-
startSessionListPush();
|
|
443
439
|
});
|
|
444
440
|
|
|
445
441
|
ws.on('message', (raw) => {
|
|
@@ -500,11 +496,9 @@ async function handleTokenRevoked(deviceId) {
|
|
|
500
496
|
// The last completed assistant turn drives the Haiku summary (see maybeSummarize).
|
|
501
497
|
|
|
502
498
|
const SESSION_POLL_MS = 1500;
|
|
503
|
-
const SESSION_LIST_PUSH_MS = 5000;
|
|
504
499
|
const SESSION_INIT_TAIL_BYTES = 512 * 1024; // bound the initial read
|
|
505
500
|
|
|
506
501
|
const _sessionWatchers = new Map(); // tabId → watcher
|
|
507
|
-
let _sessionListTimer = null;
|
|
508
502
|
|
|
509
503
|
/** Push an unsolicited event to ONE client, E2E-encrypted with its key and unicast via toConn. */
|
|
510
504
|
function pushEventTo(client, type, payload) {
|
|
@@ -691,7 +685,6 @@ function stopSessionWatch(tabId) {
|
|
|
691
685
|
|
|
692
686
|
function stopAllSessionWatches() {
|
|
693
687
|
for (const tabId of Array.from(_sessionWatchers.keys())) stopSessionWatch(tabId);
|
|
694
|
-
if (_sessionListTimer) { clearInterval(_sessionListTimer); _sessionListTimer = null; }
|
|
695
688
|
}
|
|
696
689
|
|
|
697
690
|
/**
|
|
@@ -729,47 +722,6 @@ function buildRemoteSessionList(tabs, readIndex) {
|
|
|
729
722
|
return out;
|
|
730
723
|
}
|
|
731
724
|
|
|
732
|
-
/** Push the current session list (reuses sessionsStore — the canonical source). */
|
|
733
|
-
async function pushSessionList() {
|
|
734
|
-
try {
|
|
735
|
-
// Honor the kill switch: when remote is disabled, push nothing (the project
|
|
736
|
-
// list = cwds/titles is sensitive). dispatchEnvelope already blocks cmd:*;
|
|
737
|
-
// this stops the unsolicited background push too.
|
|
738
|
-
const cfg = await loadConfig();
|
|
739
|
-
if (!cfg.remoteEnabled) return;
|
|
740
|
-
// Don't push before SAS is confirmed — session cwds/titles are sensitive user data.
|
|
741
|
-
// A relay that completes e2e:hello before the user confirms the SAS would otherwise
|
|
742
|
-
// receive the full session list immediately (same threat SAS_GATED_READS blocks for
|
|
743
|
-
// cmd:sessions:load). Guard here so client.lastSessionListJson is not poisoned either.
|
|
744
|
-
if (!anyAuthenticated()) return;
|
|
745
|
-
const sessionsStore = require('./sessionsStore.cjs');
|
|
746
|
-
const data = await sessionsStore.load();
|
|
747
|
-
// Tabs + their ACTIVE Epics → SessionMeta. tabId === claude sessionId so it
|
|
748
|
-
// matches the transcript JSONL name used by cmd:session:subscribe.
|
|
749
|
-
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
750
|
-
const sessions = buildRemoteSessionList(data?.tabs ?? [], readActiveIndex).map((s) => ({
|
|
751
|
-
...s,
|
|
752
|
-
state: _sessionWatchers.get(s.tabId)?.state ?? null,
|
|
753
|
-
}));
|
|
754
|
-
const payload = { sessions, activeTabId: data?.activeTabId ?? null };
|
|
755
|
-
const json = JSON.stringify(payload);
|
|
756
|
-
for (const c of _clients.authenticated()) {
|
|
757
|
-
if (json === c.lastSessionListJson) continue;
|
|
758
|
-
c.lastSessionListJson = json;
|
|
759
|
-
pushEventTo(c, 'event:session:list', payload);
|
|
760
|
-
}
|
|
761
|
-
} catch (e) {
|
|
762
|
-
logs.writeLine({ scope: 'webRemote', level: 'warn', message: 'pushSessionList failed', meta: { error: e?.message } });
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
function startSessionListPush() {
|
|
767
|
-
if (_sessionListTimer) return;
|
|
768
|
-
pushSessionList().catch(() => {});
|
|
769
|
-
_sessionListTimer = setInterval(() => pushSessionList().catch(() => {}), SESSION_LIST_PUSH_MS);
|
|
770
|
-
if (typeof _sessionListTimer.unref === 'function') _sessionListTimer.unref();
|
|
771
|
-
}
|
|
772
|
-
|
|
773
725
|
// ─── SM-V2-03: mobile summary via Claude Haiku 4.5 ───────────────────────────
|
|
774
726
|
// Summarization logic lives in lib/summarize.cjs (shared with exchanges.cjs).
|
|
775
727
|
|
|
@@ -811,14 +763,12 @@ async function maybeSummarize(w) {
|
|
|
811
763
|
/**
|
|
812
764
|
* Shared finish-up for any path that transitions a client's e2e state into 'authenticated'
|
|
813
765
|
* (manual SAS confirmation or pinned-key auto-trust): log, broadcast the new
|
|
814
|
-
* status, and
|
|
815
|
-
* next SESSION_LIST_PUSH_MS tick.
|
|
766
|
+
* status, and tell that one client it may start requesting (event:remote:ready).
|
|
816
767
|
*/
|
|
817
768
|
function finalizeE2eAuthenticated(logMessage, client) {
|
|
818
769
|
logs.writeLine({ scope: 'webRemote', level: 'info', message: logMessage });
|
|
819
770
|
broadcastStatus();
|
|
820
|
-
if (client) client.
|
|
821
|
-
pushSessionList().catch(() => {});
|
|
771
|
+
if (client && loadConfigSync().remoteEnabled) pushEventTo(client, 'event:remote:ready', { ts: Date.now() });
|
|
822
772
|
}
|
|
823
773
|
|
|
824
774
|
// ─── Message handling & command dispatch ─────────────────────────────────────
|
|
@@ -1111,6 +1061,70 @@ function getDispatchMap() {
|
|
|
1111
1061
|
};
|
|
1112
1062
|
},
|
|
1113
1063
|
|
|
1064
|
+
'cmd:projects:list': async () => {
|
|
1065
|
+
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
1066
|
+
const { listRemoteProjects } = require('./lib/remoteDrilldown.cjs');
|
|
1067
|
+
const data = await sessionsStore.load();
|
|
1068
|
+
return { projects: listRemoteProjects(data?.tabs ?? [], readActiveIndex) };
|
|
1069
|
+
},
|
|
1070
|
+
|
|
1071
|
+
'cmd:project:sessions': async (payload) => {
|
|
1072
|
+
const parsed = schemas.remoteProjectSessions.parse(payload);
|
|
1073
|
+
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
1074
|
+
const { listRemoteProjectSessions } = require('./lib/remoteDrilldown.cjs');
|
|
1075
|
+
const data = await sessionsStore.load();
|
|
1076
|
+
const result = listRemoteProjectSessions(data?.tabs ?? [], readActiveIndex, parsed.cwd);
|
|
1077
|
+
if (!result) throw new Error('cwd is not an open project');
|
|
1078
|
+
return result;
|
|
1079
|
+
},
|
|
1080
|
+
|
|
1081
|
+
// cwd must be exactly an open tab's cwd; sessionKey is the tab's main id or
|
|
1082
|
+
// an Epic id / claudeSessionId from that project's active index.
|
|
1083
|
+
'cmd:session:transcript': async (payload) => {
|
|
1084
|
+
const parsed = schemas.remoteSessionTranscript.parse(payload);
|
|
1085
|
+
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
1086
|
+
const { resolveEpicTranscriptPath } = require('./lib/epic/epicTranscriptPath.cjs');
|
|
1087
|
+
const { readRecentChatMessages } = require('./lib/remoteTranscriptTail.cjs');
|
|
1088
|
+
const data = await sessionsStore.load();
|
|
1089
|
+
const tab = (data?.tabs ?? []).find((t) => t && t.cwd === parsed.cwd);
|
|
1090
|
+
if (!tab) throw new Error('cwd is not an open project');
|
|
1091
|
+
validatePath(parsed.cwd);
|
|
1092
|
+
let filePath = null;
|
|
1093
|
+
if (parsed.sessionKey === (tab.id ?? tab.sessionId)) {
|
|
1094
|
+
filePath = require('./transcripts.cjs').transcriptPath(parsed.cwd, parsed.sessionKey);
|
|
1095
|
+
} else {
|
|
1096
|
+
const sessions = readActiveIndex(parsed.cwd)?.sessions ?? {};
|
|
1097
|
+
const epic = Object.values(sessions).find(
|
|
1098
|
+
(e) => e && (e.id === parsed.sessionKey || e.claudeSessionId === parsed.sessionKey),
|
|
1099
|
+
);
|
|
1100
|
+
if (epic && epic.claudeSessionId) {
|
|
1101
|
+
filePath = resolveEpicTranscriptPath({ cwd: parsed.cwd, claudeSessionId: epic.claudeSessionId }).path;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
if (!filePath) return { messages: [] };
|
|
1105
|
+
validatePath(filePath);
|
|
1106
|
+
return { messages: await readRecentChatMessages(filePath, { limit: parsed.limit }) };
|
|
1107
|
+
},
|
|
1108
|
+
|
|
1109
|
+
'cmd:session:prds': async (payload) => {
|
|
1110
|
+
const parsed = schemas.remoteSessionPrds.parse(payload);
|
|
1111
|
+
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
1112
|
+
const data = await sessionsStore.load();
|
|
1113
|
+
const tab = (data?.tabs ?? []).find((t) => t && t.cwd === parsed.cwd);
|
|
1114
|
+
if (!tab) throw new Error('cwd is not an open project');
|
|
1115
|
+
validatePath(parsed.cwd);
|
|
1116
|
+
const sessions = readActiveIndex(parsed.cwd)?.sessions ?? {};
|
|
1117
|
+
const known = Object.values(sessions).some((e) => e && e.id === parsed.epicId) || !!sessions[parsed.epicId];
|
|
1118
|
+
if (!known) throw new Error('unknown epic');
|
|
1119
|
+
const all = await require('./scheduler.cjs').listPrdsInternal();
|
|
1120
|
+
const prds = all
|
|
1121
|
+
.filter((e) => e && e.epicId === parsed.epicId)
|
|
1122
|
+
.map((e) => ({ slug: e.slug, title: e.title, status: e.status ?? null, archived: !!e.archived }))
|
|
1123
|
+
.sort((a, b) => Number(a.archived) - Number(b.archived) || String(a.slug).localeCompare(String(b.slug)))
|
|
1124
|
+
.slice(0, 50);
|
|
1125
|
+
return { prds };
|
|
1126
|
+
},
|
|
1127
|
+
|
|
1114
1128
|
'cmd:sessions:save': async (payload) => {
|
|
1115
1129
|
const parsed = schemas.sessionsPayload.parse(payload);
|
|
1116
1130
|
return sessionsStore.save(parsed);
|
|
@@ -1200,8 +1214,27 @@ function getDispatchMap() {
|
|
|
1200
1214
|
// already runs inside the Electron main process (PRD 753).
|
|
1201
1215
|
'cmd:chat:send': async (payload) => {
|
|
1202
1216
|
const parsed = schemas.chatExternalSend.parse(payload);
|
|
1203
|
-
|
|
1204
|
-
|
|
1217
|
+
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
1218
|
+
const data = await sessionsStore.load();
|
|
1219
|
+
const tabs = data?.tabs ?? [];
|
|
1220
|
+
// Resolve the target in main: an open tab, or an ACTIVE Epic (by id or
|
|
1221
|
+
// claudeSessionId) of an open project. Anything else fails loudly.
|
|
1222
|
+
if (tabs.some((t) => t.sessionId === parsed.tabId || t.id === parsed.tabId)) {
|
|
1223
|
+
chatRunner.enqueueExternalPrompt(parsed.tabId, parsed.prompt);
|
|
1224
|
+
return { ok: true };
|
|
1225
|
+
}
|
|
1226
|
+
for (const tab of tabs) {
|
|
1227
|
+
const sessions = Object.values(readActiveIndex(tab.cwd)?.sessions ?? {});
|
|
1228
|
+
const epic = sessions.find((e) => e && (e.id === parsed.tabId || e.claudeSessionId === parsed.tabId));
|
|
1229
|
+
if (!epic) continue;
|
|
1230
|
+
if (epic.status !== 'active' || !epic.claudeSessionId) throw new Error('target is not an active session');
|
|
1231
|
+
chatRunner.enqueueExternalPrompt(epic.id, parsed.prompt, {
|
|
1232
|
+
sessionId: epic.claudeSessionId,
|
|
1233
|
+
cwd: epic.cwd || tab.cwd,
|
|
1234
|
+
});
|
|
1235
|
+
return { ok: true };
|
|
1236
|
+
}
|
|
1237
|
+
throw new Error('unknown chat target');
|
|
1205
1238
|
},
|
|
1206
1239
|
|
|
1207
1240
|
'cmd:session:options': async () => {
|
|
@@ -1383,9 +1416,6 @@ function registerRemoteHandlers() {
|
|
|
1383
1416
|
logs.writeLine({ scope: 'webRemote', level: 'warn', message: 'failed to persist pinned peer pubkey', meta: { error: e?.message } });
|
|
1384
1417
|
}
|
|
1385
1418
|
}
|
|
1386
|
-
// Flush the session list immediately — the push loop was suppressed while
|
|
1387
|
-
// state !== 'authenticated', so the mobile app would otherwise wait up to
|
|
1388
|
-
// SESSION_LIST_PUSH_MS for the first useful data.
|
|
1389
1419
|
finalizeE2eAuthenticated('E2E session authenticated — SAS confirmed by user', target);
|
|
1390
1420
|
return { ok: true };
|
|
1391
1421
|
}));
|
|
@@ -1499,6 +1529,9 @@ module.exports = {
|
|
|
1499
1529
|
setE2eAuthenticated: (key, clientKey = LEGACY_CLIENT_KEY) => {
|
|
1500
1530
|
_clients.getOrCreate(clientKey).e2e = makeState('authenticated', key, null);
|
|
1501
1531
|
},
|
|
1532
|
+
setConfigCache: (cfg) => { _configCache = { ...defaultConfig(), ...cfg }; _configCacheAt = Date.now(); },
|
|
1533
|
+
finalizeE2eAuthenticated,
|
|
1534
|
+
getClient: (clientKey = LEGACY_CLIENT_KEY) => _clients.getOrCreate(clientKey),
|
|
1502
1535
|
revokeAllDevices,
|
|
1503
1536
|
streamCount: () => _clients.values().reduce((n, c) => n + c.ptyStreams.size, 0),
|
|
1504
1537
|
},
|
package/src/preload/api.d.ts
CHANGED
|
@@ -1259,9 +1259,41 @@ export interface MemoryEntry {
|
|
|
1259
1259
|
bytes: number;
|
|
1260
1260
|
}
|
|
1261
1261
|
|
|
1262
|
+
export interface MemoryAgentTarget {
|
|
1263
|
+
name: string;
|
|
1264
|
+
scope: 'user' | 'project' | 'local';
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/** Addressing for memory:* calls. `agent` targets an agent's native memory dir; `cwd` drives native resolution. */
|
|
1268
|
+
export interface MemoryTarget {
|
|
1269
|
+
workspace?: string;
|
|
1270
|
+
cwd?: string;
|
|
1271
|
+
agent?: MemoryAgentTarget;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
export interface MemoryAgent {
|
|
1275
|
+
name: string;
|
|
1276
|
+
origin: 'user' | 'project';
|
|
1277
|
+
memoryScope: 'user' | 'project' | 'local' | null;
|
|
1278
|
+
agentFile: string;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
export interface MemoryIndexHealth {
|
|
1282
|
+
lines: number;
|
|
1283
|
+
bytes: number;
|
|
1284
|
+
maxLines: number;
|
|
1285
|
+
maxBytes: number;
|
|
1286
|
+
overBudget: boolean;
|
|
1287
|
+
nearBudget: boolean;
|
|
1288
|
+
orphans: string[];
|
|
1289
|
+
dangling: string[];
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1262
1292
|
export interface MemoryListResult {
|
|
1263
1293
|
entries: MemoryEntry[];
|
|
1264
1294
|
workspace: string;
|
|
1295
|
+
dir: string;
|
|
1296
|
+
autoMemoryEnabled: boolean;
|
|
1265
1297
|
error: string | null;
|
|
1266
1298
|
}
|
|
1267
1299
|
|
|
@@ -1314,6 +1346,7 @@ export interface MemoryStaleResult {
|
|
|
1314
1346
|
entries: MemoryStaleEntry[];
|
|
1315
1347
|
workspace: string;
|
|
1316
1348
|
error: string | null;
|
|
1349
|
+
index?: MemoryIndexHealth;
|
|
1317
1350
|
}
|
|
1318
1351
|
|
|
1319
1352
|
// ────────────────────────────────────────────── Project Brief (PRD 837)
|
|
@@ -1402,37 +1435,6 @@ export interface ProjectPagesGetResult {
|
|
|
1402
1435
|
demoVideo: { path: string; mtimeMs: number } | null;
|
|
1403
1436
|
}
|
|
1404
1437
|
|
|
1405
|
-
// ────────────────────────────────────────────── Per-subagent memory
|
|
1406
|
-
// Stored at ~/.claude/session-manager/agent-memory/<agentId>.json. Keyed by
|
|
1407
|
-
// agent name (the .md filename in ~/.claude/agents/), not by workspace cwd.
|
|
1408
|
-
|
|
1409
|
-
export type AgentMemoryCategory = 'command' | 'preference' | 'pattern' | 'failure' | 'workflow';
|
|
1410
|
-
|
|
1411
|
-
export interface AgentMemoryEntry {
|
|
1412
|
-
id: string;
|
|
1413
|
-
body: string;
|
|
1414
|
-
category: AgentMemoryCategory | null;
|
|
1415
|
-
createdAt: number;
|
|
1416
|
-
updatedAt: number;
|
|
1417
|
-
bytes: number;
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
export interface AgentMemoryListResult {
|
|
1421
|
-
entries: AgentMemoryEntry[];
|
|
1422
|
-
agentId: string;
|
|
1423
|
-
error: string | null;
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
export interface AgentMemoryGetResult {
|
|
1427
|
-
entry: AgentMemoryEntry | null;
|
|
1428
|
-
error: string | null;
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
export interface AgentMemoryMutationResult {
|
|
1432
|
-
ok: boolean;
|
|
1433
|
-
error: string | null;
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
1438
|
// ────────────────────────────────────────────── Git status (richer than app:git-branch)
|
|
1437
1439
|
|
|
1438
1440
|
/** Mirrors the status returned by src/main/git.cjs mapStatus(). */
|
|
@@ -1596,6 +1598,9 @@ export interface ChatRunNoticeEvent {
|
|
|
1596
1598
|
export interface ChatExternalSendEvent {
|
|
1597
1599
|
tabId: string;
|
|
1598
1600
|
prompt: string;
|
|
1601
|
+
/** Epic's claudeSessionId + project cwd, resolved by main so the renderer can deliver before the Epic's project is loaded. */
|
|
1602
|
+
sessionId?: string;
|
|
1603
|
+
cwd?: string;
|
|
1599
1604
|
}
|
|
1600
1605
|
|
|
1601
1606
|
export type CreateProjectFolderResult =
|
|
@@ -2094,19 +2099,21 @@ export interface SessionManagerAPI {
|
|
|
2094
2099
|
};
|
|
2095
2100
|
memory: {
|
|
2096
2101
|
/** List markdown memory entries for the given workspace (defaults to 'default'). */
|
|
2097
|
-
list: (
|
|
2102
|
+
list: (target?: MemoryTarget) => Promise<MemoryListResult>;
|
|
2098
2103
|
/** Read the contents of one memory entry. */
|
|
2099
|
-
read: (name: string,
|
|
2104
|
+
read: (name: string, target?: MemoryTarget) => Promise<MemoryReadResult>;
|
|
2100
2105
|
/** Atomic write of a memory entry (max 1 MiB). */
|
|
2101
|
-
write: (name: string, content: string,
|
|
2106
|
+
write: (name: string, content: string, target?: MemoryTarget) => Promise<MemoryMutationResult>;
|
|
2102
2107
|
/** Delete a memory entry. */
|
|
2103
|
-
delete: (name: string,
|
|
2108
|
+
delete: (name: string, target?: MemoryTarget) => Promise<MemoryMutationResult>;
|
|
2104
2109
|
/** Create a new memory entry with starter frontmatter + body. */
|
|
2105
|
-
create: (name: string, description?: string,
|
|
2110
|
+
create: (name: string, description?: string, target?: MemoryTarget) => Promise<MemoryMutationResult>;
|
|
2106
2111
|
/** Aggregate workspace memories into semantic clusters. `refresh:true` fires a cost-gated `claude -p` pass; otherwise returns the cache only. */
|
|
2107
2112
|
aggregate: (workspace: string, refresh?: boolean, cwd?: string) => Promise<MemoryAggregateResult>;
|
|
2108
2113
|
/** Deterministic, zero-LLM-cost staleness report. `cwd` (optional) scopes the dead-repo-ref check. */
|
|
2109
|
-
stale: (
|
|
2114
|
+
stale: (target?: MemoryTarget) => Promise<MemoryStaleResult>;
|
|
2115
|
+
/** Agents (user + project) with their `memory:` frontmatter scope. */
|
|
2116
|
+
agents: (cwd?: string) => Promise<{ agents: MemoryAgent[] }>;
|
|
2110
2117
|
};
|
|
2111
2118
|
projectBrief: {
|
|
2112
2119
|
/** Read brief.json (or null if none yet) plus cheaply-computed source/drift metadata. Never fires an LLM call. */
|
|
@@ -2154,21 +2161,6 @@ export interface SessionManagerAPI {
|
|
|
2154
2161
|
fields: { cwd: string; epicId: string; source: string },
|
|
2155
2162
|
) => Promise<{ ok: boolean }>;
|
|
2156
2163
|
};
|
|
2157
|
-
agentMemory: {
|
|
2158
|
-
/** List all memory entries for one subagent. Sorted newest first. */
|
|
2159
|
-
list: (agentId: string) => Promise<AgentMemoryListResult>;
|
|
2160
|
-
/** Get one entry's full body. Returns `{entry:null}` if missing. */
|
|
2161
|
-
get: (agentId: string, entryId: string) => Promise<AgentMemoryGetResult>;
|
|
2162
|
-
/** Upsert one entry. Atomic write through config.cjs; max body 1 MiB. */
|
|
2163
|
-
set: (
|
|
2164
|
-
agentId: string,
|
|
2165
|
-
entryId: string,
|
|
2166
|
-
body: string,
|
|
2167
|
-
category?: AgentMemoryCategory,
|
|
2168
|
-
) => Promise<AgentMemoryMutationResult>;
|
|
2169
|
-
/** Delete one entry. Removes the file outright when last entry is removed. */
|
|
2170
|
-
delete: (agentId: string, entryId: string) => Promise<AgentMemoryMutationResult>;
|
|
2171
|
-
};
|
|
2172
2164
|
git: {
|
|
2173
2165
|
/** Full git status for `cwd`. Returns null when not a git repo, git is
|
|
2174
2166
|
* missing, or the call times out (5s ceiling). Cached per-cwd for 5s. */
|
package/src/preload/index.cjs
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
const { contextBridge, ipcRenderer } = require('electron');
|
|
2
2
|
|
|
3
|
+
/** Forward only the defined addressing keys of a memory target. */
|
|
4
|
+
function memoryTarget(target) {
|
|
5
|
+
const out = {};
|
|
6
|
+
if (!target || typeof target !== 'object') return out;
|
|
7
|
+
if (target.workspace) out.workspace = target.workspace;
|
|
8
|
+
if (target.cwd) out.cwd = target.cwd;
|
|
9
|
+
if (target.agent) out.agent = target.agent;
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
contextBridge.exposeInMainWorld('api', {
|
|
4
14
|
app: {
|
|
5
15
|
version: () => ipcRenderer.invoke('app:version'),
|
|
@@ -335,22 +345,20 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
335
345
|
writeText: (text) => ipcRenderer.invoke('clipboard:write-text', { text }),
|
|
336
346
|
},
|
|
337
347
|
memory: {
|
|
338
|
-
list: (
|
|
339
|
-
read: (name,
|
|
340
|
-
write: (name, content,
|
|
341
|
-
ipcRenderer.invoke('memory:write',
|
|
342
|
-
delete: (name,
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const payload = { name };
|
|
348
|
+
list: (target) => ipcRenderer.invoke('memory:list', memoryTarget(target)),
|
|
349
|
+
read: (name, target) => ipcRenderer.invoke('memory:read', { name, ...memoryTarget(target) }),
|
|
350
|
+
write: (name, content, target) =>
|
|
351
|
+
ipcRenderer.invoke('memory:write', { name, content, ...memoryTarget(target) }),
|
|
352
|
+
delete: (name, target) => ipcRenderer.invoke('memory:delete', { name, ...memoryTarget(target) }),
|
|
353
|
+
create: (name, description, target) => {
|
|
354
|
+
const payload = { name, ...memoryTarget(target) };
|
|
346
355
|
if (description) payload.description = description;
|
|
347
|
-
if (workspace) payload.workspace = workspace;
|
|
348
356
|
return ipcRenderer.invoke('memory:create', payload);
|
|
349
357
|
},
|
|
350
358
|
aggregate: (workspace, refresh, cwd) =>
|
|
351
359
|
ipcRenderer.invoke('memory:aggregate', { workspace, ...(refresh ? { refresh: true } : {}), ...(cwd ? { cwd } : {}) }),
|
|
352
|
-
stale: (
|
|
353
|
-
|
|
360
|
+
stale: (target) => ipcRenderer.invoke('memory:stale', memoryTarget(target)),
|
|
361
|
+
agents: (cwd) => ipcRenderer.invoke('memory:agents', cwd ? { cwd } : {}),
|
|
354
362
|
},
|
|
355
363
|
projectBrief: {
|
|
356
364
|
get: (cwd) => ipcRenderer.invoke('project-brief:get', { cwd }),
|
|
@@ -381,16 +389,6 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
381
389
|
auditLog: {
|
|
382
390
|
append: (kind, fields) => ipcRenderer.invoke('auditLog:append', { kind, ...fields }),
|
|
383
391
|
},
|
|
384
|
-
agentMemory: {
|
|
385
|
-
list: (agentId) => ipcRenderer.invoke('agent-memory:list', { agentId }),
|
|
386
|
-
get: (agentId, entryId) => ipcRenderer.invoke('agent-memory:get', { agentId, entryId }),
|
|
387
|
-
set: (agentId, entryId, body, category) => {
|
|
388
|
-
const payload = { agentId, entryId, body };
|
|
389
|
-
if (category) payload.category = category;
|
|
390
|
-
return ipcRenderer.invoke('agent-memory:set', payload);
|
|
391
|
-
},
|
|
392
|
-
delete: (agentId, entryId) => ipcRenderer.invoke('agent-memory:delete', { agentId, entryId }),
|
|
393
|
-
},
|
|
394
392
|
git: {
|
|
395
393
|
// Returns null when cwd is not a git repo, git is missing, or the call
|
|
396
394
|
// times out (5s ceiling). The existing `app.gitBranch` is intentionally
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import{b3 as I,aX as K,aF as i,aW as A,ap as e,n as W,Z as oe,ad as H,a5 as le,ah as U}from"./index-Ci1u018F.js";import{P as q}from"./Panel-DGMEuYYG.js";import{L as ee}from"./ListDetail-Bco_wfs6.js";import{M as te}from"./MarkdownEditor-C-yHSXNV.js";import{V as se}from"./ViewTabs-CjHw3kGY.js";import"./monaco-editor-BsIlgG7c.js";function ie(t){return!t||!t.trim()?"default":t.replace(/[^a-zA-Z0-9]/g,"-")}const re=/^[a-z0-9-_]+$/;function ce(t,l,n,o){return t.filter(x=>!l||x.name.toLowerCase().includes(l.toLowerCase())).filter(x=>{var s;return!n||((s=o[x.name])==null?void 0:s.stale)===!0})}const de=["Commands:"," list — show all memory entries"," show <slug> — read one entry"," search <text> — find entries containing <text>"," forget <slug> — delete an entry (with confirm)"," remember <free-form> — ask Claude to save this"," help — this message","","Anything else: I will offer to send it to Claude in the terminal,","or add it as a new memory entry directly."].join(`
|
|
2
|
-
`);function me(t){const l=t.trim();if(!l)return{kind:"freeform",arg:"",raw:t};const n=l.toLowerCase();if(n==="help"||n==="?")return{kind:"help",arg:"",raw:t};if(n==="list"||n==="ls"||n==="list memories"||n==="list all"||n==="what do you remember"||n==="what do you remember?")return{kind:"list",arg:"",raw:t};const o=l.match(/^(?:show|read|open)\s+(.+)$/i);if(o)return{kind:"show",arg:o[1].trim(),raw:t};const x=l.match(/^(?:forget|delete|remove)\s+(.+)$/i);if(x)return{kind:"forget",arg:x[1].trim(),raw:t};const s=l.match(/^(?:search|find)\s+(.+)$/i)||l.match(/^what (?:do )?you know about\s+(.+?)\??$/i);if(s)return{kind:"search",arg:s[1].trim(),raw:t};const f=l.match(/^remember(?:\s+that)?\s+(.+)$/i)||l.match(/^note(?:\s+that)?\s+(.+)$/i)||l.match(/^save(?:\s+that)?\s+(.+)$/i);return f?{kind:"remember",arg:f[1].trim(),raw:t}:{kind:"freeform",arg:l,raw:t}}function G(t,l){const n=t.toLowerCase().replace(/\.md$/,""),o=l.find(f=>f.name.replace(/\.md$/,"").toLowerCase()===n);if(o)return o;const x=l.filter(f=>f.name.replace(/\.md$/,"").toLowerCase().startsWith(n));if(x.length===1)return x[0];const s=l.filter(f=>f.name.replace(/\.md$/,"").toLowerCase().includes(n));return s.length===1?s[0]:null}function Z(t){const l=new Set(["the","a","an","is","are","to","of","in","for","and","or"]);return t.toLowerCase().replace(/[^a-z0-9\s-]/g," ").split(/\s+/).filter(x=>x&&!l.has(x)).slice(0,4).join("-").replace(/-+/g,"-").replace(/^-|-$/g,"")||`note-${Date.now().toString(36).slice(-4)}`}function X(t,l){return[`Please save this to my workspace memory (workspace: ${t}).`,`Use the memory tool (or write a markdown file under ~/.claude/projects/${t}/memory/).`,"Pick a short kebab-case filename and add a one-line title at the top.","","Memory to save:",l].join(`
|
|
3
|
-
`)}function Q(){return`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`}function xe({workspace:t,entries:l,onChanged:n}){const o=I(),x=K(),s=(x==null?void 0:x.id)??null,[f,u]=i.useState(""),[d,b]=i.useState(()=>[{id:Q(),role:"system",text:"Hi — I am the memory assistant for this workspace. Type `help` for what I understand, or just talk to me."}]),[C,g]=i.useState(!1),j=i.useRef(null),L=i.useRef(null);i.useEffect(()=>{const a=j.current;a&&(a.scrollTop=a.scrollHeight)},[d]);const c=i.useCallback(a=>{b(p=>[...p,{...a,id:Q()}])},[]),$=i.useCallback(()=>{if(l.length===0){c({role:"result",text:"No memories saved for this workspace yet."});return}c({role:"result",text:`Found ${l.length} ${l.length===1?"memory":"memories"}:`,payload:{kind:"entries",entries:l}})},[l,c]),M=i.useCallback(async a=>{const p=G(a,l);if(!p){c({role:"result",text:`No memory matches "${a}". Try \`list\` to see what is here.`});return}const y=await window.api.memory.read(p.name,t);if(y.error){A.error(`Memory read failed: ${y.error}`),c({role:"result",text:`Read failed: ${y.error}`});return}c({role:"result",text:`${p.name}`,payload:{kind:"entry",entry:p,content:y.content}})},[l,t,c]),D=i.useCallback(async a=>{const p=a.toLowerCase(),y=l.filter(k=>k.name.toLowerCase().includes(p)),O=[];for(const k of l){if(y.includes(k))continue;const B=await window.api.memory.read(k.name,t);if(B.error)continue;const m=B.content.split(`
|
|
4
|
-
`),h=m.findIndex(N=>N.toLowerCase().includes(p));h>=0&&O.push({entry:k,snippet:m[h].trim().slice(0,200)})}const r=[...y.map(k=>({entry:k,snippet:"(filename match)"})),...O];if(r.length===0){c({role:"result",text:`No matches for "${a}".`});return}c({role:"result",text:`Found ${r.length} ${r.length===1?"match":"matches"} for "${a}":`,payload:{kind:"search",query:a,hits:r}})},[l,t,c]),R=i.useCallback(async a=>{const p=G(a,l);if(!p){c({role:"result",text:`No memory matches "${a}". Try \`list\` to see what is here.`});return}if(!window.confirm(`Delete memory "${p.name}"? This cannot be undone.`))return;const y=await window.api.memory.delete(p.name,t);if(!y.ok){A.error(`Memory delete failed: ${y.error??"unknown error"}`),c({role:"result",text:`Delete failed: ${y.error??"unknown error"}`});return}c({role:"result",text:`Deleted ${p.name}.`}),n()},[l,t,c,n]),w=i.useCallback(a=>{const p=Z(a),y=X(t,a);c({role:"result",text:"I do not write to memory from this panel. Send the request to Claude in the active terminal, or add it directly as a new memory entry below.",payload:{kind:"suggest-send",prompt:y,original:a}}),c({role:"result",text:`Add it directly as \`${p}.md\` instead?`,payload:{kind:"suggest-add",slug:p,content:a}})},[t,c]),_=i.useCallback(a=>{const p=Z(a),y=X(t,a);c({role:"result",text:"Not a recognised command. Options:",payload:{kind:"suggest-send",prompt:y,original:a}}),c({role:"result",text:`…or save it directly as \`${p}.md\`.`,payload:{kind:"suggest-add",slug:p,content:a}})},[t,c]),E=i.useCallback(async a=>{g(!0);try{switch(a.kind){case"list":$();break;case"show":await M(a.arg);break;case"search":await D(a.arg);break;case"forget":await R(a.arg);break;case"help":c({role:"result",text:de});break;case"remember":w(a.arg);break;case"freeform":_(a.arg);break}}finally{g(!1)}},[$,M,D,R,w,_,c]),T=i.useCallback(async()=>{const a=f.trim();!a||C||(u(""),c({role:"user",text:a}),await E(me(a)))},[f,C,E,c]),v=a=>{a.key==="Enter"&&!a.shiftKey&&(a.preventDefault(),T())},S=i.useCallback(a=>{if(!s){A.error("No active terminal session — open or focus a tab first.");return}window.api.pty.write({tabId:s,data:a}),A.info("Prompt sent to active terminal (not submitted)")},[s]),P=i.useCallback(async(a,p)=>{if(!re.test(a)){A.error(`Invalid slug "${a}" — must be [a-z0-9-_]+`);return}const y=`${a}.md`,O=await window.api.memory.create(y,void 0,t);if(!O.ok){A.error(`Create failed: ${O.error??"unknown error"}`);return}const r=await window.api.memory.read(y,t),k=r.error?p:`${r.content.trimEnd()}
|
|
5
|
-
|
|
6
|
-
${p}
|
|
7
|
-
`,B=await window.api.memory.write(y,k,t);if(!B.ok){A.error(`Write failed: ${B.error??"unknown error"}`);return}c({role:"result",text:`Saved as ${y}.`}),n()},[t,c,n]),F=i.useMemo(()=>[{label:"List memories",value:"list"},{label:"Help",value:"help"},{label:"Search…",value:"search "},{label:"Remember…",value:"remember "}],[]);return o?e.jsxs("div",{className:"h-full flex flex-col",children:[e.jsxs("div",{ref:j,className:"flex-1 min-h-0 overflow-y-auto px-3 py-2 space-y-2",children:[d.map(a=>e.jsx(fe,{message:a,entries:l,onShow:p=>{var y;u(`show ${p.replace(/\.md$/,"")}`),(y=L.current)==null||y.focus()},onSendToClaude:S,onAddDirect:P},a.id)),C&&e.jsx("div",{className:"text-xs text-fg-faint italic",children:"working…"})]}),e.jsxs("div",{className:"shrink-0 border-t border-line px-3 py-2 space-y-2",children:[e.jsx("div",{className:"flex gap-1 flex-wrap",children:F.map(a=>e.jsx("button",{onClick:()=>{var p;u(a.value),(p=L.current)==null||p.focus()},className:"px-2 py-0.5 text-[11px] text-fg-faint hover:text-fg-dim border border-line hover:border-fg-faint rounded transition-colors",children:a.label},a.label))}),e.jsxs("div",{className:"flex gap-2 items-end",children:[e.jsx(oe,{}),e.jsx("textarea",{ref:L,value:f,onChange:a=>u(a.target.value),onKeyDown:v,placeholder:"Type `list`, `show <slug>`, `search <text>`, `remember …`, or talk freely. Enter to send, Shift+Enter for newline.",rows:2,className:"flex-1 bg-bg border border-line rounded px-2 py-1 text-xs text-fg resize-none focus:outline-none focus:border-fg-faint"}),e.jsx("button",{onClick:T,disabled:!f.trim()||C,className:"px-3 py-1 text-xs text-fg border border-line hover:bg-bg-hi rounded transition-colors disabled:opacity-40 disabled:cursor-not-allowed",children:"Send"})]}),e.jsxs("div",{className:"text-[10px] text-fg-faint",children:["Workspace: ",e.jsx("span",{className:"font-mono",children:t}),!s&&e.jsx("span",{className:"ml-2 text-amber-400",children:"(no active terminal — Send-to-Claude disabled)"})]})]})]}):e.jsx(W,{title:"loading…"})}function fe({message:t,entries:l,onShow:n,onSendToClaude:o,onAddDirect:x}){var f,u,d,b,C;const s=t.role==="user"?"bg-bg-hi text-fg ml-8":t.role==="system"?"bg-bg text-fg-dim italic":"bg-bg text-fg-dim";return e.jsxs("div",{className:`text-xs rounded border border-line px-3 py-2 ${s}`,children:[e.jsx("div",{className:"whitespace-pre-wrap font-mono text-[11px] leading-relaxed",children:t.text}),((f=t.payload)==null?void 0:f.kind)==="entries"&&e.jsx("div",{className:"mt-2 space-y-0.5",children:t.payload.entries.map(g=>e.jsxs("button",{onClick:()=>n(g.name),className:"block w-full text-left px-2 py-0.5 text-[11px] text-fg-dim hover:text-fg hover:bg-bg-hi rounded font-mono",children:[g.name," ",e.jsxs("span",{className:"text-fg-faint",children:["(",H(g.bytes),")"]})]},g.name))}),((u=t.payload)==null?void 0:u.kind)==="entry"&&e.jsx("pre",{className:"mt-2 max-h-64 overflow-auto px-2 py-1 bg-bg-hi rounded text-[11px] whitespace-pre-wrap text-fg",children:t.payload.content}),((d=t.payload)==null?void 0:d.kind)==="search"&&e.jsx("div",{className:"mt-2 space-y-1",children:t.payload.hits.map((g,j)=>e.jsxs("button",{onClick:()=>n(g.entry.name),className:"block w-full text-left px-2 py-1 hover:bg-bg-hi rounded",children:[e.jsx("div",{className:"font-mono text-[11px] text-fg-dim",children:g.entry.name}),e.jsx("div",{className:"text-[10px] text-fg-faint truncate",children:g.snippet})]},`${g.entry.name}-${j}`))}),((b=t.payload)==null?void 0:b.kind)==="suggest-send"&&e.jsxs("div",{className:"mt-2 flex gap-2 items-center",children:[e.jsx("button",{onClick:()=>o(t.payload.kind==="suggest-send"?t.payload.prompt:""),className:"px-2 py-0.5 text-[11px] text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors",children:"Send to Claude in terminal"}),e.jsx("span",{className:"text-[10px] text-fg-faint",children:"paste-only — you press Enter"})]}),((C=t.payload)==null?void 0:C.kind)==="suggest-add"&&e.jsxs("div",{className:"mt-2 flex gap-2 items-center",children:[e.jsx("button",{onClick:()=>{var g;return((g=t.payload)==null?void 0:g.kind)==="suggest-add"&&x(t.payload.slug,t.payload.content)},className:"px-2 py-0.5 text-[11px] text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors",children:"Add as new memory"}),e.jsxs("span",{className:"text-[10px] text-fg-faint font-mono",children:[t.payload.slug,".md"]})]})]})}const z=le((t,l)=>({entriesByAgent:new Map,loading:null,errorByAgent:new Map,loadAgent:async n=>{t({loading:n});const o=await window.api.agentMemory.list(n),x=new Map(l().entriesByAgent);x.set(n,o.entries);const s=new Map(l().errorByAgent);o.error?(s.set(n,o.error),A.error(`Agent memory load failed: ${o.error}`)):s.delete(n),t({entriesByAgent:x,loading:null,errorByAgent:s})},setEntry:async(n,o,x,s)=>{const f=await window.api.agentMemory.set(n,o,x,s);return f.ok&&await l().loadAgent(n),f},deleteEntry:async(n,o)=>{const x=await window.api.agentMemory.delete(n,o);if(x.ok){const s=new Map(l().entriesByAgent),f=s.get(n)??[];s.set(n,f.filter(u=>u.id!==o)),t({entriesByAgent:s})}return x}})),V=/^[A-Za-z0-9._-]{1,128}$/,Y={command:"Commands",preference:"Preferences",pattern:"Patterns",failure:"Avoid",workflow:"Workflow"},ne=["command","preference","pattern","failure","workflow"];async function ue(t,l){const n=[{dir:`${t}/.claude/agents`,scope:"user"}];l&&n.push({dir:`${l}/.claude/agents`,scope:"project"});const o=[];for(const{dir:s,scope:f}of n)try{const u=await window.api.config.listDir(s,{filesOnly:!0});for(const d of u.entries){if(!d.name.endsWith(".md"))continue;const b=d.name.replace(/\.md$/,"");V.test(b)&&o.push({id:b,label:b,scope:f})}}catch{}const x=new Map;for(const s of o)s.scope==="project"?x.set(s.id,s):x.has(s.id)||x.set(s.id,s);return Array.from(x.values()).sort((s,f)=>s.id.localeCompare(f.id))}function he(){const t=I(),l=K(),n=(l==null?void 0:l.cwd)??null,[o,x]=i.useState([]),[s,f]=i.useState(null),[u,d]=i.useState(null),[b,C]=i.useState(""),[g,j]=i.useState(!1),[L,c]=i.useState(!1),[$,M]=i.useState({}),D=z(r=>r.entriesByAgent),R=z(r=>r.loadAgent),w=z(r=>r.setEntry),_=z(r=>r.deleteEntry);i.useEffect(()=>{if(!t)return;let r=!1;return ue(t,n).then(k=>{r||(x(k),f(B=>{var m;return B&&k.find(h=>h.id===B)?B:((m=k[0])==null?void 0:m.id)??null}))}),()=>{r=!0}},[t,n]),i.useEffect(()=>{s&&(d(null),R(s))},[s,R]);const E=s?D.get(s)??[]:[],T=i.useMemo(()=>E.filter(r=>!b||r.id.toLowerCase().includes(b.toLowerCase())||r.body.toLowerCase().includes(b.toLowerCase())),[E,b]),v=i.useMemo(()=>E.find(r=>r.id===u)??null,[E,u]);i.useEffect(()=>{var r;!u&&T.length>0?d(T[0].id):u&&!T.find(k=>k.id===u)&&d(((r=T[0])==null?void 0:r.id)??null)},[T,u]);const S=s&&u?`${s}::${u}`:null,P=S?$[S]??(v==null?void 0:v.body)??"":"",F=!!S&&v!==null&&P!==v.body,a=i.useCallback(async()=>{s&&await R(s)},[s,R]),p=async()=>{if(!s||!u||!S)return;j(!0);const r=await w(s,u,P,(v==null?void 0:v.category)??void 0);if(j(!1),!r.ok){A.error(`Save failed: ${r.error??"unknown"}`);return}M(k=>{const B={...k};return delete B[S],B})},y=async()=>{if(!s||!u||!window.confirm(`Delete memory "${u}"? This cannot be undone.`))return;j(!0);const r=await _(s,u);if(j(!1),!r.ok){A.error(`Delete failed: ${r.error??"unknown"}`);return}d(null)},O=async r=>{if(!s||!u||!v)return;j(!0);const k=await w(s,u,v.body,r??void 0);j(!1),k.ok||A.error(`Update failed: ${k.error??"unknown"}`)};return t?e.jsx(q,{toolbar:e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"text-fg-faint",children:"Agent"}),e.jsxs("select",{value:s??"",onChange:r=>f(r.target.value||null),className:"bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg max-w-[16rem]",children:[o.length===0&&e.jsx("option",{value:"",children:"(no agents found)"}),o.map(r=>e.jsxs("option",{value:r.id,children:[r.label," ",r.scope==="project"?"· project":""]},`${r.scope}:${r.id}`))]}),e.jsx("span",{className:"mx-2 text-fg-faint",children:"·"}),e.jsxs("span",{className:"text-fg-faint",children:[E.length," ",E.length===1?"entry":"entries"]}),e.jsx("div",{className:"flex-1"}),e.jsx("button",{onClick:()=>c(!0),disabled:!s,className:"px-2 py-0.5 text-xs text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors disabled:opacity-40",children:"+ New entry"}),e.jsx("button",{onClick:a,disabled:!s,className:"ml-2 px-2 py-0.5 text-xs text-fg-faint hover:text-fg-dim border border-line hover:border-fg-faint rounded transition-colors disabled:opacity-40",children:"Refresh"})]}),footer:v?e.jsxs("div",{className:"px-3 py-1.5 flex items-center gap-3 text-xs",children:[e.jsx("button",{onClick:p,disabled:!F||g,className:"px-2 py-0.5 text-xs text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors disabled:opacity-40 disabled:cursor-not-allowed",children:g?"Saving…":"Save"}),e.jsx("button",{onClick:y,disabled:g,className:"px-2 py-0.5 text-xs text-red-400 hover:text-red-300 border border-line hover:border-red-400/50 rounded transition-colors disabled:opacity-40",children:"Delete"}),e.jsx("span",{className:"text-fg-faint",children:"Category:"}),e.jsxs("select",{value:v.category??"",onChange:r=>O(r.target.value||null),disabled:g,className:"bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg-dim disabled:opacity-40",children:[e.jsx("option",{value:"",children:"(none)"}),ne.map(r=>e.jsx("option",{value:r,children:Y[r]},r))]}),e.jsx("span",{className:"text-fg-faint truncate flex-1",children:v.id}),F&&e.jsx("span",{className:"text-accent",children:"unsaved"})]}):null,children:e.jsx(ee,{sidebar:e.jsxs("div",{className:"py-1",children:[e.jsx("div",{className:"px-2 pb-1",children:e.jsx("input",{value:b,onChange:r=>C(r.target.value),placeholder:"filter…",className:"w-full bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg"})}),s?T.length===0?e.jsx("div",{className:"px-3 py-2 text-xs text-fg-faint italic",children:E.length===0?"no entries yet":"no matches"}):T.map(r=>e.jsxs("button",{onClick:()=>d(r.id),className:`w-full text-left px-3 py-1 text-xs flex items-center justify-between ${u===r.id?"bg-bg-hi text-fg":"text-fg-dim hover:text-fg hover:bg-bg-hi"}`,title:r.id,children:[e.jsx("span",{className:"truncate",children:r.id}),e.jsxs("span",{className:"ml-2 text-fg-faint shrink-0 text-[10px]",children:[H(r.bytes)," · ",U(r.updatedAt)]})]},r.id)):e.jsx("div",{className:"px-3 py-2 text-xs text-fg-faint italic",children:"select an agent first"})]}),detail:L&&s?e.jsx(pe,{agentId:s,existingIds:new Set(E.map(r=>r.id)),onCancel:()=>c(!1),onCreated:r=>{c(!1),d(r),R(s)}}):v&&s&&u&&S?e.jsxs("div",{className:"h-full flex flex-col",children:[e.jsxs("div",{className:"shrink-0 px-3 py-2 border-b border-line text-xs",children:[e.jsx("div",{className:"text-fg truncate",children:v.id}),e.jsxs("div",{className:"text-fg-faint truncate",children:[H(v.bytes)," · modified ",U(v.updatedAt),v.category?` · ${Y[v.category]}`:""]})]}),e.jsx("div",{className:"flex-1 min-h-0",children:e.jsx(te,{path:`agent-memory/${s}/${v.id}.md`,value:P,onChange:r=>M(k=>({...k,[S]:r}))})})]}):e.jsx(W,{title:s?E.length===0?"no entries yet":"select an entry":"no agent selected",hint:s&&E.length===0?"Use “+ New entry” to give this subagent its first memory.":void 0})})}):e.jsx(W,{title:"loading…"})}function pe({agentId:t,existingIds:l,onCancel:n,onCreated:o}){const x=z(w=>w.setEntry),[s,f]=i.useState(""),[u,d]=i.useState(""),[b,C]=i.useState(""),[g,j]=i.useState(!1),[L,c]=i.useState(null),$=s.trim(),M=V.test($)&&!l.has($),D=s?V.test($)?l.has($)?"an entry with this id already exists":null:"must match [A-Za-z0-9._-]+":null,R=async()=>{if(!M)return;j(!0),c(null);const w=await x(t,$,u,b||void 0);if(j(!1),!w.ok){c(w.error??"create failed");return}o($)};return e.jsxs("div",{className:"p-4 max-w-xl space-y-3",children:[e.jsxs("div",{className:"text-sm text-fg",children:["New memory entry for ",e.jsx("span",{className:"font-mono text-fg-dim",children:t})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs text-fg-faint",children:"Entry id"}),e.jsx("input",{autoFocus:!0,value:s,onChange:w=>f(w.target.value),placeholder:"e.g. preferred-test-runner",className:"w-full bg-bg border border-line rounded px-2 py-1 text-xs text-fg font-mono"}),D&&e.jsx("div",{className:"text-[10px] text-red-400",children:D}),e.jsxs("div",{className:"text-[10px] text-fg-faint",children:["Letters, digits, dots, dashes, underscores. Persists at",e.jsxs("code",{className:"ml-1 font-mono text-fg-dim",children:["~/.claude/session-manager/agent-memory/",t,".json"]}),"."]})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs text-fg-faint",children:"Category (optional)"}),e.jsxs("select",{value:b,onChange:w=>C(w.target.value),className:"bg-bg border border-line rounded px-2 py-1 text-xs text-fg",children:[e.jsx("option",{value:"",children:"(none)"}),ne.map(w=>e.jsx("option",{value:w,children:Y[w]},w))]})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs text-fg-faint",children:"Body (markdown)"}),e.jsx("textarea",{value:u,onChange:w=>d(w.target.value),rows:6,placeholder:"What should this subagent remember?",className:"w-full bg-bg border border-line rounded px-2 py-1 text-xs text-fg font-mono"})]}),L&&e.jsx("div",{className:"text-xs text-red-400",children:L}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("button",{onClick:R,disabled:!M||g,className:"px-3 py-1 text-xs text-fg border border-line hover:bg-bg-hi rounded transition-colors disabled:opacity-40",children:g?"Creating…":"Create"}),e.jsx("button",{onClick:n,disabled:g,className:"px-3 py-1 text-xs text-fg-faint hover:text-fg-dim rounded transition-colors",children:"Cancel"})]})]})}function ge({workspace:t,cwd:l,onOpenMember:n}){const[o,x]=i.useState(null),[s,f]=i.useState(!1),[u,d]=i.useState(!1),b=i.useRef(0),C=i.useCallback(async c=>{const $=++b.current;f(!0);try{const M=await window.api.memory.aggregate(t,c,l??void 0);if(b.current!==$)return;x(M)}catch(M){if(b.current!==$)return;A.error(`Memory aggregate failed: ${M instanceof Error?M.message:String(M)}`)}finally{b.current===$&&(f(!1),d(!0))}},[t,l]);i.useEffect(()=>{x(null),d(!1),C(!1)},[C]);const g=(o==null?void 0:o.clusters)??[],j=(o==null?void 0:o.orphans)??[],L=g.length>0||j.length>0;return e.jsxs("div",{className:"h-full flex flex-col",children:[e.jsxs("div",{className:"shrink-0 flex items-center gap-2 px-3 py-2 border-b border-line text-xs",children:[e.jsx("span",{className:"text-fg-faint",children:o!=null&&o.generatedAt?`generated ${U(o.generatedAt)} ago`:"not yet aggregated"}),e.jsx("div",{className:"flex-1"}),e.jsx("button",{onClick:()=>C(!0),disabled:s,className:"px-2 py-0.5 text-xs text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors disabled:opacity-40 disabled:cursor-not-allowed",children:s?"Aggregating…":o!=null&&o.generatedAt?"Refresh":"Aggregate"})]}),e.jsx("div",{className:"flex-1 min-h-0 overflow-auto p-3",children:u?L?e.jsxs("div",{className:"space-y-4",children:[g.length>0&&e.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-3",children:g.map(c=>e.jsx(be,{cluster:c,onOpenMember:n},c.id))}),j.length>0&&e.jsxs("div",{className:"border border-line rounded p-3",children:[e.jsx("div",{className:"text-xs text-fg mb-2",children:"Unclustered"}),e.jsx("div",{className:"flex flex-wrap gap-1",children:j.map(c=>e.jsx("button",{onClick:()=>n(c),className:"px-2 py-0.5 text-[11px] font-mono text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors",children:c},c))})]})]}):e.jsx(W,{title:"no clusters yet",hint:"Click Aggregate to organize this workspace's memories into named clusters."}):e.jsx(W,{title:"loading…"})})]})}function be({cluster:t,onOpenMember:l}){return e.jsxs("div",{className:"border border-line rounded p-3",children:[e.jsx("div",{className:"text-sm text-fg",children:t.name}),t.summary&&e.jsx("div",{className:"text-xs text-fg-faint mt-0.5",children:t.summary}),e.jsx("div",{className:"mt-2 flex flex-wrap gap-1",children:t.memberSlugs.map(n=>e.jsx("button",{onClick:()=>l(n),className:"px-2 py-0.5 text-[11px] font-mono text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors",children:n},n))}),t.links.length>0&&e.jsx("div",{className:"mt-2 space-y-0.5",children:t.links.map((n,o)=>e.jsxs("div",{className:"text-[10px] text-fg-faint font-mono",children:[n.from," → ",n.to,n.label?` (${n.label})`:""]},`${n.from}-${n.to}-${o}`))})]})}const ye=[{key:"workspace",label:"Workspace"},{key:"subagent",label:"Subagent"}],J="sm.memoryTab.scope";function je(){const[t,l]=i.useState(()=>localStorage.getItem(J)==="subagent"?"subagent":"workspace");return i.useEffect(()=>{localStorage.setItem(J,t)},[t]),e.jsxs("div",{className:"h-full flex flex-col",children:[e.jsxs("div",{className:"shrink-0 flex items-center gap-3 px-3 py-2 border-b border-line",children:[e.jsx(se,{options:ye,active:t,onChange:l}),e.jsx("span",{className:"text-[11px] text-fg-faint",children:t==="workspace"?"memories scoped to the active project":"long-term memory per subagent"})]}),e.jsx("div",{className:"flex-1 min-h-0",children:t==="workspace"?e.jsx(ve,{}):e.jsx(he,{})})]})}const Me=i.memo(je);function ve(){const t=I(),l=K(),n=(l==null?void 0:l.cwd)??null,o=i.useMemo(()=>ie(n),[n]),[x,s]=i.useState("classic"),[f,u]=i.useState([]),[d,b]=i.useState(null),[C,g]=i.useState(""),[j,L]=i.useState({}),[c,$]=i.useState(!1),[M,D]=i.useState({}),[R,w]=i.useState({}),[_,E]=i.useState(!1),[T,v]=i.useState(!1),S=i.useCallback(async()=>{if(!t)return;const m=await window.api.memory.list(o);u(m.entries),m.error&&A.error(`Memory list failed: ${m.error}`),b(h=>{var N;return h&&m.entries.some(ae=>ae.name===h)?h:((N=m.entries[0])==null?void 0:N.name)??null});try{const h=await window.api.memory.stale(o,n??void 0);L(Object.fromEntries(h.entries.map(N=>[N.name,N])))}catch(h){L({}),A.error(`Memory staleness check failed: ${h instanceof Error?h.message:String(h)}`)}},[t,o,n]);if(i.useEffect(()=>{D({}),w({}),b(null),S()},[o,S]),i.useEffect(()=>{if(!t)return;const m=`${t}/.claude/projects/${o}/memory`;window.api.config.watch([m]);const h=window.api.config.onChanged(({path:N})=>{(N===m||N.startsWith(m+"/"))&&S()});return()=>{h(),window.api.config.unwatch([m])}},[t,o,S]),i.useEffect(()=>{if(!d||M[d]!==void 0)return;let m=!1;return(async()=>{const h=await window.api.memory.read(d,o);if(!m){if(h.error){A.error(`Memory read failed: ${h.error}`);return}D(N=>({...N,[d]:h.content})),w(N=>({...N,[d]:h.content}))}})(),()=>{m=!0}},[d,o]),!t)return e.jsx(W,{title:"loading…"});const P=f.reduce((m,h)=>{var N;return m+((N=j[h.name])!=null&&N.stale?1:0)},0),F=ce(f,C,c,j),a=f.find(m=>m.name===d)??null,p=d?j[d]:void 0,y=d?M[d]??"":"",O=d?R[d]??"":"",r=d?y!==O:!1,k=async()=>{if(!d)return;E(!0);const m=await window.api.memory.write(d,y,o);if(E(!1),!m.ok){A.error(`Memory save failed: ${m.error??"unknown error"}`);return}w(h=>({...h,[d]:y})),S()},B=async()=>{if(!d||!window.confirm(`Delete memory "${d}"? This cannot be undone.`))return;E(!0);const m=await window.api.memory.delete(d,o);if(E(!1),!m.ok){A.error(`Memory delete failed: ${m.error??"unknown error"}`);return}D(h=>{const N={...h};return delete N[d],N}),w(h=>{const N={...h};return delete N[d],N}),b(null),S()};return e.jsx(q,{toolbar:e.jsxs(e.Fragment,{children:[e.jsx(se,{options:[{key:"classic",label:"Editor"},{key:"natural",label:"Natural"},{key:"clusters",label:"Clusters"}],active:x,onChange:s}),e.jsx("span",{className:"mx-2 text-fg-faint",children:"·"}),e.jsx("span",{className:"text-fg-faint",children:"Workspace"}),e.jsx("span",{className:"font-mono text-fg-dim truncate max-w-[24rem]",title:o,children:o}),e.jsx("span",{className:"mx-2 text-fg-faint",children:"·"}),e.jsxs("span",{className:"text-fg-faint",children:[f.length," ",f.length===1?"entry":"entries"]}),e.jsx("div",{className:"flex-1"}),x==="classic"&&P>0&&e.jsxs("button",{onClick:()=>$(m=>!m),className:`px-2 py-0.5 text-xs border rounded transition-colors ${c?"text-amber-400 border-amber-400/50 bg-amber-400/10":"text-fg-dim hover:text-fg border-line hover:border-fg-faint"}`,children:["Stale only (",P,")"]}),x==="classic"&&e.jsx("button",{onClick:()=>v(!0),className:"px-2 py-0.5 text-xs text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors",children:"+ New memory"}),x!=="clusters"&&e.jsx("button",{onClick:S,className:"ml-2 px-2 py-0.5 text-xs text-fg-faint hover:text-fg-dim border border-line hover:border-fg-faint rounded transition-colors",children:"Refresh"})]}),footer:x==="classic"&&a?e.jsxs("div",{className:"px-3 py-1.5 flex items-center gap-3 text-xs",children:[e.jsx("button",{onClick:k,disabled:!r||_,className:"px-2 py-0.5 text-xs text-fg-dim hover:text-fg border border-line hover:border-fg-faint rounded transition-colors disabled:opacity-40 disabled:cursor-not-allowed",children:_?"Saving…":"Save"}),e.jsx("button",{onClick:B,disabled:_,className:"px-2 py-0.5 text-xs text-red-400 hover:text-red-300 border border-line hover:border-red-400/50 rounded transition-colors disabled:opacity-40",children:"Delete"}),(p==null?void 0:p.stale)&&e.jsxs("span",{className:"text-amber-400/90 truncate",title:p.reasons.join("; "),children:["stale · ",p.reasons.join(", ")]}),e.jsx("span",{className:"text-fg-faint truncate flex-1",children:a.path}),r&&e.jsx("span",{className:"text-accent",children:"unsaved"})]}):null,children:x==="natural"?e.jsx(xe,{workspace:o,entries:f,onChanged:S}):x==="clusters"?e.jsx(ge,{workspace:o,cwd:n,onOpenMember:m=>{b(`${m}.md`),s("classic")}}):e.jsx(ee,{sidebar:e.jsxs("div",{className:"py-1",children:[e.jsx("div",{className:"px-2 pb-1",children:e.jsx("input",{value:C,onChange:m=>g(m.target.value),placeholder:"filter…",className:"w-full bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg"})}),F.length===0?e.jsx("div",{className:"px-3 py-2 text-xs text-fg-faint italic",children:f.length===0?"no memories yet":"no matches"}):F.map(m=>{const h=j[m.name],N=h!=null&&h.stale?`${m.path}
|
|
8
|
-
stale: ${h.reasons.join(", ")}`:m.path;return e.jsxs("button",{onClick:()=>b(m.name),className:`w-full text-left px-3 py-1 text-xs flex items-center justify-between ${d===m.name?"bg-bg-hi text-fg":"text-fg-dim hover:text-fg hover:bg-bg-hi"}`,title:N,children:[e.jsxs("span",{className:"truncate flex items-center",children:[e.jsx("span",{className:"truncate",children:m.name.replace(/\.md$/,"")}),(h==null?void 0:h.stale)&&e.jsx("span",{className:"ml-1.5 shrink-0 px-1 rounded text-[9px] uppercase tracking-wide text-amber-400/90 border border-amber-400/30",children:"stale"})]}),e.jsxs("span",{className:"ml-2 text-fg-faint shrink-0 text-[10px]",children:[H(m.bytes)," · ",U(m.mtimeMs)]})]},m.name)})]}),detail:T?e.jsx(we,{workspace:o,onCancel:()=>v(!1),onCreated:m=>{v(!1),b(m),S()}}):a&&d?e.jsxs("div",{className:"h-full flex flex-col",children:[e.jsxs("div",{className:"shrink-0 px-3 py-2 border-b border-line text-xs",children:[e.jsx("div",{className:"text-fg truncate",children:a.name}),e.jsxs("div",{className:"text-fg-faint truncate",children:[H(a.bytes)," · modified ",U(a.mtimeMs)]})]}),e.jsx("div",{className:"flex-1 min-h-0",children:e.jsx(te,{path:a.path,value:y,onChange:m=>D(h=>({...h,[d]:m}))})})]}):e.jsx(W,{title:f.length===0?"no memories yet":"select a memory",hint:f.length===0?"Claude writes to memories during sessions when you ask it to remember things, or you can create one manually with the + New memory button.":void 0})})})}function we({workspace:t,onCancel:l,onCreated:n}){const[o,x]=i.useState(""),[s,f]=i.useState(""),[u,d]=i.useState(!1),[b,C]=i.useState(null),g=o.trim().toLowerCase(),j=re.test(g),L=async()=>{if(!j){C("Slug must match [a-z0-9-_]+");return}d(!0),C(null);const c=`${g}.md`,$=await window.api.memory.create(c,s.trim()||void 0,t);if(d(!1),!$.ok){C($.error??"create failed");return}n(c)};return e.jsxs("div",{className:"p-4 max-w-xl space-y-3",children:[e.jsx("div",{className:"text-sm text-fg",children:"New memory"}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs text-fg-faint",children:"Slug"}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{autoFocus:!0,value:o,onChange:c=>x(c.target.value),placeholder:"my-notes",className:"flex-1 bg-bg border border-line rounded px-2 py-1 text-xs text-fg font-mono"}),e.jsx("span",{className:"text-xs text-fg-faint",children:".md"})]}),e.jsxs("div",{className:"text-[10px] text-fg-faint",children:["Lowercase letters, digits, dashes, underscores. The file is created at",e.jsxs("code",{className:"ml-1 font-mono text-fg-dim",children:["~/.claude/projects/",t,"/memory/",g||"…",".md"]})]})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs text-fg-faint",children:"Description (optional)"}),e.jsx("input",{value:s,onChange:c=>f(c.target.value),placeholder:"one-line summary",className:"w-full bg-bg border border-line rounded px-2 py-1 text-xs text-fg"})]}),b&&e.jsx("div",{className:"text-xs text-red-400",children:b}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("button",{onClick:L,disabled:!j||u,className:"px-3 py-1 text-xs text-fg border border-line hover:bg-bg-hi rounded transition-colors disabled:opacity-40",children:u?"Creating…":"Create"}),e.jsx("button",{onClick:l,disabled:u,className:"px-3 py-1 text-xs text-fg-faint hover:text-fg-dim rounded transition-colors",children:"Cancel"})]})]})}export{Me as Memory};
|