@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,92 @@
1
+ import { getDb, generateId } from './schema.ts';
2
+
3
+ export type VectorRecord = {
4
+ id: string;
5
+ ref_type: string;
6
+ ref_id: string;
7
+ embedding: Float32Array;
8
+ model: string;
9
+ created_at: number;
10
+ };
11
+
12
+ type VectorRow = {
13
+ id: string;
14
+ ref_type: string;
15
+ ref_id: string;
16
+ embedding: ArrayBuffer;
17
+ model: string;
18
+ created_at: number;
19
+ };
20
+
21
+ /**
22
+ * Parse vector row from database, converting BLOB to Float32Array
23
+ */
24
+ function parseVector(row: VectorRow): VectorRecord {
25
+ return {
26
+ ...row,
27
+ embedding: new Float32Array(row.embedding),
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Store a vector embedding for a reference entity or fact
33
+ */
34
+ export function storeVector(
35
+ ref_type: string,
36
+ ref_id: string,
37
+ embedding: Float32Array,
38
+ model: string
39
+ ): VectorRecord {
40
+ const db = getDb();
41
+ const id = generateId();
42
+ const now = Date.now();
43
+
44
+ // Convert Float32Array to Buffer for SQLite BLOB storage
45
+ const buffer = Buffer.from(embedding.buffer);
46
+
47
+ const stmt = db.prepare(
48
+ 'INSERT INTO vectors (id, ref_type, ref_id, embedding, model, created_at) VALUES (?, ?, ?, ?, ?, ?)'
49
+ );
50
+
51
+ stmt.run(id, ref_type, ref_id, buffer, model, now);
52
+
53
+ return {
54
+ id,
55
+ ref_type,
56
+ ref_id,
57
+ embedding,
58
+ model,
59
+ created_at: now,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Find similar vectors using cosine similarity
65
+ *
66
+ * TODO: This is a stub implementation. For production use, integrate sqlite-vec extension
67
+ * which provides optimized vector similarity search with HNSW indexing.
68
+ *
69
+ * See: https://github.com/asg017/sqlite-vec
70
+ *
71
+ * Example with sqlite-vec:
72
+ * SELECT ref_type, ref_id, vec_distance_cosine(embedding, ?) as similarity
73
+ * FROM vectors
74
+ * ORDER BY similarity DESC
75
+ * LIMIT ?
76
+ */
77
+ export function findSimilar(
78
+ embedding: Float32Array,
79
+ limit: number = 10
80
+ ): Array<{ ref_type: string; ref_id: string; similarity: number }> {
81
+ // TODO: Implement vector similarity search with sqlite-vec extension
82
+ return [];
83
+ }
84
+
85
+ /**
86
+ * Delete all vectors for a given reference
87
+ */
88
+ export function deleteVectors(ref_type: string, ref_id: string): void {
89
+ const db = getDb();
90
+ const stmt = db.prepare('DELETE FROM vectors WHERE ref_type = ? AND ref_id = ?');
91
+ stmt.run(ref_type, ref_id);
92
+ }
@@ -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
+ }