loopengine 0.3.5 → 0.3.6
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.
|
@@ -49,6 +49,18 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
49
49
|
form.add-source .schedule-when input[type="number"] { width: 80px; }
|
|
50
50
|
form.add-source .schedule-when select, form.add-source .schedule-when input[type="datetime-local"] { width: auto; }
|
|
51
51
|
.workflow-card { margin-bottom: 10px; }
|
|
52
|
+
.workflow-starts { margin: 10px 0 0; padding: 10px 12px; border: 1px solid var(--le-line, #e5e7eb); border-radius: 8px; background: var(--le-surface, #f6f7f9); }
|
|
53
|
+
.workflow-starts h4 { margin: 0; font-size: 13.5px; font-weight: 600; }
|
|
54
|
+
.wf-start { padding: 10px 0; border-top: 1px solid var(--le-line, #e5e7eb); display: grid; gap: 6px; font-size: 13px; }
|
|
55
|
+
.wf-start:first-of-type { border-top: 0; padding-top: 6px; }
|
|
56
|
+
.wf-start-head { display: flex; align-items: center; gap: 6px; }
|
|
57
|
+
.wf-start-head strong { font-size: 13px; }
|
|
58
|
+
.help-q { width: 18px; height: 18px; padding: 0; border-radius: 50%; font-size: 11px; line-height: 16px; font-weight: 600; text-align: center; cursor: pointer; }
|
|
59
|
+
.help-text { margin: 0; }
|
|
60
|
+
.help-text[hidden] { display: none; }
|
|
61
|
+
.wf-start .schedule-history { margin: 0; gap: 6px; }
|
|
62
|
+
.wf-start .workflow-params { margin: 0; }
|
|
63
|
+
.wf-start pre { margin: 0; padding: 8px 10px; border-radius: 6px; background: var(--le-bg, #fff); border: 1px solid var(--le-line, #e5e7eb); font-size: 12px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
52
64
|
.workflow-steps-text { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 13px; margin: 6px 0 0; padding: 8px 10px; border-radius: 6px; background: var(--le-surface, #f6f7f9); }
|
|
53
65
|
.workflow-params { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
|
|
54
66
|
.workflow-params[hidden] { display: none; }
|
|
@@ -692,14 +704,22 @@ ${consoleShellScript}
|
|
|
692
704
|
function workflowStartsSummary(w) {
|
|
693
705
|
var parts = (w.schedules || []).filter(function (x) { return x.repeating && x.status !== 'paused'; }).map(function (x) { return x.describe; });
|
|
694
706
|
if (w.webhooks) parts.push(w.webhooks === 1 ? 'when its webhook is called' : 'when one of its ' + w.webhooks + ' webhooks is called');
|
|
695
|
-
if (!parts.length) return '
|
|
707
|
+
if (!parts.length) return 'No schedule or webhook yet';
|
|
696
708
|
var text = parts.join(', and ');
|
|
697
709
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
698
710
|
}
|
|
699
711
|
|
|
700
|
-
// How it starts: each
|
|
701
|
-
//
|
|
702
|
-
|
|
712
|
+
// How it starts: four ways, each with a ? that says what it is. A
|
|
713
|
+
// schedule (pause, resume, remove, add), a webhook, Run now with the
|
|
714
|
+
// values a run is given, and the API call an app makes.
|
|
715
|
+
function wfStartSection(key, title, help, body) {
|
|
716
|
+
return '<div class="wf-start">' +
|
|
717
|
+
'<div class="wf-start-head"><strong>' + title + '</strong>' +
|
|
718
|
+
'<button type="button" class="help-q" aria-expanded="false" aria-controls="help-' + key + '" aria-label="What is ' + title.toLowerCase() + '?">?</button></div>' +
|
|
719
|
+
'<p class="hint help-text" hidden>' + help + '</p>' + body + '</div>';
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function workflowStartsHtml(w, agentName, paramsHtml) {
|
|
703
723
|
var schedules = (w.schedules || []).map(function (x) {
|
|
704
724
|
var state = x.waiting ? '<span class="le-pill ask">Waiting for a decision</span>'
|
|
705
725
|
: x.status === 'paused' ? '<span class="le-pill">Paused</span>'
|
|
@@ -716,15 +736,28 @@ ${consoleShellScript}
|
|
|
716
736
|
'<button type="button" class="delete-btn workflow-schedule-action" data-id="' + escapeHtml(x.taskId) + '" data-action="cancel">Remove</button>' +
|
|
717
737
|
'</span></li>';
|
|
718
738
|
}).join('');
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
'
|
|
727
|
-
|
|
739
|
+
var paramNames = Object.keys(w.parameters || {});
|
|
740
|
+
var example = paramNames.length
|
|
741
|
+
? '{ "params": { ' + paramNames.map(function (p) { return '"' + p + '": "' + String.fromCharCode(8230) + '"'; }).join(', ') + ' } }'
|
|
742
|
+
: '{}';
|
|
743
|
+
var apiUrl = location.origin + '/agents/' + encodeURIComponent(agentName) + '/workflows/' + encodeURIComponent(w.id) + '/run';
|
|
744
|
+
return '<div class="workflow-starts"><h4>How it starts</h4>' +
|
|
745
|
+
wfStartSection('schedule-' + w.id, 'Schedule',
|
|
746
|
+
'Runs it on a timer, like every day at 9:00, with the values you give the schedule. Nobody needs to be there: approvals and questions go to the agent' + String.fromCharCode(8217) + 's messaging app.',
|
|
747
|
+
(schedules ? '<ul class="schedule-history">' + schedules + '</ul>' : '<span class="hint">None yet.</span>') +
|
|
748
|
+
'<div><button type="button" class="workflow-add-schedule">Add a schedule</button></div>') +
|
|
749
|
+
wfStartSection('webhook-' + w.id, 'Webhook',
|
|
750
|
+
'A private web address another app (Stripe, GitHub, Shopify or your own) calls when something happens. Each call starts a run, with values taken from what the app sends.',
|
|
751
|
+
'<span' + (w.webhooks ? '' : ' class="hint"') + '>' + (w.webhooks ? (w.webhooks === 1 ? '1 webhook is on.' : w.webhooks + ' webhooks are on.') : 'None yet.') + '</span>' +
|
|
752
|
+
'<div><button type="button" class="workflow-hooks-btn">' + (w.webhooks ? 'Webhooks' : 'Add a webhook') + '</button></div>') +
|
|
753
|
+
wfStartSection('run-' + w.id, 'Run now',
|
|
754
|
+
'Starts a run right away, from here' + (paramNames.length ? ', with the values you fill in' : '') + '. It shows under Recent runs as it goes.',
|
|
755
|
+
(paramsHtml ? '<div class="workflow-params">' + paramsHtml + '</div>' : '') +
|
|
756
|
+
'<div><button type="button" class="workflow-run primary">Run now</button></div>') +
|
|
757
|
+
wfStartSection('api-' + w.id, 'API',
|
|
758
|
+
'Your own app starts a run by calling this address with an API key. Add "wait": true to get the result in the answer, or "callback_url" to have it posted to you when the run ends.',
|
|
759
|
+
'<pre>POST ' + escapeHtml(apiUrl) + String.fromCharCode(10) + escapeHtml(example) + '</pre>') +
|
|
760
|
+
'</div>';
|
|
728
761
|
}
|
|
729
762
|
|
|
730
763
|
function renderWorkflowsHtml(name, workflows) {
|
|
@@ -763,15 +796,15 @@ ${consoleShellScript}
|
|
|
763
796
|
'<div style="display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;align-items:baseline">' +
|
|
764
797
|
'<strong>' + escapeHtml(w.title) + '</strong><span>' + state + '</span></div>' +
|
|
765
798
|
'<p class="hint" style="margin:4px 0 6px">' + escapeHtml(w.description) + (s ? ' ' + String.fromCharCode(183) + ' ' + escapeHtml(workflowStartsSummary(w)) : '') + '</p>' +
|
|
766
|
-
'<details><summary class="hint">Instructions <span>(the ' + escapeHtml(w.id) + ' skill)</span></summary><div class="workflow-steps-text">' + escapeHtml(w.instructions) + '</div></details>' +
|
|
767
|
-
workflowStartsHtml(w) +
|
|
768
|
-
|
|
769
|
-
(runs ? '<details class="workflow-runs"' + ((w.recentRuns || []).some(function (r) { return r.status === 'running' || r.status === 'awaiting_approval'; }) ? ' open' : '') + '><summary class="hint">Recent runs (' + w.recentRuns.length + ')</summary><ul class="schedule-history">' + runs + '</ul></details>'
|
|
770
|
-
|
|
771
|
-
|
|
799
|
+
'<details open><summary class="hint">Instructions <span>(the ' + escapeHtml(w.id) + ' skill)</span></summary><div class="workflow-steps-text">' + escapeHtml(w.instructions) + '</div></details>' +
|
|
800
|
+
workflowStartsHtml(w, name, params) +
|
|
801
|
+
'<div class="hint workflow-result" style="margin-top:6px"></div><div class="workflow-hooks" hidden></div>' +
|
|
802
|
+
(runs ? '<details class="workflow-runs" style="margin-top:10px"' + ((w.recentRuns || []).some(function (r) { return r.status === 'running' || r.status === 'awaiting_approval'; }) ? ' open' : '') + '><summary class="hint">Recent runs (' + w.recentRuns.length + ')</summary><ul class="schedule-history">' + runs + '</ul></details>'
|
|
803
|
+
: '<p class="hint" style="margin:10px 0 0">No runs yet.</p>') +
|
|
804
|
+
'<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:10px">' +
|
|
772
805
|
'<button type="button" class="workflow-edit">Edit</button>' +
|
|
773
|
-
'<button type="button" class="delete-btn workflow-forget">
|
|
774
|
-
'</div><div class="
|
|
806
|
+
'<button type="button" class="delete-btn workflow-forget">Remove workflow</button>' +
|
|
807
|
+
'</div><div class="workflow-editor" hidden></div></div>';
|
|
775
808
|
}).join('') + '<p class="hint" style="margin:6px 0 0"><a href="/workflows/new">Describe another workflow</a></p>';
|
|
776
809
|
}
|
|
777
810
|
|
|
@@ -1101,6 +1134,16 @@ ${consoleShellScript}
|
|
|
1101
1134
|
var result = card.querySelector('.workflow-result');
|
|
1102
1135
|
var base = '/agents/' + encodeURIComponent(name) + '/workflows/' + encodeURIComponent(id);
|
|
1103
1136
|
card.querySelector('.workflow-edit').addEventListener('click', function () { openWorkflowEditor(name, card); });
|
|
1137
|
+
// The ? beside each way it starts shows or hides what it is.
|
|
1138
|
+
var helps = card.querySelectorAll('.help-q');
|
|
1139
|
+
for (var hq = 0; hq < helps.length; hq++) {
|
|
1140
|
+
helps[hq].addEventListener('click', function (ev) {
|
|
1141
|
+
var btn = ev.currentTarget;
|
|
1142
|
+
var text = btn.closest('.wf-start').querySelector('.help-text');
|
|
1143
|
+
text.hidden = !text.hidden;
|
|
1144
|
+
btn.setAttribute('aria-expanded', String(!text.hidden));
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1104
1147
|
card.querySelector('.workflow-hooks-btn').addEventListener('click', function () { openWorkflowHooks(name, card); });
|
|
1105
1148
|
// Add a schedule: the Schedules tab's form, with this workflow chosen.
|
|
1106
1149
|
card.querySelector('.workflow-add-schedule').addEventListener('click', function () {
|
|
@@ -1137,7 +1180,7 @@ ${consoleShellScript}
|
|
|
1137
1180
|
});
|
|
1138
1181
|
card.querySelector('.workflow-forget').addEventListener('click', function (ev) {
|
|
1139
1182
|
var btn = ev.currentTarget;
|
|
1140
|
-
if (btn.dataset.confirming !== '1') { btn.dataset.confirming = '1'; btn.textContent = 'Click again
|
|
1183
|
+
if (btn.dataset.confirming !== '1') { btn.dataset.confirming = '1'; btn.textContent = 'Click again: its schedules and webhooks go; its instructions stay as a skill for Chat'; return; }
|
|
1141
1184
|
fetchJsonRetryOnce(base, { method: 'DELETE' }).then(function () { loadWorkflowsSection(name); });
|
|
1142
1185
|
});
|
|
1143
1186
|
}
|
|
@@ -2327,7 +2370,7 @@ ${consoleShellScript}
|
|
|
2327
2370
|
var list = tasks.length
|
|
2328
2371
|
? '<div class="le-table-wrap"><table><thead><tr><th>Task</th><th>When</th><th>Status</th><th>Last run</th><th></th></tr></thead><tbody>' + tasks.map(renderScheduleRow).join('') + '</tbody></table></div>'
|
|
2329
2372
|
: '<p class="hint">Nothing scheduled yet.</p>';
|
|
2330
|
-
return '<p class="muted" style="margin:0 0 20px">Workflows and messages this agent runs on a timer, like a cron job. A workflow schedule
|
|
2373
|
+
return '<p class="muted" style="margin:0 0 20px">Workflows and messages this agent runs on a timer, like a cron job. A workflow schedule runs the workflow's instructions as they are at each run. Each run works like an app calling it over HTTP: an approval goes wherever the Messaging tab sends it, and with nowhere set, a call that needs approval is denied. The agent can also schedule its own tasks when asked in Chat (“remind me tomorrow at 9”); they appear here too.</p>' +
|
|
2331
2374
|
'<section><h3>Scheduled (' + active.length + ' active)</h3>' + list + '</section>' +
|
|
2332
2375
|
'<section><h3>New schedule</h3>' +
|
|
2333
2376
|
'<form class="add-source" id="scheduleForm" style="max-width:560px">' +
|
|
@@ -56,8 +56,8 @@ export const consoleShellScript = `<script>
|
|
|
56
56
|
// ability on the Apps tab (id 'abilities', so older links still work).
|
|
57
57
|
// ?tab=tools and ?tab=skills still open Advanced.
|
|
58
58
|
var TABS = [
|
|
59
|
-
['overview', 'Overview'], ['workflows', 'Workflows'], ['
|
|
60
|
-
['messaging', 'Messaging'], ['schedules', 'Schedules'], ['env', 'Environment'], ['advanced', 'Advanced']
|
|
59
|
+
['overview', 'Overview'], ['workflows', 'Workflows'], ['abilities', 'Apps'], ['actauth', 'Permissions'],
|
|
60
|
+
['messaging', 'Messaging'], ['schedules', 'Schedules'], ['env', 'Environment'], ['advanced', 'Advanced'], ['chat', 'Chat']
|
|
61
61
|
];
|
|
62
62
|
|
|
63
63
|
function esc(s) {
|
|
@@ -72,11 +72,13 @@ export const consoleShellScript = `<script>
|
|
|
72
72
|
return '/agents/config?agent=' + enc(name) + (tab && tab !== 'overview' ? '&tab=' + enc(tab) : '');
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
//
|
|
76
|
-
//
|
|
75
|
+
// An agent in the list opens its Overview. Switching agents keeps you
|
|
76
|
+
// where you are: on a config tab you land on the same tab for the other
|
|
77
|
+
// agent, and in Chat on its Chat.
|
|
77
78
|
function agentHref(name) {
|
|
78
79
|
if (page === 'agent-config') return tabHref(name, params.get('tab') || 'overview');
|
|
79
|
-
return tabHref(name, 'chat');
|
|
80
|
+
if (page === 'chat') return tabHref(name, 'chat');
|
|
81
|
+
return tabHref(name, 'overview');
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
function renderList() {
|
|
@@ -207,10 +207,13 @@ export declare function prepareWorkflowRun(agentName: string, agentDir: string,
|
|
|
207
207
|
* matter is kept. A body with `start_message` (and no `instructions`) is
|
|
208
208
|
* still taken, as before. */
|
|
209
209
|
export declare function updateWorkflow(agentDir: string, id: string, input: Record<string, unknown>): Workflow;
|
|
210
|
-
/**
|
|
211
|
-
*
|
|
210
|
+
/** Removes a workflow: its skill stays, as a plain skill the agent can
|
|
211
|
+
* still use in chat, and what started it goes: its schedules are
|
|
212
|
+
* cancelled and its webhooks deleted (their URLs stop working). Returns
|
|
213
|
+
* how many of each. */
|
|
212
214
|
export declare function forgetWorkflow(agentName: string, agentDir: string, id: string): Promise<{
|
|
213
215
|
cancelled: number;
|
|
216
|
+
webhooks: number;
|
|
214
217
|
}>;
|
|
215
218
|
export declare function agentDirIn(projectDir: string, name: string): string;
|
|
216
219
|
export {};
|
|
@@ -11,7 +11,7 @@ import { writeSkill } from './skills-admin.js';
|
|
|
11
11
|
import { createFromTemplateObject, installFailureReason, templateChecklist } from './templates-admin.js';
|
|
12
12
|
import { templateScheduleStart } from '../core/system-agents/templates.js';
|
|
13
13
|
import { planParameters, planWebhook, planWorkflowWithModel, slugify, workflowTemplate } from '../core/system-agents/workflow-planner.js';
|
|
14
|
-
import { createTrigger, listTriggers, publicTrigger } from '../core/workflow-triggers.js';
|
|
14
|
+
import { createTrigger, getTriggerStore, listTriggers, publicTrigger } from '../core/workflow-triggers.js';
|
|
15
15
|
import { mcpAbilityNames } from '../core/mcp-ability-generator.js';
|
|
16
16
|
import { clearWorkflowFields, migrateWorkflowsFile, readWorkflow, readWorkflows, setWorkflowFields, validateParams, workflowRunMessage } from '../core/workflows.js';
|
|
17
17
|
import { isUnderWay, listRuns } from '../core/workflow-runs.js';
|
|
@@ -634,8 +634,10 @@ export function updateWorkflow(agentDir, id, input) {
|
|
|
634
634
|
setWorkflowFields(agentDir, id, instructions !== undefined ? { ...(title ? { title } : {}), instructions, parameters } : { ...(title ? { title } : {}), startMessage, ...(trigger ? { trigger } : {}), parameters });
|
|
635
635
|
return readWorkflow(agentDir, id);
|
|
636
636
|
}
|
|
637
|
-
/**
|
|
638
|
-
*
|
|
637
|
+
/** Removes a workflow: its skill stays, as a plain skill the agent can
|
|
638
|
+
* still use in chat, and what started it goes: its schedules are
|
|
639
|
+
* cancelled and its webhooks deleted (their URLs stop working). Returns
|
|
640
|
+
* how many of each. */
|
|
639
641
|
export async function forgetWorkflow(agentName, agentDir, id) {
|
|
640
642
|
clearWorkflowFields(agentDir, id);
|
|
641
643
|
let cancelled = 0;
|
|
@@ -643,7 +645,12 @@ export async function forgetWorkflow(agentName, agentDir, id) {
|
|
|
643
645
|
await cancelScheduledTask(task.task_id);
|
|
644
646
|
cancelled++;
|
|
645
647
|
}
|
|
646
|
-
|
|
648
|
+
let webhooks = 0;
|
|
649
|
+
for (const trigger of await listTriggers(agentName, id)) {
|
|
650
|
+
await getTriggerStore().remove(trigger.trigger_id);
|
|
651
|
+
webhooks++;
|
|
652
|
+
}
|
|
653
|
+
return { cancelled, webhooks };
|
|
647
654
|
}
|
|
648
655
|
export function agentDirIn(projectDir, name) {
|
|
649
656
|
return join(projectDir, 'agents', name);
|