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.
Files changed (49) hide show
  1. package/dist/assets/{AgentLibrary-wvrcLGGS.js → AgentLibrary-CjsZLl5e.js} +1 -1
  2. package/dist/assets/{Button-B6h0F0XT.js → Button-mj0vvcdc.js} +1 -1
  3. package/dist/assets/{DataModel-C5pW9E3u.js → DataModel-CG6ycpfG.js} +1 -1
  4. package/dist/assets/{History-BV4e8YOM.js → History-DDqLQiAN.js} +1 -1
  5. package/dist/assets/{Hooks-CrFSv4x1.js → Hooks-dRje24Nk.js} +1 -1
  6. package/dist/assets/{Library-D-GIt3EY.js → Library-Dl71nylN.js} +1 -1
  7. package/dist/assets/{ListDetail-Bco_wfs6.js → ListDetail-DcAD_cuA.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-C-yHSXNV.js → MarkdownEditor-D7JTNOOp.js} +1 -1
  9. package/dist/assets/{McpServers-CfDekadG.js → McpServers-BbvbhemH.js} +1 -1
  10. package/dist/assets/Memory-dyz8ZU1e.js +8 -0
  11. package/dist/assets/{Panel-DGMEuYYG.js → Panel-BkfK-W74.js} +1 -1
  12. package/dist/assets/{Permissions-Cx2zjtYZ.js → Permissions-BnjsXblr.js} +1 -1
  13. package/dist/assets/{Plugins-Bc_W3IyR.js → Plugins-DSyTJtTp.js} +2 -2
  14. package/dist/assets/{ProvenanceBadge-DiKAUR1u.js → ProvenanceBadge-CQXSiPHq.js} +1 -1
  15. package/dist/assets/{RemoteMachines-8_77-WPD.js → RemoteMachines-li6EBPAt.js} +1 -1
  16. package/dist/assets/{SaveBar-Blpzdq1R.js → SaveBar-0aiuy1L2.js} +1 -1
  17. package/dist/assets/{Scheduler-Bd70AM4X.js → Scheduler-Ul4jiK7q.js} +1 -1
  18. package/dist/assets/{ScopeSwitcher-wxN0h4YW.js → ScopeSwitcher-DR262W_R.js} +1 -1
  19. package/dist/assets/{Settings-CQ7DA3zW.js → Settings-BwUcGuar.js} +1 -1
  20. package/dist/assets/{SkillReferenceGraph-DH1NLOiS.js → SkillReferenceGraph-fv2FJUNK.js} +1 -1
  21. package/dist/assets/{Skills-CmyRyu4_.js → Skills-BA2I-iRE.js} +1 -1
  22. package/dist/assets/{SystemPrompt-KAc3UMCd.js → SystemPrompt-BgvyrAKx.js} +1 -1
  23. package/dist/assets/{TagLibrary-C8EvHnpg.js → TagLibrary-B5rKEDVT.js} +1 -1
  24. package/dist/assets/{TiptapBody-d423mH6D.js → TiptapBody-CBxxEulT.js} +1 -1
  25. package/dist/assets/{Toggle-D1wsGE9S.js → Toggle-W944m375.js} +1 -1
  26. package/dist/assets/{ViewTabs-CjHw3kGY.js → ViewTabs-BLQrBE2n.js} +1 -1
  27. package/dist/assets/{historyTotals-DFHzqLt7.js → historyTotals-BUeyjlNY.js} +1 -1
  28. package/dist/assets/{index-Dutcsly-.css → index-HpEbvDiE.css} +1 -1
  29. package/dist/assets/{index-Ci1u018F.js → index-t37UuEZu.js} +101 -101
  30. package/dist/assets/{settingsSchema-Ctf3pZVI.js → settingsSchema-HwiOXtC7.js} +1 -1
  31. package/dist/assets/{useAgentsDirWatch-B1NBmOAe.js → useAgentsDirWatch-D0rI9G_h.js} +1 -1
  32. package/dist/assets/{useRemotePeers-BHjSJs9D.js → useRemotePeers-BipAySOB.js} +1 -1
  33. package/dist/index.html +2 -2
  34. package/package.json +1 -1
  35. package/src/main/build-info.json +4 -4
  36. package/src/main/chatRunner.cjs +8 -2
  37. package/src/main/index.cjs +0 -2
  38. package/src/main/ipcSchemas.cjs +50 -35
  39. package/src/main/lib/memoryDirResolve.cjs +139 -0
  40. package/src/main/lib/memoryIndex.cjs +98 -0
  41. package/src/main/lib/remoteDrilldown.cjs +91 -0
  42. package/src/main/lib/remoteTranscriptTail.cjs +90 -0
  43. package/src/main/memoryAggregate.cjs +15 -8
  44. package/src/main/memoryTool.cjs +151 -22
  45. package/src/main/webRemote.cjs +90 -57
  46. package/src/preload/api.d.ts +44 -52
  47. package/src/preload/index.cjs +19 -21
  48. package/dist/assets/Memory-CGGLexkR.js +0 -8
  49. package/src/main/agentMemory.cjs +0 -246
@@ -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 flush the session list immediately rather than waiting for the
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.lastSessionListJson = null;
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
- chatRunner.enqueueExternalPrompt(parsed.tabId, parsed.prompt);
1204
- return { ok: true };
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
  },
@@ -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: (workspace?: string) => Promise<MemoryListResult>;
2102
+ list: (target?: MemoryTarget) => Promise<MemoryListResult>;
2098
2103
  /** Read the contents of one memory entry. */
2099
- read: (name: string, workspace?: string) => Promise<MemoryReadResult>;
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, workspace?: string) => Promise<MemoryMutationResult>;
2106
+ write: (name: string, content: string, target?: MemoryTarget) => Promise<MemoryMutationResult>;
2102
2107
  /** Delete a memory entry. */
2103
- delete: (name: string, workspace?: string) => Promise<MemoryMutationResult>;
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, workspace?: string) => Promise<MemoryMutationResult>;
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: (workspace?: string, cwd?: string) => Promise<MemoryStaleResult>;
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. */
@@ -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: (workspace) => ipcRenderer.invoke('memory:list', workspace ? { workspace } : {}),
339
- read: (name, workspace) => ipcRenderer.invoke('memory:read', workspace ? { name, workspace } : { name }),
340
- write: (name, content, workspace) =>
341
- ipcRenderer.invoke('memory:write', workspace ? { name, content, workspace } : { name, content }),
342
- delete: (name, workspace) =>
343
- ipcRenderer.invoke('memory:delete', workspace ? { name, workspace } : { name }),
344
- create: (name, description, workspace) => {
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: (workspace, cwd) =>
353
- ipcRenderer.invoke('memory:stale', { ...(workspace ? { workspace } : {}), ...(cwd ? { cwd } : {}) }),
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};