loopengine 0.3.6 → 0.3.8
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.
|
@@ -131,7 +131,10 @@ export function validateWorkflowPlan(raw) {
|
|
|
131
131
|
if (!raw || typeof raw !== 'object')
|
|
132
132
|
return undefined;
|
|
133
133
|
const r = raw;
|
|
134
|
-
|
|
134
|
+
// A name for people: one written as an id ("daily-hn-digest") becomes
|
|
135
|
+
// words ("Daily hn digest").
|
|
136
|
+
const rawName = str(r.name, 80);
|
|
137
|
+
const name = rawName && /^[a-z0-9]+(-[a-z0-9]+)+$/.test(rawName) ? rawName.charAt(0).toUpperCase() + rawName.slice(1).replace(/-/g, ' ') : rawName;
|
|
135
138
|
const summary = str(r.summary, 600);
|
|
136
139
|
const skillRaw = (r.skill ?? {});
|
|
137
140
|
const skillBody = str(skillRaw.body, 12000);
|
package/dist/core/workflows.d.ts
CHANGED
|
@@ -26,6 +26,9 @@ export interface Workflow {
|
|
|
26
26
|
* `startMessage` (from before) is kept for workflows saved that way. */
|
|
27
27
|
export interface WorkflowFields {
|
|
28
28
|
title?: string;
|
|
29
|
+
/** The skill's one-line description: what the job is, which chat
|
|
30
|
+
* matches requests against. Kept as it is when not given. */
|
|
31
|
+
description?: string;
|
|
29
32
|
instructions?: string;
|
|
30
33
|
startMessage?: string;
|
|
31
34
|
trigger?: string;
|
package/dist/core/workflows.js
CHANGED
|
@@ -97,7 +97,8 @@ function workflowFrom(id, frontmatter, body) {
|
|
|
97
97
|
return undefined;
|
|
98
98
|
return {
|
|
99
99
|
id,
|
|
100
|
-
|
|
100
|
+
// A title that's just the id (a plan once saved it that way) reads as words.
|
|
101
|
+
title: typeof frontmatter.title === 'string' && frontmatter.title.trim() && frontmatter.title.trim() !== id ? frontmatter.title.trim() : titleFromId(id),
|
|
101
102
|
description: typeof frontmatter.description === 'string' ? frontmatter.description : '',
|
|
102
103
|
startMessage,
|
|
103
104
|
trigger: typeof frontmatter.trigger === 'string' && frontmatter.trigger.trim() ? frontmatter.trigger.trim() : 'When asked',
|
|
@@ -141,6 +142,8 @@ export function setWorkflowFields(agentDir, id, fields) {
|
|
|
141
142
|
const frontmatter = { ...skill.frontmatter };
|
|
142
143
|
for (const key of WORKFLOW_KEYS)
|
|
143
144
|
delete frontmatter[key];
|
|
145
|
+
if (fields.description)
|
|
146
|
+
frontmatter.description = fields.description;
|
|
144
147
|
const parameters = fields.parameters && Object.keys(fields.parameters).length ? { parameters: fields.parameters } : {};
|
|
145
148
|
if (fields.instructions !== undefined) {
|
|
146
149
|
writeSkillFile(path, { ...frontmatter, workflow: true, ...(fields.title ? { title: fields.title } : {}), ...parameters }, fields.instructions);
|
|
@@ -49,6 +49,8 @@ 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-card.workflow-focus { box-shadow: 0 0 0 2px var(--le-accent, #0f766e); transition: box-shadow 0.3s; }
|
|
53
|
+
tr.schedule-focus td { background: var(--le-accent-soft, #e6f4ef); transition: background 0.3s; }
|
|
52
54
|
.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
55
|
.workflow-starts h4 { margin: 0; font-size: 13.5px; font-weight: 600; }
|
|
54
56
|
.wf-start { padding: 10px 0; border-top: 1px solid var(--le-line, #e5e7eb); display: grid; gap: 6px; font-size: 13px; }
|
|
@@ -709,7 +711,7 @@ ${consoleShellScript}
|
|
|
709
711
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
710
712
|
}
|
|
711
713
|
|
|
712
|
-
//
|
|
714
|
+
// Ways to run it: four, each with a ? that says what it is. A
|
|
713
715
|
// schedule (pause, resume, remove, add), a webhook, Run now with the
|
|
714
716
|
// values a run is given, and the API call an app makes.
|
|
715
717
|
function wfStartSection(key, title, help, body) {
|
|
@@ -722,14 +724,15 @@ ${consoleShellScript}
|
|
|
722
724
|
function workflowStartsHtml(w, agentName, paramsHtml) {
|
|
723
725
|
var schedules = (w.schedules || []).map(function (x) {
|
|
724
726
|
var state = x.waiting ? '<span class="le-pill ask">Waiting for a decision</span>'
|
|
725
|
-
: x.status === 'paused' ? '<span class="le-pill">Paused</span>'
|
|
727
|
+
: x.status === 'paused' ? '<span class="le-pill warn">Paused</span>'
|
|
726
728
|
: x.status === 'running' ? '<span class="le-pill accent">Running</span>'
|
|
727
729
|
: (x.nextRunAt ? '<span class="hint">next ' + escapeHtml(new Date(x.nextRunAt).toLocaleString()) + '</span>' : '');
|
|
728
730
|
var values = x.params ? Object.keys(x.params).map(function (k) { return escapeHtml(k) + ': ' + escapeHtml(x.params[k]); }).join(' ' + String.fromCharCode(183) + ' ') : '';
|
|
729
731
|
return '<li style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">' +
|
|
730
|
-
'<span
|
|
732
|
+
'<span><a href="?agent=' + encodeURIComponent(agentName) + '&tab=schedules#schedule-' + escapeHtml(x.taskId) + '" class="workflow-schedule-link" data-task="' + escapeHtml(x.taskId) + '" title="See it in the Schedules tab">' +
|
|
733
|
+
escapeHtml(x.describe.charAt(0).toUpperCase() + x.describe.slice(1)) + '</a></span>' + state +
|
|
731
734
|
(values ? '<span class="hint">' + values + '</span>' : '') +
|
|
732
|
-
(x.notify ? '<span class="hint">
|
|
735
|
+
(x.notify ? '<span class="hint">results go to your messaging app</span>' : '') +
|
|
733
736
|
'<span style="display:flex;gap:6px;margin-left:auto">' +
|
|
734
737
|
(x.status === 'paused' ? '<button type="button" class="workflow-schedule-action" data-id="' + escapeHtml(x.taskId) + '" data-action="resume">Resume</button>'
|
|
735
738
|
: x.status === 'scheduled' ? '<button type="button" class="workflow-schedule-action" data-id="' + escapeHtml(x.taskId) + '" data-action="pause">Pause</button>' : '') +
|
|
@@ -737,11 +740,11 @@ ${consoleShellScript}
|
|
|
737
740
|
'</span></li>';
|
|
738
741
|
}).join('');
|
|
739
742
|
var paramNames = Object.keys(w.parameters || {});
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
+
// A body that shows what a call can carry: its values, and waiting
|
|
744
|
+
// for the result.
|
|
745
|
+
var example = '{ ' + (paramNames.length ? '"params": { ' + paramNames.map(function (p) { return '"' + p + '": "' + String.fromCharCode(8230) + '"'; }).join(', ') + ' }, ' : '') + '"wait": true }';
|
|
743
746
|
var apiUrl = location.origin + '/agents/' + encodeURIComponent(agentName) + '/workflows/' + encodeURIComponent(w.id) + '/run';
|
|
744
|
-
return '<div class="workflow-starts"><h4>
|
|
747
|
+
return '<div class="workflow-starts"><h4>Ways to run it</h4>' +
|
|
745
748
|
wfStartSection('schedule-' + w.id, 'Schedule',
|
|
746
749
|
'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
750
|
(schedules ? '<ul class="schedule-history">' + schedules + '</ul>' : '<span class="hint">None yet.</span>') +
|
|
@@ -756,7 +759,9 @@ ${consoleShellScript}
|
|
|
756
759
|
'<div><button type="button" class="workflow-run primary">Run now</button></div>') +
|
|
757
760
|
wfStartSection('api-' + w.id, 'API',
|
|
758
761
|
'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>'
|
|
762
|
+
'<pre>POST ' + escapeHtml(apiUrl) + String.fromCharCode(10) + escapeHtml(example) + '</pre>' +
|
|
763
|
+
'<p class="hint" style="margin:0">' + (paramNames.length ? '<code>params</code>: ' + paramNames.map(function (p) { return '<code>' + escapeHtml(p) + '</code>' + (w.parameters[p].required ? ' (required)' : '') + (w.parameters[p].description ? ', ' + escapeHtml(w.parameters[p].description) : ''); }).join('; ') + '. ' : 'It takes no params. ') +
|
|
764
|
+
'Optional: <code>"wait": true</code> answers with the result; <code>"notify": true</code> sends it through the agent' + String.fromCharCode(8217) + 's messaging app; <code>"callback_url"</code> posts it to your app when the run ends. Leave them out to get the run' + String.fromCharCode(8217) + 's id back at once.</p>') +
|
|
760
765
|
'</div>';
|
|
761
766
|
}
|
|
762
767
|
|
|
@@ -771,7 +776,7 @@ ${consoleShellScript}
|
|
|
771
776
|
: s.waiting
|
|
772
777
|
? '<span class="le-pill ask">Waiting for a decision</span>'
|
|
773
778
|
: s.status === 'paused'
|
|
774
|
-
? '<span class="le-pill">Schedule paused</span>'
|
|
779
|
+
? '<span class="le-pill warn">Schedule paused</span>'
|
|
775
780
|
: s.status === 'running'
|
|
776
781
|
? '<span class="le-pill accent">Running</span>'
|
|
777
782
|
: '<span class="le-pill good">On</span>' + (s.nextRunAt ? ' <span class="hint">next ' + escapeHtml(new Date(s.nextRunAt).toLocaleString()) + '</span>' : '');
|
|
@@ -833,6 +838,53 @@ ${consoleShellScript}
|
|
|
833
838
|
var workflowsRefreshTimer = null;
|
|
834
839
|
// Set by a workflow's "Add a schedule", for the Schedules tab's form.
|
|
835
840
|
var pendingScheduleWorkflow = null;
|
|
841
|
+
// Set by a schedule's workflow link: the card to scroll to and mark.
|
|
842
|
+
var pendingWorkflowFocus = null;
|
|
843
|
+
|
|
844
|
+
function focusWorkflowCard(tries) {
|
|
845
|
+
if (!pendingWorkflowFocus) return;
|
|
846
|
+
var card = detail.querySelector('.workflow-card[data-workflow="' + CSS.escape(pendingWorkflowFocus) + '"]');
|
|
847
|
+
if (!card) {
|
|
848
|
+
if ((tries || 0) < 40) setTimeout(function () { focusWorkflowCard((tries || 0) + 1); }, 150);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
pendingWorkflowFocus = null;
|
|
852
|
+
card.scrollIntoView({ block: 'start' });
|
|
853
|
+
card.classList.add('workflow-focus');
|
|
854
|
+
setTimeout(function () { card.classList.remove('workflow-focus'); }, 2500);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Set by a workflow's Edit on the Skills list: the workflow to open in
|
|
858
|
+
// its editor once the Workflows tab has drawn it.
|
|
859
|
+
var pendingWorkflowEdit = null;
|
|
860
|
+
|
|
861
|
+
function openPendingWorkflowEdit() {
|
|
862
|
+
if (!pendingWorkflowEdit) return;
|
|
863
|
+
var card = detail.querySelector('.workflow-card[data-workflow="' + CSS.escape(pendingWorkflowEdit) + '"]');
|
|
864
|
+
if (!card) return;
|
|
865
|
+
pendingWorkflowEdit = null;
|
|
866
|
+
openWorkflowEditor(currentName, card);
|
|
867
|
+
card.scrollIntoView({ block: 'start' });
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Set by a schedule on a workflow card (or a #schedule-<id> link): the
|
|
871
|
+
// Schedules tab's row to show.
|
|
872
|
+
var pendingScheduleFocus = /^#schedule-/.test(location.hash) ? location.hash.slice('#schedule-'.length) : null;
|
|
873
|
+
|
|
874
|
+
// Scrolls to that row and marks it for a moment, once the tab has drawn
|
|
875
|
+
// it (it may still be loading).
|
|
876
|
+
function focusScheduleRow(tries) {
|
|
877
|
+
if (!pendingScheduleFocus) return;
|
|
878
|
+
var row = detail.querySelector('#schedule-' + CSS.escape(pendingScheduleFocus));
|
|
879
|
+
if (!row) {
|
|
880
|
+
if ((tries || 0) < 40) setTimeout(function () { focusScheduleRow((tries || 0) + 1); }, 150);
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
pendingScheduleFocus = null;
|
|
884
|
+
row.scrollIntoView({ block: 'center' });
|
|
885
|
+
row.classList.add('schedule-focus');
|
|
886
|
+
setTimeout(function () { row.classList.remove('schedule-focus'); }, 2500);
|
|
887
|
+
}
|
|
836
888
|
var workflowsById = {};
|
|
837
889
|
// Set while a workflow's editor is open, so the running-run refresh
|
|
838
890
|
// doesn't redraw the card under someone typing.
|
|
@@ -852,6 +904,7 @@ ${consoleShellScript}
|
|
|
852
904
|
el.innerHTML = renderWorkflowsHtml(name, workflows);
|
|
853
905
|
var cards = el.querySelectorAll('.workflow-card');
|
|
854
906
|
for (var i = 0; i < cards.length; i++) wireWorkflowCard(name, cards[i]);
|
|
907
|
+
openPendingWorkflowEdit();
|
|
855
908
|
// Keep a running run's status current while the Overview is open.
|
|
856
909
|
var running = workflows.some(function (w) { return (w.recentRuns || []).some(function (x) { return x.status === 'running'; }); });
|
|
857
910
|
if (running && currentTab === 'workflows') workflowsRefreshTimer = setTimeout(function () { if (!workflowEditing) loadWorkflowsSection(name); }, 3000);
|
|
@@ -871,7 +924,10 @@ ${consoleShellScript}
|
|
|
871
924
|
function workflowEditorHtml(w) {
|
|
872
925
|
var rows = Object.keys(w.parameters || {}).map(function (p) { return workflowParamRowHtml(p, w.parameters[p]); }).join('');
|
|
873
926
|
return '<form class="add-source workflow-editor-form" style="margin-top:10px">' +
|
|
874
|
-
'<label>Title<input name="title" maxlength="120" value="' + escapeHtml(w.title) + '"
|
|
927
|
+
'<label>Title<input name="title" maxlength="120" value="' + escapeHtml(w.title) + '">' +
|
|
928
|
+
'<span class="hint">ID: <code>' + escapeHtml(w.id) + '</code>, used by the API, schedules and webhooks; it doesn' + String.fromCharCode(8217) + 't change when you edit.</span></label>' +
|
|
929
|
+
'<label>Description <span class="hint">(one line on what this job is; the agent uses it to recognise the job when you ask in Chat)</span>' +
|
|
930
|
+
'<input name="description" maxlength="500" required value="' + escapeHtml(w.description) + '"></label>' +
|
|
875
931
|
'<label>Instructions <span class="hint">(what the agent does, step by step; $name puts in a parameter's value. Chat uses them too.)</span>' +
|
|
876
932
|
'<textarea name="instructions" maxlength="12000" rows="12" required>' + escapeHtml(w.instructions) + '</textarea></label>' +
|
|
877
933
|
'<div class="body-field">Parameters <span class="hint">(values a run is given; apps send them as params)</span>' +
|
|
@@ -938,7 +994,7 @@ ${consoleShellScript}
|
|
|
938
994
|
result.textContent = 'Saving' + String.fromCharCode(8230);
|
|
939
995
|
fetchJsonRetryOnce('/agents/' + encodeURIComponent(name) + '/workflows/' + encodeURIComponent(w.id), {
|
|
940
996
|
method: 'PUT', headers: { 'content-type': 'application/json' },
|
|
941
|
-
body: JSON.stringify({ title: form.title.value, instructions: form.instructions.value, parameters: parameters }),
|
|
997
|
+
body: JSON.stringify({ title: form.title.value, description: form.description.value, instructions: form.instructions.value, parameters: parameters }),
|
|
942
998
|
}).then(function (r) {
|
|
943
999
|
if (!r.ok) { result.textContent = r.body.error || 'Could not save'; return; }
|
|
944
1000
|
workflowEditing = false;
|
|
@@ -1134,6 +1190,17 @@ ${consoleShellScript}
|
|
|
1134
1190
|
var result = card.querySelector('.workflow-result');
|
|
1135
1191
|
var base = '/agents/' + encodeURIComponent(name) + '/workflows/' + encodeURIComponent(id);
|
|
1136
1192
|
card.querySelector('.workflow-edit').addEventListener('click', function () { openWorkflowEditor(name, card); });
|
|
1193
|
+
// A schedule opens its own row in the Schedules tab.
|
|
1194
|
+
var scheduleLinks = card.querySelectorAll('.workflow-schedule-link');
|
|
1195
|
+
for (var sl = 0; sl < scheduleLinks.length; sl++) {
|
|
1196
|
+
scheduleLinks[sl].addEventListener('click', function (ev) {
|
|
1197
|
+
if (ev.metaKey || ev.ctrlKey || ev.shiftKey) return;
|
|
1198
|
+
ev.preventDefault();
|
|
1199
|
+
pendingScheduleFocus = ev.currentTarget.dataset.task;
|
|
1200
|
+
switchTab('schedules');
|
|
1201
|
+
focusScheduleRow();
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1137
1204
|
// The ? beside each way it starts shows or hides what it is.
|
|
1138
1205
|
var helps = card.querySelectorAll('.help-q');
|
|
1139
1206
|
for (var hq = 0; hq < helps.length; hq++) {
|
|
@@ -2345,10 +2412,11 @@ ${consoleShellScript}
|
|
|
2345
2412
|
return '<li><span class="le-pill ' + (h.status === 'ok' ? 'good' : 'bad') + '">' + escapeHtml(h.status) + '</span> <span class="hint">' + escapeHtml(fmtScheduleTime(h.ran_at)) + '</span>' +
|
|
2346
2413
|
'<div class="schedule-result">' + escapeHtml(h.status === 'ok' ? (h.result || '') : (h.error || '')) + '</div></li>';
|
|
2347
2414
|
}).join('');
|
|
2348
|
-
return '<tr>' +
|
|
2415
|
+
return '<tr id="schedule-' + escapeHtml(t.task_id) + '">' +
|
|
2349
2416
|
'<td style="min-width:180px"><strong>' + escapeHtml(t.task_name || 'Untitled') + '</strong>' +
|
|
2350
2417
|
(t.workflow
|
|
2351
|
-
? '<div><span class="le-pill accent">Workflow</span> ' + escapeHtml(t.workflow.
|
|
2418
|
+
? '<div><span class="le-pill accent">Workflow</span> <a href="?agent=' + encodeURIComponent(currentName) + '&tab=workflows" class="schedule-workflow-link" data-workflow="' + escapeHtml(t.workflow.id) + '" title="See it on the Workflows tab">' + escapeHtml(t.workflow.title) + '</a></div>' +
|
|
2419
|
+
'<div class="schedule-result hint">' + escapeHtml(t.workflow.description || t.workflow.start_message) +
|
|
2352
2420
|
(t.params ? '<br>' + Object.keys(t.params).map(function (k) { return escapeHtml(k) + ': ' + escapeHtml(t.params[k]); }).join(' ' + String.fromCharCode(183) + ' ') : '') + '</div>'
|
|
2353
2421
|
: t.workflow_missing
|
|
2354
2422
|
? '<div><span class="le-pill bad">Workflow removed</span> <span class="hint">' + escapeHtml(t.workflow_id) + '</span></div>'
|
|
@@ -2381,7 +2449,7 @@ ${consoleShellScript}
|
|
|
2381
2449
|
return '<option value="' + escapeHtml(w.id) + '">' + escapeHtml(w.title) + '</option>';
|
|
2382
2450
|
}).join('') + '</select></div>' +
|
|
2383
2451
|
'<div class="workflow-params" id="scheduleParams" style="margin:0 0 4px 22px"></div>' +
|
|
2384
|
-
'<p class="hint" style="margin:0 0 4px 22px">Each run
|
|
2452
|
+
'<p class="hint" style="margin:0 0 4px 22px">Each run follows the workflow's instructions as they are then, so changing the workflow changes what runs.</p>' +
|
|
2385
2453
|
'<div class="schedule-when"><input type="radio" name="runs" value="message" id="runsMessage"><label for="runsMessage">A message</label></div>' +
|
|
2386
2454
|
'</div>'
|
|
2387
2455
|
: '') +
|
|
@@ -2422,6 +2490,17 @@ ${consoleShellScript}
|
|
|
2422
2490
|
function wireSchedulesHandlers(name) {
|
|
2423
2491
|
var content = schedulesContentEl();
|
|
2424
2492
|
if (!content) return;
|
|
2493
|
+
// A schedule's workflow opens its card on the Workflows tab.
|
|
2494
|
+
var workflowLinks = content.querySelectorAll('.schedule-workflow-link');
|
|
2495
|
+
for (var wl = 0; wl < workflowLinks.length; wl++) {
|
|
2496
|
+
workflowLinks[wl].addEventListener('click', function (ev) {
|
|
2497
|
+
if (ev.metaKey || ev.ctrlKey || ev.shiftKey) return;
|
|
2498
|
+
ev.preventDefault();
|
|
2499
|
+
pendingWorkflowFocus = ev.currentTarget.dataset.workflow;
|
|
2500
|
+
switchTab('workflows');
|
|
2501
|
+
focusWorkflowCard();
|
|
2502
|
+
});
|
|
2503
|
+
}
|
|
2425
2504
|
var actionBtns = content.querySelectorAll('.schedule-action');
|
|
2426
2505
|
for (var i = 0; i < actionBtns.length; i++) {
|
|
2427
2506
|
actionBtns[i].addEventListener('click', function (ev) {
|
|
@@ -2480,6 +2559,7 @@ ${consoleShellScript}
|
|
|
2480
2559
|
form.scrollIntoView({ block: 'start' });
|
|
2481
2560
|
}
|
|
2482
2561
|
pendingScheduleWorkflow = null;
|
|
2562
|
+
focusScheduleRow();
|
|
2483
2563
|
renderScheduleParams();
|
|
2484
2564
|
if (workflowSelect) workflowSelect.addEventListener('change', renderScheduleParams);
|
|
2485
2565
|
for (var k = 0; k < runsRadios.length; k++) {
|
|
@@ -3510,12 +3590,17 @@ ${consoleShellScript}
|
|
|
3510
3590
|
// needs a further per-skill GET, to pull the body that index doesn't
|
|
3511
3591
|
// carry. ----
|
|
3512
3592
|
|
|
3593
|
+
// A skill that's a workflow is labelled, and its Edit opens the workflow
|
|
3594
|
+
// editor: its instructions are one text there, with its title,
|
|
3595
|
+
// description and parameters.
|
|
3513
3596
|
function renderSkillCard(s) {
|
|
3514
3597
|
return '<div class="source">' +
|
|
3515
3598
|
'<div class="source-head">' +
|
|
3516
|
-
'<h4>' + escapeHtml(s.name) + '</h4>' +
|
|
3599
|
+
'<h4>' + escapeHtml(s.name) + (s.workflow ? ' <span class="le-pill accent">Workflow</span>' : '') + '</h4>' +
|
|
3517
3600
|
'<span>' +
|
|
3518
|
-
|
|
3601
|
+
(s.workflow
|
|
3602
|
+
? '<button type="button" class="edit-workflow-skill-btn" data-id="' + escapeHtml(s.name) + '" title="Edit it as a workflow, on the Workflows tab">Edit</button> '
|
|
3603
|
+
: '<button type="button" class="edit-skill-btn" data-id="' + escapeHtml(s.name) + '">Edit</button> ') +
|
|
3519
3604
|
'<button type="button" class="delete-btn delete-skill-btn" data-id="' + escapeHtml(s.name) + '" title="Delete this skill" aria-label="Delete this skill">Delete</button>' +
|
|
3520
3605
|
'</span>' +
|
|
3521
3606
|
'</div>' +
|
|
@@ -3623,6 +3708,15 @@ ${consoleShellScript}
|
|
|
3623
3708
|
});
|
|
3624
3709
|
}
|
|
3625
3710
|
|
|
3711
|
+
var workflowEditButtons = panel.querySelectorAll('.edit-workflow-skill-btn');
|
|
3712
|
+
for (var we = 0; we < workflowEditButtons.length; we++) {
|
|
3713
|
+
workflowEditButtons[we].addEventListener('click', function (ev) {
|
|
3714
|
+
pendingWorkflowEdit = ev.currentTarget.getAttribute('data-id');
|
|
3715
|
+
switchTab('workflows');
|
|
3716
|
+
openPendingWorkflowEdit();
|
|
3717
|
+
});
|
|
3718
|
+
}
|
|
3719
|
+
|
|
3626
3720
|
var editButtons = panel.querySelectorAll('.edit-skill-btn');
|
|
3627
3721
|
for (var i = 0; i < editButtons.length; i++) {
|
|
3628
3722
|
editButtons[i].addEventListener('click', function (ev) {
|
|
@@ -4413,7 +4507,7 @@ ${consoleShellScript}
|
|
|
4413
4507
|
'<div class="tab-panel" data-tab-panel="overview">' + renderOverviewHtml(cfg) + '</div>' +
|
|
4414
4508
|
// Workflows: the jobs this agent does, each with what starts it.
|
|
4415
4509
|
'<div class="tab-panel" data-tab-panel="workflows">' +
|
|
4416
|
-
'<p class="muted" style="margin:0 0 20px">The jobs this agent does on its own: what it does,
|
|
4510
|
+
'<p class="muted" style="margin:0 0 20px">The jobs this agent does on its own: what it does, the ways to run it (a schedule, a webhook, Run now or an app's API call) and how its runs went. In Chat it can do them too, when you ask.</p>' +
|
|
4417
4511
|
'<section id="workflowsSection"><div id="workflowsContent"><p class="hint">Loading…</p></div></section>' +
|
|
4418
4512
|
'</div>' +
|
|
4419
4513
|
// Advanced: the agent's own tools (and Composio, subagents), then its
|
|
@@ -614,6 +614,9 @@ export function updateWorkflow(agentDir, id, input) {
|
|
|
614
614
|
return trimmed;
|
|
615
615
|
};
|
|
616
616
|
const title = text(input.title, 'The title', 120);
|
|
617
|
+
const description = input.description === undefined ? undefined : text(input.description, 'The description', 500);
|
|
618
|
+
if (description !== undefined && !description)
|
|
619
|
+
throw new WorkflowError('Write a one-line description: what this job is, e.g. "Daily digest of AI-agent stories on Hacker News". The agent uses it to recognise the job in Chat.');
|
|
617
620
|
const instructions = input.instructions === undefined ? undefined : text(input.instructions, 'The instructions', 12000);
|
|
618
621
|
if (instructions !== undefined && !instructions)
|
|
619
622
|
throw new WorkflowError('Write the instructions: what the agent does, e.g. "Check the refund request for order $order_number: look up the order, then…"');
|
|
@@ -631,7 +634,8 @@ export function updateWorkflow(agentDir, id, input) {
|
|
|
631
634
|
const s = spec && typeof spec === 'object' ? spec : {};
|
|
632
635
|
parameters[name] = { description: text(s.description, `The description of "${name}"`, 300), required: s.required === true };
|
|
633
636
|
}
|
|
634
|
-
|
|
637
|
+
const named = { ...(title ? { title } : {}), ...(description ? { description: description.replace(/\s+/g, ' ') } : {}) };
|
|
638
|
+
setWorkflowFields(agentDir, id, instructions !== undefined ? { ...named, instructions, parameters } : { ...named, startMessage, ...(trigger ? { trigger } : {}), parameters });
|
|
635
639
|
return readWorkflow(agentDir, id);
|
|
636
640
|
}
|
|
637
641
|
/** Removes a workflow: its skill stays, as a plain skill the agent can
|