@positronic/template-new-project 0.0.61 → 0.0.62

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.
package/index.js CHANGED
@@ -53,10 +53,10 @@ module.exports = {
53
53
  ],
54
54
  setup: async ctx => {
55
55
  const devRootPath = process.env.POSITRONIC_LOCAL_PATH;
56
- let coreVersion = '^0.0.61';
57
- let cloudflareVersion = '^0.0.61';
58
- let clientVercelVersion = '^0.0.61';
59
- let genUIComponentsVersion = '^0.0.61';
56
+ let coreVersion = '^0.0.62';
57
+ let cloudflareVersion = '^0.0.62';
58
+ let clientVercelVersion = '^0.0.62';
59
+ let genUIComponentsVersion = '^0.0.62';
60
60
 
61
61
  // Map backend selection to package names
62
62
  const backendPackageMap = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@positronic/template-new-project",
3
- "version": "0.0.61",
3
+ "version": "0.0.62",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -6,6 +6,9 @@
6
6
  "compatibility_flags": ["nodejs_compat", "nodejs_compat_populate_process_env"],
7
7
  "workers_dev": true,
8
8
  "preview_urls": true,
9
+ "limits": {
10
+ "cpu_ms": 300000
11
+ },
9
12
  "migrations": [
10
13
  {
11
14
  "tag": "v1",
@@ -157,6 +157,43 @@ const mainBrain = brain('Main Process')
157
157
  );
158
158
  ```
159
159
 
160
+ ## Guard Clauses
161
+
162
+ Use `.guard()` to short-circuit a brain when a condition isn't met. If the predicate returns `true`, execution continues normally. If it returns `false`, all remaining steps are skipped and the brain completes with the current state.
163
+
164
+ ```typescript
165
+ brain('email-checker')
166
+ .step('Check Emails', async ({ state, client }) => {
167
+ const emails = await analyzeEmails(client, state);
168
+ return { ...state, emails };
169
+ })
170
+ .guard(({ state }) => state.emails.some(e => e.important))
171
+ // everything below only runs if guard passes
172
+ .ui('Review emails', { ... })
173
+ .step('Notify and wait', ...)
174
+ .step('Handle response', ...);
175
+ ```
176
+
177
+ Key points:
178
+ - The predicate is synchronous and receives `{ state, options }`
179
+ - Returns `true` to continue, `false` to skip all remaining steps
180
+ - The guard doesn't transform state — if you need to set "early exit" fields, do it in the step before the guard
181
+ - State type is unchanged after a guard (subsequent steps see the same type)
182
+ - Multiple guards can be chained — the first one that fails skips everything after it
183
+ - Halted steps appear as "halted" in the CLI watch view
184
+ - An optional title can be passed as the second argument: `.guard(predicate, 'Check emails exist')`
185
+
186
+ ### Multiple Guards
187
+
188
+ ```typescript
189
+ brain('processor')
190
+ .step('Init', () => ({ data: [], validated: false }))
191
+ .guard(({ state }) => state.data.length > 0, 'Has data')
192
+ .step('Validate', ({ state }) => ({ ...state, validated: true }))
193
+ .guard(({ state }) => state.validated, 'Is valid')
194
+ .step('Process', ({ state }) => ({ ...state, processed: true }));
195
+ ```
196
+
160
197
  ## Step Parameters
161
198
 
162
199
  Each step receives these parameters:
@@ -831,12 +868,7 @@ brain('Batch Processor')
831
868
  over: (state) => state.items, // Array to iterate over
832
869
  concurrency: 10, // Parallel requests (default: 10)
833
870
  stagger: 100, // Delay between requests in ms
834
- retry: {
835
- maxRetries: 3,
836
- backoff: 'exponential',
837
- initialDelay: 1000,
838
- maxDelay: 30000,
839
- },
871
+ maxRetries: 3,
840
872
  error: (item, error) => ({ summary: 'Failed to summarize' }) // Fallback on error
841
873
  })
842
874
  .step('Process Results', ({ state }) => ({
@@ -854,7 +886,7 @@ brain('Batch Processor')
854
886
  - `over: (state) => T[]` - Function returning the array to iterate over
855
887
  - `concurrency: number` - Maximum parallel requests (default: 10)
856
888
  - `stagger: number` - Milliseconds to wait between starting requests
857
- - `retry: RetryConfig` - Retry configuration for failed requests
889
+ - `maxRetries: number` - Maximum number of retries for failed requests (passed to the AI client SDK)
858
890
  - `error: (item, error) => Response` - Fallback function when a request fails
859
891
 
860
892
  ### Result Format
@@ -917,7 +949,60 @@ brain('Research Assistant')
917
949
  Each tool requires:
918
950
  - `description: string` - What the tool does
919
951
  - `inputSchema: ZodSchema` - Zod schema for the tool's input
920
- - `execute: (input) => Promise<any>` - Function to execute when the tool is called
952
+ - `execute: (input, context) => Promise<any>` - Function to execute when the tool is called
953
+ - `terminal?: boolean` - If true, calling this tool ends the agent loop
954
+
955
+ ### Tool Webhooks (waitFor)
956
+
957
+ Tools can pause agent execution and wait for external events by returning `{ waitFor: webhook(...) }` from their `execute` function. This is useful for human-in-the-loop workflows where the agent needs to wait for approval, external API callbacks, or other asynchronous events.
958
+
959
+ ```typescript
960
+ import approvalWebhook from '../webhooks/approval.js';
961
+
962
+ brain('Support Ticket Handler')
963
+ .brain('Handle Support Request', {
964
+ system: 'You are a support agent. Escalate complex issues for human review.',
965
+ prompt: ({ ticket }) => `Handle this support ticket: <%= '${ticket.description}' %>`,
966
+ tools: {
967
+ escalateToHuman: {
968
+ description: 'Escalate the ticket to a human reviewer for approval',
969
+ inputSchema: z.object({
970
+ summary: z.string().describe('Summary of the issue'),
971
+ recommendation: z.string().describe('Your recommended action'),
972
+ }),
973
+ execute: async ({ summary, recommendation }, context) => {
974
+ // Send notification to human reviewer (e.g., via Slack, email)
975
+ await notifyReviewer({ summary, recommendation, ticketId: context.state.ticketId });
976
+
977
+ // Return waitFor to pause until the webhook fires
978
+ return {
979
+ waitFor: approvalWebhook(context.state.ticketId),
980
+ };
981
+ },
982
+ },
983
+ resolveTicket: {
984
+ description: 'Mark the ticket as resolved',
985
+ inputSchema: z.object({
986
+ resolution: z.string().describe('How the ticket was resolved'),
987
+ }),
988
+ terminal: true,
989
+ },
990
+ },
991
+ })
992
+ .step('Process Result', ({ state, response }) => ({
993
+ ...state,
994
+ // response contains the webhook data (e.g., { approved: true, reviewerNote: '...' })
995
+ approved: response?.approved,
996
+ reviewerNote: response?.reviewerNote,
997
+ }));
998
+ ```
999
+
1000
+ Key points about tool `waitFor`:
1001
+ - Return `{ waitFor: webhook(...) }` to pause the agent and wait for an external event
1002
+ - The webhook response is available in the next step via the `response` parameter
1003
+ - You can wait for multiple webhooks (first response wins): `{ waitFor: [webhook1(...), webhook2(...)] }`
1004
+ - The `execute` function receives a `context` parameter with access to `state`, `options`, `env`, etc.
1005
+ - Use this pattern for approvals, external API callbacks, or any human-in-the-loop workflow
921
1006
 
922
1007
  ### Agent Output Schema
923
1008
 
@@ -105,6 +105,26 @@ kill $(lsof -ti:38291)
105
105
  - Always clean up by killing the server process when done
106
106
  - The log file contains timestamped entries with [INFO], [ERROR], and [WARN] prefixes
107
107
 
108
+ ## Guard Clauses
109
+
110
+ Use `.guard()` to short-circuit a brain when a condition isn't met:
111
+
112
+ ```typescript
113
+ brain('approval-example')
114
+ .step('Init', () => ({ needsApproval: true, data: [] }))
115
+ .guard(({ state }) => state.data.length > 0, 'Has data')
116
+ // everything below only runs if guard passes
117
+ .step('Process', ({ state }) => ({ ...state, processed: true }))
118
+ .step('Continue', ({ state }) => ({ ...state, done: true }));
119
+ ```
120
+
121
+ Key rules:
122
+ - Predicate returns `true` to continue, `false` to skip all remaining steps
123
+ - The predicate is synchronous and receives `{ state, options }`
124
+ - State type is unchanged after a guard
125
+ - Optional title as second argument: `.guard(predicate, 'Check condition')`
126
+ - See `/docs/brain-dsl-guide.md` for more details
127
+
108
128
  ## Brain DSL Type Inference
109
129
 
110
130
  The Brain DSL has very strong type inference capabilities. **Important**: You should NOT explicitly specify types on the state object as it flows through steps. The types are automatically inferred from the previous step.