@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,244 @@
1
+ /**
2
+ * Webapp Templates — Pre-built browser navigation instructions
3
+ *
4
+ * Stores per-app instructions that get injected into the system prompt
5
+ * when Jarvis detects a relevant webapp in the user's message or URL.
6
+ */
7
+
8
+ import { getDb, generateId } from './schema.ts';
9
+
10
+ export type WebappTemplate = {
11
+ id: string;
12
+ app_name: string;
13
+ domains: string[];
14
+ keywords: string[];
15
+ description: string;
16
+ instructions: string;
17
+ version: number;
18
+ enabled: boolean;
19
+ created_at: number;
20
+ updated_at: number;
21
+ };
22
+
23
+ type WebappRow = {
24
+ id: string;
25
+ app_name: string;
26
+ domains: string;
27
+ keywords: string;
28
+ description: string;
29
+ instructions: string;
30
+ version: number;
31
+ enabled: number;
32
+ created_at: number;
33
+ updated_at: number;
34
+ };
35
+
36
+ function rowToTemplate(row: WebappRow): WebappTemplate {
37
+ return {
38
+ ...row,
39
+ domains: JSON.parse(row.domains),
40
+ keywords: JSON.parse(row.keywords),
41
+ enabled: row.enabled === 1,
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Upsert a webapp template (insert or update by app_name).
47
+ */
48
+ export function upsertWebappTemplate(template: {
49
+ app_name: string;
50
+ domains: string[];
51
+ keywords?: string[];
52
+ description: string;
53
+ instructions: string;
54
+ version?: number;
55
+ enabled?: boolean;
56
+ }): WebappTemplate {
57
+ const db = getDb();
58
+ const now = Date.now();
59
+
60
+ // Check if exists
61
+ const existing = db.prepare(
62
+ 'SELECT id, version FROM webapp_templates WHERE app_name = ?'
63
+ ).get(template.app_name) as { id: string; version: number } | null;
64
+
65
+ if (existing) {
66
+ db.prepare(`
67
+ UPDATE webapp_templates
68
+ SET domains = ?, keywords = ?, description = ?, instructions = ?, version = ?, enabled = ?, updated_at = ?
69
+ WHERE id = ?
70
+ `).run(
71
+ JSON.stringify(template.domains),
72
+ JSON.stringify(template.keywords ?? []),
73
+ template.description,
74
+ template.instructions,
75
+ template.version ?? existing.version + 1,
76
+ (template.enabled ?? true) ? 1 : 0,
77
+ now,
78
+ existing.id,
79
+ );
80
+ return getWebappTemplate(existing.id)!;
81
+ }
82
+
83
+ const id = generateId();
84
+ db.prepare(`
85
+ INSERT INTO webapp_templates (id, app_name, domains, keywords, description, instructions, version, enabled, created_at, updated_at)
86
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
87
+ `).run(
88
+ id,
89
+ template.app_name,
90
+ JSON.stringify(template.domains),
91
+ JSON.stringify(template.keywords ?? []),
92
+ template.description,
93
+ template.instructions,
94
+ template.version ?? 1,
95
+ (template.enabled ?? true) ? 1 : 0,
96
+ now,
97
+ now,
98
+ );
99
+
100
+ return getWebappTemplate(id)!;
101
+ }
102
+
103
+ /**
104
+ * Get a template by ID.
105
+ */
106
+ export function getWebappTemplate(id: string): WebappTemplate | null {
107
+ const db = getDb();
108
+ const row = db.prepare('SELECT * FROM webapp_templates WHERE id = ?').get(id) as WebappRow | null;
109
+ return row ? rowToTemplate(row) : null;
110
+ }
111
+
112
+ /**
113
+ * Get a template by app name (case-insensitive).
114
+ */
115
+ export function getWebappTemplateByName(appName: string): WebappTemplate | null {
116
+ const db = getDb();
117
+ const row = db.prepare(
118
+ 'SELECT * FROM webapp_templates WHERE LOWER(app_name) = LOWER(?) AND enabled = 1'
119
+ ).get(appName) as WebappRow | null;
120
+ return row ? rowToTemplate(row) : null;
121
+ }
122
+
123
+ /**
124
+ * Find templates matching a domain (e.g. "web.whatsapp.com").
125
+ */
126
+ export function getWebappTemplateByDomain(url: string): WebappTemplate | null {
127
+ const db = getDb();
128
+ const rows = db.prepare(
129
+ 'SELECT * FROM webapp_templates WHERE enabled = 1'
130
+ ).all() as WebappRow[];
131
+
132
+ // Extract hostname from URL
133
+ let hostname: string;
134
+ try {
135
+ hostname = new URL(url.startsWith('http') ? url : `https://${url}`).hostname;
136
+ } catch {
137
+ hostname = url.toLowerCase();
138
+ }
139
+
140
+ for (const row of rows) {
141
+ const domains: string[] = JSON.parse(row.domains);
142
+ for (const domain of domains) {
143
+ if (hostname === domain || hostname.endsWith(`.${domain}`)) {
144
+ return rowToTemplate(row);
145
+ }
146
+ }
147
+ }
148
+
149
+ return null;
150
+ }
151
+
152
+ /**
153
+ * List all webapp templates.
154
+ */
155
+ export function listWebappTemplates(enabledOnly = true): WebappTemplate[] {
156
+ const db = getDb();
157
+ const query = enabledOnly
158
+ ? 'SELECT * FROM webapp_templates WHERE enabled = 1 ORDER BY app_name'
159
+ : 'SELECT * FROM webapp_templates ORDER BY app_name';
160
+ const rows = db.prepare(query).all() as WebappRow[];
161
+ return rows.map(rowToTemplate);
162
+ }
163
+
164
+ /**
165
+ * Match webapp templates against a user message.
166
+ * Checks for app name mentions, URL patterns, and keyword triggers.
167
+ * Returns all matching templates (usually 0-1).
168
+ */
169
+ export function matchWebappTemplates(message: string): WebappTemplate[] {
170
+ const db = getDb();
171
+ const rows = db.prepare(
172
+ 'SELECT * FROM webapp_templates WHERE enabled = 1'
173
+ ).all() as WebappRow[];
174
+
175
+ if (rows.length === 0) return [];
176
+
177
+ const msgLower = message.toLowerCase();
178
+ const matched: WebappTemplate[] = [];
179
+
180
+ for (const row of rows) {
181
+ const appNameLower = row.app_name.toLowerCase();
182
+
183
+ // Check if app name appears in message
184
+ if (msgLower.includes(appNameLower)) {
185
+ matched.push(rowToTemplate(row));
186
+ continue;
187
+ }
188
+
189
+ // Check if any domain appears in message
190
+ const domains: string[] = JSON.parse(row.domains);
191
+ let domainMatch = false;
192
+ for (const domain of domains) {
193
+ if (msgLower.includes(domain)) {
194
+ matched.push(rowToTemplate(row));
195
+ domainMatch = true;
196
+ break;
197
+ }
198
+ }
199
+ if (domainMatch) continue;
200
+
201
+ // Check if any keyword triggers match
202
+ const keywords: string[] = JSON.parse(row.keywords);
203
+ for (const keyword of keywords) {
204
+ if (msgLower.includes(keyword.toLowerCase())) {
205
+ matched.push(rowToTemplate(row));
206
+ break;
207
+ }
208
+ }
209
+ }
210
+
211
+ return matched;
212
+ }
213
+
214
+ /**
215
+ * Format matched templates into prompt-ready text.
216
+ */
217
+ export function formatWebappInstructions(templates: WebappTemplate[]): string {
218
+ if (templates.length === 0) return '';
219
+
220
+ const sections: string[] = [];
221
+
222
+ for (const t of templates) {
223
+ sections.push(`## ${t.app_name} — Browser Instructions`);
224
+ sections.push(`Domains: ${t.domains.join(', ')}`);
225
+ sections.push('');
226
+ sections.push(t.instructions);
227
+ }
228
+
229
+ return sections.join('\n');
230
+ }
231
+
232
+ /**
233
+ * Main entry: get formatted webapp instructions for a user message.
234
+ * Returns empty string if no matching templates found.
235
+ */
236
+ export function getWebappInstructionsForMessage(message: string): string {
237
+ try {
238
+ const templates = matchWebappTemplates(message);
239
+ return formatWebappInstructions(templates);
240
+ } catch (err) {
241
+ console.error('[WebappTemplates] Error matching templates:', err);
242
+ return '';
243
+ }
244
+ }
@@ -0,0 +1,403 @@
1
+ /**
2
+ * Workflow Vault — CRUD operations for workflow automation engine
3
+ */
4
+
5
+ import { getDb, generateId } from './schema.ts';
6
+ import type {
7
+ Workflow, WorkflowVersion, WorkflowExecution, WorkflowStepResult,
8
+ WorkflowDefinition, ExecutionStatus, StepStatus,
9
+ } from '../workflows/types.ts';
10
+
11
+ // ── Row types (raw DB) ──
12
+
13
+ type WorkflowRow = Omit<Workflow, 'enabled' | 'authority_approved' | 'tags'> & {
14
+ enabled: number;
15
+ authority_approved: number;
16
+ tags: string | null;
17
+ };
18
+
19
+ type VersionRow = Omit<WorkflowVersion, 'definition'> & { definition: string };
20
+ type ExecutionRow = Omit<WorkflowExecution, 'trigger_data' | 'variables'> & {
21
+ trigger_data: string | null;
22
+ variables: string | null;
23
+ };
24
+ type StepRow = Omit<WorkflowStepResult, 'input_data' | 'output_data'> & {
25
+ input_data: string | null;
26
+ output_data: string | null;
27
+ };
28
+
29
+ // ── Parsers ──
30
+
31
+ function parseWorkflow(row: WorkflowRow): Workflow {
32
+ return {
33
+ ...row,
34
+ enabled: row.enabled === 1,
35
+ authority_approved: row.authority_approved === 1,
36
+ tags: row.tags ? JSON.parse(row.tags) : [],
37
+ };
38
+ }
39
+
40
+ function parseVersion(row: VersionRow): WorkflowVersion {
41
+ return { ...row, definition: JSON.parse(row.definition) };
42
+ }
43
+
44
+ function parseExecution(row: ExecutionRow): WorkflowExecution {
45
+ return {
46
+ ...row,
47
+ trigger_data: row.trigger_data ? JSON.parse(row.trigger_data) : null,
48
+ variables: row.variables ? JSON.parse(row.variables) : {},
49
+ };
50
+ }
51
+
52
+ function parseStep(row: StepRow): WorkflowStepResult {
53
+ return {
54
+ ...row,
55
+ input_data: row.input_data ? JSON.parse(row.input_data) : null,
56
+ output_data: row.output_data ? JSON.parse(row.output_data) : null,
57
+ };
58
+ }
59
+
60
+ // ── Workflows ──
61
+
62
+ export function createWorkflow(
63
+ name: string,
64
+ opts?: {
65
+ description?: string;
66
+ authority_level?: number;
67
+ tags?: string[];
68
+ enabled?: boolean;
69
+ }
70
+ ): Workflow {
71
+ const db = getDb();
72
+ const id = generateId();
73
+ const now = Date.now();
74
+
75
+ db.prepare(
76
+ `INSERT INTO workflows (id, name, description, enabled, authority_level, authority_approved, tags, current_version, execution_count, created_at, updated_at)
77
+ VALUES (?, ?, ?, ?, ?, 0, ?, 1, 0, ?, ?)`
78
+ ).run(
79
+ id, name,
80
+ opts?.description ?? '',
81
+ opts?.enabled !== false ? 1 : 0,
82
+ opts?.authority_level ?? 3,
83
+ opts?.tags ? JSON.stringify(opts.tags) : null,
84
+ now, now,
85
+ );
86
+
87
+ return {
88
+ id, name,
89
+ description: opts?.description ?? '',
90
+ enabled: opts?.enabled !== false,
91
+ authority_level: opts?.authority_level ?? 3,
92
+ authority_approved: false,
93
+ approved_at: null,
94
+ approved_by: null,
95
+ tags: opts?.tags ?? [],
96
+ current_version: 1,
97
+ execution_count: 0,
98
+ last_executed_at: null,
99
+ last_success_at: null,
100
+ last_failure_at: null,
101
+ created_at: now,
102
+ updated_at: now,
103
+ };
104
+ }
105
+
106
+ export function getWorkflow(id: string): Workflow | null {
107
+ const row = getDb().prepare('SELECT * FROM workflows WHERE id = ?').get(id) as WorkflowRow | null;
108
+ return row ? parseWorkflow(row) : null;
109
+ }
110
+
111
+ export function findWorkflows(query?: {
112
+ enabled?: boolean;
113
+ tag?: string;
114
+ limit?: number;
115
+ }): Workflow[] {
116
+ const conditions: string[] = [];
117
+ const params: unknown[] = [];
118
+
119
+ if (query?.enabled !== undefined) {
120
+ conditions.push('enabled = ?');
121
+ params.push(query.enabled ? 1 : 0);
122
+ }
123
+
124
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
125
+ const limitVal = query?.limit ? Math.max(1, Math.min(parseInt(String(query.limit), 10) || 100, 1000)) : null;
126
+ const limitClause = limitVal ? 'LIMIT ?' : '';
127
+ if (limitVal) params.push(limitVal);
128
+ const rows = getDb().prepare(`SELECT * FROM workflows ${where} ORDER BY updated_at DESC ${limitClause}`).all(...params as any[]) as WorkflowRow[];
129
+
130
+ let result = rows.map(parseWorkflow);
131
+ if (query?.tag) {
132
+ result = result.filter(w => w.tags.includes(query.tag!));
133
+ }
134
+ return result;
135
+ }
136
+
137
+ export function updateWorkflow(
138
+ id: string,
139
+ updates: Partial<Pick<Workflow, 'name' | 'description' | 'enabled' | 'authority_level' | 'authority_approved' | 'approved_at' | 'approved_by' | 'tags' | 'current_version' | 'execution_count' | 'last_executed_at' | 'last_success_at' | 'last_failure_at'>>
140
+ ): Workflow | null {
141
+ const existing = getWorkflow(id);
142
+ if (!existing) return null;
143
+
144
+ const db = getDb();
145
+ const sets: string[] = ['updated_at = ?'];
146
+ const params: unknown[] = [Date.now()];
147
+
148
+ if (updates.name !== undefined) { sets.push('name = ?'); params.push(updates.name); }
149
+ if (updates.description !== undefined) { sets.push('description = ?'); params.push(updates.description); }
150
+ if (updates.enabled !== undefined) { sets.push('enabled = ?'); params.push(updates.enabled ? 1 : 0); }
151
+ if (updates.authority_level !== undefined) { sets.push('authority_level = ?'); params.push(updates.authority_level); }
152
+ if (updates.authority_approved !== undefined) { sets.push('authority_approved = ?'); params.push(updates.authority_approved ? 1 : 0); }
153
+ if (updates.approved_at !== undefined) { sets.push('approved_at = ?'); params.push(updates.approved_at); }
154
+ if (updates.approved_by !== undefined) { sets.push('approved_by = ?'); params.push(updates.approved_by); }
155
+ if (updates.tags !== undefined) { sets.push('tags = ?'); params.push(JSON.stringify(updates.tags)); }
156
+ if (updates.current_version !== undefined) { sets.push('current_version = ?'); params.push(updates.current_version); }
157
+ if (updates.execution_count !== undefined) { sets.push('execution_count = ?'); params.push(updates.execution_count); }
158
+ if (updates.last_executed_at !== undefined) { sets.push('last_executed_at = ?'); params.push(updates.last_executed_at); }
159
+ if (updates.last_success_at !== undefined) { sets.push('last_success_at = ?'); params.push(updates.last_success_at); }
160
+ if (updates.last_failure_at !== undefined) { sets.push('last_failure_at = ?'); params.push(updates.last_failure_at); }
161
+
162
+ params.push(id);
163
+ db.prepare(`UPDATE workflows SET ${sets.join(', ')} WHERE id = ?`).run(...params as any[]);
164
+ return getWorkflow(id);
165
+ }
166
+
167
+ export function deleteWorkflow(id: string): boolean {
168
+ const result = getDb().prepare('DELETE FROM workflows WHERE id = ?').run(id);
169
+ return result.changes > 0;
170
+ }
171
+
172
+ // ── Versions ──
173
+
174
+ export function createVersion(
175
+ workflowId: string,
176
+ definition: WorkflowDefinition,
177
+ changelog?: string,
178
+ createdBy?: string,
179
+ ): WorkflowVersion {
180
+ const db = getDb();
181
+ const id = generateId();
182
+ const now = Date.now();
183
+
184
+ // Get next version number
185
+ const latest = db.prepare(
186
+ 'SELECT MAX(version) as max_v FROM workflow_versions WHERE workflow_id = ?'
187
+ ).get(workflowId) as { max_v: number | null } | null;
188
+ const version = (latest?.max_v ?? 0) + 1;
189
+
190
+ db.prepare(
191
+ `INSERT INTO workflow_versions (id, workflow_id, version, definition, changelog, created_by, created_at)
192
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
193
+ ).run(id, workflowId, version, JSON.stringify(definition), changelog ?? null, createdBy ?? 'user', now);
194
+
195
+ // Update current_version on workflow
196
+ db.prepare('UPDATE workflows SET current_version = ?, updated_at = ? WHERE id = ?').run(version, now, workflowId);
197
+
198
+ return { id, workflow_id: workflowId, version, definition, changelog: changelog ?? null, created_by: createdBy ?? 'user', created_at: now };
199
+ }
200
+
201
+ export function getVersion(workflowId: string, version: number): WorkflowVersion | null {
202
+ const row = getDb().prepare(
203
+ 'SELECT * FROM workflow_versions WHERE workflow_id = ? AND version = ?'
204
+ ).get(workflowId, version) as VersionRow | null;
205
+ return row ? parseVersion(row) : null;
206
+ }
207
+
208
+ export function getLatestVersion(workflowId: string): WorkflowVersion | null {
209
+ const row = getDb().prepare(
210
+ 'SELECT * FROM workflow_versions WHERE workflow_id = ? ORDER BY version DESC LIMIT 1'
211
+ ).get(workflowId) as VersionRow | null;
212
+ return row ? parseVersion(row) : null;
213
+ }
214
+
215
+ export function getVersionHistory(workflowId: string): WorkflowVersion[] {
216
+ const rows = getDb().prepare(
217
+ 'SELECT * FROM workflow_versions WHERE workflow_id = ? ORDER BY version DESC'
218
+ ).all(workflowId) as VersionRow[];
219
+ return rows.map(parseVersion);
220
+ }
221
+
222
+ // ── Executions ──
223
+
224
+ export function createExecution(
225
+ workflowId: string,
226
+ version: number,
227
+ triggerType: string,
228
+ triggerData?: Record<string, unknown>,
229
+ ): WorkflowExecution {
230
+ const db = getDb();
231
+ const id = generateId();
232
+ const now = Date.now();
233
+
234
+ db.prepare(
235
+ `INSERT INTO workflow_executions (id, workflow_id, version, trigger_type, trigger_data, status, variables, started_at)
236
+ VALUES (?, ?, ?, ?, ?, 'running', '{}', ?)`
237
+ ).run(id, workflowId, version, triggerType, triggerData ? JSON.stringify(triggerData) : null, now);
238
+
239
+ // Bump execution count
240
+ db.prepare(
241
+ 'UPDATE workflows SET execution_count = execution_count + 1, last_executed_at = ?, updated_at = ? WHERE id = ?'
242
+ ).run(now, now, workflowId);
243
+
244
+ return {
245
+ id, workflow_id: workflowId, version, trigger_type: triggerType,
246
+ trigger_data: triggerData ?? null, status: 'running', variables: {},
247
+ error_message: null, started_at: now, completed_at: null, duration_ms: null,
248
+ };
249
+ }
250
+
251
+ export function getExecution(id: string): WorkflowExecution | null {
252
+ const row = getDb().prepare('SELECT * FROM workflow_executions WHERE id = ?').get(id) as ExecutionRow | null;
253
+ return row ? parseExecution(row) : null;
254
+ }
255
+
256
+ export function updateExecution(
257
+ id: string,
258
+ updates: Partial<Pick<WorkflowExecution, 'status' | 'variables' | 'error_message' | 'completed_at' | 'duration_ms'>>
259
+ ): WorkflowExecution | null {
260
+ const db = getDb();
261
+ const sets: string[] = [];
262
+ const params: unknown[] = [];
263
+
264
+ if (updates.status !== undefined) { sets.push('status = ?'); params.push(updates.status); }
265
+ if (updates.variables !== undefined) { sets.push('variables = ?'); params.push(JSON.stringify(updates.variables)); }
266
+ if (updates.error_message !== undefined) { sets.push('error_message = ?'); params.push(updates.error_message); }
267
+ if (updates.completed_at !== undefined) { sets.push('completed_at = ?'); params.push(updates.completed_at); }
268
+ if (updates.duration_ms !== undefined) { sets.push('duration_ms = ?'); params.push(updates.duration_ms); }
269
+
270
+ if (sets.length === 0) return getExecution(id);
271
+
272
+ params.push(id);
273
+ db.prepare(`UPDATE workflow_executions SET ${sets.join(', ')} WHERE id = ?`).run(...params as any[]);
274
+
275
+ // Update workflow success/failure timestamps
276
+ if (updates.status === 'completed' || updates.status === 'failed') {
277
+ const exec = getExecution(id);
278
+ if (exec) {
279
+ const field = updates.status === 'completed' ? 'last_success_at' : 'last_failure_at';
280
+ db.prepare(`UPDATE workflows SET ${field} = ?, updated_at = ? WHERE id = ?`).run(Date.now(), Date.now(), exec.workflow_id);
281
+ }
282
+ }
283
+
284
+ return getExecution(id);
285
+ }
286
+
287
+ export function findExecutions(query: {
288
+ workflow_id?: string;
289
+ status?: ExecutionStatus;
290
+ limit?: number;
291
+ }): WorkflowExecution[] {
292
+ const conditions: string[] = [];
293
+ const params: unknown[] = [];
294
+
295
+ if (query.workflow_id) { conditions.push('workflow_id = ?'); params.push(query.workflow_id); }
296
+ if (query.status) { conditions.push('status = ?'); params.push(query.status); }
297
+
298
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
299
+ const limitVal = Math.max(1, Math.min(parseInt(String(query.limit ?? 100), 10) || 100, 1000));
300
+ params.push(limitVal);
301
+ const rows = getDb().prepare(`SELECT * FROM workflow_executions ${where} ORDER BY started_at DESC LIMIT ?`).all(...params as any[]) as ExecutionRow[];
302
+ return rows.map(parseExecution);
303
+ }
304
+
305
+ // ── Step Results ──
306
+
307
+ export function createStepResult(
308
+ executionId: string,
309
+ nodeId: string,
310
+ nodeType: string,
311
+ ): WorkflowStepResult {
312
+ const db = getDb();
313
+ const id = generateId();
314
+
315
+ db.prepare(
316
+ `INSERT INTO workflow_step_results (id, execution_id, node_id, node_type, status, retry_count)
317
+ VALUES (?, ?, ?, ?, 'pending', 0)`
318
+ ).run(id, executionId, nodeId, nodeType);
319
+
320
+ return {
321
+ id, execution_id: executionId, node_id: nodeId, node_type: nodeType,
322
+ status: 'pending', input_data: null, output_data: null, error_message: null,
323
+ retry_count: 0, started_at: null, completed_at: null, duration_ms: null,
324
+ };
325
+ }
326
+
327
+ export function updateStepResult(
328
+ id: string,
329
+ updates: Partial<Pick<WorkflowStepResult, 'status' | 'input_data' | 'output_data' | 'error_message' | 'retry_count' | 'started_at' | 'completed_at' | 'duration_ms'>>
330
+ ): WorkflowStepResult | null {
331
+ const db = getDb();
332
+ const sets: string[] = [];
333
+ const params: unknown[] = [];
334
+
335
+ if (updates.status !== undefined) { sets.push('status = ?'); params.push(updates.status); }
336
+ if (updates.input_data !== undefined) { sets.push('input_data = ?'); params.push(JSON.stringify(updates.input_data)); }
337
+ if (updates.output_data !== undefined) { sets.push('output_data = ?'); params.push(JSON.stringify(updates.output_data)); }
338
+ if (updates.error_message !== undefined) { sets.push('error_message = ?'); params.push(updates.error_message); }
339
+ if (updates.retry_count !== undefined) { sets.push('retry_count = ?'); params.push(updates.retry_count); }
340
+ if (updates.started_at !== undefined) { sets.push('started_at = ?'); params.push(updates.started_at); }
341
+ if (updates.completed_at !== undefined) { sets.push('completed_at = ?'); params.push(updates.completed_at); }
342
+ if (updates.duration_ms !== undefined) { sets.push('duration_ms = ?'); params.push(updates.duration_ms); }
343
+
344
+ if (sets.length === 0) return null;
345
+
346
+ params.push(id);
347
+ db.prepare(`UPDATE workflow_step_results SET ${sets.join(', ')} WHERE id = ?`).run(...params as any[]);
348
+
349
+ const row = db.prepare('SELECT * FROM workflow_step_results WHERE id = ?').get(id) as StepRow | null;
350
+ return row ? parseStep(row) : null;
351
+ }
352
+
353
+ export function getStepResults(executionId: string): WorkflowStepResult[] {
354
+ const rows = getDb().prepare(
355
+ 'SELECT * FROM workflow_step_results WHERE execution_id = ? ORDER BY started_at ASC'
356
+ ).all(executionId) as StepRow[];
357
+ return rows.map(parseStep);
358
+ }
359
+
360
+ // ── Persistent Variables ──
361
+
362
+ export function getVariable(workflowId: string, key: string): unknown | null {
363
+ const row = getDb().prepare(
364
+ 'SELECT value FROM workflow_variables WHERE workflow_id = ? AND key = ?'
365
+ ).get(workflowId, key) as { value: string } | null;
366
+ return row ? JSON.parse(row.value) : null;
367
+ }
368
+
369
+ export function setVariable(workflowId: string, key: string, value: unknown): void {
370
+ const db = getDb();
371
+ const now = Date.now();
372
+ const existing = db.prepare(
373
+ 'SELECT id FROM workflow_variables WHERE workflow_id = ? AND key = ?'
374
+ ).get(workflowId, key) as { id: string } | null;
375
+
376
+ if (existing) {
377
+ db.prepare('UPDATE workflow_variables SET value = ?, updated_at = ? WHERE id = ?')
378
+ .run(JSON.stringify(value), now, existing.id);
379
+ } else {
380
+ db.prepare(
381
+ 'INSERT INTO workflow_variables (id, workflow_id, key, value, updated_at) VALUES (?, ?, ?, ?, ?)'
382
+ ).run(generateId(), workflowId, key, JSON.stringify(value), now);
383
+ }
384
+ }
385
+
386
+ export function getVariables(workflowId: string): Record<string, unknown> {
387
+ const rows = getDb().prepare(
388
+ 'SELECT key, value FROM workflow_variables WHERE workflow_id = ?'
389
+ ).all(workflowId) as { key: string; value: string }[];
390
+
391
+ const result: Record<string, unknown> = {};
392
+ for (const row of rows) {
393
+ result[row.key] = JSON.parse(row.value);
394
+ }
395
+ return result;
396
+ }
397
+
398
+ export function deleteVariable(workflowId: string, key: string): boolean {
399
+ const result = getDb().prepare(
400
+ 'DELETE FROM workflow_variables WHERE workflow_id = ? AND key = ?'
401
+ ).run(workflowId, key);
402
+ return result.changes > 0;
403
+ }