@askexenow/exe-os 0.9.293 → 0.9.295

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.
@@ -0,0 +1,470 @@
1
+ // src/lib/status-brief.ts
2
+ import pathMod from "path";
3
+ import fsMod from "fs";
4
+ import osMod from "os";
5
+ function roleIcon(role) {
6
+ const lower = role.toLowerCase();
7
+ if (lower === "coo") return "\u{1F3AF}";
8
+ if (lower === "cto" || lower.includes("architect")) return "\u26A1";
9
+ if (lower === "cmo" || lower.includes("marketing")) return "\u{1F3A8}";
10
+ if (lower.includes("engineer")) return "\u{1F529}";
11
+ if (lower.includes("content")) return "\u{1F4F8}";
12
+ return "\u{1F464}";
13
+ }
14
+ function displayWidth(str) {
15
+ let w = 0;
16
+ for (const ch of str) {
17
+ const cp = ch.codePointAt(0);
18
+ if (cp > 255) w += 2;
19
+ else w += 1;
20
+ }
21
+ return w;
22
+ }
23
+ function padRight(str, target) {
24
+ const gap = target - displayWidth(str);
25
+ return gap > 0 ? str + " ".repeat(gap) : str;
26
+ }
27
+ function boxTop(w) {
28
+ return `\u250C${"\u2500".repeat(w)}\u2510`;
29
+ }
30
+ function boxMid(w) {
31
+ return `\u251C${"\u2500".repeat(w)}\u2524`;
32
+ }
33
+ function boxBot(w) {
34
+ return `\u2514${"\u2500".repeat(w)}\u2518`;
35
+ }
36
+ function truncateToWidth(str, maxW) {
37
+ if (displayWidth(str) <= maxW) return str;
38
+ let w = 0;
39
+ let i = 0;
40
+ for (const ch of str) {
41
+ const cw = ch.codePointAt(0) > 255 ? 2 : 1;
42
+ if (w + cw + 1 > maxW) break;
43
+ w += cw;
44
+ i += ch.length;
45
+ }
46
+ return str.slice(0, i) + "\u2026";
47
+ }
48
+ function boxRow(content, w) {
49
+ const maxContent = w - 2;
50
+ const truncated = truncateToWidth(content, maxContent);
51
+ return `\u2502 ${padRight(truncated, maxContent)} \u2502`;
52
+ }
53
+ function formatUptime(seconds) {
54
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
55
+ return `${(seconds / 3600).toFixed(1)}h`;
56
+ }
57
+ async function generateStatusBrief(employees, data, _activeAgentIds) {
58
+ const now = /* @__PURE__ */ new Date();
59
+ const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
60
+ const sessionTag = data.exeSession ? ` [${data.exeSession}]` : "";
61
+ if (data.embedding && data.embedding.totalMemories < 5 && data.globalTasks.length === 0 && (data.projects?.length ?? 0) === 0) {
62
+ return buildFirstBootBrief(employees, dateStr, sessionTag);
63
+ }
64
+ const sections = [];
65
+ sections.push([` EXE STATUS BRIEF \u2014 ${dateStr}${sessionTag}`]);
66
+ const degradedLines = buildDegradedBanner(data);
67
+ if (degradedLines.length > 0) sections.push(degradedLines);
68
+ const orchestrationLines = buildOrchestrationPhase(data, employees);
69
+ if (orchestrationLines.length > 0) sections.push(orchestrationLines);
70
+ const reminderLines = buildReminders(data);
71
+ if (reminderLines.length > 0) sections.push(reminderLines);
72
+ const whatsNewLines = buildWhatsNew();
73
+ if (whatsNewLines.length > 0) sections.push(whatsNewLines);
74
+ const actionLines = buildActionRequired(data);
75
+ if (actionLines.length > 0) sections.push(actionLines);
76
+ sections.push(buildProjects(data));
77
+ sections.push(buildTeam(employees, data));
78
+ sections.push(buildHealth(data));
79
+ const termCols = process.stdout.columns || 80;
80
+ const maxInnerW = termCols - 2;
81
+ let maxW = 0;
82
+ for (const sec of sections) {
83
+ for (const line of sec) {
84
+ const w = displayWidth(line);
85
+ if (w > maxW) maxW = w;
86
+ }
87
+ }
88
+ const innerW = Math.min(maxW + 4, maxInnerW);
89
+ const out = [];
90
+ out.push("[VERBATIM OUTPUT \u2014 do not reformat]");
91
+ for (let i = 0; i < sections.length; i++) {
92
+ out.push(i === 0 ? boxTop(innerW) : boxMid(innerW));
93
+ for (const line of sections[i]) {
94
+ out.push(boxRow(line, innerW));
95
+ }
96
+ }
97
+ out.push(boxBot(innerW));
98
+ return out.join("\n");
99
+ }
100
+ function buildFirstBootBrief(employees, dateStr, sessionTag) {
101
+ const termCols = process.stdout.columns || 80;
102
+ const maxInnerW = termCols - 2;
103
+ const lines = [];
104
+ lines.push(` WELCOME TO EXE OS \u2014 ${dateStr}${sessionTag}`);
105
+ const bodyLines = [];
106
+ bodyLines.push(" \u{1F44B} First time? Here's your team:");
107
+ bodyLines.push("");
108
+ for (const emp of employees) {
109
+ const emoji = roleIcon(emp.role);
110
+ const role = emp.role ? ` (${emp.role})` : "";
111
+ bodyLines.push(` ${emoji} ${emp.name}${role}`);
112
+ }
113
+ bodyLines.push("");
114
+ bodyLines.push(" \u{1F9ED} Orchestration:");
115
+ bodyLines.push(" \u2022 Phase 1 \u2014 Coordinator / Chief of Staff mode");
116
+ bodyLines.push(" \u2022 Recommended start: build company context first");
117
+ bodyLines.push(" \u2022 You can unlock executives or parallel mode anytime");
118
+ bodyLines.push("");
119
+ bodyLines.push(" \u{1F4A1} Quick start:");
120
+ bodyLines.push(" \u2022 Run `exe-os backfill-conversations` to import Claude Code history");
121
+ bodyLines.push(" \u2022 Say `/exe` to launch your coordinator with a full status brief");
122
+ bodyLines.push(" \u2022 Say `/exe-team` to manage your team");
123
+ let maxW = 0;
124
+ for (const line of [...lines, ...bodyLines]) {
125
+ const w = displayWidth(line);
126
+ if (w > maxW) maxW = w;
127
+ }
128
+ const innerW = Math.min(maxW + 4, maxInnerW);
129
+ const out = [];
130
+ out.push("[VERBATIM OUTPUT \u2014 do not reformat]");
131
+ out.push(boxTop(innerW));
132
+ for (const line of lines) out.push(boxRow(line, innerW));
133
+ out.push(boxMid(innerW));
134
+ for (const line of bodyLines) out.push(boxRow(line, innerW));
135
+ out.push(boxBot(innerW));
136
+ return out.join("\n");
137
+ }
138
+ function buildWhatsNew() {
139
+ try {
140
+ const pkgRoot = pathMod.resolve(new URL(".", import.meta.url).pathname, "../..");
141
+ const notesPath = pathMod.join(pkgRoot, "release-notes.json");
142
+ if (!fsMod.existsSync(notesPath)) return [];
143
+ const notes = JSON.parse(fsMod.readFileSync(notesPath, "utf8"));
144
+ const current = notes.current;
145
+ if (!current || !notes.notes?.[current]) return [];
146
+ const markerPath = pathMod.join(osMod.homedir(), ".exe-os", ".last-shown-version");
147
+ let lastShown = "";
148
+ try {
149
+ lastShown = fsMod.readFileSync(markerPath, "utf8").trim();
150
+ } catch {
151
+ }
152
+ if (lastShown === current) return [];
153
+ try {
154
+ fsMod.writeFileSync(markerPath, current, "utf8");
155
+ } catch {
156
+ }
157
+ const entry = notes.notes[current];
158
+ const lines = [];
159
+ lines.push(` \u{1F680} WHAT'S NEW \u2014 v${current} (${entry.date || "today"})`);
160
+ const features = entry.features || [];
161
+ const fixes = entry.fixes || [];
162
+ const topItems = [...features.slice(0, 3), ...fixes.slice(0, 2)];
163
+ for (const item of topItems) {
164
+ lines.push(` \u2022 ${item.length > 80 ? item.slice(0, 77) + "..." : item}`);
165
+ }
166
+ const remaining = features.length + fixes.length - topItems.length;
167
+ if (remaining > 0) lines.push(` + ${remaining} more`);
168
+ return lines;
169
+ } catch {
170
+ return [];
171
+ }
172
+ }
173
+ function buildReminders(data) {
174
+ if (!data.reminders || data.reminders.length === 0) return [];
175
+ const lines = [];
176
+ lines.push(" \u{1F4DD} REMINDERS");
177
+ for (const r of data.reminders) {
178
+ const due = r.dueDate ? ` (due: ${r.dueDate})` : "";
179
+ const prefix = r.overdue ? " \u{1F534}" : " \u2022";
180
+ lines.push(`${prefix} ${r.text}${due}`);
181
+ }
182
+ return lines;
183
+ }
184
+ function buildDegradedBanner(data) {
185
+ const dh = data.daemonHealth;
186
+ if (!dh) return [];
187
+ const lines = [];
188
+ if (!dh.daemonAlive) {
189
+ lines.push("\u{1F6A8}\u{1F6A8}\u{1F6A8} DEGRADED BOOT \u{1F6A8}\u{1F6A8}\u{1F6A8}");
190
+ lines.push("");
191
+ if (dh.stalePidFile) {
192
+ lines.push(" \u274C Daemon is DEAD (stale PID file found)");
193
+ } else {
194
+ lines.push(" \u274C Daemon is UNAVAILABLE");
195
+ }
196
+ lines.push(" Memory, tasks, and MCP tools will NOT work.");
197
+ lines.push("");
198
+ lines.push(" \u{1F527} TO FIX:");
199
+ lines.push(" 1. Run: exe-os start (restart the daemon)");
200
+ lines.push(" 2. Run: /mcp (reconnect MCP server)");
201
+ lines.push(" 3. Run: exe-os healthcheck (verify everything)");
202
+ return lines;
203
+ }
204
+ if (dh.mcpDisconnectRateHigh) {
205
+ lines.push("\u26A0\uFE0F MCP INSTABILITY DETECTED");
206
+ lines.push("");
207
+ lines.push(` \u{1F534} ${dh.mcpDisconnectsLastHour} disconnect${dh.mcpDisconnectsLastHour === 1 ? "" : "s"}/hour (${dh.mcpDisconnectsLast24h} in 24h)`);
208
+ lines.push(" MCP tools may fail intermittently.");
209
+ lines.push("");
210
+ lines.push(" \u{1F527} TO FIX:");
211
+ lines.push(" 1. Run: /mcp (reconnect MCP server)");
212
+ lines.push(" 2. Run: exe-os healthcheck (diagnose root cause)");
213
+ return lines;
214
+ }
215
+ return [];
216
+ }
217
+ function buildOrchestrationPhase(data, employees) {
218
+ if (!data.orchestrationPhase) return [];
219
+ const phase = data.orchestrationPhase;
220
+ const hasExecutiveBench = employees.some((e) => ["cto", "cmo"].includes(e.role.toLowerCase()));
221
+ const openWorkCount = data.globalTasks.filter((t) => t.status === "open" || t.status === "in_progress").length;
222
+ const domainKeywordHits = data.globalTasks.filter(
223
+ (t) => /\b(api|bug|code|repo|build|deploy|design|brand|copy|content|marketing|legal|finance|sales|crm)\b/i.test(t.title)
224
+ ).length;
225
+ const phase1Signal = !hasExecutiveBench && (openWorkCount >= 3 || domainKeywordHits >= 2);
226
+ if (phase === "phase_2_executives") {
227
+ return [
228
+ "\u{1F9ED} ORCHESTRATION",
229
+ " Phase 2 \u2014 Executive bench",
230
+ " Focus: Coordinator works with CTO/CMO/domain executives before specialist fan-out",
231
+ " You can switch phases anytime: exe-os org phase"
232
+ ];
233
+ }
234
+ if (phase === "phase_3_parallel_org") {
235
+ return [
236
+ "\u{1F9ED} ORCHESTRATION",
237
+ " Phase 3 \u2014 Parallel execution org",
238
+ " Focus: executives can delegate to specialists in parallel with review gates",
239
+ " You can switch phases anytime: exe-os org phase"
240
+ ];
241
+ }
242
+ return [
243
+ "\u{1F9ED} ORCHESTRATION",
244
+ " Phase 1 \u2014 Coordinator / Chief of Staff mode",
245
+ " Focus: building company context before delegation",
246
+ phase1Signal ? " Signal: repeated domain work detected. Consider: exe-os org unlock executives" : " Ready later? exe-os org unlock executives"
247
+ ];
248
+ }
249
+ function buildActionRequired(data) {
250
+ const lines = [];
251
+ let hasIssues = false;
252
+ const STALE_REVIEW_MS = 24 * 60 * 60 * 1e3;
253
+ const staleReviews = data.pendingReviews.filter(
254
+ (r) => r.updatedAt && Date.now() - new Date(r.updatedAt).getTime() > STALE_REVIEW_MS
255
+ );
256
+ if (staleReviews.length > 0) {
257
+ hasIssues = true;
258
+ const oldest = staleReviews.reduce(
259
+ (a, b) => new Date(a.updatedAt).getTime() < new Date(b.updatedAt).getTime() ? a : b
260
+ );
261
+ const oldestAge = ((Date.now() - new Date(oldest.updatedAt).getTime()) / 36e5).toFixed(0);
262
+ lines.push(` \u{1F534} ${staleReviews.length} STALE REVIEW${staleReviews.length === 1 ? "" : "S"} (>24h, oldest: ${oldestAge}h) \u2014 run /exe-review`);
263
+ }
264
+ if (data.pendingReviews.length > 0 && staleReviews.length === 0) {
265
+ const oldest = data.pendingReviews.reduce((a, b) => {
266
+ if (!a.updatedAt || !b.updatedAt) return a;
267
+ return new Date(a.updatedAt).getTime() < new Date(b.updatedAt).getTime() ? a : b;
268
+ });
269
+ const ageStr = oldest.updatedAt ? (() => {
270
+ const hrs = (Date.now() - new Date(oldest.updatedAt).getTime()) / 36e5;
271
+ return hrs < 1 ? `${Math.floor(hrs * 60)}m` : `${hrs.toFixed(0)}h`;
272
+ })() : "";
273
+ lines.push(` \u{1F4CB} ${data.pendingReviews.length} review${data.pendingReviews.length === 1 ? "" : "s"} pending${ageStr ? ` (oldest: ${ageStr})` : ""}`);
274
+ hasIssues = true;
275
+ }
276
+ const blockedTasks = data.globalTasks.filter((t) => t.status === "blocked");
277
+ if (blockedTasks.length > 0) {
278
+ hasIssues = true;
279
+ const ESCALATION_MS = 48 * 60 * 60 * 1e3;
280
+ const stale = blockedTasks.filter((t) => {
281
+ if (!t.updatedAt) return false;
282
+ return Date.now() - new Date(t.updatedAt).getTime() > ESCALATION_MS;
283
+ });
284
+ if (stale.length > 0) {
285
+ lines.push(` \u{1F534} ESCALATION: ${stale.length} task${stale.length === 1 ? "" : "s"} blocked >48h:`);
286
+ for (const t of stale) {
287
+ const ageH = Math.round((Date.now() - new Date(t.updatedAt).getTime()) / 36e5);
288
+ lines.push(` - "${t.title}" (${t.assignedTo}) \u2014 blocked ${ageH}h`);
289
+ }
290
+ } else {
291
+ lines.push(` \u{1F534} ${blockedTasks.length} blocked task${blockedTasks.length === 1 ? "" : "s"} \u2014 needs your decision`);
292
+ }
293
+ }
294
+ if (data.flaggedIssues.length > 0) {
295
+ hasIssues = true;
296
+ lines.push(` \u{1F534} ${data.flaggedIssues.length} error${data.flaggedIssues.length === 1 ? "" : "s"} (24h)`);
297
+ }
298
+ const STALE_MS = 2 * 60 * 60 * 1e3;
299
+ const staleTasks = data.globalTasks.filter((t) => {
300
+ if (t.status !== "in_progress" || !t.updatedAt) return false;
301
+ return Date.now() - new Date(t.updatedAt).getTime() > STALE_MS;
302
+ });
303
+ if (staleTasks.length > 0) {
304
+ hasIssues = true;
305
+ lines.push(` \u{1F7E0} ${staleTasks.length} stale task${staleTasks.length === 1 ? "" : "s"} (in_progress >2h)`);
306
+ for (const t of staleTasks) {
307
+ const hrs = ((Date.now() - new Date(t.updatedAt).getTime()) / 36e5).toFixed(1);
308
+ lines.push(` \u2022 "${t.title}" \u2192 ${t.assignedTo} (${hrs}h)`);
309
+ }
310
+ }
311
+ const ub = data.unmergedBranches;
312
+ if (ub && ub.total > 0 && ub.staleCount > 0) {
313
+ hasIssues = true;
314
+ lines.push(
315
+ ` \u{1F500} ${ub.total} unmerged remote branch${ub.total === 1 ? "" : "es"} (${ub.staleCount} >${ub.staleHours}h) \u2014 check before starting new work`
316
+ );
317
+ for (const b of ub.branches.slice(0, 5)) {
318
+ const age = b.ageHours >= 24 ? `${(b.ageHours / 24).toFixed(1)}d` : `${b.ageHours.toFixed(0)}h`;
319
+ lines.push(` \u2022 ${b.name} (+${b.ahead} commit${b.ahead === 1 ? "" : "s"}, ${b.filesChanged} file${b.filesChanged === 1 ? "" : "s"}, ${age})`);
320
+ }
321
+ if (ub.total > 5) lines.push(` ... ${ub.total - 5} more \u2014 run: exe-os git-sweep --dry-run`);
322
+ }
323
+ if (data.failedMessages && data.failedMessages.length > 0) {
324
+ hasIssues = true;
325
+ lines.push(` \u{1F534} ${data.failedMessages.length} undeliverable message${data.failedMessages.length === 1 ? "" : "s"}`);
326
+ for (const m of data.failedMessages) {
327
+ const snippet = m.content.slice(0, 60).replace(/\n/g, " ");
328
+ lines.push(` \u2022 "${snippet}" \u2192 ${m.targetAgent} (${m.failureReason ?? "unknown"})`);
329
+ }
330
+ }
331
+ if (!hasIssues) return [];
332
+ return ["\u26A0\uFE0F ACTION REQUIRED", ...lines];
333
+ }
334
+ function buildProjects(data) {
335
+ const lines = ["\u{1F4C2} PROJECTS"];
336
+ if (!data.projects || data.projects.length === 0) {
337
+ lines.push(" No projects tracked.");
338
+ return lines;
339
+ }
340
+ const sevenDaysMs = 7 * 24 * 60 * 60 * 1e3;
341
+ const active = [];
342
+ const idle = [];
343
+ for (const p of data.projects) {
344
+ const hasOpenTasks = p.openTasks > 0;
345
+ const hasRecentActivity = p.recentTasks.some(
346
+ (t) => t.updatedAt && Date.now() - new Date(t.updatedAt).getTime() < sevenDaysMs
347
+ );
348
+ if (hasOpenTasks || hasRecentActivity || p.isCurrent) {
349
+ active.push(p);
350
+ } else {
351
+ idle.push(p);
352
+ }
353
+ }
354
+ const MAX_SHOWN_PROJECTS = 6;
355
+ const shown = active.slice(0, MAX_SHOWN_PROJECTS);
356
+ const hidden = active.length - shown.length;
357
+ for (const p of shown) {
358
+ const blocked = p.blockedCount ?? 0;
359
+ const statusEmoji = blocked > 0 ? "\u{1F534}" : "\u{1F7E2}";
360
+ const sessionStr = p.sessionName ? ` (${p.sessionName})` : p.isCurrent ? " (current)" : "";
361
+ lines.push(` \u{1F4E6} ${p.projectName} ${statusEmoji}${sessionStr}`);
362
+ if (p.commits7d != null && p.commits7d > 0) {
363
+ lines.push(` ${p.commits7d} commits (7d)`);
364
+ }
365
+ if (p.teamActivity && p.teamActivity.length > 0) {
366
+ for (const ta of p.teamActivity) {
367
+ const parts = [];
368
+ if (ta.tasksDone > 0) parts.push(`${ta.tasksDone} ${ta.tasksDone === 1 ? "task" : "tasks"} done`);
369
+ if (ta.reviewsCleared > 0) parts.push(`${ta.reviewsCleared} ${ta.reviewsCleared === 1 ? "review" : "reviews"} cleared`);
370
+ if (ta.tasksAssigned && ta.tasksAssigned > 0) parts.push(`${ta.tasksAssigned} ${ta.tasksAssigned === 1 ? "task" : "tasks"} assigned`);
371
+ if (parts.length > 0) {
372
+ lines.push(` ${ta.name} \u2014 ${parts.join(", ")}`);
373
+ }
374
+ }
375
+ }
376
+ if (blocked > 0) {
377
+ lines.push(` Blocked: ${blocked}`);
378
+ }
379
+ }
380
+ if (hidden > 0) {
381
+ lines.push(` ... and ${hidden} more project${hidden === 1 ? "" : "s"}`);
382
+ }
383
+ if (idle.length > 0) {
384
+ const idleNames = idle.map((p) => p.projectName);
385
+ lines.push(` Idle: ${idleNames.join(", ")}`);
386
+ }
387
+ return lines;
388
+ }
389
+ function buildTeam(employees, data) {
390
+ const lines = ["\u{1F465} TEAM"];
391
+ const memMap = /* @__PURE__ */ new Map();
392
+ if (data.memoryStats) {
393
+ for (const m of data.memoryStats) memMap.set(m.agentId, m.totalMemories);
394
+ }
395
+ for (const emp of employees) {
396
+ const emoji = roleIcon(emp.role);
397
+ const memCount = memMap.get(emp.name) ?? 0;
398
+ const memStr = memCount > 0 ? `${memCount.toLocaleString()} memories` : "0 memories";
399
+ const role = emp.role ? ` (${emp.role})` : "";
400
+ lines.push(` ${emoji} ${emp.name}${role} \u2014 ${memStr}`);
401
+ }
402
+ if (data.plan) {
403
+ const planLabel = data.plan === "pro" ? "Solopreneur" : data.plan.charAt(0).toUpperCase() + data.plan.slice(1);
404
+ const limit = data.employeeLimit ?? "?";
405
+ lines.push(` Plan: ${planLabel} | ${employees.length}/${limit} employees | https://askexe.com`);
406
+ }
407
+ return lines;
408
+ }
409
+ function buildHealth(data) {
410
+ const lines = ["\u{1F48A} HEALTH"];
411
+ if (data.embedding) {
412
+ const e = data.embedding;
413
+ const pct = e.totalMemories > 0 ? Math.round(e.memoriesWithVectors / e.totalMemories * 100) : 100;
414
+ lines.push(` \u{1F9E0} Memory: ${e.memoriesWithVectors.toLocaleString()}/${e.totalMemories.toLocaleString()} (${pct}%)`);
415
+ if (e.recent24hTotal != null && e.recent24hWithVectors != null && e.recent24hTotal > 0) {
416
+ const recentPct = Math.round(e.recent24hWithVectors / e.recent24hTotal * 100);
417
+ if (recentPct < 60) {
418
+ lines.push(` \u{1F534} Recent vectors: ${e.recent24hWithVectors}/${e.recent24hTotal} (${recentPct}%) \u2014 backfill stalled`);
419
+ } else if (recentPct < 80) {
420
+ lines.push(` \u{1F7E0} Recent vectors: ${e.recent24hWithVectors}/${e.recent24hTotal} (${recentPct}%) \u2014 backfill needed`);
421
+ }
422
+ }
423
+ if (e.daemonRunning) {
424
+ const uptime = e.daemonUptime != null ? formatUptime(e.daemonUptime) : "?";
425
+ const reqs = e.daemonRequestsServed != null ? `, ${e.daemonRequestsServed.toLocaleString()} requests` : "";
426
+ lines.push(` \u{1F916} Model: \u{1F7E2} (${uptime} uptime${reqs})`);
427
+ } else {
428
+ lines.push(` \u{1F916} Model: \u26A0 not running`);
429
+ }
430
+ const nullCount = e.totalMemories - e.memoriesWithVectors;
431
+ if (nullCount > 0) {
432
+ if (!e.daemonRunning) {
433
+ lines.push(` \u{1F534} ${nullCount.toLocaleString()} memories missing embeddings \u2014 start model to backfill`);
434
+ } else if (e.backfillRunning) {
435
+ lines.push(` \u{1F7E1} Backfilling ${nullCount.toLocaleString()} memories\u2026`);
436
+ } else {
437
+ lines.push(` \u{1F7E0} ${nullCount.toLocaleString()} memories missing embeddings`);
438
+ }
439
+ }
440
+ }
441
+ const cloudLabel = data.cloudConnected ? "\u{1F7E2} synced" : data.cloudConnected === false && data.plan && data.plan !== "free" ? "\u{1F534} offline" : "not configured";
442
+ lines.push(` \u2601\uFE0F Cloud: ${cloudLabel}`);
443
+ if (data.cpuSteal && data.cpuSteal.available) {
444
+ const cs = data.cpuSteal;
445
+ const pct = Math.round(cs.stealPercent ?? 0);
446
+ if (cs.severity === "critical") {
447
+ lines.push(` \u{1F534} CPU steal: ${pct}% \u2014 ${cs.message}`);
448
+ } else if (cs.severity === "warning") {
449
+ lines.push(` \u{1F7E0} CPU steal: ${pct}% \u2014 ${cs.message}`);
450
+ } else {
451
+ lines.push(` \u{1F7E2} CPU steal: ${pct}% (host healthy)`);
452
+ }
453
+ }
454
+ if (data.sessionsKilledToday !== void 0) {
455
+ lines.push(` \u{1F480} Sessions killed today: ${data.sessionsKilledToday}`);
456
+ }
457
+ if (data.idleKillSuspectBroken) {
458
+ lines.push(` \u{1F534} Idle-kill may be broken or workload is unusually high.`);
459
+ }
460
+ if (data.missingBinaries && data.missingBinaries.length > 0) {
461
+ for (const bin of data.missingBinaries) {
462
+ lines.push(` \u{1F534} Missing binary: ${bin} \u2014 run npm run build`);
463
+ }
464
+ }
465
+ return lines;
466
+ }
467
+
468
+ export {
469
+ generateStatusBrief
470
+ };