@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,240 @@
1
+ /**
2
+ * Accountability Engine — Drill Sergeant Escalation
3
+ *
4
+ * Staged escalation for goals that are behind or at risk:
5
+ * - Week 1-2: Pressure — daily blunt reminders
6
+ * - Week 3: Root Cause — LLM analyzes why the goal is failing
7
+ * - Week 4+: Suggest Kill — recommend killing or replanning the goal
8
+ *
9
+ * Also generates replan options when goals need course correction.
10
+ */
11
+
12
+ import type { Goal, EscalationStage } from './types.ts';
13
+ import * as vault from '../vault/goals.ts';
14
+
15
+ export type EscalationAction = {
16
+ goalId: string;
17
+ goalTitle: string;
18
+ currentStage: EscalationStage;
19
+ newStage: EscalationStage;
20
+ message: string;
21
+ weeksBehind: number;
22
+ };
23
+
24
+ export type ReplanOption = {
25
+ id: string;
26
+ label: string;
27
+ description: string;
28
+ impact: 'low' | 'medium' | 'high';
29
+ };
30
+
31
+ export type ReplanAnalysis = {
32
+ options: ReplanOption[];
33
+ analysis: string;
34
+ recommendation: string;
35
+ };
36
+
37
+ export class AccountabilityEngine {
38
+ private llmManager: any; // LLMManager
39
+ private accountabilityStyle: 'drill_sergeant' | 'supportive' | 'balanced';
40
+ private escalationWeeks: { pressure: number; root_cause: number; suggest_kill: number };
41
+
42
+ constructor(
43
+ llmManager: unknown,
44
+ style: 'drill_sergeant' | 'supportive' | 'balanced' = 'drill_sergeant',
45
+ escalationWeeks = { pressure: 1, root_cause: 3, suggest_kill: 4 },
46
+ ) {
47
+ this.llmManager = llmManager;
48
+ this.accountabilityStyle = style;
49
+ this.escalationWeeks = escalationWeeks;
50
+ }
51
+
52
+ /**
53
+ * Check all active goals and return escalation actions needed.
54
+ */
55
+ runEscalationCheck(): EscalationAction[] {
56
+ const needingEscalation = vault.getGoalsNeedingEscalation();
57
+ const actions: EscalationAction[] = [];
58
+
59
+ for (const goal of needingEscalation) {
60
+ const action = this.evaluateEscalation(goal);
61
+ if (action) actions.push(action);
62
+ }
63
+
64
+ return actions;
65
+ }
66
+
67
+ /**
68
+ * Generate an escalation message for a goal at a given stage.
69
+ */
70
+ async generateEscalationMessage(goal: Goal, stage: EscalationStage): Promise<string> {
71
+ if (stage === 'none') return '';
72
+
73
+ const history = vault.getProgressHistory(goal.id, 10);
74
+ const progressContext = history.length > 0
75
+ ? `\nProgress history:\n${history.map(h => ` ${new Date(h.created_at).toLocaleDateString()}: ${h.score_before} → ${h.score_after} (${h.note})`).join('\n')}`
76
+ : '';
77
+
78
+ const prompt = [
79
+ {
80
+ role: 'system' as const,
81
+ content: this.getEscalationSystemPrompt(stage),
82
+ },
83
+ {
84
+ role: 'user' as const,
85
+ content: `Goal: ${goal.title}\nLevel: ${goal.level}\nScore: ${goal.score}\nHealth: ${goal.health}\nDeadline: ${goal.deadline ? new Date(goal.deadline).toLocaleDateString() : 'none'}\nStarted: ${goal.started_at ? new Date(goal.started_at).toLocaleDateString() : 'unknown'}${progressContext}\n\nGenerate the escalation message.`,
86
+ },
87
+ ];
88
+
89
+ try {
90
+ const response = await this.llmManager.chat(prompt, {
91
+ temperature: 0.5,
92
+ max_tokens: 500,
93
+ });
94
+ return typeof response.content === 'string' ? response.content : String(response.content);
95
+ } catch {
96
+ return this.getFallbackMessage(goal, stage);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Generate replan options for a struggling goal.
102
+ */
103
+ async generateReplanOptions(goal: Goal): Promise<ReplanAnalysis> {
104
+ const children = vault.getGoalChildren(goal.id);
105
+ const history = vault.getProgressHistory(goal.id, 20);
106
+
107
+ const childContext = children.length > 0
108
+ ? `\nChild goals:\n${children.map(c => `- ${c.title} (${c.level}, score: ${c.score}, status: ${c.status})`).join('\n')}`
109
+ : '';
110
+
111
+ const historyContext = history.length > 0
112
+ ? `\nRecent progress:\n${history.slice(0, 5).map(h => ` ${h.score_before} → ${h.score_after}: ${h.note}`).join('\n')}`
113
+ : '';
114
+
115
+ const prompt = [
116
+ {
117
+ role: 'system' as const,
118
+ content: `You are an OKR coach analyzing a struggling goal. Generate replan options.
119
+
120
+ Respond with ONLY valid JSON:
121
+ {
122
+ "options": [
123
+ { "id": "unique_id", "label": "short label", "description": "what this option means", "impact": "low|medium|high" }
124
+ ],
125
+ "analysis": "why the goal is struggling",
126
+ "recommendation": "which option you recommend and why"
127
+ }
128
+
129
+ Always include these standard options:
130
+ - Extend deadline
131
+ - Reduce scope
132
+ - Change approach
133
+ - Kill the goal
134
+ Plus 1-2 context-specific options.`,
135
+ },
136
+ {
137
+ role: 'user' as const,
138
+ content: `Goal: ${goal.title}\nDescription: ${goal.description}\nSuccess criteria: ${goal.success_criteria}\nScore: ${goal.score}\nHealth: ${goal.health}\nDeadline: ${goal.deadline ? new Date(goal.deadline).toLocaleDateString() : 'none'}${childContext}${historyContext}`,
139
+ },
140
+ ];
141
+
142
+ try {
143
+ const response = await this.llmManager.chat(prompt, {
144
+ temperature: 0.3,
145
+ max_tokens: 1500,
146
+ });
147
+
148
+ const text = typeof response.content === 'string' ? response.content : JSON.stringify(response.content);
149
+ const json = text.match(/\{[\s\S]*\}/)?.[0];
150
+ if (!json) throw new Error('No JSON in response');
151
+
152
+ return JSON.parse(json) as ReplanAnalysis;
153
+ } catch {
154
+ return this.getFallbackReplanOptions(goal);
155
+ }
156
+ }
157
+
158
+ // ── Private ──────────────────────────────────────────────────────
159
+
160
+ private evaluateEscalation(goal: Goal): EscalationAction | null {
161
+ const startedAt = goal.escalation_started_at ?? goal.updated_at;
162
+ const weeksBehind = (Date.now() - startedAt) / (7 * 24 * 60 * 60 * 1000);
163
+
164
+ let newStage: EscalationStage | null = null;
165
+
166
+ if (goal.escalation_stage === 'none' && weeksBehind >= this.escalationWeeks.pressure) {
167
+ newStage = 'pressure';
168
+ } else if (goal.escalation_stage === 'pressure') {
169
+ const escalationStart = goal.escalation_started_at ?? goal.updated_at;
170
+ const weeksSinceEscalation = (Date.now() - escalationStart) / (7 * 24 * 60 * 60 * 1000);
171
+ if (weeksSinceEscalation >= this.escalationWeeks.root_cause) {
172
+ newStage = 'root_cause';
173
+ }
174
+ } else if (goal.escalation_stage === 'root_cause') {
175
+ const escalationStart = goal.escalation_started_at ?? goal.updated_at;
176
+ const weeksSinceEscalation = (Date.now() - escalationStart) / (7 * 24 * 60 * 60 * 1000);
177
+ if (weeksSinceEscalation >= this.escalationWeeks.suggest_kill) {
178
+ newStage = 'suggest_kill';
179
+ }
180
+ }
181
+
182
+ if (!newStage) return null;
183
+
184
+ return {
185
+ goalId: goal.id,
186
+ goalTitle: goal.title,
187
+ currentStage: goal.escalation_stage,
188
+ newStage,
189
+ message: this.getFallbackMessage(goal, newStage),
190
+ weeksBehind: Math.round(weeksBehind * 10) / 10,
191
+ };
192
+ }
193
+
194
+ private getEscalationSystemPrompt(stage: EscalationStage): string {
195
+ const tone = this.accountabilityStyle === 'drill_sergeant'
196
+ ? 'Be brutally honest. No sugarcoating. Short, punchy sentences.'
197
+ : this.accountabilityStyle === 'supportive'
198
+ ? 'Be encouraging but honest about the situation.'
199
+ : 'Be direct but fair.';
200
+
201
+ switch (stage) {
202
+ case 'pressure':
203
+ return `You are generating a PRESSURE escalation message for a goal that's falling behind. ${tone} Focus on urgency and the consequences of continued inaction. Keep it under 100 words.`;
204
+ case 'root_cause':
205
+ return `You are generating a ROOT CAUSE analysis for a failing goal. ${tone} Identify why this goal is stalling and what needs to change. Keep it under 150 words.`;
206
+ case 'suggest_kill':
207
+ return `You are generating a KILL SUGGESTION for a goal that has been failing for too long. ${tone} Make the case for either killing the goal or doing a major replan. Be direct about sunk cost. Keep it under 150 words.`;
208
+ default:
209
+ return '';
210
+ }
211
+ }
212
+
213
+ private getFallbackMessage(goal: Goal, stage: EscalationStage): string {
214
+ switch (stage) {
215
+ case 'pressure':
216
+ return `"${goal.title}" is behind schedule. Score: ${goal.score}. ${goal.deadline ? `Deadline: ${new Date(goal.deadline).toLocaleDateString()}.` : ''} Stop making excuses and deliver.`;
217
+ case 'root_cause':
218
+ return `"${goal.title}" has been behind for weeks. Score: ${goal.score}. Time to figure out what's actually wrong. Is the goal realistic? Are you avoiding something? Fix it or kill it.`;
219
+ case 'suggest_kill':
220
+ return `"${goal.title}" has been failing for over a month. Score: ${goal.score}. Consider killing this goal. Sunk cost is sunk. Either commit to a radical replan or move on.`;
221
+ default:
222
+ return '';
223
+ }
224
+ }
225
+
226
+ private getFallbackReplanOptions(goal: Goal): ReplanAnalysis {
227
+ return {
228
+ options: [
229
+ { id: 'extend', label: 'Extend deadline', description: 'Push the deadline back 2-4 weeks', impact: 'low' },
230
+ { id: 'reduce', label: 'Reduce scope', description: 'Cut non-essential success criteria', impact: 'medium' },
231
+ { id: 'pivot', label: 'Change approach', description: 'Try a fundamentally different strategy', impact: 'high' },
232
+ { id: 'kill', label: 'Kill the goal', description: 'Accept the loss and reallocate effort', impact: 'high' },
233
+ ],
234
+ analysis: `"${goal.title}" has a score of ${goal.score} and is ${goal.health}. The current approach isn't working.`,
235
+ recommendation: goal.score < 0.2
236
+ ? 'Consider killing this goal or making a major pivot.'
237
+ : 'Extend the deadline and reduce scope to focus on the most impactful parts.',
238
+ };
239
+ }
240
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Awareness → Goals Bridge
3
+ *
4
+ * Subscribes to awareness events and fuzzy-matches detected context
5
+ * (apps, windows, files, text) against active goal descriptions.
6
+ * Auto-logs progress entries as 'auto_detected' when matches are found.
7
+ * Feeds the evening review with detected activity.
8
+ */
9
+
10
+ import type { Goal } from './types.ts';
11
+ import * as vault from '../vault/goals.ts';
12
+
13
+ export type AwarenessGoalMatch = {
14
+ goalId: string;
15
+ goalTitle: string;
16
+ matchScore: number;
17
+ matchedTerms: string[];
18
+ source: string;
19
+ };
20
+
21
+ /**
22
+ * Process an awareness event and check if it relates to any active goals.
23
+ * Returns any matches found so the caller can log them.
24
+ */
25
+ export function matchAwarenessToGoals(
26
+ eventData: Record<string, unknown>,
27
+ ): AwarenessGoalMatch[] {
28
+ const activeGoals = vault.findGoals({ status: 'active' });
29
+ if (activeGoals.length === 0) return [];
30
+
31
+ // Extract searchable text from awareness event
32
+ const eventText = extractEventText(eventData);
33
+ if (!eventText) return [];
34
+
35
+ const eventWords = tokenize(eventText);
36
+ if (eventWords.length === 0) return [];
37
+
38
+ const matches: AwarenessGoalMatch[] = [];
39
+
40
+ for (const goal of activeGoals) {
41
+ const goalWords = tokenize(`${goal.title} ${goal.description} ${goal.success_criteria}`);
42
+ if (goalWords.length === 0) continue;
43
+
44
+ const { score, matched } = fuzzyMatch(eventWords, goalWords);
45
+
46
+ // Threshold: require at least 2 matching terms and 0.15 score
47
+ if (score >= 0.15 && matched.length >= 2) {
48
+ matches.push({
49
+ goalId: goal.id,
50
+ goalTitle: goal.title,
51
+ matchScore: score,
52
+ matchedTerms: matched,
53
+ source: String(eventData.app_name ?? eventData.window_title ?? 'awareness'),
54
+ });
55
+ }
56
+ }
57
+
58
+ // Sort by match score descending
59
+ matches.sort((a, b) => b.matchScore - a.matchScore);
60
+
61
+ return matches;
62
+ }
63
+
64
+ /**
65
+ * Log auto-detected progress for matched goals.
66
+ * Only logs if the goal hasn't had a recent auto-detection (within 30 min).
67
+ */
68
+ export function logAutoDetectedProgress(
69
+ matches: AwarenessGoalMatch[],
70
+ eventType: string,
71
+ ): void {
72
+ const now = Date.now();
73
+ const thirtyMinutes = 30 * 60 * 1000;
74
+
75
+ for (const match of matches) {
76
+ // Check for recent auto-detection to avoid spam
77
+ const recentProgress = vault.getProgressHistory(match.goalId, 5);
78
+ const hasRecentAutoDetect = recentProgress.some(
79
+ p => p.type === 'auto_detected' && (now - p.created_at) < thirtyMinutes
80
+ );
81
+
82
+ if (hasRecentAutoDetect) continue;
83
+
84
+ const goal = vault.getGoal(match.goalId);
85
+ if (!goal) continue;
86
+
87
+ // Log progress entry (no score change, just detection)
88
+ vault.addProgressEntry(
89
+ match.goalId,
90
+ 'auto_detected',
91
+ goal.score,
92
+ goal.score, // no automatic score change
93
+ `Activity detected: ${eventType} via ${match.source} (matched: ${match.matchedTerms.join(', ')})`,
94
+ 'awareness',
95
+ );
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Extract searchable text from an awareness event's data payload.
101
+ */
102
+ function extractEventText(data: Record<string, unknown>): string {
103
+ const parts: string[] = [];
104
+
105
+ // Common awareness event fields
106
+ if (data.app_name) parts.push(String(data.app_name));
107
+ if (data.window_title) parts.push(String(data.window_title));
108
+ if (data.ocr_text) parts.push(String(data.ocr_text));
109
+ if (data.file_path) parts.push(String(data.file_path));
110
+ if (data.url) parts.push(String(data.url));
111
+ if (data.title) parts.push(String(data.title));
112
+ if (data.body) parts.push(String(data.body));
113
+ if (data.description) parts.push(String(data.description));
114
+ if (data.context) parts.push(String(data.context));
115
+
116
+ // Session data
117
+ if (data.dominant_app) parts.push(String(data.dominant_app));
118
+ if (data.summary) parts.push(String(data.summary));
119
+ if (data.activities && Array.isArray(data.activities)) {
120
+ for (const a of data.activities) {
121
+ if (typeof a === 'string') parts.push(a);
122
+ else if (a && typeof a === 'object' && 'description' in a) parts.push(String(a.description));
123
+ }
124
+ }
125
+
126
+ return parts.join(' ');
127
+ }
128
+
129
+ // Common words to filter from matching
130
+ const STOP_WORDS = new Set([
131
+ 'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
132
+ 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
133
+ 'should', 'may', 'might', 'must', 'shall', 'can', 'to', 'of', 'in',
134
+ 'for', 'on', 'with', 'at', 'by', 'from', 'as', 'into', 'about',
135
+ 'and', 'but', 'or', 'not', 'no', 'if', 'than', 'so', 'up', 'out',
136
+ 'that', 'this', 'it', 'its', 'my', 'your', 'his', 'her', 'our',
137
+ 'all', 'each', 'every', 'both', 'few', 'more', 'most', 'some',
138
+ 'new', 'file', 'window', 'app', 'application', 'open', 'close',
139
+ ]);
140
+
141
+ /**
142
+ * Tokenize text into meaningful words (lowercase, filtered).
143
+ */
144
+ function tokenize(text: string): string[] {
145
+ return text
146
+ .toLowerCase()
147
+ .split(/[^a-zA-Z0-9]+/)
148
+ .filter(w => w.length >= 3 && !STOP_WORDS.has(w));
149
+ }
150
+
151
+ /**
152
+ * Fuzzy match: check how many of the event words overlap with goal words.
153
+ * Returns a score (0-1) and the matched terms.
154
+ */
155
+ function fuzzyMatch(
156
+ eventWords: string[],
157
+ goalWords: string[],
158
+ ): { score: number; matched: string[] } {
159
+ const goalSet = new Set(goalWords);
160
+ const matched: string[] = [];
161
+
162
+ for (const word of eventWords) {
163
+ if (goalSet.has(word)) {
164
+ matched.push(word);
165
+ } else {
166
+ // Partial match: check if event word is substring of any goal word or vice versa
167
+ for (const gw of goalSet) {
168
+ if (gw.length >= 4 && word.length >= 4) {
169
+ if (gw.includes(word) || word.includes(gw)) {
170
+ matched.push(word);
171
+ break;
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+
178
+ // Deduplicate
179
+ const uniqueMatched = [...new Set(matched)];
180
+
181
+ // Score: proportion of goal words that were matched
182
+ const score = goalWords.length > 0 ? uniqueMatched.length / goalWords.length : 0;
183
+
184
+ return { score, matched: uniqueMatched };
185
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Goal Estimator — Hybrid LLM + historical estimation
3
+ *
4
+ * Blends LLM estimates with personal historical data from completed goals.
5
+ * 60/40 split (history/LLM) when history is available, 100% LLM otherwise.
6
+ */
7
+
8
+ import type { GoalEstimate, GoalLevel } from './types.ts';
9
+ import * as vault from '../vault/goals.ts';
10
+
11
+ export class GoalEstimator {
12
+ private llmManager: any; // LLMManager
13
+
14
+ constructor(llmManager: unknown) {
15
+ this.llmManager = llmManager;
16
+ }
17
+
18
+ /**
19
+ * Estimate hours for a goal by combining LLM judgment with historical data.
20
+ */
21
+ async estimate(goalId: string): Promise<GoalEstimate | null> {
22
+ const goal = vault.getGoal(goalId);
23
+ if (!goal) return null;
24
+
25
+ // Find similar completed goals
26
+ const similarGoals = this.findSimilarCompleted(goal.title, goal.level, goal.description);
27
+
28
+ // Get LLM estimate
29
+ const llmEstimate = await this.getLLMEstimate(goal.title, goal.level, goal.description, goal.success_criteria);
30
+
31
+ // Calculate historical estimate
32
+ const historicalEstimate = this.calculateHistoricalEstimate(similarGoals);
33
+
34
+ // Blend estimates
35
+ let finalEstimate: number;
36
+ let confidence: number;
37
+ let reasoning: string;
38
+
39
+ if (historicalEstimate !== null && similarGoals.length >= 2) {
40
+ // 60% history, 40% LLM when we have good historical data
41
+ finalEstimate = historicalEstimate * 0.6 + llmEstimate.hours * 0.4;
42
+ confidence = Math.min(0.9, 0.5 + similarGoals.length * 0.1);
43
+ reasoning = `Blended estimate: ${similarGoals.length} similar past goals averaged ${historicalEstimate.toFixed(1)}h (actual), LLM estimates ${llmEstimate.hours.toFixed(1)}h. ${llmEstimate.reasoning}`;
44
+ } else if (historicalEstimate !== null) {
45
+ // Some history, less weight
46
+ finalEstimate = historicalEstimate * 0.4 + llmEstimate.hours * 0.6;
47
+ confidence = 0.4;
48
+ reasoning = `Limited history (${similarGoals.length} similar goal${similarGoals.length === 1 ? '' : 's'}): ${historicalEstimate.toFixed(1)}h actual. LLM estimates ${llmEstimate.hours.toFixed(1)}h. ${llmEstimate.reasoning}`;
49
+ } else {
50
+ // No history, pure LLM
51
+ finalEstimate = llmEstimate.hours;
52
+ confidence = Math.min(0.5, llmEstimate.confidence);
53
+ reasoning = `No historical data. LLM estimate: ${llmEstimate.reasoning}`;
54
+ }
55
+
56
+ return {
57
+ llm_estimate_hours: llmEstimate.hours,
58
+ historical_estimate_hours: historicalEstimate,
59
+ final_estimate_hours: Math.round(finalEstimate * 10) / 10,
60
+ confidence: Math.round(confidence * 100) / 100,
61
+ reasoning,
62
+ similar_past_goals: similarGoals.map(g => g.id),
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Quick estimate without LLM — uses only historical data and heuristics.
68
+ */
69
+ quickEstimate(title: string, level: GoalLevel): { hours: number; confidence: number } {
70
+ const similar = this.findSimilarCompleted(title, level);
71
+ const historical = this.calculateHistoricalEstimate(similar);
72
+
73
+ if (historical !== null && similar.length >= 2) {
74
+ return { hours: Math.round(historical * 10) / 10, confidence: 0.6 };
75
+ }
76
+
77
+ // Heuristic fallback based on level
78
+ const levelDefaults: Record<GoalLevel, number> = {
79
+ objective: 200,
80
+ key_result: 40,
81
+ milestone: 20,
82
+ task: 4,
83
+ daily_action: 1,
84
+ };
85
+ return { hours: levelDefaults[level], confidence: 0.2 };
86
+ }
87
+
88
+ // ── Private ──────────────────────────────────────────────────────
89
+
90
+ private findSimilarCompleted(title: string, level: GoalLevel, description?: string): ReturnType<typeof vault.findGoals> {
91
+ // Find completed goals at the same level
92
+ const completed = vault.findGoals({ status: 'completed', level, limit: 50 });
93
+
94
+ // Score similarity using simple word overlap
95
+ const titleWords = new Set(title.toLowerCase().split(/\s+/).filter(w => w.length > 2));
96
+ const descWords = description
97
+ ? new Set(description.toLowerCase().split(/\s+/).filter(w => w.length > 2))
98
+ : new Set<string>();
99
+
100
+ const scored = completed
101
+ .map(goal => {
102
+ const goalWords = new Set([
103
+ ...goal.title.toLowerCase().split(/\s+/).filter(w => w.length > 2),
104
+ ...goal.description.toLowerCase().split(/\s+/).filter(w => w.length > 2),
105
+ ]);
106
+
107
+ let overlap = 0;
108
+ for (const word of titleWords) {
109
+ if (goalWords.has(word)) overlap += 2; // Title matches worth more
110
+ }
111
+ for (const word of descWords) {
112
+ if (goalWords.has(word)) overlap += 1;
113
+ }
114
+
115
+ return { goal, score: overlap };
116
+ })
117
+ .filter(({ score }) => score > 0)
118
+ .sort((a, b) => b.score - a.score)
119
+ .slice(0, 5);
120
+
121
+ return scored.map(s => s.goal);
122
+ }
123
+
124
+ private calculateHistoricalEstimate(goals: ReturnType<typeof vault.findGoals>): number | null {
125
+ const withHours = goals.filter(g => g.actual_hours > 0);
126
+ if (withHours.length === 0) return null;
127
+
128
+ const total = withHours.reduce((sum, g) => sum + g.actual_hours, 0);
129
+ return total / withHours.length;
130
+ }
131
+
132
+ private async getLLMEstimate(
133
+ title: string,
134
+ level: GoalLevel,
135
+ description: string,
136
+ successCriteria: string,
137
+ ): Promise<{ hours: number; confidence: number; reasoning: string }> {
138
+ const prompt = [
139
+ {
140
+ role: 'system' as const,
141
+ content: `You are a project estimation expert. Estimate the total hours needed to complete a goal.
142
+
143
+ Consider:
144
+ - Goal level: objective (large), key_result (medium), milestone (small), task (tiny), daily_action (1-2h)
145
+ - Include planning, execution, review, and buffer time
146
+ - Be realistic, not optimistic
147
+
148
+ Respond with ONLY valid JSON: { "hours": number, "confidence": number (0-1), "reasoning": "brief explanation" }`,
149
+ },
150
+ {
151
+ role: 'user' as const,
152
+ content: `Estimate hours for:\nLevel: ${level}\nTitle: ${title}\nDescription: ${description}\nSuccess Criteria: ${successCriteria}`,
153
+ },
154
+ ];
155
+
156
+ try {
157
+ const response = await this.llmManager.chat(prompt, {
158
+ temperature: 0.2,
159
+ max_tokens: 500,
160
+ });
161
+
162
+ const text = typeof response.content === 'string' ? response.content : JSON.stringify(response.content);
163
+ const json = text.match(/\{[\s\S]*\}/)?.[0];
164
+ if (!json) throw new Error('No JSON in response');
165
+
166
+ const parsed = JSON.parse(json);
167
+ return {
168
+ hours: Math.max(0.5, Number(parsed.hours) || 4),
169
+ confidence: Math.max(0, Math.min(1, Number(parsed.confidence) || 0.3)),
170
+ reasoning: String(parsed.reasoning || 'LLM estimate'),
171
+ };
172
+ } catch (err) {
173
+ console.error('[GoalEstimator] LLM estimate failed:', err instanceof Error ? err.message : err);
174
+ // Fallback heuristic
175
+ const defaults: Record<GoalLevel, number> = {
176
+ objective: 200, key_result: 40, milestone: 20, task: 4, daily_action: 1,
177
+ };
178
+ return {
179
+ hours: defaults[level],
180
+ confidence: 0.1,
181
+ reasoning: 'Fallback heuristic (LLM unavailable)',
182
+ };
183
+ }
184
+ }
185
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Goal Event Types for M16 — Autonomous Goal Pursuit
3
+ *
4
+ * Events emitted by GoalService and broadcast via WebSocket.
5
+ */
6
+
7
+ export type GoalEventType =
8
+ | 'goal_created'
9
+ | 'goal_updated'
10
+ | 'goal_scored'
11
+ | 'goal_status_changed'
12
+ | 'goal_completed'
13
+ | 'goal_failed'
14
+ | 'goal_killed'
15
+ | 'goal_health_changed'
16
+ | 'goal_escalated'
17
+ | 'goal_deleted'
18
+ | 'check_in_morning'
19
+ | 'check_in_evening'
20
+ | 'daily_actions_generated'
21
+ | 'replan_triggered';
22
+
23
+ export type GoalEvent = {
24
+ type: GoalEventType;
25
+ goalId?: string;
26
+ data: Record<string, unknown>;
27
+ timestamp: number;
28
+ };