@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,348 @@
1
+ /**
2
+ * GoalService — Core service for M16 Autonomous Goal Pursuit
3
+ *
4
+ * Manages goal lifecycle, daily rhythm (morning plan + evening review),
5
+ * accountability checks, health recalculation, and escalation.
6
+ * Implements the Service interface for daemon integration.
7
+ */
8
+
9
+ import type { Service, ServiceStatus } from '../daemon/services.ts';
10
+ import type { GoalEvent } from './events.ts';
11
+ import type { GoalConfig } from '../config/types.ts';
12
+ import type { Goal, GoalLevel, GoalStatus, GoalHealth } from './types.ts';
13
+ import * as vault from '../vault/goals.ts';
14
+
15
+ export class GoalService implements Service {
16
+ name = 'goals';
17
+ private _status: ServiceStatus = 'stopped';
18
+ private config: GoalConfig;
19
+ private eventCallback: ((event: GoalEvent) => void) | null = null;
20
+
21
+ // Timers
22
+ private rhythmTimer: Timer | null = null; // daily rhythm check (60s)
23
+ private accountabilityTimer: Timer | null = null; // accountability check (5min)
24
+ private healthTimer: Timer | null = null; // health recalc (15min)
25
+
26
+ constructor(config: GoalConfig) {
27
+ this.config = config;
28
+ }
29
+
30
+ /**
31
+ * Set callback for broadcasting goal events via WebSocket.
32
+ */
33
+ setEventCallback(cb: (event: GoalEvent) => void): void {
34
+ this.eventCallback = cb;
35
+ }
36
+
37
+ private emit(event: GoalEvent): void {
38
+ if (this.eventCallback) {
39
+ this.eventCallback(event);
40
+ }
41
+ }
42
+
43
+ async start(): Promise<void> {
44
+ if (!this.config.enabled) {
45
+ this._status = 'stopped';
46
+ console.log('[GoalService] Disabled by config');
47
+ return;
48
+ }
49
+
50
+ this._status = 'starting';
51
+
52
+ // Daily rhythm check — runs every 60s to detect morning/evening windows
53
+ this.rhythmTimer = setInterval(() => {
54
+ this.checkDailyRhythm().catch(err =>
55
+ console.error('[GoalService] Rhythm check error:', err)
56
+ );
57
+ }, 60_000);
58
+
59
+ // Accountability check — runs every 5min for escalation monitoring
60
+ this.accountabilityTimer = setInterval(() => {
61
+ this.checkAccountability().catch(err =>
62
+ console.error('[GoalService] Accountability check error:', err)
63
+ );
64
+ }, 5 * 60_000);
65
+
66
+ // Health recalculation — runs every 15min
67
+ this.healthTimer = setInterval(() => {
68
+ this.recalculateAllHealth().catch(err =>
69
+ console.error('[GoalService] Health recalc error:', err)
70
+ );
71
+ }, 15 * 60_000);
72
+
73
+ this._status = 'running';
74
+ console.log('[GoalService] Started (rhythm=60s, accountability=5min, health=15min)');
75
+ }
76
+
77
+ async stop(): Promise<void> {
78
+ this._status = 'stopping';
79
+
80
+ if (this.rhythmTimer) { clearInterval(this.rhythmTimer); this.rhythmTimer = null; }
81
+ if (this.accountabilityTimer) { clearInterval(this.accountabilityTimer); this.accountabilityTimer = null; }
82
+ if (this.healthTimer) { clearInterval(this.healthTimer); this.healthTimer = null; }
83
+
84
+ this._status = 'stopped';
85
+ console.log('[GoalService] Stopped');
86
+ }
87
+
88
+ status(): ServiceStatus {
89
+ return this._status;
90
+ }
91
+
92
+ // ── Goal CRUD with events ─────────────────────────────────────────
93
+
94
+ createGoal(title: string, level: GoalLevel, opts?: Parameters<typeof vault.createGoal>[2]): Goal {
95
+ const goal = vault.createGoal(title, level, opts);
96
+ this.emit({
97
+ type: 'goal_created',
98
+ goalId: goal.id,
99
+ data: { title, level, parent_id: goal.parent_id },
100
+ timestamp: Date.now(),
101
+ });
102
+ return goal;
103
+ }
104
+
105
+ getGoal(id: string): Goal | null {
106
+ return vault.getGoal(id);
107
+ }
108
+
109
+ updateGoal(id: string, updates: Parameters<typeof vault.updateGoal>[1]): Goal | null {
110
+ const goal = vault.updateGoal(id, updates);
111
+ if (goal) {
112
+ this.emit({
113
+ type: 'goal_updated',
114
+ goalId: id,
115
+ data: { updates },
116
+ timestamp: Date.now(),
117
+ });
118
+ }
119
+ return goal;
120
+ }
121
+
122
+ scoreGoal(id: string, score: number, reason: string, source = 'user'): Goal | null {
123
+ const goal = vault.updateGoalScore(id, score, reason, source);
124
+ if (goal) {
125
+ this.emit({
126
+ type: 'goal_scored',
127
+ goalId: id,
128
+ data: { score: goal.score, reason, source },
129
+ timestamp: Date.now(),
130
+ });
131
+ }
132
+ return goal;
133
+ }
134
+
135
+ updateStatus(id: string, status: GoalStatus): Goal | null {
136
+ const goal = vault.updateGoalStatus(id, status);
137
+ if (!goal) return null;
138
+
139
+ const eventType = status === 'completed' ? 'goal_completed'
140
+ : status === 'failed' ? 'goal_failed'
141
+ : status === 'killed' ? 'goal_killed'
142
+ : 'goal_status_changed';
143
+
144
+ this.emit({
145
+ type: eventType,
146
+ goalId: id,
147
+ data: { status },
148
+ timestamp: Date.now(),
149
+ });
150
+
151
+ // Extract goal completion data for vault knowledge
152
+ if (status === 'completed' || status === 'failed' || status === 'killed') {
153
+ try {
154
+ const { extractGoalCompletion } = require('../vault/extractor.ts');
155
+ extractGoalCompletion(goal);
156
+ } catch {
157
+ // Extractor may not be available — ignore
158
+ }
159
+ }
160
+
161
+ return goal;
162
+ }
163
+
164
+ updateHealth(id: string, health: GoalHealth): Goal | null {
165
+ const goal = vault.updateGoalHealth(id, health);
166
+ if (goal) {
167
+ this.emit({
168
+ type: 'goal_health_changed',
169
+ goalId: id,
170
+ data: { health },
171
+ timestamp: Date.now(),
172
+ });
173
+ }
174
+ return goal;
175
+ }
176
+
177
+ deleteGoal(id: string): boolean {
178
+ const result = vault.deleteGoal(id);
179
+ if (result) {
180
+ this.emit({
181
+ type: 'goal_deleted',
182
+ goalId: id,
183
+ data: {},
184
+ timestamp: Date.now(),
185
+ });
186
+ }
187
+ return result;
188
+ }
189
+
190
+ // ── Daily Rhythm ──────────────────────────────────────────────────
191
+
192
+ /**
193
+ * Check if we're in a morning or evening window and trigger check-ins.
194
+ * This is a lightweight timer check — the actual NL-driven rhythm
195
+ * is in src/goals/rhythm.ts (Phase 4).
196
+ */
197
+ private async checkDailyRhythm(): Promise<void> {
198
+ const now = new Date();
199
+ const hour = now.getHours();
200
+
201
+ const morningWindow = this.config.morning_window ?? { start: 7, end: 9 };
202
+ const eveningWindow = this.config.evening_window ?? { start: 20, end: 22 };
203
+
204
+ // Check morning window
205
+ if (hour >= morningWindow.start && hour < morningWindow.end) {
206
+ const existing = vault.getTodayCheckIn('morning_plan');
207
+ if (!existing) {
208
+ // Morning plan needed — Phase 4 (rhythm.ts) will handle the LLM-driven planning
209
+ // For now, just log that it's time
210
+ console.log('[GoalService] Morning plan window — check-in needed');
211
+ }
212
+ }
213
+
214
+ // Check evening window
215
+ if (hour >= eveningWindow.start && hour < eveningWindow.end) {
216
+ const existing = vault.getTodayCheckIn('evening_review');
217
+ if (!existing) {
218
+ console.log('[GoalService] Evening review window — check-in needed');
219
+ }
220
+ }
221
+ }
222
+
223
+ // ── Accountability ────────────────────────────────────────────────
224
+
225
+ /**
226
+ * Check active goals for escalation needs.
227
+ * Full drill-sergeant logic is in src/goals/accountability.ts (Phase 4).
228
+ */
229
+ private async checkAccountability(): Promise<void> {
230
+ const needingEscalation = vault.getGoalsNeedingEscalation();
231
+ const overdue = vault.getOverdueGoals();
232
+
233
+ for (const goal of needingEscalation) {
234
+ if (goal.escalation_stage === 'none') {
235
+ // Auto-escalate to 'pressure' stage
236
+ const escalationWeeks = this.config.escalation_weeks ?? { pressure: 1, root_cause: 3, suggest_kill: 4 };
237
+ const behindSince = goal.updated_at;
238
+ const weeksBehind = (Date.now() - behindSince) / (7 * 24 * 60 * 60 * 1000);
239
+
240
+ if (weeksBehind >= escalationWeeks.pressure) {
241
+ vault.updateGoalEscalation(goal.id, 'pressure');
242
+ this.emit({
243
+ type: 'goal_escalated',
244
+ goalId: goal.id,
245
+ data: { stage: 'pressure', weeksBehind },
246
+ timestamp: Date.now(),
247
+ });
248
+ }
249
+ } else if (goal.escalation_stage === 'pressure') {
250
+ const escalationWeeks = this.config.escalation_weeks ?? { pressure: 1, root_cause: 3, suggest_kill: 4 };
251
+ const startedAt = goal.escalation_started_at ?? goal.updated_at;
252
+ const weeksSinceEscalation = (Date.now() - startedAt) / (7 * 24 * 60 * 60 * 1000);
253
+
254
+ if (weeksSinceEscalation >= escalationWeeks.root_cause) {
255
+ vault.updateGoalEscalation(goal.id, 'root_cause');
256
+ this.emit({
257
+ type: 'goal_escalated',
258
+ goalId: goal.id,
259
+ data: { stage: 'root_cause', weeksSinceEscalation },
260
+ timestamp: Date.now(),
261
+ });
262
+ }
263
+ } else if (goal.escalation_stage === 'root_cause') {
264
+ const escalationWeeks = this.config.escalation_weeks ?? { pressure: 1, root_cause: 3, suggest_kill: 4 };
265
+ const startedAt = goal.escalation_started_at ?? goal.updated_at;
266
+ const weeksSinceEscalation = (Date.now() - startedAt) / (7 * 24 * 60 * 60 * 1000);
267
+
268
+ if (weeksSinceEscalation >= escalationWeeks.suggest_kill) {
269
+ vault.updateGoalEscalation(goal.id, 'suggest_kill');
270
+ this.emit({
271
+ type: 'goal_escalated',
272
+ goalId: goal.id,
273
+ data: { stage: 'suggest_kill', weeksSinceEscalation },
274
+ timestamp: Date.now(),
275
+ });
276
+ }
277
+ }
278
+ }
279
+
280
+ // Log overdue goals (Phase 4 will handle the drill-sergeant messaging)
281
+ if (overdue.length > 0) {
282
+ console.log(`[GoalService] ${overdue.length} overdue goal(s) detected`);
283
+ }
284
+ }
285
+
286
+ // ── Health Recalculation ──────────────────────────────────────────
287
+
288
+ /**
289
+ * Recalculate health for all active goals based on score and deadline.
290
+ */
291
+ private async recalculateAllHealth(): Promise<void> {
292
+ const activeGoals = vault.findGoals({ status: 'active' });
293
+ let changed = 0;
294
+
295
+ for (const goal of activeGoals) {
296
+ const newHealth = this.calculateHealth(goal);
297
+ if (newHealth !== goal.health) {
298
+ this.updateHealth(goal.id, newHealth);
299
+ changed++;
300
+ }
301
+ }
302
+
303
+ if (changed > 0) {
304
+ console.log(`[GoalService] Health recalculated: ${changed} goal(s) changed`);
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Calculate health for a single goal based on score progress vs time elapsed.
310
+ */
311
+ private calculateHealth(goal: Goal): GoalHealth {
312
+ // If no deadline, base purely on score
313
+ if (!goal.deadline) {
314
+ if (goal.score >= 0.6) return 'on_track';
315
+ if (goal.score >= 0.3) return 'at_risk';
316
+ return 'behind';
317
+ }
318
+
319
+ const now = Date.now();
320
+ const startTime = goal.started_at ?? goal.created_at;
321
+ const totalDuration = goal.deadline - startTime;
322
+ const elapsed = now - startTime;
323
+
324
+ // If past deadline
325
+ if (now > goal.deadline) {
326
+ if (goal.score >= 0.7) return 'on_track'; // nearly done
327
+ if (goal.score >= 0.4) return 'behind';
328
+ return 'critical';
329
+ }
330
+
331
+ // Ratio: how far along are we in time vs score
332
+ const timeRatio = totalDuration > 0 ? elapsed / totalDuration : 0;
333
+ const expectedScore = timeRatio * 0.7; // expecting 0.7 = good at deadline
334
+
335
+ const gap = expectedScore - goal.score;
336
+
337
+ if (gap <= 0) return 'on_track'; // ahead of pace
338
+ if (gap <= 0.15) return 'at_risk'; // slightly behind
339
+ if (gap <= 0.3) return 'behind'; // significantly behind
340
+ return 'critical'; // way behind
341
+ }
342
+
343
+ // ── Metrics ───────────────────────────────────────────────────────
344
+
345
+ getMetrics() {
346
+ return vault.getGoalMetrics();
347
+ }
348
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Goal Pursuit Types for M16 — Autonomous Goal Pursuit & Long-Term Planning
3
+ *
4
+ * OKR-style hierarchical goal system with Google-style 0.0-1.0 scoring.
5
+ * Goals nest: objective → key_result → milestone → task → daily_action.
6
+ */
7
+
8
+ // ── Enums ───────────────────────────────────────────────────────────
9
+
10
+ export type GoalLevel = 'objective' | 'key_result' | 'milestone' | 'task' | 'daily_action';
11
+
12
+ export type GoalStatus = 'draft' | 'active' | 'paused' | 'completed' | 'failed' | 'killed';
13
+
14
+ export type GoalHealth = 'on_track' | 'at_risk' | 'behind' | 'critical';
15
+
16
+ export type TimeHorizon = 'life' | 'yearly' | 'quarterly' | 'monthly' | 'weekly' | 'daily';
17
+
18
+ export type EscalationStage = 'none' | 'pressure' | 'root_cause' | 'suggest_kill';
19
+
20
+ export type ProgressType = 'manual' | 'auto_detected' | 'review' | 'system';
21
+
22
+ export type CheckInType = 'morning_plan' | 'evening_review';
23
+
24
+ // ── Core Types ──────────────────────────────────────────────────────
25
+
26
+ export type Goal = {
27
+ id: string;
28
+ parent_id: string | null;
29
+ level: GoalLevel;
30
+ title: string;
31
+ description: string;
32
+ success_criteria: string;
33
+ time_horizon: TimeHorizon;
34
+ score: number; // 0.0-1.0 OKR score (0.7 = good)
35
+ score_reason: string | null;
36
+ status: GoalStatus;
37
+ health: GoalHealth;
38
+ deadline: number | null; // epoch ms
39
+ started_at: number | null;
40
+ estimated_hours: number | null;
41
+ actual_hours: number;
42
+ authority_level: number; // min authority for actions within this goal
43
+ tags: string[];
44
+ dependencies: string[]; // goal IDs that must complete first
45
+ escalation_stage: EscalationStage;
46
+ escalation_started_at: number | null;
47
+ sort_order: number;
48
+ created_at: number;
49
+ updated_at: number;
50
+ completed_at: number | null;
51
+ };
52
+
53
+ export type GoalProgressEntry = {
54
+ id: string;
55
+ goal_id: string;
56
+ type: ProgressType;
57
+ score_before: number;
58
+ score_after: number;
59
+ note: string;
60
+ source: string; // 'user', 'awareness', 'daily_review', etc.
61
+ created_at: number;
62
+ };
63
+
64
+ export type GoalCheckIn = {
65
+ id: string;
66
+ type: CheckInType;
67
+ summary: string;
68
+ goals_reviewed: string[]; // goal IDs
69
+ actions_planned: string[]; // for morning
70
+ actions_completed: string[]; // for evening
71
+ created_at: number;
72
+ };
73
+
74
+ export type GoalEstimate = {
75
+ llm_estimate_hours: number;
76
+ historical_estimate_hours: number | null;
77
+ final_estimate_hours: number;
78
+ confidence: number; // 0.0-1.0
79
+ reasoning: string;
80
+ similar_past_goals: string[]; // vault entity IDs
81
+ };
82
+
83
+ // ── Query Types ─────────────────────────────────────────────────────
84
+
85
+ export type GoalQuery = {
86
+ status?: GoalStatus;
87
+ level?: GoalLevel;
88
+ parent_id?: string | null;
89
+ health?: GoalHealth;
90
+ tag?: string;
91
+ time_horizon?: TimeHorizon;
92
+ limit?: number;
93
+ };
94
+
95
+ export type GoalUpdate = {
96
+ title?: string;
97
+ description?: string;
98
+ success_criteria?: string;
99
+ time_horizon?: TimeHorizon;
100
+ deadline?: number | null;
101
+ estimated_hours?: number | null;
102
+ authority_level?: number;
103
+ tags?: string[];
104
+ dependencies?: string[];
105
+ sort_order?: number;
106
+ };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Goal → Workflow Bridge
3
+ *
4
+ * Auto-generates cron-triggered workflows for morning plan and evening review.
5
+ * Also supports creating recurring task workflows for goals with repeating work.
6
+ */
7
+
8
+ import type { GoalConfig } from '../config/types.ts';
9
+
10
+ /**
11
+ * Workflow definition for the goal system's daily rhythm.
12
+ * These can be registered with the TriggerManager when both
13
+ * the workflow engine and goal service are available.
14
+ */
15
+ export type GoalWorkflowDefinition = {
16
+ id: string;
17
+ name: string;
18
+ description: string;
19
+ triggerType: 'cron';
20
+ cronExpression: string;
21
+ action: 'morning_plan' | 'evening_review';
22
+ };
23
+
24
+ /**
25
+ * Generate workflow definitions for the daily rhythm check-ins.
26
+ * Returns cron-triggered workflow specs that can be registered with the engine.
27
+ */
28
+ export function generateRhythmWorkflows(config: GoalConfig): GoalWorkflowDefinition[] {
29
+ if (!config.enabled) return [];
30
+
31
+ const workflows: GoalWorkflowDefinition[] = [];
32
+
33
+ const morningWindow = config.morning_window ?? { start: 7, end: 9 };
34
+ const eveningWindow = config.evening_window ?? { start: 20, end: 22 };
35
+
36
+ // Morning plan: fire at the start of the morning window
37
+ workflows.push({
38
+ id: 'goal_morning_plan',
39
+ name: 'Morning Goal Plan',
40
+ description: 'Automated morning planning session — reviews active goals, generates daily actions, and sets focus areas.',
41
+ triggerType: 'cron',
42
+ cronExpression: `0 ${morningWindow.start} * * *`, // e.g., "0 7 * * *"
43
+ action: 'morning_plan',
44
+ });
45
+
46
+ // Evening review: fire at the start of the evening window
47
+ workflows.push({
48
+ id: 'goal_evening_review',
49
+ name: 'Evening Goal Review',
50
+ description: 'Automated evening review — scores daily progress, generates accountability assessment, and updates goal health.',
51
+ triggerType: 'cron',
52
+ cronExpression: `0 ${eveningWindow.start} * * *`, // e.g., "0 20 * * *"
53
+ action: 'evening_review',
54
+ });
55
+
56
+ return workflows;
57
+ }
58
+
59
+ /**
60
+ * Register goal rhythm workflows with the trigger manager.
61
+ * This bridges goal check-ins with the workflow execution system.
62
+ *
63
+ * When the workflow fires, it calls the goalService's daily rhythm methods.
64
+ */
65
+ export function registerGoalWorkflows(
66
+ goalWorkflows: GoalWorkflowDefinition[],
67
+ triggerManager: { fireTrigger: (workflowId: string, triggerType: string, data?: Record<string, unknown>) => void },
68
+ ): void {
69
+ // Note: The actual cron scheduling is handled by the GoalService's own timers
70
+ // (checkDailyRhythm runs every 60s and checks the time window).
71
+ // This function exists for future use when we want to create
72
+ // full workflow graph executions for morning/evening routines.
73
+ //
74
+ // For now, we just log the available workflows.
75
+ for (const wf of goalWorkflows) {
76
+ console.log(`[GoalWorkflowBridge] Registered: ${wf.name} (${wf.cronExpression})`);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Handle a goal workflow trigger firing.
82
+ * Called from the goal service when daily rhythm check-ins happen.
83
+ */
84
+ export async function handleGoalWorkflowTrigger(
85
+ action: 'morning_plan' | 'evening_review',
86
+ goalService: { getGoal: (id: string) => unknown },
87
+ onComplete?: (result: Record<string, unknown>) => void,
88
+ ): Promise<void> {
89
+ console.log(`[GoalWorkflowBridge] Executing ${action}`);
90
+
91
+ // The actual planning/review logic is in rhythm.ts
92
+ // This bridge just provides the workflow integration layer
93
+ if (onComplete) {
94
+ onComplete({ action, completedAt: Date.now() });
95
+ }
96
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Google API Wrappers
3
+ *
4
+ * Thin wrappers around Gmail and Calendar REST APIs.
5
+ * Uses raw fetch() — no googleapis package needed.
6
+ */
7
+
8
+ const GMAIL_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me';
9
+ const CALENDAR_BASE = 'https://www.googleapis.com/calendar/v3';
10
+
11
+ // --- Types ---
12
+
13
+ export type GmailMessage = {
14
+ id: string;
15
+ threadId: string;
16
+ };
17
+
18
+ export type GmailMessageDetail = {
19
+ id: string;
20
+ threadId: string;
21
+ subject: string;
22
+ from: string;
23
+ to: string;
24
+ date: string;
25
+ snippet: string;
26
+ labels: string[];
27
+ };
28
+
29
+ export type CalendarEvent = {
30
+ id: string;
31
+ summary: string;
32
+ description?: string;
33
+ start: string;
34
+ end: string;
35
+ location?: string;
36
+ attendees: string[];
37
+ htmlLink?: string;
38
+ };
39
+
40
+ // --- Gmail ---
41
+
42
+ export async function listUnreadEmails(
43
+ accessToken: string,
44
+ maxResults: number = 10
45
+ ): Promise<GmailMessage[]> {
46
+ const params = new URLSearchParams({
47
+ q: 'is:unread',
48
+ maxResults: String(maxResults),
49
+ });
50
+
51
+ const resp = await fetch(`${GMAIL_BASE}/messages?${params}`, {
52
+ headers: { Authorization: `Bearer ${accessToken}` },
53
+ });
54
+
55
+ if (!resp.ok) {
56
+ throw new Error(`Gmail list failed: ${resp.status} ${resp.statusText}`);
57
+ }
58
+
59
+ const data = await resp.json() as any;
60
+ return (data.messages ?? []).map((m: any) => ({
61
+ id: m.id,
62
+ threadId: m.threadId,
63
+ }));
64
+ }
65
+
66
+ export async function getEmailDetail(
67
+ accessToken: string,
68
+ messageId: string
69
+ ): Promise<GmailMessageDetail> {
70
+ const resp = await fetch(`${GMAIL_BASE}/messages/${messageId}?format=metadata&metadataHeaders=Subject&metadataHeaders=From&metadataHeaders=To&metadataHeaders=Date`, {
71
+ headers: { Authorization: `Bearer ${accessToken}` },
72
+ });
73
+
74
+ if (!resp.ok) {
75
+ throw new Error(`Gmail get failed: ${resp.status} ${resp.statusText}`);
76
+ }
77
+
78
+ const data = await resp.json() as any;
79
+ const headers = data.payload?.headers ?? [];
80
+
81
+ function getHeader(name: string): string {
82
+ return headers.find((h: any) => h.name === name)?.value ?? '';
83
+ }
84
+
85
+ return {
86
+ id: data.id,
87
+ threadId: data.threadId,
88
+ subject: getHeader('Subject'),
89
+ from: getHeader('From'),
90
+ to: getHeader('To'),
91
+ date: getHeader('Date'),
92
+ snippet: data.snippet ?? '',
93
+ labels: data.labelIds ?? [],
94
+ };
95
+ }
96
+
97
+ // --- Calendar ---
98
+
99
+ export async function listUpcomingEvents(
100
+ accessToken: string,
101
+ calendarId: string,
102
+ timeMin: string,
103
+ timeMax: string,
104
+ maxResults: number = 20
105
+ ): Promise<CalendarEvent[]> {
106
+ const params = new URLSearchParams({
107
+ timeMin,
108
+ timeMax,
109
+ maxResults: String(maxResults),
110
+ singleEvents: 'true',
111
+ orderBy: 'startTime',
112
+ });
113
+
114
+ const resp = await fetch(
115
+ `${CALENDAR_BASE}/calendars/${encodeURIComponent(calendarId)}/events?${params}`,
116
+ { headers: { Authorization: `Bearer ${accessToken}` } }
117
+ );
118
+
119
+ if (!resp.ok) {
120
+ throw new Error(`Calendar list failed: ${resp.status} ${resp.statusText}`);
121
+ }
122
+
123
+ const data = await resp.json() as any;
124
+ return (data.items ?? []).map((event: any) => ({
125
+ id: event.id,
126
+ summary: event.summary ?? '(No title)',
127
+ description: event.description,
128
+ start: event.start?.dateTime ?? event.start?.date ?? '',
129
+ end: event.end?.dateTime ?? event.end?.date ?? '',
130
+ location: event.location,
131
+ attendees: (event.attendees ?? []).map((a: any) => a.email).filter(Boolean),
132
+ htmlLink: event.htmlLink,
133
+ }));
134
+ }