@virtengine/openfleet 0.25.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/.env.example +914 -0
- package/LICENSE +190 -0
- package/README.md +500 -0
- package/agent-endpoint.mjs +918 -0
- package/agent-hook-bridge.mjs +230 -0
- package/agent-hooks.mjs +1188 -0
- package/agent-pool.mjs +2403 -0
- package/agent-prompts.mjs +689 -0
- package/agent-sdk.mjs +141 -0
- package/anomaly-detector.mjs +1195 -0
- package/autofix.mjs +1294 -0
- package/claude-shell.mjs +708 -0
- package/cli.mjs +906 -0
- package/codex-config.mjs +1274 -0
- package/codex-model-profiles.mjs +135 -0
- package/codex-shell.mjs +762 -0
- package/config-doctor.mjs +613 -0
- package/config.mjs +1720 -0
- package/conflict-resolver.mjs +248 -0
- package/container-runner.mjs +450 -0
- package/copilot-shell.mjs +827 -0
- package/daemon-restart-policy.mjs +56 -0
- package/diff-stats.mjs +282 -0
- package/error-detector.mjs +829 -0
- package/fetch-runtime.mjs +34 -0
- package/fleet-coordinator.mjs +838 -0
- package/get-telegram-chat-id.mjs +71 -0
- package/git-safety.mjs +170 -0
- package/github-reconciler.mjs +403 -0
- package/hook-profiles.mjs +651 -0
- package/kanban-adapter.mjs +4491 -0
- package/lib/logger.mjs +645 -0
- package/maintenance.mjs +828 -0
- package/merge-strategy.mjs +1171 -0
- package/monitor.mjs +12207 -0
- package/openfleet.config.example.json +115 -0
- package/openfleet.schema.json +465 -0
- package/package.json +203 -0
- package/postinstall.mjs +187 -0
- package/pr-cleanup-daemon.mjs +978 -0
- package/preflight.mjs +408 -0
- package/prepublish-check.mjs +90 -0
- package/presence.mjs +328 -0
- package/primary-agent.mjs +282 -0
- package/publish.mjs +151 -0
- package/repo-root.mjs +29 -0
- package/restart-controller.mjs +100 -0
- package/review-agent.mjs +557 -0
- package/rotate-agent-logs.sh +133 -0
- package/sdk-conflict-resolver.mjs +973 -0
- package/session-tracker.mjs +880 -0
- package/setup.mjs +3937 -0
- package/shared-knowledge.mjs +410 -0
- package/shared-state-manager.mjs +841 -0
- package/shared-workspace-cli.mjs +199 -0
- package/shared-workspace-registry.mjs +537 -0
- package/shared-workspaces.json +18 -0
- package/startup-service.mjs +1070 -0
- package/sync-engine.mjs +1063 -0
- package/task-archiver.mjs +801 -0
- package/task-assessment.mjs +550 -0
- package/task-claims.mjs +924 -0
- package/task-complexity.mjs +581 -0
- package/task-executor.mjs +5111 -0
- package/task-store.mjs +753 -0
- package/telegram-bot.mjs +9281 -0
- package/telegram-sentinel.mjs +2010 -0
- package/ui/app.js +867 -0
- package/ui/app.legacy.js +1464 -0
- package/ui/app.monolith.js +2488 -0
- package/ui/components/charts.js +226 -0
- package/ui/components/chat-view.js +567 -0
- package/ui/components/command-palette.js +587 -0
- package/ui/components/diff-viewer.js +190 -0
- package/ui/components/forms.js +327 -0
- package/ui/components/kanban-board.js +451 -0
- package/ui/components/session-list.js +305 -0
- package/ui/components/shared.js +473 -0
- package/ui/index.html +70 -0
- package/ui/modules/api.js +297 -0
- package/ui/modules/icons.js +461 -0
- package/ui/modules/router.js +81 -0
- package/ui/modules/settings-schema.js +261 -0
- package/ui/modules/state.js +679 -0
- package/ui/modules/telegram.js +331 -0
- package/ui/modules/utils.js +270 -0
- package/ui/styles/animations.css +140 -0
- package/ui/styles/base.css +98 -0
- package/ui/styles/components.css +1915 -0
- package/ui/styles/kanban.css +286 -0
- package/ui/styles/layout.css +809 -0
- package/ui/styles/sessions.css +827 -0
- package/ui/styles/variables.css +188 -0
- package/ui/styles.css +141 -0
- package/ui/styles.monolith.css +1046 -0
- package/ui/tabs/agents.js +1417 -0
- package/ui/tabs/chat.js +74 -0
- package/ui/tabs/control.js +887 -0
- package/ui/tabs/dashboard.js +515 -0
- package/ui/tabs/infra.js +537 -0
- package/ui/tabs/logs.js +783 -0
- package/ui/tabs/settings.js +1487 -0
- package/ui/tabs/tasks.js +1385 -0
- package/ui-server.mjs +4073 -0
- package/update-check.mjs +465 -0
- package/utils.mjs +172 -0
- package/ve-kanban.mjs +654 -0
- package/ve-kanban.ps1 +1365 -0
- package/ve-kanban.sh +18 -0
- package/ve-orchestrator.mjs +340 -0
- package/ve-orchestrator.ps1 +6546 -0
- package/ve-orchestrator.sh +18 -0
- package/vibe-kanban-wrapper.mjs +41 -0
- package/vk-error-resolver.mjs +470 -0
- package/vk-log-stream.mjs +914 -0
- package/whatsapp-channel.mjs +520 -0
- package/workspace-monitor.mjs +581 -0
- package/workspace-reaper.mjs +405 -0
- package/workspace-registry.mjs +238 -0
- package/worktree-manager.mjs +1266 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* shared-knowledge.mjs — Agent-to-agent knowledge sharing for openfleet.
|
|
3
|
+
*
|
|
4
|
+
* Allows agents across the fleet to contribute lessons learned, patterns,
|
|
5
|
+
* and critical findings to a shared knowledge base (AGENTS.md or a
|
|
6
|
+
* designated knowledge file).
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Append-only knowledge entries with dedup
|
|
10
|
+
* - Structured entry format with metadata (agent, timestamp, scope)
|
|
11
|
+
* - Git-conflict-safe appending (append to dedicated section)
|
|
12
|
+
* - Rate limiting to prevent spam
|
|
13
|
+
* - Entry validation before write
|
|
14
|
+
*
|
|
15
|
+
* Knowledge entries are appended to a `## Agent Learnings` section at the
|
|
16
|
+
* bottom of the target file (default: AGENTS.md).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
20
|
+
import { existsSync } from "node:fs";
|
|
21
|
+
import { resolve, basename } from "node:path";
|
|
22
|
+
import crypto from "node:crypto";
|
|
23
|
+
|
|
24
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const DEFAULT_SECTION_HEADER = "## Agent Learnings";
|
|
27
|
+
const DEFAULT_TARGET_FILE = "AGENTS.md";
|
|
28
|
+
const ENTRY_SEPARATOR = "\n---\n";
|
|
29
|
+
const MAX_ENTRY_LENGTH = 2000; // chars
|
|
30
|
+
const MIN_ENTRY_LENGTH = 20; // chars
|
|
31
|
+
const RATE_LIMIT_MS = 30_000; // 30s between entries from same agent
|
|
32
|
+
|
|
33
|
+
// ── State ────────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
const knowledgeState = {
|
|
36
|
+
repoRoot: null,
|
|
37
|
+
targetFile: DEFAULT_TARGET_FILE,
|
|
38
|
+
sectionHeader: DEFAULT_SECTION_HEADER,
|
|
39
|
+
entriesWritten: 0,
|
|
40
|
+
lastWriteAt: null,
|
|
41
|
+
entryHashes: new Set(), // dedup
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// ── Initialization ───────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Initialize the shared knowledge system.
|
|
48
|
+
*
|
|
49
|
+
* @param {object} opts
|
|
50
|
+
* @param {string} opts.repoRoot - Git repository root
|
|
51
|
+
* @param {string} [opts.targetFile] - File to append to (default: AGENTS.md)
|
|
52
|
+
* @param {string} [opts.sectionHeader] - Markdown section header for learnings
|
|
53
|
+
*/
|
|
54
|
+
export function initSharedKnowledge(opts = {}) {
|
|
55
|
+
knowledgeState.repoRoot = opts.repoRoot || process.cwd();
|
|
56
|
+
knowledgeState.targetFile = opts.targetFile || DEFAULT_TARGET_FILE;
|
|
57
|
+
knowledgeState.sectionHeader = opts.sectionHeader || DEFAULT_SECTION_HEADER;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Entry Format ─────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Build a knowledge entry object.
|
|
64
|
+
*
|
|
65
|
+
* @param {object} opts
|
|
66
|
+
* @param {string} opts.content - The actual learning / insight
|
|
67
|
+
* @param {string} [opts.scope] - Area of the codebase (e.g., "veid", "market")
|
|
68
|
+
* @param {string} [opts.agentId] - Instance/agent identifier
|
|
69
|
+
* @param {string} [opts.agentType] - "codex" | "copilot" | "human"
|
|
70
|
+
* @param {string} [opts.category] - "pattern" | "gotcha" | "perf" | "security" | "convention"
|
|
71
|
+
* @param {string} [opts.taskRef] - VK task ID or branch name reference
|
|
72
|
+
* @returns {object} entry
|
|
73
|
+
*/
|
|
74
|
+
export function buildKnowledgeEntry(opts = {}) {
|
|
75
|
+
const {
|
|
76
|
+
content,
|
|
77
|
+
scope = null,
|
|
78
|
+
agentId = "unknown",
|
|
79
|
+
agentType = "codex",
|
|
80
|
+
category = "pattern",
|
|
81
|
+
taskRef = null,
|
|
82
|
+
} = opts;
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
content: String(content || "").trim(),
|
|
86
|
+
scope,
|
|
87
|
+
agentId,
|
|
88
|
+
agentType,
|
|
89
|
+
category,
|
|
90
|
+
taskRef,
|
|
91
|
+
timestamp: new Date().toISOString(),
|
|
92
|
+
hash: hashEntry(content, scope),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Format a knowledge entry as Markdown for appending to file.
|
|
98
|
+
*/
|
|
99
|
+
export function formatEntryAsMarkdown(entry) {
|
|
100
|
+
const lines = [];
|
|
101
|
+
const datePart =
|
|
102
|
+
entry.timestamp?.split("T")[0] || new Date().toISOString().split("T")[0];
|
|
103
|
+
const scopePart = entry.scope ? ` (${entry.scope})` : "";
|
|
104
|
+
const catPart = entry.category ? `[${entry.category}]` : "";
|
|
105
|
+
const taskPart = entry.taskRef ? ` • ref: \`${entry.taskRef}\`` : "";
|
|
106
|
+
|
|
107
|
+
lines.push(`### ${catPart}${scopePart} — ${datePart}${taskPart}`);
|
|
108
|
+
lines.push("");
|
|
109
|
+
lines.push(`> **Agent:** ${entry.agentId} (${entry.agentType})`);
|
|
110
|
+
lines.push("");
|
|
111
|
+
lines.push(entry.content);
|
|
112
|
+
lines.push("");
|
|
113
|
+
|
|
114
|
+
return lines.join("\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Validation ───────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Validate a knowledge entry before writing.
|
|
121
|
+
* Returns { valid: boolean, reason?: string }.
|
|
122
|
+
*/
|
|
123
|
+
export function validateEntry(entry) {
|
|
124
|
+
if (!entry || typeof entry !== "object") {
|
|
125
|
+
return { valid: false, reason: "entry must be an object" };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const content = String(entry.content || "").trim();
|
|
129
|
+
if (content.length < MIN_ENTRY_LENGTH) {
|
|
130
|
+
return {
|
|
131
|
+
valid: false,
|
|
132
|
+
reason: `content too short (min ${MIN_ENTRY_LENGTH} chars)`,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (content.length > MAX_ENTRY_LENGTH) {
|
|
136
|
+
return {
|
|
137
|
+
valid: false,
|
|
138
|
+
reason: `content too long (max ${MAX_ENTRY_LENGTH} chars)`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Check for obviously low-value entries
|
|
143
|
+
const lowValuePatterns = [
|
|
144
|
+
/^(ok|done|yes|no|maybe|test|todo|fixme|hack)$/i,
|
|
145
|
+
/^[^a-zA-Z]*$/, // no letters at all
|
|
146
|
+
/(.)\1{20,}/, // 20+ repeated chars
|
|
147
|
+
];
|
|
148
|
+
for (const pat of lowValuePatterns) {
|
|
149
|
+
if (pat.test(content)) {
|
|
150
|
+
return { valid: false, reason: "entry appears to be low-value or noise" };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Validate category
|
|
155
|
+
const validCategories = [
|
|
156
|
+
"pattern",
|
|
157
|
+
"gotcha",
|
|
158
|
+
"perf",
|
|
159
|
+
"security",
|
|
160
|
+
"convention",
|
|
161
|
+
"tip",
|
|
162
|
+
"bug",
|
|
163
|
+
];
|
|
164
|
+
if (entry.category && !validCategories.includes(entry.category)) {
|
|
165
|
+
return {
|
|
166
|
+
valid: false,
|
|
167
|
+
reason: `invalid category — must be one of: ${validCategories.join(", ")}`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { valid: true };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── Deduplication ────────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
function hashEntry(content, scope) {
|
|
177
|
+
const data = `${scope || ""}|${String(content || "")
|
|
178
|
+
.trim()
|
|
179
|
+
.toLowerCase()}`;
|
|
180
|
+
return crypto.createHash("sha256").update(data).digest("hex").slice(0, 16);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check if an entry with this content already exists (in-memory dedup).
|
|
185
|
+
*/
|
|
186
|
+
export function isDuplicate(entry) {
|
|
187
|
+
return knowledgeState.entryHashes.has(entry.hash);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Load existing entry hashes from the target file for dedup.
|
|
192
|
+
*/
|
|
193
|
+
async function loadExistingHashes() {
|
|
194
|
+
const filePath = resolve(knowledgeState.repoRoot, knowledgeState.targetFile);
|
|
195
|
+
if (!existsSync(filePath)) return;
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const content = await readFile(filePath, "utf8");
|
|
199
|
+
// Extract content blocks from Agent Learnings section
|
|
200
|
+
const sectionIdx = content.indexOf(knowledgeState.sectionHeader);
|
|
201
|
+
if (sectionIdx === -1) return;
|
|
202
|
+
|
|
203
|
+
const sectionContent = content.slice(sectionIdx);
|
|
204
|
+
// Parse each learning entry (between ### headers)
|
|
205
|
+
const entries = sectionContent.split(/^### /m).slice(1);
|
|
206
|
+
for (const block of entries) {
|
|
207
|
+
// Extract the content after the metadata lines
|
|
208
|
+
const lines = block.split("\n");
|
|
209
|
+
const contentLines = lines.filter(
|
|
210
|
+
(l) =>
|
|
211
|
+
!l.startsWith(">") &&
|
|
212
|
+
!l.startsWith("###") &&
|
|
213
|
+
!l.startsWith("---") &&
|
|
214
|
+
l.trim().length > 0,
|
|
215
|
+
);
|
|
216
|
+
const entryContent = contentLines.join(" ").trim();
|
|
217
|
+
if (entryContent) {
|
|
218
|
+
// Extract scope from header if present
|
|
219
|
+
const scopeMatch = lines[0]?.match(/\(([^)]+)\)/);
|
|
220
|
+
const scope = scopeMatch?.[1] || null;
|
|
221
|
+
const hash = hashEntry(entryContent, scope);
|
|
222
|
+
knowledgeState.entryHashes.add(hash);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
// file read error — skip
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── Write ────────────────────────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Append a knowledge entry to the target file.
|
|
234
|
+
*
|
|
235
|
+
* @param {object} entry - From buildKnowledgeEntry()
|
|
236
|
+
* @returns {object} { success: boolean, reason?: string }
|
|
237
|
+
*/
|
|
238
|
+
export async function appendKnowledgeEntry(entry) {
|
|
239
|
+
// Validate
|
|
240
|
+
const validation = validateEntry(entry);
|
|
241
|
+
if (!validation.valid) {
|
|
242
|
+
return { success: false, reason: validation.reason };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Rate limit
|
|
246
|
+
if (knowledgeState.lastWriteAt) {
|
|
247
|
+
const elapsed = Date.now() - knowledgeState.lastWriteAt;
|
|
248
|
+
if (elapsed < RATE_LIMIT_MS) {
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
reason: `rate limited — wait ${Math.ceil((RATE_LIMIT_MS - elapsed) / 1000)}s`,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Dedup check
|
|
257
|
+
await loadExistingHashes();
|
|
258
|
+
if (isDuplicate(entry)) {
|
|
259
|
+
return { success: false, reason: "duplicate entry — already recorded" };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Format
|
|
263
|
+
const markdown = formatEntryAsMarkdown(entry);
|
|
264
|
+
|
|
265
|
+
// Append to file
|
|
266
|
+
const filePath = resolve(knowledgeState.repoRoot, knowledgeState.targetFile);
|
|
267
|
+
try {
|
|
268
|
+
let content = "";
|
|
269
|
+
if (existsSync(filePath)) {
|
|
270
|
+
content = await readFile(filePath, "utf8");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Find or create the section
|
|
274
|
+
const sectionIdx = content.indexOf(knowledgeState.sectionHeader);
|
|
275
|
+
if (sectionIdx === -1) {
|
|
276
|
+
// Append section at end of file
|
|
277
|
+
const newContent =
|
|
278
|
+
content.trimEnd() +
|
|
279
|
+
"\n\n" +
|
|
280
|
+
knowledgeState.sectionHeader +
|
|
281
|
+
"\n\n" +
|
|
282
|
+
markdown +
|
|
283
|
+
ENTRY_SEPARATOR;
|
|
284
|
+
await writeFile(filePath, newContent, "utf8");
|
|
285
|
+
} else {
|
|
286
|
+
// Append at end of existing section (before any next ## header or EOF)
|
|
287
|
+
|
|
288
|
+
const afterSection = content.slice(
|
|
289
|
+
sectionIdx + knowledgeState.sectionHeader.length,
|
|
290
|
+
);
|
|
291
|
+
// Find next top-level heading (## but not ###)
|
|
292
|
+
const nextSectionMatch = afterSection.match(/\n## [^#]/);
|
|
293
|
+
if (nextSectionMatch) {
|
|
294
|
+
const insertPos =
|
|
295
|
+
|
|
296
|
+
sectionIdx +
|
|
297
|
+
knowledgeState.sectionHeader.length +
|
|
298
|
+
nextSectionMatch.index;
|
|
299
|
+
const before = content.slice(0, insertPos);
|
|
300
|
+
const after = content.slice(insertPos);
|
|
301
|
+
await writeFile(
|
|
302
|
+
filePath,
|
|
303
|
+
before + "\n" + markdown + ENTRY_SEPARATOR + after,
|
|
304
|
+
"utf8",
|
|
305
|
+
);
|
|
306
|
+
} else {
|
|
307
|
+
// Append at end of file
|
|
308
|
+
await writeFile(
|
|
309
|
+
filePath,
|
|
310
|
+
content.trimEnd() + "\n\n" + markdown + ENTRY_SEPARATOR,
|
|
311
|
+
"utf8",
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Track
|
|
317
|
+
knowledgeState.entryHashes.add(entry.hash);
|
|
318
|
+
knowledgeState.entriesWritten++;
|
|
319
|
+
knowledgeState.lastWriteAt = Date.now();
|
|
320
|
+
|
|
321
|
+
return { success: true, hash: entry.hash };
|
|
322
|
+
} catch (err) {
|
|
323
|
+
return { success: false, reason: `write error: ${err.message}` };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── Read ─────────────────────────────────────────────────────────────────────
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Read all knowledge entries from the target file.
|
|
331
|
+
* Returns structured entries, not raw markdown.
|
|
332
|
+
*/
|
|
333
|
+
export async function readKnowledgeEntries() {
|
|
334
|
+
const filePath = resolve(knowledgeState.repoRoot, knowledgeState.targetFile);
|
|
335
|
+
if (!existsSync(filePath)) return [];
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const content = await readFile(filePath, "utf8");
|
|
339
|
+
const sectionIdx = content.indexOf(knowledgeState.sectionHeader);
|
|
340
|
+
if (sectionIdx === -1) return [];
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
const sectionContent = content.slice(
|
|
344
|
+
sectionIdx + knowledgeState.sectionHeader.length,
|
|
345
|
+
);
|
|
346
|
+
// Find next top-level heading
|
|
347
|
+
const nextSectionMatch = sectionContent.match(/\n## [^#]/);
|
|
348
|
+
const relevantContent = nextSectionMatch
|
|
349
|
+
? sectionContent.slice(0, nextSectionMatch.index)
|
|
350
|
+
: sectionContent;
|
|
351
|
+
|
|
352
|
+
const blocks = relevantContent.split(/^### /m).slice(1);
|
|
353
|
+
const entries = [];
|
|
354
|
+
|
|
355
|
+
for (const block of blocks) {
|
|
356
|
+
const lines = block.split("\n");
|
|
357
|
+
const header = lines[0] || "";
|
|
358
|
+
|
|
359
|
+
// Parse header: [category](scope) — date • ref: `taskRef`
|
|
360
|
+
const catMatch = header.match(/^\[([^\]]+)\]/);
|
|
361
|
+
const scopeMatch = header.match(/\(([^)]+)\)/);
|
|
362
|
+
const dateMatch = header.match(/(\d{4}-\d{2}-\d{2})/);
|
|
363
|
+
const refMatch = header.match(/ref: `([^`]+)`/);
|
|
364
|
+
|
|
365
|
+
// Parse agent line
|
|
366
|
+
const agentLine = lines.find((l) => l.startsWith("> **Agent:**"));
|
|
367
|
+
const agentMatch = agentLine?.match(/\*\*Agent:\*\* ([^ ]+) \(([^)]+)\)/);
|
|
368
|
+
|
|
369
|
+
// Extract content
|
|
370
|
+
|
|
371
|
+
const contentLines = lines
|
|
372
|
+
.filter(
|
|
373
|
+
(l) =>
|
|
374
|
+
!l.startsWith(">") && l.trim().length > 0 && !l.startsWith("---"),
|
|
375
|
+
)
|
|
376
|
+
.slice(1); // skip header line
|
|
377
|
+
|
|
378
|
+
entries.push({
|
|
379
|
+
category: catMatch?.[1] || "unknown",
|
|
380
|
+
scope: scopeMatch?.[1] || null,
|
|
381
|
+
date: dateMatch?.[1] || null,
|
|
382
|
+
taskRef: refMatch?.[1] || null,
|
|
383
|
+
agentId: agentMatch?.[1] || "unknown",
|
|
384
|
+
agentType: agentMatch?.[2] || "unknown",
|
|
385
|
+
content: contentLines.join("\n").trim(),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return entries;
|
|
390
|
+
} catch {
|
|
391
|
+
return [];
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ── Getters ──────────────────────────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
export function getKnowledgeState() {
|
|
398
|
+
return { ...knowledgeState, entryHashes: knowledgeState.entryHashes.size };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function formatKnowledgeSummary() {
|
|
402
|
+
return [
|
|
403
|
+
`📚 Shared Knowledge: ${knowledgeState.entriesWritten} entries written this session`,
|
|
404
|
+
`Target: ${knowledgeState.targetFile}`,
|
|
405
|
+
`Dedup cache: ${knowledgeState.entryHashes.size} hashes`,
|
|
406
|
+
knowledgeState.lastWriteAt
|
|
407
|
+
? `Last write: ${new Date(knowledgeState.lastWriteAt).toISOString()}`
|
|
408
|
+
: "No writes this session",
|
|
409
|
+
].join("\n");
|
|
410
|
+
}
|