@positronic/core 0.0.63 → 0.0.65

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 (47) hide show
  1. package/dist/src/dsl/brain-state-machine.js +1 -5
  2. package/dist/src/dsl/builder/brain.js +7 -5
  3. package/dist/src/dsl/constants.js +0 -1
  4. package/dist/src/dsl/duration.js +23 -0
  5. package/dist/src/dsl/example-webhook.js +2 -0
  6. package/dist/src/dsl/execution/constants.js +0 -3
  7. package/dist/src/dsl/execution/event-stream.js +113 -112
  8. package/dist/src/dsl/webhook.js +3 -2
  9. package/dist/src/index.js +6 -0
  10. package/dist/src/tools/index.js +18 -8
  11. package/dist/src/ui/generate-page-html.js +15 -3
  12. package/dist/src/ui/parse-form-data.js +102 -0
  13. package/dist/src/validate-webhook-token.js +17 -0
  14. package/dist/types/clients/types.d.ts +0 -5
  15. package/dist/types/clients/types.d.ts.map +1 -1
  16. package/dist/types/dsl/brain-state-machine.d.ts +0 -6
  17. package/dist/types/dsl/brain-state-machine.d.ts.map +1 -1
  18. package/dist/types/dsl/brain.d.ts +1 -1
  19. package/dist/types/dsl/brain.d.ts.map +1 -1
  20. package/dist/types/dsl/builder/brain.d.ts +4 -3
  21. package/dist/types/dsl/builder/brain.d.ts.map +1 -1
  22. package/dist/types/dsl/constants.d.ts +0 -1
  23. package/dist/types/dsl/constants.d.ts.map +1 -1
  24. package/dist/types/dsl/definitions/blocks.d.ts +2 -2
  25. package/dist/types/dsl/definitions/blocks.d.ts.map +1 -1
  26. package/dist/types/dsl/definitions/events.d.ts +2 -8
  27. package/dist/types/dsl/definitions/events.d.ts.map +1 -1
  28. package/dist/types/dsl/duration.d.ts +7 -0
  29. package/dist/types/dsl/duration.d.ts.map +1 -0
  30. package/dist/types/dsl/execution/constants.d.ts +0 -4
  31. package/dist/types/dsl/execution/constants.d.ts.map +1 -1
  32. package/dist/types/dsl/execution/event-stream.d.ts.map +1 -1
  33. package/dist/types/dsl/types.d.ts +1 -0
  34. package/dist/types/dsl/types.d.ts.map +1 -1
  35. package/dist/types/dsl/webhook.d.ts +3 -1
  36. package/dist/types/dsl/webhook.d.ts.map +1 -1
  37. package/dist/types/index.d.ts +5 -1
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/dist/types/tools/index.d.ts +12 -0
  40. package/dist/types/tools/index.d.ts.map +1 -1
  41. package/dist/types/ui/generate-page-html.d.ts +13 -0
  42. package/dist/types/ui/generate-page-html.d.ts.map +1 -1
  43. package/dist/types/ui/parse-form-data.d.ts +12 -0
  44. package/dist/types/ui/parse-form-data.d.ts.map +1 -0
  45. package/dist/types/validate-webhook-token.d.ts +10 -0
  46. package/dist/types/validate-webhook-token.d.ts.map +1 -0
  47. package/package.json +3 -1
@@ -436,10 +436,6 @@ var stepStatus = reduce(function(ctx, param) {
436
436
  brains: newBrains
437
437
  });
438
438
  });
439
- // stepRetry is a no-op - we just let the event pass through
440
- var stepRetry = reduce(function(ctx) {
441
- return ctx;
442
- });
443
439
  // passthrough is now a no-op - we just let the event pass through
444
440
  var passthrough = function() {
445
441
  return reduce(function(ctx) {
@@ -568,7 +564,7 @@ var makeBrainMachine = function(initialContext) {
568
564
  transition(BRAIN_EVENTS.PAUSED, 'paused', pauseBrain), // Webhook -> waiting (for non-agent webhooks)
569
565
  transition(BRAIN_EVENTS.WEBHOOK, 'waiting', webhookPause), // Webhook response (for resume from non-agent webhook)
570
566
  transition(BRAIN_EVENTS.WEBHOOK_RESPONSE, 'running', webhookResponse), // Step events
571
- transition(BRAIN_EVENTS.STEP_START, 'running', startStep), transition(BRAIN_EVENTS.STEP_COMPLETE, 'running', completeStep), transition(BRAIN_EVENTS.STEP_STATUS, 'running', stepStatus), transition(BRAIN_EVENTS.STEP_RETRY, 'running', stepRetry), // Batch chunk complete - stays in running, accumulates results
567
+ transition(BRAIN_EVENTS.STEP_START, 'running', startStep), transition(BRAIN_EVENTS.STEP_COMPLETE, 'running', completeStep), transition(BRAIN_EVENTS.STEP_STATUS, 'running', stepStatus), // Batch chunk complete - stays in running, accumulates results
572
568
  transition(BRAIN_EVENTS.BATCH_CHUNK_COMPLETE, 'running', batchChunkComplete), // AGENT_START transitions to the agentLoop state
573
569
  transition(BRAIN_EVENTS.AGENT_START, 'agentLoop', agentStart)),
574
570
  // Explicit agent loop state - isolates agent execution logic
@@ -375,6 +375,7 @@ function _ts_values(o) {
375
375
  }
376
376
  import { z } from 'zod';
377
377
  import { BrainEventStream } from '../execution/event-stream.js';
378
+ import { parseDuration } from '../duration.js';
378
379
  export var Brain = /*#__PURE__*/ function() {
379
380
  "use strict";
380
381
  function Brain(title, description) {
@@ -554,12 +555,14 @@ export var Brain = /*#__PURE__*/ function() {
554
555
  },
555
556
  {
556
557
  key: "wait",
557
- value: function wait(title, action) {
558
- var waitBlock = {
558
+ value: function wait(title, action, options) {
559
+ var waitBlock = _object_spread({
559
560
  type: 'wait',
560
561
  title: title,
561
562
  action: action
562
- };
563
+ }, (options === null || options === void 0 ? void 0 : options.timeout) !== undefined && {
564
+ timeout: parseDuration(options.timeout)
565
+ });
563
566
  this.blocks.push(waitBlock);
564
567
  return this.nextBrain();
565
568
  }
@@ -685,13 +688,12 @@ export var Brain = /*#__PURE__*/ function() {
685
688
  },
686
689
  batchConfig: {
687
690
  over: batchConfig.over,
688
- maxRetries: batchConfig.maxRetries,
689
691
  error: batchConfig.error,
690
692
  template: config.template,
691
693
  schema: outputSchema.schema,
692
694
  schemaName: outputSchema.name,
693
695
  client: config.client,
694
- chunkSize: batchConfig.chunkSize
696
+ concurrency: batchConfig.concurrency
695
697
  }
696
698
  };
697
699
  this.blocks.push(promptBlock1);
@@ -5,7 +5,6 @@ export var BRAIN_EVENTS = {
5
5
  RESTART: 'brain:restart',
6
6
  STEP_START: 'step:start',
7
7
  STEP_COMPLETE: 'step:complete',
8
- STEP_RETRY: 'step:retry',
9
8
  STEP_STATUS: 'step:status',
10
9
  ERROR: 'brain:error',
11
10
  COMPLETE: 'brain:complete',
@@ -0,0 +1,23 @@
1
+ import ms from 'ms';
2
+ /**
3
+ * Parse a duration value to milliseconds.
4
+ * Accepts a number (ms passthrough) or a string parsed by `ms` (e.g., '1h', '30m', '7d').
5
+ * Throws on invalid input.
6
+ */ export function parseDuration(input) {
7
+ if (typeof input === 'number') {
8
+ if (!Number.isFinite(input) || input <= 0) {
9
+ throw new Error("Invalid duration: ".concat(input, ". Must be a positive finite number of milliseconds."));
10
+ }
11
+ return input;
12
+ }
13
+ var result;
14
+ try {
15
+ result = ms(input);
16
+ } catch (e) {
17
+ throw new Error('Invalid duration string: "'.concat(input, '". Use formats like "30m", "1h", "24h", "7d".'));
18
+ }
19
+ if (result === undefined || result <= 0) {
20
+ throw new Error('Invalid duration string: "'.concat(input, '". Use formats like "30m", "1h", "24h", "7d".'));
21
+ }
22
+ return result;
23
+ }
@@ -246,6 +246,8 @@ var myBrain = brain('My Brain').step('My Step', function(param) {
246
246
  slackWebhook('thread-123'),
247
247
  emailWebhook('email-456')
248
248
  ];
249
+ }, {
250
+ timeout: '24h'
249
251
  }).step('My Step 2', function(param) {
250
252
  var state = param.state, response = param.response;
251
253
  if (response) {
@@ -9,6 +9,3 @@
9
9
  * Default system prompt prepended to all agent steps.
10
10
  * Explains the headless nature of Positronic Brains.
11
11
  */ export var DEFAULT_AGENT_SYSTEM_PROMPT = "## You Are a Positronic Brain\n\nYou are running as an automated agent in a headless workflow. This is NOT a chat interface - there is no user watching your text output.\n\n**To communicate with users, you MUST use tool calls.** Look at your available tools and use them to send messages, notifications, or create pages for user interaction.\n\n## Tool Execution\n- Tools execute sequentially in the order you call them\n- Webhook-triggering tools pause execution until the webhook fires\n- Terminal tools (like 'done') end the agent immediately\n\n## Resumption\nWhen resuming after a webhook, that response appears as the tool result in your conversation history.";
12
- /**
13
- * Maximum number of retries for step execution.
14
- */ export var MAX_RETRIES = 1;
@@ -469,11 +469,50 @@ import { generateUI } from '../../ui/generate-ui.js';
469
469
  import { generatePageHtml } from '../../ui/generate-page-html.js';
470
470
  import { createScopedMemory } from '../../memory/scoped-memory.js';
471
471
  import { Step } from '../builder/step.js';
472
- import { DEFAULT_ENV, DEFAULT_AGENT_SYSTEM_PROMPT, MAX_RETRIES } from './constants.js';
472
+ import { DEFAULT_ENV, DEFAULT_AGENT_SYSTEM_PROMPT } from './constants.js';
473
473
  import { defaultDoneSchema } from '../../tools/index.js';
474
474
  var clone = function(value) {
475
475
  return structuredClone(value);
476
476
  };
477
+ function createSemaphore(limit) {
478
+ var running = 0;
479
+ var queue = [];
480
+ return {
481
+ acquire: function acquire() {
482
+ return _async_to_generator(function() {
483
+ return _ts_generator(this, function(_state) {
484
+ if (running < limit) {
485
+ running++;
486
+ return [
487
+ 2,
488
+ function() {
489
+ running--;
490
+ if (queue.length > 0) {
491
+ running++;
492
+ queue.shift()();
493
+ }
494
+ }
495
+ ];
496
+ }
497
+ return [
498
+ 2,
499
+ new Promise(function(resolve) {
500
+ queue.push(function() {
501
+ return resolve(function() {
502
+ running--;
503
+ if (queue.length > 0) {
504
+ running++;
505
+ queue.shift()();
506
+ }
507
+ });
508
+ });
509
+ })
510
+ ];
511
+ });
512
+ })();
513
+ }
514
+ };
515
+ }
477
516
  export var BrainEventStream = /*#__PURE__*/ function() {
478
517
  "use strict";
479
518
  function BrainEventStream(params) {
@@ -1009,7 +1048,7 @@ export var BrainEventStream = /*#__PURE__*/ function() {
1009
1048
  key: "executeStep",
1010
1049
  value: function executeStep(step) {
1011
1050
  return _wrap_async_generator(function() {
1012
- var block, stepBlock, _this_resumeContext, brainBlock, initialState, innerResumeContext, patches, innerBrainPaused, _this_options, _this_options1, innerRun, _iteratorAbruptCompletion, _didIteratorError, _iteratorError, _iterator, _step, _value, event, err, _innerResumeContext_state, baseState, innerState, prevState, _, prevState1, stepBlock1, retries, result, _this_options2, actionPromise, error, _this_options3;
1051
+ var block, stepBlock, _this_resumeContext, brainBlock, initialState, innerResumeContext, patches, innerBrainPaused, _this_options, _this_options1, innerRun, _iteratorAbruptCompletion, _didIteratorError, _iteratorError, _iterator, _step, _value, event, err, _innerResumeContext_state, baseState, innerState, prevState, _, prevState1, stepBlock1, _this_options2, result;
1013
1052
  return _ts_generator(this, function(_state) {
1014
1053
  switch(_state.label){
1015
1054
  case 0:
@@ -1203,7 +1242,7 @@ export var BrainEventStream = /*#__PURE__*/ function() {
1203
1242
  _state.sent();
1204
1243
  return [
1205
1244
  3,
1206
- 33
1245
+ 25
1207
1246
  ];
1208
1247
  case 20:
1209
1248
  if (!(block.type === 'agent')) return [
@@ -1218,91 +1257,28 @@ export var BrainEventStream = /*#__PURE__*/ function() {
1218
1257
  _state.sent();
1219
1258
  return [
1220
1259
  3,
1221
- 33
1260
+ 25
1222
1261
  ];
1223
1262
  case 22:
1224
1263
  // Get previous state before action
1225
1264
  prevState1 = this.currentState;
1226
1265
  stepBlock1 = block;
1227
- // Execute step with automatic retry on failure
1228
- retries = 0;
1229
- _state.label = 23;
1230
- case 23:
1231
- if (!true) return [
1232
- 3,
1233
- 31
1234
- ];
1235
- _state.label = 24;
1236
- case 24:
1237
- _state.trys.push([
1238
- 24,
1239
- 26,
1240
- ,
1241
- 30
1242
- ]);
1243
- actionPromise = Promise.resolve(stepBlock1.action(_object_spread({
1244
- state: this.currentState,
1245
- options: (_this_options2 = this.options) !== null && _this_options2 !== void 0 ? _this_options2 : {},
1246
- client: this.client,
1247
- resources: this.resources,
1248
- response: this.currentResponse,
1249
- page: this.currentPage,
1250
- pages: this.pages,
1251
- env: this.env,
1252
- memory: this.scopedMemory
1253
- }, this.services)));
1254
1266
  return [
1255
1267
  4,
1256
- _await_async_generator(actionPromise)
1268
+ _await_async_generator(Promise.resolve(stepBlock1.action(_object_spread({
1269
+ state: this.currentState,
1270
+ options: (_this_options2 = this.options) !== null && _this_options2 !== void 0 ? _this_options2 : {},
1271
+ client: this.client,
1272
+ resources: this.resources,
1273
+ response: this.currentResponse,
1274
+ page: this.currentPage,
1275
+ pages: this.pages,
1276
+ env: this.env,
1277
+ memory: this.scopedMemory
1278
+ }, this.services))))
1257
1279
  ];
1258
- case 25:
1280
+ case 23:
1259
1281
  result = _state.sent();
1260
- return [
1261
- 3,
1262
- 31
1263
- ]; // Success
1264
- case 26:
1265
- error = _state.sent();
1266
- if (!(retries < MAX_RETRIES)) return [
1267
- 3,
1268
- 28
1269
- ];
1270
- retries++;
1271
- return [
1272
- 4,
1273
- {
1274
- type: BRAIN_EVENTS.STEP_RETRY,
1275
- stepTitle: step.block.title,
1276
- stepId: step.id,
1277
- error: {
1278
- name: error.name,
1279
- message: error.message,
1280
- stack: error.stack
1281
- },
1282
- attempt: retries,
1283
- options: (_this_options3 = this.options) !== null && _this_options3 !== void 0 ? _this_options3 : {},
1284
- brainRunId: this.brainRunId
1285
- }
1286
- ];
1287
- case 27:
1288
- _state.sent();
1289
- return [
1290
- 3,
1291
- 29
1292
- ];
1293
- case 28:
1294
- throw error;
1295
- case 29:
1296
- return [
1297
- 3,
1298
- 30
1299
- ];
1300
- case 30:
1301
- return [
1302
- 3,
1303
- 23
1304
- ];
1305
- case 31:
1306
1282
  // Extract state from result (handles promptResponse case)
1307
1283
  if (result && (typeof result === "undefined" ? "undefined" : _type_of(result)) === 'object' && 'promptResponse' in result) {
1308
1284
  this.currentState = result.state;
@@ -1313,7 +1289,7 @@ export var BrainEventStream = /*#__PURE__*/ function() {
1313
1289
  5,
1314
1290
  _ts_values(_async_generator_delegate(_async_iterator(this.completeStep(step, prevState1))))
1315
1291
  ];
1316
- case 32:
1292
+ case 24:
1317
1293
  _state.sent();
1318
1294
  // Handle promptResponse - set currentResponse for next step
1319
1295
  if (result && (typeof result === "undefined" ? "undefined" : _type_of(result)) === 'object' && 'promptResponse' in result) {
@@ -1321,8 +1297,8 @@ export var BrainEventStream = /*#__PURE__*/ function() {
1321
1297
  }
1322
1298
  // Reset currentPage after step consumes it (page is ephemeral)
1323
1299
  this.currentPage = undefined;
1324
- _state.label = 33;
1325
- case 33:
1300
+ _state.label = 25;
1301
+ case 25:
1326
1302
  return [
1327
1303
  2
1328
1304
  ];
@@ -1734,7 +1710,7 @@ export var BrainEventStream = /*#__PURE__*/ function() {
1734
1710
  var desc = comp.description.split('\n')[0]; // First line only
1735
1711
  return "- ".concat(compName, ": ").concat(desc);
1736
1712
  }).join('\n');
1737
- description = "Generate a web page for displaying rich content or collecting user input.\n\nSometimes you need more than simple notifications to communicate with users. This tool creates web pages that can display formatted content, dashboards, or forms to collect information.\n\nAVAILABLE COMPONENTS:\n".concat(componentList, "\n\nRETURNS: { url: string, webhook: { slug: string, identifier: string } | null }\n- url: The page URL\n- webhook: For forms (hasForm=true), contains slug and identifier that can be passed to waitForWebhook to pause execution until the user submits the form\n\nIMPORTANT: Users have no way to discover the page URL on their own. After generating a page, you must tell them the URL using whatever communication tools are available.");
1713
+ description = "Generate a web page for displaying rich content or collecting user input.\n\nSometimes you need more than simple notifications to communicate with users. This tool creates web pages that can display formatted content, dashboards, or forms to collect information.\n\nAVAILABLE COMPONENTS:\n".concat(componentList, "\n\nRETURNS: { url: string, webhook: { slug: string, identifier: string, token: string } | null }\n- url: The page URL\n- webhook: For forms (hasForm=true), contains slug, identifier, and token that must all be passed to waitForWebhook to pause execution until the user submits the form\n\nIMPORTANT: Users have no way to discover the page URL on their own. After generating a page, you must tell them the URL using whatever communication tools are available.");
1738
1714
  }
1739
1715
  toolsForClient[name1] = {
1740
1716
  description: description,
@@ -1995,9 +1971,11 @@ export var BrainEventStream = /*#__PURE__*/ function() {
1995
1971
  webhooks: webhooks.map(function(w) {
1996
1972
  return {
1997
1973
  slug: w.slug,
1998
- identifier: w.identifier
1974
+ identifier: w.identifier,
1975
+ token: w.token
1999
1976
  };
2000
- })
1977
+ }),
1978
+ timeout: waitForResult.timeout
2001
1979
  };
2002
1980
  // Emit tool result event for debugging/visibility (with pending status)
2003
1981
  return [
@@ -2133,12 +2111,15 @@ export var BrainEventStream = /*#__PURE__*/ function() {
2133
2111
  // Emit WEBHOOK event with all webhooks (first response wins)
2134
2112
  return [
2135
2113
  4,
2136
- {
2114
+ _object_spread_props(_object_spread({
2137
2115
  type: BRAIN_EVENTS.WEBHOOK,
2138
- waitFor: pendingWebhook.webhooks,
2116
+ waitFor: pendingWebhook.webhooks
2117
+ }, pendingWebhook.timeout !== undefined && {
2118
+ timeout: pendingWebhook.timeout
2119
+ }), {
2139
2120
  options: (_this_options18 = this.options) !== null && _this_options18 !== void 0 ? _this_options18 : {},
2140
2121
  brainRunId: this.brainRunId
2141
- }
2122
+ })
2142
2123
  ];
2143
2124
  case 58:
2144
2125
  _state.sent();
@@ -2167,7 +2148,7 @@ export var BrainEventStream = /*#__PURE__*/ function() {
2167
2148
  * Cloudflare backends can restart the DO to reclaim memory.
2168
2149
  */ function executeBatchPrompt(step) {
2169
2150
  return _wrap_async_generator(function() {
2170
- var _this, _this_resumeContext, block, batchConfig, prevState, _batchConfig_client, client, items, totalItems, _batchConfig_chunkSize, chunkSize, batchProgress, _batchProgress_processedCount, startIndex, results, chunkStart, signals, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, signal, _this_options, err, chunkEnd, chunk, chunkResults, i, _this_options1, finalResults;
2151
+ var _this, _this_resumeContext, block, batchConfig, prevState, _batchConfig_client, client, items, totalItems, _batchConfig_concurrency, concurrency, semaphore, batchProgress, _batchProgress_processedCount, startIndex, results, chunkStart, signals, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, signal, _this_options, err, chunkEnd, chunk, chunkResults, i, _this_options1, finalResults;
2171
2152
  return _ts_generator(this, function(_state) {
2172
2153
  switch(_state.label){
2173
2154
  case 0:
@@ -2178,7 +2159,8 @@ export var BrainEventStream = /*#__PURE__*/ function() {
2178
2159
  client = (_batchConfig_client = batchConfig.client) !== null && _batchConfig_client !== void 0 ? _batchConfig_client : this.client;
2179
2160
  items = batchConfig.over(this.currentState);
2180
2161
  totalItems = items.length;
2181
- chunkSize = (_batchConfig_chunkSize = batchConfig.chunkSize) !== null && _batchConfig_chunkSize !== void 0 ? _batchConfig_chunkSize : 10;
2162
+ concurrency = (_batchConfig_concurrency = batchConfig.concurrency) !== null && _batchConfig_concurrency !== void 0 ? _batchConfig_concurrency : 10;
2163
+ semaphore = createSemaphore(concurrency);
2182
2164
  // Resume support: pick up from where we left off
2183
2165
  batchProgress = (_this_resumeContext = this.resumeContext) === null || _this_resumeContext === void 0 ? void 0 : _this_resumeContext.batchProgress;
2184
2166
  startIndex = (_batchProgress_processedCount = batchProgress === null || batchProgress === void 0 ? void 0 : batchProgress.processedCount) !== null && _batchProgress_processedCount !== void 0 ? _batchProgress_processedCount : 0;
@@ -2285,39 +2267,45 @@ export var BrainEventStream = /*#__PURE__*/ function() {
2285
2267
  7
2286
2268
  ];
2287
2269
  case 11:
2288
- chunkEnd = Math.min(chunkStart + chunkSize, totalItems);
2270
+ chunkEnd = Math.min(chunkStart + concurrency, totalItems);
2289
2271
  chunk = items.slice(chunkStart, chunkEnd);
2290
2272
  return [
2291
2273
  4,
2292
2274
  _await_async_generator(Promise.all(chunk.map(function(item) {
2293
2275
  return _async_to_generator(function() {
2294
- var promptText, output, error, fallback;
2276
+ var release, promptText, output, error, fallback;
2295
2277
  return _ts_generator(this, function(_state) {
2296
2278
  switch(_state.label){
2297
2279
  case 0:
2280
+ return [
2281
+ 4,
2282
+ semaphore.acquire()
2283
+ ];
2284
+ case 1:
2285
+ release = _state.sent();
2286
+ _state.label = 2;
2287
+ case 2:
2298
2288
  _state.trys.push([
2299
- 0,
2300
- 3,
2301
- ,
2302
- 4
2289
+ 2,
2290
+ 5,
2291
+ 6,
2292
+ 7
2303
2293
  ]);
2304
2294
  return [
2305
2295
  4,
2306
2296
  batchConfig.template(item, this.resources)
2307
2297
  ];
2308
- case 1:
2298
+ case 3:
2309
2299
  promptText = _state.sent();
2310
2300
  return [
2311
2301
  4,
2312
- client.generateObject(_object_spread({
2302
+ client.generateObject({
2313
2303
  schema: batchConfig.schema,
2314
2304
  schemaName: batchConfig.schemaName,
2315
2305
  prompt: promptText
2316
- }, batchConfig.maxRetries !== undefined && {
2317
- maxRetries: batchConfig.maxRetries
2318
- }))
2306
+ })
2319
2307
  ];
2320
- case 2:
2308
+ case 4:
2321
2309
  output = _state.sent();
2322
2310
  return [
2323
2311
  2,
@@ -2326,7 +2314,7 @@ export var BrainEventStream = /*#__PURE__*/ function() {
2326
2314
  output
2327
2315
  ]
2328
2316
  ];
2329
- case 3:
2317
+ case 5:
2330
2318
  error = _state.sent();
2331
2319
  if (batchConfig.error) {
2332
2320
  fallback = batchConfig.error(item, error);
@@ -2339,7 +2327,12 @@ export var BrainEventStream = /*#__PURE__*/ function() {
2339
2327
  ];
2340
2328
  }
2341
2329
  throw error;
2342
- case 4:
2330
+ case 6:
2331
+ release();
2332
+ return [
2333
+ 7
2334
+ ];
2335
+ case 7:
2343
2336
  return [
2344
2337
  2
2345
2338
  ];
@@ -2374,7 +2367,7 @@ export var BrainEventStream = /*#__PURE__*/ function() {
2374
2367
  _state.sent();
2375
2368
  _state.label = 14;
2376
2369
  case 14:
2377
- chunkStart += chunkSize;
2370
+ chunkStart += concurrency;
2378
2371
  return [
2379
2372
  3,
2380
2373
  1
@@ -2406,7 +2399,7 @@ export var BrainEventStream = /*#__PURE__*/ function() {
2406
2399
  * Generates UI components, renders to HTML, creates page, and sets up webhook.
2407
2400
  */ function executeUIStep(step, stepBlock) {
2408
2401
  return _wrap_async_generator(function() {
2409
- var prevState, uiConfig, prompt, uiResult, placementCount, placementInfo, _uiResult_text, webhookIdentifier, formAction, html, page, _uiConfig_responseSchema, webhook;
2402
+ var prevState, uiConfig, prompt, uiResult, placementCount, placementInfo, _uiResult_text, webhookIdentifier, formToken, formAction, html, page, _uiConfig_responseSchema, webhook;
2410
2403
  return _ts_generator(this, function(_state) {
2411
2404
  switch(_state.label){
2412
2405
  case 0:
@@ -2453,6 +2446,8 @@ export var BrainEventStream = /*#__PURE__*/ function() {
2453
2446
  }
2454
2447
  // Create unique identifier for this form submission webhook
2455
2448
  webhookIdentifier = "".concat(this.brainRunId, "-").concat(step.id);
2449
+ // Generate CSRF token for form submission validation
2450
+ formToken = crypto.randomUUID();
2456
2451
  // Construct form action URL for the webhook
2457
2452
  formAction = "".concat(this.env.origin, "/webhooks/system/ui-form?identifier=").concat(encodeURIComponent(webhookIdentifier));
2458
2453
  // Generate HTML page
@@ -2461,7 +2456,8 @@ export var BrainEventStream = /*#__PURE__*/ function() {
2461
2456
  rootId: uiResult.rootId,
2462
2457
  data: this.currentState,
2463
2458
  title: stepBlock.title,
2464
- formAction: formAction
2459
+ formAction: formAction,
2460
+ formToken: formToken
2465
2461
  });
2466
2462
  return [
2467
2463
  4,
@@ -2474,7 +2470,8 @@ export var BrainEventStream = /*#__PURE__*/ function() {
2474
2470
  webhook = {
2475
2471
  slug: 'ui-form',
2476
2472
  identifier: webhookIdentifier,
2477
- schema: (_uiConfig_responseSchema = uiConfig.responseSchema) !== null && _uiConfig_responseSchema !== void 0 ? _uiConfig_responseSchema : z.record(z.unknown())
2473
+ schema: (_uiConfig_responseSchema = uiConfig.responseSchema) !== null && _uiConfig_responseSchema !== void 0 ? _uiConfig_responseSchema : z.record(z.unknown()),
2474
+ token: formToken
2478
2475
  };
2479
2476
  // Set currentPage for the next step to access
2480
2477
  this.currentPage = {
@@ -2583,18 +2580,22 @@ export var BrainEventStream = /*#__PURE__*/ function() {
2583
2580
  serializedWaitFor = webhooks.map(function(registration) {
2584
2581
  return {
2585
2582
  slug: registration.slug,
2586
- identifier: registration.identifier
2583
+ identifier: registration.identifier,
2584
+ token: registration.token
2587
2585
  };
2588
2586
  });
2589
2587
  // Emit WEBHOOK event
2590
2588
  return [
2591
2589
  4,
2592
- {
2590
+ _object_spread_props(_object_spread({
2593
2591
  type: BRAIN_EVENTS.WEBHOOK,
2594
- waitFor: serializedWaitFor,
2592
+ waitFor: serializedWaitFor
2593
+ }, waitBlock.timeout !== undefined && {
2594
+ timeout: waitBlock.timeout
2595
+ }), {
2595
2596
  options: this.options,
2596
2597
  brainRunId: this.brainRunId
2597
- }
2598
+ })
2598
2599
  ];
2599
2600
  case 6:
2600
2601
  _state.sent();
@@ -1,11 +1,12 @@
1
1
  // Factory function to create webhooks
2
2
  export function createWebhook(slug, schema, handler) {
3
3
  // Create the registration function
4
- var webhookFn = function(identifier) {
4
+ var webhookFn = function(identifier, token) {
5
5
  return {
6
6
  slug: slug,
7
7
  identifier: identifier,
8
- schema: schema
8
+ schema: schema,
9
+ token: token
9
10
  };
10
11
  };
11
12
  // Attach properties to the function
package/dist/src/index.js CHANGED
@@ -3,6 +3,7 @@ export { BrainRunner } from './dsl/brain-runner.js';
3
3
  export { createBrain } from './dsl/create-brain.js';
4
4
  export { STATUS, BRAIN_EVENTS } from './dsl/constants.js';
5
5
  export { applyPatches } from './dsl/json-patch.js';
6
+ export { parseDuration } from './dsl/duration.js';
6
7
  // Only needed for development to ensure that zod version numbers are the same, it's a peer
7
8
  // dependency so when not using file://..path/to/package links the version numbers
8
9
  // will match just fine if the user has the same version of zod installed.
@@ -14,6 +15,11 @@ export { RESOURCE_TYPES } from './resources/resources.js';
14
15
  // Default tools
15
16
  export { createTool, defaultTools, defaultDoneSchema, generateUI, waitForWebhook, print, consoleLog } from './tools/index.js';
16
17
  export { createScopedMemory } from './memory/scoped-memory.js';
18
+ // UI utilities
19
+ export { generateFormToken } from './ui/generate-page-html.js';
20
+ export { parseFormData } from './ui/parse-form-data.js';
21
+ // Webhook token validation
22
+ export { validateWebhookToken } from './validate-webhook-token.js';
17
23
  // Brain state machine
18
24
  export { createBrainExecutionMachine, createBrainMachine, sendEvent, reconstructBrainTree, brainMachineDefinition } from './dsl/brain-state-machine.js';
19
25
  // Signal validation