loopengine 0.3.10 → 0.3.11
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/dist/core/system-agents/templates.d.ts +4 -0
- package/dist/core/system-agents/templates.js +18 -446
- package/dist/core/system-agents/workflow-examples.d.ts +10 -0
- package/dist/core/system-agents/workflow-examples.js +432 -0
- package/dist/core/system-agents/workflow-planner.d.ts +10 -2
- package/dist/core/system-agents/workflow-planner.js +6 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/web/agents-list-page.js +2 -2
- package/dist/web/console-shell.js +2 -3
- package/dist/web/templates-page.d.ts +1 -0
- package/dist/web/templates-page.js +8 -150
- package/dist/web/workflow-admin.js +13 -0
- package/dist/web/workflow-page.js +76 -20
- package/package.json +1 -1
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
const GMAIL = '@loopengine-co/gmail';
|
|
2
|
+
const SHOPIFY = '@loopengine-co/shopify';
|
|
3
|
+
const HUBSPOT = '@loopengine-co/hubspot';
|
|
4
|
+
const GMAIL_KEYS = ['GMAIL_CLIENT_ID', 'GMAIL_CLIENT_SECRET', 'GMAIL_REFRESH_TOKEN'];
|
|
5
|
+
const SHOPIFY_KEYS = ['SHOPIFY_STORE_DOMAIN', 'SHOPIFY_ADMIN_TOKEN'];
|
|
6
|
+
const GITHUB_APP = 'io.github.github/github-mcp-server';
|
|
7
|
+
const FIRECRAWL_APP = 'io.github.firecrawl/firecrawl-mcp-server';
|
|
8
|
+
const NEW_AGENT = 'A ready-made example, set up as its own agent; you can add it to an agent you have instead.';
|
|
9
|
+
const policiesNote = (what, skill) => `Write ${what} into its ${skill} skill (Advanced → Skills): the agent answers from it and nothing else`;
|
|
10
|
+
const SUPPORT_POLICIES = {
|
|
11
|
+
id: 'support-policies',
|
|
12
|
+
description: 'Our shipping, returns, refunds and other customer policies. Read it before telling a customer anything about a policy.',
|
|
13
|
+
body: `# Our customer policies
|
|
14
|
+
|
|
15
|
+
Edit this: replace each [bracket] with your own policy. The agent answers customers from this and nothing else.
|
|
16
|
+
|
|
17
|
+
## Shipping
|
|
18
|
+
- Where we ship: [countries]
|
|
19
|
+
- How long it takes: [e.g. 3–5 business days]
|
|
20
|
+
- Tracking: [when and how customers get it]
|
|
21
|
+
|
|
22
|
+
## Returns and refunds
|
|
23
|
+
- Return window: [e.g. 30 days from delivery]
|
|
24
|
+
- What can't be returned: [e.g. sale items, opened cosmetics]
|
|
25
|
+
- How refunds are paid: [e.g. to the original payment method, in 5–10 business days]
|
|
26
|
+
|
|
27
|
+
## Other questions
|
|
28
|
+
- [Order changes and cancellations]
|
|
29
|
+
- [Warranty]
|
|
30
|
+
- [Anything customers often ask]
|
|
31
|
+
`,
|
|
32
|
+
};
|
|
33
|
+
const RETURN_POLICY = {
|
|
34
|
+
id: 'return-policy',
|
|
35
|
+
description: 'Our return and refund policy: the window, what qualifies, and how much is refunded. Read it before proposing any refund.',
|
|
36
|
+
body: `# Our return policy
|
|
37
|
+
|
|
38
|
+
Edit this: replace each [bracket] with your own policy. The agent proposes refunds from this and nothing else.
|
|
39
|
+
|
|
40
|
+
- Return window: [e.g. 30 days from delivery]
|
|
41
|
+
- Eligible items: [e.g. unused, in the original packaging]
|
|
42
|
+
- Not eligible: [e.g. sale items, gift cards, personalised items]
|
|
43
|
+
- Damaged or wrong items: [e.g. full refund including shipping, no return needed]
|
|
44
|
+
- Change of mind: [e.g. refund of the item price, customer pays return shipping]
|
|
45
|
+
- Shipping costs: [when they're refunded]
|
|
46
|
+
- Partial refunds: [when you offer one, and how much]
|
|
47
|
+
`,
|
|
48
|
+
};
|
|
49
|
+
const EXPENSE_POLICY = {
|
|
50
|
+
id: 'expense-policy',
|
|
51
|
+
description: 'Our expense policy: what can be claimed, the limits, and what a claim needs. Read it before giving any verdict.',
|
|
52
|
+
body: `# Our expense policy
|
|
53
|
+
|
|
54
|
+
Edit this: replace each [bracket] with your own policy. The agent checks claims against this and nothing else.
|
|
55
|
+
|
|
56
|
+
## What can be claimed, and the limits
|
|
57
|
+
- Travel: [e.g. economy flights and trains; taxis when there's no public transport]
|
|
58
|
+
- Hotels: [e.g. up to 150 per night]
|
|
59
|
+
- Meals: [e.g. up to 50 per day when travelling]
|
|
60
|
+
- Equipment and software: [e.g. up to 200 without pre-approval]
|
|
61
|
+
|
|
62
|
+
## What can't be claimed
|
|
63
|
+
- [e.g. alcohol, fines, personal entertainment]
|
|
64
|
+
|
|
65
|
+
## What every claim needs
|
|
66
|
+
- An itemised receipt showing the amount, date and merchant
|
|
67
|
+
- [Pre-approval for anything over a limit]
|
|
68
|
+
- Submitted within [e.g. 60 days]
|
|
69
|
+
`,
|
|
70
|
+
};
|
|
71
|
+
/** The parts every example's plan shares. */
|
|
72
|
+
function example(id, category, plan) {
|
|
73
|
+
return {
|
|
74
|
+
id,
|
|
75
|
+
category,
|
|
76
|
+
plan: {
|
|
77
|
+
parameters: {},
|
|
78
|
+
notify: { on: false, reason: '' },
|
|
79
|
+
gaps: [],
|
|
80
|
+
target: { kind: 'new', reason: NEW_AGENT },
|
|
81
|
+
...plan,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export const WORKFLOW_EXAMPLES = [
|
|
86
|
+
example('customer-email-replies', 'Customer service', {
|
|
87
|
+
name: 'Answer customer emails',
|
|
88
|
+
id: 'answer-customer-emails',
|
|
89
|
+
summary: 'Checks the support inbox every 10 minutes, looks up each customer\'s order and your policies, and replies. Every reply waits for your approval until you trust it; refund requests are left for a person.',
|
|
90
|
+
trigger: { kind: 'schedule', description: 'every 10 minutes, for new emails', every: '10m' },
|
|
91
|
+
steps: [
|
|
92
|
+
{ title: 'Find new customer emails', kind: 'tool', tool: 'gmail_search', ability: GMAIL },
|
|
93
|
+
{ title: 'Read the whole conversation', kind: 'tool', tool: 'gmail_get_thread', ability: GMAIL },
|
|
94
|
+
{ title: 'Look up their order', kind: 'tool', tool: 'shopify_find_orders', ability: SHOPIFY, detail: 'then shopify_get_order for its details' },
|
|
95
|
+
{ title: 'Check our policies', kind: 'other', detail: 'from its support-policies skill' },
|
|
96
|
+
{ title: 'Reply in the thread', kind: 'approval', tool: 'gmail_send', ability: GMAIL },
|
|
97
|
+
{ title: 'Mark the email done', kind: 'tool', tool: 'gmail_modify_labels', ability: GMAIL },
|
|
98
|
+
],
|
|
99
|
+
abilities: [GMAIL, SHOPIFY],
|
|
100
|
+
rules: [
|
|
101
|
+
{ tool: 'gmail_search', decision: 'allow' },
|
|
102
|
+
{ tool: 'gmail_get_thread', decision: 'allow' },
|
|
103
|
+
{ tool: 'gmail_create_draft', decision: 'allow' },
|
|
104
|
+
{ tool: 'gmail_modify_labels', decision: 'allow' },
|
|
105
|
+
{ tool: 'gmail_send', decision: 'ask' },
|
|
106
|
+
{ tool: 'shopify_create_refund', decision: 'deny' },
|
|
107
|
+
],
|
|
108
|
+
keys: [...GMAIL_KEYS, ...SHOPIFY_KEYS],
|
|
109
|
+
needsApprovals: true,
|
|
110
|
+
skill: {
|
|
111
|
+
description: 'Handle new customer emails in the support inbox: look up each order and our policies, and reply in the thread.',
|
|
112
|
+
body: `1. Find new emails with gmail_search, query "is:unread in:inbox". Skip newsletters, receipts, automatic notifications and "no-reply" senders: mark those read and move on.
|
|
113
|
+
2. For each customer email, read the whole conversation with gmail_get_thread first.
|
|
114
|
+
3. Work out what they want: order status, a product question, a return or refund, a complaint, or something else.
|
|
115
|
+
4. Look up what you need before answering: the order with shopify_find_orders (by the order number they give, or their email) and shopify_get_order, and our policies in the support-policies skill (open it with Skill).
|
|
116
|
+
5. Reply with gmail_send, passing reply_to_message_id so it stays in the same thread: short, friendly, in the customer's language, using their name, answering the question first and saying what happens next.
|
|
117
|
+
6. Mark the email read with gmail_modify_labels (remove "UNREAD").
|
|
118
|
+
|
|
119
|
+
- A refund, return or replacement request: reply that it has been passed to the team, then star the email (add "STARRED") instead of acting on it.
|
|
120
|
+
- An angry customer, or one who mentions a lawyer, a chargeback or a safety problem: save a calm reply with gmail_create_draft and star the email for a person.
|
|
121
|
+
- If you can't find the order or the answer, say so and ask for the order number.`,
|
|
122
|
+
},
|
|
123
|
+
startMessage: 'Handle new customer emails in the support inbox.',
|
|
124
|
+
newAgent: {
|
|
125
|
+
name: 'customer-email-replies',
|
|
126
|
+
systemPrompt: `You answer customer emails for our business, from our support inbox.
|
|
127
|
+
|
|
128
|
+
Rules:
|
|
129
|
+
- Refunds, returns and replacements are handled by a person; never promise or make one yourself.
|
|
130
|
+
- Never guess an order detail, a date or a policy. Our policies are in your support-policies skill: answer from it and nothing else.
|
|
131
|
+
- If a customer is angry, or mentions a lawyer, a chargeback or a safety problem, don't promise anything; leave it for a person.
|
|
132
|
+
- Never share one customer's information with anyone else.`,
|
|
133
|
+
},
|
|
134
|
+
skills: [SUPPORT_POLICIES],
|
|
135
|
+
notes: [policiesNote('your policies', 'support-policies')],
|
|
136
|
+
}),
|
|
137
|
+
example('refund-approvals', 'Customer service', {
|
|
138
|
+
name: 'Refund approvals',
|
|
139
|
+
id: 'refund-approvals',
|
|
140
|
+
summary: 'Checks the inbox for refund and return requests, checks each against the order and your return policy, and proposes the refund and the reply. Both wait for your approval.',
|
|
141
|
+
trigger: { kind: 'schedule', description: 'every 10 minutes, for new refund requests', every: '10m' },
|
|
142
|
+
steps: [
|
|
143
|
+
{ title: 'Find refund and return requests', kind: 'tool', tool: 'gmail_search', ability: GMAIL },
|
|
144
|
+
{ title: 'Read each conversation', kind: 'tool', tool: 'gmail_get_thread', ability: GMAIL },
|
|
145
|
+
{ title: 'Look up the order', kind: 'tool', tool: 'shopify_find_orders', ability: SHOPIFY, detail: 'then shopify_get_order: what was paid, refunded and delivered' },
|
|
146
|
+
{ title: 'Check it against the return policy', kind: 'other', detail: 'from its return-policy skill' },
|
|
147
|
+
{ title: 'Work out the exact amount', kind: 'tool', tool: 'shopify_preview_refund', ability: SHOPIFY },
|
|
148
|
+
{ title: 'Issue the refund', kind: 'approval', tool: 'shopify_create_refund', ability: SHOPIFY },
|
|
149
|
+
{ title: 'Reply to the customer', kind: 'approval', tool: 'gmail_send', ability: GMAIL },
|
|
150
|
+
],
|
|
151
|
+
abilities: [GMAIL, SHOPIFY],
|
|
152
|
+
rules: [
|
|
153
|
+
{ tool: 'gmail_search', decision: 'allow' },
|
|
154
|
+
{ tool: 'gmail_get_thread', decision: 'allow' },
|
|
155
|
+
{ tool: 'gmail_create_draft', decision: 'allow' },
|
|
156
|
+
{ tool: 'gmail_modify_labels', decision: 'allow' },
|
|
157
|
+
{ tool: 'shopify_preview_refund', decision: 'allow' },
|
|
158
|
+
{ tool: 'shopify_create_refund', decision: 'ask' },
|
|
159
|
+
{ tool: 'gmail_send', decision: 'ask' },
|
|
160
|
+
],
|
|
161
|
+
keys: [...GMAIL_KEYS, ...SHOPIFY_KEYS],
|
|
162
|
+
needsApprovals: true,
|
|
163
|
+
skill: {
|
|
164
|
+
description: 'Handle new refund and return requests: check each against the order and our return policy, propose the refund, and reply.',
|
|
165
|
+
body: `1. Find requests with gmail_search, query "is:unread in:inbox (refund OR return OR damaged OR broken OR "money back")". Read each conversation with gmail_get_thread. If an email isn't really a refund or return request, leave it unread for the support team.
|
|
166
|
+
2. Find the order with shopify_find_orders (the order number they give, or their email) and read it with shopify_get_order: what was bought, when, how much, what's already been refunded, whether it was delivered.
|
|
167
|
+
3. Check the request against our return policy in the return-policy skill (open it with Skill): the return window, whether the item is eligible, and whether the reason is covered.
|
|
168
|
+
4. Decide what to propose: a full refund, a partial refund, refunding shipping, or no refund (and which part of the policy says so).
|
|
169
|
+
5. Work out the exact amount with shopify_preview_refund. Never state an amount you haven't previewed.
|
|
170
|
+
6. Issue it with shopify_create_refund, with a note giving the policy rule and the facts from the order. A person approves it first.
|
|
171
|
+
7. Once the refund is done, reply with gmail_send (reply_to_message_id set): what was refunded, to which payment method, and that it takes 5–10 business days to arrive. Then mark the email read with gmail_modify_labels.
|
|
172
|
+
|
|
173
|
+
- If the refund is denied, don't try again; save a reply with gmail_create_draft explaining what you'd suggest instead, and star the email for a person.
|
|
174
|
+
- If the order can't be found, ask the customer for the order number.`,
|
|
175
|
+
},
|
|
176
|
+
startMessage: 'Handle new refund and return requests in the support inbox.',
|
|
177
|
+
newAgent: {
|
|
178
|
+
name: 'refund-approvals',
|
|
179
|
+
systemPrompt: `You handle refund and return requests for our store.
|
|
180
|
+
|
|
181
|
+
Rules:
|
|
182
|
+
- Never refund more than was paid, and never refund an order twice: check its refunds first.
|
|
183
|
+
- Every amount you state comes from a refund preview, and every decision from your return-policy skill; never guess either.
|
|
184
|
+
- If a customer mentions a chargeback, a lawyer or a safety problem, don't act; leave it for a person.`,
|
|
185
|
+
},
|
|
186
|
+
skills: [RETURN_POLICY],
|
|
187
|
+
notes: [policiesNote('your return policy', 'return-policy')],
|
|
188
|
+
}),
|
|
189
|
+
example('incident-triage', 'Operations', {
|
|
190
|
+
name: 'Triage an incident',
|
|
191
|
+
id: 'triage-incident',
|
|
192
|
+
summary: 'Takes an alert or a bug report, works out what broke and how bad it is, finds the deploys and changes that likely caused it, and sends you a summary. Opening or commenting on issues waits for approval.',
|
|
193
|
+
trigger: { kind: 'on_request', description: 'when your alerting tool calls its webhook, or someone passes it an alert in Chat or Slack' },
|
|
194
|
+
parameters: { alert: { description: 'The alert, error or bug report', required: true } },
|
|
195
|
+
steps: [
|
|
196
|
+
{ title: 'Summarise it and rate the severity', kind: 'other' },
|
|
197
|
+
{ title: 'Find recent deploys and failed builds', kind: 'tool', tool: 'github_actions_list', detail: 'from the GitHub app' },
|
|
198
|
+
{ title: 'Find recently merged changes', kind: 'tool', tool: 'github_list_pull_requests', detail: 'and github_list_commits for detail' },
|
|
199
|
+
{ title: 'Check whether it\'s already known', kind: 'tool', tool: 'github_search_issues', detail: 'and github_issue_read for a match' },
|
|
200
|
+
{ title: 'Add findings to the issue, or open one', kind: 'approval', tool: 'github_issue_write', detail: 'or github_add_issue_comment' },
|
|
201
|
+
{ title: 'End with the summary', kind: 'other', detail: 'sent to you when the run finishes' },
|
|
202
|
+
],
|
|
203
|
+
abilities: [],
|
|
204
|
+
apps: [{ id: GITHUB_APP, title: 'GitHub', tools: ['list_commits', 'list_pull_requests', 'actions_list', 'search_issues', 'issue_read', 'issue_write', 'add_issue_comment'] }],
|
|
205
|
+
rules: [
|
|
206
|
+
{ tool: 'github_issue_write', decision: 'ask' },
|
|
207
|
+
{ tool: 'github_add_issue_comment', decision: 'ask' },
|
|
208
|
+
],
|
|
209
|
+
keys: [],
|
|
210
|
+
needsApprovals: true,
|
|
211
|
+
skill: {
|
|
212
|
+
description: 'Triage an alert, error or bug report: what broke, how bad it is, and the change that likely caused it.',
|
|
213
|
+
body: `Triage this alert: $alert
|
|
214
|
+
|
|
215
|
+
1. Summarise it in two sentences: what is failing and who is affected.
|
|
216
|
+
2. Rate the severity: SEV1 (customers can't use the product), SEV2 (a major feature is broken), SEV3 (degraded or a workaround exists), SEV4 (minor).
|
|
217
|
+
3. Find what changed shortly before it started: github_actions_list (method list_workflow_runs) for recent deploys and failed builds, github_list_pull_requests (state closed) for recently merged changes, and github_list_commits (narrowed with since and path) for detail. Match times carefully.
|
|
218
|
+
4. Check whether it's already known with github_search_issues; read a match with github_issue_read (method get).
|
|
219
|
+
5. If an issue already tracks it, add your findings with github_add_issue_comment; otherwise, for SEV1 or SEV2, open one with github_issue_write (method create). Both ask a person first.
|
|
220
|
+
6. End with the summary: what's broken, severity, the most likely cause with links, and one next step (who should look, what to check, or whether to roll back). Keep it short enough to read on a phone.`,
|
|
221
|
+
},
|
|
222
|
+
startMessage: 'Triage this alert: $alert',
|
|
223
|
+
newAgent: {
|
|
224
|
+
name: 'incident-triage',
|
|
225
|
+
systemPrompt: `You help the on-call engineer triage incidents.
|
|
226
|
+
|
|
227
|
+
Our repository on GitHub: [owner/name] (edit this). Pass its owner and repo to every GitHub tool.
|
|
228
|
+
|
|
229
|
+
Rules:
|
|
230
|
+
- Only read on your own; never roll anything back yourself.
|
|
231
|
+
- Say how sure you are. If the evidence is thin, say what would confirm it instead of guessing.`,
|
|
232
|
+
},
|
|
233
|
+
notes: ['Send it alerts: add a webhook to this workflow (Workflows tab) with "Message me the result when it finishes" on, and paste its URL into your alerting tool; or mention it in Slack, or paste an alert into Chat'],
|
|
234
|
+
}),
|
|
235
|
+
example('expense-approvals', 'Finance', {
|
|
236
|
+
name: 'Check expense claims',
|
|
237
|
+
id: 'check-expense-claims',
|
|
238
|
+
summary: 'Checks the claims inbox, reads each claim and its receipt, checks it against your expense policy, and sends the approver a one-line verdict. Replies to claimants wait for your approval.',
|
|
239
|
+
trigger: { kind: 'schedule', description: 'every 15 minutes, for new claims', every: '15m' },
|
|
240
|
+
steps: [
|
|
241
|
+
{ title: 'Find new claims', kind: 'tool', tool: 'gmail_search', ability: GMAIL },
|
|
242
|
+
{ title: 'Read each claim and its receipt', kind: 'tool', tool: 'gmail_get_thread', ability: GMAIL },
|
|
243
|
+
{ title: 'Check it against the expense policy', kind: 'other', detail: 'from its expense-policy skill' },
|
|
244
|
+
{ title: 'Email the approver the verdict', kind: 'approval', tool: 'gmail_send', ability: GMAIL },
|
|
245
|
+
{ title: 'Mark the claim done', kind: 'tool', tool: 'gmail_modify_labels', ability: GMAIL },
|
|
246
|
+
],
|
|
247
|
+
abilities: [GMAIL],
|
|
248
|
+
rules: [
|
|
249
|
+
{ tool: 'gmail_search', decision: 'allow' },
|
|
250
|
+
{ tool: 'gmail_get_thread', decision: 'allow' },
|
|
251
|
+
{ tool: 'gmail_create_draft', decision: 'allow' },
|
|
252
|
+
{ tool: 'gmail_modify_labels', decision: 'allow' },
|
|
253
|
+
{ tool: 'gmail_send', decision: 'ask' },
|
|
254
|
+
],
|
|
255
|
+
keys: [...GMAIL_KEYS],
|
|
256
|
+
needsApprovals: true,
|
|
257
|
+
skill: {
|
|
258
|
+
description: 'Check new expense claims in the inbox against our expense policy and send the approver a verdict.',
|
|
259
|
+
body: `1. Find new claims with gmail_search, query "is:unread in:inbox". Read each with gmail_get_thread.
|
|
260
|
+
2. Read the amount, currency, date, merchant, category and who submitted it, from the email and its receipt (the attachment names show whether a receipt is there).
|
|
261
|
+
3. Check it against our expense policy in the expense-policy skill (open it with Skill): the category is allowed, it's under the limit, the receipt matches the amount and date, and it isn't a duplicate of an earlier claim (search the inbox for the same merchant and amount).
|
|
262
|
+
4. Write a one-line verdict: "Within policy" or "Outside policy: <reason>", then the details you checked.
|
|
263
|
+
5. Email the verdict to the approver at the address in the EXPENSE_APPROVER_EMAIL setting with gmail_send, then mark the claim read with gmail_modify_labels.
|
|
264
|
+
|
|
265
|
+
- If the receipt is missing or unreadable, draft a reply to the claimant asking for it with gmail_create_draft; don't fill in values.
|
|
266
|
+
- If the policy doesn't cover the case, say which part is unclear rather than deciding.`,
|
|
267
|
+
},
|
|
268
|
+
startMessage: 'Check the new expense claims in the inbox.',
|
|
269
|
+
newAgent: {
|
|
270
|
+
name: 'expense-approvals',
|
|
271
|
+
systemPrompt: `You check expense claims against our expense policy, in your expense-policy skill.
|
|
272
|
+
|
|
273
|
+
Rules:
|
|
274
|
+
- Never approve or pay a claim yourself. A person always decides.
|
|
275
|
+
- Judge only by the policy and the receipt; never fill in a value you can't read.`,
|
|
276
|
+
},
|
|
277
|
+
skills: [EXPENSE_POLICY],
|
|
278
|
+
notes: [policiesNote('your expense policy', 'expense-policy'), 'Set EXPENSE_APPROVER_EMAIL (Environment tab) to the approver\'s address'],
|
|
279
|
+
}),
|
|
280
|
+
example('lead-follow-up', 'Sales', {
|
|
281
|
+
name: 'Lead follow-up',
|
|
282
|
+
id: 'lead-follow-up',
|
|
283
|
+
summary: 'Every hour, picks up new leads in HubSpot, researches each on the web, and sends a short, personal first email. Sending and updating the CRM wait for your approval.',
|
|
284
|
+
trigger: { kind: 'schedule', description: 'every hour, for new leads', every: '1h' },
|
|
285
|
+
steps: [
|
|
286
|
+
{ title: 'Find new leads', kind: 'tool', tool: 'hubspot_find_contacts', ability: HUBSPOT },
|
|
287
|
+
{ title: 'Skip anyone already being handled', kind: 'tool', tool: 'hubspot_get_contact', ability: HUBSPOT },
|
|
288
|
+
{ title: 'Research them on the web', kind: 'tool', tool: 'firecrawl_search', detail: 'and firecrawl_scrape, from the Firecrawl app' },
|
|
289
|
+
{ title: 'Send a short first email', kind: 'approval', tool: 'gmail_send', ability: GMAIL },
|
|
290
|
+
{ title: 'Log it in HubSpot', kind: 'tool', tool: 'hubspot_add_note', ability: HUBSPOT },
|
|
291
|
+
{ title: 'Update the lead\'s status', kind: 'approval', tool: 'hubspot_update_contact', ability: HUBSPOT },
|
|
292
|
+
],
|
|
293
|
+
abilities: [HUBSPOT, GMAIL],
|
|
294
|
+
apps: [{ id: FIRECRAWL_APP, title: 'Firecrawl', tools: ['firecrawl_search', 'firecrawl_scrape'] }],
|
|
295
|
+
rules: [
|
|
296
|
+
{ tool: 'firecrawl_search', decision: 'allow' },
|
|
297
|
+
{ tool: 'firecrawl_scrape', decision: 'allow' },
|
|
298
|
+
{ tool: 'hubspot_add_note', decision: 'allow' },
|
|
299
|
+
{ tool: 'hubspot_update_contact', decision: 'ask' },
|
|
300
|
+
{ tool: 'hubspot_create_contact', decision: 'deny' },
|
|
301
|
+
{ tool: 'gmail_send', decision: 'ask' },
|
|
302
|
+
],
|
|
303
|
+
keys: ['HUBSPOT_ACCESS_TOKEN', ...GMAIL_KEYS],
|
|
304
|
+
needsApprovals: true,
|
|
305
|
+
skill: {
|
|
306
|
+
description: 'Follow up with new leads in HubSpot: research each one and send a short, personal first email.',
|
|
307
|
+
body: `1. Find new leads with hubspot_find_contacts: lead_status "NEW", created_in_last_days 2.
|
|
308
|
+
2. For each, read hubspot_get_contact. Skip anyone with an owner, recent notes, or a last-contacted date: someone is already on it.
|
|
309
|
+
3. Research them with firecrawl_search and firecrawl_scrape: what their company does, its size, and anything recent that matters (funding, hiring, a launch). Note your sources.
|
|
310
|
+
4. Work out what they most likely need from us, in one sentence.
|
|
311
|
+
5. Send a short first email (under 120 words) with gmail_send: a specific opening based on your research, one sentence on how we help with that need, and one easy question to reply to. A person approves it first.
|
|
312
|
+
6. Log what you sent and found with hubspot_add_note, and set the lead to "ATTEMPTED_TO_CONTACT" with hubspot_update_contact.
|
|
313
|
+
|
|
314
|
+
- If the lead looks like a competitor, a student or spam, add a note saying so and don't email them.`,
|
|
315
|
+
},
|
|
316
|
+
startMessage: 'Follow up with the new leads in HubSpot.',
|
|
317
|
+
newAgent: {
|
|
318
|
+
name: 'lead-follow-up',
|
|
319
|
+
systemPrompt: `You follow up with new leads for our business.
|
|
320
|
+
|
|
321
|
+
What we sell (edit this):
|
|
322
|
+
- [What your product does, in one sentence]
|
|
323
|
+
- [Who it is for]
|
|
324
|
+
- [The one result customers care about most]
|
|
325
|
+
|
|
326
|
+
Rules:
|
|
327
|
+
- Never invent facts about a lead. If research finds nothing reliable, write a simple, honest email instead.
|
|
328
|
+
- No pressure tactics, fake urgency or discounts you weren't told about.`,
|
|
329
|
+
},
|
|
330
|
+
notes: ['Edit "What we sell" at the top of its instructions (Overview tab)'],
|
|
331
|
+
}),
|
|
332
|
+
example('inbox-to-tasks', 'Operations', {
|
|
333
|
+
name: 'Inbox to tasks',
|
|
334
|
+
id: 'inbox-to-tasks',
|
|
335
|
+
summary: 'Checks a shared inbox every 10 minutes and turns each request into a task (a GitHub issue) with an owner and a due date, then tells the sender who is handling it.',
|
|
336
|
+
trigger: { kind: 'schedule', description: 'every 10 minutes, for new emails', every: '10m' },
|
|
337
|
+
steps: [
|
|
338
|
+
{ title: 'Find new emails', kind: 'tool', tool: 'gmail_search', ability: GMAIL },
|
|
339
|
+
{ title: 'Read each one', kind: 'tool', tool: 'gmail_get_thread', ability: GMAIL },
|
|
340
|
+
{ title: 'Check for an existing task', kind: 'tool', tool: 'github_search_issues', detail: 'from the GitHub app' },
|
|
341
|
+
{ title: 'Open the task, or add to it', kind: 'tool', tool: 'github_issue_write', detail: 'or github_add_issue_comment' },
|
|
342
|
+
{ title: 'Tell the sender who is handling it', kind: 'approval', tool: 'gmail_send', ability: GMAIL },
|
|
343
|
+
{ title: 'Mark the email done', kind: 'tool', tool: 'gmail_modify_labels', ability: GMAIL },
|
|
344
|
+
],
|
|
345
|
+
abilities: [GMAIL],
|
|
346
|
+
apps: [{ id: GITHUB_APP, title: 'GitHub', tools: ['search_issues', 'issue_write', 'add_issue_comment'] }],
|
|
347
|
+
rules: [
|
|
348
|
+
{ tool: 'gmail_search', decision: 'allow' },
|
|
349
|
+
{ tool: 'gmail_get_thread', decision: 'allow' },
|
|
350
|
+
{ tool: 'gmail_modify_labels', decision: 'allow' },
|
|
351
|
+
{ tool: 'github_issue_write', decision: 'allow' },
|
|
352
|
+
{ tool: 'github_add_issue_comment', decision: 'allow' },
|
|
353
|
+
{ tool: 'gmail_send', decision: 'ask' },
|
|
354
|
+
],
|
|
355
|
+
keys: [...GMAIL_KEYS],
|
|
356
|
+
needsApprovals: true,
|
|
357
|
+
skill: {
|
|
358
|
+
description: 'Turn new emails in the shared inbox into tasks (GitHub issues) with an owner and a due date, and tell each sender.',
|
|
359
|
+
body: `1. Find new emails with gmail_search, query "is:unread in:inbox". Read each with gmail_get_thread.
|
|
360
|
+
2. Decide whether it's a request someone needs to act on. Newsletters, receipts, out-of-office replies and spam are not: mark them read and move on.
|
|
361
|
+
3. Check whether a task already exists with github_search_issues (the sender and subject). If so, add the new details with github_add_issue_comment instead.
|
|
362
|
+
4. Otherwise open one with github_issue_write (method create): a short title starting with a verb, a two-line summary, the sender, the due date (the date they asked for, or 2 working days if they didn't say), and the owner from "Who handles what" as the assignee.
|
|
363
|
+
5. Reply to the sender with gmail_send (reply_to_message_id set): who is handling it and by when. A person approves it first.
|
|
364
|
+
6. Mark the email read with gmail_modify_labels.
|
|
365
|
+
|
|
366
|
+
- If an email holds several requests, make one task for each.`,
|
|
367
|
+
},
|
|
368
|
+
startMessage: 'Turn the new emails in the shared inbox into tasks.',
|
|
369
|
+
newAgent: {
|
|
370
|
+
name: 'inbox-to-tasks',
|
|
371
|
+
systemPrompt: `You turn requests in our shared inbox into tasks, kept as GitHub issues.
|
|
372
|
+
|
|
373
|
+
Tasks live in this GitHub repository: [owner/name] (edit this). Pass its owner and repo to every GitHub tool.
|
|
374
|
+
|
|
375
|
+
Who handles what (edit this; GitHub usernames):
|
|
376
|
+
- Billing and invoices: [username]
|
|
377
|
+
- Technical problems: [username]
|
|
378
|
+
- Everything else: [username]
|
|
379
|
+
|
|
380
|
+
Rules:
|
|
381
|
+
- Never promise a fix or a refund to a sender; only say who is handling it and by when.`,
|
|
382
|
+
},
|
|
383
|
+
notes: ['Edit "Who handles what" and the repository at the top of its instructions (Overview tab)'],
|
|
384
|
+
}),
|
|
385
|
+
example('weekly-research-digest', 'Research', {
|
|
386
|
+
name: 'Weekly research digest',
|
|
387
|
+
id: 'weekly-research-digest',
|
|
388
|
+
summary: 'Every Monday morning, searches the web for news on the topics you choose and sends you a short digest with links.',
|
|
389
|
+
trigger: { kind: 'schedule', description: 'Mondays at 09:00', every: '7d', at: '09:00', weekday: 1 },
|
|
390
|
+
steps: [
|
|
391
|
+
{ title: 'Search for this week\'s news on each topic', kind: 'tool', tool: 'firecrawl_search', detail: 'from the Firecrawl app' },
|
|
392
|
+
{ title: 'Read the most relevant pages', kind: 'tool', tool: 'firecrawl_scrape' },
|
|
393
|
+
{ title: 'Write the digest', kind: 'other', detail: 'sent to you when the run finishes' },
|
|
394
|
+
],
|
|
395
|
+
abilities: [],
|
|
396
|
+
apps: [{ id: FIRECRAWL_APP, title: 'Firecrawl', tools: ['firecrawl_search', 'firecrawl_scrape'] }],
|
|
397
|
+
rules: [
|
|
398
|
+
{ tool: 'firecrawl_search', decision: 'allow' },
|
|
399
|
+
{ tool: 'firecrawl_scrape', decision: 'allow' },
|
|
400
|
+
],
|
|
401
|
+
keys: [],
|
|
402
|
+
needsApprovals: false,
|
|
403
|
+
notify: { on: true, reason: 'The digest is the result, and no step posts it.' },
|
|
404
|
+
skill: {
|
|
405
|
+
description: 'Write this week\'s research digest on the topics to follow.',
|
|
406
|
+
body: `1. Search the web with firecrawl_search for what happened in the last 7 days on each topic you follow.
|
|
407
|
+
2. Open the most relevant pages with firecrawl_scrape and read them; don't rely on search snippets.
|
|
408
|
+
3. Write the digest: for each topic, up to three items, each with a one-sentence summary, why it matters to us, and the link.
|
|
409
|
+
4. End with "Worth a closer look": the single most important item, and why.
|
|
410
|
+
5. Answer with the digest alone, nothing before or after it: it's sent as it is.`,
|
|
411
|
+
},
|
|
412
|
+
startMessage: 'Write this week\'s research digest.',
|
|
413
|
+
newAgent: {
|
|
414
|
+
name: 'weekly-research-digest',
|
|
415
|
+
systemPrompt: `You follow news for our team and write research digests.
|
|
416
|
+
|
|
417
|
+
Topics to follow (edit this list):
|
|
418
|
+
- [Your industry or market]
|
|
419
|
+
- [Competitors, by name]
|
|
420
|
+
- [Technologies or regulations you care about]
|
|
421
|
+
|
|
422
|
+
Rules:
|
|
423
|
+
- Only include things published in the period you're asked about, and give each item's date.
|
|
424
|
+
- Prefer original sources over aggregators. Never invent a link.
|
|
425
|
+
- If a topic had no real news, say so in one line.`,
|
|
426
|
+
},
|
|
427
|
+
notes: ['Edit "Topics to follow" at the top of its instructions (Overview tab)'],
|
|
428
|
+
}),
|
|
429
|
+
];
|
|
430
|
+
export function workflowExample(id) {
|
|
431
|
+
return WORKFLOW_EXAMPLES.find((e) => e.id === id);
|
|
432
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { ToolSchema } from '#core/agent-config.js';
|
|
|
2
2
|
import type { WorkflowParameter } from '#core/workflows.js';
|
|
3
3
|
import type { TriggerSender } from '#core/workflow-triggers.js';
|
|
4
4
|
import type { ModelCall } from '#core/run-agent.js';
|
|
5
|
-
import type { StarterTemplate, TemplateApp } from './templates.js';
|
|
5
|
+
import type { StarterTemplate, TemplateApp, TemplateSkill } from './templates.js';
|
|
6
6
|
export type Decision = 'allow' | 'ask' | 'deny';
|
|
7
7
|
export interface WorkflowStep {
|
|
8
8
|
title: string;
|
|
@@ -110,6 +110,14 @@ export interface WorkflowPlan {
|
|
|
110
110
|
/** Connected apps it adds to the agent, with just the tools its steps
|
|
111
111
|
* use (filled in on Create, from mcpServers). */
|
|
112
112
|
apps?: TemplateApp[];
|
|
113
|
+
/** Skills it adds besides its own, such as a policy the person fills
|
|
114
|
+
* in (the examples, core/system-agents/workflow-examples.ts). One the
|
|
115
|
+
* agent already has is kept as it is. */
|
|
116
|
+
skills?: TemplateSkill[];
|
|
117
|
+
/** Non-secret settings it fills in; one the agent already has is kept. */
|
|
118
|
+
env?: Record<string, string>;
|
|
119
|
+
/** Setup steps for its checklist, in plain words. */
|
|
120
|
+
notes?: string[];
|
|
113
121
|
}
|
|
114
122
|
export interface PlannedWebhook {
|
|
115
123
|
/** Whose events: the sender whose signature it checks. "custom" is the
|
|
@@ -151,7 +159,7 @@ export interface WorkflowPlannerTools {
|
|
|
151
159
|
/** A server's tools, when it can be listed without a key (or with one already set). */
|
|
152
160
|
mcpServerTools?(id: string): Promise<unknown>;
|
|
153
161
|
}
|
|
154
|
-
export declare const WORKFLOW_PLANNER_PROMPT = "You turn a workflow a person describes into a plan for a LoopEngine agent.\n\nLoopEngine agents call tools. Tools come from abilities (installable packages: public ones on npm, or the workspace's own private ones) or are built in. Each tool has a permission: allow (runs on its own), ask (waits for a person to approve, edit or deny it) or deny. When nobody is in Chat, questions and approvals go to the agent's messaging app (Slack, Telegram, Lark or email) and the run waits, for days if needed, then carries on.\n\nHow to plan:\n1. Look before you plan: list_agents (what exists, and what each agent's tools and rules are), list_workspace_abilities, search_public_abilities for each kind of step, ability_details for any ability you might use (its exact tool names and the settings it needs), and list_connections.\n2. Split the workflow into steps, in order. For each step:\n - something to fetch or do: kind \"tool\", with the exact tool name and where it comes from. Pick the source in this order:\n a. A tool the target agent already has (from list_agents): use it, nothing to install.\n b. An ability: a workspace ability (list_workspace_abilities) or a public one (search_public_abilities, e.g. \"firecrawl\" or \"web search\" for searching and reading the web). Set \"ability\" to its install spec (the npm name, or a workspace ability's spec) and use the exact tool name from ability_details.\n c. Only when no ability can do it, and search_mcp_servers is available: an app's MCP server. Search for the app (e.g. \"linear\", \"stripe\"), pick the app's own server (official), else a saved or verified one, then call mcp_server_tools. Set \"mcp\" to the server's id, and \"tool\" to its exact tool name when mcp_server_tools lists them (leave \"tool\" empty when it needs a key first, and say in \"detail\" what the step does). It's set up as a private ability when the workflow is created, which picks its tools and their permissions, so add no rules for MCP tools.\n - the person chooses or answers (\"ask me which\", \"let me pick\"): kind \"ask\", tool \"system_ask_user\" (built in, no ability). Say what options are offered. It reaches them through the agent's messaging app by itself, so don't add a separate step to post to Slack or another chat first, and don't install a chat ability just to ask.\n - the person reviews or approves before something happens (\"let me review it\", \"before it's sent\"): kind \"approval\", on the tool that does that thing; its rule becomes \"ask\".\n - the agent's own work (writing, summarising, choosing): kind \"other\".\n - nothing found can do it: set \"missing\" to what's needed, and add it to gaps.\n3. Rules: every tool the workflow uses gets one. Reading and searching: allow. Anything a person should see first (sending, publishing, paying, creating something others see): ask. Never set deny unless asked.\n4. Skill: the steps in order, as numbered instructions the agent follows, naming the tools and saying exactly when to stop and ask. Write it so the agent keeps going after each answer or decision.\n5. Trigger: how a run starts. startMessage is what the agent receives when it starts.\n - schedule: a time or interval (\"every Monday at 9\" is every \"7d\", at \"09:00\", weekday 1).\n - webhook: when something happens in another service that can call a URL (\"when a Stripe dispute comes in\", \"when a GitHub issue is opened\", \"when a Shopify order is paid\", \"when our app sends a new signup\"). Fill in \"webhook\" (below). Prefer it over polling on a schedule whenever the service sends webhooks.\n - on_request: started by a person, from Chat, a message, the Run button or an app's API call.\n A workflow that runs on a schedule and on an event as well: trigger schedule, and fill in \"webhook\" too.\n webhook: sender (\"stripe\", \"github\", \"shopify\", \"standard\" for Standard Webhooks senders such as Clerk, Resend or Svix, else \"custom\" for the person's own app or any other service); a short name (\"Stripe: new disputes\"); setup: one sentence on what to pick in the sender's webhook settings; filter: which events start a run; mapping: each parameter from a dot path in the payload. What senders send:\n - Stripe: one endpoint can get many event types, so filter path \"type\", values the event names (charge.dispute.created, checkout.session.completed, invoice.payment_failed, customer.subscription.deleted). The object is at data.object: data.object.id, data.object.customer, data.object.amount, data.object.charge.\n - GitHub: the event type is chosen in GitHub's settings (Issues, Pull requests, Pushes), so say it in setup; filter path \"action\", values such as opened, closed, created. Fields: issue.number, issue.title, pull_request.number, repository.full_name, sender.login.\n - Shopify: one webhook per topic (orders/paid, orders/create, customers/create), chosen in Shopify, so say it in setup and use no filter. The body is the object itself: id, name (the order number, like #1001), email, total_price, financial_status.\n - standard: filter path \"type\" (user.created, email.bounced); fields under data (data.id, data.email_addresses).\n - custom: whatever the person's app sends; say in setup what to post (JSON with these fields) and use the paths you described. Set \"header\" when the person names the signature header.\n Every required parameter should come from the payload through mapping; parameters are the only way event details reach the run.\n6. Notify (scheduled and webhook workflows): whether each run's result is sent to the person through the agent's messaging app. true when the result is for them and no step already delivers it (\"tell me\", \"send me\", \"let me know\", a summary or findings that no step posts or emails). false when a step already sends it (posting to a channel, emailing a report): it would arrive twice. false when the work itself is the outcome (answering emails, filing tasks), where a message every run is noise; questions and approvals reach them either way. Say why in one short sentence.\n7. Parameters: if the job is about something that changes from run to run (an order, a ticket, an alert, a customer, a date range), declare it as a parameter: a short snake_case name, a one-line description, and required when a run makes no sense without it. Write $name wherever its value goes, in startMessage and in the skill (\"Check the refund request for order $order_number.\"). Apps and people fill them in when they start a run. A scheduled job that finds its own work (\"check the inbox every 10 minutes\", \"last week's sales\") needs none. Don't make fixed settings, such as the repository or the channel, parameters: write them into the skill.\n8. Target: an existing agent only when it already has (or can install) the tools, none of the workflow's rules would change a decision it already has, and the same people would approve. Otherwise a new agent, with a short lowercase-hyphenated name and
|
|
162
|
+
export declare const WORKFLOW_PLANNER_PROMPT = "You turn a workflow a person describes into a plan for a LoopEngine agent.\n\nLoopEngine agents call tools. Tools come from abilities (installable packages: public ones on npm, or the workspace's own private ones) or are built in. Each tool has a permission: allow (runs on its own), ask (waits for a person to approve, edit or deny it) or deny. When nobody is in Chat, questions and approvals go to the agent's messaging app (Slack, Telegram, Lark or email) and the run waits, for days if needed, then carries on.\n\nHow to plan:\n1. Look before you plan: list_agents (what exists, and what each agent's tools and rules are), list_workspace_abilities, search_public_abilities for each kind of step, ability_details for any ability you might use (its exact tool names and the settings it needs), and list_connections.\n2. Split the workflow into steps, in order. For each step:\n - something to fetch or do: kind \"tool\", with the exact tool name and where it comes from. Pick the source in this order:\n a. A tool the target agent already has (from list_agents): use it, nothing to install.\n b. An ability: a workspace ability (list_workspace_abilities) or a public one (search_public_abilities, e.g. \"firecrawl\" or \"web search\" for searching and reading the web). Set \"ability\" to its install spec (the npm name, or a workspace ability's spec) and use the exact tool name from ability_details.\n c. Only when no ability can do it, and search_mcp_servers is available: an app's MCP server. Search for the app (e.g. \"linear\", \"stripe\"), pick the app's own server (official), else a saved or verified one, then call mcp_server_tools. Set \"mcp\" to the server's id, and \"tool\" to its exact tool name when mcp_server_tools lists them (leave \"tool\" empty when it needs a key first, and say in \"detail\" what the step does). It's set up as a private ability when the workflow is created, which picks its tools and their permissions, so add no rules for MCP tools.\n - the person chooses or answers (\"ask me which\", \"let me pick\"): kind \"ask\", tool \"system_ask_user\" (built in, no ability). Say what options are offered. It reaches them through the agent's messaging app by itself, so don't add a separate step to post to Slack or another chat first, and don't install a chat ability just to ask.\n - the person reviews or approves before something happens (\"let me review it\", \"before it's sent\"): kind \"approval\", on the tool that does that thing; its rule becomes \"ask\".\n - the agent's own work (writing, summarising, choosing): kind \"other\".\n - nothing found can do it: set \"missing\" to what's needed, and add it to gaps.\n3. Rules: every tool the workflow uses gets one. Reading and searching: allow. Anything a person should see first (sending, publishing, paying, creating something others see): ask. Never set deny unless asked.\n4. Skill: the steps in order, as numbered instructions the agent follows, naming the tools and saying exactly when to stop and ask. Write it so the agent keeps going after each answer or decision.\n5. Trigger: how a run starts. startMessage is what the agent receives when it starts.\n - schedule: a time or interval (\"every Monday at 9\" is every \"7d\", at \"09:00\", weekday 1).\n - webhook: when something happens in another service that can call a URL (\"when a Stripe dispute comes in\", \"when a GitHub issue is opened\", \"when a Shopify order is paid\", \"when our app sends a new signup\"). Fill in \"webhook\" (below). Prefer it over polling on a schedule whenever the service sends webhooks.\n - on_request: started by a person, from Chat, a message, the Run button or an app's API call.\n A workflow that runs on a schedule and on an event as well: trigger schedule, and fill in \"webhook\" too.\n webhook: sender (\"stripe\", \"github\", \"shopify\", \"standard\" for Standard Webhooks senders such as Clerk, Resend or Svix, else \"custom\" for the person's own app or any other service); a short name (\"Stripe: new disputes\"); setup: one sentence on what to pick in the sender's webhook settings; filter: which events start a run; mapping: each parameter from a dot path in the payload. What senders send:\n - Stripe: one endpoint can get many event types, so filter path \"type\", values the event names (charge.dispute.created, checkout.session.completed, invoice.payment_failed, customer.subscription.deleted). The object is at data.object: data.object.id, data.object.customer, data.object.amount, data.object.charge.\n - GitHub: the event type is chosen in GitHub's settings (Issues, Pull requests, Pushes), so say it in setup; filter path \"action\", values such as opened, closed, created. Fields: issue.number, issue.title, pull_request.number, repository.full_name, sender.login.\n - Shopify: one webhook per topic (orders/paid, orders/create, customers/create), chosen in Shopify, so say it in setup and use no filter. The body is the object itself: id, name (the order number, like #1001), email, total_price, financial_status.\n - standard: filter path \"type\" (user.created, email.bounced); fields under data (data.id, data.email_addresses).\n - custom: whatever the person's app sends; say in setup what to post (JSON with these fields) and use the paths you described. Set \"header\" when the person names the signature header.\n Every required parameter should come from the payload through mapping; parameters are the only way event details reach the run.\n6. Notify (scheduled and webhook workflows): whether each run's result is sent to the person through the agent's messaging app. true when the result is for them and no step already delivers it (\"tell me\", \"send me\", \"let me know\", a summary or findings that no step posts or emails). false when a step already sends it (posting to a channel, emailing a report): it would arrive twice. false when the work itself is the outcome (answering emails, filing tasks), where a message every run is noise; questions and approvals reach them either way. Say why in one short sentence.\n7. Parameters: if the job is about something that changes from run to run (an order, a ticket, an alert, a customer, a date range), declare it as a parameter: a short snake_case name, a one-line description, and required when a run makes no sense without it. Write $name wherever its value goes, in startMessage and in the skill (\"Check the refund request for order $order_number.\"). Apps and people fill them in when they start a run. A scheduled job that finds its own work (\"check the inbox every 10 minutes\", \"last week's sales\") needs none. Don't make fixed settings, such as the repository or the channel, parameters: write them into the skill.\n8. Target: an existing agent only when it already has (or can install) the tools, none of the workflow's rules would change a decision it already has, and the same people would approve. Otherwise a new agent, with a short lowercase-hyphenated name and its system prompt (rule 9). Give the reason either way.\n9. newAgent.systemPrompt: the new agent's standing instructions, read on every run of every workflow it will ever have, so write what's true whatever it's doing: who it works for and its role, in one or two sentences (\"You help the on-call engineer triage incidents.\"); facts every job needs that the person must fill in, as \"[...] (edit this)\" (\"Our repository on GitHub: [owner/name] (edit this).\"); and rules that always hold (\"Never roll anything back yourself.\"). Don't repeat the steps, the tools or the schedule: they're the skill's. Under 15 lines.\n10. keys: the setting names its abilities need that list_connections or the agents don't show as set (from ability_details settings). Not for MCP servers: their keys are asked for on the plan.\n\nThen call save_plan once, with the whole plan. Don't ask the person questions: if something is unclear, plan the most reasonable version and note the assumption in gaps.";
|
|
155
163
|
export declare function workflowPlannerTools(withMcp?: boolean): ToolSchema[];
|
|
156
164
|
/** lowercase-hyphenated, 2–40 characters. */
|
|
157
165
|
export declare function slugify(value: string, fallback?: string): string;
|
|
@@ -30,8 +30,9 @@ How to plan:
|
|
|
30
30
|
Every required parameter should come from the payload through mapping; parameters are the only way event details reach the run.
|
|
31
31
|
6. Notify (scheduled and webhook workflows): whether each run's result is sent to the person through the agent's messaging app. true when the result is for them and no step already delivers it ("tell me", "send me", "let me know", a summary or findings that no step posts or emails). false when a step already sends it (posting to a channel, emailing a report): it would arrive twice. false when the work itself is the outcome (answering emails, filing tasks), where a message every run is noise; questions and approvals reach them either way. Say why in one short sentence.
|
|
32
32
|
7. Parameters: if the job is about something that changes from run to run (an order, a ticket, an alert, a customer, a date range), declare it as a parameter: a short snake_case name, a one-line description, and required when a run makes no sense without it. Write $name wherever its value goes, in startMessage and in the skill ("Check the refund request for order $order_number."). Apps and people fill them in when they start a run. A scheduled job that finds its own work ("check the inbox every 10 minutes", "last week's sales") needs none. Don't make fixed settings, such as the repository or the channel, parameters: write them into the skill.
|
|
33
|
-
8. Target: an existing agent only when it already has (or can install) the tools, none of the workflow's rules would change a decision it already has, and the same people would approve. Otherwise a new agent, with a short lowercase-hyphenated name and
|
|
34
|
-
9.
|
|
33
|
+
8. Target: an existing agent only when it already has (or can install) the tools, none of the workflow's rules would change a decision it already has, and the same people would approve. Otherwise a new agent, with a short lowercase-hyphenated name and its system prompt (rule 9). Give the reason either way.
|
|
34
|
+
9. newAgent.systemPrompt: the new agent's standing instructions, read on every run of every workflow it will ever have, so write what's true whatever it's doing: who it works for and its role, in one or two sentences ("You help the on-call engineer triage incidents."); facts every job needs that the person must fill in, as "[...] (edit this)" ("Our repository on GitHub: [owner/name] (edit this)."); and rules that always hold ("Never roll anything back yourself."). Don't repeat the steps, the tools or the schedule: they're the skill's. Under 15 lines.
|
|
35
|
+
10. keys: the setting names its abilities need that list_connections or the agents don't show as set (from ability_details settings). Not for MCP servers: their keys are asked for on the plan.
|
|
35
36
|
|
|
36
37
|
Then call save_plan once, with the whole plan. Don't ask the person questions: if something is unclear, plan the most reasonable version and note the assumption in gaps.`;
|
|
37
38
|
export function workflowPlannerTools(withMcp = false) {
|
|
@@ -410,7 +411,9 @@ export function workflowTemplate(plan) {
|
|
|
410
411
|
abilities: plan.abilities,
|
|
411
412
|
...(plan.apps?.length ? { apps: plan.apps } : {}),
|
|
412
413
|
keys: plan.keys,
|
|
413
|
-
skills: [{ id: plan.id, description: plan.skill.description, body: plan.skill.body }],
|
|
414
|
+
skills: [{ id: plan.id, description: plan.skill.description, body: plan.skill.body }, ...(plan.skills ?? []).filter((s) => s.id !== plan.id)],
|
|
415
|
+
...(plan.env && Object.keys(plan.env).length ? { env: plan.env } : {}),
|
|
416
|
+
...(plan.notes?.length ? { notes: plan.notes } : {}),
|
|
414
417
|
// Named after the tool: readable, and made unique if taken.
|
|
415
418
|
rules: plan.rules.map((r) => ({ name: r.tool, tool: r.tool, decision: r.decision })),
|
|
416
419
|
...(plan.trigger.kind === 'schedule' && plan.trigger.every
|
package/dist/index.d.ts
CHANGED
|
@@ -87,6 +87,7 @@ export { STARTER_TEMPLATES, starterTemplate, templateScheduleStart, type Starter
|
|
|
87
87
|
export { workflowPageHtml } from './web/workflow-page.js';
|
|
88
88
|
export { WorkflowError, checkWorkflowForAgent, createWorkflow, forgetWorkflow, migrateAgentWorkflows, getPlanJob, listAgentWorkflows, planWorkflow, resolveConnections, prepareWorkflowRun, updateWorkflow, startPlanJob, type AgentWorkflowView, type CreatedWorkflow, type PlanJob, type PlannedWorkflow, type WorkflowAgentInfo, type WorkflowCheck, type WorkflowConflict, type WorkflowCreateDeps, type WorkflowMcpDeps, type WorkflowMcpCreateDeps, resolveMcpServers, prepareMcpAbilities, } from './web/workflow-admin.js';
|
|
89
89
|
export { WORKFLOW_PLANNER_PROMPT, planParameters, planWorkflowWithModel, validateWorkflowPlan, workflowTemplate, type WorkflowPlan, type WorkflowPlannerTools, type WorkflowStep } from './core/system-agents/workflow-planner.js';
|
|
90
|
+
export { WORKFLOW_EXAMPLES, workflowExample, type WorkflowExample } from './core/system-agents/workflow-examples.js';
|
|
90
91
|
export { createTrigger, updateTrigger, rotateTrigger, listTriggers, findTriggerByToken, publicTrigger, checkTriggerSettings, verifySenderSignature, parsePayload, valueAt, deliveryId, passesFilter, paramsFromPayload, recordDelivery as recordTriggerDelivery, FileTriggerStore, getTriggerStore, setTriggerStore, TriggerError, type WorkflowTrigger, type PublicTrigger, type TriggerSettings, type TriggerVerify, type TriggerFilter, type TriggerSender, type SignatureAlgorithm, type TriggerDelivery, type TriggerOutcome, type TriggerStore } from './core/workflow-triggers.js';
|
|
91
92
|
export { checkCallbackUrl, callbackSecret, signCallback, deliverCallback, runCallbackPayload, isPrivateAddress, CallbackUrlError, type CallbackDelivery, type RunCallbackEvent } from './core/run-callbacks.js';
|
|
92
93
|
export { WorkflowRunner, FileRunStore, getRunStore, setRunStore, listRuns, getRun, isUnderWay, RunError, type WorkflowRun, type WorkflowRunStatus, type RunStore, type ExecuteRun, type RunOutcome, type StartRunInput } from './core/workflow-runs.js';
|
package/dist/index.js
CHANGED
|
@@ -80,6 +80,7 @@ export { STARTER_TEMPLATES, starterTemplate, templateScheduleStart, } from './co
|
|
|
80
80
|
export { workflowPageHtml } from './web/workflow-page.js';
|
|
81
81
|
export { WorkflowError, checkWorkflowForAgent, createWorkflow, forgetWorkflow, migrateAgentWorkflows, getPlanJob, listAgentWorkflows, planWorkflow, resolveConnections, prepareWorkflowRun, updateWorkflow, startPlanJob, resolveMcpServers, prepareMcpAbilities, } from './web/workflow-admin.js';
|
|
82
82
|
export { WORKFLOW_PLANNER_PROMPT, planParameters, planWorkflowWithModel, validateWorkflowPlan, workflowTemplate } from './core/system-agents/workflow-planner.js';
|
|
83
|
+
export { WORKFLOW_EXAMPLES, workflowExample } from './core/system-agents/workflow-examples.js';
|
|
83
84
|
export { createTrigger, updateTrigger, rotateTrigger, listTriggers, findTriggerByToken, publicTrigger, checkTriggerSettings, verifySenderSignature, parsePayload, valueAt, deliveryId, passesFilter, paramsFromPayload, recordDelivery as recordTriggerDelivery, FileTriggerStore, getTriggerStore, setTriggerStore, TriggerError } from './core/workflow-triggers.js';
|
|
84
85
|
export { checkCallbackUrl, callbackSecret, signCallback, deliverCallback, runCallbackPayload, isPrivateAddress, CallbackUrlError } from './core/run-callbacks.js';
|
|
85
86
|
export { WorkflowRunner, FileRunStore, getRunStore, setRunStore, listRuns, getRun, isUnderWay, RunError } from './core/workflow-runs.js';
|
|
@@ -50,13 +50,13 @@ ${consoleSidebarHtml}
|
|
|
50
50
|
<div class="le-title"><button type="button" class="le-menu-btn" data-le-menu aria-label="Open navigation">☰</button>
|
|
51
51
|
<div><h1>Agents</h1><p>Every agent under agents/ in this project. Open one to chat with it or change its tools, permissions and settings.</p></div>
|
|
52
52
|
</div>
|
|
53
|
-
<div style="display:flex;gap:8px;flex-wrap:wrap"><a href="/
|
|
53
|
+
<div style="display:flex;gap:8px;flex-wrap:wrap"><a href="/workflows/new#examples" style="padding:6px 12px;border:1px solid var(--line);border-radius:6px;text-decoration:none;color:var(--ink);font-size:13px">Workflow examples</a><a href="/interview" style="padding:6px 12px;border:1px solid var(--line);border-radius:6px;text-decoration:none;color:var(--ink);font-size:13px">Find your first agent</a>
|
|
54
54
|
<button type="button" id="newAgentBtn" class="primary">New agent</button></div>
|
|
55
55
|
</div>
|
|
56
56
|
</div>
|
|
57
57
|
<div class="le-scroll"><div class="le-page">
|
|
58
58
|
<form id="newAgentForm" style="display:none">
|
|
59
|
-
<p class="hint" style="margin:0">Not sure what to build? <a href="/
|
|
59
|
+
<p class="hint" style="margin:0">Not sure what to build? <a href="/workflows/new#examples">Start from a workflow example</a>, or <a href="/interview">find your first agent</a>: a short interview that suggests one and creates it.</p>
|
|
60
60
|
<h2>New agent</h2>
|
|
61
61
|
<label>Name <span class="hint">(lowercase, hyphens allowed: becomes agents/<name>/)</span>
|
|
62
62
|
<input type="text" name="name" placeholder="weather-agent" pattern="[a-z0-9]+(-[a-z0-9]+)*" required>
|
|
@@ -28,8 +28,7 @@ export const consoleSidebarHtml = `<aside class="le-sidebar" aria-label="Console
|
|
|
28
28
|
<div class="le-nav-label"><span>Agents</span><a href="/agents?new=1">+ New</a></div>
|
|
29
29
|
<ul class="le-agent-list" id="leAgentList"></ul>
|
|
30
30
|
<a class="le-find" href="/interview" data-le-nav="interview">Find your first agent</a>
|
|
31
|
-
<a class="le-find" href="/
|
|
32
|
-
<a class="le-find" href="/workflows/new" data-le-nav="workflows">Describe a workflow</a>
|
|
31
|
+
<a class="le-find" href="/workflows/new" data-le-nav="workflows">New workflow</a>
|
|
33
32
|
<nav class="le-foot" aria-label="Settings">
|
|
34
33
|
<a href="/config?section=models" data-le-nav="models">Models</a>
|
|
35
34
|
<a href="/config?section=apps" data-le-nav="apps">Apps</a>
|
|
@@ -174,7 +173,7 @@ export const consoleShellScript = `<script>
|
|
|
174
173
|
}
|
|
175
174
|
|
|
176
175
|
var section = params.get('section');
|
|
177
|
-
var foot = page === 'config' ? (section === 'abilities' || section === 'gateways' ? 'abilities' : section === 'apps' ? 'apps' : section === 'settings' ? 'settings' : 'models') : page === 'interview' || page === '
|
|
176
|
+
var foot = page === 'config' ? (section === 'abilities' || section === 'gateways' ? 'abilities' : section === 'apps' ? 'apps' : section === 'settings' ? 'settings' : 'models') : page === 'interview' || page === 'workflows' ? page : '';
|
|
178
177
|
var footLinks = document.querySelectorAll('[data-le-nav]');
|
|
179
178
|
for (var i = 0; i < footLinks.length; i++) {
|
|
180
179
|
if (footLinks[i].getAttribute('data-le-nav') === foot) footLinks[i].setAttribute('aria-current', 'page');
|