claude-code-session-manager 0.10.5 → 0.11.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/{TiptapBody-BroECZ_z.js → TiptapBody-CAJSNRPs.js} +1 -1
- package/dist/assets/{cssMode-Crq-Rykh.js → cssMode-o7rZCrm4.js} +1 -1
- package/dist/assets/{freemarker2-B6CC21Ql.js → freemarker2-CgmCS5Wh.js} +1 -1
- package/dist/assets/{handlebars-BLgR-12n.js → handlebars-BcPLqhPv.js} +1 -1
- package/dist/assets/{html-CiQkt_KY.js → html-CC9xWnC3.js} +1 -1
- package/dist/assets/{htmlMode-Cy8mc91p.js → htmlMode-DEgCqH7k.js} +1 -1
- package/dist/assets/{index-DU-o-LEm.js → index-C7ljEoqc.js} +1161 -1130
- package/dist/assets/{index-DW-tvyin.css → index-CH3K1pkS.css} +1 -1
- package/dist/assets/{javascript-CHNCN8qj.js → javascript-CjwqkQrn.js} +1 -1
- package/dist/assets/{jsonMode-BSN7mvBT.js → jsonMode-BYTLu76d.js} +1 -1
- package/dist/assets/{liquid-B0kmZauA.js → liquid-wbQUuJwT.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-DI_RToRa.js → lspLanguageFeatures-BJGMI7Xu.js} +1 -1
- package/dist/assets/{mdx-BSF-fsyJ.js → mdx-DcDstgPF.js} +1 -1
- package/dist/assets/{python-DUl3Fmgk.js → python-B96yyM_5.js} +1 -1
- package/dist/assets/{razor-Df7WxBjo.js → razor-C7aRIxIE.js} +1 -1
- package/dist/assets/{tsMode-qccVs0_G.js → tsMode-B3UYlGaL.js} +1 -1
- package/dist/assets/{typescript-BEwM5qbq.js → typescript-CV587TvC.js} +1 -1
- package/dist/assets/{xml-CCtx-_Kw.js → xml-PWUJecBf.js} +1 -1
- package/dist/assets/{yaml-B66nOkCW.js → yaml-D8bBNHE4.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/main/agentMemory.cjs +267 -0
- package/src/main/files.cjs +346 -0
- package/src/main/git.cjs +333 -0
- package/src/main/historyAggregator.cjs +70 -0
- package/src/main/index.cjs +12 -0
- package/src/main/ipcSchemas.cjs +62 -0
- package/src/main/projectSkills.cjs +124 -0
- package/src/main/superagent.cjs +202 -0
- package/src/preload/api.d.ts +203 -0
- package/src/preload/index.cjs +47 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{l as e}from"./index-
|
|
1
|
+
import{l as e}from"./index-C7ljEoqc.js";const n={wordPattern:/(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,comments:{lineComment:"//",blockComment:["/*","*/"]},brackets:[["{","}"],["[","]"],["(",")"]],onEnterRules:[{beforeText:/^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,afterText:/^\s*\*\/$/,action:{indentAction:e.IndentAction.IndentOutdent,appendText:" * "}},{beforeText:/^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,action:{indentAction:e.IndentAction.None,appendText:" * "}},{beforeText:/^(\t|(\ \ ))*\ \*(\ ([^\*]|\*(?!\/))*)?$/,action:{indentAction:e.IndentAction.None,appendText:"* "}},{beforeText:/^(\t|(\ \ ))*\ \*\/\s*$/,action:{indentAction:e.IndentAction.None,removeText:1}}],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"',notIn:["string"]},{open:"'",close:"'",notIn:["string","comment"]},{open:"`",close:"`",notIn:["string","comment"]},{open:"/**",close:" */",notIn:["string"]}],folding:{markers:{start:new RegExp("^\\s*//\\s*#?region\\b"),end:new RegExp("^\\s*//\\s*#?endregion\\b")}}},o={defaultToken:"invalid",tokenPostfix:".ts",keywords:["abstract","any","as","asserts","bigint","boolean","break","case","catch","class","continue","const","constructor","debugger","declare","default","delete","do","else","enum","export","extends","false","finally","for","from","function","get","if","implements","import","in","infer","instanceof","interface","is","keyof","let","module","namespace","never","new","null","number","object","out","package","private","protected","public","override","readonly","require","global","return","satisfies","set","static","string","super","switch","symbol","this","throw","true","try","type","typeof","undefined","unique","unknown","var","void","while","with","yield","async","await","of"],operators:["<=",">=","==","!=","===","!==","=>","+","-","**","*","/","%","++","--","<<","</",">>",">>>","&","|","^","!","~","&&","||","??","?",":","=","+=","-=","*=","**=","/=","%=","<<=",">>=",">>>=","&=","|=","^=","@"],symbols:/[=><!~?:&|+\-*\/\^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,digits:/\d+(_+\d+)*/,octaldigits:/[0-7]+(_+[0-7]+)*/,binarydigits:/[0-1]+(_+[0-1]+)*/,hexdigits:/[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/,regexpctl:/[(){}\[\]\$\^|\-*+?\.]/,regexpesc:/\\(?:[bBdDfnrstvwWn0\\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})/,tokenizer:{root:[[/[{}]/,"delimiter.bracket"],{include:"common"}],common:[[/#?[a-z_$][\w$]*/,{cases:{"@keywords":"keyword","@default":"identifier"}}],[/[A-Z][\w\$]*/,"type.identifier"],{include:"@whitespace"},[/\/(?=([^\\\/]|\\.)+\/([dgimsuy]*)(\s*)(\.|;|,|\)|\]|\}|$))/,{token:"regexp",bracket:"@open",next:"@regexp"}],[/[()\[\]]/,"@brackets"],[/[<>](?!@symbols)/,"@brackets"],[/!(?=([^=]|$))/,"delimiter"],[/@symbols/,{cases:{"@operators":"delimiter","@default":""}}],[/(@digits)[eE]([\-+]?(@digits))?/,"number.float"],[/(@digits)\.(@digits)([eE][\-+]?(@digits))?/,"number.float"],[/0[xX](@hexdigits)n?/,"number.hex"],[/0[oO]?(@octaldigits)n?/,"number.octal"],[/0[bB](@binarydigits)n?/,"number.binary"],[/(@digits)n?/,"number"],[/[;,.]/,"delimiter"],[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/"/,"string","@string_double"],[/'/,"string","@string_single"],[/`/,"string","@string_backtick"]],whitespace:[[/[ \t\r\n]+/,""],[/\/\*\*(?!\/)/,"comment.doc","@jsdoc"],[/\/\*/,"comment","@comment"],[/\/\/.*$/,"comment"]],comment:[[/[^\/*]+/,"comment"],[/\*\//,"comment","@pop"],[/[\/*]/,"comment"]],jsdoc:[[/[^\/*]+/,"comment.doc"],[/\*\//,"comment.doc","@pop"],[/[\/*]/,"comment.doc"]],regexp:[[/(\{)(\d+(?:,\d*)?)(\})/,["regexp.escape.control","regexp.escape.control","regexp.escape.control"]],[/(\[)(\^?)(?=(?:[^\]\\\/]|\\.)+)/,["regexp.escape.control",{token:"regexp.escape.control",next:"@regexrange"}]],[/(\()(\?:|\?=|\?!)/,["regexp.escape.control","regexp.escape.control"]],[/[()]/,"regexp.escape.control"],[/@regexpctl/,"regexp.escape.control"],[/[^\\\/]/,"regexp"],[/@regexpesc/,"regexp.escape"],[/\\\./,"regexp.invalid"],[/(\/)([dgimsuy]*)/,[{token:"regexp",bracket:"@close",next:"@pop"},"keyword.other"]]],regexrange:[[/-/,"regexp.escape.control"],[/\^/,"regexp.invalid"],[/@regexpesc/,"regexp.escape"],[/[^\]]/,"regexp"],[/\]/,{token:"regexp.escape.control",next:"@pop",bracket:"@close"}]],string_double:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@pop"]],string_single:[[/[^\\']+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/'/,"string","@pop"]],string_backtick:[[/\$\{/,{token:"delimiter.bracket",next:"@bracketCounting"}],[/[^\\`$]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/`/,"string","@pop"]],bracketCounting:[[/\{/,"delimiter.bracket","@bracketCounting"],[/\}/,"delimiter.bracket","@pop"],{include:"common"}]}};export{n as conf,o as language};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{l as e}from"./index-
|
|
1
|
+
import{l as e}from"./index-C7ljEoqc.js";const n={comments:{blockComment:["<!--","-->"]},brackets:[["<",">"]],autoClosingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],surroundingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],onEnterRules:[{beforeText:new RegExp("<([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$","i"),afterText:/^<\/([_:\w][_:\w-.\d]*)\s*>$/i,action:{indentAction:e.IndentAction.IndentOutdent}},{beforeText:new RegExp("<(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$","i"),action:{indentAction:e.IndentAction.Indent}}]},o={defaultToken:"",tokenPostfix:".xml",ignoreCase:!0,qualifiedName:/(?:[\w\.\-]+:)?[\w\.\-]+/,tokenizer:{root:[[/[^<&]+/,""],{include:"@whitespace"},[/(<)(@qualifiedName)/,[{token:"delimiter"},{token:"tag",next:"@tag"}]],[/(<\/)(@qualifiedName)(\s*)(>)/,[{token:"delimiter"},{token:"tag"},"",{token:"delimiter"}]],[/(<\?)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/(<\!)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/<\!\[CDATA\[/,{token:"delimiter.cdata",next:"@cdata"}],[/&\w+;/,"string.escape"]],cdata:[[/[^\]]+/,""],[/\]\]>/,{token:"delimiter.cdata",next:"@pop"}],[/\]/,""]],tag:[[/[ \t\r\n]+/,""],[/(@qualifiedName)(\s*=\s*)("[^"]*"|'[^']*')/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">?\/]*|'[^'>?\/]*)(?=[\?\/]\>)/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">]*|'[^'>]*)/,["attribute.name","","attribute.value"]],[/@qualifiedName/,"attribute.name"],[/\?>/,{token:"delimiter",next:"@pop"}],[/(\/)(>)/,[{token:"tag"},{token:"delimiter",next:"@pop"}]],[/>/,{token:"delimiter",next:"@pop"}]],whitespace:[[/[ \t\r\n]+/,""],[/<!--/,{token:"comment",next:"@comment"}]],comment:[[/[^<\-]+/,"comment.content"],[/-->/,{token:"comment",next:"@pop"}],[/<!--/,"comment.content.invalid"],[/[<\-]/,"comment.content"]]}};export{n as conf,o as language};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{l as e}from"./index-
|
|
1
|
+
import{l as e}from"./index-C7ljEoqc.js";const t={comments:{lineComment:"#"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],folding:{offSide:!0},onEnterRules:[{beforeText:/:\s*$/,action:{indentAction:e.IndentAction.Indent}}]},o={tokenPostfix:".yaml",brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.square",open:"[",close:"]"}],keywords:["true","True","TRUE","false","False","FALSE","null","Null","Null","~"],numberInteger:/(?:0|[+-]?[0-9]+)/,numberFloat:/(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/,numberOctal:/0o[0-7]+/,numberHex:/0x[0-9a-fA-F]+/,numberInfinity:/[+-]?\.(?:inf|Inf|INF)/,numberNaN:/\.(?:nan|Nan|NAN)/,numberDate:/\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/,escapes:/\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,tokenizer:{root:[{include:"@whitespace"},{include:"@comment"},[/%[^ ]+.*$/,"meta.directive"],[/---/,"operators.directivesEnd"],[/\.{3}/,"operators.documentEnd"],[/[-?:](?= )/,"operators"],{include:"@anchor"},{include:"@tagHandle"},{include:"@flowCollections"},{include:"@blockStyle"},[/@numberInteger(?![ \t]*\S+)/,"number"],[/@numberFloat(?![ \t]*\S+)/,"number.float"],[/@numberOctal(?![ \t]*\S+)/,"number.octal"],[/@numberHex(?![ \t]*\S+)/,"number.hex"],[/@numberInfinity(?![ \t]*\S+)/,"number.infinity"],[/@numberNaN(?![ \t]*\S+)/,"number.nan"],[/@numberDate(?![ \t]*\S+)/,"number.date"],[/(".*?"|'.*?'|[^#'"]*?)([ \t]*)(:)( |$)/,["type","white","operators","white"]],{include:"@flowScalars"},[/.+?(?=(\s+#|$))/,{cases:{"@keywords":"keyword","@default":"string"}}]],object:[{include:"@whitespace"},{include:"@comment"},[/\}/,"@brackets","@pop"],[/,/,"delimiter.comma"],[/:(?= )/,"operators"],[/(?:".*?"|'.*?'|[^,\{\[]+?)(?=: )/,"type"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\},]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],array:[{include:"@whitespace"},{include:"@comment"},[/\]/,"@brackets","@pop"],[/,/,"delimiter.comma"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\],]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],multiString:[[/^( +).+$/,"string","@multiStringContinued.$1"]],multiStringContinued:[[/^( *).+$/,{cases:{"$1==$S2":"string","@default":{token:"@rematch",next:"@popall"}}}]],whitespace:[[/[ \t\r\n]+/,"white"]],comment:[[/#.*$/,"comment"]],flowCollections:[[/\[/,"@brackets","@array"],[/\{/,"@brackets","@object"]],flowScalars:[[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/'[^']*'/,"string"],[/"/,"string","@doubleQuotedString"]],doubleQuotedString:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@pop"]],blockStyle:[[/[>|][0-9]*[+-]?$/,"operators","@multiString"]],flowNumber:[[/@numberInteger(?=[ \t]*[,\]\}])/,"number"],[/@numberFloat(?=[ \t]*[,\]\}])/,"number.float"],[/@numberOctal(?=[ \t]*[,\]\}])/,"number.octal"],[/@numberHex(?=[ \t]*[,\]\}])/,"number.hex"],[/@numberInfinity(?=[ \t]*[,\]\}])/,"number.infinity"],[/@numberNaN(?=[ \t]*[,\]\}])/,"number.nan"],[/@numberDate(?=[ \t]*[,\]\}])/,"number.date"]],tagHandle:[[/\![^ ]*/,"tag"]],anchor:[[/[&*][^ ]+/,"namespace"]]}};export{t as conf,o as language};
|
package/dist/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Claude Session Manager</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-C7ljEoqc.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CH3K1pkS.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body class="bg-bg text-fg font-mono antialiased">
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-session-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Local cockpit for the Claude Code CLI — multi-tab terminal, full config surface, scheduler, voice dictation, and live observability.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main/index.cjs",
|
|
@@ -0,0 +1,267 @@
|
|
|
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
|
+
async function listAgents() {
|
|
232
|
+
try {
|
|
233
|
+
await ensureRoot();
|
|
234
|
+
const dir = rootDir();
|
|
235
|
+
const r = await config.listDir(dir, { filesOnly: true });
|
|
236
|
+
if (!r.ok) return { agents: [], error: r.error };
|
|
237
|
+
const agents = r.entries
|
|
238
|
+
.filter((e) => e.name.endsWith('.json'))
|
|
239
|
+
.map((e) => ({
|
|
240
|
+
agentId: e.name.replace(/\.json$/, ''),
|
|
241
|
+
bytes: e.size,
|
|
242
|
+
mtimeMs: e.mtimeMs,
|
|
243
|
+
}))
|
|
244
|
+
.sort((a, b) => a.agentId.localeCompare(b.agentId));
|
|
245
|
+
return { agents, error: null };
|
|
246
|
+
} catch (e) {
|
|
247
|
+
return { agents: [], error: e.message };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function registerAgentMemoryHandlers() {
|
|
252
|
+
const { schemas: s, validated: v } = require('./ipcSchemas.cjs');
|
|
253
|
+
ipcMain.handle('agent-memory:list', v(s.agentMemoryList, list));
|
|
254
|
+
ipcMain.handle('agent-memory:get', v(s.agentMemoryGet, get));
|
|
255
|
+
ipcMain.handle('agent-memory:set', v(s.agentMemorySet, set));
|
|
256
|
+
ipcMain.handle('agent-memory:delete', v(s.agentMemoryDelete, deleteEntry));
|
|
257
|
+
ipcMain.handle('agent-memory:list-agents', () => listAgents());
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = {
|
|
261
|
+
registerAgentMemoryHandlers,
|
|
262
|
+
// exported for tests
|
|
263
|
+
rootDir,
|
|
264
|
+
recordPath,
|
|
265
|
+
AGENT_ID_RE,
|
|
266
|
+
ENTRY_ID_RE,
|
|
267
|
+
};
|