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
|
@@ -1,157 +1,15 @@
|
|
|
1
|
-
//
|
|
2
|
-
// (
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
// script avoids backslashes and template placeholders, since it lives
|
|
6
|
-
// inside a template literal.
|
|
7
|
-
import { devUiCss } from './dev-ui-styles.js';
|
|
8
|
-
import { consoleShellScript, consoleSidebarHtml } from './console-shell.js';
|
|
1
|
+
// Templates are now workflow examples, on the new-workflow page
|
|
2
|
+
// (web/workflow-page.ts). This page only sends a project server from an
|
|
3
|
+
// older template, which still serves it at /templates, there.
|
|
4
|
+
/** @deprecated The console redirects /templates to the workflow examples. */
|
|
9
5
|
export const templatesPageHtml = `<!doctype html>
|
|
10
6
|
<html lang="en">
|
|
11
7
|
<head>
|
|
12
8
|
<meta charset="utf-8">
|
|
13
|
-
<meta
|
|
14
|
-
<title>
|
|
15
|
-
<style>${devUiCss}
|
|
16
|
-
.tp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 12px; max-width: 1000px; }
|
|
17
|
-
.tp-card { border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px; display: grid; gap: 8px; align-content: start; }
|
|
18
|
-
.tp-card h3 { margin: 0; font-size: 15px; }
|
|
19
|
-
.tp-cat { justify-self: start; font-size: 11.5px; font-weight: 600; color: var(--ink-muted); background: var(--surface); padding: 2px 8px; border-radius: 999px; }
|
|
20
|
-
.tp-card .meta { font-size: 12.5px; color: var(--ink-muted); display: grid; gap: 4px; }
|
|
21
|
-
.tp-card .row { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
22
|
-
.tp-preview { border-top: 1px solid var(--line); padding-top: 10px; display: grid; gap: 8px; font-size: 13px; }
|
|
23
|
-
.tp-preview pre { white-space: pre-wrap; max-height: 220px; overflow: auto; margin: 0; }
|
|
24
|
-
.tp-btn { display: inline-block; padding: 6px 12px; border-radius: 6px; text-decoration: none; font-size: 13px; font-weight: 500; background: var(--accent); color: var(--on-accent); }
|
|
25
|
-
.tp-btn.plain { background: none; color: var(--ink); border: 1px solid var(--line); }
|
|
26
|
-
.hint { font-size: 12px; color: var(--ink-muted); }
|
|
27
|
-
.error { font-size: 13px; color: var(--bad); }
|
|
28
|
-
</style>
|
|
9
|
+
<meta http-equiv="refresh" content="0; url=/workflows/new#examples">
|
|
10
|
+
<title>Workflow examples</title>
|
|
29
11
|
</head>
|
|
30
|
-
<body
|
|
31
|
-
<
|
|
32
|
-
${consoleSidebarHtml}
|
|
33
|
-
<main class="le-main">
|
|
34
|
-
<div class="le-head plain">
|
|
35
|
-
<div class="le-head-row">
|
|
36
|
-
<div class="le-title"><button type="button" class="le-menu-btn" data-le-menu aria-label="Open navigation">☰</button>
|
|
37
|
-
<div><h1>Templates</h1><p>Ready-made agents to start from. Each one asks you before it sends, pays, changes or deletes anything.</p></div>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
<div class="le-scroll"><div class="le-page" id="tpPage"><p class="hint">Loading…</p></div></div>
|
|
42
|
-
</main>
|
|
43
|
-
</div>
|
|
44
|
-
${consoleShellScript}
|
|
45
|
-
<script>
|
|
46
|
-
(function () {
|
|
47
|
-
var page = document.getElementById('tpPage');
|
|
48
|
-
var ELLIPSIS = String.fromCharCode(8230);
|
|
49
|
-
|
|
50
|
-
function esc(s) {
|
|
51
|
-
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
|
|
52
|
-
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function api(method, path) {
|
|
56
|
-
return fetch(path, { method: method, cache: 'no-store' })
|
|
57
|
-
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); });
|
|
58
|
-
}
|
|
59
|
-
function checklistHtml(items) {
|
|
60
|
-
if (!items.length) return '<p>Nothing else to set up.</p>';
|
|
61
|
-
return '<ul style="margin:0;padding-left:18px">' + items.map(function (it) {
|
|
62
|
-
return '<li>' + (it.href ? '<a href="' + esc(it.href) + '">' + esc(it.label) + '</a>' : esc(it.label)) + '</li>';
|
|
63
|
-
}).join('') + '</ul>';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
var canInstall = true;
|
|
67
|
-
var DECISIONS = { allow: 'runs on its own', ask: 'asks you first', deny: 'never' };
|
|
68
|
-
var CHECK = String.fromCharCode(10003);
|
|
69
|
-
|
|
70
|
-
function cardHtml(t) {
|
|
71
|
-
return '<div class="tp-card" data-id="' + esc(t.id) + '">' +
|
|
72
|
-
'<span class="tp-cat">' + esc(t.category) + '</span>' +
|
|
73
|
-
'<h3>' + esc(t.title) + '</h3>' +
|
|
74
|
-
'<div>' + esc(t.summary) + '</div>' +
|
|
75
|
-
'<div class="meta">' +
|
|
76
|
-
'<div><strong>Runs:</strong> ' + esc(t.trigger) + '</div>' +
|
|
77
|
-
(t.asksFirst.length ? '<div><strong>Asks you first:</strong> ' + esc(t.asksFirst.join(', ')) + '</div>' : '') +
|
|
78
|
-
(t.abilities.length || (t.apps || []).length ? '<div><strong>Uses:</strong> ' + t.abilities.map(function (a) { return '<code>' + esc(a) + '</code>'; }).concat((t.apps || []).map(function (a) { return esc(a); })).join(' ') + '</div>' : '') +
|
|
79
|
-
'</div>' +
|
|
80
|
-
'<div class="row"><button type="button" class="primary tp-use">Use this template</button></div>' +
|
|
81
|
-
'<div class="tp-slot"></div></div>';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function listHtml(items) {
|
|
85
|
-
return '<ul style="margin:4px 0 0;padding-left:18px">' + items.join('') + '</ul>';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function show() {
|
|
89
|
-
api('GET', '/starter-templates').then(function (r) {
|
|
90
|
-
if (!r.ok) { page.innerHTML = '<p class="error">' + esc(r.body.error || 'Could not load the templates') + '</p>'; return; }
|
|
91
|
-
canInstall = r.body.canInstallAbilities !== false;
|
|
92
|
-
page.innerHTML = '<div class="tp-grid">' + r.body.templates.map(cardHtml).join('') + '</div>' +
|
|
93
|
-
'<p class="hint" style="margin-top:16px">Not what you need? <a href="/interview">Find your first agent</a> suggests one from your own work, or <a href="/agents?new=1">start from a blank agent</a>.</p>';
|
|
94
|
-
Array.prototype.forEach.call(page.querySelectorAll('.tp-use'), function (btn) {
|
|
95
|
-
btn.addEventListener('click', function () { preview(btn.closest('.tp-card')); });
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function preview(card) {
|
|
101
|
-
var id = card.getAttribute('data-id');
|
|
102
|
-
var slot = card.querySelector('.tp-slot');
|
|
103
|
-
slot.innerHTML = '<p class="hint">Loading' + ELLIPSIS + '</p>';
|
|
104
|
-
api('GET', '/starter-templates/' + encodeURIComponent(id) + '/preview').then(function (r) {
|
|
105
|
-
if (!r.ok) { slot.innerHTML = '<p class="error">' + esc(r.body.error || 'Could not prepare it') + '</p>'; return; }
|
|
106
|
-
var p = r.body;
|
|
107
|
-
var settings = Object.keys(p.settings || {});
|
|
108
|
-
slot.innerHTML = '<div class="tp-preview">' +
|
|
109
|
-
'<div><strong>Agent:</strong> <code>' + esc(p.name) + '</code></div>' +
|
|
110
|
-
'<div><strong>Instructions</strong> <span class="hint">(edit them later in its Overview tab)</span><pre>' + esc(p.systemPrompt) + '</pre></div>' +
|
|
111
|
-
(p.abilities.length ? '<div><strong>Installs</strong> <span class="hint">(abilities: their tools, skills and permission rules)</span>' +
|
|
112
|
-
listHtml(p.abilities.map(function (a) { return '<li><code>' + esc(a) + '</code></li>'; })) +
|
|
113
|
-
(canInstall ? '' : '<p class="error" style="margin:4px 0 0">Installing abilities needs <code>LOOPENGINE_ADMIN_AUTH</code> set on the server. Without it, the agent is created and its abilities head the checklist.</p>') + '</div>' : '') +
|
|
114
|
-
(p.apps && p.apps.length ? '<div><strong>Apps it uses</strong> <span class="hint">(connected on the <a href="/config?section=apps">Apps</a> page; one that isn' + String.fromCharCode(8217) + 't connected yet heads the checklist)</span>' + listHtml(p.apps.map(function (a) { return '<li>' + esc(a) + '</li>'; })) + '</div>' : '') +
|
|
115
|
-
'<div><strong>Permissions</strong> <span class="hint">(anything not listed asks you first)</span>' +
|
|
116
|
-
listHtml(p.rules.map(function (rule) { return '<li><code>' + esc(rule.tool) + '</code>: ' + esc(DECISIONS[rule.decision] || rule.decision) + '</li>'; })) + '</div>' +
|
|
117
|
-
(p.skills && p.skills.length ? '<div><strong>Skills it starts with</strong> <span class="hint">(edit them later in Advanced ' + String.fromCharCode(8594) + ' Skills)</span>' + listHtml(p.skills.map(function (sk) { return '<li><code>' + esc(sk) + '</code></li>'; })) + '</div>' : '') +
|
|
118
|
-
(p.keys.length ? '<div><strong>Keys it needs</strong>' +
|
|
119
|
-
listHtml(p.keys.map(function (k) {
|
|
120
|
-
var names = k.names.length > 1 ? 'one of ' + k.names.join(', ') : k.names[0];
|
|
121
|
-
return '<li>' + esc(names) + (k.set ? ' <span class="hint">' + CHECK + ' already set</span>' : '') + '</li>';
|
|
122
|
-
})) + '</div>' : '') +
|
|
123
|
-
(settings.length ? '<div><strong>Settings filled in:</strong> ' + settings.map(function (s) { return '<code>' + esc(s) + '=' + esc(p.settings[s]) + '</code>'; }).join(', ') + '</div>' : '') +
|
|
124
|
-
(p.schedule ? '<div><strong>Schedule:</strong> ' + esc(p.schedule) + '</div>' : '') +
|
|
125
|
-
(p.needsApprovals ? '<div><strong>Approvals:</strong> requests reach you wherever its Messaging tab sends them (Slack, email, Lark or a webhook).</div>' : '') +
|
|
126
|
-
(p.notes.length ? '<div><strong>You also</strong>' + listHtml(p.notes.map(function (n) { return '<li>' + esc(n) + '</li>'; })) + '</div>' : '') +
|
|
127
|
-
'<div class="row"><button type="button" class="primary tp-create">Create it</button><button type="button" class="tp-cancel">Cancel</button></div>' +
|
|
128
|
-
'<div class="hint tp-status"></div></div>';
|
|
129
|
-
slot.querySelector('.tp-cancel').addEventListener('click', function () { slot.innerHTML = ''; });
|
|
130
|
-
slot.querySelector('.tp-create').addEventListener('click', function (ev) {
|
|
131
|
-
var btn = ev.currentTarget;
|
|
132
|
-
btn.disabled = true;
|
|
133
|
-
slot.querySelector('.tp-status').textContent = 'Creating it and installing its abilities' + ELLIPSIS + ' (this can take a minute)';
|
|
134
|
-
api('POST', '/starter-templates/' + encodeURIComponent(id) + '/create').then(function (r2) {
|
|
135
|
-
if (!r2.ok) { btn.disabled = false; slot.querySelector('.tp-status').textContent = r2.body.error || 'Could not create it'; return; }
|
|
136
|
-
var c = r2.body;
|
|
137
|
-
var installed = c.abilities.installed.map(function (a) { return '<code>' + esc(a) + '</code>'; }).join(' ');
|
|
138
|
-
slot.innerHTML = '<div class="tp-preview">' +
|
|
139
|
-
'<div><strong>Created <code>' + esc(c.name) + '</code>.</strong> Anything its permissions don' + String.fromCharCode(8217) + 't cover asks you first.</div>' +
|
|
140
|
-
(installed ? '<div>' + CHECK + ' Installed ' + installed + '</div>' : '') +
|
|
141
|
-
(c.schedule ? '<div>' + CHECK + ' Schedule created, paused: ' + esc(c.schedule.description) + (c.schedule.next_run_at ? ' (first run ' + esc(new Date(c.schedule.next_run_at).toLocaleString(undefined, c.schedule.timeZone ? { timeZone: c.schedule.timeZone, timeZoneName: 'short' } : undefined)) + ' once turned on)' : '') + '</div>' : '') +
|
|
142
|
-
(c.loadError ? '<div class="error">It was written but couldn' + String.fromCharCode(8217) + 't be loaded: ' + esc(c.loadError) + '</div>' : '') +
|
|
143
|
-
(c.scheduleError ? '<div class="error">Its schedule wasn' + String.fromCharCode(8217) + 't created: ' + esc(c.scheduleError) + '. Add one in its Schedules tab.</div>' : '') +
|
|
144
|
-
'<div><strong>Finish setup:</strong>' + checklistHtml(c.checklist) + '</div>' +
|
|
145
|
-
(c.exampleMessage ? '<div><strong>Try it</strong> in Chat with: <em>' + esc(c.exampleMessage) + '</em></div>' : '') +
|
|
146
|
-
'<div class="row"><a class="tp-btn" href="/agents/config?agent=' + encodeURIComponent(c.name) + '">Open ' + esc(c.name) + '</a>' +
|
|
147
|
-
'<a class="tp-btn plain" href="' + esc(leShell.tabHref(c.name, 'chat')) + '">Try it in Chat</a></div></div>';
|
|
148
|
-
}).catch(function () { btn.disabled = false; slot.querySelector('.tp-status').textContent = 'Could not reach the server; try again.'; });
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
show();
|
|
154
|
-
})();
|
|
155
|
-
</script>
|
|
12
|
+
<body>
|
|
13
|
+
<p>Templates are now workflow examples: <a href="/workflows/new#examples">open them</a>.</p>
|
|
156
14
|
</body>
|
|
157
15
|
</html>`;
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
// agent it would go into, and creates it: as a new agent through the same
|
|
5
5
|
// path as a template (web/templates-admin.ts), or added to an existing
|
|
6
6
|
// one without changing anything that agent already does.
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
7
8
|
import { join } from 'node:path';
|
|
8
9
|
import { addGatewayTool, readGatewayToolsFromDir } from '../core/gateway-tools.js';
|
|
9
10
|
import { applyPlannedRules } from './actauth-admin.js';
|
|
10
11
|
import { writeSkill } from './skills-admin.js';
|
|
12
|
+
import { setAgentEnvVar } from './env-admin.js';
|
|
11
13
|
import { createFromTemplateObject, installFailureReason, templateChecklist } from './templates-admin.js';
|
|
12
14
|
import { templateScheduleStart } from '../core/system-agents/templates.js';
|
|
13
15
|
import { planParameters, planWebhook, planWorkflowWithModel, workflowTemplate } from '../core/system-agents/workflow-planner.js';
|
|
@@ -436,6 +438,17 @@ export async function createWorkflow(input, deps) {
|
|
|
436
438
|
const ruled = applyPlannedRules(agent.name, plan.rules);
|
|
437
439
|
// 3. Its steps, as a skill; then load the agent again with all of it.
|
|
438
440
|
writeSkill(agent.name, plan.id, { description: plan.skill.description, body: plan.skill.body });
|
|
441
|
+
// The skills and settings it comes with, where the agent has none:
|
|
442
|
+
// its own policies and values stay as they are.
|
|
443
|
+
for (const skill of plan.skills ?? []) {
|
|
444
|
+
if (skill.id === plan.id || existsSync(join(deps.agentDir(agent.name), 'skills', skill.id, 'SKILL.md')))
|
|
445
|
+
continue;
|
|
446
|
+
writeSkill(agent.name, skill.id, { description: skill.description, body: skill.body });
|
|
447
|
+
}
|
|
448
|
+
for (const [variable, value] of Object.entries(plan.env ?? {})) {
|
|
449
|
+
if (!deps.envValue(agent.name, variable))
|
|
450
|
+
await setAgentEnvVar(agent.name, variable, value);
|
|
451
|
+
}
|
|
439
452
|
let loadError;
|
|
440
453
|
try {
|
|
441
454
|
await deps.reloadAgent(agent.name);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// The console's "
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// The console's "New workflow" page (/workflows/new): a person starts
|
|
2
|
+
// from a ready-made example (core/system-agents/workflow-examples.ts,
|
|
3
|
+
// which replace the old templates) or describes a workflow they already
|
|
4
|
+
// have in mind, and sees the plan (web/workflow-admin.ts behind /workflows/*): each
|
|
4
5
|
// step matched to a tool, the checkpoints where it asks them, the
|
|
5
6
|
// permissions, the abilities it needs and the agent it goes into, and
|
|
6
7
|
// creates it with one click. Same shell and styles as the other console
|
|
@@ -13,12 +14,18 @@ export const workflowPageHtml = `<!doctype html>
|
|
|
13
14
|
<head>
|
|
14
15
|
<meta charset="utf-8">
|
|
15
16
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
16
|
-
<title>
|
|
17
|
+
<title>New workflow</title>
|
|
17
18
|
<style>${devUiCss}
|
|
18
19
|
.wf { max-width: 860px; display: grid; gap: 18px; }
|
|
19
20
|
.wf textarea { width: 100%; min-height: 130px; box-sizing: border-box; font: inherit; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); color: var(--ink); }
|
|
20
|
-
.wf-
|
|
21
|
-
.wf-
|
|
21
|
+
.wf-ex-group { display: grid; gap: 8px; }
|
|
22
|
+
.wf-ex-group h3 { margin: 4px 0 0; font-size: 12px; font-weight: 600; color: var(--ink-muted); text-transform: uppercase; letter-spacing: 0.05em; }
|
|
23
|
+
.wf-ex-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 10px; }
|
|
24
|
+
.wf-ex { border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 14px; display: grid; gap: 6px; align-content: start; background: var(--bg); }
|
|
25
|
+
.wf-ex strong { font-size: 14px; }
|
|
26
|
+
.wf-ex p { margin: 0; font-size: 13px; color: var(--ink-muted); }
|
|
27
|
+
.wf-ex .wf-btn { justify-self: start; }
|
|
28
|
+
.wf-instructions textarea { min-height: 180px; font-family: var(--font-mono); font-size: 12px; }
|
|
22
29
|
.wf-card { border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px; display: grid; gap: 10px; background: var(--bg); }
|
|
23
30
|
.wf-card h2 { margin: 0; font-size: 18px; }
|
|
24
31
|
.wf-card h3 { margin: 6px 0 0; font-size: 14px; }
|
|
@@ -61,7 +68,7 @@ ${consoleSidebarHtml}
|
|
|
61
68
|
<div class="le-head plain">
|
|
62
69
|
<div class="le-head-row">
|
|
63
70
|
<div class="le-title"><button type="button" class="le-menu-btn" data-le-menu aria-label="Open navigation">☰</button>
|
|
64
|
-
<div><h1>
|
|
71
|
+
<div><h1>New workflow</h1><p>Describe what you want done, or start from an example. You get a plan to check before anything is created.</p></div>
|
|
65
72
|
</div>
|
|
66
73
|
</div>
|
|
67
74
|
</div>
|
|
@@ -75,14 +82,8 @@ ${consoleShellScript}
|
|
|
75
82
|
var ELLIPSIS = String.fromCharCode(8230);
|
|
76
83
|
var DASH = String.fromCharCode(8212);
|
|
77
84
|
var LABELS = { allow: 'Allow', ask: 'Ask me', deny: 'Never' };
|
|
78
|
-
var EXAMPLES = [
|
|
79
|
-
['Blog post', 'Every Monday morning, browse the web for timely blog post ideas about our product area. Ask me on Slack to pick one of the ideas. Then draft the post and let me review it before it is created.'],
|
|
80
|
-
['Weekly digest', 'Every Friday afternoon, search the web for news about our competitors and summarise the important changes. Send me the summary for review before it goes to the team channel.'],
|
|
81
|
-
['Refund requests', 'When asked, look up the customer' + String.fromCharCode(8217) + 's order and check our refund policy. If a refund fits the policy, prepare it and ask me to approve it before it is issued.'],
|
|
82
|
-
['Stripe disputes', 'When a Stripe dispute comes in, look up the order and its delivery, write the evidence for Stripe, and ask me before submitting it.']
|
|
83
|
-
];
|
|
84
85
|
var SENDERS = { stripe: 'Stripe', github: 'GitHub', shopify: 'Shopify', standard: 'Standard Webhooks', custom: 'your app' };
|
|
85
|
-
var state = { description: '', planned: null, agents: [], ready: false, canInstall: false };
|
|
86
|
+
var state = { description: '', planned: null, agents: [], examples: [], ready: false, canInstall: false };
|
|
86
87
|
|
|
87
88
|
function esc(s) {
|
|
88
89
|
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
|
|
@@ -103,16 +104,20 @@ ${consoleShellScript}
|
|
|
103
104
|
(state.ready ? '' : '<p class="error">Planning needs a model: set one on an agent (Overview ' + String.fromCharCode(8594) + ' Model) and add its key under <a href="/config?section=models">Models</a>.</p>') +
|
|
104
105
|
'<div class="wf-card"><label for="wfText"><strong>Your workflow</strong> <span class="hint">what starts it, the steps in order, and where you want to choose or approve</span></label>' +
|
|
105
106
|
'<textarea id="wfText" placeholder="Every Monday, find blog ideas on the web, ask me on Slack to pick one, draft it, and let me review it before it goes up.">' + esc(state.description) + '</textarea>' +
|
|
106
|
-
'<div class="wf-examples"><span class="hint">Examples:</span>' + EXAMPLES.map(function (e, i) { return '<button type="button" class="wf-btn plain" data-example="' + i + '">' + esc(e[0]) + '</button>'; }).join('') + '</div>' +
|
|
107
107
|
'<div class="wf-actions"><button type="button" class="wf-btn" id="wfPlan"' + (state.ready ? '' : ' disabled') + '>Plan it</button>' +
|
|
108
108
|
'<span class="hint" id="wfStatus">It looks at your agents and abilities first; this can take a few minutes.</span></div>' +
|
|
109
109
|
(error ? '<p class="error">' + esc(error) + '</p>' : '') +
|
|
110
110
|
'</div>' +
|
|
111
|
-
'<p class="hint">Rather start from something ready-made? <a href="
|
|
111
|
+
'<p class="hint">Rather start from something ready-made? <a href="#examples">Choose a workflow example</a>, or <a href="/interview">find your first agent</a>.</p>' +
|
|
112
|
+
examplesHtml();
|
|
112
113
|
var text = document.getElementById('wfText');
|
|
113
114
|
var examples = page.querySelectorAll('[data-example]');
|
|
114
115
|
for (var i = 0; i < examples.length; i++) {
|
|
115
|
-
examples[i].addEventListener('click', function (ev) {
|
|
116
|
+
examples[i].addEventListener('click', function (ev) { openExample(ev.currentTarget.dataset.example); });
|
|
117
|
+
}
|
|
118
|
+
if (location.hash === '#examples') {
|
|
119
|
+
var box = document.getElementById('examples');
|
|
120
|
+
if (box) box.scrollIntoView();
|
|
116
121
|
}
|
|
117
122
|
document.getElementById('wfPlan').addEventListener('click', function () {
|
|
118
123
|
state.description = text.value.trim();
|
|
@@ -127,6 +132,37 @@ ${consoleShellScript}
|
|
|
127
132
|
});
|
|
128
133
|
}
|
|
129
134
|
|
|
135
|
+
// Ready-made plans, by category: opening one goes straight to the
|
|
136
|
+
// review, with nothing planned by a model.
|
|
137
|
+
function examplesHtml() {
|
|
138
|
+
if (!state.examples.length) return '';
|
|
139
|
+
var groups = [];
|
|
140
|
+
state.examples.forEach(function (e) {
|
|
141
|
+
var g = groups.filter(function (x) { return x.name === e.category; })[0];
|
|
142
|
+
if (!g) { g = { name: e.category, items: [] }; groups.push(g); }
|
|
143
|
+
g.items.push(e);
|
|
144
|
+
});
|
|
145
|
+
return '<div class="wf-card" id="examples"><h2>Start from an example</h2>' +
|
|
146
|
+
'<p class="hint" style="margin:0">Ready-made and tested. You see the whole plan, and can change it, before anything is created.</p>' +
|
|
147
|
+
groups.map(function (g) {
|
|
148
|
+
return '<div class="wf-ex-group"><h3>' + esc(g.name) + '</h3><div class="wf-ex-grid">' + g.items.map(function (e) {
|
|
149
|
+
return '<div class="wf-ex"><strong>' + esc(e.plan.name) + '</strong><p>' + esc(e.plan.summary) + '</p>' +
|
|
150
|
+
'<p><strong>Starts:</strong> ' + esc(e.plan.trigger.description) + '</p>' +
|
|
151
|
+
'<button type="button" class="wf-btn plain" data-example="' + esc(e.id) + '">Use this</button></div>';
|
|
152
|
+
}).join('') + '</div></div>';
|
|
153
|
+
}).join('') +
|
|
154
|
+
'</div>';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function openExample(id) {
|
|
158
|
+
var e = state.examples.filter(function (x) { return x.id === id; })[0];
|
|
159
|
+
if (!e) return;
|
|
160
|
+
// A copy: the review screen edits the plan it shows.
|
|
161
|
+
state.planned = { plan: JSON.parse(JSON.stringify(e.plan)), example: e.id };
|
|
162
|
+
renderPlan();
|
|
163
|
+
window.scrollTo(0, 0);
|
|
164
|
+
}
|
|
165
|
+
|
|
130
166
|
// Planning runs on the server as a job; check on it every few seconds.
|
|
131
167
|
function waitForPlan(id, started) {
|
|
132
168
|
setTimeout(function () {
|
|
@@ -272,10 +308,13 @@ ${consoleShellScript}
|
|
|
272
308
|
(p.rules.length ? '<h3>Permissions it sets</h3><ul class="wf-list">' + p.rules.map(function (r) { return '<li><code>' + esc(r.tool) + '</code> ' + pill(r.decision) + '</li>'; }).join('') + '</ul>' : '') +
|
|
273
309
|
(p.abilities.length ? '<h3>Abilities it uses</h3><ul class="wf-list">' + p.abilities.map(function (a) { return '<li><code>' + esc(a) + '</code></li>'; }).join('') + '</ul>' + (state.canInstall ? '' : '<p class="hint">Installing abilities needs LOOPENGINE_ADMIN_AUTH set; otherwise they head the checklist.</p>') : '') +
|
|
274
310
|
(p.mcpServers && p.mcpServers.length ? '<h3>Apps it connects to</h3><div class="wf-mcp">' + p.mcpServers.map(mcpRow).join('') + '</div>' : '') +
|
|
311
|
+
(p.apps && p.apps.length ? '<h3>Apps it uses</h3><ul class="wf-list">' + p.apps.map(function (a) { return '<li>' + esc(a.title) + ' <span class="hint">' + a.tools.length + ' tool' + (a.tools.length === 1 ? '' : 's') + '</span></li>'; }).join('') + '</ul><p class="hint" style="margin:0">Connected on the <a href="/config?section=apps">Apps</a> page; one that isn' + String.fromCharCode(8217) + 't connected yet heads the checklist.</p>' : '') +
|
|
312
|
+
(p.skills && p.skills.length ? '<h3>Skills it comes with</h3><ul class="wf-list">' + p.skills.map(function (sk) { return '<li><code>' + esc(sk.id) + '</code> <span class="hint">' + esc(sk.description) + '</span></li>'; }).join('') + '</ul><p class="hint" style="margin:0">An agent that already has one keeps its own.</p>' : '') +
|
|
275
313
|
(p.keys.length ? '<h3>Keys it needs</h3><p class="hint" style="margin:0">' + p.keys.map(esc).join(', ') + ' ' + DASH + ' you add them after, from the checklist.</p>' : '') +
|
|
276
314
|
(p.needsApprovals ? '<p class="hint" style="margin:0">It asks you through the agent' + String.fromCharCode(8217) + 's primary messaging app (Messaging tab) when nobody is in Chat.</p>' : '') +
|
|
277
315
|
'</div>' +
|
|
278
316
|
(p.gaps.length ? '<div class="wf-gaps"><strong>Worth knowing</strong><ul>' + p.gaps.map(function (g) { return '<li>' + esc(g) + '</li>'; }).join('') + '</ul></div>' : '') +
|
|
317
|
+
(p.notes && p.notes.length ? '<div class="wf-card"><strong>After it' + String.fromCharCode(8217) + 's created, you also</strong><ul class="wf-list">' + p.notes.map(function (n) { return '<li>' + esc(n) + '</li>'; }).join('') + '</ul></div>' : '') +
|
|
279
318
|
'<div class="wf-card"><h3 style="margin:0">Where it goes</h3>' +
|
|
280
319
|
'<div class="wf-target">' +
|
|
281
320
|
'<label><input type="radio" name="wfTarget" value="new"' + (suggested ? '' : ' checked') + '> A new agent, <code>' + esc(p.newAgent.name) + '</code></label>' +
|
|
@@ -283,14 +322,29 @@ ${consoleShellScript}
|
|
|
283
322
|
(others.length ? '<label><input type="radio" name="wfTarget" value="other"> Add to <select id="wfOther">' + others.map(function (a) { return '<option>' + esc(a) + '</option>'; }).join('') + '</select></label>' : '') +
|
|
284
323
|
'</div>' +
|
|
285
324
|
(p.target.reason ? '<p class="hint" style="margin:0">' + esc(p.target.reason) + '</p>' : '') +
|
|
325
|
+
'<div class="wf-instructions" id="wfNewAgentBox"' + (suggested ? ' hidden' : '') + '><label for="wfInstructions"><strong>Its instructions</strong> <span class="hint">read on every run of every workflow it has: who it is, rules it always follows, and anything marked (edit this). The steps are the workflow' + String.fromCharCode(8217) + 's.</span></label>' +
|
|
326
|
+
'<textarea id="wfInstructions">' + esc(p.newAgent.systemPrompt) + '</textarea></div>' +
|
|
327
|
+
'<p class="hint" style="margin:0" id="wfExistingNote"' + (suggested ? '' : ' hidden') + '>An agent you have keeps its own instructions; only the workflow, its abilities and permissions are added.</p>' +
|
|
286
328
|
(state.planned.note ? '<p class="hint" style="margin:0"><strong>Note:</strong> ' + esc(state.planned.note) + '</p>' : '') +
|
|
287
329
|
'<p class="hint" style="margin:0">Added to an existing agent, it keeps every decision that agent already has; any it would change are listed afterwards.</p>' +
|
|
288
330
|
'</div>' +
|
|
289
331
|
'<details class="wf-card"><summary><strong>The steps the agent follows</strong> <span class="hint">(saved as its skill <code>' + esc(p.id) + '</code>)</span></summary><div class="wf-skill">' + esc(p.skill.body) + '</div></details>' +
|
|
290
|
-
'<div class="wf-actions"><button type="button" class="wf-btn" id="wfCreate">Create it</button><button type="button" class="wf-btn plain" id="wfBack">Change the description</button>' +
|
|
332
|
+
'<div class="wf-actions"><button type="button" class="wf-btn" id="wfCreate">Create it</button><button type="button" class="wf-btn plain" id="wfBack">' + (state.planned.example ? 'Back to the examples' : 'Change the description') + '</button>' +
|
|
291
333
|
'<span class="hint" id="wfStatus"></span></div>' +
|
|
292
334
|
(error ? '<p class="error">' + esc(error) + '</p>' : '');
|
|
293
|
-
document.getElementById('wfBack').addEventListener('click', function () {
|
|
335
|
+
document.getElementById('wfBack').addEventListener('click', function () {
|
|
336
|
+
var fromExample = state.planned.example;
|
|
337
|
+
renderDescribe();
|
|
338
|
+
if (fromExample) document.getElementById('examples').scrollIntoView();
|
|
339
|
+
});
|
|
340
|
+
var targets = page.querySelectorAll('input[name="wfTarget"]');
|
|
341
|
+
for (var t = 0; t < targets.length; t++) {
|
|
342
|
+
targets[t].addEventListener('change', function () {
|
|
343
|
+
var isNew = page.querySelector('input[name="wfTarget"]:checked').value === 'new';
|
|
344
|
+
document.getElementById('wfNewAgentBox').hidden = !isNew;
|
|
345
|
+
document.getElementById('wfExistingNote').hidden = isNew;
|
|
346
|
+
});
|
|
347
|
+
}
|
|
294
348
|
var paramsBox = document.getElementById('wfParams');
|
|
295
349
|
paramsBox.addEventListener('click', function (ev) {
|
|
296
350
|
if (ev.target.classList.contains('wf-param-remove')) ev.target.closest('.wf-param').remove();
|
|
@@ -310,6 +364,7 @@ ${consoleShellScript}
|
|
|
310
364
|
delete p.webhook;
|
|
311
365
|
if (p.trigger.kind === 'webhook') p.trigger.kind = 'on_request';
|
|
312
366
|
}
|
|
367
|
+
p.newAgent.systemPrompt = document.getElementById('wfInstructions').value.trim() || p.newAgent.systemPrompt;
|
|
313
368
|
var chosen = page.querySelector('input[name="wfTarget"]:checked');
|
|
314
369
|
var target = chosen.value === 'new' ? { kind: 'new' } : { kind: 'existing', agent: chosen.value === 'other' ? document.getElementById('wfOther').value : chosen.dataset.agent };
|
|
315
370
|
var btn = document.getElementById('wfCreate');
|
|
@@ -340,7 +395,7 @@ ${consoleShellScript}
|
|
|
340
395
|
(c.keptDecisions.length ? '<p class="hint" style="margin:0"><strong>Kept as they were:</strong> ' + c.keptDecisions.map(function (k) { return '<code>' + esc(k.tool) + '</code> stays ' + esc(LABELS[k.has]) + ' (the workflow wanted ' + esc(LABELS[k.wants]) + ')'; }).join('; ') + '. Change them in Permissions if you want.</p>' : '') +
|
|
341
396
|
(items.length ? '<h3>Left to do</h3><ul class="wf-list">' + items.map(function (it) { return '<li>' + (it.href ? '<a href="' + esc(it.href) + '">' + esc(it.label) + '</a>' : esc(it.label)) + '</li>'; }).join('') + '</ul>' : '<p class="hint" style="margin:0">Nothing left to set up.</p>') +
|
|
342
397
|
'<div class="wf-actions"><a class="wf-btn" href="' + agentHref + '">Open ' + esc(c.agent) + '</a><a class="wf-btn plain" href="/playground?agent=' + encodeURIComponent(c.agent) + '">Try it in Chat</a>' +
|
|
343
|
-
'<button type="button" class="wf-btn plain" id="wfAnother">
|
|
398
|
+
'<button type="button" class="wf-btn plain" id="wfAnother">New workflow</button></div>' +
|
|
344
399
|
'</div>';
|
|
345
400
|
document.getElementById('wfAnother').addEventListener('click', function () { state.description = ''; state.planned = null; renderDescribe(); });
|
|
346
401
|
wireWebhookDone(c);
|
|
@@ -410,6 +465,7 @@ ${consoleShellScript}
|
|
|
410
465
|
state.ready = Boolean(d.ready);
|
|
411
466
|
state.canInstall = Boolean(d.canInstallAbilities);
|
|
412
467
|
state.agents = d.agents || [];
|
|
468
|
+
state.examples = d.examples || [];
|
|
413
469
|
renderDescribe();
|
|
414
470
|
})
|
|
415
471
|
.catch(function (err) { page.innerHTML = '<p class="error">Could not load: ' + esc(err.message) + '</p>'; });
|