ralph-mem 0.1.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/LICENSE +21 -0
- package/README.md +232 -0
- package/dist/chunk-41rc1bhg.js +1116 -0
- package/dist/chunk-c3a91ngd.js +2734 -0
- package/dist/chunk-kga64hvg.js +90 -0
- package/dist/chunk-ns0dgdnb.js +21 -0
- package/dist/chunk-v8anyhk1.js +103 -0
- package/dist/chunk-w40c0y00.js +36 -0
- package/dist/hooks/post-tool-use.js +155 -0
- package/dist/hooks/session-end.js +89 -0
- package/dist/hooks/session-start.js +95 -0
- package/dist/hooks/user-prompt-submit.js +234 -0
- package/dist/index.js +64 -0
- package/dist/skills/mem-forget.js +192 -0
- package/dist/skills/mem-inject.js +130 -0
- package/dist/skills/mem-search.js +303 -0
- package/dist/skills/mem-status.js +200 -0
- package/dist/skills/ralph-config.js +404 -0
- package/dist/skills/ralph.js +654 -0
- package/package.json +64 -0
- package/plugin.json +51 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadConfig
|
|
3
|
+
} from "../chunk-c3a91ngd.js";
|
|
4
|
+
import {
|
|
5
|
+
estimateTokens
|
|
6
|
+
} from "../chunk-v8anyhk1.js";
|
|
7
|
+
import {
|
|
8
|
+
createSearchEngine
|
|
9
|
+
} from "../chunk-kga64hvg.js";
|
|
10
|
+
import {
|
|
11
|
+
createDBClient
|
|
12
|
+
} from "../chunk-41rc1bhg.js";
|
|
13
|
+
import {
|
|
14
|
+
ensureProjectDirs,
|
|
15
|
+
getProjectDBPath
|
|
16
|
+
} from "../chunk-w40c0y00.js";
|
|
17
|
+
import"../chunk-ns0dgdnb.js";
|
|
18
|
+
|
|
19
|
+
// src/hooks/user-prompt-submit.ts
|
|
20
|
+
var STOPWORDS = new Set([
|
|
21
|
+
"a",
|
|
22
|
+
"an",
|
|
23
|
+
"and",
|
|
24
|
+
"are",
|
|
25
|
+
"as",
|
|
26
|
+
"at",
|
|
27
|
+
"be",
|
|
28
|
+
"by",
|
|
29
|
+
"for",
|
|
30
|
+
"from",
|
|
31
|
+
"has",
|
|
32
|
+
"he",
|
|
33
|
+
"in",
|
|
34
|
+
"is",
|
|
35
|
+
"it",
|
|
36
|
+
"its",
|
|
37
|
+
"of",
|
|
38
|
+
"on",
|
|
39
|
+
"or",
|
|
40
|
+
"that",
|
|
41
|
+
"the",
|
|
42
|
+
"to",
|
|
43
|
+
"was",
|
|
44
|
+
"were",
|
|
45
|
+
"will",
|
|
46
|
+
"with",
|
|
47
|
+
"this",
|
|
48
|
+
"they",
|
|
49
|
+
"but",
|
|
50
|
+
"have",
|
|
51
|
+
"not",
|
|
52
|
+
"what",
|
|
53
|
+
"when",
|
|
54
|
+
"where",
|
|
55
|
+
"who",
|
|
56
|
+
"which",
|
|
57
|
+
"why",
|
|
58
|
+
"how",
|
|
59
|
+
"all",
|
|
60
|
+
"each",
|
|
61
|
+
"she",
|
|
62
|
+
"do",
|
|
63
|
+
"if",
|
|
64
|
+
"my",
|
|
65
|
+
"their",
|
|
66
|
+
"then",
|
|
67
|
+
"so",
|
|
68
|
+
"than",
|
|
69
|
+
"too",
|
|
70
|
+
"very",
|
|
71
|
+
"can",
|
|
72
|
+
"just",
|
|
73
|
+
"should",
|
|
74
|
+
"now",
|
|
75
|
+
"i",
|
|
76
|
+
"you",
|
|
77
|
+
"we",
|
|
78
|
+
"me",
|
|
79
|
+
"은",
|
|
80
|
+
"는",
|
|
81
|
+
"이",
|
|
82
|
+
"가",
|
|
83
|
+
"을",
|
|
84
|
+
"를",
|
|
85
|
+
"의",
|
|
86
|
+
"에",
|
|
87
|
+
"에서",
|
|
88
|
+
"으로",
|
|
89
|
+
"로",
|
|
90
|
+
"와",
|
|
91
|
+
"과",
|
|
92
|
+
"도",
|
|
93
|
+
"만",
|
|
94
|
+
"까지",
|
|
95
|
+
"부터",
|
|
96
|
+
"처럼",
|
|
97
|
+
"같이",
|
|
98
|
+
"please",
|
|
99
|
+
"help",
|
|
100
|
+
"show",
|
|
101
|
+
"tell",
|
|
102
|
+
"explain",
|
|
103
|
+
"find",
|
|
104
|
+
"get",
|
|
105
|
+
"make",
|
|
106
|
+
"create",
|
|
107
|
+
"해줘",
|
|
108
|
+
"해주세요",
|
|
109
|
+
"알려줘",
|
|
110
|
+
"보여줘"
|
|
111
|
+
]);
|
|
112
|
+
function extractKeywords(prompt) {
|
|
113
|
+
const tokens = prompt.toLowerCase().split(/[\s,.;:!?()[\]{}'"]+/).filter((t) => t.length >= 2);
|
|
114
|
+
const keywords = tokens.filter((t) => !STOPWORDS.has(t));
|
|
115
|
+
return [...new Set(keywords)].slice(0, 5);
|
|
116
|
+
}
|
|
117
|
+
function formatNotification(results) {
|
|
118
|
+
if (results.length === 0) {
|
|
119
|
+
return "";
|
|
120
|
+
}
|
|
121
|
+
const lines = ["\uD83D\uDD0D 관련 메모리 발견:"];
|
|
122
|
+
for (const result of results.slice(0, 3)) {
|
|
123
|
+
const date = result.createdAt ? result.createdAt.toLocaleDateString("ko-KR", {
|
|
124
|
+
month: "numeric",
|
|
125
|
+
day: "numeric"
|
|
126
|
+
}) : "";
|
|
127
|
+
const score = result.score.toFixed(2);
|
|
128
|
+
const summary = result.summary || "(요약 없음)";
|
|
129
|
+
lines.push(`- ${summary.slice(0, 40)}${summary.length > 40 ? "..." : ""} (${date}, 관련도: ${score})`);
|
|
130
|
+
}
|
|
131
|
+
if (results.length > 3) {
|
|
132
|
+
lines.push(` 외 ${results.length - 3}건...`);
|
|
133
|
+
}
|
|
134
|
+
lines.push("상세 조회: /mem-search --layer 3 <id>");
|
|
135
|
+
return lines.join(`
|
|
136
|
+
`);
|
|
137
|
+
}
|
|
138
|
+
function formatContext(results, maxTokens) {
|
|
139
|
+
if (results.length === 0) {
|
|
140
|
+
return { context: "", tokenCount: 0 };
|
|
141
|
+
}
|
|
142
|
+
const lines = ["\uD83D\uDCDD 관련 기억:"];
|
|
143
|
+
let tokenCount = estimateTokens(lines[0]);
|
|
144
|
+
for (const result of results) {
|
|
145
|
+
const date = result.createdAt ? result.createdAt.toLocaleDateString("ko-KR", {
|
|
146
|
+
month: "numeric",
|
|
147
|
+
day: "numeric"
|
|
148
|
+
}) : "";
|
|
149
|
+
const content = result.content || result.summary || "";
|
|
150
|
+
const preview = content.slice(0, 200) + (content.length > 200 ? "..." : "");
|
|
151
|
+
const line = `- [${date}] ${preview}`;
|
|
152
|
+
const lineTokens = estimateTokens(line);
|
|
153
|
+
if (tokenCount + lineTokens > maxTokens) {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
lines.push(line);
|
|
157
|
+
tokenCount += lineTokens;
|
|
158
|
+
}
|
|
159
|
+
if (lines.length === 1) {
|
|
160
|
+
return { context: "", tokenCount: 0 };
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
context: lines.join(`
|
|
164
|
+
`),
|
|
165
|
+
tokenCount
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async function userPromptSubmitHook(context, options) {
|
|
169
|
+
const { prompt, projectPath } = context;
|
|
170
|
+
const keywords = extractKeywords(prompt);
|
|
171
|
+
if (keywords.length === 0) {
|
|
172
|
+
return {
|
|
173
|
+
notification: "",
|
|
174
|
+
injectedContext: "",
|
|
175
|
+
tokenCount: 0,
|
|
176
|
+
relatedMemories: []
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const config = options?.config ? { ...loadConfig(projectPath), ...options.config } : loadConfig(projectPath);
|
|
180
|
+
ensureProjectDirs(projectPath);
|
|
181
|
+
const dbPath = getProjectDBPath(projectPath);
|
|
182
|
+
const client = options?.client ?? createDBClient(dbPath);
|
|
183
|
+
const engine = options?.engine ?? createSearchEngine(client);
|
|
184
|
+
const searchQuery = keywords.join(" ");
|
|
185
|
+
const searchOptions = {
|
|
186
|
+
limit: config.search.default_limit,
|
|
187
|
+
layer: 2,
|
|
188
|
+
projectPath
|
|
189
|
+
};
|
|
190
|
+
let results = [];
|
|
191
|
+
try {
|
|
192
|
+
results = engine.search(searchQuery, searchOptions);
|
|
193
|
+
} catch {
|
|
194
|
+
if (!options?.client) {
|
|
195
|
+
client.close();
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
notification: "",
|
|
199
|
+
injectedContext: "",
|
|
200
|
+
tokenCount: 0,
|
|
201
|
+
relatedMemories: []
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const notification = formatNotification(results);
|
|
205
|
+
let injectedContext = "";
|
|
206
|
+
let tokenCount = 0;
|
|
207
|
+
if (config.memory.auto_inject && results.length > 0) {
|
|
208
|
+
const fullResults = engine.search(searchQuery, {
|
|
209
|
+
...searchOptions,
|
|
210
|
+
layer: 3,
|
|
211
|
+
limit: 5
|
|
212
|
+
});
|
|
213
|
+
const formatted = formatContext(fullResults, config.memory.max_inject_tokens);
|
|
214
|
+
injectedContext = formatted.context;
|
|
215
|
+
tokenCount = formatted.tokenCount;
|
|
216
|
+
}
|
|
217
|
+
if (!options?.client) {
|
|
218
|
+
client.close();
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
notification,
|
|
222
|
+
injectedContext,
|
|
223
|
+
tokenCount,
|
|
224
|
+
relatedMemories: results
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
export {
|
|
228
|
+
userPromptSubmitHook,
|
|
229
|
+
formatNotification,
|
|
230
|
+
formatContext,
|
|
231
|
+
extractKeywords
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export { userPromptSubmitHook };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ralphSkill
|
|
3
|
+
} from "./skills/ralph.js";
|
|
4
|
+
import {
|
|
5
|
+
memStatusSkill
|
|
6
|
+
} from "./skills/mem-status.js";
|
|
7
|
+
import {
|
|
8
|
+
userPromptSubmitHook
|
|
9
|
+
} from "./hooks/user-prompt-submit.js";
|
|
10
|
+
import {
|
|
11
|
+
postToolUseHook
|
|
12
|
+
} from "./hooks/post-tool-use.js";
|
|
13
|
+
import {
|
|
14
|
+
sessionStartHook
|
|
15
|
+
} from "./hooks/session-start.js";
|
|
16
|
+
import"./chunk-c3a91ngd.js";
|
|
17
|
+
import {
|
|
18
|
+
sessionEndHook
|
|
19
|
+
} from "./hooks/session-end.js";
|
|
20
|
+
import {
|
|
21
|
+
createMemoryStore,
|
|
22
|
+
estimateTokens
|
|
23
|
+
} from "./chunk-v8anyhk1.js";
|
|
24
|
+
import {
|
|
25
|
+
memForgetSkill
|
|
26
|
+
} from "./skills/mem-forget.js";
|
|
27
|
+
import {
|
|
28
|
+
memInjectSkill
|
|
29
|
+
} from "./skills/mem-inject.js";
|
|
30
|
+
import {
|
|
31
|
+
memSearchSkill
|
|
32
|
+
} from "./skills/mem-search.js";
|
|
33
|
+
import {
|
|
34
|
+
createSearchEngine
|
|
35
|
+
} from "./chunk-kga64hvg.js";
|
|
36
|
+
import"./chunk-41rc1bhg.js";
|
|
37
|
+
import"./chunk-w40c0y00.js";
|
|
38
|
+
import"./chunk-ns0dgdnb.js";
|
|
39
|
+
|
|
40
|
+
// src/index.ts
|
|
41
|
+
var VERSION = "0.1.0";
|
|
42
|
+
async function activate() {
|
|
43
|
+
console.log(`ralph-mem v${VERSION} activated`);
|
|
44
|
+
}
|
|
45
|
+
async function deactivate() {
|
|
46
|
+
console.log("ralph-mem deactivated");
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
userPromptSubmitHook,
|
|
50
|
+
sessionStartHook,
|
|
51
|
+
sessionEndHook,
|
|
52
|
+
ralphSkill,
|
|
53
|
+
postToolUseHook,
|
|
54
|
+
memStatusSkill,
|
|
55
|
+
memSearchSkill,
|
|
56
|
+
memInjectSkill,
|
|
57
|
+
memForgetSkill,
|
|
58
|
+
estimateTokens,
|
|
59
|
+
deactivate,
|
|
60
|
+
createSearchEngine,
|
|
61
|
+
createMemoryStore,
|
|
62
|
+
activate,
|
|
63
|
+
VERSION
|
|
64
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import"../chunk-ns0dgdnb.js";
|
|
2
|
+
|
|
3
|
+
// src/skills/mem-forget.ts
|
|
4
|
+
function parseBefore(before) {
|
|
5
|
+
const isoDate = new Date(before);
|
|
6
|
+
if (!Number.isNaN(isoDate.getTime())) {
|
|
7
|
+
return isoDate;
|
|
8
|
+
}
|
|
9
|
+
const match = before.match(/^(\d+)d$/);
|
|
10
|
+
if (match) {
|
|
11
|
+
const days = Number.parseInt(match[1], 10);
|
|
12
|
+
const date = new Date;
|
|
13
|
+
date.setDate(date.getDate() - days);
|
|
14
|
+
return date;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
function parseMemForgetArgs(argsString) {
|
|
19
|
+
const args = {};
|
|
20
|
+
const tokens = [];
|
|
21
|
+
let current = "";
|
|
22
|
+
let inQuotes = false;
|
|
23
|
+
let quoteChar = "";
|
|
24
|
+
for (const char of argsString) {
|
|
25
|
+
if ((char === '"' || char === "'") && !inQuotes) {
|
|
26
|
+
inQuotes = true;
|
|
27
|
+
quoteChar = char;
|
|
28
|
+
} else if (char === quoteChar && inQuotes) {
|
|
29
|
+
inQuotes = false;
|
|
30
|
+
quoteChar = "";
|
|
31
|
+
} else if (char === " " && !inQuotes) {
|
|
32
|
+
if (current) {
|
|
33
|
+
tokens.push(current);
|
|
34
|
+
current = "";
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
current += char;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (current) {
|
|
41
|
+
tokens.push(current);
|
|
42
|
+
}
|
|
43
|
+
let i = 0;
|
|
44
|
+
while (i < tokens.length) {
|
|
45
|
+
const token = tokens[i];
|
|
46
|
+
if (token === "--session" && i + 1 < tokens.length) {
|
|
47
|
+
args.sessionId = tokens[i + 1];
|
|
48
|
+
i += 2;
|
|
49
|
+
} else if (token === "--before" && i + 1 < tokens.length) {
|
|
50
|
+
args.before = tokens[i + 1];
|
|
51
|
+
i += 2;
|
|
52
|
+
} else if (token === "--confirm") {
|
|
53
|
+
args.confirm = true;
|
|
54
|
+
i++;
|
|
55
|
+
} else if (!token.startsWith("--") && !args.id) {
|
|
56
|
+
args.id = token;
|
|
57
|
+
i++;
|
|
58
|
+
} else {
|
|
59
|
+
i++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return args;
|
|
63
|
+
}
|
|
64
|
+
function formatPreviewMessage(targets) {
|
|
65
|
+
if (targets.length === 0) {
|
|
66
|
+
return "삭제 대상 없음";
|
|
67
|
+
}
|
|
68
|
+
const lines = targets.slice(0, 5).map((t) => {
|
|
69
|
+
const content = t.content.length > 40 ? `${t.content.slice(0, 37)}...` : t.content;
|
|
70
|
+
return `- ${t.id}: ${content}`;
|
|
71
|
+
});
|
|
72
|
+
if (targets.length > 5) {
|
|
73
|
+
lines.push(`... 외 ${targets.length - 5}개`);
|
|
74
|
+
}
|
|
75
|
+
return `⚠️ 삭제 대상:
|
|
76
|
+
${lines.join(`
|
|
77
|
+
`)}
|
|
78
|
+
|
|
79
|
+
삭제하려면: /mem-forget ${targets[0].id} --confirm`;
|
|
80
|
+
}
|
|
81
|
+
function formatSuccessMessage(deletedCount) {
|
|
82
|
+
return `✅ 삭제됨: ${deletedCount}개 observation`;
|
|
83
|
+
}
|
|
84
|
+
function getTargets(client, args, currentSessionId) {
|
|
85
|
+
if (args.id) {
|
|
86
|
+
const obs = client.getObservation(args.id);
|
|
87
|
+
return obs ? [obs] : [];
|
|
88
|
+
}
|
|
89
|
+
if (args.sessionId) {
|
|
90
|
+
return client.listObservations(args.sessionId, 1000);
|
|
91
|
+
}
|
|
92
|
+
if (args.before) {
|
|
93
|
+
const date = parseBefore(args.before);
|
|
94
|
+
if (!date) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const dateStr = date.toISOString();
|
|
98
|
+
const rows = client.db.prepare(`
|
|
99
|
+
SELECT * FROM observations
|
|
100
|
+
WHERE session_id = ? AND created_at < ?
|
|
101
|
+
ORDER BY created_at ASC
|
|
102
|
+
`).all(currentSessionId, dateStr);
|
|
103
|
+
return rows;
|
|
104
|
+
}
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
function deleteObservations(client, ids) {
|
|
108
|
+
if (ids.length === 0)
|
|
109
|
+
return 0;
|
|
110
|
+
let deleted = 0;
|
|
111
|
+
for (const id of ids) {
|
|
112
|
+
const result = client.db.prepare("DELETE FROM observations WHERE id = ?").run(id);
|
|
113
|
+
deleted += result.changes;
|
|
114
|
+
}
|
|
115
|
+
return deleted;
|
|
116
|
+
}
|
|
117
|
+
function forgetMemory(context, args) {
|
|
118
|
+
const { sessionId, client } = context;
|
|
119
|
+
if (!args.id && !args.sessionId && !args.before) {
|
|
120
|
+
return {
|
|
121
|
+
success: false,
|
|
122
|
+
deletedCount: 0,
|
|
123
|
+
message: "",
|
|
124
|
+
error: "삭제 대상을 지정해주세요. 사용법: /mem-forget <id> 또는 --before <일수>d"
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const targets = getTargets(client, args, sessionId);
|
|
128
|
+
if (targets.length === 0) {
|
|
129
|
+
return {
|
|
130
|
+
success: false,
|
|
131
|
+
deletedCount: 0,
|
|
132
|
+
message: "삭제 대상 없음"
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (!args.confirm) {
|
|
136
|
+
const targetSummary = targets.map((t) => ({
|
|
137
|
+
id: t.id,
|
|
138
|
+
content: t.content
|
|
139
|
+
}));
|
|
140
|
+
return {
|
|
141
|
+
success: false,
|
|
142
|
+
deletedCount: 0,
|
|
143
|
+
targets: targetSummary,
|
|
144
|
+
message: formatPreviewMessage(targetSummary),
|
|
145
|
+
requiresConfirmation: true
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const ids = targets.map((t) => t.id);
|
|
149
|
+
const deletedCount = deleteObservations(client, ids);
|
|
150
|
+
return {
|
|
151
|
+
success: true,
|
|
152
|
+
deletedCount,
|
|
153
|
+
message: formatSuccessMessage(deletedCount)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function createMemForgetSkill(context) {
|
|
157
|
+
return {
|
|
158
|
+
name: "/mem-forget",
|
|
159
|
+
execute(args) {
|
|
160
|
+
return forgetMemory(context, args);
|
|
161
|
+
},
|
|
162
|
+
parseArgs(argsString) {
|
|
163
|
+
return parseMemForgetArgs(argsString);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
async function executeMemForget(argsString, context) {
|
|
168
|
+
const args = parseMemForgetArgs(argsString);
|
|
169
|
+
const result = forgetMemory(context, args);
|
|
170
|
+
if (result.error) {
|
|
171
|
+
return `❌ ${result.error}`;
|
|
172
|
+
}
|
|
173
|
+
return result.message;
|
|
174
|
+
}
|
|
175
|
+
async function memForgetSkill(_input) {
|
|
176
|
+
return {
|
|
177
|
+
success: false,
|
|
178
|
+
deletedCount: 0,
|
|
179
|
+
message: "Use createMemForgetSkill() for full functionality"
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
export {
|
|
183
|
+
parseMemForgetArgs,
|
|
184
|
+
memForgetSkill,
|
|
185
|
+
formatSuccessMessage,
|
|
186
|
+
formatPreviewMessage,
|
|
187
|
+
forgetMemory,
|
|
188
|
+
executeMemForget,
|
|
189
|
+
createMemForgetSkill
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export { memForgetSkill };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import"../chunk-ns0dgdnb.js";
|
|
2
|
+
|
|
3
|
+
// src/skills/mem-inject.ts
|
|
4
|
+
var DEFAULT_IMPORTANCE = 0.7;
|
|
5
|
+
function parseMemInjectArgs(argsString) {
|
|
6
|
+
const args = {
|
|
7
|
+
content: ""
|
|
8
|
+
};
|
|
9
|
+
const tokens = [];
|
|
10
|
+
let current = "";
|
|
11
|
+
let inQuotes = false;
|
|
12
|
+
let quoteChar = "";
|
|
13
|
+
for (const char of argsString) {
|
|
14
|
+
if ((char === '"' || char === "'") && !inQuotes) {
|
|
15
|
+
inQuotes = true;
|
|
16
|
+
quoteChar = char;
|
|
17
|
+
} else if (char === quoteChar && inQuotes) {
|
|
18
|
+
inQuotes = false;
|
|
19
|
+
quoteChar = "";
|
|
20
|
+
} else if (char === " " && !inQuotes) {
|
|
21
|
+
if (current) {
|
|
22
|
+
tokens.push(current);
|
|
23
|
+
current = "";
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
current += char;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (current) {
|
|
30
|
+
tokens.push(current);
|
|
31
|
+
}
|
|
32
|
+
let i = 0;
|
|
33
|
+
while (i < tokens.length) {
|
|
34
|
+
const token = tokens[i];
|
|
35
|
+
if (token === "--importance" && i + 1 < tokens.length) {
|
|
36
|
+
const value = Number.parseFloat(tokens[i + 1]);
|
|
37
|
+
if (!Number.isNaN(value) && value >= 0 && value <= 1) {
|
|
38
|
+
args.importance = value;
|
|
39
|
+
}
|
|
40
|
+
i += 2;
|
|
41
|
+
} else if (token === "--type" && i + 1 < tokens.length) {
|
|
42
|
+
const typeValue = tokens[i + 1].toLowerCase();
|
|
43
|
+
if (typeValue === "note" || typeValue === "context") {
|
|
44
|
+
args.type = typeValue;
|
|
45
|
+
}
|
|
46
|
+
i += 2;
|
|
47
|
+
} else if (!token.startsWith("--") && !args.content) {
|
|
48
|
+
args.content = token;
|
|
49
|
+
i++;
|
|
50
|
+
} else {
|
|
51
|
+
i++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return args;
|
|
55
|
+
}
|
|
56
|
+
function formatInjectSuccess(observationId, content, importance) {
|
|
57
|
+
const displayContent = content.length > 100 ? `${content.slice(0, 97)}...` : content;
|
|
58
|
+
return `✅ 메모리에 추가됨
|
|
59
|
+
|
|
60
|
+
ID: ${observationId}
|
|
61
|
+
내용: ${displayContent}
|
|
62
|
+
중요도: ${importance}`;
|
|
63
|
+
}
|
|
64
|
+
function injectMemory(context, args) {
|
|
65
|
+
const { sessionId, client } = context;
|
|
66
|
+
if (!args.content || args.content.trim() === "") {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
message: "",
|
|
70
|
+
error: '내용이 필요합니다. 사용법: /mem-inject "내용"'
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const importance = args.importance ?? DEFAULT_IMPORTANCE;
|
|
74
|
+
const type = "note";
|
|
75
|
+
try {
|
|
76
|
+
const observation = client.createObservation({
|
|
77
|
+
session_id: sessionId,
|
|
78
|
+
type,
|
|
79
|
+
content: args.content.trim(),
|
|
80
|
+
importance
|
|
81
|
+
});
|
|
82
|
+
const message = formatInjectSuccess(observation.id, observation.content, importance);
|
|
83
|
+
return {
|
|
84
|
+
success: true,
|
|
85
|
+
observationId: observation.id,
|
|
86
|
+
message
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
message: "",
|
|
92
|
+
error: error instanceof Error ? error.message : String(error)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function createMemInjectSkill(context) {
|
|
97
|
+
return {
|
|
98
|
+
name: "/mem-inject",
|
|
99
|
+
execute(args) {
|
|
100
|
+
return injectMemory(context, args);
|
|
101
|
+
},
|
|
102
|
+
parseArgs(argsString) {
|
|
103
|
+
return parseMemInjectArgs(argsString);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async function executeMemInject(argsString, context) {
|
|
108
|
+
const args = parseMemInjectArgs(argsString);
|
|
109
|
+
const result = injectMemory(context, args);
|
|
110
|
+
if (!result.success) {
|
|
111
|
+
return `❌ ${result.error}`;
|
|
112
|
+
}
|
|
113
|
+
return result.message;
|
|
114
|
+
}
|
|
115
|
+
async function memInjectSkill(_input) {
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
message: "Use createMemInjectSkill() for full functionality"
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
parseMemInjectArgs,
|
|
123
|
+
memInjectSkill,
|
|
124
|
+
injectMemory,
|
|
125
|
+
formatInjectSuccess,
|
|
126
|
+
executeMemInject,
|
|
127
|
+
createMemInjectSkill
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export { memInjectSkill };
|