@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,197 @@
1
+ /**
2
+ * NotificationListener — D-Bus Notification Monitor (Linux/WSL2)
3
+ *
4
+ * Monitors system notifications by watching D-Bus for
5
+ * org.freedesktop.Notifications.Notify method calls.
6
+ * Parses notification fields: app_name, summary, body, urgency.
7
+ *
8
+ * Graceful: if dbus-monitor is not found, logs warning and stays no-op.
9
+ */
10
+
11
+ import type { Observer, ObserverEventHandler } from './index';
12
+ import type { Subprocess } from 'bun';
13
+
14
+ type NotificationData = {
15
+ app: string;
16
+ title: string;
17
+ body: string;
18
+ urgency: string;
19
+ };
20
+
21
+ export class NotificationListener implements Observer {
22
+ name = 'notifications';
23
+ private running = false;
24
+ private handler: ObserverEventHandler | null = null;
25
+ private process: Subprocess | null = null;
26
+ private available = false;
27
+
28
+ async start(): Promise<void> {
29
+ this.running = true;
30
+
31
+ // Check if dbus-monitor exists
32
+ try {
33
+ const check = Bun.spawnSync(['which', 'dbus-monitor']);
34
+ if (check.exitCode !== 0) {
35
+ console.log('[notifications] dbus-monitor not found — notification monitoring disabled');
36
+ return;
37
+ }
38
+ this.available = true;
39
+ } catch {
40
+ console.log('[notifications] Cannot check for dbus-monitor — notification monitoring disabled');
41
+ return;
42
+ }
43
+
44
+ // Spawn dbus-monitor watching for Notify signals
45
+ try {
46
+ this.process = Bun.spawn(
47
+ [
48
+ 'dbus-monitor',
49
+ '--session',
50
+ "interface='org.freedesktop.Notifications',member='Notify'",
51
+ ],
52
+ {
53
+ stdout: 'pipe',
54
+ stderr: 'ignore',
55
+ }
56
+ );
57
+
58
+ console.log('[notifications] Observer started — monitoring D-Bus notifications');
59
+
60
+ // Parse stdout in background
61
+ this.readOutput();
62
+ } catch (err) {
63
+ console.error('[notifications] Failed to start dbus-monitor:', err);
64
+ this.available = false;
65
+ }
66
+ }
67
+
68
+ async stop(): Promise<void> {
69
+ this.running = false;
70
+
71
+ if (this.process) {
72
+ try {
73
+ this.process.kill();
74
+ } catch {
75
+ // Ignore
76
+ }
77
+ this.process = null;
78
+ }
79
+
80
+ console.log('[notifications] Observer stopped');
81
+ }
82
+
83
+ isRunning(): boolean {
84
+ return this.running;
85
+ }
86
+
87
+ onEvent(handler: ObserverEventHandler): void {
88
+ this.handler = handler;
89
+ }
90
+
91
+ /**
92
+ * Read dbus-monitor output and parse notification blocks.
93
+ *
94
+ * D-Bus notification format (method_call):
95
+ * method call ... dest=org.freedesktop.Notifications ... member=Notify
96
+ * string "app_name"
97
+ * uint32 ...
98
+ * string "icon"
99
+ * string "summary"
100
+ * string "body"
101
+ * array [ ... ]
102
+ * ...
103
+ */
104
+ private async readOutput(): Promise<void> {
105
+ if (!this.process?.stdout) return;
106
+
107
+ const reader = (this.process.stdout as ReadableStream<Uint8Array>).getReader();
108
+ const decoder = new TextDecoder();
109
+ let buffer = '';
110
+
111
+ // State machine for parsing method_call blocks
112
+ let inMethodCall = false;
113
+ let stringIndex = 0;
114
+ let currentNotification: Partial<NotificationData> = {};
115
+
116
+ try {
117
+ while (this.running) {
118
+ const { done, value } = await reader.read();
119
+ if (done) break;
120
+
121
+ buffer += decoder.decode(value, { stream: true });
122
+ const lines = buffer.split('\n');
123
+ buffer = lines.pop() ?? '';
124
+
125
+ for (const line of lines) {
126
+ const trimmed = line.trim();
127
+
128
+ // Detect start of a new method call
129
+ if (trimmed.startsWith('method call') && trimmed.includes('member=Notify')) {
130
+ inMethodCall = true;
131
+ stringIndex = 0;
132
+ currentNotification = {};
133
+ continue;
134
+ }
135
+
136
+ // Detect start of next method/signal (end of current block)
137
+ if (inMethodCall && (trimmed.startsWith('method call') || trimmed.startsWith('signal') || trimmed.startsWith('method return'))) {
138
+ // Emit the notification if we got enough data
139
+ this.emitNotification(currentNotification);
140
+ inMethodCall = trimmed.startsWith('method call') && trimmed.includes('member=Notify');
141
+ stringIndex = 0;
142
+ currentNotification = {};
143
+ continue;
144
+ }
145
+
146
+ if (!inMethodCall) continue;
147
+
148
+ // Parse string values — the Notify method has these string args in order:
149
+ // 0: app_name, 1: (replaces_id is uint32, skip), 2: icon, 3: summary, 4: body
150
+ const stringMatch = trimmed.match(/^string\s+"(.*)"/);
151
+ if (stringMatch) {
152
+ const val = stringMatch[1];
153
+ switch (stringIndex) {
154
+ case 0: currentNotification.app = val; break;
155
+ // index 1 is icon (we skip)
156
+ case 1: break; // icon
157
+ case 2: currentNotification.title = val; break;
158
+ case 3: currentNotification.body = val; break;
159
+ }
160
+ stringIndex++;
161
+ continue;
162
+ }
163
+
164
+ // Parse urgency from hints dict (byte value)
165
+ const urgencyMatch = trimmed.match(/byte\s+(\d+)/);
166
+ if (urgencyMatch) {
167
+ const urgencyLevel = parseInt(urgencyMatch[1]!);
168
+ currentNotification.urgency =
169
+ urgencyLevel === 2 ? 'critical' :
170
+ urgencyLevel === 1 ? 'normal' :
171
+ 'low';
172
+ }
173
+ }
174
+ }
175
+ } catch (err) {
176
+ if (this.running) {
177
+ console.error('[notifications] Read error:', err);
178
+ }
179
+ }
180
+ }
181
+
182
+ private emitNotification(data: Partial<NotificationData>): void {
183
+ if (!data.title && !data.body) return;
184
+ if (!this.handler) return;
185
+
186
+ this.handler({
187
+ type: 'notification',
188
+ data: {
189
+ app: data.app ?? 'unknown',
190
+ title: data.title ?? '',
191
+ body: data.body ?? '',
192
+ urgency: data.urgency ?? 'normal',
193
+ },
194
+ timestamp: Date.now(),
195
+ });
196
+ }
197
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Tests for Observer Layer
3
+ */
4
+
5
+ import { test, expect, describe, beforeAll, afterAll } from 'bun:test';
6
+ import { mkdtempSync, rmSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+ import {
10
+ ObserverManager,
11
+ FileWatcher,
12
+ ClipboardMonitor,
13
+ ProcessMonitor,
14
+ NotificationListener,
15
+ CalendarSync,
16
+ EmailSync,
17
+ type ObserverEvent,
18
+ } from './index';
19
+
20
+ // Use an isolated temp dir instead of /tmp to avoid slow recursive watches on CI
21
+ let testDir: string;
22
+ beforeAll(() => {
23
+ testDir = mkdtempSync(join(tmpdir(), 'jarvis-test-'));
24
+ });
25
+ afterAll(() => {
26
+ try { rmSync(testDir, { recursive: true }); } catch {}
27
+ });
28
+
29
+ describe('ObserverManager', () => {
30
+ test('registers observers', () => {
31
+ const manager = new ObserverManager();
32
+ const watcher = new FileWatcher([testDir]);
33
+
34
+ manager.register(watcher);
35
+
36
+ expect(manager.listObservers()).toEqual(['file-watcher']);
37
+ });
38
+
39
+ test('propagates event handler to observers', () => {
40
+ const manager = new ObserverManager();
41
+ const watcher = new FileWatcher([testDir]);
42
+
43
+ manager.register(watcher);
44
+
45
+ let handlerCalled = false;
46
+ manager.setEventHandler(() => {
47
+ handlerCalled = true;
48
+ });
49
+
50
+ // Handler should be set on the observer
51
+ expect(handlerCalled).toBe(false); // Not called yet, just registered
52
+ });
53
+
54
+ test('starts and stops all observers', async () => {
55
+ const manager = new ObserverManager();
56
+ const watcher = new FileWatcher([testDir]);
57
+ const clipboard = new ClipboardMonitor(5000);
58
+
59
+ manager.register(watcher);
60
+ manager.register(clipboard);
61
+
62
+ await manager.startAll();
63
+
64
+ const status = manager.getStatus();
65
+ expect(status['file-watcher']).toBe(true);
66
+ expect(status['clipboard']).toBe(true);
67
+
68
+ await manager.stopAll();
69
+
70
+ const statusAfter = manager.getStatus();
71
+ expect(statusAfter['file-watcher']).toBe(false);
72
+ expect(statusAfter['clipboard']).toBe(false);
73
+ });
74
+
75
+ test('starts and stops individual observers', async () => {
76
+ const manager = new ObserverManager();
77
+ const watcher = new FileWatcher([testDir]);
78
+
79
+ manager.register(watcher);
80
+
81
+ await manager.startObserver('file-watcher');
82
+ expect(manager.getStatus()['file-watcher']).toBe(true);
83
+
84
+ await manager.stopObserver('file-watcher');
85
+ expect(manager.getStatus()['file-watcher']).toBe(false);
86
+ });
87
+ });
88
+
89
+ describe('FileWatcher', () => {
90
+ test('starts and stops', async () => {
91
+ const watcher = new FileWatcher([testDir]);
92
+
93
+ expect(watcher.isRunning()).toBe(false);
94
+
95
+ await watcher.start();
96
+ expect(watcher.isRunning()).toBe(true);
97
+
98
+ await watcher.stop();
99
+ expect(watcher.isRunning()).toBe(false);
100
+ });
101
+
102
+ test('prevents double start', async () => {
103
+ const watcher = new FileWatcher([testDir]);
104
+
105
+ await watcher.start();
106
+ await watcher.start(); // Should not throw
107
+
108
+ expect(watcher.isRunning()).toBe(true);
109
+
110
+ await watcher.stop();
111
+ });
112
+ });
113
+
114
+ describe('ClipboardMonitor', () => {
115
+ test('starts and stops', async () => {
116
+ const clipboard = new ClipboardMonitor(5000);
117
+
118
+ expect(clipboard.isRunning()).toBe(false);
119
+
120
+ await clipboard.start();
121
+ expect(clipboard.isRunning()).toBe(true);
122
+
123
+ await clipboard.stop();
124
+ expect(clipboard.isRunning()).toBe(false);
125
+ });
126
+
127
+ test('uses custom poll interval', async () => {
128
+ const clipboard = new ClipboardMonitor(10000);
129
+
130
+ await clipboard.start();
131
+ expect(clipboard.isRunning()).toBe(true);
132
+
133
+ await clipboard.stop();
134
+ });
135
+ });
136
+
137
+ describe('ProcessMonitor', () => {
138
+ test('starts and stops', async () => {
139
+ const monitor = new ProcessMonitor(10000);
140
+
141
+ expect(monitor.isRunning()).toBe(false);
142
+
143
+ await monitor.start();
144
+ expect(monitor.isRunning()).toBe(true);
145
+
146
+ await monitor.stop();
147
+ expect(monitor.isRunning()).toBe(false);
148
+ });
149
+
150
+ test('gets process list', async () => {
151
+ const monitor = new ProcessMonitor(10000);
152
+
153
+ const processes = await monitor.getProcessList();
154
+
155
+ expect(Array.isArray(processes)).toBe(true);
156
+ expect(processes.length).toBeGreaterThan(0);
157
+
158
+ // Check structure of first process
159
+ const proc = processes[0];
160
+ expect(proc).toHaveProperty('pid');
161
+ expect(proc).toHaveProperty('name');
162
+ expect(proc).toHaveProperty('cpu');
163
+ expect(proc).toHaveProperty('memory');
164
+ });
165
+ });
166
+
167
+ describe('Stub Observers', () => {
168
+ test('NotificationListener starts and stops', async () => {
169
+ const listener = new NotificationListener();
170
+
171
+ expect(listener.isRunning()).toBe(false);
172
+
173
+ await listener.start();
174
+ expect(listener.isRunning()).toBe(true);
175
+
176
+ await listener.stop();
177
+ expect(listener.isRunning()).toBe(false);
178
+ });
179
+
180
+ test('CalendarSync starts and stops', async () => {
181
+ const sync = new CalendarSync();
182
+
183
+ expect(sync.isRunning()).toBe(false);
184
+
185
+ await sync.start();
186
+ expect(sync.isRunning()).toBe(true);
187
+
188
+ await sync.stop();
189
+ expect(sync.isRunning()).toBe(false);
190
+ });
191
+
192
+ test('EmailSync starts and stops', async () => {
193
+ const sync = new EmailSync();
194
+
195
+ expect(sync.isRunning()).toBe(false);
196
+
197
+ await sync.start();
198
+ expect(sync.isRunning()).toBe(true);
199
+
200
+ await sync.stop();
201
+ expect(sync.isRunning()).toBe(false);
202
+ });
203
+ });
@@ -0,0 +1,225 @@
1
+ /**
2
+ * ProcessMonitor - Monitors running processes
3
+ *
4
+ * Polls the system process list at regular intervals and detects when processes
5
+ * start or terminate. Emits events for process lifecycle changes.
6
+ */
7
+
8
+ import type { Observer, ObserverEvent, ObserverEventHandler } from './index';
9
+
10
+ export type ProcessInfo = {
11
+ pid: number;
12
+ name: string;
13
+ cpu: number;
14
+ memory: number;
15
+ };
16
+
17
+ export class ProcessMonitor implements Observer {
18
+ name = 'processes';
19
+ private interval: Timer | null = null;
20
+ private knownProcesses: Map<number, string> = new Map();
21
+ private handler: ObserverEventHandler | null = null;
22
+ private running = false;
23
+ private pollMs: number;
24
+
25
+ constructor(pollMs: number = 5000) {
26
+ this.pollMs = pollMs;
27
+ }
28
+
29
+ async start(): Promise<void> {
30
+ if (this.running) {
31
+ console.log('[processes] Already running');
32
+ return;
33
+ }
34
+
35
+ console.log(`[processes] Starting process monitoring (polling every ${this.pollMs}ms)...`);
36
+
37
+ // Initialize with current process list
38
+ try {
39
+ const processes = await this.getProcessList();
40
+ for (const proc of processes) {
41
+ this.knownProcesses.set(proc.pid, proc.name);
42
+ }
43
+ console.log(`[processes] Initialized with ${processes.length} processes`);
44
+ } catch (error) {
45
+ console.error('[processes] Failed to get initial process list:', error);
46
+ }
47
+
48
+ // Start polling
49
+ this.interval = setInterval(async () => {
50
+ try {
51
+ const processes = await this.getProcessList();
52
+ const currentPids = new Set<number>();
53
+
54
+ // Check for new processes
55
+ for (const proc of processes) {
56
+ currentPids.add(proc.pid);
57
+
58
+ if (!this.knownProcesses.has(proc.pid)) {
59
+ // New process detected
60
+ this.knownProcesses.set(proc.pid, proc.name);
61
+
62
+ if (this.handler) {
63
+ const event: ObserverEvent = {
64
+ type: 'process_started',
65
+ data: {
66
+ pid: proc.pid,
67
+ name: proc.name,
68
+ cpu: proc.cpu,
69
+ memory: proc.memory,
70
+ },
71
+ timestamp: Date.now(),
72
+ };
73
+
74
+ this.handler(event);
75
+ }
76
+ }
77
+ }
78
+
79
+ // Check for terminated processes
80
+ for (const [pid, name] of this.knownProcesses.entries()) {
81
+ if (!currentPids.has(pid)) {
82
+ // Process terminated
83
+ this.knownProcesses.delete(pid);
84
+
85
+ if (this.handler) {
86
+ const event: ObserverEvent = {
87
+ type: 'process_stopped',
88
+ data: {
89
+ pid,
90
+ name,
91
+ },
92
+ timestamp: Date.now(),
93
+ };
94
+
95
+ this.handler(event);
96
+ }
97
+ }
98
+ }
99
+ } catch (error) {
100
+ console.error('[processes] Failed to monitor processes:', error);
101
+ }
102
+ }, this.pollMs);
103
+
104
+ this.running = true;
105
+ }
106
+
107
+ async stop(): Promise<void> {
108
+ if (!this.running) {
109
+ return;
110
+ }
111
+
112
+ console.log('[processes] Stopping process monitoring...');
113
+
114
+ if (this.interval) {
115
+ clearInterval(this.interval);
116
+ this.interval = null;
117
+ }
118
+
119
+ this.knownProcesses.clear();
120
+ this.running = false;
121
+ }
122
+
123
+ isRunning(): boolean {
124
+ return this.running;
125
+ }
126
+
127
+ onEvent(handler: ObserverEventHandler): void {
128
+ this.handler = handler;
129
+ }
130
+
131
+ /**
132
+ * Get list of running processes
133
+ */
134
+ async getProcessList(): Promise<ProcessInfo[]> {
135
+ const platform = process.platform;
136
+
137
+ try {
138
+ if (platform === 'linux' || platform === 'darwin') {
139
+ // Use ps command for Unix-like systems
140
+ const result = await Bun.$`ps aux --no-headers`.quiet();
141
+ const output = result.stdout.toString();
142
+
143
+ return this.parsePS(output);
144
+ } else if (platform === 'win32') {
145
+ // Use PowerShell for Windows
146
+ const result = await Bun.$`powershell.exe Get-Process | Select-Object Id,Name,CPU,WorkingSet | ConvertTo-Csv -NoTypeInformation`.quiet();
147
+ const output = result.stdout.toString();
148
+
149
+ return this.parseWindowsPS(output);
150
+ } else {
151
+ throw new Error(`Unsupported platform: ${platform}`);
152
+ }
153
+ } catch (error) {
154
+ console.error('[processes] Failed to get process list:', error);
155
+ return [];
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Parse output from Unix ps command
161
+ */
162
+ private parsePS(output: string): ProcessInfo[] {
163
+ const processes: ProcessInfo[] = [];
164
+ const lines = output.split('\n').filter(line => line.trim());
165
+
166
+ for (const line of lines) {
167
+ // ps aux format: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
168
+ const parts = line.trim().split(/\s+/);
169
+
170
+ if (parts.length < 11) {
171
+ continue;
172
+ }
173
+
174
+ const pid = parseInt(parts[1]!, 10);
175
+ const cpu = parseFloat(parts[2]!);
176
+ const memory = parseFloat(parts[3]!);
177
+ const name = parts.slice(10).join(' '); // COMMAND can have spaces
178
+
179
+ if (!isNaN(pid)) {
180
+ processes.push({
181
+ pid,
182
+ name,
183
+ cpu,
184
+ memory,
185
+ });
186
+ }
187
+ }
188
+
189
+ return processes;
190
+ }
191
+
192
+ /**
193
+ * Parse output from Windows PowerShell Get-Process
194
+ */
195
+ private parseWindowsPS(output: string): ProcessInfo[] {
196
+ const processes: ProcessInfo[] = [];
197
+ const lines = output.split('\n').filter(line => line.trim());
198
+
199
+ // Skip header line
200
+ for (let i = 1; i < lines.length; i++) {
201
+ const line = lines[i]!;
202
+ const parts = line.split(',').map(p => p.replace(/"/g, '').trim());
203
+
204
+ if (parts.length < 4) {
205
+ continue;
206
+ }
207
+
208
+ const pid = parseInt(parts[0]!, 10);
209
+ const name: string = parts[1]!;
210
+ const cpu = parseFloat(parts[2]!) || 0;
211
+ const memory = parseFloat(parts[3]!) || 0;
212
+
213
+ if (!isNaN(pid)) {
214
+ processes.push({
215
+ pid,
216
+ name,
217
+ cpu,
218
+ memory,
219
+ });
220
+ }
221
+ }
222
+
223
+ return processes;
224
+ }
225
+ }
@@ -0,0 +1,61 @@
1
+ # Personality Engine
2
+
3
+ An adaptive personality system for J.A.R.V.I.S. that learns user preferences and adapts communication style over time.
4
+
5
+ ## Quick Start
6
+
7
+ ```typescript
8
+ import { initDatabase } from '@/vault/schema';
9
+ import {
10
+ getPersonality,
11
+ savePersonality,
12
+ extractSignals,
13
+ applySignals,
14
+ recordInteraction,
15
+ personalityToPrompt,
16
+ } from '@/personality';
17
+
18
+ // Initialize database
19
+ initDatabase('./data/jarvis.db');
20
+
21
+ // Process a user interaction
22
+ let personality = getPersonality();
23
+ const signals = extractSignals("Keep it brief", "Sure!");
24
+ personality = applySignals(personality, signals);
25
+ personality = recordInteraction(personality);
26
+ savePersonality(personality);
27
+
28
+ // Generate LLM prompt
29
+ const prompt = personalityToPrompt(personality);
30
+ ```
31
+
32
+ ## Modules
33
+
34
+ - **`model.ts`**: Personality state structure and persistence
35
+ - **`learner.ts`**: Signal extraction and learning
36
+ - **`adapter.ts`**: Channel adaptation and prompt generation
37
+ - **`index.ts`**: Public API exports
38
+
39
+ ## Features
40
+
41
+ ✓ Learns from user feedback (verbosity, formality, humor)
42
+ ✓ Detects emoji usage preferences
43
+ ✓ Adapts to different channels (WhatsApp, Email, Terminal)
44
+ ✓ Builds trust over time
45
+ ✓ Generates personality-aware LLM prompts
46
+
47
+ ## Documentation
48
+
49
+ See [docs/PERSONALITY_ENGINE.md](~/jarvis/docs/PERSONALITY_ENGINE.md) for full documentation.
50
+
51
+ ## Testing
52
+
53
+ ```bash
54
+ bun test src/personality/personality.test.ts
55
+ ```
56
+
57
+ ## Demo
58
+
59
+ ```bash
60
+ bun run examples/personality-demo.ts
61
+ ```