claude-code-session-manager 0.113.0 → 0.114.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-DZFM7Lto.js} +1 -1
- package/dist/assets/{Button-B6h0F0XT.js → Button-BJVFKdGH.js} +1 -1
- package/dist/assets/{DataModel-C5pW9E3u.js → DataModel-CebvcP_y.js} +1 -1
- package/dist/assets/{History-BV4e8YOM.js → History-BwISEUhR.js} +1 -1
- package/dist/assets/{Hooks-CrFSv4x1.js → Hooks-Dac3smYP.js} +1 -1
- package/dist/assets/{Library-D-GIt3EY.js → Library-DTjsUr9W.js} +1 -1
- package/dist/assets/{ListDetail-Bco_wfs6.js → ListDetail-B1L5TbjS.js} +1 -1
- package/dist/assets/{MarkdownEditor-C-yHSXNV.js → MarkdownEditor-BFRhWZD4.js} +1 -1
- package/dist/assets/{McpServers-CfDekadG.js → McpServers-CBjGCMg0.js} +1 -1
- package/dist/assets/Memory-DdLi5fqe.js +8 -0
- package/dist/assets/{Panel-DGMEuYYG.js → Panel-BQkkkjex.js} +1 -1
- package/dist/assets/{Permissions-Cx2zjtYZ.js → Permissions-CaACwY0B.js} +1 -1
- package/dist/assets/{Plugins-Bc_W3IyR.js → Plugins-DJeBwVoh.js} +2 -2
- package/dist/assets/{ProvenanceBadge-DiKAUR1u.js → ProvenanceBadge--N5j8xiK.js} +1 -1
- package/dist/assets/{RemoteMachines-8_77-WPD.js → RemoteMachines-WjWaDHEA.js} +1 -1
- package/dist/assets/{SaveBar-Blpzdq1R.js → SaveBar-DFmOjb4_.js} +1 -1
- package/dist/assets/{Scheduler-Bd70AM4X.js → Scheduler-Miap6_aY.js} +1 -1
- package/dist/assets/{ScopeSwitcher-wxN0h4YW.js → ScopeSwitcher-CiEujp4L.js} +1 -1
- package/dist/assets/{Settings-CQ7DA3zW.js → Settings-UGpW4P0j.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-DH1NLOiS.js → SkillReferenceGraph-BgyAnxGW.js} +1 -1
- package/dist/assets/{Skills-CmyRyu4_.js → Skills-BQUpnNfx.js} +1 -1
- package/dist/assets/{SystemPrompt-KAc3UMCd.js → SystemPrompt-CQjOgHGT.js} +1 -1
- package/dist/assets/{TagLibrary-C8EvHnpg.js → TagLibrary-CTVpoLl0.js} +1 -1
- package/dist/assets/{TiptapBody-d423mH6D.js → TiptapBody-BtOcrdRR.js} +1 -1
- package/dist/assets/{Toggle-D1wsGE9S.js → Toggle-Chri4G19.js} +1 -1
- package/dist/assets/{ViewTabs-CjHw3kGY.js → ViewTabs-BIenDGVv.js} +1 -1
- package/dist/assets/{historyTotals-DFHzqLt7.js → historyTotals-CX8mUxJY.js} +1 -1
- package/dist/assets/{index-Ci1u018F.js → index-AVrhGmUO.js} +86 -86
- package/dist/assets/{index-Dutcsly-.css → index-HpEbvDiE.css} +1 -1
- package/dist/assets/{settingsSchema-Ctf3pZVI.js → settingsSchema-uqwlznbH.js} +1 -1
- package/dist/assets/{useAgentsDirWatch-B1NBmOAe.js → useAgentsDirWatch-BaezRtWw.js} +1 -1
- package/dist/assets/{useRemotePeers-BHjSJs9D.js → useRemotePeers-BuFbBqDt.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/index.cjs +0 -2
- package/src/main/ipcSchemas.cjs +24 -34
- package/src/main/lib/memoryDirResolve.cjs +139 -0
- package/src/main/lib/memoryIndex.cjs +98 -0
- package/src/main/memoryAggregate.cjs +15 -8
- package/src/main/memoryTool.cjs +151 -22
- package/src/preload/api.d.ts +41 -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/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(). */
|
|
@@ -2094,19 +2096,21 @@ export interface SessionManagerAPI {
|
|
|
2094
2096
|
};
|
|
2095
2097
|
memory: {
|
|
2096
2098
|
/** List markdown memory entries for the given workspace (defaults to 'default'). */
|
|
2097
|
-
list: (
|
|
2099
|
+
list: (target?: MemoryTarget) => Promise<MemoryListResult>;
|
|
2098
2100
|
/** Read the contents of one memory entry. */
|
|
2099
|
-
read: (name: string,
|
|
2101
|
+
read: (name: string, target?: MemoryTarget) => Promise<MemoryReadResult>;
|
|
2100
2102
|
/** Atomic write of a memory entry (max 1 MiB). */
|
|
2101
|
-
write: (name: string, content: string,
|
|
2103
|
+
write: (name: string, content: string, target?: MemoryTarget) => Promise<MemoryMutationResult>;
|
|
2102
2104
|
/** Delete a memory entry. */
|
|
2103
|
-
delete: (name: string,
|
|
2105
|
+
delete: (name: string, target?: MemoryTarget) => Promise<MemoryMutationResult>;
|
|
2104
2106
|
/** Create a new memory entry with starter frontmatter + body. */
|
|
2105
|
-
create: (name: string, description?: string,
|
|
2107
|
+
create: (name: string, description?: string, target?: MemoryTarget) => Promise<MemoryMutationResult>;
|
|
2106
2108
|
/** Aggregate workspace memories into semantic clusters. `refresh:true` fires a cost-gated `claude -p` pass; otherwise returns the cache only. */
|
|
2107
2109
|
aggregate: (workspace: string, refresh?: boolean, cwd?: string) => Promise<MemoryAggregateResult>;
|
|
2108
2110
|
/** Deterministic, zero-LLM-cost staleness report. `cwd` (optional) scopes the dead-repo-ref check. */
|
|
2109
|
-
stale: (
|
|
2111
|
+
stale: (target?: MemoryTarget) => Promise<MemoryStaleResult>;
|
|
2112
|
+
/** Agents (user + project) with their `memory:` frontmatter scope. */
|
|
2113
|
+
agents: (cwd?: string) => Promise<{ agents: MemoryAgent[] }>;
|
|
2110
2114
|
};
|
|
2111
2115
|
projectBrief: {
|
|
2112
2116
|
/** Read brief.json (or null if none yet) plus cheaply-computed source/drift metadata. Never fires an LLM call. */
|
|
@@ -2154,21 +2158,6 @@ export interface SessionManagerAPI {
|
|
|
2154
2158
|
fields: { cwd: string; epicId: string; source: string },
|
|
2155
2159
|
) => Promise<{ ok: boolean }>;
|
|
2156
2160
|
};
|
|
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
2161
|
git: {
|
|
2173
2162
|
/** Full git status for `cwd`. Returns null when not a git repo, git is
|
|
2174
2163
|
* 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};
|
package/src/main/agentMemory.cjs
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* agentMemory.cjs — per-subagent memory backend.
|
|
3
|
-
*
|
|
4
|
-
* Distinct from the workspace-scoped Memory tool (memoryTool.cjs): this stores
|
|
5
|
-
* memory entries keyed by **agentId** (the subagent name, e.g. "code-reviewer"),
|
|
6
|
-
* not by workspace cwd. Conceptually mirrors Anthropic's "give each subagent its
|
|
7
|
-
* own scratchpad" pattern.
|
|
8
|
-
*
|
|
9
|
-
* Storage: `~/.claude/session-manager/agent-memory/<agentId>.json`
|
|
10
|
-
*
|
|
11
|
-
* {
|
|
12
|
-
* "agentId": "code-reviewer",
|
|
13
|
-
* "entries": [
|
|
14
|
-
* { "id": "mem_<ts>_<rand>", "body": "<markdown>",
|
|
15
|
-
* "category": "command|preference|pattern|failure|workflow" (optional),
|
|
16
|
-
* "createdAt": <epoch ms>, "updatedAt": <epoch ms> }
|
|
17
|
-
* ],
|
|
18
|
-
* "lastUpdated": <epoch ms>
|
|
19
|
-
* }
|
|
20
|
-
*
|
|
21
|
-
* Deviation from Unleashed (which keys by projectPath hash and writes to
|
|
22
|
-
* `~/.claudecodeui/agent-memory/<hash>.json` with raw fs.writeFile): we key by
|
|
23
|
-
* agentId per the user's spec, write under `~/.claude/session-manager/` (the
|
|
24
|
-
* conventional spot for this app's own state), and route every mutation through
|
|
25
|
-
* config.cjs's writeJson / validatePath helpers.
|
|
26
|
-
*
|
|
27
|
-
* Hard caps:
|
|
28
|
-
* - agentId must match /^[A-Za-z0-9._-]{1,128}$/
|
|
29
|
-
* - entryId must match /^[A-Za-z0-9._-]{1,128}$/
|
|
30
|
-
* - body up to 1 MiB
|
|
31
|
-
* - up to 200 entries per agent (oldest dropped when over cap on set())
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
'use strict';
|
|
35
|
-
|
|
36
|
-
const { ipcMain } = require('electron');
|
|
37
|
-
const fs = require('node:fs');
|
|
38
|
-
const fsp = require('node:fs/promises');
|
|
39
|
-
const path = require('node:path');
|
|
40
|
-
const os = require('node:os');
|
|
41
|
-
const config = require('./config.cjs');
|
|
42
|
-
|
|
43
|
-
const MAX_BODY_BYTES = 1024 * 1024; // 1 MiB
|
|
44
|
-
const MAX_ENTRIES = 200;
|
|
45
|
-
const AGENT_ID_RE = /^[A-Za-z0-9._-]{1,128}$/;
|
|
46
|
-
const ENTRY_ID_RE = /^[A-Za-z0-9._-]{1,128}$/;
|
|
47
|
-
|
|
48
|
-
function rootDir() {
|
|
49
|
-
return path.join(os.homedir(), '.claude', 'session-manager', 'agent-memory');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function recordPath(agentId) {
|
|
53
|
-
if (!AGENT_ID_RE.test(agentId)) {
|
|
54
|
-
throw new Error(`invalid agentId (must match ${AGENT_ID_RE.source})`);
|
|
55
|
-
}
|
|
56
|
-
return path.join(rootDir(), `${agentId}.json`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function ensureRoot() {
|
|
60
|
-
await fsp.mkdir(rootDir(), { recursive: true });
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function emptyRecord(agentId) {
|
|
64
|
-
return { agentId, entries: [], lastUpdated: 0 };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function loadRecord(agentId) {
|
|
68
|
-
const abs = recordPath(agentId);
|
|
69
|
-
const r = await config.readJson(abs).catch(() => ({ exists: false, data: null }));
|
|
70
|
-
if (!r || !r.exists || !r.data) return emptyRecord(agentId);
|
|
71
|
-
const data = r.data;
|
|
72
|
-
if (!data || typeof data !== 'object' || !Array.isArray(data.entries)) {
|
|
73
|
-
return emptyRecord(agentId);
|
|
74
|
-
}
|
|
75
|
-
return {
|
|
76
|
-
agentId,
|
|
77
|
-
entries: data.entries.filter(
|
|
78
|
-
(e) => e && typeof e === 'object' && typeof e.id === 'string' && typeof e.body === 'string'
|
|
79
|
-
),
|
|
80
|
-
lastUpdated: typeof data.lastUpdated === 'number' ? data.lastUpdated : 0,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async function persist(record) {
|
|
85
|
-
await ensureRoot();
|
|
86
|
-
// Cap entry count: drop oldest by updatedAt (or createdAt) ascending.
|
|
87
|
-
if (record.entries.length > MAX_ENTRIES) {
|
|
88
|
-
record.entries = record.entries
|
|
89
|
-
.slice()
|
|
90
|
-
.sort((a, b) => (b.updatedAt ?? b.createdAt ?? 0) - (a.updatedAt ?? a.createdAt ?? 0))
|
|
91
|
-
.slice(0, MAX_ENTRIES);
|
|
92
|
-
}
|
|
93
|
-
const abs = recordPath(record.agentId);
|
|
94
|
-
const out = { ...record, lastUpdated: Date.now() };
|
|
95
|
-
await config.writeJson(abs, out);
|
|
96
|
-
return out;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ────────────────────────────────────────────────── handlers
|
|
100
|
-
|
|
101
|
-
async function list({ agentId }) {
|
|
102
|
-
if (!AGENT_ID_RE.test(agentId || '')) {
|
|
103
|
-
return { entries: [], agentId: agentId || '', error: 'invalid agentId' };
|
|
104
|
-
}
|
|
105
|
-
try {
|
|
106
|
-
const record = await loadRecord(agentId);
|
|
107
|
-
const entries = record.entries
|
|
108
|
-
.slice()
|
|
109
|
-
.sort((a, b) => (b.updatedAt ?? b.createdAt ?? 0) - (a.updatedAt ?? a.createdAt ?? 0))
|
|
110
|
-
.map((e) => ({
|
|
111
|
-
id: e.id,
|
|
112
|
-
body: e.body,
|
|
113
|
-
category: e.category ?? null,
|
|
114
|
-
createdAt: e.createdAt ?? 0,
|
|
115
|
-
updatedAt: e.updatedAt ?? e.createdAt ?? 0,
|
|
116
|
-
bytes: Buffer.byteLength(e.body, 'utf8'),
|
|
117
|
-
}));
|
|
118
|
-
return { entries, agentId, error: null };
|
|
119
|
-
} catch (e) {
|
|
120
|
-
return { entries: [], agentId, error: e.message };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function get({ agentId, entryId }) {
|
|
125
|
-
if (!AGENT_ID_RE.test(agentId || '') || !ENTRY_ID_RE.test(entryId || '')) {
|
|
126
|
-
return { entry: null, error: 'invalid id' };
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
const record = await loadRecord(agentId);
|
|
130
|
-
const entry = record.entries.find((e) => e.id === entryId) ?? null;
|
|
131
|
-
if (!entry) return { entry: null, error: null };
|
|
132
|
-
return {
|
|
133
|
-
entry: {
|
|
134
|
-
id: entry.id,
|
|
135
|
-
body: entry.body,
|
|
136
|
-
category: entry.category ?? null,
|
|
137
|
-
createdAt: entry.createdAt ?? 0,
|
|
138
|
-
updatedAt: entry.updatedAt ?? entry.createdAt ?? 0,
|
|
139
|
-
bytes: Buffer.byteLength(entry.body, 'utf8'),
|
|
140
|
-
},
|
|
141
|
-
error: null,
|
|
142
|
-
};
|
|
143
|
-
} catch (e) {
|
|
144
|
-
return { entry: null, error: e.message };
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async function set({ agentId, entryId, body, category }) {
|
|
149
|
-
if (!AGENT_ID_RE.test(agentId || '')) {
|
|
150
|
-
return { ok: false, error: 'invalid agentId' };
|
|
151
|
-
}
|
|
152
|
-
if (!ENTRY_ID_RE.test(entryId || '')) {
|
|
153
|
-
return { ok: false, error: 'invalid entryId' };
|
|
154
|
-
}
|
|
155
|
-
if (typeof body !== 'string') {
|
|
156
|
-
return { ok: false, error: 'body must be a string' };
|
|
157
|
-
}
|
|
158
|
-
const bytes = Buffer.byteLength(body, 'utf8');
|
|
159
|
-
if (bytes > MAX_BODY_BYTES) {
|
|
160
|
-
return { ok: false, error: `body exceeds 1 MiB cap (${bytes} bytes)` };
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
const record = await loadRecord(agentId);
|
|
164
|
-
const now = Date.now();
|
|
165
|
-
const idx = record.entries.findIndex((e) => e.id === entryId);
|
|
166
|
-
if (idx >= 0) {
|
|
167
|
-
// Update — preserve createdAt
|
|
168
|
-
record.entries[idx] = {
|
|
169
|
-
...record.entries[idx],
|
|
170
|
-
id: entryId,
|
|
171
|
-
body,
|
|
172
|
-
category: category ?? record.entries[idx].category ?? null,
|
|
173
|
-
updatedAt: now,
|
|
174
|
-
};
|
|
175
|
-
} else {
|
|
176
|
-
// Enforce cap on create
|
|
177
|
-
if (record.entries.length >= MAX_ENTRIES) {
|
|
178
|
-
return { ok: false, error: `agent at ${MAX_ENTRIES}-entry cap` };
|
|
179
|
-
}
|
|
180
|
-
record.entries.push({
|
|
181
|
-
id: entryId,
|
|
182
|
-
body,
|
|
183
|
-
category: category ?? null,
|
|
184
|
-
createdAt: now,
|
|
185
|
-
updatedAt: now,
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
await persist(record);
|
|
189
|
-
return { ok: true, error: null };
|
|
190
|
-
} catch (e) {
|
|
191
|
-
return { ok: false, error: e.message };
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function deleteEntry({ agentId, entryId }) {
|
|
196
|
-
if (!AGENT_ID_RE.test(agentId || '') || !ENTRY_ID_RE.test(entryId || '')) {
|
|
197
|
-
return { ok: false, error: 'invalid id' };
|
|
198
|
-
}
|
|
199
|
-
try {
|
|
200
|
-
const record = await loadRecord(agentId);
|
|
201
|
-
const before = record.entries.length;
|
|
202
|
-
record.entries = record.entries.filter((e) => e.id !== entryId);
|
|
203
|
-
if (record.entries.length === before) {
|
|
204
|
-
// No-op delete (entry didn't exist) — treat as success, same as fs unlink ENOENT.
|
|
205
|
-
return { ok: true, error: null };
|
|
206
|
-
}
|
|
207
|
-
// If we just emptied the record, remove the file outright so listProjects()
|
|
208
|
-
// doesn't report a stale ghost agent.
|
|
209
|
-
if (record.entries.length === 0) {
|
|
210
|
-
const abs = recordPath(agentId);
|
|
211
|
-
let real;
|
|
212
|
-
try {
|
|
213
|
-
real = config.validatePath(abs);
|
|
214
|
-
config.validateWrite(real);
|
|
215
|
-
await fsp.unlink(real);
|
|
216
|
-
} catch (e) {
|
|
217
|
-
if (e.code !== 'ENOENT') {
|
|
218
|
-
// Fall back to persisting an empty record; better than throwing.
|
|
219
|
-
await persist(record);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return { ok: true, error: null };
|
|
223
|
-
}
|
|
224
|
-
await persist(record);
|
|
225
|
-
return { ok: true, error: null };
|
|
226
|
-
} catch (e) {
|
|
227
|
-
return { ok: false, error: e.message };
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function registerAgentMemoryHandlers() {
|
|
232
|
-
const { schemas: s, validated: v } = require('./ipcSchemas.cjs');
|
|
233
|
-
ipcMain.handle('agent-memory:list', v(s.agentMemoryList, list));
|
|
234
|
-
ipcMain.handle('agent-memory:get', v(s.agentMemoryGet, get));
|
|
235
|
-
ipcMain.handle('agent-memory:set', v(s.agentMemorySet, set));
|
|
236
|
-
ipcMain.handle('agent-memory:delete', v(s.agentMemoryDelete, deleteEntry));
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
module.exports = {
|
|
240
|
-
registerAgentMemoryHandlers,
|
|
241
|
-
// exported for tests
|
|
242
|
-
rootDir,
|
|
243
|
-
recordPath,
|
|
244
|
-
AGENT_ID_RE,
|
|
245
|
-
ENTRY_ID_RE,
|
|
246
|
-
};
|