@shykaruu/jarvis-brain 0.4.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 (330) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +428 -0
  3. package/bin/jarvis.ts +449 -0
  4. package/package.json +79 -0
  5. package/roles/activity-observer.yaml +60 -0
  6. package/roles/ceo-founder.yaml +144 -0
  7. package/roles/chief-of-staff.yaml +158 -0
  8. package/roles/dev-lead.yaml +182 -0
  9. package/roles/executive-assistant.yaml +77 -0
  10. package/roles/marketing-director.yaml +168 -0
  11. package/roles/personal-assistant.yaml +266 -0
  12. package/roles/research-specialist.yaml +60 -0
  13. package/roles/specialists/content-writer.yaml +53 -0
  14. package/roles/specialists/customer-support.yaml +57 -0
  15. package/roles/specialists/data-analyst.yaml +57 -0
  16. package/roles/specialists/financial-analyst.yaml +56 -0
  17. package/roles/specialists/hr-specialist.yaml +55 -0
  18. package/roles/specialists/legal-advisor.yaml +58 -0
  19. package/roles/specialists/marketing-strategist.yaml +56 -0
  20. package/roles/specialists/project-coordinator.yaml +55 -0
  21. package/roles/specialists/research-analyst.yaml +58 -0
  22. package/roles/specialists/software-engineer.yaml +57 -0
  23. package/roles/specialists/system-administrator.yaml +57 -0
  24. package/roles/system-admin.yaml +76 -0
  25. package/scripts/ensure-bun.cjs +16 -0
  26. package/src/actions/README.md +421 -0
  27. package/src/actions/app-control/desktop-controller.test.ts +26 -0
  28. package/src/actions/app-control/desktop-controller.ts +438 -0
  29. package/src/actions/app-control/interface.ts +64 -0
  30. package/src/actions/app-control/linux.ts +273 -0
  31. package/src/actions/app-control/macos.ts +54 -0
  32. package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
  33. package/src/actions/app-control/sidecar-launcher.ts +286 -0
  34. package/src/actions/app-control/windows.ts +44 -0
  35. package/src/actions/browser/cdp.ts +138 -0
  36. package/src/actions/browser/chrome-launcher.ts +261 -0
  37. package/src/actions/browser/session.ts +506 -0
  38. package/src/actions/browser/stealth.ts +49 -0
  39. package/src/actions/index.ts +20 -0
  40. package/src/actions/terminal/executor.ts +157 -0
  41. package/src/actions/terminal/wsl-bridge.ts +126 -0
  42. package/src/actions/test.ts +93 -0
  43. package/src/actions/tools/agents.ts +363 -0
  44. package/src/actions/tools/builtin.ts +950 -0
  45. package/src/actions/tools/commitments.ts +192 -0
  46. package/src/actions/tools/content.ts +217 -0
  47. package/src/actions/tools/delegate.ts +147 -0
  48. package/src/actions/tools/desktop.test.ts +55 -0
  49. package/src/actions/tools/desktop.ts +305 -0
  50. package/src/actions/tools/documents.ts +169 -0
  51. package/src/actions/tools/goals.ts +376 -0
  52. package/src/actions/tools/local-tools-guard.ts +31 -0
  53. package/src/actions/tools/registry.ts +173 -0
  54. package/src/actions/tools/research.ts +111 -0
  55. package/src/actions/tools/sidecar-list.ts +57 -0
  56. package/src/actions/tools/sidecar-route.ts +105 -0
  57. package/src/actions/tools/workflows.ts +216 -0
  58. package/src/agents/agent.ts +132 -0
  59. package/src/agents/delegation.ts +107 -0
  60. package/src/agents/hierarchy.ts +113 -0
  61. package/src/agents/index.ts +19 -0
  62. package/src/agents/messaging.ts +125 -0
  63. package/src/agents/orchestrator.ts +592 -0
  64. package/src/agents/role-discovery.ts +61 -0
  65. package/src/agents/sub-agent-runner.ts +309 -0
  66. package/src/agents/task-manager.ts +151 -0
  67. package/src/authority/approval-delivery.ts +59 -0
  68. package/src/authority/approval.ts +196 -0
  69. package/src/authority/audit.ts +158 -0
  70. package/src/authority/authority.test.ts +519 -0
  71. package/src/authority/deferred-executor.ts +103 -0
  72. package/src/authority/emergency.ts +66 -0
  73. package/src/authority/engine.ts +301 -0
  74. package/src/authority/index.ts +12 -0
  75. package/src/authority/learning.ts +111 -0
  76. package/src/authority/tool-action-map.ts +74 -0
  77. package/src/awareness/analytics.ts +466 -0
  78. package/src/awareness/awareness.test.ts +332 -0
  79. package/src/awareness/capture-engine.ts +305 -0
  80. package/src/awareness/context-graph.ts +130 -0
  81. package/src/awareness/context-tracker.ts +349 -0
  82. package/src/awareness/index.ts +25 -0
  83. package/src/awareness/intelligence.ts +321 -0
  84. package/src/awareness/ocr-engine.ts +88 -0
  85. package/src/awareness/service.ts +528 -0
  86. package/src/awareness/struggle-detector.ts +342 -0
  87. package/src/awareness/suggestion-engine.ts +476 -0
  88. package/src/awareness/types.ts +201 -0
  89. package/src/cli/autostart.ts +417 -0
  90. package/src/cli/deps.ts +449 -0
  91. package/src/cli/doctor.ts +238 -0
  92. package/src/cli/helpers.ts +401 -0
  93. package/src/cli/onboard.ts +827 -0
  94. package/src/cli/uninstall.test.ts +37 -0
  95. package/src/cli/uninstall.ts +202 -0
  96. package/src/comms/README.md +329 -0
  97. package/src/comms/auth-error.html +48 -0
  98. package/src/comms/channels/discord.ts +228 -0
  99. package/src/comms/channels/signal.ts +56 -0
  100. package/src/comms/channels/telegram.ts +316 -0
  101. package/src/comms/channels/whatsapp.ts +60 -0
  102. package/src/comms/channels.test.ts +173 -0
  103. package/src/comms/dashboard-auth.ts +75 -0
  104. package/src/comms/desktop-notify.ts +114 -0
  105. package/src/comms/example.ts +129 -0
  106. package/src/comms/index.ts +129 -0
  107. package/src/comms/streaming.ts +149 -0
  108. package/src/comms/voice.test.ts +504 -0
  109. package/src/comms/voice.ts +341 -0
  110. package/src/comms/websocket.test.ts +409 -0
  111. package/src/comms/websocket.ts +669 -0
  112. package/src/config/README.md +389 -0
  113. package/src/config/index.ts +6 -0
  114. package/src/config/loader.test.ts +183 -0
  115. package/src/config/loader.ts +148 -0
  116. package/src/config/types.ts +293 -0
  117. package/src/daemon/README.md +232 -0
  118. package/src/daemon/agent-service-interface.ts +9 -0
  119. package/src/daemon/agent-service.ts +667 -0
  120. package/src/daemon/api-routes.ts +3067 -0
  121. package/src/daemon/background-agent-service.ts +396 -0
  122. package/src/daemon/background-agent.test.ts +78 -0
  123. package/src/daemon/channel-service.ts +201 -0
  124. package/src/daemon/commitment-executor.ts +297 -0
  125. package/src/daemon/dashboard-auth.test.ts +170 -0
  126. package/src/daemon/event-classifier.ts +239 -0
  127. package/src/daemon/event-coalescer.ts +123 -0
  128. package/src/daemon/event-reactor.ts +214 -0
  129. package/src/daemon/flock.c +7 -0
  130. package/src/daemon/health.ts +220 -0
  131. package/src/daemon/index.ts +1070 -0
  132. package/src/daemon/llm-settings.test.ts +78 -0
  133. package/src/daemon/llm-settings.ts +450 -0
  134. package/src/daemon/observer-service.ts +150 -0
  135. package/src/daemon/pid.test.ts +283 -0
  136. package/src/daemon/pid.ts +224 -0
  137. package/src/daemon/research-queue.ts +155 -0
  138. package/src/daemon/services.ts +175 -0
  139. package/src/daemon/ws-service.ts +926 -0
  140. package/src/global.d.ts +4 -0
  141. package/src/goals/accountability.ts +240 -0
  142. package/src/goals/awareness-bridge.ts +185 -0
  143. package/src/goals/estimator.ts +185 -0
  144. package/src/goals/events.ts +28 -0
  145. package/src/goals/goals.test.ts +400 -0
  146. package/src/goals/integration.test.ts +329 -0
  147. package/src/goals/nl-builder.test.ts +220 -0
  148. package/src/goals/nl-builder.ts +256 -0
  149. package/src/goals/rhythm.test.ts +177 -0
  150. package/src/goals/rhythm.ts +275 -0
  151. package/src/goals/service.test.ts +135 -0
  152. package/src/goals/service.ts +407 -0
  153. package/src/goals/types.ts +106 -0
  154. package/src/goals/workflow-bridge.ts +96 -0
  155. package/src/integrations/google-api.ts +134 -0
  156. package/src/integrations/google-auth.ts +175 -0
  157. package/src/llm/README.md +291 -0
  158. package/src/llm/anthropic.ts +400 -0
  159. package/src/llm/gemini.ts +380 -0
  160. package/src/llm/groq.ts +406 -0
  161. package/src/llm/history.ts +147 -0
  162. package/src/llm/index.ts +21 -0
  163. package/src/llm/manager.ts +226 -0
  164. package/src/llm/ollama.ts +316 -0
  165. package/src/llm/openai.ts +411 -0
  166. package/src/llm/openrouter.ts +390 -0
  167. package/src/llm/provider.test.ts +487 -0
  168. package/src/llm/provider.ts +61 -0
  169. package/src/llm/test.ts +88 -0
  170. package/src/observers/README.md +278 -0
  171. package/src/observers/calendar.ts +113 -0
  172. package/src/observers/clipboard.ts +136 -0
  173. package/src/observers/email.ts +109 -0
  174. package/src/observers/example.ts +58 -0
  175. package/src/observers/file-watcher.ts +124 -0
  176. package/src/observers/index.ts +159 -0
  177. package/src/observers/notifications.ts +197 -0
  178. package/src/observers/observers.test.ts +203 -0
  179. package/src/observers/processes.ts +225 -0
  180. package/src/personality/README.md +61 -0
  181. package/src/personality/adapter.ts +196 -0
  182. package/src/personality/index.ts +20 -0
  183. package/src/personality/learner.ts +209 -0
  184. package/src/personality/model.ts +132 -0
  185. package/src/personality/personality.test.ts +236 -0
  186. package/src/roles/README.md +252 -0
  187. package/src/roles/authority.ts +120 -0
  188. package/src/roles/example-usage.ts +198 -0
  189. package/src/roles/index.ts +42 -0
  190. package/src/roles/loader.ts +143 -0
  191. package/src/roles/prompt-builder.ts +218 -0
  192. package/src/roles/test-multi.ts +102 -0
  193. package/src/roles/test-role.yaml +77 -0
  194. package/src/roles/test-utils.ts +93 -0
  195. package/src/roles/test.ts +106 -0
  196. package/src/roles/tool-guide.ts +195 -0
  197. package/src/roles/types.ts +36 -0
  198. package/src/roles/utils.ts +200 -0
  199. package/src/scripts/google-setup.ts +168 -0
  200. package/src/sidecar/connection.ts +179 -0
  201. package/src/sidecar/index.ts +6 -0
  202. package/src/sidecar/manager.ts +542 -0
  203. package/src/sidecar/protocol.ts +85 -0
  204. package/src/sidecar/rpc.ts +161 -0
  205. package/src/sidecar/scheduler.ts +136 -0
  206. package/src/sidecar/types.ts +112 -0
  207. package/src/sidecar/validator.ts +144 -0
  208. package/src/sites/builder-tools.ts +215 -0
  209. package/src/sites/dev-server-manager.ts +286 -0
  210. package/src/sites/fixtures/security-test-site/.jarvis-project.json +6 -0
  211. package/src/sites/fixtures/security-test-site/Makefile +15 -0
  212. package/src/sites/fixtures/security-test-site/README.md +18 -0
  213. package/src/sites/fixtures/security-test-site/index.html +12 -0
  214. package/src/sites/fixtures/security-test-site/index.ts +16 -0
  215. package/src/sites/fixtures/security-test-site/package.json +13 -0
  216. package/src/sites/fixtures/security-test-site/src/app.tsx +780 -0
  217. package/src/sites/fixtures/security-test-site/tsconfig.json +10 -0
  218. package/src/sites/git-manager.ts +240 -0
  219. package/src/sites/github-manager.ts +355 -0
  220. package/src/sites/index.ts +25 -0
  221. package/src/sites/project-manager.ts +389 -0
  222. package/src/sites/proxy.ts +133 -0
  223. package/src/sites/service.ts +136 -0
  224. package/src/sites/templates.ts +169 -0
  225. package/src/sites/types.ts +89 -0
  226. package/src/user/profile-followup.test.ts +84 -0
  227. package/src/user/profile-followup.ts +185 -0
  228. package/src/user/profile.ts +224 -0
  229. package/src/vault/README.md +110 -0
  230. package/src/vault/awareness.ts +341 -0
  231. package/src/vault/commitments.ts +299 -0
  232. package/src/vault/content-pipeline.ts +270 -0
  233. package/src/vault/conversations.ts +173 -0
  234. package/src/vault/dashboard-sessions.ts +44 -0
  235. package/src/vault/documents.ts +130 -0
  236. package/src/vault/entities.ts +185 -0
  237. package/src/vault/extractor.test.ts +356 -0
  238. package/src/vault/extractor.ts +345 -0
  239. package/src/vault/facts.ts +190 -0
  240. package/src/vault/goals.ts +477 -0
  241. package/src/vault/index.ts +87 -0
  242. package/src/vault/keychain.ts +99 -0
  243. package/src/vault/observations.ts +115 -0
  244. package/src/vault/relationships.ts +178 -0
  245. package/src/vault/retrieval.test.ts +139 -0
  246. package/src/vault/retrieval.ts +258 -0
  247. package/src/vault/schema.ts +709 -0
  248. package/src/vault/settings.ts +38 -0
  249. package/src/vault/user-profile.test.ts +113 -0
  250. package/src/vault/user-profile.ts +176 -0
  251. package/src/vault/vectors.ts +92 -0
  252. package/src/vault/webapp-template-seeds.ts +116 -0
  253. package/src/vault/webapp-templates.ts +244 -0
  254. package/src/vault/workflows.ts +403 -0
  255. package/src/workflows/auto-suggest.ts +290 -0
  256. package/src/workflows/engine.ts +366 -0
  257. package/src/workflows/events.ts +24 -0
  258. package/src/workflows/executor.ts +207 -0
  259. package/src/workflows/nl-builder.ts +198 -0
  260. package/src/workflows/nodes/actions/agent-task.ts +73 -0
  261. package/src/workflows/nodes/actions/calendar-action.ts +85 -0
  262. package/src/workflows/nodes/actions/code-execution.ts +73 -0
  263. package/src/workflows/nodes/actions/discord.ts +77 -0
  264. package/src/workflows/nodes/actions/file-write.ts +73 -0
  265. package/src/workflows/nodes/actions/gmail.ts +69 -0
  266. package/src/workflows/nodes/actions/http-request.ts +117 -0
  267. package/src/workflows/nodes/actions/notification.ts +85 -0
  268. package/src/workflows/nodes/actions/run-tool.ts +55 -0
  269. package/src/workflows/nodes/actions/send-message.ts +82 -0
  270. package/src/workflows/nodes/actions/shell-command.ts +76 -0
  271. package/src/workflows/nodes/actions/telegram.ts +60 -0
  272. package/src/workflows/nodes/builtin.ts +119 -0
  273. package/src/workflows/nodes/error/error-handler.ts +37 -0
  274. package/src/workflows/nodes/error/fallback.ts +47 -0
  275. package/src/workflows/nodes/error/retry.ts +82 -0
  276. package/src/workflows/nodes/logic/delay.ts +42 -0
  277. package/src/workflows/nodes/logic/if-else.ts +41 -0
  278. package/src/workflows/nodes/logic/loop.ts +90 -0
  279. package/src/workflows/nodes/logic/merge.ts +38 -0
  280. package/src/workflows/nodes/logic/race.ts +40 -0
  281. package/src/workflows/nodes/logic/switch.ts +59 -0
  282. package/src/workflows/nodes/logic/template-render.ts +53 -0
  283. package/src/workflows/nodes/logic/variable-get.ts +37 -0
  284. package/src/workflows/nodes/logic/variable-set.ts +59 -0
  285. package/src/workflows/nodes/registry.ts +99 -0
  286. package/src/workflows/nodes/transform/aggregate.ts +99 -0
  287. package/src/workflows/nodes/transform/csv-parse.ts +70 -0
  288. package/src/workflows/nodes/transform/json-parse.ts +63 -0
  289. package/src/workflows/nodes/transform/map-filter.ts +84 -0
  290. package/src/workflows/nodes/transform/regex-match.ts +89 -0
  291. package/src/workflows/nodes/triggers/calendar.ts +33 -0
  292. package/src/workflows/nodes/triggers/clipboard.ts +32 -0
  293. package/src/workflows/nodes/triggers/cron.ts +40 -0
  294. package/src/workflows/nodes/triggers/email.ts +40 -0
  295. package/src/workflows/nodes/triggers/file-change.ts +45 -0
  296. package/src/workflows/nodes/triggers/git.ts +46 -0
  297. package/src/workflows/nodes/triggers/manual.ts +23 -0
  298. package/src/workflows/nodes/triggers/poll.ts +81 -0
  299. package/src/workflows/nodes/triggers/process.ts +44 -0
  300. package/src/workflows/nodes/triggers/screen-event.ts +37 -0
  301. package/src/workflows/nodes/triggers/webhook.ts +39 -0
  302. package/src/workflows/safe-eval.ts +139 -0
  303. package/src/workflows/template.ts +118 -0
  304. package/src/workflows/triggers/cron.ts +311 -0
  305. package/src/workflows/triggers/manager.ts +285 -0
  306. package/src/workflows/triggers/observer-bridge.ts +172 -0
  307. package/src/workflows/triggers/poller.ts +201 -0
  308. package/src/workflows/triggers/screen-condition.ts +218 -0
  309. package/src/workflows/triggers/triggers.test.ts +740 -0
  310. package/src/workflows/triggers/webhook.ts +191 -0
  311. package/src/workflows/types.ts +133 -0
  312. package/src/workflows/variables.ts +72 -0
  313. package/src/workflows/workflows.test.ts +383 -0
  314. package/src/workflows/yaml.ts +104 -0
  315. package/ui/dist/index-3gr23jt9.js +112614 -0
  316. package/ui/dist/index-9vmj8127.css +14239 -0
  317. package/ui/dist/index-hy9pc1gm.js +112873 -0
  318. package/ui/dist/index-j2ep5d1w.js +112374 -0
  319. package/ui/dist/index-jt00vjqs.js +112858 -0
  320. package/ui/dist/index-k9ymx5qb.js +112374 -0
  321. package/ui/dist/index.html +16 -0
  322. package/ui/public/audio/pcm-capture-processor.js +11 -0
  323. package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
  324. package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
  325. package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
  326. package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
  327. package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
  328. package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
  329. package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
  330. package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
@@ -0,0 +1,173 @@
1
+ import { getDb, generateId } from './schema.ts';
2
+
3
+ export type MessageRole = 'user' | 'assistant' | 'system';
4
+
5
+ export type ConversationMessage = {
6
+ id: string;
7
+ conversation_id: string;
8
+ role: MessageRole;
9
+ content: string;
10
+ tool_calls: unknown[] | null;
11
+ created_at: number;
12
+ };
13
+
14
+ export type Conversation = {
15
+ id: string;
16
+ agent_id: string | null;
17
+ channel: string | null;
18
+ started_at: number;
19
+ last_message_at: number;
20
+ message_count: number;
21
+ metadata: Record<string, unknown> | null;
22
+ };
23
+
24
+ type ConversationRow = {
25
+ id: string;
26
+ agent_id: string | null;
27
+ channel: string | null;
28
+ started_at: number;
29
+ last_message_at: number;
30
+ message_count: number;
31
+ metadata: string | null;
32
+ };
33
+
34
+ type MessageRow = {
35
+ id: string;
36
+ conversation_id: string;
37
+ role: MessageRole;
38
+ content: string;
39
+ tool_calls: string | null;
40
+ created_at: number;
41
+ };
42
+
43
+ function parseConversation(row: ConversationRow): Conversation {
44
+ return {
45
+ ...row,
46
+ metadata: row.metadata ? JSON.parse(row.metadata) : null,
47
+ };
48
+ }
49
+
50
+ function parseMessage(row: MessageRow): ConversationMessage {
51
+ return {
52
+ ...row,
53
+ tool_calls: row.tool_calls ? JSON.parse(row.tool_calls) : null,
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Get or create the active conversation for a channel.
59
+ * Returns the most recent conversation for the channel, or creates a new one.
60
+ */
61
+ export function getOrCreateConversation(channel: string): Conversation {
62
+ const db = getDb();
63
+ const now = Date.now();
64
+
65
+ // Look for a recent conversation on this channel (within last 4 hours)
66
+ const cutoff = now - 4 * 60 * 60 * 1000;
67
+ const existing = db.prepare(
68
+ 'SELECT * FROM conversations WHERE channel = ? AND last_message_at > ? ORDER BY last_message_at DESC LIMIT 1'
69
+ ).get(channel, cutoff) as ConversationRow | null;
70
+
71
+ if (existing) {
72
+ return parseConversation(existing);
73
+ }
74
+
75
+ // Create new conversation
76
+ const id = generateId();
77
+ db.prepare(
78
+ 'INSERT INTO conversations (id, channel, started_at, last_message_at, message_count) VALUES (?, ?, ?, ?, 0)'
79
+ ).run(id, channel, now, now);
80
+
81
+ return {
82
+ id,
83
+ agent_id: null,
84
+ channel,
85
+ started_at: now,
86
+ last_message_at: now,
87
+ message_count: 0,
88
+ metadata: null,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Add a message to a conversation.
94
+ * Updates conversation metadata (last_message_at, message_count).
95
+ */
96
+ export function addMessage(
97
+ conversationId: string,
98
+ msg: { role: MessageRole; content: string; tool_calls?: unknown[] }
99
+ ): ConversationMessage {
100
+ const db = getDb();
101
+ const id = generateId();
102
+ const now = Date.now();
103
+
104
+ db.prepare(
105
+ 'INSERT INTO conversation_messages (id, conversation_id, role, content, tool_calls, created_at) VALUES (?, ?, ?, ?, ?, ?)'
106
+ ).run(
107
+ id,
108
+ conversationId,
109
+ msg.role,
110
+ msg.content,
111
+ msg.tool_calls ? JSON.stringify(msg.tool_calls) : null,
112
+ now
113
+ );
114
+
115
+ // Update conversation
116
+ db.prepare(
117
+ 'UPDATE conversations SET last_message_at = ?, message_count = message_count + 1 WHERE id = ?'
118
+ ).run(now, conversationId);
119
+
120
+ return {
121
+ id,
122
+ conversation_id: conversationId,
123
+ role: msg.role,
124
+ content: msg.content,
125
+ tool_calls: msg.tool_calls ?? null,
126
+ created_at: now,
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Get messages for a conversation, ordered by time ascending.
132
+ */
133
+ export function getMessages(
134
+ conversationId: string,
135
+ opts?: { limit?: number; before?: number }
136
+ ): ConversationMessage[] {
137
+ const db = getDb();
138
+ const limit = opts?.limit ?? 100;
139
+
140
+ if (opts?.before) {
141
+ const rows = db.prepare(
142
+ 'SELECT * FROM conversation_messages WHERE conversation_id = ? AND created_at < ? ORDER BY created_at ASC LIMIT ?'
143
+ ).all(conversationId, opts.before, limit) as MessageRow[];
144
+ return rows.map(parseMessage);
145
+ }
146
+
147
+ // Get the last N messages, ordered ascending
148
+ const rows = db.prepare(
149
+ 'SELECT * FROM (SELECT * FROM conversation_messages WHERE conversation_id = ? ORDER BY created_at DESC LIMIT ?) ORDER BY created_at ASC'
150
+ ).all(conversationId, limit) as MessageRow[];
151
+
152
+ return rows.map(parseMessage);
153
+ }
154
+
155
+ /**
156
+ * Get the most recent conversation for a channel, with its messages.
157
+ */
158
+ export function getRecentConversation(channel: string): {
159
+ conversation: Conversation;
160
+ messages: ConversationMessage[];
161
+ } | null {
162
+ const db = getDb();
163
+ const row = db.prepare(
164
+ 'SELECT * FROM conversations WHERE channel = ? ORDER BY last_message_at DESC LIMIT 1'
165
+ ).get(channel) as ConversationRow | null;
166
+
167
+ if (!row) return null;
168
+
169
+ const conversation = parseConversation(row);
170
+ const messages = getMessages(conversation.id);
171
+
172
+ return { conversation, messages };
173
+ }
@@ -0,0 +1,44 @@
1
+ import { getDb } from './schema.ts';
2
+
3
+ export type DashboardSession = {
4
+ id: string;
5
+ created_at: number;
6
+ expires_at: number;
7
+ };
8
+
9
+ export function createDashboardSession(id: string, expiresAt: number): DashboardSession {
10
+ const now = Date.now();
11
+ getDb().prepare(`
12
+ INSERT INTO dashboard_sessions (id, created_at, expires_at)
13
+ VALUES (?, ?, ?)
14
+ `).run(id, now, expiresAt);
15
+ return { id, created_at: now, expires_at: expiresAt };
16
+ }
17
+
18
+ export function getDashboardSession(id: string): DashboardSession | null {
19
+ const row = getDb().prepare(`
20
+ SELECT id, created_at, expires_at
21
+ FROM dashboard_sessions
22
+ WHERE id = ?
23
+ `).get(id) as DashboardSession | null;
24
+ return row ?? null;
25
+ }
26
+
27
+ export function deleteDashboardSession(id: string): void {
28
+ getDb().prepare('DELETE FROM dashboard_sessions WHERE id = ?').run(id);
29
+ }
30
+
31
+ export function deleteExpiredDashboardSessions(now = Date.now()): void {
32
+ getDb().prepare('DELETE FROM dashboard_sessions WHERE expires_at <= ?').run(now);
33
+ }
34
+
35
+ export function validateDashboardSession(id: string, now = Date.now()): DashboardSession | null {
36
+ deleteExpiredDashboardSessions(now);
37
+ const session = getDashboardSession(id);
38
+ if (!session) return null;
39
+ if (session.expires_at <= now) {
40
+ deleteDashboardSession(id);
41
+ return null;
42
+ }
43
+ return session;
44
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Vault Documents — CRUD for vault-stored documents
3
+ *
4
+ * Documents are files JARVIS creates (reports, plans, analyses, etc.)
5
+ * stored in the vault SQLite database instead of on disk.
6
+ */
7
+
8
+ import { getDb, generateId } from './schema.ts';
9
+
10
+ /** Escape SQL LIKE wildcard characters in user input */
11
+ function escapeLike(s: string): string {
12
+ return s.replace(/[%_\\]/g, '\\$&');
13
+ }
14
+
15
+ export type DocumentFormat = 'markdown' | 'plain' | 'html' | 'json' | 'csv' | 'code';
16
+
17
+ export type Document = {
18
+ id: string;
19
+ title: string;
20
+ body: string;
21
+ format: DocumentFormat;
22
+ tags: string[];
23
+ created_at: number;
24
+ updated_at: number;
25
+ };
26
+
27
+ type DocumentRow = Omit<Document, 'tags'> & { tags: string | null };
28
+
29
+ function parseRow(row: DocumentRow): Document {
30
+ return {
31
+ ...row,
32
+ tags: row.tags ? JSON.parse(row.tags) : [],
33
+ };
34
+ }
35
+
36
+ export function createDocument(title: string, body: string, opts?: {
37
+ format?: DocumentFormat;
38
+ tags?: string[];
39
+ }): Document {
40
+ const db = getDb();
41
+ const id = generateId();
42
+ const now = Date.now();
43
+
44
+ db.prepare(
45
+ `INSERT INTO documents (id, title, body, format, tags, created_at, updated_at)
46
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
47
+ ).run(
48
+ id,
49
+ title,
50
+ body,
51
+ opts?.format ?? 'markdown',
52
+ opts?.tags ? JSON.stringify(opts.tags) : null,
53
+ now,
54
+ now,
55
+ );
56
+
57
+ return {
58
+ id, title, body,
59
+ format: opts?.format ?? 'markdown',
60
+ tags: opts?.tags ?? [],
61
+ created_at: now,
62
+ updated_at: now,
63
+ };
64
+ }
65
+
66
+ export function getDocument(id: string): Document | null {
67
+ const db = getDb();
68
+ const row = db.prepare('SELECT * FROM documents WHERE id = ?').get(id) as DocumentRow | null;
69
+ return row ? parseRow(row) : null;
70
+ }
71
+
72
+ export function findDocuments(query?: {
73
+ format?: DocumentFormat;
74
+ tag?: string;
75
+ search?: string;
76
+ }): Document[] {
77
+ const db = getDb();
78
+ const conditions: string[] = [];
79
+ const params: unknown[] = [];
80
+
81
+ if (query?.format) {
82
+ conditions.push('format = ?');
83
+ params.push(query.format);
84
+ }
85
+ if (query?.tag) {
86
+ conditions.push("tags LIKE ? ESCAPE '\\'");
87
+ params.push(`%"${escapeLike(query.tag)}"%`);
88
+ }
89
+ if (query?.search) {
90
+ conditions.push("(title LIKE ? ESCAPE '\\' OR body LIKE ? ESCAPE '\\')");
91
+ params.push(`%${escapeLike(query.search)}%`, `%${escapeLike(query.search)}%`);
92
+ }
93
+
94
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
95
+ const rows = db.prepare(
96
+ `SELECT * FROM documents ${where} ORDER BY updated_at DESC`
97
+ ).all(...params as any[]) as DocumentRow[];
98
+
99
+ return rows.map(parseRow);
100
+ }
101
+
102
+ export function updateDocument(id: string, updates: {
103
+ title?: string;
104
+ body?: string;
105
+ format?: DocumentFormat;
106
+ tags?: string[];
107
+ }): Document | null {
108
+ const db = getDb();
109
+ const existing = getDocument(id);
110
+ if (!existing) return null;
111
+
112
+ const sets: string[] = ['updated_at = ?'];
113
+ const params: unknown[] = [Date.now()];
114
+
115
+ if (updates.title !== undefined) { sets.push('title = ?'); params.push(updates.title); }
116
+ if (updates.body !== undefined) { sets.push('body = ?'); params.push(updates.body); }
117
+ if (updates.format !== undefined) { sets.push('format = ?'); params.push(updates.format); }
118
+ if (updates.tags !== undefined) { sets.push('tags = ?'); params.push(JSON.stringify(updates.tags)); }
119
+
120
+ params.push(id);
121
+ db.prepare(`UPDATE documents SET ${sets.join(', ')} WHERE id = ?`).run(...params as any[]);
122
+
123
+ return getDocument(id);
124
+ }
125
+
126
+ export function deleteDocument(id: string): boolean {
127
+ const db = getDb();
128
+ const result = db.prepare('DELETE FROM documents WHERE id = ?').run(id);
129
+ return result.changes > 0;
130
+ }
@@ -0,0 +1,185 @@
1
+ import { getDb, generateId } from './schema.ts';
2
+
3
+ /** Escape SQL LIKE wildcard characters in user input */
4
+ function escapeLike(s: string): string {
5
+ return s.replace(/[%_\\]/g, '\\$&');
6
+ }
7
+
8
+ export type EntityType = 'person' | 'project' | 'tool' | 'place' | 'concept' | 'event';
9
+
10
+ export type Entity = {
11
+ id: string;
12
+ type: EntityType;
13
+ name: string;
14
+ properties: Record<string, unknown> | null;
15
+ created_at: number;
16
+ updated_at: number;
17
+ source: string | null;
18
+ };
19
+
20
+ type EntityRow = {
21
+ id: string;
22
+ type: EntityType;
23
+ name: string;
24
+ properties: string | null;
25
+ created_at: number;
26
+ updated_at: number;
27
+ source: string | null;
28
+ };
29
+
30
+ /**
31
+ * Parse entity row from database, deserializing JSON fields
32
+ */
33
+ function parseEntity(row: EntityRow): Entity {
34
+ return {
35
+ ...row,
36
+ properties: row.properties ? JSON.parse(row.properties) : null,
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Create a new entity in the knowledge graph
42
+ */
43
+ export function createEntity(
44
+ type: EntityType,
45
+ name: string,
46
+ properties?: Record<string, unknown>,
47
+ source?: string
48
+ ): Entity {
49
+ const db = getDb();
50
+ const id = generateId();
51
+ const now = Date.now();
52
+
53
+ const stmt = db.prepare(
54
+ 'INSERT INTO entities (id, type, name, properties, created_at, updated_at, source) VALUES (?, ?, ?, ?, ?, ?, ?)'
55
+ );
56
+
57
+ stmt.run(
58
+ id,
59
+ type,
60
+ name,
61
+ properties ? JSON.stringify(properties) : null,
62
+ now,
63
+ now,
64
+ source ?? null
65
+ );
66
+
67
+ return {
68
+ id,
69
+ type,
70
+ name,
71
+ properties: properties ?? null,
72
+ created_at: now,
73
+ updated_at: now,
74
+ source: source ?? null,
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Get an entity by ID
80
+ */
81
+ export function getEntity(id: string): Entity | null {
82
+ const db = getDb();
83
+ const stmt = db.prepare('SELECT * FROM entities WHERE id = ?');
84
+ const row = stmt.get(id) as EntityRow | null;
85
+
86
+ if (!row) return null;
87
+
88
+ return parseEntity(row);
89
+ }
90
+
91
+ /**
92
+ * Find entities matching query criteria
93
+ */
94
+ export function findEntities(query: {
95
+ type?: EntityType;
96
+ name?: string;
97
+ nameContains?: string;
98
+ }): Entity[] {
99
+ const db = getDb();
100
+ const conditions: string[] = [];
101
+ const params: unknown[] = [];
102
+
103
+ if (query.type) {
104
+ conditions.push('type = ?');
105
+ params.push(query.type);
106
+ }
107
+
108
+ if (query.name) {
109
+ conditions.push('name = ?');
110
+ params.push(query.name);
111
+ }
112
+
113
+ if (query.nameContains) {
114
+ conditions.push("name LIKE ? ESCAPE '\\'");
115
+ params.push(`%${escapeLike(query.nameContains)}%`);
116
+ }
117
+
118
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
119
+ const stmt = db.prepare(`SELECT * FROM entities ${where} ORDER BY updated_at DESC`);
120
+ const rows = stmt.all(...params as any[]) as EntityRow[];
121
+
122
+ return rows.map(parseEntity);
123
+ }
124
+
125
+ /**
126
+ * Update an entity's properties
127
+ */
128
+ export function updateEntity(
129
+ id: string,
130
+ updates: Partial<Pick<Entity, 'name' | 'properties' | 'type'>>
131
+ ): Entity | null {
132
+ const db = getDb();
133
+ const entity = getEntity(id);
134
+ if (!entity) return null;
135
+
136
+ const fields: string[] = [];
137
+ const params: unknown[] = [];
138
+
139
+ if (updates.name !== undefined) {
140
+ fields.push('name = ?');
141
+ params.push(updates.name);
142
+ }
143
+
144
+ if (updates.type !== undefined) {
145
+ fields.push('type = ?');
146
+ params.push(updates.type);
147
+ }
148
+
149
+ if (updates.properties !== undefined) {
150
+ fields.push('properties = ?');
151
+ params.push(JSON.stringify(updates.properties));
152
+ }
153
+
154
+ if (fields.length === 0) return entity;
155
+
156
+ fields.push('updated_at = ?');
157
+ params.push(Date.now());
158
+
159
+ params.push(id);
160
+
161
+ const stmt = db.prepare(`UPDATE entities SET ${fields.join(', ')} WHERE id = ?`);
162
+ stmt.run(...params as any[]);
163
+
164
+ return getEntity(id);
165
+ }
166
+
167
+ /**
168
+ * Delete an entity and all related facts/relationships (via cascade)
169
+ */
170
+ export function deleteEntity(id: string): boolean {
171
+ const db = getDb();
172
+ const stmt = db.prepare('DELETE FROM entities WHERE id = ?');
173
+ const result = stmt.run(id);
174
+ return result.changes > 0;
175
+ }
176
+
177
+ /**
178
+ * Search entities by name using LIKE query
179
+ */
180
+ export function searchEntitiesByName(query: string): Entity[] {
181
+ const db = getDb();
182
+ const stmt = db.prepare("SELECT * FROM entities WHERE name LIKE ? ESCAPE '\\' ORDER BY name");
183
+ const rows = stmt.all(`%${escapeLike(query)}%`) as EntityRow[];
184
+ return rows.map(parseEntity);
185
+ }