create-forge-team 0.2.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/README.md +73 -0
- package/dist/chunk-GOZ3OAFK.js +805 -0
- package/dist/chunk-N62P5WWV.js +108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7456 -0
- package/dist/memory-DGOVUFNJ.js +21 -0
- package/dist/openrouter-client-DAUFUMZX.js +8 -0
- package/package.json +54 -0
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
import {
|
|
2
|
+
callOpenRouter,
|
|
3
|
+
isMemoryAgentConfigured
|
|
4
|
+
} from "./chunk-N62P5WWV.js";
|
|
5
|
+
|
|
6
|
+
// src/memory/index.ts
|
|
7
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
8
|
+
import { join as join4 } from "path";
|
|
9
|
+
import { homedir as homedir3 } from "os";
|
|
10
|
+
|
|
11
|
+
// src/memory/prompts.ts
|
|
12
|
+
function buildExtractionPrompt(transcript, agentSlug, existingIndex) {
|
|
13
|
+
const agentKnowledge = existingIndex?.agents?.[agentSlug];
|
|
14
|
+
const existingItems = agentKnowledge?.knowledgeItems || [];
|
|
15
|
+
const existingDocsSection = existingItems.length > 0 ? `
|
|
16
|
+
## Existing Knowledge Entries for This Agent
|
|
17
|
+
These are documents that already exist. If this session contains updates to any of these topics, include them in "upsert_targets" instead of creating duplicates.
|
|
18
|
+
|
|
19
|
+
${existingItems.map((item) => `- ${item.filePath}: ${item.title} (${item.summary})`).join("\n")}` : "";
|
|
20
|
+
return `You are a knowledge extraction assistant. Analyze the following session transcript and extract structured information.
|
|
21
|
+
|
|
22
|
+
## Agent
|
|
23
|
+
Slug: ${agentSlug}
|
|
24
|
+
${existingDocsSection}
|
|
25
|
+
|
|
26
|
+
## Session Transcript
|
|
27
|
+
${transcript}
|
|
28
|
+
|
|
29
|
+
## Instructions
|
|
30
|
+
Extract the following from the transcript. Output ONLY valid JSON, no markdown fences, no explanation.
|
|
31
|
+
|
|
32
|
+
{
|
|
33
|
+
"session_summary": "2-3 sentence overview of what happened in this session",
|
|
34
|
+
"headline": "One-line headline suitable for a cross-agent activity feed (e.g., 'Practiced PM interview questions, focused on product sense')",
|
|
35
|
+
"key_decisions": [
|
|
36
|
+
{ "decision": "What was decided", "reasoning": "Why it was decided", "date": "YYYY-MM-DD" }
|
|
37
|
+
],
|
|
38
|
+
"facts_learned": [
|
|
39
|
+
{ "fact": "A concrete fact learned during the session", "category": "category (e.g., 'user_preference', 'project_info', 'domain_knowledge')" }
|
|
40
|
+
],
|
|
41
|
+
"open_threads": [
|
|
42
|
+
{ "description": "Something that needs follow-up", "urgency": "high|medium|low", "deadline": "YYYY-MM-DD or null" }
|
|
43
|
+
],
|
|
44
|
+
"tool_data": [
|
|
45
|
+
{ "tool": "tool name used", "query": "what was queried", "data_summary": "key data returned", "staleness_category": "realtime|semi_dynamic|reference" }
|
|
46
|
+
],
|
|
47
|
+
"upsert_targets": [
|
|
48
|
+
{ "existing_file_slug": "slug of existing doc to update", "update_summary": "what new info to add" }
|
|
49
|
+
],
|
|
50
|
+
"topic_tags": ["tag1", "tag2"]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Rules:
|
|
54
|
+
- If no items exist for a field, use an empty array []
|
|
55
|
+
- Dates should be in YYYY-MM-DD format
|
|
56
|
+
- The headline should be concise (under 80 chars) and describe the session from the agent's perspective
|
|
57
|
+
- For upsert_targets, only reference files that appear in the existing knowledge entries above
|
|
58
|
+
- tool_data should capture the RESULTS of tool calls, not just the fact that tools were used
|
|
59
|
+
- Be specific in summaries \u2014 include names, numbers, and concrete details
|
|
60
|
+
- Output ONLY the JSON object, nothing else`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/memory/pipeline/organize.ts
|
|
64
|
+
import {
|
|
65
|
+
readFileSync as readFileSync2,
|
|
66
|
+
writeFileSync,
|
|
67
|
+
mkdirSync,
|
|
68
|
+
existsSync as existsSync2,
|
|
69
|
+
readdirSync
|
|
70
|
+
} from "fs";
|
|
71
|
+
import { join as join2 } from "path";
|
|
72
|
+
import { homedir as homedir2 } from "os";
|
|
73
|
+
|
|
74
|
+
// src/memory/tool-classifier.ts
|
|
75
|
+
import { readFileSync, existsSync } from "fs";
|
|
76
|
+
import { join } from "path";
|
|
77
|
+
import { homedir } from "os";
|
|
78
|
+
var DEFAULT_PATTERNS = {
|
|
79
|
+
"google-calendar__list-events": { category: "semi_dynamic", maxAgeMinutes: 120 },
|
|
80
|
+
"google-calendar__get-event": { category: "semi_dynamic", maxAgeMinutes: 120 },
|
|
81
|
+
"google-calendar__search-events": { category: "semi_dynamic", maxAgeMinutes: 120 },
|
|
82
|
+
"google-calendar__get-freebusy": { category: "semi_dynamic", maxAgeMinutes: 60 },
|
|
83
|
+
"gmail__search_emails": { category: "semi_dynamic", maxAgeMinutes: 60 },
|
|
84
|
+
"gmail__read_email": { category: "reference", maxAgeMinutes: 10080 },
|
|
85
|
+
"gmail__list_email_labels": { category: "reference", maxAgeMinutes: 1440 },
|
|
86
|
+
"WebSearch": { category: "semi_dynamic", maxAgeMinutes: 1440 },
|
|
87
|
+
"WebFetch": { category: "semi_dynamic", maxAgeMinutes: 1440 },
|
|
88
|
+
"browser-use__browser_task": { category: "semi_dynamic", maxAgeMinutes: 240 }
|
|
89
|
+
};
|
|
90
|
+
var DEFAULT_FALLBACK = {
|
|
91
|
+
category: "semi_dynamic",
|
|
92
|
+
maxAgeMinutes: 240
|
|
93
|
+
};
|
|
94
|
+
function loadUserConfig() {
|
|
95
|
+
const configPath = join(homedir(), ".claude", "forge", "config.json");
|
|
96
|
+
if (!existsSync(configPath)) return null;
|
|
97
|
+
try {
|
|
98
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
99
|
+
return config.toolStaleness || null;
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function classifyToolStaleness(toolName) {
|
|
105
|
+
const normalized = toolName.replace(/^mcp__/, "");
|
|
106
|
+
const userConfig = loadUserConfig();
|
|
107
|
+
if (userConfig) {
|
|
108
|
+
if (userConfig.patterns[normalized]) {
|
|
109
|
+
return userConfig.patterns[normalized];
|
|
110
|
+
}
|
|
111
|
+
if (userConfig.patterns[toolName]) {
|
|
112
|
+
return userConfig.patterns[toolName];
|
|
113
|
+
}
|
|
114
|
+
for (const [pattern, config] of Object.entries(userConfig.defaults || {})) {
|
|
115
|
+
if (matchWildcard(normalized, pattern) || matchWildcard(toolName, pattern)) {
|
|
116
|
+
return config;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const [pattern, config] of Object.entries(DEFAULT_PATTERNS)) {
|
|
121
|
+
if (normalized.includes(pattern) || toolName.includes(pattern)) {
|
|
122
|
+
return config;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return DEFAULT_FALLBACK;
|
|
126
|
+
}
|
|
127
|
+
function isStale(fetchedAt, maxAgeMinutes) {
|
|
128
|
+
const fetchedTime = new Date(fetchedAt).getTime();
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
const ageMinutes = (now - fetchedTime) / (1e3 * 60);
|
|
131
|
+
return ageMinutes > maxAgeMinutes;
|
|
132
|
+
}
|
|
133
|
+
function matchWildcard(text, pattern) {
|
|
134
|
+
if (pattern.endsWith("*")) {
|
|
135
|
+
return text.startsWith(pattern.slice(0, -1));
|
|
136
|
+
}
|
|
137
|
+
return text === pattern;
|
|
138
|
+
}
|
|
139
|
+
function formatStaleness(fetchedAt) {
|
|
140
|
+
const fetchedTime = new Date(fetchedAt).getTime();
|
|
141
|
+
const now = Date.now();
|
|
142
|
+
const ageMinutes = (now - fetchedTime) / (1e3 * 60);
|
|
143
|
+
if (ageMinutes < 60) return `${Math.round(ageMinutes)}m ago`;
|
|
144
|
+
if (ageMinutes < 1440) return `${Math.round(ageMinutes / 60)}h ago`;
|
|
145
|
+
return `${Math.round(ageMinutes / 1440)}d ago`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/memory/pipeline/organize.ts
|
|
149
|
+
function getKnowledgeDir() {
|
|
150
|
+
return join2(homedir2(), ".claude", "forge", "knowledge");
|
|
151
|
+
}
|
|
152
|
+
function getTranscriptsDir() {
|
|
153
|
+
return join2(homedir2(), ".claude", "forge", "transcripts");
|
|
154
|
+
}
|
|
155
|
+
function getAgentKnowledgeDir(agentSlug) {
|
|
156
|
+
return join2(getKnowledgeDir(), agentSlug);
|
|
157
|
+
}
|
|
158
|
+
function getIndexPath() {
|
|
159
|
+
return join2(getKnowledgeDir(), "_index.json");
|
|
160
|
+
}
|
|
161
|
+
function getFeedPath() {
|
|
162
|
+
return join2(getKnowledgeDir(), "_feed.md");
|
|
163
|
+
}
|
|
164
|
+
function getQueuePath() {
|
|
165
|
+
return join2(getKnowledgeDir(), "_queue.json");
|
|
166
|
+
}
|
|
167
|
+
function loadKnowledgeIndex() {
|
|
168
|
+
const indexPath = getIndexPath();
|
|
169
|
+
if (!existsSync2(indexPath)) return null;
|
|
170
|
+
try {
|
|
171
|
+
return JSON.parse(readFileSync2(indexPath, "utf-8"));
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function saveKnowledgeIndex(index) {
|
|
177
|
+
const dir = getKnowledgeDir();
|
|
178
|
+
mkdirSync(dir, { recursive: true });
|
|
179
|
+
writeFileSync(getIndexPath(), JSON.stringify(index, null, 2), "utf-8");
|
|
180
|
+
}
|
|
181
|
+
function ensureAgentDirs(agentSlug) {
|
|
182
|
+
const agentDir = getAgentKnowledgeDir(agentSlug);
|
|
183
|
+
mkdirSync(join2(agentDir, "sessions"), { recursive: true });
|
|
184
|
+
mkdirSync(join2(agentDir, "reference"), { recursive: true });
|
|
185
|
+
mkdirSync(join2(agentDir, "tool-cache"), { recursive: true });
|
|
186
|
+
}
|
|
187
|
+
function formatDate(date) {
|
|
188
|
+
return date.toISOString().split("T")[0];
|
|
189
|
+
}
|
|
190
|
+
function formatRelativeDate(dateStr) {
|
|
191
|
+
const date = new Date(dateStr);
|
|
192
|
+
const now = /* @__PURE__ */ new Date();
|
|
193
|
+
const diffMs = now.getTime() - date.getTime();
|
|
194
|
+
const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
|
|
195
|
+
if (diffDays === 0) return "today";
|
|
196
|
+
if (diffDays === 1) return "yesterday";
|
|
197
|
+
if (diffDays < 7) return `${diffDays} days ago`;
|
|
198
|
+
if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;
|
|
199
|
+
return `${Math.floor(diffDays / 30)} months ago`;
|
|
200
|
+
}
|
|
201
|
+
function formatDateWithRelative(dateStr) {
|
|
202
|
+
const date = new Date(dateStr);
|
|
203
|
+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
204
|
+
const abs = `${months[date.getMonth()]} ${date.getDate()}`;
|
|
205
|
+
const rel = formatRelativeDate(dateStr);
|
|
206
|
+
return `${abs} (${rel})`;
|
|
207
|
+
}
|
|
208
|
+
function slugify(text) {
|
|
209
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
210
|
+
}
|
|
211
|
+
function organizeKnowledge(extraction, transcript) {
|
|
212
|
+
const { agentSlug } = transcript;
|
|
213
|
+
ensureAgentDirs(agentSlug);
|
|
214
|
+
const today = formatDate(/* @__PURE__ */ new Date());
|
|
215
|
+
let index = loadKnowledgeIndex() || {
|
|
216
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
217
|
+
agents: {}
|
|
218
|
+
};
|
|
219
|
+
if (!index.agents[agentSlug]) {
|
|
220
|
+
index.agents[agentSlug] = {
|
|
221
|
+
slug: agentSlug,
|
|
222
|
+
sessionCount: 0,
|
|
223
|
+
lastSessionDate: today,
|
|
224
|
+
knowledgeItems: []
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const agentIndex = index.agents[agentSlug];
|
|
228
|
+
agentIndex.sessionCount++;
|
|
229
|
+
agentIndex.lastSessionDate = today;
|
|
230
|
+
const topicSlug = slugify(extraction.headline);
|
|
231
|
+
const sessionFileName = `${today}-${topicSlug}.md`;
|
|
232
|
+
const sessionFilePath = join2(getAgentKnowledgeDir(agentSlug), "sessions", sessionFileName);
|
|
233
|
+
const sessionContent = buildSessionSummary(extraction, transcript);
|
|
234
|
+
writeFileSync(sessionFilePath, sessionContent, "utf-8");
|
|
235
|
+
agentIndex.knowledgeItems.push({
|
|
236
|
+
id: `session-${transcript.conversationId}`,
|
|
237
|
+
type: "session_summary",
|
|
238
|
+
filePath: `sessions/${sessionFileName}`,
|
|
239
|
+
title: extraction.headline,
|
|
240
|
+
summary: extraction.sessionSummary,
|
|
241
|
+
tags: extraction.topicTags,
|
|
242
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
243
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
244
|
+
});
|
|
245
|
+
for (const target of extraction.upsertTargets) {
|
|
246
|
+
const refPath = join2(getAgentKnowledgeDir(agentSlug), "reference", `${target.existingFileSlug}.md`);
|
|
247
|
+
if (existsSync2(refPath)) {
|
|
248
|
+
const existing = readFileSync2(refPath, "utf-8");
|
|
249
|
+
const updated = `${existing}
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
**Updated ${today}:**
|
|
253
|
+
${target.updateSummary}
|
|
254
|
+
`;
|
|
255
|
+
writeFileSync(refPath, updated, "utf-8");
|
|
256
|
+
const item = agentIndex.knowledgeItems.find(
|
|
257
|
+
(i) => i.filePath === `reference/${target.existingFileSlug}.md`
|
|
258
|
+
);
|
|
259
|
+
if (item) {
|
|
260
|
+
item.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
261
|
+
item.summary = target.updateSummary;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (extraction.keyDecisions.length > 0) {
|
|
266
|
+
const decisionsPath = join2(getAgentKnowledgeDir(agentSlug), "decisions.md");
|
|
267
|
+
let existing = "";
|
|
268
|
+
if (existsSync2(decisionsPath)) {
|
|
269
|
+
existing = readFileSync2(decisionsPath, "utf-8");
|
|
270
|
+
} else {
|
|
271
|
+
existing = `# Key Decisions
|
|
272
|
+
|
|
273
|
+
Running log of important decisions made during sessions.
|
|
274
|
+
`;
|
|
275
|
+
}
|
|
276
|
+
const newDecisions = extraction.keyDecisions.map(
|
|
277
|
+
(d) => `
|
|
278
|
+
## ${d.date}: ${d.decision}
|
|
279
|
+
- **Reasoning:** ${d.reasoning}
|
|
280
|
+
`
|
|
281
|
+
).join("");
|
|
282
|
+
writeFileSync(decisionsPath, existing + newDecisions, "utf-8");
|
|
283
|
+
}
|
|
284
|
+
for (const toolData of extraction.toolData) {
|
|
285
|
+
const staleness = classifyToolStaleness(toolData.tool);
|
|
286
|
+
const sourceSlug = slugify(toolData.tool);
|
|
287
|
+
const querySlug = slugify(toolData.query);
|
|
288
|
+
const cacheFileName = `${sourceSlug}-${querySlug}.json`;
|
|
289
|
+
const cachePath = join2(getAgentKnowledgeDir(agentSlug), "tool-cache", cacheFileName);
|
|
290
|
+
const cacheEntry = {
|
|
291
|
+
source: toolData.tool,
|
|
292
|
+
tool: toolData.tool,
|
|
293
|
+
query: toolData.query,
|
|
294
|
+
fetched: (/* @__PURE__ */ new Date()).toISOString(),
|
|
295
|
+
staleness: {
|
|
296
|
+
category: toolData.stalenessCategory || staleness.category,
|
|
297
|
+
maxAgeMinutes: staleness.maxAgeMinutes,
|
|
298
|
+
isStale: false
|
|
299
|
+
},
|
|
300
|
+
refreshHint: `Re-fetch ${toolData.tool} data for: ${toolData.query}`,
|
|
301
|
+
data: toolData.dataSummary
|
|
302
|
+
};
|
|
303
|
+
writeFileSync(cachePath, JSON.stringify(cacheEntry, null, 2), "utf-8");
|
|
304
|
+
agentIndex.knowledgeItems.push({
|
|
305
|
+
id: `tool-${sourceSlug}-${querySlug}`,
|
|
306
|
+
type: "tool_cache",
|
|
307
|
+
filePath: `tool-cache/${cacheFileName}`,
|
|
308
|
+
title: `${toolData.tool}: ${toolData.query}`,
|
|
309
|
+
summary: toolData.dataSummary.slice(0, 200),
|
|
310
|
+
tags: [toolData.tool],
|
|
311
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
312
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
313
|
+
staleness: {
|
|
314
|
+
category: staleness.category,
|
|
315
|
+
maxAgeMinutes: staleness.maxAgeMinutes,
|
|
316
|
+
lastFetched: (/* @__PURE__ */ new Date()).toISOString(),
|
|
317
|
+
isStale: false,
|
|
318
|
+
refreshHint: cacheEntry.refreshHint
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
appendToFeed(agentSlug, extraction.headline);
|
|
323
|
+
index.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
324
|
+
saveKnowledgeIndex(index);
|
|
325
|
+
regenerateReadme(agentSlug, index);
|
|
326
|
+
}
|
|
327
|
+
function buildSessionSummary(extraction, transcript) {
|
|
328
|
+
const lines = [];
|
|
329
|
+
const today = formatDate(/* @__PURE__ */ new Date());
|
|
330
|
+
lines.push(`# Session Summary \u2014 ${today}`);
|
|
331
|
+
lines.push("");
|
|
332
|
+
lines.push(`**Agent:** ${transcript.agentSlug}`);
|
|
333
|
+
lines.push(`**Messages:** ${transcript.messageCount} | **Tools:** ${transcript.toolsUsed.join(", ") || "none"}`);
|
|
334
|
+
lines.push(`**Model:** ${transcript.usage.model} | **Cost:** $${transcript.usage.cost.toFixed(4)} | **Duration:** ${Math.round(transcript.usage.durationMs / 1e3)}s`);
|
|
335
|
+
lines.push("");
|
|
336
|
+
lines.push(`## Summary`);
|
|
337
|
+
lines.push(extraction.sessionSummary);
|
|
338
|
+
lines.push("");
|
|
339
|
+
if (extraction.keyDecisions.length > 0) {
|
|
340
|
+
lines.push(`## Decisions Made`);
|
|
341
|
+
for (const d of extraction.keyDecisions) {
|
|
342
|
+
lines.push(`- **${d.decision}** \u2014 ${d.reasoning}`);
|
|
343
|
+
}
|
|
344
|
+
lines.push("");
|
|
345
|
+
}
|
|
346
|
+
if (extraction.openThreads.length > 0) {
|
|
347
|
+
lines.push(`## Open Threads`);
|
|
348
|
+
for (const t of extraction.openThreads) {
|
|
349
|
+
const urgencyLabel = t.urgency === "high" ? "HIGH" : t.urgency === "medium" ? "MED" : "LOW";
|
|
350
|
+
lines.push(`- [${urgencyLabel}] ${t.description}${t.deadline ? ` (deadline: ${t.deadline})` : ""}`);
|
|
351
|
+
}
|
|
352
|
+
lines.push("");
|
|
353
|
+
}
|
|
354
|
+
if (extraction.factsLearned.length > 0) {
|
|
355
|
+
lines.push(`## Facts Learned`);
|
|
356
|
+
for (const f of extraction.factsLearned) {
|
|
357
|
+
lines.push(`- [${f.category}] ${f.fact}`);
|
|
358
|
+
}
|
|
359
|
+
lines.push("");
|
|
360
|
+
}
|
|
361
|
+
if (extraction.topicTags.length > 0) {
|
|
362
|
+
lines.push(`**Tags:** ${extraction.topicTags.join(", ")}`);
|
|
363
|
+
}
|
|
364
|
+
return lines.join("\n");
|
|
365
|
+
}
|
|
366
|
+
function appendToFeed(agentSlug, headline) {
|
|
367
|
+
const feedPath = getFeedPath();
|
|
368
|
+
mkdirSync(getKnowledgeDir(), { recursive: true });
|
|
369
|
+
let existing = "";
|
|
370
|
+
if (existsSync2(feedPath)) {
|
|
371
|
+
existing = readFileSync2(feedPath, "utf-8");
|
|
372
|
+
}
|
|
373
|
+
const now = /* @__PURE__ */ new Date();
|
|
374
|
+
const timestamp = now.toISOString().replace("T", " ").slice(0, 16);
|
|
375
|
+
const entry = `[${timestamp}] ${agentSlug}: ${headline}
|
|
376
|
+
`;
|
|
377
|
+
writeFileSync(feedPath, entry + existing, "utf-8");
|
|
378
|
+
}
|
|
379
|
+
function regenerateReadme(agentSlug, index) {
|
|
380
|
+
if (!index) {
|
|
381
|
+
index = loadKnowledgeIndex();
|
|
382
|
+
}
|
|
383
|
+
if (!index) return;
|
|
384
|
+
const agentIndex = index.agents[agentSlug];
|
|
385
|
+
if (!agentIndex) return;
|
|
386
|
+
const agentDir = getAgentKnowledgeDir(agentSlug);
|
|
387
|
+
const today = /* @__PURE__ */ new Date();
|
|
388
|
+
const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
389
|
+
const dayName = days[today.getDay()];
|
|
390
|
+
const todayStr = formatDate(today);
|
|
391
|
+
const lines = [];
|
|
392
|
+
lines.push(`# ${agentSlug} \u2014 Knowledge Briefing`);
|
|
393
|
+
lines.push(
|
|
394
|
+
`**Today:** ${todayStr} (${dayName}) | **Last session:** ${formatDateWithRelative(agentIndex.lastSessionDate)} | **Total sessions:** ${agentIndex.sessionCount}`
|
|
395
|
+
);
|
|
396
|
+
lines.push("");
|
|
397
|
+
const sessionItems = agentIndex.knowledgeItems.filter((i) => i.type === "session_summary").sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
398
|
+
if (sessionItems.length > 0) {
|
|
399
|
+
lines.push(`## Active Context`);
|
|
400
|
+
const latest = sessionItems[0];
|
|
401
|
+
lines.push(`- Last session: ${latest.summary}`);
|
|
402
|
+
const recentItems = sessionItems.slice(0, 5);
|
|
403
|
+
const allThreads = [];
|
|
404
|
+
const seenThreads = /* @__PURE__ */ new Set();
|
|
405
|
+
const allFacts = [];
|
|
406
|
+
for (const item of recentItems) {
|
|
407
|
+
const itemPath = join2(agentDir, item.filePath);
|
|
408
|
+
if (!existsSync2(itemPath)) continue;
|
|
409
|
+
const content = readFileSync2(itemPath, "utf-8");
|
|
410
|
+
const threadStart = content.indexOf("\n## Open Threads\n");
|
|
411
|
+
if (threadStart !== -1) {
|
|
412
|
+
const afterHeader = content.indexOf("\n", threadStart + 1);
|
|
413
|
+
const nextSection = content.indexOf("\n## ", afterHeader + 1);
|
|
414
|
+
const tagsLine = content.indexOf("\n**Tags:", afterHeader + 1);
|
|
415
|
+
const threadEnd = Math.min(
|
|
416
|
+
nextSection === -1 ? content.length : nextSection,
|
|
417
|
+
tagsLine === -1 ? content.length : tagsLine
|
|
418
|
+
);
|
|
419
|
+
const threadBlock = content.slice(afterHeader + 1, threadEnd).trim();
|
|
420
|
+
for (const line of threadBlock.split("\n").filter((l) => l.startsWith("- "))) {
|
|
421
|
+
const key = line.toLowerCase().replace(/\s+/g, " ").trim();
|
|
422
|
+
if (!seenThreads.has(key)) {
|
|
423
|
+
seenThreads.add(key);
|
|
424
|
+
allThreads.push(line);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const factStart = content.indexOf("\n## Facts Learned\n");
|
|
429
|
+
if (factStart !== -1) {
|
|
430
|
+
const afterFactHeader = content.indexOf("\n", factStart + 1);
|
|
431
|
+
const nextFactSection = content.indexOf("\n## ", afterFactHeader + 1);
|
|
432
|
+
const factTagsLine = content.indexOf("\n**Tags:", afterFactHeader + 1);
|
|
433
|
+
const factEnd = Math.min(
|
|
434
|
+
nextFactSection === -1 ? content.length : nextFactSection,
|
|
435
|
+
factTagsLine === -1 ? content.length : factTagsLine
|
|
436
|
+
);
|
|
437
|
+
const factBlock = content.slice(afterFactHeader + 1, factEnd).trim();
|
|
438
|
+
for (const line of factBlock.split("\n").filter((l) => l.startsWith("- "))) {
|
|
439
|
+
allFacts.push(line);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (allThreads.length > 0) {
|
|
444
|
+
lines.push("");
|
|
445
|
+
lines.push("**Open threads:**");
|
|
446
|
+
for (const thread of allThreads.slice(0, 5)) {
|
|
447
|
+
lines.push(thread);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (allFacts.length > 0) {
|
|
451
|
+
lines.push("");
|
|
452
|
+
lines.push("**Key facts:**");
|
|
453
|
+
for (const fact of allFacts.slice(0, 5)) {
|
|
454
|
+
lines.push(fact);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
lines.push("");
|
|
458
|
+
}
|
|
459
|
+
const feedPath = getFeedPath();
|
|
460
|
+
if (existsSync2(feedPath)) {
|
|
461
|
+
const feed = readFileSync2(feedPath, "utf-8");
|
|
462
|
+
const feedLines = feed.split("\n").filter((l) => l.trim().length > 0);
|
|
463
|
+
if (feedLines.length > 0) {
|
|
464
|
+
lines.push(`## Recent Activity (from all agents)`);
|
|
465
|
+
for (const line of feedLines.slice(0, 5)) {
|
|
466
|
+
lines.push(`- ${line}`);
|
|
467
|
+
}
|
|
468
|
+
lines.push("");
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const toolItems = agentIndex.knowledgeItems.filter((i) => i.type === "tool_cache" && i.staleness);
|
|
472
|
+
if (toolItems.length > 0) {
|
|
473
|
+
lines.push(`## Tool Data Status`);
|
|
474
|
+
for (const item of toolItems) {
|
|
475
|
+
if (item.staleness) {
|
|
476
|
+
const stale = isStale(item.staleness.lastFetched, item.staleness.maxAgeMinutes);
|
|
477
|
+
const ageStr = formatStaleness(item.staleness.lastFetched);
|
|
478
|
+
const status = stale ? `STALE (last fetched ${ageStr})` : `Fresh (updated ${ageStr})`;
|
|
479
|
+
const hint = stale ? ` \u2014 ${item.staleness.refreshHint}` : "";
|
|
480
|
+
lines.push(`- ${item.title}: ${status}${hint}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
lines.push("");
|
|
484
|
+
}
|
|
485
|
+
lines.push(`## Deep Knowledge Available (use Read to access)`);
|
|
486
|
+
const knowledgePath = getAgentKnowledgeDir(agentSlug);
|
|
487
|
+
const sessionsDir = join2(knowledgePath, "sessions");
|
|
488
|
+
if (existsSync2(sessionsDir)) {
|
|
489
|
+
const sessionFiles = readdirSync(sessionsDir).filter((f) => f.endsWith(".md"));
|
|
490
|
+
lines.push(`- sessions/ \u2014 ${sessionFiles.length} session summaries`);
|
|
491
|
+
}
|
|
492
|
+
const refDir = join2(knowledgePath, "reference");
|
|
493
|
+
if (existsSync2(refDir)) {
|
|
494
|
+
const refFiles = readdirSync(refDir).filter((f) => f.endsWith(".md"));
|
|
495
|
+
for (const file of refFiles) {
|
|
496
|
+
const item = agentIndex.knowledgeItems.find(
|
|
497
|
+
(i) => i.filePath === `reference/${file}`
|
|
498
|
+
);
|
|
499
|
+
const updated = item ? ` \u2014 last updated ${formatDateWithRelative(item.updatedAt)}` : "";
|
|
500
|
+
lines.push(`- reference/${file}${updated}`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const decisionsPath = join2(knowledgePath, "decisions.md");
|
|
504
|
+
if (existsSync2(decisionsPath)) {
|
|
505
|
+
const content = readFileSync2(decisionsPath, "utf-8");
|
|
506
|
+
const decisionCount = (content.match(/^## /gm) || []).length;
|
|
507
|
+
lines.push(`- decisions.md \u2014 ${decisionCount} tracked decisions`);
|
|
508
|
+
}
|
|
509
|
+
lines.push("");
|
|
510
|
+
lines.push(`## How to Access Knowledge`);
|
|
511
|
+
lines.push(`Your knowledge library is at ${knowledgePath}/`);
|
|
512
|
+
lines.push(` sessions/ \u2014 summaries of your past sessions`);
|
|
513
|
+
lines.push(` reference/ \u2014 long-lived reference docs`);
|
|
514
|
+
lines.push(` tool-cache/ \u2014 cached tool data (check staleness dates)`);
|
|
515
|
+
lines.push(` decisions.md \u2014 running log of key decisions`);
|
|
516
|
+
lines.push(`Use Read to access any file. Use Grep to search across all agents' knowledge at ${getKnowledgeDir()}/`);
|
|
517
|
+
lines.push("");
|
|
518
|
+
writeFileSync(join2(agentDir, "README.md"), lines.join("\n"), "utf-8");
|
|
519
|
+
}
|
|
520
|
+
function loadQueue() {
|
|
521
|
+
const queuePath = getQueuePath();
|
|
522
|
+
if (!existsSync2(queuePath)) return { entries: [] };
|
|
523
|
+
try {
|
|
524
|
+
return JSON.parse(readFileSync2(queuePath, "utf-8"));
|
|
525
|
+
} catch {
|
|
526
|
+
return { entries: [] };
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
function saveQueue(queue) {
|
|
530
|
+
mkdirSync(getKnowledgeDir(), { recursive: true });
|
|
531
|
+
writeFileSync(getQueuePath(), JSON.stringify(queue, null, 2), "utf-8");
|
|
532
|
+
}
|
|
533
|
+
function enqueueTranscript(agentSlug, conversationId, transcriptPath) {
|
|
534
|
+
const queue = loadQueue();
|
|
535
|
+
queue.entries.push({
|
|
536
|
+
agentSlug,
|
|
537
|
+
conversationId,
|
|
538
|
+
transcriptPath,
|
|
539
|
+
enqueuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
540
|
+
status: "pending"
|
|
541
|
+
});
|
|
542
|
+
saveQueue(queue);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/memory/pipeline/extract.ts
|
|
546
|
+
async function extractFromTranscript(transcript) {
|
|
547
|
+
const transcriptText = transcript.entries.map((entry) => {
|
|
548
|
+
switch (entry.type) {
|
|
549
|
+
case "user_message":
|
|
550
|
+
return `[USER]: ${entry.content}`;
|
|
551
|
+
case "assistant_text":
|
|
552
|
+
return `[ASSISTANT]: ${entry.content}`;
|
|
553
|
+
case "tool_call":
|
|
554
|
+
return `[TOOL CALL: ${entry.metadata?.toolName}]: ${entry.content}`;
|
|
555
|
+
case "tool_result":
|
|
556
|
+
return `[TOOL RESULT: ${entry.metadata?.toolName} (${entry.metadata?.status})]: ${entry.content}`;
|
|
557
|
+
default:
|
|
558
|
+
return entry.content;
|
|
559
|
+
}
|
|
560
|
+
}).join("\n\n");
|
|
561
|
+
const existingIndex = loadKnowledgeIndex();
|
|
562
|
+
const prompt = buildExtractionPrompt(
|
|
563
|
+
transcriptText,
|
|
564
|
+
transcript.agentSlug,
|
|
565
|
+
existingIndex
|
|
566
|
+
);
|
|
567
|
+
const response = await callOpenRouter([
|
|
568
|
+
{ role: "user", content: prompt }
|
|
569
|
+
], {
|
|
570
|
+
maxTokens: 4096,
|
|
571
|
+
temperature: 0.2
|
|
572
|
+
});
|
|
573
|
+
return parseExtractionResponse(response);
|
|
574
|
+
}
|
|
575
|
+
function parseExtractionResponse(response) {
|
|
576
|
+
let cleaned = response.trim();
|
|
577
|
+
if (cleaned.startsWith("```")) {
|
|
578
|
+
cleaned = cleaned.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
|
|
579
|
+
}
|
|
580
|
+
try {
|
|
581
|
+
const parsed = JSON.parse(cleaned);
|
|
582
|
+
return {
|
|
583
|
+
sessionSummary: parsed.session_summary || "No summary extracted.",
|
|
584
|
+
headline: parsed.headline || "Session completed",
|
|
585
|
+
keyDecisions: (parsed.key_decisions || []).map((d) => ({
|
|
586
|
+
decision: d.decision || "",
|
|
587
|
+
reasoning: d.reasoning || "",
|
|
588
|
+
date: d.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
589
|
+
})),
|
|
590
|
+
factsLearned: (parsed.facts_learned || []).map((f) => ({
|
|
591
|
+
fact: f.fact || "",
|
|
592
|
+
category: f.category || "general"
|
|
593
|
+
})),
|
|
594
|
+
openThreads: (parsed.open_threads || []).map((t) => ({
|
|
595
|
+
description: t.description || "",
|
|
596
|
+
urgency: t.urgency || "medium",
|
|
597
|
+
deadline: t.deadline || void 0
|
|
598
|
+
})),
|
|
599
|
+
toolData: (parsed.tool_data || []).map((t) => ({
|
|
600
|
+
tool: t.tool || "",
|
|
601
|
+
query: t.query || "",
|
|
602
|
+
dataSummary: t.data_summary || "",
|
|
603
|
+
stalenessCategory: t.staleness_category || "semi_dynamic"
|
|
604
|
+
})),
|
|
605
|
+
upsertTargets: (parsed.upsert_targets || []).map((u) => ({
|
|
606
|
+
existingFileSlug: u.existing_file_slug || "",
|
|
607
|
+
updateSummary: u.update_summary || ""
|
|
608
|
+
})),
|
|
609
|
+
topicTags: parsed.topic_tags || []
|
|
610
|
+
};
|
|
611
|
+
} catch {
|
|
612
|
+
return {
|
|
613
|
+
sessionSummary: response.slice(0, 500),
|
|
614
|
+
headline: "Session completed (extraction failed)",
|
|
615
|
+
keyDecisions: [],
|
|
616
|
+
factsLearned: [],
|
|
617
|
+
openThreads: [],
|
|
618
|
+
toolData: [],
|
|
619
|
+
upsertTargets: [],
|
|
620
|
+
topicTags: []
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// src/memory/pipeline/maintain.ts
|
|
626
|
+
import {
|
|
627
|
+
readFileSync as readFileSync3,
|
|
628
|
+
writeFileSync as writeFileSync2,
|
|
629
|
+
existsSync as existsSync3,
|
|
630
|
+
readdirSync as readdirSync2,
|
|
631
|
+
unlinkSync
|
|
632
|
+
} from "fs";
|
|
633
|
+
import { join as join3 } from "path";
|
|
634
|
+
function runMaintenance() {
|
|
635
|
+
let staleMarked = 0;
|
|
636
|
+
let readmesRegenerated = 0;
|
|
637
|
+
let transcriptsDeleted = 0;
|
|
638
|
+
const index = loadKnowledgeIndex();
|
|
639
|
+
if (!index) return { staleMarked, readmesRegenerated, transcriptsDeleted };
|
|
640
|
+
for (const [slug, agentData] of Object.entries(index.agents)) {
|
|
641
|
+
for (const item of agentData.knowledgeItems) {
|
|
642
|
+
if (item.type === "tool_cache" && item.staleness) {
|
|
643
|
+
const wasStale = item.staleness.isStale;
|
|
644
|
+
item.staleness.isStale = isStale(
|
|
645
|
+
item.staleness.lastFetched,
|
|
646
|
+
item.staleness.maxAgeMinutes
|
|
647
|
+
);
|
|
648
|
+
if (!wasStale && item.staleness.isStale) {
|
|
649
|
+
staleMarked++;
|
|
650
|
+
}
|
|
651
|
+
const cachePath = join3(getKnowledgeDir(), slug, item.filePath);
|
|
652
|
+
if (existsSync3(cachePath)) {
|
|
653
|
+
try {
|
|
654
|
+
const cacheEntry = JSON.parse(
|
|
655
|
+
readFileSync3(cachePath, "utf-8")
|
|
656
|
+
);
|
|
657
|
+
cacheEntry.staleness.isStale = item.staleness.isStale;
|
|
658
|
+
writeFileSync2(cachePath, JSON.stringify(cacheEntry, null, 2), "utf-8");
|
|
659
|
+
} catch {
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
index.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
666
|
+
const indexPath = join3(getKnowledgeDir(), "_index.json");
|
|
667
|
+
writeFileSync2(indexPath, JSON.stringify(index, null, 2), "utf-8");
|
|
668
|
+
for (const slug of Object.keys(index.agents)) {
|
|
669
|
+
regenerateReadme(slug, index);
|
|
670
|
+
readmesRegenerated++;
|
|
671
|
+
}
|
|
672
|
+
const transcriptsDir = getTranscriptsDir();
|
|
673
|
+
if (existsSync3(transcriptsDir)) {
|
|
674
|
+
const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1e3;
|
|
675
|
+
try {
|
|
676
|
+
const agentDirs = readdirSync2(transcriptsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
677
|
+
for (const agentDir of agentDirs) {
|
|
678
|
+
const agentTranscriptsPath = join3(transcriptsDir, agentDir);
|
|
679
|
+
const files = readdirSync2(agentTranscriptsPath).filter(
|
|
680
|
+
(f) => f.endsWith(".json")
|
|
681
|
+
);
|
|
682
|
+
for (const file of files) {
|
|
683
|
+
const filePath = join3(agentTranscriptsPath, file);
|
|
684
|
+
try {
|
|
685
|
+
const transcript = JSON.parse(readFileSync3(filePath, "utf-8"));
|
|
686
|
+
const startedAt = new Date(transcript.startedAt).getTime();
|
|
687
|
+
if (startedAt < thirtyDaysAgo) {
|
|
688
|
+
unlinkSync(filePath);
|
|
689
|
+
transcriptsDeleted++;
|
|
690
|
+
}
|
|
691
|
+
} catch {
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
} catch {
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return { staleMarked, readmesRegenerated, transcriptsDeleted };
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/memory/index.ts
|
|
702
|
+
function saveTranscript(transcript) {
|
|
703
|
+
const transcriptsDir = join4(
|
|
704
|
+
getTranscriptsDir(),
|
|
705
|
+
transcript.agentSlug
|
|
706
|
+
);
|
|
707
|
+
mkdirSync2(transcriptsDir, { recursive: true });
|
|
708
|
+
const filePath = join4(transcriptsDir, `${transcript.conversationId}.json`);
|
|
709
|
+
writeFileSync3(filePath, JSON.stringify(transcript, null, 2), "utf-8");
|
|
710
|
+
enqueueTranscript(
|
|
711
|
+
transcript.agentSlug,
|
|
712
|
+
transcript.conversationId,
|
|
713
|
+
filePath
|
|
714
|
+
);
|
|
715
|
+
return filePath;
|
|
716
|
+
}
|
|
717
|
+
async function processTranscript(transcript) {
|
|
718
|
+
const extraction = await extractFromTranscript(transcript);
|
|
719
|
+
organizeKnowledge(extraction, transcript);
|
|
720
|
+
}
|
|
721
|
+
async function processQueue() {
|
|
722
|
+
if (!isMemoryAgentConfigured()) {
|
|
723
|
+
return { processed: 0, failed: 0, skipped: 0 };
|
|
724
|
+
}
|
|
725
|
+
const queue = loadQueue();
|
|
726
|
+
let processed = 0;
|
|
727
|
+
let failed = 0;
|
|
728
|
+
let skipped = 0;
|
|
729
|
+
for (const entry of queue.entries) {
|
|
730
|
+
if (entry.status !== "pending") {
|
|
731
|
+
skipped++;
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
entry.status = "processing";
|
|
735
|
+
saveQueue(queue);
|
|
736
|
+
try {
|
|
737
|
+
if (!existsSync4(entry.transcriptPath)) {
|
|
738
|
+
entry.status = "failed";
|
|
739
|
+
entry.error = "Transcript file not found";
|
|
740
|
+
failed++;
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
const transcript = JSON.parse(
|
|
744
|
+
readFileSync4(entry.transcriptPath, "utf-8")
|
|
745
|
+
);
|
|
746
|
+
await processTranscript(transcript);
|
|
747
|
+
entry.status = "completed";
|
|
748
|
+
processed++;
|
|
749
|
+
} catch (err) {
|
|
750
|
+
entry.status = "failed";
|
|
751
|
+
entry.error = err instanceof Error ? err.message : String(err);
|
|
752
|
+
failed++;
|
|
753
|
+
}
|
|
754
|
+
saveQueue(queue);
|
|
755
|
+
}
|
|
756
|
+
const oneDayAgo = Date.now() - 24 * 60 * 60 * 1e3;
|
|
757
|
+
queue.entries = queue.entries.filter(
|
|
758
|
+
(e) => e.status !== "completed" || new Date(e.enqueuedAt).getTime() > oneDayAgo
|
|
759
|
+
);
|
|
760
|
+
saveQueue(queue);
|
|
761
|
+
return { processed, failed, skipped };
|
|
762
|
+
}
|
|
763
|
+
function getMemoryStatus() {
|
|
764
|
+
const configured = isMemoryAgentConfigured();
|
|
765
|
+
let model;
|
|
766
|
+
const configPath = join4(homedir3(), ".claude", "forge", "config.json");
|
|
767
|
+
if (existsSync4(configPath)) {
|
|
768
|
+
try {
|
|
769
|
+
const config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
770
|
+
model = config.memoryAgent?.model;
|
|
771
|
+
} catch {
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const queue = loadQueue();
|
|
775
|
+
const queueDepth = queue.entries.filter((e) => e.status === "pending").length;
|
|
776
|
+
const agentStats = [];
|
|
777
|
+
const indexPath = join4(getKnowledgeDir(), "_index.json");
|
|
778
|
+
if (existsSync4(indexPath)) {
|
|
779
|
+
try {
|
|
780
|
+
const index = JSON.parse(readFileSync4(indexPath, "utf-8"));
|
|
781
|
+
for (const [slug, data] of Object.entries(index.agents || {})) {
|
|
782
|
+
const agentData = data;
|
|
783
|
+
agentStats.push({
|
|
784
|
+
slug,
|
|
785
|
+
sessionCount: agentData.sessionCount || 0,
|
|
786
|
+
knowledgeItems: agentData.knowledgeItems?.length || 0,
|
|
787
|
+
lastProcessed: agentData.lastSessionDate
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
} catch {
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return { configured, model, queueDepth, agentStats };
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
export {
|
|
797
|
+
getKnowledgeDir,
|
|
798
|
+
getTranscriptsDir,
|
|
799
|
+
enqueueTranscript,
|
|
800
|
+
runMaintenance,
|
|
801
|
+
saveTranscript,
|
|
802
|
+
processTranscript,
|
|
803
|
+
processQueue,
|
|
804
|
+
getMemoryStatus
|
|
805
|
+
};
|