@positronic/template-new-project 0.0.60 → 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.
|
|
57
|
-
let cloudflareVersion = '^0.0.
|
|
58
|
-
let clientVercelVersion = '^0.0.
|
|
59
|
-
let genUIComponentsVersion = '^0.0.
|
|
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
|
@@ -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",
|
|
@@ -28,6 +31,21 @@
|
|
|
28
31
|
"vars": {
|
|
29
32
|
"R2_BUCKET_NAME": "<%= projectName %>"
|
|
30
33
|
},
|
|
34
|
+
"observability": {
|
|
35
|
+
"enabled": false,
|
|
36
|
+
"head_sampling_rate": 1,
|
|
37
|
+
"logs": {
|
|
38
|
+
"enabled": true,
|
|
39
|
+
"head_sampling_rate": 1,
|
|
40
|
+
"persist": true,
|
|
41
|
+
"invocation_logs": true
|
|
42
|
+
},
|
|
43
|
+
"traces": {
|
|
44
|
+
"enabled": false,
|
|
45
|
+
"persist": true,
|
|
46
|
+
"head_sampling_rate": 1
|
|
47
|
+
}
|
|
48
|
+
},
|
|
31
49
|
"env": {
|
|
32
50
|
"production": {
|
|
33
51
|
"name": "<%= projectName %>",
|
|
@@ -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
|
-
|
|
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
|
-
- `
|
|
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.
|