@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,40 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const cronTrigger: NodeDefinition = {
4
+ type: 'trigger.cron',
5
+ label: 'Cron Trigger',
6
+ description: 'Fire a workflow on a cron schedule.',
7
+ category: 'trigger',
8
+ icon: '⏰',
9
+ color: '#8b5cf6',
10
+ configSchema: {
11
+ expression: {
12
+ type: 'string',
13
+ label: 'Cron Expression',
14
+ description: 'Standard cron expression, e.g. "0 9 * * 1-5"',
15
+ required: true,
16
+ placeholder: '0 9 * * *',
17
+ },
18
+ timezone: {
19
+ type: 'string',
20
+ label: 'Timezone',
21
+ description: 'IANA timezone name, e.g. "America/New_York". Defaults to UTC.',
22
+ required: false,
23
+ placeholder: 'UTC',
24
+ },
25
+ },
26
+ inputs: [],
27
+ outputs: ['default'],
28
+ execute: async (input, config, ctx) => {
29
+ ctx.logger.info(`Cron trigger fired — expression: ${config.expression}`);
30
+ return {
31
+ data: {
32
+ triggerType: 'cron',
33
+ expression: config.expression,
34
+ timezone: config.timezone ?? 'UTC',
35
+ firedAt: Date.now(),
36
+ ...input.data,
37
+ },
38
+ };
39
+ },
40
+ };
@@ -0,0 +1,40 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const emailTrigger: NodeDefinition = {
4
+ type: 'trigger.email',
5
+ label: 'Email Trigger',
6
+ description: 'Fire when a new email is received matching optional from/subject filters.',
7
+ category: 'trigger',
8
+ icon: '📧',
9
+ color: '#8b5cf6',
10
+ configSchema: {
11
+ from_filter: {
12
+ type: 'string',
13
+ label: 'From Filter',
14
+ description: 'Regex or plain-text pattern to match the sender address.',
15
+ required: false,
16
+ placeholder: 'alerts@example.com',
17
+ },
18
+ subject_filter: {
19
+ type: 'string',
20
+ label: 'Subject Filter',
21
+ description: 'Regex or plain-text pattern to match the email subject.',
22
+ required: false,
23
+ placeholder: 'URGENT:',
24
+ },
25
+ },
26
+ inputs: [],
27
+ outputs: ['default'],
28
+ execute: async (input, config, ctx) => {
29
+ ctx.logger.info('Email trigger fired');
30
+ return {
31
+ data: {
32
+ triggerType: 'email',
33
+ from_filter: config.from_filter ?? null,
34
+ subject_filter: config.subject_filter ?? null,
35
+ receivedAt: Date.now(),
36
+ ...input.data,
37
+ },
38
+ };
39
+ },
40
+ };
@@ -0,0 +1,45 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const fileChangeTrigger: NodeDefinition = {
4
+ type: 'trigger.file_change',
5
+ label: 'File Change Trigger',
6
+ description: 'Fire when a file or directory changes on disk.',
7
+ category: 'trigger',
8
+ icon: '📁',
9
+ color: '#8b5cf6',
10
+ configSchema: {
11
+ watch_path: {
12
+ type: 'string',
13
+ label: 'Watch Path',
14
+ description: 'Absolute or relative path to watch. Supports glob patterns.',
15
+ required: true,
16
+ placeholder: '/home/user/documents/**',
17
+ },
18
+ events: {
19
+ type: 'select',
20
+ label: 'Events',
21
+ description: 'Which file system events to listen for.',
22
+ required: true,
23
+ default: 'modified',
24
+ options: [
25
+ { label: 'Created', value: 'created' },
26
+ { label: 'Modified', value: 'modified' },
27
+ { label: 'Deleted', value: 'deleted' },
28
+ ],
29
+ },
30
+ },
31
+ inputs: [],
32
+ outputs: ['default'],
33
+ execute: async (input, config, ctx) => {
34
+ ctx.logger.info(`File change trigger fired — path: ${config.watch_path}, event: ${config.events}`);
35
+ return {
36
+ data: {
37
+ triggerType: 'file_change',
38
+ watch_path: config.watch_path,
39
+ event: config.events,
40
+ firedAt: Date.now(),
41
+ ...input.data,
42
+ },
43
+ };
44
+ },
45
+ };
@@ -0,0 +1,46 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const gitTrigger: NodeDefinition = {
4
+ type: 'trigger.git',
5
+ label: 'Git Trigger',
6
+ description: 'Fire when a git event occurs in a local repository.',
7
+ category: 'trigger',
8
+ icon: '🌿',
9
+ color: '#8b5cf6',
10
+ configSchema: {
11
+ repo_path: {
12
+ type: 'string',
13
+ label: 'Repository Path',
14
+ description: 'Absolute path to the local git repository.',
15
+ required: true,
16
+ placeholder: '/home/user/my-project',
17
+ },
18
+ events: {
19
+ type: 'select',
20
+ label: 'Git Event',
21
+ description: 'Which git event to listen for.',
22
+ required: true,
23
+ default: 'commit',
24
+ options: [
25
+ { label: 'Push', value: 'push' },
26
+ { label: 'Pull', value: 'pull' },
27
+ { label: 'Commit', value: 'commit' },
28
+ { label: 'Branch', value: 'branch' },
29
+ ],
30
+ },
31
+ },
32
+ inputs: [],
33
+ outputs: ['default'],
34
+ execute: async (input, config, ctx) => {
35
+ ctx.logger.info(`Git trigger fired — repo: ${config.repo_path}, event: ${config.events}`);
36
+ return {
37
+ data: {
38
+ triggerType: 'git',
39
+ repo_path: config.repo_path,
40
+ event: config.events,
41
+ firedAt: Date.now(),
42
+ ...input.data,
43
+ },
44
+ };
45
+ },
46
+ };
@@ -0,0 +1,23 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const manualTrigger: NodeDefinition = {
4
+ type: 'trigger.manual',
5
+ label: 'Manual Trigger',
6
+ description: 'Trigger the workflow manually via the dashboard or API.',
7
+ category: 'trigger',
8
+ icon: '▶️',
9
+ color: '#8b5cf6',
10
+ configSchema: {},
11
+ inputs: [],
12
+ outputs: ['default'],
13
+ execute: async (input, _config, ctx) => {
14
+ ctx.logger.info('Manual trigger fired');
15
+ return {
16
+ data: {
17
+ triggerType: 'manual',
18
+ firedAt: Date.now(),
19
+ ...input.data,
20
+ },
21
+ };
22
+ },
23
+ };
@@ -0,0 +1,81 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const pollTrigger: NodeDefinition = {
4
+ type: 'trigger.poll',
5
+ label: 'Poll Trigger',
6
+ description: 'Periodically poll a URL and fire when the response changes.',
7
+ category: 'trigger',
8
+ icon: '🔄',
9
+ color: '#8b5cf6',
10
+ configSchema: {
11
+ url: {
12
+ type: 'template',
13
+ label: 'URL',
14
+ description: 'URL to poll. Supports template expressions.',
15
+ required: true,
16
+ placeholder: 'https://api.example.com/status',
17
+ },
18
+ interval_ms: {
19
+ type: 'number',
20
+ label: 'Interval (ms)',
21
+ description: 'Polling interval in milliseconds.',
22
+ required: true,
23
+ default: 60000,
24
+ },
25
+ method: {
26
+ type: 'select',
27
+ label: 'HTTP Method',
28
+ description: 'HTTP method to use when polling.',
29
+ required: true,
30
+ default: 'GET',
31
+ options: [
32
+ { label: 'GET', value: 'GET' },
33
+ { label: 'POST', value: 'POST' },
34
+ ],
35
+ },
36
+ dedup_field: {
37
+ type: 'string',
38
+ label: 'Dedup Field',
39
+ description: 'Optional dot-path into the response body to use for deduplication.',
40
+ required: false,
41
+ placeholder: 'data.id',
42
+ },
43
+ },
44
+ inputs: [],
45
+ outputs: ['default'],
46
+ execute: async (input, config, ctx) => {
47
+ const url = String(config.url ?? '');
48
+ const method = String(config.method ?? 'GET');
49
+ ctx.logger.info(`Poll trigger fired — url: ${url}, method: ${method}`);
50
+
51
+ let responseData: unknown = null;
52
+ let responseStatus = 0;
53
+
54
+ try {
55
+ const response = await fetch(url, { method, signal: ctx.abortSignal });
56
+ responseStatus = response.status;
57
+ const text = await response.text();
58
+ try {
59
+ responseData = JSON.parse(text);
60
+ } catch {
61
+ responseData = text;
62
+ }
63
+ } catch (err) {
64
+ ctx.logger.warn(`Poll fetch failed: ${err instanceof Error ? err.message : String(err)}`);
65
+ }
66
+
67
+ return {
68
+ data: {
69
+ triggerType: 'poll',
70
+ url,
71
+ method,
72
+ interval_ms: config.interval_ms,
73
+ dedup_field: config.dedup_field ?? null,
74
+ status: responseStatus,
75
+ response: responseData,
76
+ firedAt: Date.now(),
77
+ ...input.data,
78
+ },
79
+ };
80
+ },
81
+ };
@@ -0,0 +1,44 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const processTrigger: NodeDefinition = {
4
+ type: 'trigger.process',
5
+ label: 'Process Trigger',
6
+ description: 'Fire when a system process starts or stops.',
7
+ category: 'trigger',
8
+ icon: '⚙️',
9
+ color: '#8b5cf6',
10
+ configSchema: {
11
+ process_name: {
12
+ type: 'string',
13
+ label: 'Process Name',
14
+ description: 'Name or partial name of the process to watch.',
15
+ required: true,
16
+ placeholder: 'chrome',
17
+ },
18
+ event: {
19
+ type: 'select',
20
+ label: 'Event',
21
+ description: 'Whether to fire when the process starts or stops.',
22
+ required: true,
23
+ default: 'started',
24
+ options: [
25
+ { label: 'Started', value: 'started' },
26
+ { label: 'Stopped', value: 'stopped' },
27
+ ],
28
+ },
29
+ },
30
+ inputs: [],
31
+ outputs: ['default'],
32
+ execute: async (input, config, ctx) => {
33
+ ctx.logger.info(`Process trigger fired — process: ${config.process_name}, event: ${config.event}`);
34
+ return {
35
+ data: {
36
+ triggerType: 'process',
37
+ process_name: config.process_name,
38
+ event: config.event,
39
+ firedAt: Date.now(),
40
+ ...input.data,
41
+ },
42
+ };
43
+ },
44
+ };
@@ -0,0 +1,37 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const screenEventTrigger: NodeDefinition = {
4
+ type: 'trigger.screen_event',
5
+ label: 'Screen Event Trigger',
6
+ description: 'Fire when the Continuous Awareness Engine detects a screen event.',
7
+ category: 'trigger',
8
+ icon: '🖥️',
9
+ color: '#8b5cf6',
10
+ configSchema: {
11
+ event_type: {
12
+ type: 'select',
13
+ label: 'Event Type',
14
+ description: 'The type of screen event to listen for.',
15
+ required: true,
16
+ default: 'error_detected',
17
+ options: [
18
+ { label: 'Error Detected', value: 'error_detected' },
19
+ { label: 'Struggle Detected', value: 'struggle_detected' },
20
+ { label: 'Context Changed', value: 'context_changed' },
21
+ ],
22
+ },
23
+ },
24
+ inputs: [],
25
+ outputs: ['default'],
26
+ execute: async (input, config, ctx) => {
27
+ ctx.logger.info(`Screen event trigger fired — type: ${config.event_type}`);
28
+ return {
29
+ data: {
30
+ triggerType: 'screen_event',
31
+ event_type: config.event_type,
32
+ firedAt: Date.now(),
33
+ ...input.data,
34
+ },
35
+ };
36
+ },
37
+ };
@@ -0,0 +1,39 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const webhookTrigger: NodeDefinition = {
4
+ type: 'trigger.webhook',
5
+ label: 'Webhook Trigger',
6
+ description: 'Receive an inbound HTTP request and fire the workflow.',
7
+ category: 'trigger',
8
+ icon: '🔗',
9
+ color: '#8b5cf6',
10
+ configSchema: {
11
+ path: {
12
+ type: 'string',
13
+ label: 'Path',
14
+ description: 'URL path this webhook listens on, e.g. "/webhooks/my-event"',
15
+ required: true,
16
+ placeholder: '/webhooks/my-event',
17
+ },
18
+ secret: {
19
+ type: 'string',
20
+ label: 'Secret',
21
+ description: 'Optional HMAC-SHA256 secret used to verify incoming payloads.',
22
+ required: false,
23
+ placeholder: 'my-secret-token',
24
+ },
25
+ },
26
+ inputs: [],
27
+ outputs: ['default'],
28
+ execute: async (input, config, ctx) => {
29
+ ctx.logger.info(`Webhook trigger fired — path: ${config.path}`);
30
+ return {
31
+ data: {
32
+ triggerType: 'webhook',
33
+ path: config.path,
34
+ receivedAt: Date.now(),
35
+ ...input.data,
36
+ },
37
+ };
38
+ },
39
+ };
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Safe Expression Evaluator — sandboxed execution for workflow nodes.
3
+ *
4
+ * Validates expressions against a blocklist of dangerous patterns,
5
+ * freezes scope objects, and enforces timeouts for code execution.
6
+ */
7
+
8
+ /** Patterns that indicate dangerous code — block these in expressions */
9
+ const BLOCKED_PATTERNS = [
10
+ /\brequire\s*\(/,
11
+ /\bimport\s*[\(\{]/,
12
+ /\bimport\b/,
13
+ /\bprocess\b/,
14
+ /\bBun\b/,
15
+ /\bDeno\b/,
16
+ /\bglobalThis\b/,
17
+ /\bglobal\b/,
18
+ /\beval\s*\(/,
19
+ /\bFunction\s*\(/,
20
+ /\bchild_process\b/,
21
+ /\b__dirname\b/,
22
+ /\b__filename\b/,
23
+ /\bsetTimeout\b/,
24
+ /\bsetInterval\b/,
25
+ /\bsetImmediate\b/,
26
+ /\bclearTimeout\b/,
27
+ /\bclearInterval\b/,
28
+ /\bfetch\s*\(/,
29
+ /\bXMLHttpRequest\b/,
30
+ /\bWebSocket\b/,
31
+ /\bnew\s+Worker\b/,
32
+ /\bfs\b\.\b/,
33
+ /\bexecSync\b/,
34
+ /\bspawnSync\b/,
35
+ /\bexec\s*\(/,
36
+ /\bspawn\s*\(/,
37
+ ];
38
+
39
+ /**
40
+ * Validate an expression or code string against the blocklist.
41
+ * Throws if a dangerous pattern is detected.
42
+ */
43
+ export function validateExpression(expr: string): void {
44
+ for (const pattern of BLOCKED_PATTERNS) {
45
+ if (pattern.test(expr)) {
46
+ throw new Error(
47
+ `Blocked: expression contains forbidden pattern "${pattern.source}". ` +
48
+ `Workflow expressions cannot access system APIs, imports, or process globals.`
49
+ );
50
+ }
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Safely evaluate a simple JavaScript expression.
56
+ * Used by if-else, switch, and map-filter nodes.
57
+ *
58
+ * The expression is validated against the blocklist, then executed
59
+ * in a Function with frozen scope objects.
60
+ */
61
+ export function safeEvaluateExpression(
62
+ expression: string,
63
+ scope: Record<string, unknown>,
64
+ ): unknown {
65
+ validateExpression(expression);
66
+
67
+ // Freeze scope to prevent mutation
68
+ const frozenScope: Record<string, unknown> = {};
69
+ for (const [key, value] of Object.entries(scope)) {
70
+ frozenScope[key] = value && typeof value === 'object' ? Object.freeze({ ...value as object }) : value;
71
+ }
72
+
73
+ const scopeKeys = Object.keys(frozenScope);
74
+ const scopeValues = Object.values(frozenScope);
75
+
76
+ // eslint-disable-next-line no-new-func
77
+ const fn = new Function(
78
+ ...scopeKeys,
79
+ `"use strict"; return (${expression});`,
80
+ );
81
+
82
+ return fn(...scopeValues);
83
+ }
84
+
85
+ /**
86
+ * Safely execute a code block with timeout.
87
+ * Used by the code-execution node.
88
+ *
89
+ * Validates against blocklist, shadows dangerous globals,
90
+ * freezes context, and enforces a timeout.
91
+ */
92
+ export async function safeExecuteCode(
93
+ code: string,
94
+ input: { data: Record<string, unknown>; variables: Record<string, unknown>; executionId: string },
95
+ safeCtx: Record<string, unknown>,
96
+ timeoutMs: number = 10_000,
97
+ ): Promise<unknown> {
98
+ validateExpression(code);
99
+
100
+ // Freeze context objects
101
+ const frozenInput = Object.freeze({
102
+ data: Object.freeze({ ...input.data }),
103
+ variables: Object.freeze({ ...input.variables }),
104
+ executionId: input.executionId,
105
+ });
106
+ const frozenCtx = Object.freeze({ ...safeCtx });
107
+
108
+ // Shadow dangerous globals by declaring them as undefined in the function scope
109
+ const wrappedCode = `
110
+ "use strict";
111
+ var process = undefined, Bun = undefined, Deno = undefined;
112
+ var require = undefined, globalThis = undefined, global = undefined;
113
+ var fetch = undefined, XMLHttpRequest = undefined, WebSocket = undefined;
114
+ var __dirname = undefined, __filename = undefined;
115
+ return (async function(input, ctx) {
116
+ ${code}
117
+ })(input, ctx);
118
+ `;
119
+
120
+ // eslint-disable-next-line no-new-func
121
+ const fn = new Function('input', 'ctx', wrappedCode);
122
+
123
+ // Execute with timeout
124
+ let timer: ReturnType<typeof setTimeout> | undefined;
125
+ try {
126
+ const result = await Promise.race([
127
+ fn(frozenInput, frozenCtx),
128
+ new Promise<never>((_, reject) => {
129
+ timer = setTimeout(
130
+ () => reject(new Error(`Code execution timed out after ${timeoutMs}ms`)),
131
+ timeoutMs,
132
+ );
133
+ }),
134
+ ]);
135
+ return result;
136
+ } finally {
137
+ clearTimeout(timer);
138
+ }
139
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Template Expression Engine
3
+ *
4
+ * Resolves {{...}} expressions in workflow node configs.
5
+ * Supported patterns:
6
+ * {{variable.path}} — access execution/persistent variables
7
+ * {{$trigger.field}} — trigger data
8
+ * {{$node["nodeName"].data.field}} — output from a previous node
9
+ * {{$env.VAR_NAME}} — environment variable
10
+ */
11
+
12
+ export type TemplateContext = {
13
+ variables: Record<string, unknown>;
14
+ nodeOutputs: Map<string, Record<string, unknown>>;
15
+ triggerData: Record<string, unknown>;
16
+ env: Record<string, string>;
17
+ };
18
+
19
+ const EXPR_RE = /\{\{(.+?)\}\}/g;
20
+
21
+ /**
22
+ * Resolve a single expression (the content between {{ and }})
23
+ */
24
+ export function resolveExpression(expr: string, ctx: TemplateContext): unknown {
25
+ const trimmed = expr.trim();
26
+
27
+ // $node["nodeName"].data.field
28
+ const nodeMatch = trimmed.match(/^\$node\["([^"]+)"\]\.(.+)$/);
29
+ if (nodeMatch) {
30
+ const [, nodeName, path] = nodeMatch;
31
+ const nodeData = ctx.nodeOutputs.get(nodeName!);
32
+ if (!nodeData) return undefined;
33
+ return resolvePath(nodeData, path!);
34
+ }
35
+
36
+ // $trigger.field
37
+ if (trimmed.startsWith('$trigger.')) {
38
+ const path = trimmed.slice('$trigger.'.length);
39
+ return resolvePath(ctx.triggerData, path);
40
+ }
41
+
42
+ // $env.VAR
43
+ if (trimmed.startsWith('$env.')) {
44
+ const key = trimmed.slice('$env.'.length);
45
+ return ctx.env[key] ?? '';
46
+ }
47
+
48
+ // variable.path (default)
49
+ return resolvePath(ctx.variables, trimmed);
50
+ }
51
+
52
+ /**
53
+ * Resolve all {{...}} expressions in a string. If the entire string
54
+ * is a single expression, return the raw value (not stringified).
55
+ */
56
+ export function resolveTemplateString(template: string, ctx: TemplateContext): unknown {
57
+ // If entire string is a single expression, return raw value
58
+ const singleMatch = template.match(/^\{\{(.+?)\}\}$/);
59
+ if (singleMatch) {
60
+ return resolveExpression(singleMatch[1]!, ctx);
61
+ }
62
+
63
+ // Otherwise interpolate all expressions as strings
64
+ return template.replace(EXPR_RE, (_match, expr) => {
65
+ const val = resolveExpression(expr, ctx);
66
+ if (val === undefined || val === null) return '';
67
+ return typeof val === 'object' ? JSON.stringify(val) : String(val);
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Recursively resolve all template strings in a config object.
73
+ */
74
+ export function resolveAllTemplates(
75
+ config: Record<string, unknown>,
76
+ ctx: TemplateContext,
77
+ ): Record<string, unknown> {
78
+ const result: Record<string, unknown> = {};
79
+
80
+ for (const [key, value] of Object.entries(config)) {
81
+ if (typeof value === 'string' && value.includes('{{')) {
82
+ result[key] = resolveTemplateString(value, ctx);
83
+ } else if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
84
+ result[key] = resolveAllTemplates(value as Record<string, unknown>, ctx);
85
+ } else if (Array.isArray(value)) {
86
+ result[key] = value.map(item => {
87
+ if (typeof item === 'string' && item.includes('{{')) {
88
+ return resolveTemplateString(item, ctx);
89
+ }
90
+ if (item !== null && typeof item === 'object' && !Array.isArray(item)) {
91
+ return resolveAllTemplates(item as Record<string, unknown>, ctx);
92
+ }
93
+ return item;
94
+ });
95
+ } else {
96
+ result[key] = value;
97
+ }
98
+ }
99
+
100
+ return result;
101
+ }
102
+
103
+ /**
104
+ * Resolve a dot-separated path on an object.
105
+ * Supports nested access: "foo.bar.baz" → obj.foo.bar.baz
106
+ */
107
+ function resolvePath(obj: unknown, path: string): unknown {
108
+ const parts = path.split('.');
109
+ let current: unknown = obj;
110
+
111
+ for (const part of parts) {
112
+ if (current === null || current === undefined) return undefined;
113
+ if (typeof current !== 'object') return undefined;
114
+ current = (current as Record<string, unknown>)[part];
115
+ }
116
+
117
+ return current;
118
+ }