akemon 0.3.6 → 0.3.7
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/DATA_POLICY.md +11 -3
- package/README.md +133 -21
- package/dist/akemon-home.js +56 -0
- package/dist/akemon-message.js +107 -0
- package/dist/best-effort.js +8 -0
- package/dist/cli.js +1188 -100
- package/dist/cognitive-artifact-store.js +101 -0
- package/dist/cognitive-event-log.js +47 -0
- package/dist/config.js +45 -9
- package/dist/context.js +27 -6
- package/dist/core/contracts/layers.js +1 -0
- package/dist/core/contracts/permission.js +1 -0
- package/dist/core/contracts/workspace.js +1 -0
- package/dist/core-cognitive-module.js +768 -0
- package/dist/engine-peripheral.js +127 -26
- package/dist/engine-routing.js +58 -17
- package/dist/interactive-session.js +361 -0
- package/dist/local-interconnect.js +156 -0
- package/dist/local-registry.js +178 -0
- package/dist/mcp-server.js +4 -1
- package/dist/memory-proposal.js +379 -0
- package/dist/memory-recorder.js +368 -0
- package/dist/orphan-scan.js +36 -24
- package/dist/passive-reflection-cognitive-module.js +172 -0
- package/dist/peripheral-registry.js +235 -0
- package/dist/permission-audit.js +132 -0
- package/dist/relay-client.js +68 -9
- package/dist/relay-mode.js +34 -0
- package/dist/relay-peripheral.js +139 -49
- package/dist/runtime-platform.js +122 -0
- package/dist/secretariat/client.js +87 -0
- package/dist/self.js +15 -6
- package/dist/server.js +3675 -512
- package/dist/social-discovery.js +231 -0
- package/dist/software-agent-peripheral.js +185 -244
- package/dist/software-agent-transport.js +177 -0
- package/dist/task-module.js +243 -0
- package/dist/task-registry.js +756 -0
- package/dist/vendor/xterm/addon-fit.js +2 -0
- package/dist/vendor/xterm/addon-search.js +2 -0
- package/dist/vendor/xterm/addon-web-links.js +2 -0
- package/dist/vendor/xterm/xterm.css +285 -0
- package/dist/vendor/xterm/xterm.js +2 -0
- package/dist/work-memory.js +59 -15
- package/dist/workbench-peripheral-guide.js +79 -0
- package/dist/workbench-session.js +1074 -0
- package/dist/workbench.html +4011 -0
- package/package.json +8 -3
- package/scripts/build.cjs +24 -0
- package/scripts/check-architecture-baseline.cjs +68 -0
- package/scripts/test.cjs +38 -0
package/dist/mcp-server.js
CHANGED
|
@@ -17,7 +17,10 @@ import { biosPath, loadBioState, saveBioState, localNow, bioStatePromptModifier,
|
|
|
17
17
|
async function handleCallAgent(agentName, target, task) {
|
|
18
18
|
console.log(`[call_agent] ${agentName} → ${target}: ${task.slice(0, 80)}`);
|
|
19
19
|
try {
|
|
20
|
-
const result = await callAgent(target, task
|
|
20
|
+
const result = await callAgent(target, task, {
|
|
21
|
+
sourceAgent: agentName,
|
|
22
|
+
memoryScope: "task",
|
|
23
|
+
});
|
|
21
24
|
return { content: [{ type: "text", text: result }] };
|
|
22
25
|
}
|
|
23
26
|
catch (err) {
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
3
|
+
import { appendImpression, loadImpressions, selfDir } from "./self.js";
|
|
4
|
+
import { appendWorkMemoryNote, buildWorkMemoryContext } from "./work-memory.js";
|
|
5
|
+
const pendingMemoryProposals = new Map();
|
|
6
|
+
export const MEMORY_ACTION_PROPOSAL_GUIDE = [
|
|
7
|
+
"[Akemon Memory Proposal Contract]",
|
|
8
|
+
"Akemon memory is Akemon's own memory mechanism. It is not CLAUDE.md, ChatGPT memory, Claude memory, or an external agent's private memory.",
|
|
9
|
+
"When the owner asks Akemon to remember something and provides the content, output only JSON in this exact shape:",
|
|
10
|
+
"{",
|
|
11
|
+
" \"kind\": \"memory_proposal\",",
|
|
12
|
+
" \"answer\": \"natural-language reply to the owner; do not mention JSON or internal proposal mechanics\",",
|
|
13
|
+
" \"memory\": {",
|
|
14
|
+
" \"summary\": \"why this may be useful to remember\",",
|
|
15
|
+
" \"self\": [\"optional Akemon self-memory item\"],",
|
|
16
|
+
" \"work\": [\"optional owner/project work-memory item\"]",
|
|
17
|
+
" },",
|
|
18
|
+
" \"reason\": \"short machine-readable reason\",",
|
|
19
|
+
" \"processNote\": \"one concise owner-facing sentence explaining why Memory CM is proposing this\"",
|
|
20
|
+
"}",
|
|
21
|
+
"",
|
|
22
|
+
"Memory proposal rules:",
|
|
23
|
+
"- Use memory_proposal for owner phrases such as remember, 记住, 记一下, 以后默认, 偏好, 写入记忆, Akemon 记住.",
|
|
24
|
+
"- Prefer work memory for project preferences, project decisions, repo conventions, and owner instructions scoped to the current workdir.",
|
|
25
|
+
"- Prefer self memory for Akemon's own durable behavior or identity, not project facts.",
|
|
26
|
+
"- Do not redirect Akemon memory requests to CLAUDE.md or other external files unless the owner explicitly asks to edit those files.",
|
|
27
|
+
"- Do not claim memory has been written when creating a proposal; Akemon will show the pending memory to the owner for confirmation.",
|
|
28
|
+
"- Always write processNote in the configured owner-facing language. It is for the CM area, not a debug log.",
|
|
29
|
+
"",
|
|
30
|
+
"[Memory Action Proposal Contract]",
|
|
31
|
+
"Use this only when the owner is responding to a pending Akemon memory suggestion.",
|
|
32
|
+
"Output only JSON in this exact shape:",
|
|
33
|
+
"{",
|
|
34
|
+
" \"kind\": \"memory_action\",",
|
|
35
|
+
" \"action\": \"list_pending\" | \"show_memory\" | \"accept_pending\" | \"reject_pending\" | \"revise_pending\",",
|
|
36
|
+
" \"proposalId\": \"latest\" | string,",
|
|
37
|
+
" \"scope\": \"self\" | \"work\" | \"all\",",
|
|
38
|
+
" \"memory\": { \"summary\": \"optional revised summary\", \"self\": [\"optional revised self-memory item\"], \"work\": [\"optional revised work-memory item\"] },",
|
|
39
|
+
" \"reason\": \"short machine-readable reason\",",
|
|
40
|
+
" \"processNote\": \"one concise owner-facing sentence explaining the memory action\"",
|
|
41
|
+
"}",
|
|
42
|
+
"",
|
|
43
|
+
"Rules:",
|
|
44
|
+
"- Use show_memory when the owner asks what Akemon remembers or what work memory contains.",
|
|
45
|
+
"- Use accept_pending only when the owner clearly confirms that the pending memory should be kept.",
|
|
46
|
+
"- Use reject_pending only when the owner clearly rejects or cancels the pending memory.",
|
|
47
|
+
"- Use revise_pending when the owner says the pending suggestion is wrong and gives replacement wording.",
|
|
48
|
+
"- Use list_pending when the owner asks what Akemon is waiting to remember.",
|
|
49
|
+
"- Use proposalId \"latest\" when the owner refers to the most recent pending memory.",
|
|
50
|
+
"- Default scope to \"all\" when the owner says a general phrase such as remember this.",
|
|
51
|
+
"- Do not write memory yourself. Akemon will execute the memory action after this JSON is returned.",
|
|
52
|
+
].join("\n");
|
|
53
|
+
export function createMemoryProposal(input) {
|
|
54
|
+
const self = normalizeMemoryItems(input.content?.self);
|
|
55
|
+
const work = normalizeMemoryItems(input.content?.work);
|
|
56
|
+
if (!self.length && !work.length)
|
|
57
|
+
return null;
|
|
58
|
+
const now = new Date().toISOString();
|
|
59
|
+
const proposal = {
|
|
60
|
+
id: `mem_${Date.now()}_${randomUUID().slice(0, 8)}`,
|
|
61
|
+
createdAt: now,
|
|
62
|
+
updatedAt: now,
|
|
63
|
+
agentName: input.agentName,
|
|
64
|
+
workdir: input.workdir,
|
|
65
|
+
source: input.source,
|
|
66
|
+
status: "pending",
|
|
67
|
+
summary: cleanOptionalText(input.content?.summary),
|
|
68
|
+
self,
|
|
69
|
+
work,
|
|
70
|
+
reason: cleanOptionalText(input.reason),
|
|
71
|
+
};
|
|
72
|
+
pendingMemoryProposals.set(proposal.id, proposal);
|
|
73
|
+
return proposal;
|
|
74
|
+
}
|
|
75
|
+
export function describePendingMemoryProposalsForCoreCm(input) {
|
|
76
|
+
const proposals = listPendingMemoryProposals(input);
|
|
77
|
+
if (!proposals.length)
|
|
78
|
+
return "- No pending memory proposals.";
|
|
79
|
+
return proposals.map((proposal) => {
|
|
80
|
+
const lines = [
|
|
81
|
+
`- ${proposal.id}`,
|
|
82
|
+
` summary: ${proposal.summary || "(none)"}`,
|
|
83
|
+
` self: ${proposal.self.length ? proposal.self.join(" | ") : "(none)"}`,
|
|
84
|
+
` work: ${proposal.work.length ? proposal.work.join(" | ") : "(none)"}`,
|
|
85
|
+
];
|
|
86
|
+
return lines.join("\n");
|
|
87
|
+
}).join("\n");
|
|
88
|
+
}
|
|
89
|
+
export function formatMemoryProposalForOwner(proposal) {
|
|
90
|
+
const lines = [
|
|
91
|
+
"I have a memory suggestion waiting for confirmation.",
|
|
92
|
+
];
|
|
93
|
+
if (proposal.summary)
|
|
94
|
+
lines.push(proposal.summary);
|
|
95
|
+
if (proposal.self.length)
|
|
96
|
+
lines.push(`Akemon self memory: ${proposal.self.join("; ")}`);
|
|
97
|
+
if (proposal.work.length)
|
|
98
|
+
lines.push(`Work memory: ${proposal.work.join("; ")}`);
|
|
99
|
+
lines.push("You can tell me to remember it or ignore it.");
|
|
100
|
+
return lines.join("\n");
|
|
101
|
+
}
|
|
102
|
+
export function formatPendingMemoryProposalsForOwner(input) {
|
|
103
|
+
const proposals = listPendingMemoryProposals(input);
|
|
104
|
+
if (!proposals.length)
|
|
105
|
+
return "There are no pending memory suggestions.";
|
|
106
|
+
return [
|
|
107
|
+
`There ${proposals.length === 1 ? "is" : "are"} ${proposals.length} pending memory suggestion${proposals.length === 1 ? "" : "s"}.`,
|
|
108
|
+
"",
|
|
109
|
+
...proposals.map(formatMemoryProposalForOwner),
|
|
110
|
+
].join("\n");
|
|
111
|
+
}
|
|
112
|
+
export async function formatCurrentMemoryForOwner(input) {
|
|
113
|
+
const scope = normalizeScope(input.scope);
|
|
114
|
+
const sections = [];
|
|
115
|
+
if (scope === "self" || scope === "all") {
|
|
116
|
+
const impressions = await loadImpressions(input.workdir, input.agentName, 30);
|
|
117
|
+
const selfMemory = impressions
|
|
118
|
+
.filter((entry) => entry.cat === "owner_confirmed_memory")
|
|
119
|
+
.slice(-20);
|
|
120
|
+
sections.push("Akemon self memory:");
|
|
121
|
+
if (selfMemory.length) {
|
|
122
|
+
sections.push(...selfMemory.map((entry) => `- ${entry.text}`));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
sections.push("- No owner-confirmed self memory found.");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (scope === "work" || scope === "all") {
|
|
129
|
+
const packet = await buildWorkMemoryContext({
|
|
130
|
+
workdir: input.workdir,
|
|
131
|
+
agentName: input.agentName,
|
|
132
|
+
purpose: "Akemon chat memory review",
|
|
133
|
+
budget: 5_000,
|
|
134
|
+
});
|
|
135
|
+
sections.push("Project work memory:");
|
|
136
|
+
sections.push(formatWorkMemoryForOwner(packet));
|
|
137
|
+
}
|
|
138
|
+
if (scope === "all") {
|
|
139
|
+
sections.push("Pending memory suggestions:");
|
|
140
|
+
sections.push(formatPendingMemoryProposalsForOwner(input));
|
|
141
|
+
}
|
|
142
|
+
return sections.join("\n\n");
|
|
143
|
+
}
|
|
144
|
+
export async function formatCurrentMemoryForCoreCm(input) {
|
|
145
|
+
const scope = normalizeScope(input.scope);
|
|
146
|
+
const sections = [];
|
|
147
|
+
if (scope === "self" || scope === "all") {
|
|
148
|
+
const impressions = await loadImpressions(input.workdir, input.agentName, 30);
|
|
149
|
+
const selfMemory = impressions
|
|
150
|
+
.filter((entry) => entry.cat === "owner_confirmed_memory")
|
|
151
|
+
.slice(-20);
|
|
152
|
+
sections.push("Akemon self memory:");
|
|
153
|
+
if (selfMemory.length) {
|
|
154
|
+
sections.push(...selfMemory.map((entry) => `- ${entry.text}`));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
sections.push("- No owner-confirmed self memory found.");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (scope === "work" || scope === "all") {
|
|
161
|
+
const packet = await buildWorkMemoryContext({
|
|
162
|
+
workdir: input.workdir,
|
|
163
|
+
agentName: input.agentName,
|
|
164
|
+
purpose: "Akemon Core CM memory context",
|
|
165
|
+
budget: 8_000,
|
|
166
|
+
});
|
|
167
|
+
sections.push("Project work memory:");
|
|
168
|
+
sections.push(packet.sections.length ? packet.text : `- No project work memory found in ${packet.workMemoryDir}.`);
|
|
169
|
+
}
|
|
170
|
+
if (scope === "all") {
|
|
171
|
+
sections.push("Pending memory suggestions:");
|
|
172
|
+
sections.push(formatPendingMemoryProposalsForOwner(input));
|
|
173
|
+
}
|
|
174
|
+
return sections.join("\n\n");
|
|
175
|
+
}
|
|
176
|
+
function formatWorkMemoryForOwner(packet) {
|
|
177
|
+
const contentSections = packet.sections
|
|
178
|
+
.filter((section) => section.title !== "work memory file index")
|
|
179
|
+
.map(formatWorkMemorySectionForOwner)
|
|
180
|
+
.filter(Boolean);
|
|
181
|
+
if (!contentSections.length)
|
|
182
|
+
return "- No project work memory found.";
|
|
183
|
+
return contentSections.join("\n");
|
|
184
|
+
}
|
|
185
|
+
function formatWorkMemorySectionForOwner(section) {
|
|
186
|
+
const content = section.content
|
|
187
|
+
.split(/\r?\n/)
|
|
188
|
+
.map((line) => line.trim())
|
|
189
|
+
.filter((line) => line && !line.startsWith("Source:") && !line.startsWith("## "))
|
|
190
|
+
.join("\n")
|
|
191
|
+
.trim();
|
|
192
|
+
if (!content)
|
|
193
|
+
return "";
|
|
194
|
+
const oneLine = content.replace(/\s+/g, " ").trim();
|
|
195
|
+
const excerpt = oneLine.length > 600 ? `${oneLine.slice(0, 597)}...` : oneLine;
|
|
196
|
+
return `- ${section.relativePath || section.title}: ${excerpt}`;
|
|
197
|
+
}
|
|
198
|
+
export async function executeMemoryActionProposal(input) {
|
|
199
|
+
const result = await executeMemoryActionProposalWithResult(input);
|
|
200
|
+
return result.output;
|
|
201
|
+
}
|
|
202
|
+
export async function executeMemoryActionProposalWithResult(input) {
|
|
203
|
+
const requestedProposalId = input.proposal.proposalId?.trim();
|
|
204
|
+
if (input.proposal.action === "list_pending") {
|
|
205
|
+
return {
|
|
206
|
+
output: formatPendingMemoryProposalsForOwner(input),
|
|
207
|
+
action: input.proposal.action,
|
|
208
|
+
status: "listed",
|
|
209
|
+
proposalId: requestedProposalId,
|
|
210
|
+
scope: normalizeScope(input.proposal.scope),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (input.proposal.action === "show_memory") {
|
|
214
|
+
const scope = normalizeScope(input.proposal.scope);
|
|
215
|
+
return {
|
|
216
|
+
output: await formatCurrentMemoryForOwner({
|
|
217
|
+
agentName: input.agentName,
|
|
218
|
+
workdir: input.workdir,
|
|
219
|
+
scope,
|
|
220
|
+
}),
|
|
221
|
+
action: input.proposal.action,
|
|
222
|
+
status: "shown",
|
|
223
|
+
proposalId: requestedProposalId,
|
|
224
|
+
scope,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const proposal = resolvePendingMemoryProposal(input);
|
|
228
|
+
if (!proposal) {
|
|
229
|
+
return {
|
|
230
|
+
output: "I could not find a pending memory suggestion to update.",
|
|
231
|
+
action: input.proposal.action,
|
|
232
|
+
status: "not_found",
|
|
233
|
+
proposalId: requestedProposalId,
|
|
234
|
+
scope: normalizeScope(input.proposal.scope),
|
|
235
|
+
error: "pending memory proposal not found",
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (input.proposal.action === "reject_pending") {
|
|
239
|
+
proposal.status = "rejected";
|
|
240
|
+
proposal.updatedAt = new Date().toISOString();
|
|
241
|
+
proposal.reason = cleanOptionalText(input.proposal.reason) || proposal.reason;
|
|
242
|
+
return {
|
|
243
|
+
output: "I discarded that pending memory suggestion.",
|
|
244
|
+
action: input.proposal.action,
|
|
245
|
+
status: "rejected",
|
|
246
|
+
proposalId: requestedProposalId,
|
|
247
|
+
resolvedProposalId: proposal.id,
|
|
248
|
+
scope: normalizeScope(input.proposal.scope),
|
|
249
|
+
selfCount: proposal.self.length,
|
|
250
|
+
workCount: proposal.work.length,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (input.proposal.action === "revise_pending") {
|
|
254
|
+
const self = normalizeMemoryItems(input.proposal.memory?.self);
|
|
255
|
+
const work = normalizeMemoryItems(input.proposal.memory?.work);
|
|
256
|
+
if (!self.length && !work.length) {
|
|
257
|
+
return {
|
|
258
|
+
output: "I could not revise that memory suggestion because the replacement did not contain any memory content.",
|
|
259
|
+
action: input.proposal.action,
|
|
260
|
+
status: "invalid",
|
|
261
|
+
proposalId: requestedProposalId,
|
|
262
|
+
resolvedProposalId: proposal.id,
|
|
263
|
+
scope: normalizeScope(input.proposal.scope),
|
|
264
|
+
error: "replacement did not contain memory content",
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
proposal.summary = cleanOptionalText(input.proposal.memory?.summary) || proposal.summary;
|
|
268
|
+
proposal.self = self;
|
|
269
|
+
proposal.work = work;
|
|
270
|
+
proposal.reason = cleanOptionalText(input.proposal.reason) || proposal.reason;
|
|
271
|
+
proposal.updatedAt = new Date().toISOString();
|
|
272
|
+
return {
|
|
273
|
+
output: [
|
|
274
|
+
"I updated that pending memory suggestion.",
|
|
275
|
+
"",
|
|
276
|
+
formatMemoryProposalForOwner(proposal),
|
|
277
|
+
].join("\n"),
|
|
278
|
+
action: input.proposal.action,
|
|
279
|
+
status: "revised",
|
|
280
|
+
proposalId: requestedProposalId,
|
|
281
|
+
resolvedProposalId: proposal.id,
|
|
282
|
+
scope: normalizeScope(input.proposal.scope),
|
|
283
|
+
selfCount: proposal.self.length,
|
|
284
|
+
workCount: proposal.work.length,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const scope = normalizeScope(input.proposal.scope);
|
|
288
|
+
const writtenWorkMemoryPaths = [];
|
|
289
|
+
if ((scope === "self" || scope === "all") && proposal.self.length) {
|
|
290
|
+
await mkdir(selfDir(input.workdir, input.agentName), { recursive: true });
|
|
291
|
+
for (const item of proposal.self) {
|
|
292
|
+
await appendImpression(input.workdir, input.agentName, "owner_confirmed_memory", item);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if ((scope === "work" || scope === "all") && proposal.work.length) {
|
|
296
|
+
for (const item of proposal.work) {
|
|
297
|
+
const result = await appendWorkMemoryNote({
|
|
298
|
+
workdir: input.workdir,
|
|
299
|
+
agentName: input.agentName,
|
|
300
|
+
text: item,
|
|
301
|
+
source: "user",
|
|
302
|
+
kind: "akemon-chat-memory-proposal",
|
|
303
|
+
});
|
|
304
|
+
writtenWorkMemoryPaths.push(result.path);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
proposal.status = "accepted";
|
|
308
|
+
proposal.updatedAt = new Date().toISOString();
|
|
309
|
+
proposal.acceptedScopes = [scope];
|
|
310
|
+
proposal.writtenWorkMemoryPaths = writtenWorkMemoryPaths;
|
|
311
|
+
return {
|
|
312
|
+
output: formatAcceptedMemoryProposal(proposal, scope),
|
|
313
|
+
action: input.proposal.action,
|
|
314
|
+
status: "accepted",
|
|
315
|
+
proposalId: requestedProposalId,
|
|
316
|
+
resolvedProposalId: proposal.id,
|
|
317
|
+
scope,
|
|
318
|
+
selfCount: proposal.self.length,
|
|
319
|
+
workCount: proposal.work.length,
|
|
320
|
+
writtenWorkMemoryPaths,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function listPendingMemoryProposals(input) {
|
|
324
|
+
return Array.from(pendingMemoryProposals.values())
|
|
325
|
+
.filter((proposal) => {
|
|
326
|
+
return proposal.status === "pending"
|
|
327
|
+
&& proposal.agentName === input.agentName
|
|
328
|
+
&& proposal.workdir === input.workdir;
|
|
329
|
+
})
|
|
330
|
+
.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
331
|
+
}
|
|
332
|
+
function resolvePendingMemoryProposal(input) {
|
|
333
|
+
const proposalId = input.proposal.proposalId?.trim();
|
|
334
|
+
if (!proposalId || proposalId === "latest" || proposalId === "last") {
|
|
335
|
+
return listPendingMemoryProposals(input)[0] || null;
|
|
336
|
+
}
|
|
337
|
+
const proposal = pendingMemoryProposals.get(proposalId);
|
|
338
|
+
if (!proposal || proposal.status !== "pending")
|
|
339
|
+
return null;
|
|
340
|
+
if (proposal.agentName !== input.agentName || proposal.workdir !== input.workdir)
|
|
341
|
+
return null;
|
|
342
|
+
return proposal;
|
|
343
|
+
}
|
|
344
|
+
function formatAcceptedMemoryProposal(proposal, scope) {
|
|
345
|
+
const remembered = [];
|
|
346
|
+
if ((scope === "self" || scope === "all") && proposal.self.length) {
|
|
347
|
+
remembered.push("Akemon self memory");
|
|
348
|
+
}
|
|
349
|
+
if ((scope === "work" || scope === "all") && proposal.work.length) {
|
|
350
|
+
remembered.push("work memory");
|
|
351
|
+
}
|
|
352
|
+
if (!remembered.length)
|
|
353
|
+
return "That memory suggestion did not contain anything for the selected scope.";
|
|
354
|
+
return `I remembered that in ${remembered.join(" and ")}.`;
|
|
355
|
+
}
|
|
356
|
+
function normalizeScope(scope) {
|
|
357
|
+
if (scope === "self" || scope === "work" || scope === "all")
|
|
358
|
+
return scope;
|
|
359
|
+
return "all";
|
|
360
|
+
}
|
|
361
|
+
function normalizeMemoryItems(items) {
|
|
362
|
+
if (!Array.isArray(items))
|
|
363
|
+
return [];
|
|
364
|
+
const normalized = [];
|
|
365
|
+
for (const item of items) {
|
|
366
|
+
const text = cleanOptionalText(item);
|
|
367
|
+
if (text)
|
|
368
|
+
normalized.push(text);
|
|
369
|
+
}
|
|
370
|
+
return normalized;
|
|
371
|
+
}
|
|
372
|
+
function cleanOptionalText(value) {
|
|
373
|
+
if (typeof value !== "string")
|
|
374
|
+
return undefined;
|
|
375
|
+
const trimmed = value.replace(/\s+/g, " ").trim();
|
|
376
|
+
if (!trimmed)
|
|
377
|
+
return undefined;
|
|
378
|
+
return trimmed.length > 1000 ? `${trimmed.slice(0, 997)}...` : trimmed;
|
|
379
|
+
}
|