@usejarvis/brain 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +278 -0
  3. package/bin/jarvis.ts +413 -0
  4. package/package.json +74 -0
  5. package/scripts/ensure-bun.cjs +8 -0
  6. package/src/actions/README.md +421 -0
  7. package/src/actions/app-control/desktop-controller.test.ts +26 -0
  8. package/src/actions/app-control/desktop-controller.ts +438 -0
  9. package/src/actions/app-control/interface.ts +64 -0
  10. package/src/actions/app-control/linux.ts +273 -0
  11. package/src/actions/app-control/macos.ts +54 -0
  12. package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
  13. package/src/actions/app-control/sidecar-launcher.ts +286 -0
  14. package/src/actions/app-control/windows.ts +44 -0
  15. package/src/actions/browser/cdp.ts +138 -0
  16. package/src/actions/browser/chrome-launcher.ts +252 -0
  17. package/src/actions/browser/session.ts +437 -0
  18. package/src/actions/browser/stealth.ts +49 -0
  19. package/src/actions/index.ts +20 -0
  20. package/src/actions/terminal/executor.ts +157 -0
  21. package/src/actions/terminal/wsl-bridge.ts +126 -0
  22. package/src/actions/test.ts +93 -0
  23. package/src/actions/tools/agents.ts +321 -0
  24. package/src/actions/tools/builtin.ts +846 -0
  25. package/src/actions/tools/commitments.ts +192 -0
  26. package/src/actions/tools/content.ts +217 -0
  27. package/src/actions/tools/delegate.ts +147 -0
  28. package/src/actions/tools/desktop.test.ts +55 -0
  29. package/src/actions/tools/desktop.ts +305 -0
  30. package/src/actions/tools/goals.ts +376 -0
  31. package/src/actions/tools/local-tools-guard.ts +20 -0
  32. package/src/actions/tools/registry.ts +171 -0
  33. package/src/actions/tools/research.ts +111 -0
  34. package/src/actions/tools/sidecar-list.ts +57 -0
  35. package/src/actions/tools/sidecar-route.ts +105 -0
  36. package/src/actions/tools/workflows.ts +216 -0
  37. package/src/agents/agent.ts +132 -0
  38. package/src/agents/delegation.ts +107 -0
  39. package/src/agents/hierarchy.ts +113 -0
  40. package/src/agents/index.ts +19 -0
  41. package/src/agents/messaging.ts +125 -0
  42. package/src/agents/orchestrator.ts +576 -0
  43. package/src/agents/role-discovery.ts +61 -0
  44. package/src/agents/sub-agent-runner.ts +307 -0
  45. package/src/agents/task-manager.ts +151 -0
  46. package/src/authority/approval-delivery.ts +59 -0
  47. package/src/authority/approval.ts +196 -0
  48. package/src/authority/audit.ts +158 -0
  49. package/src/authority/authority.test.ts +519 -0
  50. package/src/authority/deferred-executor.ts +103 -0
  51. package/src/authority/emergency.ts +66 -0
  52. package/src/authority/engine.ts +297 -0
  53. package/src/authority/index.ts +12 -0
  54. package/src/authority/learning.ts +111 -0
  55. package/src/authority/tool-action-map.ts +74 -0
  56. package/src/awareness/analytics.ts +466 -0
  57. package/src/awareness/awareness.test.ts +332 -0
  58. package/src/awareness/capture-engine.ts +305 -0
  59. package/src/awareness/context-graph.ts +130 -0
  60. package/src/awareness/context-tracker.ts +349 -0
  61. package/src/awareness/index.ts +25 -0
  62. package/src/awareness/intelligence.ts +321 -0
  63. package/src/awareness/ocr-engine.ts +88 -0
  64. package/src/awareness/service.ts +528 -0
  65. package/src/awareness/struggle-detector.ts +342 -0
  66. package/src/awareness/suggestion-engine.ts +476 -0
  67. package/src/awareness/types.ts +201 -0
  68. package/src/cli/autostart.ts +241 -0
  69. package/src/cli/deps.ts +449 -0
  70. package/src/cli/doctor.ts +230 -0
  71. package/src/cli/helpers.ts +401 -0
  72. package/src/cli/onboard.ts +580 -0
  73. package/src/comms/README.md +329 -0
  74. package/src/comms/auth-error.html +48 -0
  75. package/src/comms/channels/discord.ts +228 -0
  76. package/src/comms/channels/signal.ts +56 -0
  77. package/src/comms/channels/telegram.ts +316 -0
  78. package/src/comms/channels/whatsapp.ts +60 -0
  79. package/src/comms/channels.test.ts +173 -0
  80. package/src/comms/desktop-notify.ts +114 -0
  81. package/src/comms/example.ts +129 -0
  82. package/src/comms/index.ts +129 -0
  83. package/src/comms/streaming.ts +142 -0
  84. package/src/comms/voice.test.ts +152 -0
  85. package/src/comms/voice.ts +291 -0
  86. package/src/comms/websocket.test.ts +409 -0
  87. package/src/comms/websocket.ts +473 -0
  88. package/src/config/README.md +387 -0
  89. package/src/config/index.ts +6 -0
  90. package/src/config/loader.test.ts +137 -0
  91. package/src/config/loader.ts +142 -0
  92. package/src/config/types.ts +260 -0
  93. package/src/daemon/README.md +232 -0
  94. package/src/daemon/agent-service-interface.ts +9 -0
  95. package/src/daemon/agent-service.ts +600 -0
  96. package/src/daemon/api-routes.ts +2119 -0
  97. package/src/daemon/background-agent-service.ts +396 -0
  98. package/src/daemon/background-agent.test.ts +78 -0
  99. package/src/daemon/channel-service.ts +201 -0
  100. package/src/daemon/commitment-executor.ts +297 -0
  101. package/src/daemon/event-classifier.ts +239 -0
  102. package/src/daemon/event-coalescer.ts +123 -0
  103. package/src/daemon/event-reactor.ts +214 -0
  104. package/src/daemon/health.ts +220 -0
  105. package/src/daemon/index.ts +1004 -0
  106. package/src/daemon/llm-settings.ts +316 -0
  107. package/src/daemon/observer-service.ts +150 -0
  108. package/src/daemon/pid.ts +98 -0
  109. package/src/daemon/research-queue.ts +155 -0
  110. package/src/daemon/services.ts +175 -0
  111. package/src/daemon/ws-service.ts +788 -0
  112. package/src/goals/accountability.ts +240 -0
  113. package/src/goals/awareness-bridge.ts +185 -0
  114. package/src/goals/estimator.ts +185 -0
  115. package/src/goals/events.ts +28 -0
  116. package/src/goals/goals.test.ts +400 -0
  117. package/src/goals/integration.test.ts +329 -0
  118. package/src/goals/nl-builder.test.ts +220 -0
  119. package/src/goals/nl-builder.ts +256 -0
  120. package/src/goals/rhythm.test.ts +177 -0
  121. package/src/goals/rhythm.ts +275 -0
  122. package/src/goals/service.test.ts +135 -0
  123. package/src/goals/service.ts +348 -0
  124. package/src/goals/types.ts +106 -0
  125. package/src/goals/workflow-bridge.ts +96 -0
  126. package/src/integrations/google-api.ts +134 -0
  127. package/src/integrations/google-auth.ts +175 -0
  128. package/src/llm/README.md +291 -0
  129. package/src/llm/anthropic.ts +386 -0
  130. package/src/llm/gemini.ts +371 -0
  131. package/src/llm/index.ts +19 -0
  132. package/src/llm/manager.ts +153 -0
  133. package/src/llm/ollama.ts +307 -0
  134. package/src/llm/openai.ts +350 -0
  135. package/src/llm/provider.test.ts +231 -0
  136. package/src/llm/provider.ts +60 -0
  137. package/src/llm/test.ts +87 -0
  138. package/src/observers/README.md +278 -0
  139. package/src/observers/calendar.ts +113 -0
  140. package/src/observers/clipboard.ts +136 -0
  141. package/src/observers/email.ts +109 -0
  142. package/src/observers/example.ts +58 -0
  143. package/src/observers/file-watcher.ts +124 -0
  144. package/src/observers/index.ts +159 -0
  145. package/src/observers/notifications.ts +197 -0
  146. package/src/observers/observers.test.ts +203 -0
  147. package/src/observers/processes.ts +225 -0
  148. package/src/personality/README.md +61 -0
  149. package/src/personality/adapter.ts +196 -0
  150. package/src/personality/index.ts +20 -0
  151. package/src/personality/learner.ts +209 -0
  152. package/src/personality/model.ts +132 -0
  153. package/src/personality/personality.test.ts +236 -0
  154. package/src/roles/README.md +252 -0
  155. package/src/roles/authority.ts +119 -0
  156. package/src/roles/example-usage.ts +198 -0
  157. package/src/roles/index.ts +42 -0
  158. package/src/roles/loader.ts +143 -0
  159. package/src/roles/prompt-builder.ts +194 -0
  160. package/src/roles/test-multi.ts +102 -0
  161. package/src/roles/test-role.yaml +77 -0
  162. package/src/roles/test-utils.ts +93 -0
  163. package/src/roles/test.ts +106 -0
  164. package/src/roles/tool-guide.ts +190 -0
  165. package/src/roles/types.ts +36 -0
  166. package/src/roles/utils.ts +200 -0
  167. package/src/scripts/google-setup.ts +168 -0
  168. package/src/sidecar/connection.ts +179 -0
  169. package/src/sidecar/index.ts +6 -0
  170. package/src/sidecar/manager.ts +542 -0
  171. package/src/sidecar/protocol.ts +85 -0
  172. package/src/sidecar/rpc.ts +161 -0
  173. package/src/sidecar/scheduler.ts +136 -0
  174. package/src/sidecar/types.ts +112 -0
  175. package/src/sidecar/validator.ts +144 -0
  176. package/src/vault/README.md +110 -0
  177. package/src/vault/awareness.ts +341 -0
  178. package/src/vault/commitments.ts +299 -0
  179. package/src/vault/content-pipeline.ts +260 -0
  180. package/src/vault/conversations.ts +173 -0
  181. package/src/vault/entities.ts +180 -0
  182. package/src/vault/extractor.test.ts +356 -0
  183. package/src/vault/extractor.ts +345 -0
  184. package/src/vault/facts.ts +190 -0
  185. package/src/vault/goals.ts +477 -0
  186. package/src/vault/index.ts +87 -0
  187. package/src/vault/keychain.ts +99 -0
  188. package/src/vault/observations.ts +115 -0
  189. package/src/vault/relationships.ts +178 -0
  190. package/src/vault/retrieval.test.ts +126 -0
  191. package/src/vault/retrieval.ts +227 -0
  192. package/src/vault/schema.ts +658 -0
  193. package/src/vault/settings.ts +38 -0
  194. package/src/vault/vectors.ts +92 -0
  195. package/src/vault/workflows.ts +403 -0
  196. package/src/workflows/auto-suggest.ts +290 -0
  197. package/src/workflows/engine.ts +366 -0
  198. package/src/workflows/events.ts +24 -0
  199. package/src/workflows/executor.ts +207 -0
  200. package/src/workflows/nl-builder.ts +198 -0
  201. package/src/workflows/nodes/actions/agent-task.ts +73 -0
  202. package/src/workflows/nodes/actions/calendar-action.ts +85 -0
  203. package/src/workflows/nodes/actions/code-execution.ts +73 -0
  204. package/src/workflows/nodes/actions/discord.ts +77 -0
  205. package/src/workflows/nodes/actions/file-write.ts +73 -0
  206. package/src/workflows/nodes/actions/gmail.ts +69 -0
  207. package/src/workflows/nodes/actions/http-request.ts +117 -0
  208. package/src/workflows/nodes/actions/notification.ts +85 -0
  209. package/src/workflows/nodes/actions/run-tool.ts +55 -0
  210. package/src/workflows/nodes/actions/send-message.ts +82 -0
  211. package/src/workflows/nodes/actions/shell-command.ts +76 -0
  212. package/src/workflows/nodes/actions/telegram.ts +60 -0
  213. package/src/workflows/nodes/builtin.ts +119 -0
  214. package/src/workflows/nodes/error/error-handler.ts +37 -0
  215. package/src/workflows/nodes/error/fallback.ts +47 -0
  216. package/src/workflows/nodes/error/retry.ts +82 -0
  217. package/src/workflows/nodes/logic/delay.ts +42 -0
  218. package/src/workflows/nodes/logic/if-else.ts +41 -0
  219. package/src/workflows/nodes/logic/loop.ts +90 -0
  220. package/src/workflows/nodes/logic/merge.ts +38 -0
  221. package/src/workflows/nodes/logic/race.ts +40 -0
  222. package/src/workflows/nodes/logic/switch.ts +59 -0
  223. package/src/workflows/nodes/logic/template-render.ts +53 -0
  224. package/src/workflows/nodes/logic/variable-get.ts +37 -0
  225. package/src/workflows/nodes/logic/variable-set.ts +59 -0
  226. package/src/workflows/nodes/registry.ts +99 -0
  227. package/src/workflows/nodes/transform/aggregate.ts +99 -0
  228. package/src/workflows/nodes/transform/csv-parse.ts +70 -0
  229. package/src/workflows/nodes/transform/json-parse.ts +63 -0
  230. package/src/workflows/nodes/transform/map-filter.ts +84 -0
  231. package/src/workflows/nodes/transform/regex-match.ts +89 -0
  232. package/src/workflows/nodes/triggers/calendar.ts +33 -0
  233. package/src/workflows/nodes/triggers/clipboard.ts +32 -0
  234. package/src/workflows/nodes/triggers/cron.ts +40 -0
  235. package/src/workflows/nodes/triggers/email.ts +40 -0
  236. package/src/workflows/nodes/triggers/file-change.ts +45 -0
  237. package/src/workflows/nodes/triggers/git.ts +46 -0
  238. package/src/workflows/nodes/triggers/manual.ts +23 -0
  239. package/src/workflows/nodes/triggers/poll.ts +81 -0
  240. package/src/workflows/nodes/triggers/process.ts +44 -0
  241. package/src/workflows/nodes/triggers/screen-event.ts +37 -0
  242. package/src/workflows/nodes/triggers/webhook.ts +39 -0
  243. package/src/workflows/safe-eval.ts +139 -0
  244. package/src/workflows/template.ts +118 -0
  245. package/src/workflows/triggers/cron.ts +311 -0
  246. package/src/workflows/triggers/manager.ts +285 -0
  247. package/src/workflows/triggers/observer-bridge.ts +172 -0
  248. package/src/workflows/triggers/poller.ts +201 -0
  249. package/src/workflows/triggers/screen-condition.ts +218 -0
  250. package/src/workflows/triggers/triggers.test.ts +740 -0
  251. package/src/workflows/triggers/webhook.ts +191 -0
  252. package/src/workflows/types.ts +133 -0
  253. package/src/workflows/variables.ts +72 -0
  254. package/src/workflows/workflows.test.ts +383 -0
  255. package/src/workflows/yaml.ts +104 -0
  256. package/ui/dist/index-j75njzc1.css +1199 -0
  257. package/ui/dist/index-p2zh407q.js +80603 -0
  258. package/ui/dist/index.html +13 -0
  259. package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
  260. package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
  261. package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
  262. package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
  263. package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
  264. package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
  265. package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
  266. package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Vault Retrieval — Memory Query Engine
3
+ *
4
+ * Takes a user message, extracts search terms, queries the knowledge graph
5
+ * for matching entities/facts/relationships, and returns formatted context
6
+ * that gets injected into the system prompt.
7
+ */
8
+
9
+ import { getDb } from './schema.ts';
10
+ import { searchEntitiesByName, type Entity } from './entities.ts';
11
+ import { findFacts, type Fact } from './facts.ts';
12
+ import { getEntityRelationships } from './relationships.ts';
13
+
14
+ // Common stopwords to filter from search queries
15
+ const STOPWORDS = new Set([
16
+ 'i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your',
17
+ 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her',
18
+ 'hers', 'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs',
19
+ 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those',
20
+ 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had',
21
+ 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if',
22
+ 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with',
23
+ 'about', 'against', 'between', 'through', 'during', 'before', 'after', 'above',
24
+ 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under',
25
+ 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why',
26
+ 'how', 'all', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such',
27
+ 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very',
28
+ 'can', 'will', 'just', 'don', 'should', 'now', 'could', 'would', 'shall',
29
+ 'may', 'might', 'must', 'tell', 'know', 'think', 'say', 'said', 'get', 'go',
30
+ 'make', 'like', 'also', 'well', 'back', 'way', 'want', 'look', 'first', 'even',
31
+ 'give', 'yeah', 'yes', 'please', 'thanks', 'thank', 'hi', 'hello', 'hey',
32
+ 'okay', 'ok', 'sure', 'right', 'much', 'many', 'need', 'let', 'remember',
33
+ 'recall', 'told', 'mentioned', 'talked', 'work', 'works', 'working',
34
+ ]);
35
+
36
+ export type EntityProfile = {
37
+ entity: Entity;
38
+ facts: Fact[];
39
+ relationships: Array<{ type: string; target: string; direction: 'from' | 'to' }>;
40
+ };
41
+
42
+ /**
43
+ * Extract meaningful search terms from a user message.
44
+ * Filters stopwords and short words, deduplicates.
45
+ */
46
+ export function extractSearchTerms(message: string): string[] {
47
+ const words = message
48
+ .toLowerCase()
49
+ .split(/[^a-zA-Z0-9']+/)
50
+ .map(w => w.replace(/^'+|'+$/g, '')) // trim quotes
51
+ .filter(w => w.length > 1 && !STOPWORDS.has(w));
52
+
53
+ return [...new Set(words)];
54
+ }
55
+
56
+ /**
57
+ * Search the vault for entities matching the given terms.
58
+ * Searches entity names and fact objects/predicates.
59
+ */
60
+ export function retrieveForMessage(message: string): EntityProfile[] {
61
+ const terms = extractSearchTerms(message);
62
+ if (terms.length === 0) return [];
63
+
64
+ const entityMap = new Map<string, Entity>();
65
+
66
+ // 1. Search entity names
67
+ for (const term of terms) {
68
+ const matches = searchEntitiesByName(term);
69
+ for (const entity of matches) {
70
+ entityMap.set(entity.id, entity);
71
+ }
72
+ }
73
+
74
+ // 2. Search fact objects and predicates for matching terms
75
+ try {
76
+ const db = getDb();
77
+ for (const term of terms) {
78
+ const stmt = db.prepare(`
79
+ SELECT DISTINCT e.id, e.type, e.name, e.properties, e.created_at, e.updated_at, e.source
80
+ FROM entities e
81
+ JOIN facts f ON e.id = f.subject_id
82
+ WHERE f.object LIKE ? OR f.predicate LIKE ?
83
+ LIMIT 10
84
+ `);
85
+ const rows = stmt.all(`%${term}%`, `%${term}%`) as any[];
86
+ for (const row of rows) {
87
+ if (!entityMap.has(row.id)) {
88
+ entityMap.set(row.id, {
89
+ ...row,
90
+ properties: row.properties ? JSON.parse(row.properties) : null,
91
+ });
92
+ }
93
+ }
94
+ }
95
+ } catch {
96
+ // DB not available — return what we have from entity search
97
+ }
98
+
99
+ // 3. Build full profiles for matched entities (cap at 10)
100
+ const entities = [...entityMap.values()].slice(0, 10);
101
+ const profiles: EntityProfile[] = [];
102
+
103
+ for (const entity of entities) {
104
+ const facts = findFacts({ subject_id: entity.id });
105
+
106
+ let relationships: EntityProfile['relationships'] = [];
107
+ try {
108
+ const rels = getEntityRelationships(entity.id);
109
+ relationships = rels.map(r => ({
110
+ type: r.type,
111
+ target: r.from_id === entity.id ? r.to_entity.name : r.from_entity.name,
112
+ direction: (r.from_id === entity.id ? 'from' : 'to') as 'from' | 'to',
113
+ }));
114
+ } catch {
115
+ // Relationship query failed — skip
116
+ }
117
+
118
+ profiles.push({ entity, facts, relationships });
119
+ }
120
+
121
+ return profiles;
122
+ }
123
+
124
+ /**
125
+ * Format entity profiles into readable text for the system prompt.
126
+ */
127
+ export function formatKnowledgeContext(profiles: EntityProfile[]): string {
128
+ if (profiles.length === 0) return '';
129
+
130
+ const sections: string[] = [];
131
+
132
+ for (const { entity, facts, relationships } of profiles) {
133
+ const lines: string[] = [];
134
+
135
+ lines.push(`**${entity.name}** (${entity.type})`);
136
+
137
+ for (const fact of facts) {
138
+ lines.push(` - ${fact.predicate}: ${fact.object}`);
139
+ }
140
+
141
+ for (const rel of relationships) {
142
+ if (rel.direction === 'from') {
143
+ lines.push(` - ${rel.type} -> ${rel.target}`);
144
+ } else {
145
+ lines.push(` - ${rel.target} -> ${rel.type} -> ${entity.name}`);
146
+ }
147
+ }
148
+
149
+ sections.push(lines.join('\n'));
150
+ }
151
+
152
+ return sections.join('\n\n');
153
+ }
154
+
155
+ /**
156
+ * Main entry point: get formatted knowledge context for a user message.
157
+ * Returns empty string if no relevant knowledge found.
158
+ */
159
+ export function getKnowledgeForMessage(message: string): string {
160
+ try {
161
+ const profiles = retrieveForMessage(message);
162
+ return formatKnowledgeContext(profiles);
163
+ } catch (err) {
164
+ console.error('[Retrieval] Error querying vault:', err);
165
+ return '';
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Get a summary of active goals for system prompt injection.
171
+ * Returns formatted text showing goal hierarchy with scores, or empty string.
172
+ */
173
+ export function getActiveGoalsSummary(): string {
174
+ try {
175
+ const { findGoals } = require('./goals.ts');
176
+ const activeGoals = findGoals({ status: 'active' }) as Array<{
177
+ id: string;
178
+ parent_id: string | null;
179
+ level: string;
180
+ title: string;
181
+ score: number;
182
+ health: string;
183
+ deadline: number | null;
184
+ }>;
185
+
186
+ if (activeGoals.length === 0) return '';
187
+
188
+ const levelOrder: Record<string, number> = {
189
+ objective: 0,
190
+ key_result: 1,
191
+ milestone: 2,
192
+ task: 3,
193
+ daily_action: 4,
194
+ };
195
+
196
+ // Sort by level then title
197
+ activeGoals.sort((a, b) => {
198
+ const la = levelOrder[a.level] ?? 5;
199
+ const lb = levelOrder[b.level] ?? 5;
200
+ if (la !== lb) return la - lb;
201
+ return a.title.localeCompare(b.title);
202
+ });
203
+
204
+ // Cap at 15 most important goals (objectives + key results + top milestones)
205
+ const topGoals = activeGoals.slice(0, 15);
206
+
207
+ const lines: string[] = [];
208
+ for (const goal of topGoals) {
209
+ const indent = ' '.repeat(levelOrder[goal.level] ?? 0);
210
+ const healthIcon = goal.health === 'on_track' ? '+' :
211
+ goal.health === 'at_risk' ? '~' :
212
+ goal.health === 'behind' ? '-' : '!';
213
+ const deadlineStr = goal.deadline
214
+ ? ` (due: ${new Date(goal.deadline).toLocaleDateString()})`
215
+ : '';
216
+ lines.push(`${indent}[${healthIcon}] ${goal.title} — ${goal.score.toFixed(1)}/1.0${deadlineStr}`);
217
+ }
218
+
219
+ if (activeGoals.length > 15) {
220
+ lines.push(` ... and ${activeGoals.length - 15} more active goals`);
221
+ }
222
+
223
+ return lines.join('\n');
224
+ } catch {
225
+ return '';
226
+ }
227
+ }