botinabox 2.5.0 → 2.5.2

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 (278) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +190 -190
  3. package/bin/botinabox.mjs +2 -2
  4. package/dist/channels/discord/adapter.d.ts +32 -0
  5. package/dist/channels/discord/adapter.js +70 -0
  6. package/dist/channels/discord/inbound.d.ts +25 -0
  7. package/dist/channels/discord/inbound.js +24 -0
  8. package/dist/channels/discord/models.d.ts +8 -0
  9. package/dist/channels/discord/models.js +5 -0
  10. package/dist/channels/discord/outbound.d.ts +14 -0
  11. package/dist/channels/discord/outbound.js +38 -0
  12. package/dist/channels/slack/adapter.d.ts +33 -0
  13. package/dist/channels/slack/adapter.js +74 -0
  14. package/dist/channels/slack/inbound.d.ts +59 -0
  15. package/dist/channels/slack/inbound.js +96 -0
  16. package/dist/channels/slack/models.d.ts +9 -0
  17. package/dist/channels/slack/models.js +5 -0
  18. package/dist/channels/slack/outbound.d.ts +12 -0
  19. package/dist/channels/slack/outbound.js +18 -0
  20. package/dist/channels/slack/transcribe.d.ts +41 -0
  21. package/dist/channels/slack/transcribe.js +106 -0
  22. package/dist/channels/webhook/adapter.d.ts +23 -0
  23. package/dist/channels/webhook/adapter.js +86 -0
  24. package/dist/channels/webhook/hmac.d.ts +13 -0
  25. package/dist/channels/webhook/hmac.js +26 -0
  26. package/dist/channels/webhook/models.d.ts +9 -0
  27. package/dist/channels/webhook/models.js +5 -0
  28. package/dist/channels/webhook/server.d.ts +20 -0
  29. package/dist/channels/webhook/server.js +91 -0
  30. package/dist/chat-pipeline-BWrtVqEP.d.ts +652 -0
  31. package/dist/chat-pipeline-C-XlLGNl.d.ts +648 -0
  32. package/dist/chat-pipeline-CR1KF6eX.d.ts +652 -0
  33. package/dist/chat-pipeline-DisuC8SB.d.ts +643 -0
  34. package/dist/chunk-2LGXQPEA.js +41 -0
  35. package/dist/chunk-3X3YKI4T.js +357 -0
  36. package/dist/chunk-D47AIFOD.js +351 -0
  37. package/dist/chunk-DSNJKNEW.js +328 -0
  38. package/dist/chunk-GS2JFL6I.js +144 -0
  39. package/dist/chunk-J6S6QMUY.js +144 -0
  40. package/dist/chunk-QLA6YOFN.js +22 -0
  41. package/dist/chunk-UACT2WXX.js +381 -0
  42. package/dist/cli/templates/config.yml.d.ts +7 -0
  43. package/dist/cli/templates/config.yml.js +61 -0
  44. package/dist/cli/templates/env.d.ts +1 -0
  45. package/dist/cli/templates/env.js +30 -0
  46. package/dist/cli/templates/index.ts.d.ts +2 -0
  47. package/dist/cli/templates/index.ts.js +30 -0
  48. package/dist/cli/templates/package.json.d.ts +5 -0
  49. package/dist/cli/templates/package.json.js +28 -0
  50. package/dist/cli.js +0 -0
  51. package/dist/connector-DDahQw-2.d.ts +63 -0
  52. package/dist/connectors/google/calendar-connector.d.ts +40 -0
  53. package/dist/connectors/google/calendar-connector.js +243 -0
  54. package/dist/connectors/google/gmail-connector.d.ts +42 -0
  55. package/dist/connectors/google/gmail-connector.js +345 -0
  56. package/dist/connectors/google/index.d.ts +67 -1
  57. package/dist/connectors/google/index.js +240 -0
  58. package/dist/connectors/google/oauth.d.ts +48 -0
  59. package/dist/connectors/google/oauth.js +112 -0
  60. package/dist/connectors/google/types.d.ts +78 -0
  61. package/dist/connectors/google/types.js +2 -0
  62. package/dist/core/chat/auto-discovery.d.ts +16 -0
  63. package/dist/core/chat/auto-discovery.js +54 -0
  64. package/dist/core/chat/channel-registry.d.ts +45 -0
  65. package/dist/core/chat/channel-registry.js +96 -0
  66. package/dist/core/chat/chat-pipeline.d.ts +113 -0
  67. package/dist/core/chat/chat-pipeline.js +395 -0
  68. package/dist/core/chat/chat-responder.d.ts +90 -0
  69. package/dist/core/chat/chat-responder.js +185 -0
  70. package/dist/core/chat/formatter.d.ts +11 -0
  71. package/dist/core/chat/formatter.js +60 -0
  72. package/dist/core/chat/index.d.ts +24 -0
  73. package/dist/core/chat/index.js +18 -0
  74. package/dist/core/chat/message-interpreter.d.ts +91 -0
  75. package/dist/core/chat/message-interpreter.js +166 -0
  76. package/dist/core/chat/message-store.d.ts +66 -0
  77. package/dist/core/chat/message-store.js +131 -0
  78. package/dist/core/chat/notification-queue.d.ts +34 -0
  79. package/dist/core/chat/notification-queue.js +111 -0
  80. package/dist/core/chat/pipeline.d.ts +38 -0
  81. package/dist/core/chat/pipeline.js +89 -0
  82. package/dist/core/chat/policies.d.ts +16 -0
  83. package/dist/core/chat/policies.js +25 -0
  84. package/dist/core/chat/routing.d.ts +17 -0
  85. package/dist/core/chat/routing.js +36 -0
  86. package/dist/core/chat/session-key.d.ts +30 -0
  87. package/dist/core/chat/session-key.js +65 -0
  88. package/dist/core/chat/session-manager.d.ts +17 -0
  89. package/dist/core/chat/session-manager.js +23 -0
  90. package/dist/core/chat/text-chunker.d.ts +9 -0
  91. package/dist/core/chat/text-chunker.js +48 -0
  92. package/dist/core/chat/triage-router.d.ts +75 -0
  93. package/dist/core/chat/triage-router.js +142 -0
  94. package/dist/core/chat/types.d.ts +5 -0
  95. package/dist/core/chat/types.js +5 -0
  96. package/dist/core/config/defaults.d.ts +2 -0
  97. package/dist/core/config/defaults.js +38 -0
  98. package/dist/core/config/index.d.ts +6 -0
  99. package/dist/core/config/index.js +4 -0
  100. package/dist/core/config/interpolate.d.ts +5 -0
  101. package/dist/core/config/interpolate.js +27 -0
  102. package/dist/core/config/loader.d.ts +24 -0
  103. package/dist/core/config/loader.js +59 -0
  104. package/dist/core/config/schema.d.ts +5 -0
  105. package/dist/core/config/schema.js +119 -0
  106. package/dist/core/data/core-entity-contexts.d.ts +14 -0
  107. package/dist/core/data/core-entity-contexts.js +197 -0
  108. package/dist/core/data/core-migrations.d.ts +5 -0
  109. package/dist/core/data/core-migrations.js +45 -0
  110. package/dist/core/data/core-schema.d.ts +6 -0
  111. package/dist/core/data/core-schema.js +454 -0
  112. package/dist/core/data/data-store.d.ts +67 -0
  113. package/dist/core/data/data-store.js +218 -0
  114. package/dist/core/data/domain-entity-contexts.d.ts +29 -0
  115. package/dist/core/data/domain-entity-contexts.js +321 -0
  116. package/dist/core/data/domain-schema.d.ts +36 -0
  117. package/dist/core/data/domain-schema.js +323 -0
  118. package/dist/core/data/index.d.ts +7 -0
  119. package/dist/core/data/index.js +7 -0
  120. package/dist/core/data/types.d.ts +111 -0
  121. package/dist/core/data/types.js +1 -0
  122. package/dist/core/hooks/hook-bus.d.ts +18 -0
  123. package/dist/core/hooks/hook-bus.js +120 -0
  124. package/dist/core/hooks/index.d.ts +2 -0
  125. package/dist/core/hooks/index.js +1 -0
  126. package/dist/core/hooks/types.d.ts +19 -0
  127. package/dist/core/hooks/types.js +1 -0
  128. package/dist/core/index.d.ts +4 -0
  129. package/dist/core/index.js +4 -0
  130. package/dist/core/llm/auto-discovery.d.ts +11 -0
  131. package/dist/core/llm/auto-discovery.js +49 -0
  132. package/dist/core/llm/cost-tracker.d.ts +6 -0
  133. package/dist/core/llm/cost-tracker.js +38 -0
  134. package/dist/core/llm/index.d.ts +4 -0
  135. package/dist/core/llm/index.js +3 -0
  136. package/dist/core/llm/model-router.d.ts +25 -0
  137. package/dist/core/llm/model-router.js +49 -0
  138. package/dist/core/llm/provider-registry.d.ts +9 -0
  139. package/dist/core/llm/provider-registry.js +25 -0
  140. package/dist/core/llm/types.d.ts +2 -0
  141. package/dist/core/llm/types.js +2 -0
  142. package/dist/core/orchestrator/adapters/api-adapter.d.ts +34 -0
  143. package/dist/core/orchestrator/adapters/api-adapter.js +88 -0
  144. package/dist/core/orchestrator/adapters/cli-adapter.d.ts +22 -0
  145. package/dist/core/orchestrator/adapters/cli-adapter.js +69 -0
  146. package/dist/core/orchestrator/adapters/deterministic-adapter.d.ts +35 -0
  147. package/dist/core/orchestrator/adapters/deterministic-adapter.js +75 -0
  148. package/dist/core/orchestrator/adapters/env-whitelist.d.ts +4 -0
  149. package/dist/core/orchestrator/adapters/env-whitelist.js +27 -0
  150. package/dist/core/orchestrator/adapters/output-extractor.d.ts +11 -0
  151. package/dist/core/orchestrator/adapters/output-extractor.js +59 -0
  152. package/dist/core/orchestrator/adapters/process-manager.d.ts +15 -0
  153. package/dist/core/orchestrator/adapters/process-manager.js +26 -0
  154. package/dist/core/orchestrator/adapters/tool-loop.d.ts +22 -0
  155. package/dist/core/orchestrator/adapters/tool-loop.js +66 -0
  156. package/dist/core/orchestrator/agent-registry.d.ts +31 -0
  157. package/dist/core/orchestrator/agent-registry.js +135 -0
  158. package/dist/core/orchestrator/budget-controller.d.ts +19 -0
  159. package/dist/core/orchestrator/budget-controller.js +73 -0
  160. package/dist/core/orchestrator/chain-guard.d.ts +14 -0
  161. package/dist/core/orchestrator/chain-guard.js +23 -0
  162. package/dist/core/orchestrator/circuit-breaker.d.ts +65 -0
  163. package/dist/core/orchestrator/circuit-breaker.js +159 -0
  164. package/dist/core/orchestrator/claude-stream-parser.d.ts +31 -0
  165. package/dist/core/orchestrator/claude-stream-parser.js +99 -0
  166. package/dist/core/orchestrator/config-revisions.d.ts +6 -0
  167. package/dist/core/orchestrator/config-revisions.js +17 -0
  168. package/dist/core/orchestrator/dependency-resolver.d.ts +20 -0
  169. package/dist/core/orchestrator/dependency-resolver.js +78 -0
  170. package/dist/core/orchestrator/governance-gate.d.ts +110 -0
  171. package/dist/core/orchestrator/governance-gate.js +170 -0
  172. package/dist/core/orchestrator/learning-pipeline.d.ts +109 -0
  173. package/dist/core/orchestrator/learning-pipeline.js +249 -0
  174. package/dist/core/orchestrator/loop-detector.d.ts +51 -0
  175. package/dist/core/orchestrator/loop-detector.js +133 -0
  176. package/dist/core/orchestrator/ndjson-logger.d.ts +6 -0
  177. package/dist/core/orchestrator/ndjson-logger.js +18 -0
  178. package/dist/core/orchestrator/permission-relay.d.ts +72 -0
  179. package/dist/core/orchestrator/permission-relay.js +164 -0
  180. package/dist/core/orchestrator/run-manager.d.ts +31 -0
  181. package/dist/core/orchestrator/run-manager.js +178 -0
  182. package/dist/core/orchestrator/scheduler.d.ts +70 -0
  183. package/dist/core/orchestrator/scheduler.js +198 -0
  184. package/dist/core/orchestrator/secret-store.d.ts +57 -0
  185. package/dist/core/orchestrator/secret-store.js +171 -0
  186. package/dist/core/orchestrator/session-manager.d.ts +13 -0
  187. package/dist/core/orchestrator/session-manager.js +66 -0
  188. package/dist/core/orchestrator/task-queue.d.ts +34 -0
  189. package/dist/core/orchestrator/task-queue.js +83 -0
  190. package/dist/core/orchestrator/template-interpolate.d.ts +5 -0
  191. package/dist/core/orchestrator/template-interpolate.js +18 -0
  192. package/dist/core/orchestrator/user-registry.d.ts +47 -0
  193. package/dist/core/orchestrator/user-registry.js +76 -0
  194. package/dist/core/orchestrator/wakeup-queue.d.ts +9 -0
  195. package/dist/core/orchestrator/wakeup-queue.js +45 -0
  196. package/dist/core/orchestrator/workflow-engine.d.ts +47 -0
  197. package/dist/core/orchestrator/workflow-engine.js +204 -0
  198. package/dist/core/security/audit.d.ts +20 -0
  199. package/dist/core/security/audit.js +33 -0
  200. package/dist/core/security/column-validator.d.ts +20 -0
  201. package/dist/core/security/column-validator.js +37 -0
  202. package/dist/core/security/index.d.ts +5 -0
  203. package/dist/core/security/index.js +5 -0
  204. package/dist/core/security/process-env.d.ts +13 -0
  205. package/dist/core/security/process-env.js +49 -0
  206. package/dist/core/security/sanitizer.d.ts +11 -0
  207. package/dist/core/security/sanitizer.js +39 -0
  208. package/dist/core/security/types.d.ts +11 -0
  209. package/dist/core/security/types.js +1 -0
  210. package/dist/core/update/auto-update.d.ts +21 -0
  211. package/dist/core/update/auto-update.js +102 -0
  212. package/dist/core/update/backup-manager.d.ts +7 -0
  213. package/dist/core/update/backup-manager.js +24 -0
  214. package/dist/core/update/index.d.ts +8 -0
  215. package/dist/core/update/index.js +6 -0
  216. package/dist/core/update/migration-hooks.d.ts +11 -0
  217. package/dist/core/update/migration-hooks.js +10 -0
  218. package/dist/core/update/types.d.ts +11 -0
  219. package/dist/core/update/types.js +1 -0
  220. package/dist/core/update/update-checker.d.ts +11 -0
  221. package/dist/core/update/update-checker.js +63 -0
  222. package/dist/core/update/update-manager.d.ts +25 -0
  223. package/dist/core/update/update-manager.js +101 -0
  224. package/dist/core/update/version-utils.d.ts +6 -0
  225. package/dist/core/update/version-utils.js +34 -0
  226. package/dist/gmail-connector-2FVYTQJH.js +6 -0
  227. package/dist/gmail-connector-MNUBRNFM.js +6 -0
  228. package/dist/gmail-connector-PS2VLGNE.js +6 -0
  229. package/dist/gmail-connector-ULSMN6X2.js +6 -0
  230. package/dist/gmail-connector-URRFX6A3.js +6 -0
  231. package/dist/inbound-AFBUPSPG.js +10 -0
  232. package/dist/inbound-AFOHYNUY.js +6 -0
  233. package/dist/inbound-CGIXRXGC.js +8 -0
  234. package/dist/inbound-MCOLRH6U.js +10 -0
  235. package/dist/inbound-SNEMBLGA.js +6 -0
  236. package/dist/inbound-ZJHAYVMF.js +10 -0
  237. package/dist/index.d.ts +11 -1
  238. package/dist/index.js +27 -11
  239. package/dist/provider-qqJYv9nv.d.ts +75 -0
  240. package/dist/providers/anthropic/models.d.ts +2 -0
  241. package/dist/providers/anthropic/models.js +29 -0
  242. package/dist/providers/anthropic/provider.d.ts +13 -0
  243. package/dist/providers/anthropic/provider.js +119 -0
  244. package/dist/providers/anthropic/tool-converter.d.ts +10 -0
  245. package/dist/providers/anthropic/tool-converter.js +7 -0
  246. package/dist/providers/ollama/provider.d.ts +17 -0
  247. package/dist/providers/ollama/provider.js +185 -0
  248. package/dist/providers/openai/models.d.ts +2 -0
  249. package/dist/providers/openai/models.js +29 -0
  250. package/dist/providers/openai/provider.d.ts +13 -0
  251. package/dist/providers/openai/provider.js +163 -0
  252. package/dist/providers/openai/tool-converter.d.ts +10 -0
  253. package/dist/providers/openai/tool-converter.js +10 -0
  254. package/dist/shared/constants.d.ts +50 -0
  255. package/dist/shared/constants.js +64 -0
  256. package/dist/shared/index.d.ts +14 -0
  257. package/dist/shared/index.js +14 -0
  258. package/dist/shared/types/agent.d.ts +36 -0
  259. package/dist/shared/types/agent.js +2 -0
  260. package/dist/shared/types/channel.d.ts +70 -0
  261. package/dist/shared/types/channel.js +2 -0
  262. package/dist/shared/types/config.d.ts +111 -0
  263. package/dist/shared/types/config.js +2 -0
  264. package/dist/shared/types/connector.d.ts +77 -0
  265. package/dist/shared/types/connector.js +2 -0
  266. package/dist/shared/types/execution.d.ts +29 -0
  267. package/dist/shared/types/execution.js +2 -0
  268. package/dist/shared/types/provider.d.ts +73 -0
  269. package/dist/shared/types/provider.js +2 -0
  270. package/dist/shared/types/task.d.ts +47 -0
  271. package/dist/shared/types/task.js +2 -0
  272. package/dist/shared/types/workflow.d.ts +39 -0
  273. package/dist/shared/types/workflow.js +2 -0
  274. package/dist/shared/utils.d.ts +6 -0
  275. package/dist/shared/utils.js +13 -0
  276. package/dist/update-check.d.ts +5 -0
  277. package/dist/update-check.js +56 -0
  278. package/package.json +100 -100
@@ -0,0 +1,204 @@
1
+ import { detectCycle } from './dependency-resolver.js';
2
+ import { interpolate } from './template-interpolate.js';
3
+ export class WorkflowEngine {
4
+ db;
5
+ hooks;
6
+ taskQueue;
7
+ constructor(db, hooks, taskQueue) {
8
+ this.db = db;
9
+ this.hooks = hooks;
10
+ this.taskQueue = taskQueue;
11
+ // Subscribe to task.completed to advance workflow steps
12
+ this.hooks.register('task.completed', async (ctx) => {
13
+ const taskId = ctx['taskId'];
14
+ const output = ctx['output'] ?? '';
15
+ if (taskId) {
16
+ await this.onStepCompleted(taskId, output);
17
+ }
18
+ });
19
+ }
20
+ /**
21
+ * Define/register a workflow.
22
+ */
23
+ async define(slug, def) {
24
+ // Validate: no duplicate step IDs
25
+ const ids = def.steps.map((s) => s.id);
26
+ const unique = new Set(ids);
27
+ if (unique.size !== ids.length) {
28
+ throw new Error('Workflow has duplicate step IDs');
29
+ }
30
+ // Validate: no invalid dependsOn references
31
+ for (const step of def.steps) {
32
+ for (const dep of step.dependsOn ?? []) {
33
+ if (!unique.has(dep)) {
34
+ throw new Error(`Step "${step.id}" depends on unknown step "${dep}"`);
35
+ }
36
+ }
37
+ }
38
+ // Validate: no cycles
39
+ if (detectCycle(def.steps)) {
40
+ throw new Error('Workflow has cyclic step dependencies');
41
+ }
42
+ // Store in workflows table
43
+ const existing = await this.db.query('workflows', { where: { slug } });
44
+ if (existing.length > 0) {
45
+ await this.db.update('workflows', { slug }, {
46
+ name: def.name,
47
+ description: def.description,
48
+ definition: JSON.stringify(def),
49
+ updated_at: new Date().toISOString(),
50
+ });
51
+ }
52
+ else {
53
+ await this.db.insert('workflows', {
54
+ slug,
55
+ name: def.name,
56
+ description: def.description,
57
+ definition: JSON.stringify(def),
58
+ });
59
+ }
60
+ }
61
+ /**
62
+ * Start a workflow run.
63
+ */
64
+ async start(workflowSlug, context) {
65
+ const workflows = await this.db.query('workflows', { where: { slug: workflowSlug } });
66
+ if (workflows.length === 0) {
67
+ throw new Error(`Workflow not found: ${workflowSlug}`);
68
+ }
69
+ const workflow = workflows[0];
70
+ const def = JSON.parse(workflow['definition']);
71
+ // Create workflow_run record
72
+ const runRow = await this.db.insert('workflow_runs', {
73
+ workflow_id: workflow['id'],
74
+ status: 'running',
75
+ step_results: JSON.stringify({}),
76
+ context: JSON.stringify(context),
77
+ });
78
+ const workflowRunId = runRow['id'];
79
+ // Find initial steps (no dependsOn or empty dependsOn)
80
+ const initialSteps = def.steps.filter((s) => !s.dependsOn || s.dependsOn.length === 0);
81
+ for (const step of initialSteps) {
82
+ await this._createStepTask(step, workflowRunId, workflow['id'], context);
83
+ }
84
+ return workflowRunId;
85
+ }
86
+ /**
87
+ * Called when a task with workflow_run_id completes.
88
+ */
89
+ async onStepCompleted(taskId, output) {
90
+ const task = await this.db.get('tasks', { id: taskId });
91
+ if (!task || !task['workflow_run_id'])
92
+ return;
93
+ const workflowRunId = task['workflow_run_id'];
94
+ const stepId = task['workflow_step_id'];
95
+ const run = await this.db.get('workflow_runs', { id: workflowRunId });
96
+ if (!run || run['status'] !== 'running')
97
+ return;
98
+ // Update step_results
99
+ const stepResults = JSON.parse(run['step_results'] ?? '{}');
100
+ if (stepId) {
101
+ stepResults[stepId] = { output, taskId };
102
+ }
103
+ await this.db.update('workflow_runs', { id: workflowRunId }, {
104
+ step_results: JSON.stringify(stepResults),
105
+ current_step: stepId,
106
+ });
107
+ // Find workflow definition
108
+ const workflow = await this.db.get('workflows', { id: run['workflow_id'] });
109
+ if (!workflow)
110
+ return;
111
+ const def = JSON.parse(workflow['definition']);
112
+ // Build context with step outputs
113
+ const runContext = JSON.parse(run['context'] ?? '{}');
114
+ const stepsContext = {};
115
+ for (const [sid, result] of Object.entries(stepResults)) {
116
+ stepsContext[sid] = result;
117
+ }
118
+ const fullContext = { ...runContext, steps: stepsContext };
119
+ // Find completed step IDs (all tasks for this run that succeeded)
120
+ const allRunTasks = await this.db.query('tasks', { where: { workflow_run_id: workflowRunId } });
121
+ const completedStepIds = new Set(allRunTasks
122
+ .filter((t) => t['status'] === 'done' || t['id'] === taskId)
123
+ .map((t) => t['workflow_step_id'])
124
+ .filter(Boolean));
125
+ // Mark current task's step as complete
126
+ if (stepId)
127
+ completedStepIds.add(stepId);
128
+ // Find next steps whose all dependsOn are satisfied and haven't been started
129
+ const startedStepIds = new Set(allRunTasks.map((t) => t['workflow_step_id']).filter(Boolean));
130
+ const nextSteps = def.steps.filter((s) => {
131
+ if (startedStepIds.has(s.id) && s.id !== stepId)
132
+ return false;
133
+ if (s.id === stepId)
134
+ return false; // Current step
135
+ if (!s.dependsOn || s.dependsOn.length === 0)
136
+ return false; // Already started as initial
137
+ return s.dependsOn.every((dep) => completedStepIds.has(dep));
138
+ });
139
+ if (nextSteps.length === 0) {
140
+ // Check if all steps are done
141
+ const allStepIds = new Set(def.steps.map((s) => s.id));
142
+ const allDone = [...allStepIds].every((id) => completedStepIds.has(id));
143
+ if (allDone) {
144
+ await this.db.update('workflow_runs', { id: workflowRunId }, {
145
+ status: 'completed',
146
+ completed_at: new Date().toISOString(),
147
+ });
148
+ await this.hooks.emit('workflow.completed', { workflowRunId });
149
+ }
150
+ return;
151
+ }
152
+ for (const step of nextSteps) {
153
+ await this._createStepTask(step, workflowRunId, workflow['id'], fullContext);
154
+ }
155
+ }
156
+ /**
157
+ * Mark a workflow run as failed.
158
+ */
159
+ async onStepFailed(taskId, error) {
160
+ const task = await this.db.get('tasks', { id: taskId });
161
+ if (!task || !task['workflow_run_id'])
162
+ return;
163
+ const workflowRunId = task['workflow_run_id'];
164
+ const stepId = task['workflow_step_id'];
165
+ const run = await this.db.get('workflow_runs', { id: workflowRunId });
166
+ if (!run || run['status'] !== 'running')
167
+ return;
168
+ // Find workflow definition to check onFail behavior
169
+ const workflow = await this.db.get('workflows', { id: run['workflow_id'] });
170
+ if (!workflow)
171
+ return;
172
+ const def = JSON.parse(workflow['definition']);
173
+ const step = stepId ? def.steps.find((s) => s.id === stepId) : undefined;
174
+ if (!step || step.onFail === 'abort' || !step.onFail) {
175
+ await this.db.update('workflow_runs', { id: workflowRunId }, {
176
+ status: 'failed',
177
+ error,
178
+ completed_at: new Date().toISOString(),
179
+ });
180
+ await this.hooks.emit('workflow.failed', { workflowRunId, error });
181
+ }
182
+ // skip/retry handled by retry policy in run-manager
183
+ }
184
+ async _createStepTask(step, workflowRunId, workflowId, context) {
185
+ // Find agent id by slug if agentSlug provided
186
+ let assigneeId;
187
+ if (step.agentSlug) {
188
+ const agents = await this.db.query('agents', { where: { slug: step.agentSlug } });
189
+ if (agents.length > 0) {
190
+ assigneeId = agents[0]['id'];
191
+ }
192
+ }
193
+ const title = interpolate(step.taskTemplate.title, context);
194
+ const description = interpolate(step.taskTemplate.description, context);
195
+ const taskId = await this.taskQueue.create({
196
+ title,
197
+ description,
198
+ assignee_id: assigneeId,
199
+ workflow_run_id: workflowRunId,
200
+ workflow_step_id: step.id,
201
+ });
202
+ return taskId;
203
+ }
204
+ }
@@ -0,0 +1,20 @@
1
+ import type { HookBus } from '../hooks/hook-bus.js';
2
+ import type { AuditEvent } from './types.js';
3
+ interface AuditEmitterOptions {
4
+ auditTables?: string[];
5
+ }
6
+ /**
7
+ * Emits audit events via the HookBus for tracked tables.
8
+ *
9
+ * - auditTables defaults to [] (nothing audited)
10
+ * - '*' wildcard audits all tables
11
+ * - emit is fire-and-forget — errors are swallowed, no awaiting
12
+ */
13
+ export declare class AuditEmitter {
14
+ private readonly hooks;
15
+ private readonly auditTables;
16
+ constructor(hooks: HookBus, opts?: AuditEmitterOptions);
17
+ shouldAudit(table: string): boolean;
18
+ emit(event: AuditEvent): void;
19
+ }
20
+ export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Emits audit events via the HookBus for tracked tables.
3
+ *
4
+ * - auditTables defaults to [] (nothing audited)
5
+ * - '*' wildcard audits all tables
6
+ * - emit is fire-and-forget — errors are swallowed, no awaiting
7
+ */
8
+ export class AuditEmitter {
9
+ hooks;
10
+ auditTables;
11
+ constructor(hooks, opts) {
12
+ this.hooks = hooks;
13
+ this.auditTables = opts?.auditTables ?? [];
14
+ }
15
+ shouldAudit(table) {
16
+ return this.auditTables.includes('*') || this.auditTables.includes(table);
17
+ }
18
+ emit(event) {
19
+ // Fire and forget — never throw, never await
20
+ try {
21
+ const context = event;
22
+ if (typeof this.hooks.emitSync === 'function') {
23
+ this.hooks.emitSync('audit', context);
24
+ }
25
+ else {
26
+ void this.hooks.emit('audit', context);
27
+ }
28
+ }
29
+ catch {
30
+ // Swallow errors — audit must never break normal operation
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,20 @@
1
+ import type { DataStore } from '../data/data-store.js';
2
+ export interface ColumnValidator {
3
+ validateWrite(table: string, row: Record<string, unknown>): Record<string, unknown>;
4
+ validateRead(table: string, columns: string[]): void;
5
+ invalidateCache(table: string): void;
6
+ }
7
+ /**
8
+ * Validates column names against the live SQLite schema.
9
+ *
10
+ * - validateWrite: strips unknown columns silently
11
+ * - validateRead: throws on unknown columns
12
+ */
13
+ export declare class ColumnValidatorImpl implements ColumnValidator {
14
+ private readonly db;
15
+ constructor(db: DataStore);
16
+ private getValidColumns;
17
+ validateWrite(table: string, row: Record<string, unknown>): Record<string, unknown>;
18
+ validateRead(table: string, columns: string[]): void;
19
+ invalidateCache(_table: string): void;
20
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Validates column names against the live SQLite schema.
3
+ *
4
+ * - validateWrite: strips unknown columns silently
5
+ * - validateRead: throws on unknown columns
6
+ */
7
+ export class ColumnValidatorImpl {
8
+ db;
9
+ constructor(db) {
10
+ this.db = db;
11
+ }
12
+ getValidColumns(table) {
13
+ const rows = this.db.tableInfo(table);
14
+ return new Set(rows.map(r => r.name));
15
+ }
16
+ validateWrite(table, row) {
17
+ const valid = this.getValidColumns(table);
18
+ const result = {};
19
+ for (const [col, val] of Object.entries(row)) {
20
+ if (valid.has(col)) {
21
+ result[col] = val;
22
+ }
23
+ }
24
+ return result;
25
+ }
26
+ validateRead(table, columns) {
27
+ const valid = this.getValidColumns(table);
28
+ for (const col of columns) {
29
+ if (!valid.has(col)) {
30
+ throw new Error(`Unknown column: ${col} in table ${table}`);
31
+ }
32
+ }
33
+ }
34
+ invalidateCache(_table) {
35
+ // No-op: DataStore.tableInfo() queries SQLite directly each time (no cache)
36
+ }
37
+ }
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export * from './sanitizer.js';
3
+ export * from './column-validator.js';
4
+ export * from './audit.js';
5
+ export * from './process-env.js';
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export * from './sanitizer.js';
3
+ export * from './column-validator.js';
4
+ export * from './audit.js';
5
+ export * from './process-env.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Build a clean environment for spawned subprocesses.
3
+ * Strips secrets and passes only safe system variables.
4
+ * Used by the CLI execution adapter when spawning agent processes.
5
+ */
6
+ /**
7
+ * Build a filtered environment for subprocess execution.
8
+ * Only passes explicitly allowed variables — all secrets are stripped.
9
+ *
10
+ * @param allowedKeys - Additional keys to allow beyond the defaults
11
+ * @param inject - Extra key-value pairs to inject into the env
12
+ */
13
+ export declare function buildProcessEnv(allowedKeys?: string[], inject?: Record<string, string>): Record<string, string>;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Build a clean environment for spawned subprocesses.
3
+ * Strips secrets and passes only safe system variables.
4
+ * Used by the CLI execution adapter when spawning agent processes.
5
+ */
6
+ const DEFAULT_ALLOWED_KEYS = new Set([
7
+ "PATH",
8
+ "HOME",
9
+ "USER",
10
+ "SHELL",
11
+ "LANG",
12
+ "TERM",
13
+ "TMPDIR",
14
+ "XDG_RUNTIME_DIR",
15
+ "NODE_ENV",
16
+ // Git
17
+ "GIT_AUTHOR_NAME",
18
+ "GIT_AUTHOR_EMAIL",
19
+ "GIT_COMMITTER_NAME",
20
+ "GIT_COMMITTER_EMAIL",
21
+ // Homebrew / system
22
+ "HOMEBREW_PREFIX",
23
+ "HOMEBREW_CELLAR",
24
+ "HOMEBREW_REPOSITORY",
25
+ ]);
26
+ /**
27
+ * Build a filtered environment for subprocess execution.
28
+ * Only passes explicitly allowed variables — all secrets are stripped.
29
+ *
30
+ * @param allowedKeys - Additional keys to allow beyond the defaults
31
+ * @param inject - Extra key-value pairs to inject into the env
32
+ */
33
+ export function buildProcessEnv(allowedKeys, inject) {
34
+ const allowed = new Set(DEFAULT_ALLOWED_KEYS);
35
+ if (allowedKeys) {
36
+ for (const k of allowedKeys)
37
+ allowed.add(k);
38
+ }
39
+ const env = {};
40
+ for (const [key, value] of Object.entries(process.env)) {
41
+ if (allowed.has(key) && value !== undefined) {
42
+ env[key] = value;
43
+ }
44
+ }
45
+ if (inject) {
46
+ Object.assign(env, inject);
47
+ }
48
+ return env;
49
+ }
@@ -0,0 +1,11 @@
1
+ import type { SanitizerOptions } from './types.js';
2
+ /**
3
+ * Sanitizes a row object by:
4
+ * 1. Stripping null bytes from string values
5
+ * 2. Stripping control characters (preserving \n, \r, \t)
6
+ * 3. Truncating fields that exceed their byte length limit
7
+ *
8
+ * Non-string values pass through unchanged.
9
+ * Returns a new object — input is never mutated.
10
+ */
11
+ export declare function sanitize(row: Record<string, unknown>, opts?: SanitizerOptions): Record<string, unknown>;
@@ -0,0 +1,39 @@
1
+ import { Buffer } from 'node:buffer';
2
+ const DEFAULT_FIELD_LIMIT = 65535;
3
+ const DEFAULT_SUFFIX = '[truncated]';
4
+ /**
5
+ * Sanitizes a row object by:
6
+ * 1. Stripping null bytes from string values
7
+ * 2. Stripping control characters (preserving \n, \r, \t)
8
+ * 3. Truncating fields that exceed their byte length limit
9
+ *
10
+ * Non-string values pass through unchanged.
11
+ * Returns a new object — input is never mutated.
12
+ */
13
+ export function sanitize(row, opts) {
14
+ const limits = opts?.fieldLengthLimits ?? {};
15
+ const suffix = opts?.truncateSuffix ?? DEFAULT_SUFFIX;
16
+ const result = {};
17
+ for (const [col, val] of Object.entries(row)) {
18
+ if (typeof val !== 'string') {
19
+ result[col] = val;
20
+ continue;
21
+ }
22
+ // 1. Strip null bytes
23
+ let s = val.replace(/\x00/g, '');
24
+ // 2. Strip control chars, preserving \n (0x0a), \r (0x0d), \t (0x09)
25
+ s = s.replace(/[\x01-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
26
+ // 3. Enforce field length limit
27
+ const limit = limits[col] ?? DEFAULT_FIELD_LIMIT;
28
+ if (Buffer.byteLength(s) > limit) {
29
+ // Truncate to fit within limit bytes (accounting for suffix)
30
+ const suffixBytes = Buffer.byteLength(suffix);
31
+ const maxContentBytes = limit - suffixBytes;
32
+ // Slice the string to fit within maxContentBytes
33
+ const buf = Buffer.from(s);
34
+ s = buf.slice(0, maxContentBytes).toString() + suffix;
35
+ }
36
+ result[col] = s;
37
+ }
38
+ return result;
39
+ }
@@ -0,0 +1,11 @@
1
+ export interface SanitizerOptions {
2
+ fieldLengthLimits?: Record<string, number>;
3
+ truncateSuffix?: string;
4
+ }
5
+ export interface AuditEvent {
6
+ table: string;
7
+ operation: 'insert' | 'update' | 'delete';
8
+ pk: unknown;
9
+ timestamp: number;
10
+ changedColumns?: string[];
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ interface UpdateResult {
2
+ updated: boolean;
3
+ packages: Array<{
4
+ name: string;
5
+ from: string;
6
+ to: string;
7
+ }>;
8
+ restartRequired: boolean;
9
+ }
10
+ /**
11
+ * Check npm for newer versions of framework packages and install them.
12
+ * Returns what was updated. Safe to call on every startup — skips if
13
+ * already on latest.
14
+ *
15
+ * @param packages - Package names to check (default: botinabox + latticesql)
16
+ * @param opts.quiet - Suppress console output (default: false)
17
+ */
18
+ export declare function autoUpdate(packages?: string[], opts?: {
19
+ quiet?: boolean;
20
+ }): Promise<UpdateResult>;
21
+ export {};
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Auto-update — checks npm for newer versions of botinabox and its
3
+ * dependencies, installs them if outdated. Call at app startup before
4
+ * initializing the framework.
5
+ *
6
+ * Usage:
7
+ * import { autoUpdate } from 'botinabox';
8
+ * await autoUpdate(); // checks + installs if needed
9
+ */
10
+ import { execFileSync } from "node:child_process";
11
+ import { readFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ /** Strict semver pattern — rejects anything that isn't a clean version string */
14
+ const SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
15
+ /**
16
+ * Get the installed version of a package from node_modules.
17
+ */
18
+ function getInstalledVersion(pkgName) {
19
+ try {
20
+ const pkgPath = join(process.cwd(), "node_modules", pkgName, "package.json");
21
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
22
+ return pkg.version;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ /**
29
+ * Get the latest version from the npm registry.
30
+ */
31
+ async function getLatestVersion(pkgName) {
32
+ try {
33
+ const res = await fetch(`https://registry.npmjs.org/${pkgName}/latest`, {
34
+ headers: { accept: "application/json" },
35
+ signal: AbortSignal.timeout(5000),
36
+ });
37
+ if (!res.ok)
38
+ return null;
39
+ const data = (await res.json());
40
+ return data.version;
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ function isNewer(latest, current) {
47
+ const a = latest.split(".").map(Number);
48
+ const b = current.split(".").map(Number);
49
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
50
+ if ((a[i] ?? 0) > (b[i] ?? 0))
51
+ return true;
52
+ if ((a[i] ?? 0) < (b[i] ?? 0))
53
+ return false;
54
+ }
55
+ return false;
56
+ }
57
+ /**
58
+ * Check npm for newer versions of framework packages and install them.
59
+ * Returns what was updated. Safe to call on every startup — skips if
60
+ * already on latest.
61
+ *
62
+ * @param packages - Package names to check (default: botinabox + latticesql)
63
+ * @param opts.quiet - Suppress console output (default: false)
64
+ */
65
+ export async function autoUpdate(packages = ["botinabox", "latticesql"], opts) {
66
+ const log = opts?.quiet ? () => { } : console.log;
67
+ const result = { updated: false, packages: [], restartRequired: false };
68
+ const toInstall = [];
69
+ for (const pkg of packages) {
70
+ const installed = getInstalledVersion(pkg);
71
+ if (!installed)
72
+ continue;
73
+ const latest = await getLatestVersion(pkg);
74
+ if (!latest)
75
+ continue;
76
+ if (isNewer(latest, installed)) {
77
+ if (!SEMVER_RE.test(latest)) {
78
+ console.error(`[autoUpdate] Rejecting invalid version "${latest}" for ${pkg}`);
79
+ continue;
80
+ }
81
+ toInstall.push(`${pkg}@${latest}`);
82
+ result.packages.push({ name: pkg, from: installed, to: latest });
83
+ }
84
+ }
85
+ if (toInstall.length === 0)
86
+ return result;
87
+ log(`[autoUpdate] Updating: ${toInstall.join(", ")}`);
88
+ try {
89
+ execFileSync("npm", ["install", ...toInstall], {
90
+ cwd: process.cwd(),
91
+ stdio: opts?.quiet ? "ignore" : "inherit",
92
+ timeout: 60_000,
93
+ });
94
+ result.updated = true;
95
+ result.restartRequired = true;
96
+ log(`[autoUpdate] Updated successfully. Restart required for changes to take effect.`);
97
+ }
98
+ catch (err) {
99
+ console.error("[autoUpdate] Failed to install updates:", err);
100
+ }
101
+ return result;
102
+ }
@@ -0,0 +1,7 @@
1
+ export declare class BackupManager {
2
+ private projectRoot;
3
+ constructor(projectRoot: string);
4
+ backup(): Promise<string>;
5
+ restore(backupPath: string): Promise<void>;
6
+ cleanup(backupPath: string): Promise<void>;
7
+ }
@@ -0,0 +1,24 @@
1
+ import { copyFileSync, mkdirSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { rmSync } from 'fs';
4
+ export class BackupManager {
5
+ projectRoot;
6
+ constructor(projectRoot) {
7
+ this.projectRoot = projectRoot;
8
+ }
9
+ async backup() {
10
+ const backupDir = join(this.projectRoot, '.botinabox-backup');
11
+ mkdirSync(backupDir, { recursive: true });
12
+ const src = join(this.projectRoot, 'pnpm-lock.yaml');
13
+ const dest = join(backupDir, 'pnpm-lock.yaml.bak');
14
+ copyFileSync(src, dest);
15
+ return dest;
16
+ }
17
+ async restore(backupPath) {
18
+ const dest = join(this.projectRoot, 'pnpm-lock.yaml');
19
+ copyFileSync(backupPath, dest);
20
+ }
21
+ async cleanup(backupPath) {
22
+ rmSync(backupPath, { force: true });
23
+ }
24
+ }
@@ -0,0 +1,8 @@
1
+ export type { PackageUpdate, UpdateManifest } from './types.js';
2
+ export { parseVersion, compareVersions, classifyUpdate } from './version-utils.js';
3
+ export { UpdateChecker } from './update-checker.js';
4
+ export { BackupManager } from './backup-manager.js';
5
+ export { UpdateManager } from './update-manager.js';
6
+ export { runPackageMigrations } from './migration-hooks.js';
7
+ export type { PackageMigration } from './migration-hooks.js';
8
+ export { autoUpdate } from './auto-update.js';
@@ -0,0 +1,6 @@
1
+ export { parseVersion, compareVersions, classifyUpdate } from './version-utils.js';
2
+ export { UpdateChecker } from './update-checker.js';
3
+ export { BackupManager } from './backup-manager.js';
4
+ export { UpdateManager } from './update-manager.js';
5
+ export { runPackageMigrations } from './migration-hooks.js';
6
+ export { autoUpdate } from './auto-update.js';
@@ -0,0 +1,11 @@
1
+ import type { DataStore } from '../data/data-store.js';
2
+ export interface PackageMigration {
3
+ version: string;
4
+ package: string;
5
+ sql: string;
6
+ }
7
+ /**
8
+ * Runs package migrations using the __lattice_migrations table for tracking.
9
+ * Each migration is keyed by "{package}:{version}" to ensure idempotency.
10
+ */
11
+ export declare function runPackageMigrations(db: DataStore, migrations: PackageMigration[]): Promise<void>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Runs package migrations using the __lattice_migrations table for tracking.
3
+ * Each migration is keyed by "{package}:{version}" to ensure idempotency.
4
+ */
5
+ export async function runPackageMigrations(db, migrations) {
6
+ await db.migrate(migrations.map((m) => ({
7
+ version: `${m.package}:${m.version}`,
8
+ sql: m.sql,
9
+ })));
10
+ }