@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,161 @@
1
+ /**
2
+ * RPC Tracker — Two-Timeout State Machine
3
+ *
4
+ * Tracks outbound RPC requests with an initial timeout (fast ack)
5
+ * and a max timeout (detached execution). Resolves promises accordingly.
6
+ */
7
+
8
+ import type { RPCState, RPCTimeouts, RPCRequest } from './protocol.ts';
9
+ import { DEFAULT_RPC_TIMEOUTS } from './protocol.ts';
10
+
11
+ export interface PendingRPC {
12
+ id: string;
13
+ sidecarId: string;
14
+ method: string;
15
+ state: RPCState;
16
+ createdAt: number;
17
+ /** Resolves the dispatch() promise. For pending: result value. For detached: "detached" string. */
18
+ resolve: (value: unknown) => void;
19
+ reject: (error: Error) => void;
20
+ initialTimer: Timer | null;
21
+ maxTimer: Timer | null;
22
+ /** Callback invoked when a detached RPC completes */
23
+ detachedCallback?: (rpcId: string, result: unknown) => void;
24
+ }
25
+
26
+ export type DetachedCompleteCallback = (rpcId: string, result: unknown, error?: Error) => void;
27
+
28
+ export class RPCTracker {
29
+ private pending = new Map<string, PendingRPC>();
30
+ private detachedCallback: DetachedCompleteCallback | null = null;
31
+
32
+ /** Register a global callback for when detached RPCs complete */
33
+ onDetachedComplete(callback: DetachedCompleteCallback): void {
34
+ this.detachedCallback = callback;
35
+ }
36
+
37
+ /**
38
+ * Dispatch an RPC: creates a tracked entry, sets initial timer.
39
+ * Returns the result if it arrives within initial_timeout, or "detached" if it transitions.
40
+ */
41
+ dispatch(
42
+ rpcId: string,
43
+ sidecarId: string,
44
+ method: string,
45
+ timeouts: RPCTimeouts = DEFAULT_RPC_TIMEOUTS,
46
+ ): Promise<unknown> {
47
+ return new Promise((resolve, reject) => {
48
+ const entry: PendingRPC = {
49
+ id: rpcId,
50
+ sidecarId,
51
+ method,
52
+ state: 'pending',
53
+ createdAt: Date.now(),
54
+ resolve,
55
+ reject,
56
+ initialTimer: null,
57
+ maxTimer: null,
58
+ };
59
+
60
+ // Initial timeout: transition to DETACHED
61
+ entry.initialTimer = setTimeout(() => {
62
+ if (entry.state !== 'pending') return;
63
+
64
+ entry.state = 'detached';
65
+ entry.initialTimer = null;
66
+
67
+ // Resolve the dispatch promise with "detached"
68
+ resolve('detached');
69
+
70
+ // Start max timeout
71
+ entry.maxTimer = setTimeout(() => {
72
+ if (entry.state !== 'detached') return;
73
+ entry.state = 'timed_out';
74
+ this.pending.delete(rpcId);
75
+ console.warn(`[RPCTracker] RPC ${rpcId} (${method}) timed out after max timeout`);
76
+ }, timeouts.max);
77
+ }, timeouts.initial);
78
+
79
+ this.pending.set(rpcId, entry);
80
+ });
81
+ }
82
+
83
+ /** Called when an rpc_result event arrives. Resolves/rejects the RPC. */
84
+ resolve(rpcId: string, result: unknown): void {
85
+ const entry = this.pending.get(rpcId);
86
+ if (!entry) return;
87
+
88
+ this.clearTimers(entry);
89
+
90
+ if (entry.state === 'pending') {
91
+ // Fast path: result arrived within initial timeout
92
+ entry.state = 'completed';
93
+ entry.resolve(result);
94
+ } else if (entry.state === 'detached') {
95
+ // Late arrival: dispatch promise already resolved with "detached"
96
+ entry.state = 'completed';
97
+ this.detachedCallback?.(rpcId, result);
98
+ }
99
+
100
+ this.pending.delete(rpcId);
101
+ }
102
+
103
+ /** Called when an rpc_result with error arrives. */
104
+ fail(rpcId: string, error: Error): void {
105
+ const entry = this.pending.get(rpcId);
106
+ if (!entry) return;
107
+
108
+ this.clearTimers(entry);
109
+
110
+ if (entry.state === 'pending') {
111
+ entry.state = 'failed';
112
+ entry.reject(error);
113
+ } else if (entry.state === 'detached') {
114
+ entry.state = 'failed';
115
+ this.detachedCallback?.(rpcId, undefined, error);
116
+ }
117
+
118
+ this.pending.delete(rpcId);
119
+ }
120
+
121
+ /** Fail all pending RPCs for a disconnected sidecar */
122
+ failAll(sidecarId: string, reason: string): void {
123
+ for (const [rpcId, entry] of this.pending) {
124
+ if (entry.sidecarId !== sidecarId) continue;
125
+
126
+ this.clearTimers(entry);
127
+
128
+ const error = new Error(`Sidecar disconnected: ${reason}`);
129
+ if (entry.state === 'pending') {
130
+ entry.state = 'failed';
131
+ entry.reject(error);
132
+ } else if (entry.state === 'detached') {
133
+ entry.state = 'failed';
134
+ this.detachedCallback?.(rpcId, undefined, error);
135
+ }
136
+
137
+ this.pending.delete(rpcId);
138
+ }
139
+ }
140
+
141
+ /** Get a pending RPC by ID */
142
+ get(rpcId: string): PendingRPC | undefined {
143
+ return this.pending.get(rpcId);
144
+ }
145
+
146
+ /** Get count of pending RPCs */
147
+ get size(): number {
148
+ return this.pending.size;
149
+ }
150
+
151
+ private clearTimers(entry: PendingRPC): void {
152
+ if (entry.initialTimer) {
153
+ clearTimeout(entry.initialTimer);
154
+ entry.initialTimer = null;
155
+ }
156
+ if (entry.maxTimer) {
157
+ clearTimeout(entry.maxTimer);
158
+ entry.maxTimer = null;
159
+ }
160
+ }
161
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Event Scheduler — Round-Robin Fairness
3
+ *
4
+ * Processes sidecar events with round-robin scheduling across sidecars
5
+ * to prevent any single sidecar from monopolizing event handling.
6
+ */
7
+
8
+ import type { SidecarEvent, EventPriority } from './protocol.ts';
9
+
10
+ interface QueuedEvent {
11
+ sidecarId: string;
12
+ event: SidecarEvent;
13
+ priority: EventPriority;
14
+ enqueuedAt: number;
15
+ }
16
+
17
+ type EventHandler = (sidecarId: string, event: SidecarEvent) => Promise<void>;
18
+
19
+ export class EventScheduler {
20
+ private queues = new Map<string, QueuedEvent[]>();
21
+ private sidecarIds: string[] = [];
22
+ private roundRobinIndex = 0;
23
+ private handlers = new Map<string, EventHandler[]>();
24
+ private running = false;
25
+ private processing = false;
26
+ private drainTimer: Timer | null = null;
27
+ private readonly drainIntervalMs: number;
28
+
29
+ constructor(drainIntervalMs = 50) {
30
+ this.drainIntervalMs = drainIntervalMs;
31
+ }
32
+
33
+ /** Register a handler for a specific event_type */
34
+ on(eventType: string, handler: EventHandler): void {
35
+ const existing = this.handlers.get(eventType) ?? [];
36
+ existing.push(handler);
37
+ this.handlers.set(eventType, existing);
38
+ }
39
+
40
+ /** Enqueue an event from a sidecar */
41
+ enqueue(sidecarId: string, event: SidecarEvent, priority?: EventPriority): void {
42
+ let queue = this.queues.get(sidecarId);
43
+ if (!queue) {
44
+ queue = [];
45
+ this.queues.set(sidecarId, queue);
46
+ this.sidecarIds.push(sidecarId);
47
+ }
48
+
49
+ queue.push({
50
+ sidecarId,
51
+ event,
52
+ priority: priority ?? event.priority ?? 'normal',
53
+ enqueuedAt: Date.now(),
54
+ });
55
+
56
+ // Sort by priority within each sidecar's queue
57
+ queue.sort((a, b) => priorityWeight(a.priority) - priorityWeight(b.priority));
58
+ }
59
+
60
+ /** Remove a sidecar's queue (on disconnect) */
61
+ removeSidecar(sidecarId: string): void {
62
+ this.queues.delete(sidecarId);
63
+ this.sidecarIds = this.sidecarIds.filter(id => id !== sidecarId);
64
+ if (this.roundRobinIndex >= this.sidecarIds.length) {
65
+ this.roundRobinIndex = 0;
66
+ }
67
+ }
68
+
69
+ /** Start the processing loop */
70
+ start(): void {
71
+ if (this.running) return;
72
+ this.running = true;
73
+ this.drainTimer = setInterval(() => this.drain(), this.drainIntervalMs);
74
+ }
75
+
76
+ /** Stop the processing loop */
77
+ stop(): void {
78
+ this.running = false;
79
+ if (this.drainTimer) {
80
+ clearInterval(this.drainTimer);
81
+ this.drainTimer = null;
82
+ }
83
+ }
84
+
85
+ private async drain(): Promise<void> {
86
+ if (this.processing || this.sidecarIds.length === 0) return;
87
+ this.processing = true;
88
+
89
+ try {
90
+ // One round-robin pass: try each sidecar once
91
+ const count = this.sidecarIds.length;
92
+ for (let i = 0; i < count; i++) {
93
+ const idx = (this.roundRobinIndex + i) % count;
94
+ const sidecarId = this.sidecarIds[idx]!;
95
+ const queue = this.queues.get(sidecarId);
96
+
97
+ if (!queue || queue.length === 0) continue;
98
+
99
+ const item = queue.shift()!;
100
+ this.roundRobinIndex = (idx + 1) % count;
101
+
102
+ await this.dispatch(item);
103
+
104
+ // Process one event per drain tick to stay non-blocking
105
+ break;
106
+ }
107
+ } catch (err) {
108
+ console.error('[EventScheduler] Drain error:', err);
109
+ } finally {
110
+ this.processing = false;
111
+ }
112
+ }
113
+
114
+ private async dispatch(item: QueuedEvent): Promise<void> {
115
+ const handlers = this.handlers.get(item.event.event_type) ?? [];
116
+ const wildcardHandlers = this.handlers.get('*') ?? [];
117
+ const allHandlers = [...handlers, ...wildcardHandlers];
118
+
119
+ for (const handler of allHandlers) {
120
+ try {
121
+ await handler(item.sidecarId, item.event);
122
+ } catch (err) {
123
+ console.error(`[EventScheduler] Handler error for ${item.event.event_type}:`, err);
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ function priorityWeight(p: EventPriority): number {
130
+ switch (p) {
131
+ case 'critical': return 0;
132
+ case 'high': return 1;
133
+ case 'normal': return 2;
134
+ case 'low': return 3;
135
+ }
136
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Sidecar Types
3
+ *
4
+ * Types for the brain-side sidecar management system.
5
+ */
6
+
7
+ /** Capabilities a sidecar can advertise */
8
+ export type SidecarCapability =
9
+ | 'terminal'
10
+ | 'filesystem'
11
+ | 'desktop'
12
+ | 'browser'
13
+ | 'clipboard'
14
+ | 'screenshot'
15
+ | 'system_info'
16
+ | 'awareness';
17
+
18
+ /** A capability that is enabled in config but unavailable on the system */
19
+ export interface UnavailableCapability {
20
+ name: SidecarCapability;
21
+ reason: string;
22
+ }
23
+
24
+ /** Sidecar status in the database */
25
+ export type SidecarStatus = 'enrolled' | 'revoked';
26
+
27
+ /** Sidecar record as stored in the database */
28
+ export interface SidecarRecord {
29
+ id: string;
30
+ name: string;
31
+ token_id: string;
32
+ enrolled_at: string;
33
+ last_seen_at: string | null;
34
+ status: SidecarStatus;
35
+ /** Populated after first connection */
36
+ hostname: string | null;
37
+ os: string | null;
38
+ platform: string | null;
39
+ /** JSON-encoded SidecarCapability[] — populated after first connection */
40
+ capabilities: string | null;
41
+ }
42
+
43
+ /** JWT claims for a sidecar enrollment token */
44
+ export interface SidecarTokenClaims {
45
+ /** Subject: "sidecar:<id>" */
46
+ sub: string;
47
+ /** Unique token ID (for revocation tracking) */
48
+ jti: string;
49
+ /** Sidecar UUID */
50
+ sid: string;
51
+ /** Human-readable sidecar name */
52
+ name: string;
53
+ /** WebSocket URL for the sidecar to connect to */
54
+ brain: string;
55
+ /** URL to fetch the brain's JWKS public key */
56
+ jwks: string;
57
+ /** Issued-at timestamp */
58
+ iat: number;
59
+ }
60
+
61
+ /** Registration message sent by sidecar on WebSocket connect */
62
+ export interface SidecarRegistration {
63
+ type: 'register';
64
+ hostname: string;
65
+ os: string;
66
+ platform: string;
67
+ capabilities: SidecarCapability[];
68
+ unavailable_capabilities?: UnavailableCapability[];
69
+ }
70
+
71
+ /** Capabilities update message sent by sidecar when config changes */
72
+ export interface SidecarCapabilitiesUpdate {
73
+ type: 'capabilities_update';
74
+ capabilities: SidecarCapability[];
75
+ unavailable_capabilities?: UnavailableCapability[];
76
+ }
77
+
78
+ /** A connected sidecar (runtime state, not persisted) */
79
+ export interface ConnectedSidecar {
80
+ id: string;
81
+ name: string;
82
+ hostname: string;
83
+ os: string;
84
+ platform: string;
85
+ capabilities: SidecarCapability[];
86
+ unavailableCapabilities: UnavailableCapability[];
87
+ connectedAt: Date;
88
+ }
89
+
90
+ /** Sidecar config as returned by get_config RPC (token excluded) */
91
+ export interface SidecarConfig {
92
+ capabilities: SidecarCapability[];
93
+ terminal: { blocked_commands: string[]; default_shell: string; timeout_ms: number };
94
+ filesystem: { blocked_paths: string[]; max_file_size_kb: number };
95
+ browser: { cdp_port: number; profile_dir: string };
96
+ awareness: { screen_interval_ms: number; window_interval_ms: number; min_change_threshold: number; stuck_threshold_ms: number };
97
+ }
98
+
99
+ /** Sidecar info returned by API (DB record + connection state) */
100
+ export interface SidecarInfo {
101
+ id: string;
102
+ name: string;
103
+ enrolled_at: string;
104
+ last_seen_at: string | null;
105
+ status: SidecarStatus;
106
+ connected: boolean;
107
+ hostname?: string;
108
+ os?: string;
109
+ platform?: string;
110
+ capabilities?: SidecarCapability[];
111
+ unavailable_capabilities?: UnavailableCapability[];
112
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Sidecar Validator — Schema Validation & Sanitization
3
+ *
4
+ * Validates inbound sidecar events and binary frames.
5
+ */
6
+
7
+ import type { SidecarEvent, EventPriority } from './protocol.ts';
8
+
9
+ // Size limits
10
+ export const MAX_JSON_SIZE = 1 * 1024 * 1024; // 1 MB
11
+ export const MAX_BINARY_SIZE = 50 * 1024 * 1024; // 50 MB
12
+ export const BINARY_INLINE_THRESHOLD = 256 * 1024; // 256 KB
13
+ export const BINARY_REF_ID_LENGTH = 36; // UUID length
14
+
15
+ export interface ValidationResult {
16
+ valid: boolean;
17
+ event?: SidecarEvent;
18
+ error?: string;
19
+ }
20
+
21
+ export interface BinaryValidationResult {
22
+ valid: boolean;
23
+ refId?: string;
24
+ payload?: Buffer;
25
+ error?: string;
26
+ }
27
+
28
+ const VALID_EVENT_TYPES = new Set([
29
+ 'rpc_result', 'rpc_progress', 'sidecar_event',
30
+ ]);
31
+
32
+ const VALID_PRIORITIES = new Set<EventPriority>([
33
+ 'critical', 'high', 'normal', 'low',
34
+ ]);
35
+
36
+ const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
37
+
38
+ /**
39
+ * Recursively strip dangerous keys from an object.
40
+ */
41
+ function sanitize(obj: unknown): unknown {
42
+ if (obj === null || obj === undefined) return obj;
43
+ if (typeof obj !== 'object') return obj;
44
+ if (Array.isArray(obj)) return obj.map(sanitize);
45
+
46
+ const clean: Record<string, unknown> = {};
47
+ for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
48
+ if (DANGEROUS_KEYS.has(key)) continue;
49
+ clean[key] = sanitize(value);
50
+ }
51
+ return clean;
52
+ }
53
+
54
+ /**
55
+ * Validate and sanitize an inbound sidecar event (parsed JSON).
56
+ */
57
+ export function validateEvent(raw: unknown): ValidationResult {
58
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
59
+ return { valid: false, error: 'Event must be a JSON object' };
60
+ }
61
+
62
+ const obj = raw as Record<string, unknown>;
63
+
64
+ // type
65
+ if (typeof obj.type !== 'string' || !VALID_EVENT_TYPES.has(obj.type)) {
66
+ return { valid: false, error: `Invalid event type: ${String(obj.type)}` };
67
+ }
68
+
69
+ // event_type
70
+ if (typeof obj.event_type !== 'string' || obj.event_type.length === 0) {
71
+ return { valid: false, error: 'Missing or empty event_type' };
72
+ }
73
+
74
+ // timestamp
75
+ if (typeof obj.timestamp !== 'number' || !Number.isFinite(obj.timestamp)) {
76
+ return { valid: false, error: 'Invalid timestamp' };
77
+ }
78
+
79
+ // payload
80
+ if (typeof obj.payload !== 'object' || obj.payload === null || Array.isArray(obj.payload)) {
81
+ return { valid: false, error: 'Payload must be a JSON object' };
82
+ }
83
+
84
+ // Validate payload fields per type
85
+ const payload = obj.payload as Record<string, unknown>;
86
+
87
+ if (obj.type === 'rpc_result') {
88
+ if (typeof payload.rpc_id !== 'string') {
89
+ return { valid: false, error: 'rpc_result requires payload.rpc_id (string)' };
90
+ }
91
+ if (payload.result === undefined && payload.error === undefined) {
92
+ return { valid: false, error: 'rpc_result requires payload.result or payload.error' };
93
+ }
94
+ if (payload.error !== undefined) {
95
+ const err = payload.error as Record<string, unknown>;
96
+ if (typeof err !== 'object' || typeof err.code !== 'string' || typeof err.message !== 'string') {
97
+ return { valid: false, error: 'rpc_result error must have code and message strings' };
98
+ }
99
+ }
100
+ }
101
+
102
+ if (obj.type === 'rpc_progress') {
103
+ if (typeof payload.rpc_id !== 'string') {
104
+ return { valid: false, error: 'rpc_progress requires payload.rpc_id (string)' };
105
+ }
106
+ if (typeof payload.progress !== 'number') {
107
+ return { valid: false, error: 'rpc_progress requires payload.progress (number)' };
108
+ }
109
+ }
110
+
111
+ // priority (optional)
112
+ if (obj.priority !== undefined && !VALID_PRIORITIES.has(obj.priority as EventPriority)) {
113
+ return { valid: false, error: `Invalid priority: ${String(obj.priority)}` };
114
+ }
115
+
116
+ // Sanitize the entire event
117
+ const sanitized = sanitize(obj) as SidecarEvent;
118
+
119
+ return { valid: true, event: sanitized };
120
+ }
121
+
122
+ /**
123
+ * Validate a binary frame. First 36 bytes = ref_id (ASCII UUID), rest = payload.
124
+ */
125
+ export function validateBinaryFrame(data: Buffer): BinaryValidationResult {
126
+ if (data.length < BINARY_REF_ID_LENGTH) {
127
+ return { valid: false, error: `Binary frame too small: ${data.length} bytes (min ${BINARY_REF_ID_LENGTH})` };
128
+ }
129
+
130
+ if (data.length > MAX_BINARY_SIZE) {
131
+ return { valid: false, error: `Binary frame too large: ${data.length} bytes (max ${MAX_BINARY_SIZE})` };
132
+ }
133
+
134
+ const refId = data.subarray(0, BINARY_REF_ID_LENGTH).toString('ascii');
135
+
136
+ // Basic UUID format check
137
+ if (!/^[0-9a-f-]{36}$/i.test(refId)) {
138
+ return { valid: false, error: `Invalid ref_id format: ${refId}` };
139
+ }
140
+
141
+ const payload = data.subarray(BINARY_REF_ID_LENGTH);
142
+
143
+ return { valid: true, refId, payload };
144
+ }
@@ -0,0 +1,110 @@
1
+ # Vault Module
2
+
3
+ Knowledge graph storage and extraction system for J.A.R.V.I.S.
4
+
5
+ ## Modules
6
+
7
+ - **`schema.ts`**: Database schema and initialization
8
+ - **`entities.ts`**: Entity CRUD operations
9
+ - **`facts.ts`**: Fact storage and queries
10
+ - **`relationships.ts`**: Relationship management
11
+ - **`commitments.ts`**: Promise and task tracking
12
+ - **`observations.ts`**: Raw event storage
13
+ - **`vectors.ts`**: Embedding storage for semantic search
14
+ - **`extractor.ts`**: LLM-powered knowledge extraction
15
+ - **`index.ts`**: Public API exports
16
+
17
+ ## Quick Start
18
+
19
+ ### Initialize Database
20
+
21
+ ```typescript
22
+ import { initDatabase } from '@/vault';
23
+
24
+ initDatabase('./data/jarvis.db');
25
+ ```
26
+
27
+ ### Store Knowledge Manually
28
+
29
+ ```typescript
30
+ import { createEntity, createFact, createRelationship } from '@/vault';
31
+
32
+ // Create entity
33
+ const anna = createEntity('person', 'Anna', {
34
+ role: 'software engineer',
35
+ });
36
+
37
+ // Add fact
38
+ createFact(anna.id, 'birthday_is', 'March 15', {
39
+ confidence: 1.0,
40
+ });
41
+
42
+ // Create relationship
43
+ const google = createEntity('tool', 'Google');
44
+ createRelationship(anna.id, google.id, 'works_at');
45
+ ```
46
+
47
+ ### Extract Knowledge with LLM
48
+
49
+ ```typescript
50
+ import { extractAndStore } from '@/vault';
51
+ import { AnthropicProvider } from '@/llm/anthropic';
52
+
53
+ const llm = new AnthropicProvider(process.env.ANTHROPIC_API_KEY);
54
+
55
+ const result = await extractAndStore(
56
+ "Anna's birthday is March 15th",
57
+ "I'll remember that!",
58
+ llm
59
+ );
60
+ ```
61
+
62
+ ### Query Knowledge
63
+
64
+ ```typescript
65
+ import { findEntities, findFacts, findCommitments } from '@/vault';
66
+
67
+ // Find people
68
+ const people = findEntities({ type: 'person' });
69
+
70
+ // Find facts about Anna
71
+ const facts = findFacts({ subject_id: anna.id });
72
+
73
+ // Find pending commitments
74
+ const pending = findCommitments({ status: 'pending' });
75
+ ```
76
+
77
+ ## Documentation
78
+
79
+ - **Vault Extractor**: [docs/VAULT_EXTRACTOR.md](~/jarvis/docs/VAULT_EXTRACTOR.md)
80
+ - **Entity Types**: person, project, tool, place, concept, event
81
+ - **Commitment Statuses**: pending, active, completed, failed, escalated
82
+
83
+ ## Testing
84
+
85
+ ```bash
86
+ # Test extractor
87
+ bun test src/vault/extractor.test.ts
88
+
89
+ # Test entire vault (if you have other tests)
90
+ bun test src/vault/**/*.test.ts
91
+ ```
92
+
93
+ ## Demo
94
+
95
+ ```bash
96
+ bun run examples/extractor-demo.ts
97
+ ```
98
+
99
+ ## Database Schema
100
+
101
+ SQLite database with the following tables:
102
+ - `entities`: People, projects, tools, places, concepts, events
103
+ - `facts`: Atomic knowledge with confidence scores
104
+ - `relationships`: Typed edges between entities
105
+ - `commitments`: Promises and tasks
106
+ - `observations`: Raw events
107
+ - `vectors`: Embeddings for semantic search
108
+ - `agent_messages`: Inter-agent communication
109
+ - `personality_state`: Personality model storage
110
+ - `conversations`: Conversation tracking