loopengine 0.1.98 → 0.2.0
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/bin/ability-manager.d.ts +35 -2
- package/dist/bin/ability-manager.js +168 -35
- package/dist/core/ability-details.js +11 -0
- package/dist/core/mcp-abilities.d.ts +91 -0
- package/dist/core/mcp-abilities.js +400 -0
- package/dist/core/mcp-ability-generator.d.ts +59 -0
- package/dist/core/mcp-ability-generator.js +193 -0
- package/dist/core/mcp-apps.d.ts +95 -0
- package/dist/core/mcp-apps.js +254 -0
- package/dist/core/mcp-directory.d.ts +32 -0
- package/dist/core/mcp-directory.js +191 -0
- package/dist/core/run-agent.js +5 -2
- package/dist/core/system-agents/interviewer.d.ts +1 -1
- package/dist/core/system-agents/interviewer.js +1 -1
- package/dist/core/system-agents/templates.d.ts +11 -0
- package/dist/core/system-agents/templates.js +22 -16
- package/dist/core/system-agents/workflow-planner.d.ts +36 -3
- package/dist/core/system-agents/workflow-planner.js +23 -6
- package/dist/core/workspace-abilities.js +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.js +6 -2
- package/dist/web/abilities-admin.d.ts +1 -1
- package/dist/web/abilities-admin.js +1 -1
- package/dist/web/agents-config-page.js +187 -11
- package/dist/web/console-shell.js +7 -5
- package/dist/web/dev-ui-styles.js +1 -1
- package/dist/web/global-config-page.js +293 -13
- package/dist/web/interview-admin.js +2 -0
- package/dist/web/templates-admin.d.ts +10 -2
- package/dist/web/templates-admin.js +36 -3
- package/dist/web/templates-page.js +1 -0
- package/dist/web/workflow-admin.d.ts +46 -0
- package/dist/web/workflow-admin.js +195 -3
- package/dist/web/workflow-page.js +33 -3
- package/package.json +10 -1
|
@@ -78,6 +78,32 @@ export const globalConfigPageHtml = `<!doctype html>
|
|
|
78
78
|
form.key-form button { font-size: 12px; }
|
|
79
79
|
.key-msg { font-size: 11px; margin-top: 4px; }
|
|
80
80
|
.key-msg.bad { color: var(--bad); }
|
|
81
|
+
.app-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 10px; }
|
|
82
|
+
.app-tile { border: 1px solid var(--line); border-radius: var(--radius); padding: 12px; display: grid; gap: 8px; align-content: start; background: var(--bg); }
|
|
83
|
+
.app-head { display: flex; gap: 10px; align-items: center; min-width: 0; }
|
|
84
|
+
.app-head strong { overflow-wrap: anywhere; }
|
|
85
|
+
.app-icon { width: 32px; height: 32px; border-radius: 8px; flex: none; object-fit: contain; background: var(--surface); border: 1px solid var(--line); }
|
|
86
|
+
.app-letter { width: 32px; height: 32px; border-radius: 8px; flex: none; display: grid; place-items: center; font-weight: 700; font-size: 15px; background: var(--accent-soft, var(--surface)); color: var(--accent); border: 1px solid var(--line); }
|
|
87
|
+
.app-desc { font-size: 12.5px; color: var(--ink-muted); margin: 0; }
|
|
88
|
+
.app-foot { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
|
|
89
|
+
.app-form { display: grid; gap: 6px; }
|
|
90
|
+
.app-form input { font: inherit; font-size: 12px; font-family: ui-monospace, monospace; padding: 5px 7px; border: 1px solid var(--line); border-radius: 6px; background: var(--bg); color: var(--ink); width: 100%; box-sizing: border-box; }
|
|
91
|
+
.app-row { display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap; padding: 10px 12px; border-top: 1px solid var(--line); }
|
|
92
|
+
.app-row:first-child { border-top: 0; }
|
|
93
|
+
.app-row-main { flex: 1; min-width: 200px; display: grid; gap: 3px; }
|
|
94
|
+
.app-list { border: 1px solid var(--line); border-radius: var(--radius); }
|
|
95
|
+
.app-tools { display: grid; gap: 8px; margin-top: 8px; font-size: 12.5px; }
|
|
96
|
+
.app-tools input[type=search] { font: inherit; font-size: 12px; padding: 5px 7px; border: 1px solid var(--line); border-radius: 6px; background: var(--bg); color: var(--ink); max-width: 280px; }
|
|
97
|
+
.app-tool-list { display: grid; gap: 2px; max-height: 360px; overflow: auto; border: 1px solid var(--line); border-radius: var(--radius); padding: 4px; }
|
|
98
|
+
.app-tool { display: flex; gap: 8px; align-items: flex-start; padding: 5px 6px; border-radius: 6px; cursor: pointer; }
|
|
99
|
+
.app-tool:hover { background: var(--surface); }
|
|
100
|
+
.app-tool input { margin-top: 3px; }
|
|
101
|
+
.app-tool code { overflow-wrap: anywhere; }
|
|
102
|
+
.app-tool-desc { display: block; }
|
|
103
|
+
.app-decision { font: inherit; font-size: 11.5px; padding: 1px 4px; border: 1px solid var(--line); border-radius: 6px; background: var(--bg); color: var(--ink); }
|
|
104
|
+
.app-cats { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 10px; }
|
|
105
|
+
.app-cats button { font-size: 12px; }
|
|
106
|
+
.app-cats button.active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
|
|
81
107
|
</style>
|
|
82
108
|
</head>
|
|
83
109
|
<body data-le-page="config">
|
|
@@ -89,9 +115,14 @@ ${consoleSidebarHtml}
|
|
|
89
115
|
<div><h1>Models</h1><p>API keys for each model provider, and which agents use each one. A saved key is stored on this server and never shown again.</p></div></div></div>
|
|
90
116
|
<div class="le-scroll"><div class="le-page" style="display:block"><div id="modelsContent"><p class="hint">Loading…</p></div></div></div>
|
|
91
117
|
</div>
|
|
118
|
+
<div class="section-panel" data-section-panel="apps" style="display:none">
|
|
119
|
+
<div class="le-head plain"><div class="le-title"><button type="button" class="le-menu-btn" data-le-menu aria-label="Open navigation">☰</button>
|
|
120
|
+
<div><h1>Apps</h1><p>Connect the apps your agents work in. All of a connected app’s tools become available to the workspace, each with a default permission; each agent then chooses which tools it gets, from its Apps tab.</p></div></div></div>
|
|
121
|
+
<div class="le-scroll"><div class="le-page" style="display:block"><div id="appsContent"><p class="hint">Loading…</p></div></div></div>
|
|
122
|
+
</div>
|
|
92
123
|
<div class="section-panel" data-section-panel="abilities" style="display:none">
|
|
93
124
|
<div class="le-head plain"><div class="le-title"><button type="button" class="le-menu-btn" data-le-menu aria-label="Open navigation">☰</button>
|
|
94
|
-
<div><h1>Abilities</h1><p>
|
|
125
|
+
<div><h1>Abilities</h1><p>What agents can do beyond your connected apps: search the web, read documents, schedule work, or your own code. An ability added to an agent brings its tools, skills and permission rules.</p></div></div></div>
|
|
95
126
|
<div class="le-scroll"><div class="le-page" style="display:block"><div id="abilitiesContent"><p class="hint">Loading…</p></div></div></div>
|
|
96
127
|
</div>
|
|
97
128
|
<div class="section-panel" data-section-panel="settings" style="display:none">
|
|
@@ -221,12 +252,12 @@ ${consoleShellScript}
|
|
|
221
252
|
|
|
222
253
|
// ---- Abilities section ----
|
|
223
254
|
// How agents reach outside apps. Public abilities (npm) are browsed
|
|
224
|
-
// here and installed from an agent's own
|
|
255
|
+
// here and installed from an agent's own Apps tab. A private one
|
|
225
256
|
// (from the sources in Settings) is installed here into the workspace
|
|
226
257
|
// (core/workspace-abilities.ts), then added to any agent from its tab.
|
|
227
258
|
|
|
228
259
|
// An ability's details (tools with their permissions, skills, settings)
|
|
229
|
-
// come from the console shell, shared with each agent's
|
|
260
|
+
// come from the console shell, shared with each agent's Apps tab.
|
|
230
261
|
var toggleAbilityDetails = leShell.toggleAbilityDetails;
|
|
231
262
|
|
|
232
263
|
function detailsButton(spec) {
|
|
@@ -240,13 +271,13 @@ ${consoleShellScript}
|
|
|
240
271
|
function renderAbilities() {
|
|
241
272
|
var content = document.getElementById('abilitiesContent');
|
|
242
273
|
content.innerHTML = '<section>' +
|
|
243
|
-
'<h4>Public <span class="hint">npm packages tagged loopengine-ability; add one to an agent from its
|
|
274
|
+
'<h4>Public <span class="hint">npm packages tagged loopengine-ability; add one to an agent from its Apps tab</span></h4>' +
|
|
244
275
|
'<form id="abilitySearchForm" style="display:flex;gap:6px;margin:0 0 10px;max-width:560px">' +
|
|
245
276
|
'<input type="search" name="q" placeholder="Search abilities, e.g. gmail" aria-label="Search public abilities" style="flex:1">' +
|
|
246
277
|
'<button type="submit">Search</button>' +
|
|
247
278
|
'</form>' +
|
|
248
279
|
'<div id="abilitySearchResults"><p class="hint">Loading…</p></div>' +
|
|
249
|
-
'<h4 id="private-abilities">Private <span class="hint">your own, from the sources in <a href="/config?section=settings">Settings</a>; install one into the workspace, then add it to agents from their
|
|
280
|
+
'<h4 id="private-abilities">Private <span class="hint">your own, from the sources in <a href="/config?section=settings">Settings</a>; install one into the workspace, then add it to agents from their Apps tab</span></h4>' +
|
|
250
281
|
'<p id="abilityInstallStatus" class="hint"></p>' +
|
|
251
282
|
'<div id="workspaceAbilities"></div>' +
|
|
252
283
|
'<div id="privateAbilities"><p class="hint">Loading…</p></div>' +
|
|
@@ -296,7 +327,7 @@ ${consoleShellScript}
|
|
|
296
327
|
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
297
328
|
.then(function (r) {
|
|
298
329
|
if (!r.ok) throw new Error(r.body.error || 'upload failed');
|
|
299
|
-
status.textContent = 'Uploaded ' + label + ' into the workspace. Add it to an agent from the agent' + String.fromCharCode(8217) + 's
|
|
330
|
+
status.textContent = 'Uploaded ' + label + ' into the workspace. Add it to an agent from the agent' + String.fromCharCode(8217) + 's Apps tab.';
|
|
300
331
|
workspaceAbilities = r.body.abilities || [];
|
|
301
332
|
renderWorkspaceAbilities();
|
|
302
333
|
renderPrivateAbilities();
|
|
@@ -367,14 +398,17 @@ ${consoleShellScript}
|
|
|
367
398
|
function renderWorkspaceAbilities() {
|
|
368
399
|
var el = document.getElementById('workspaceAbilities');
|
|
369
400
|
if (!el || !workspaceAbilities) return;
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
401
|
+
// Abilities made from a connected app are listed under Apps.
|
|
402
|
+
var fromApps = workspaceAbilities.filter(function (a) { return a.app || a.source.indexOf('mcp:') === 0; }).length;
|
|
403
|
+
var own = workspaceAbilities.filter(function (a) { return !a.app && a.source.indexOf('mcp:') !== 0; });
|
|
404
|
+
el.innerHTML = (fromApps ? '<p class="hint" style="margin:0 0 8px">' + fromApps + ' connected app' + (fromApps === 1 ? '' : 's') + ' also in this workspace: see <a href="/config?section=apps">Apps</a>.</p>' : '') + (own.length
|
|
405
|
+
? '<div class="source"><p style="margin:0 0 8px"><strong>In this workspace</strong> <span class="hint">add these to an agent from its Apps tab</span></p>' +
|
|
406
|
+
'<table><thead><tr><th>Ability</th><th>From</th><th></th></tr></thead><tbody>' + own.map(function (a) {
|
|
373
407
|
return '<tr><td><code>' + escapeHtml(a.name) + '</code>' + (a.version ? ' <span class="hint">' + escapeHtml(a.version) + '</span>' : '') + '</td>' +
|
|
374
408
|
'<td><span class="hint">' + escapeHtml(a.source.indexOf('upload:') === 0 ? 'Uploaded ' + a.source.slice(7) : a.source) + '</span></td>' +
|
|
375
409
|
'<td style="white-space:nowrap">' + detailsButton(a.spec) + ' <button type="button" class="delete-btn workspace-remove-btn" data-name="' + escapeHtml(a.name) + '">Remove</button></td></tr>';
|
|
376
410
|
}).join('') + '</tbody></table></div>'
|
|
377
|
-
: '';
|
|
411
|
+
: '');
|
|
378
412
|
}
|
|
379
413
|
|
|
380
414
|
// Each source's abilities: Install puts one into the workspace; one
|
|
@@ -408,7 +442,7 @@ ${consoleShellScript}
|
|
|
408
442
|
}).join('');
|
|
409
443
|
}
|
|
410
444
|
|
|
411
|
-
// Linked from an agent's
|
|
445
|
+
// Linked from an agent's Apps tab (/config?section=abilities#private-abilities):
|
|
412
446
|
// once every list has loaded, so the public results don't push it down.
|
|
413
447
|
var scrolledToPrivate = false;
|
|
414
448
|
function scrollToPrivateIfLinked() {
|
|
@@ -472,7 +506,7 @@ ${consoleShellScript}
|
|
|
472
506
|
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
473
507
|
.then(function (r) {
|
|
474
508
|
if (!r.ok) throw new Error(r.body.error || 'request failed');
|
|
475
|
-
status.textContent = 'Installed ' + spec + ' into the workspace. Add it to an agent from the agent' + String.fromCharCode(8217) + 's
|
|
509
|
+
status.textContent = 'Installed ' + spec + ' into the workspace. Add it to an agent from the agent' + String.fromCharCode(8217) + 's Apps tab.';
|
|
476
510
|
workspaceAbilities = r.body.abilities || [];
|
|
477
511
|
renderWorkspaceAbilities();
|
|
478
512
|
renderPrivateAbilities();
|
|
@@ -503,6 +537,251 @@ ${consoleShellScript}
|
|
|
503
537
|
loadedSections.abilities = true;
|
|
504
538
|
}
|
|
505
539
|
|
|
540
|
+
// ---- Apps section ----
|
|
541
|
+
// Popular apps' own MCP servers (core/mcp-apps.ts) and the connected
|
|
542
|
+
// ones. Connecting makes a workspace ability from the server; agents
|
|
543
|
+
// add it from their Apps tab.
|
|
544
|
+
|
|
545
|
+
var apps = null;
|
|
546
|
+
var appCategory = 'All';
|
|
547
|
+
// Open tool panels, by ability: { tools, filter, error, busy }.
|
|
548
|
+
var openTools = {};
|
|
549
|
+
var appSearch = { q: '', servers: null, error: '' };
|
|
550
|
+
|
|
551
|
+
function appLetter(title) {
|
|
552
|
+
return '<span class="app-letter" aria-hidden="true">' + escapeHtml((title || '?').charAt(0).toUpperCase()) + '</span>';
|
|
553
|
+
}
|
|
554
|
+
function appIcon(app) {
|
|
555
|
+
return app.icon
|
|
556
|
+
? '<img class="app-icon" src="' + escapeHtml(app.icon) + '" alt="" data-title="' + escapeHtml(app.title || '') + '" loading="lazy" referrerpolicy="no-referrer">'
|
|
557
|
+
: appLetter(app.title);
|
|
558
|
+
}
|
|
559
|
+
// An icon that doesn't load becomes the app's first letter.
|
|
560
|
+
document.getElementById('appsContent').addEventListener('error', function (ev) {
|
|
561
|
+
var img = ev.target;
|
|
562
|
+
if (!img.classList || !img.classList.contains('app-icon')) return;
|
|
563
|
+
var holder = document.createElement('span');
|
|
564
|
+
holder.innerHTML = appLetter(img.dataset.title);
|
|
565
|
+
img.replaceWith(holder.firstChild);
|
|
566
|
+
}, true);
|
|
567
|
+
|
|
568
|
+
function connectForm(id) {
|
|
569
|
+
return '<form class="app-form" data-connect="' + escapeHtml(id) + '">' +
|
|
570
|
+
'<input type="password" name="key" autocomplete="off" placeholder="API key, if it needs one" aria-label="API key">' +
|
|
571
|
+
'<div class="app-foot"><button type="submit">Connect</button><button type="button" class="app-cancel">Cancel</button></div>' +
|
|
572
|
+
'<p class="hint app-msg" style="margin:0"></p></form>';
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function renderApps() {
|
|
576
|
+
var el = document.getElementById('appsContent');
|
|
577
|
+
if (!apps) return;
|
|
578
|
+
var categories = ['All'];
|
|
579
|
+
apps.apps.forEach(function (a) { if (categories.indexOf(a.category) === -1) categories.push(a.category); });
|
|
580
|
+
var shown = apps.apps.filter(function (a) { return appCategory === 'All' || a.category === appCategory; });
|
|
581
|
+
el.innerHTML =
|
|
582
|
+
(apps.canConnect ? '' : '<p class="error">Connecting an app saves its key on this server, so it needs LOOPENGINE_ADMIN_AUTH set first.</p>') +
|
|
583
|
+
'<section><h3>Connected</h3>' + (apps.connected.length
|
|
584
|
+
? '<div class="app-list">' + apps.connected.map(function (c) {
|
|
585
|
+
var used = c.agents.length ? 'Used by ' + c.agents.map(escapeHtml).join(', ') : 'Not added to an agent yet: add <code>' + escapeHtml(c.ability) + '</code> from an agent' + String.fromCharCode(8217) + 's Apps tab';
|
|
586
|
+
return '<div class="app-row"><div class="app-head">' + appIcon(findApp(c) || c) + '</div>' +
|
|
587
|
+
'<div class="app-row-main"><div><strong>' + escapeHtml(c.title) + '</strong> <span class="hint">' + c.tools.length + ' tool' + (c.tools.length === 1 ? '' : 's') + ' · <code>' + escapeHtml(c.ability) + '</code></span></div>' +
|
|
588
|
+
'<div class="hint">' + used + '</div>' +
|
|
589
|
+
(c.keyEnv && !c.keySet ? '<div class="error" style="font-size:12px">Its key, ' + escapeHtml(c.keyEnv) + ', isn' + String.fromCharCode(8217) + 't set.</div>' : '') +
|
|
590
|
+
(openTools[c.ability] ? toolsPanel(c.ability) : '') +
|
|
591
|
+
'</div>' +
|
|
592
|
+
'<div class="app-foot"><button type="button" class="app-tools-btn" data-ability="' + escapeHtml(c.ability) + '">' + (openTools[c.ability] ? 'Hide tools' : 'Tools') + '</button> <button type="button" class="delete-btn app-disconnect" data-ability="' + escapeHtml(c.ability) + '">Disconnect</button></div></div>';
|
|
593
|
+
}).join('') + '</div>'
|
|
594
|
+
: '<p class="hint">No apps connected yet. Pick one below.</p>') + '</section>' +
|
|
595
|
+
'<section><h3>Popular apps</h3>' +
|
|
596
|
+
'<div class="app-cats">' + categories.map(function (c) { return '<button type="button" class="' + (c === appCategory ? 'active' : '') + '" data-app-cat="' + escapeHtml(c) + '">' + escapeHtml(c) + '</button>'; }).join('') + '</div>' +
|
|
597
|
+
'<div class="app-grid">' + shown.map(function (a) {
|
|
598
|
+
return '<div class="app-tile" data-app="' + escapeHtml(a.id) + '"><div class="app-head">' + appIcon(a) + '<div><strong>' + escapeHtml(a.title) + '</strong><div class="hint">' + escapeHtml(a.category) + '</div></div></div>' +
|
|
599
|
+
'<p class="app-desc">' + escapeHtml(a.description) + '</p>' +
|
|
600
|
+
'<div class="app-slot">' + (a.connected ? '<span class="badge badge-custom">Connected</span>' : '<button type="button" class="app-connect" data-id="' + escapeHtml(a.id) + '"' + (apps.canConnect ? '' : ' disabled') + '>Connect</button>') + '</div></div>';
|
|
601
|
+
}).join('') + '</div>' +
|
|
602
|
+
'<p class="hint">Each is the app' + String.fromCharCode(8217) + 's own MCP server. Apps that only offer a sign-in (OAuth) can' + String.fromCharCode(8217) + 't be connected yet; their Connect says so.</p>' +
|
|
603
|
+
'</section>' +
|
|
604
|
+
'<section><h3>More apps</h3>' +
|
|
605
|
+
'<form id="appSearchForm" style="display:flex;gap:6px;margin:0 0 10px;max-width:560px"><input type="search" name="q" placeholder="Search the MCP Registry, e.g. calendly" aria-label="Search apps" style="flex:1" value="' + escapeHtml(appSearch.q) + '"><button type="submit">Search</button></form>' +
|
|
606
|
+
'<div id="appSearchResults">' + renderAppSearch() + '</div>' +
|
|
607
|
+
'</section>';
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
var DECISION_LABEL = { allow: 'Allow', ask: 'Ask me', deny: 'Never' };
|
|
611
|
+
|
|
612
|
+
function toolsPanel(ability) {
|
|
613
|
+
var panel = openTools[ability];
|
|
614
|
+
if (panel.error) return '<div class="app-tools"><p class="error" style="margin:0">' + escapeHtml(panel.error) + '</p></div>';
|
|
615
|
+
if (!panel.tools) return '<div class="app-tools"><p class="hint" style="margin:0">Asking the app for its tools' + String.fromCharCode(8230) + '</p></div>';
|
|
616
|
+
var used = panel.tools.filter(function (t) { return t.usedBy.length; }).length;
|
|
617
|
+
var f = (panel.filter || '').toLowerCase();
|
|
618
|
+
var shown = panel.tools.filter(function (t) { return !f || (t.remote + ' ' + t.description).toLowerCase().indexOf(f) !== -1; });
|
|
619
|
+
return '<div class="app-tools">' +
|
|
620
|
+
'<p class="hint" style="margin:0">All ' + panel.tools.length + ' tools are available to this workspace; ' + used + ' are used by an agent. Agents choose their own tools and permissions when you add the app to them (their Apps tab). The permission here is the default an agent starts from: Allow runs it, Ask me waits for your OK, Never blocks it.</p>' +
|
|
621
|
+
'<div class="app-foot">' + (panel.tools.length > 12 ? '<input type="search" class="app-tools-filter" data-ability="' + escapeHtml(ability) + '" placeholder="Filter tools" value="' + escapeHtml(panel.filter || '') + '" aria-label="Filter tools">' : '') +
|
|
622
|
+
'<button type="button" class="app-refresh" data-ability="' + escapeHtml(ability) + '"' + (apps.canConnect && !panel.busy ? '' : ' disabled') + '>Check for new tools</button></div>' +
|
|
623
|
+
(panel.message ? '<p class="' + (panel.messageBad ? 'error' : 'hint') + '" style="margin:0">' + escapeHtml(panel.message) + '</p>' : '') +
|
|
624
|
+
'<div class="app-tool-list">' + shown.map(function (t) {
|
|
625
|
+
var who = t.usedBy.length
|
|
626
|
+
? 'Used by ' + t.usedBy.map(function (u) { return escapeHtml(u.agent) + ' (' + escapeHtml(DECISION_LABEL[u.decision]) + ')'; }).join(', ')
|
|
627
|
+
: 'Not used by an agent';
|
|
628
|
+
return '<div class="app-tool"><span><code>' + escapeHtml(t.name) + '</code> ' +
|
|
629
|
+
'<select class="app-decision" data-tool="' + escapeHtml(t.remote) + '" data-ability="' + escapeHtml(ability) + '" aria-label="Default permission for ' + escapeHtml(t.name) + '"' + (apps.canConnect && !panel.busy ? '' : ' disabled') + '>' +
|
|
630
|
+
['allow', 'ask', 'deny'].map(function (d) { return '<option value="' + d + '"' + (d === t.decision ? ' selected' : '') + '>' + DECISION_LABEL[d] + '</option>'; }).join('') + '</select>' +
|
|
631
|
+
(t.destructive ? ' <span class="hint">destructive</span>' : t.readOnly ? ' <span class="hint">read-only</span>' : '') +
|
|
632
|
+
(t.description ? '<span class="hint app-tool-desc">' + escapeHtml(t.description) + '</span>' : '') +
|
|
633
|
+
'<span class="hint app-tool-desc">' + who + '</span></span></div>';
|
|
634
|
+
}).join('') + '</div></div>';
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function loadTools(ability, fresh) {
|
|
638
|
+
openTools[ability] = { tools: null, filter: openTools[ability] ? openTools[ability].filter : '' };
|
|
639
|
+
renderApps();
|
|
640
|
+
fetch('/config/apps/tools?ability=' + encodeURIComponent(ability) + (fresh ? '&fresh=1' : ''), { cache: 'no-store' })
|
|
641
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
642
|
+
.then(function (r) {
|
|
643
|
+
if (!openTools[ability]) return;
|
|
644
|
+
if (!r.ok) openTools[ability].error = r.body.error || 'Could not list its tools.';
|
|
645
|
+
else openTools[ability].tools = r.body.tools;
|
|
646
|
+
renderApps();
|
|
647
|
+
})
|
|
648
|
+
.catch(function (err) { if (openTools[ability]) { openTools[ability].error = err.message; renderApps(); } });
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Sets a tool's default permission, or (tool null) checks the server
|
|
652
|
+
// for tools it added since.
|
|
653
|
+
function setDefault(ability, tool, decision) {
|
|
654
|
+
var panel = openTools[ability];
|
|
655
|
+
panel.busy = true;
|
|
656
|
+
panel.message = (tool ? 'Setting ' + tool + String.fromCharCode(8217) + 's default to ' + DECISION_LABEL[decision] : 'Checking the app for new tools') + String.fromCharCode(8230);
|
|
657
|
+
panel.messageBad = false;
|
|
658
|
+
renderApps();
|
|
659
|
+
var path = tool ? '/config/apps/tools' : '/config/apps/refresh';
|
|
660
|
+
fetch(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ ability: ability, tool: tool, decision: decision }) })
|
|
661
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
662
|
+
.then(function (r) {
|
|
663
|
+
panel.busy = false;
|
|
664
|
+
if (!r.ok) { panel.message = r.body.error || 'Could not change it.'; panel.messageBad = true; renderApps(); return; }
|
|
665
|
+
panel.tools = r.body.tools;
|
|
666
|
+
if (r.body.connected) apps = { apps: r.body.apps, connected: r.body.connected, canConnect: r.body.canConnect };
|
|
667
|
+
panel.message = tool
|
|
668
|
+
? tool + ' now starts at ' + DECISION_LABEL[decision] + ' for agents that add it; agents that have it keep theirs.'
|
|
669
|
+
: (r.body.added.length || r.body.removed.length
|
|
670
|
+
? (r.body.added.length ? 'New: ' + r.body.added.join(', ') + '. ' : '') + (r.body.removed.length ? 'No longer offered: ' + r.body.removed.join(', ') + '.' : '')
|
|
671
|
+
: 'No new tools.');
|
|
672
|
+
renderApps();
|
|
673
|
+
})
|
|
674
|
+
.catch(function (err) { panel.busy = false; panel.message = err.message; panel.messageBad = true; renderApps(); });
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function findApp(c) {
|
|
678
|
+
for (var i = 0; i < apps.apps.length; i++) if (apps.apps[i].id === c.id || apps.apps[i].url === c.url) return apps.apps[i];
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function renderAppSearch() {
|
|
683
|
+
if (appSearch.error) return '<p class="error">' + escapeHtml(appSearch.error) + '</p>';
|
|
684
|
+
if (!appSearch.servers) return '<p class="hint">Over a thousand apps publish an MCP server. Verified means it' + String.fromCharCode(8217) + 's on its publisher' + String.fromCharCode(8217) + 's own domain; only add a key to one you trust.</p>';
|
|
685
|
+
if (!appSearch.servers.length) return '<p class="hint">Nothing found for ' + escapeHtml(appSearch.q) + '.</p>';
|
|
686
|
+
return '<div class="app-list">' + appSearch.servers.map(function (s) {
|
|
687
|
+
var connected = apps.connected.some(function (c) { return c.url === s.url; });
|
|
688
|
+
return '<div class="app-row" data-app="' + escapeHtml(s.id) + '"><div class="app-head">' + appIcon(s) + '</div>' +
|
|
689
|
+
'<div class="app-row-main"><div><strong>' + escapeHtml(s.title) + '</strong> ' + (s.verified ? '<span class="badge badge-custom">Verified</span>' : '<span class="badge badge-default">Unverified</span>') + '</div>' +
|
|
690
|
+
'<div class="hint">' + escapeHtml(s.url) + (s.description ? ' · ' + escapeHtml(s.description) : '') + '</div></div>' +
|
|
691
|
+
'<div class="app-slot">' + (connected ? '<span class="badge badge-custom">Connected</span>' : '<button type="button" class="app-connect" data-id="' + escapeHtml(s.id) + '"' + (apps.canConnect ? '' : ' disabled') + '>Connect</button>') + '</div></div>';
|
|
692
|
+
}).join('') + '</div>';
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function loadApps() {
|
|
696
|
+
loadedSections.apps = true;
|
|
697
|
+
fetch('/config/apps', { cache: 'no-store' })
|
|
698
|
+
.then(function (r) { return r.json(); })
|
|
699
|
+
.then(function (d) { apps = d; renderApps(); })
|
|
700
|
+
.catch(function (err) { document.getElementById('appsContent').innerHTML = '<p class="error">Could not load apps: ' + escapeHtml(err.message) + '</p>'; });
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
document.getElementById('appsContent').addEventListener('click', function (ev) {
|
|
704
|
+
var t = ev.target;
|
|
705
|
+
if (t.dataset && t.dataset.appCat) { appCategory = t.dataset.appCat; renderApps(); return; }
|
|
706
|
+
if (t.classList.contains('app-refresh')) { setDefault(t.dataset.ability, null, null); return; }
|
|
707
|
+
if (t.classList.contains('app-tools-btn')) {
|
|
708
|
+
if (openTools[t.dataset.ability]) { delete openTools[t.dataset.ability]; renderApps(); }
|
|
709
|
+
else loadTools(t.dataset.ability, false);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (t.classList.contains('app-connect')) {
|
|
713
|
+
var slot = t.parentNode;
|
|
714
|
+
slot.innerHTML = connectForm(t.dataset.id);
|
|
715
|
+
slot.querySelector('input').focus();
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (t.classList.contains('app-cancel')) { renderApps(); return; }
|
|
719
|
+
if (t.classList.contains('app-disconnect')) {
|
|
720
|
+
if (t.dataset.confirming !== '1') { t.dataset.confirming = '1'; t.textContent = 'Click again to disconnect'; return; }
|
|
721
|
+
t.disabled = true;
|
|
722
|
+
fetch('/config/apps/connect?ability=' + encodeURIComponent(t.dataset.ability), { method: 'DELETE' })
|
|
723
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
724
|
+
.then(function (r) {
|
|
725
|
+
if (!r.ok) throw new Error(r.body.error || 'request failed');
|
|
726
|
+
apps = r.body;
|
|
727
|
+
renderApps();
|
|
728
|
+
})
|
|
729
|
+
.catch(function (err) { t.disabled = false; t.textContent = 'Could not disconnect: ' + err.message; });
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
document.getElementById('appsContent').addEventListener('change', function (ev) {
|
|
734
|
+
var t = ev.target;
|
|
735
|
+
if (t.classList.contains('app-decision')) setDefault(t.dataset.ability, t.dataset.tool, t.value);
|
|
736
|
+
});
|
|
737
|
+
document.getElementById('appsContent').addEventListener('input', function (ev) {
|
|
738
|
+
var t = ev.target;
|
|
739
|
+
if (!t.classList.contains('app-tools-filter')) return;
|
|
740
|
+
openTools[t.dataset.ability].filter = t.value;
|
|
741
|
+
renderApps();
|
|
742
|
+
var again = document.querySelector('.app-tools-filter[data-ability="' + t.dataset.ability + '"]');
|
|
743
|
+
if (again) { again.focus(); again.setSelectionRange(again.value.length, again.value.length); }
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
document.getElementById('appsContent').addEventListener('submit', function (ev) {
|
|
747
|
+
ev.preventDefault();
|
|
748
|
+
var form = ev.target;
|
|
749
|
+
if (form.id === 'appSearchForm') {
|
|
750
|
+
var q = form.q.value.trim();
|
|
751
|
+
appSearch = { q: q, servers: null, error: '' };
|
|
752
|
+
document.getElementById('appSearchResults').innerHTML = '<p class="hint">Searching' + String.fromCharCode(8230) + '</p>';
|
|
753
|
+
fetch('/config/apps/search?q=' + encodeURIComponent(q))
|
|
754
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
755
|
+
.then(function (r) {
|
|
756
|
+
if (!r.ok) throw new Error(r.body.error || 'search failed');
|
|
757
|
+
appSearch.servers = r.body.servers || [];
|
|
758
|
+
document.getElementById('appSearchResults').innerHTML = renderAppSearch();
|
|
759
|
+
})
|
|
760
|
+
.catch(function (err) { appSearch.error = 'Could not search: ' + err.message; document.getElementById('appSearchResults').innerHTML = renderAppSearch(); });
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
if (!form.dataset.connect) return;
|
|
764
|
+
var btn = form.querySelector('button[type=submit]');
|
|
765
|
+
var msg = form.querySelector('.app-msg');
|
|
766
|
+
btn.disabled = true;
|
|
767
|
+
msg.className = 'hint app-msg';
|
|
768
|
+
msg.textContent = 'Connecting' + String.fromCharCode(8230) + ' it looks at the app' + String.fromCharCode(8217) + 's tools and sets their permissions, which can take a minute.';
|
|
769
|
+
fetch('/config/apps/connect', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: form.dataset.connect, key: form.key.value }) })
|
|
770
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
771
|
+
.then(function (r) {
|
|
772
|
+
if (!r.ok) throw new Error(r.body.error || 'request failed');
|
|
773
|
+
apps = r.body;
|
|
774
|
+
// Straight to its tools, to choose what agents may use.
|
|
775
|
+
if (r.body.connectedAbility) loadTools(r.body.connectedAbility, false);
|
|
776
|
+
else renderApps();
|
|
777
|
+
})
|
|
778
|
+
.catch(function (err) {
|
|
779
|
+
btn.disabled = false;
|
|
780
|
+
msg.className = 'error app-msg';
|
|
781
|
+
msg.textContent = err.message;
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
|
|
506
785
|
// ---- Settings section ----
|
|
507
786
|
|
|
508
787
|
function renderSettings(d) {
|
|
@@ -841,6 +1120,7 @@ ${consoleShellScript}
|
|
|
841
1120
|
}
|
|
842
1121
|
if (section === 'models' && !loadedSections.models) loadModels();
|
|
843
1122
|
if (section === 'abilities' && !loadedSections.abilities) loadAbilities();
|
|
1123
|
+
if (section === 'apps' && !loadedSections.apps) loadApps();
|
|
844
1124
|
if (section === 'settings' && !loadedSections.settings) loadSettings();
|
|
845
1125
|
}
|
|
846
1126
|
|
|
@@ -855,7 +1135,7 @@ ${consoleShellScript}
|
|
|
855
1135
|
var requestedSection = new URLSearchParams(location.search).get('section');
|
|
856
1136
|
// ?section=gateways is the old Integrations page's link.
|
|
857
1137
|
if (requestedSection === 'gateways') requestedSection = 'abilities';
|
|
858
|
-
var initialSection = requestedSection === 'abilities' || requestedSection === 'models' || requestedSection === 'settings' ? requestedSection : currentSection;
|
|
1138
|
+
var initialSection = requestedSection === 'abilities' || requestedSection === 'apps' || requestedSection === 'models' || requestedSection === 'settings' ? requestedSection : currentSection;
|
|
859
1139
|
switchSection(initialSection);
|
|
860
1140
|
})();
|
|
861
1141
|
</script>
|
|
@@ -125,6 +125,8 @@ export function setupChecklist(candidate, agentName) {
|
|
|
125
125
|
switch (need.kind) {
|
|
126
126
|
case 'ability':
|
|
127
127
|
return { label: `Install the ${need.name ?? need.description} ability`, href: tab('abilities') };
|
|
128
|
+
case 'app':
|
|
129
|
+
return { label: `Connect ${need.name ?? need.description} on the Apps page, then add it from the agent's Apps tab`, href: '/config?section=apps' };
|
|
128
130
|
case 'api':
|
|
129
131
|
return { label: `Connect ${need.description} (an HTTP tool, or an MCP server)`, href: tab('advanced') };
|
|
130
132
|
case 'key':
|
|
@@ -10,7 +10,12 @@ export interface TemplateCreateDeps {
|
|
|
10
10
|
/** Installs an ability (an npm spec) into the agent. Absent where
|
|
11
11
|
* installing isn't allowed (no admin login): the abilities then head the
|
|
12
12
|
* checklist instead. */
|
|
13
|
-
installAbility?: (name: string, spec: string
|
|
13
|
+
installAbility?: (name: string, spec: string, options?: {
|
|
14
|
+
mcpTools?: string[];
|
|
15
|
+
}) => Promise<void>;
|
|
16
|
+
/** The workspace ability of a connected app (by catalog id), if it's
|
|
17
|
+
* connected. */
|
|
18
|
+
prepareApp?: (id: string) => Promise<string | undefined>;
|
|
14
19
|
/** A setting as the agent sees it (its own, else the shared one). */
|
|
15
20
|
envValue(name: string, variable: string): string | undefined;
|
|
16
21
|
/** Whether approval requests from the agent reach anyone. */
|
|
@@ -37,12 +42,15 @@ export interface TemplateSummary {
|
|
|
37
42
|
trigger: string;
|
|
38
43
|
asksFirst: string[];
|
|
39
44
|
abilities: string[];
|
|
45
|
+
apps: string[];
|
|
40
46
|
schedule: string | null;
|
|
41
47
|
}
|
|
42
48
|
export interface TemplatePreview {
|
|
43
49
|
name: string;
|
|
44
50
|
systemPrompt: string;
|
|
45
51
|
abilities: string[];
|
|
52
|
+
/** Apps it uses, by title. */
|
|
53
|
+
apps: string[];
|
|
46
54
|
skills: string[];
|
|
47
55
|
rules: {
|
|
48
56
|
tool: string;
|
|
@@ -88,7 +96,7 @@ export declare function previewTemplate(id: string, deps: Pick<TemplateCreateDep
|
|
|
88
96
|
export declare function templateChecklist(template: StarterTemplate, name: string, deps: Pick<TemplateCreateDeps, 'envValue' | 'messagingConfigured'>, failed?: {
|
|
89
97
|
name: string;
|
|
90
98
|
error: string;
|
|
91
|
-
}[]): TemplateChecklistItem[];
|
|
99
|
+
}[], appsToConnect?: string[]): TemplateChecklistItem[];
|
|
92
100
|
/** Creates a new agent from the template, with its whole setup, under the
|
|
93
101
|
* first free name, so the same template can be used more than once. */
|
|
94
102
|
export declare function createFromTemplate(id: string, deps: TemplateCreateDeps | LegacyTemplateCreateDeps): Promise<CreatedTemplateAgent>;
|
|
@@ -24,7 +24,7 @@ export function installFailureReason(err) {
|
|
|
24
24
|
if (/E404|404 Not Found|is not in this registry/i.test(text))
|
|
25
25
|
return "it isn't published on npm yet";
|
|
26
26
|
if (/ENOTFOUND|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN/.test(text))
|
|
27
|
-
return 'npm could not be reached; try again from the
|
|
27
|
+
return 'npm could not be reached; try again from the Apps tab';
|
|
28
28
|
const line = text.split('\n').map((l) => l.trim()).find((l) => l && !/^Command failed:/.test(l)) ?? text.split('\n')[0];
|
|
29
29
|
return line.length > 200 ? `${line.slice(0, 200)}…` : line;
|
|
30
30
|
}
|
|
@@ -57,6 +57,7 @@ export function listTemplates() {
|
|
|
57
57
|
trigger: setup.schedule ? describeTemplateSchedule(setup.schedule) : c.trigger.description,
|
|
58
58
|
asksFirst: c.asksFirst,
|
|
59
59
|
abilities: setup.abilities,
|
|
60
|
+
apps: (setup.apps ?? []).map((a) => a.title),
|
|
60
61
|
schedule: setup.schedule ? describeTemplateSchedule(setup.schedule) : null,
|
|
61
62
|
}));
|
|
62
63
|
}
|
|
@@ -72,6 +73,7 @@ export function previewTemplate(id, deps) {
|
|
|
72
73
|
name,
|
|
73
74
|
systemPrompt: template.candidate.agent.systemPrompt,
|
|
74
75
|
abilities: setup.abilities,
|
|
76
|
+
apps: (setup.apps ?? []).map((a) => a.title),
|
|
75
77
|
skills: (setup.skills ?? []).map((s) => s.id),
|
|
76
78
|
rules: setup.rules.map((r) => ({ tool: r.tool, decision: r.decision })),
|
|
77
79
|
// A key set for the whole project already counts for a new agent.
|
|
@@ -83,9 +85,11 @@ export function previewTemplate(id, deps) {
|
|
|
83
85
|
};
|
|
84
86
|
}
|
|
85
87
|
/** What's left once the agent exists, each linked to where it's done. */
|
|
86
|
-
export function templateChecklist(template, name, deps, failed = []) {
|
|
88
|
+
export function templateChecklist(template, name, deps, failed = [], appsToConnect = []) {
|
|
87
89
|
const tab = (t) => `/agents/config?agent=${encodeURIComponent(name)}&tab=${t}`;
|
|
88
90
|
const items = [];
|
|
91
|
+
for (const title of appsToConnect)
|
|
92
|
+
items.push({ label: `Connect ${title} on the Apps page, then add it to ${name} from its Apps tab`, href: '/config?section=apps' });
|
|
89
93
|
for (const f of failed)
|
|
90
94
|
items.push({ label: `Install the ${f.name} ability (it couldn't be installed: ${f.error})`, href: tab('abilities') });
|
|
91
95
|
const missing = keyGroups(template).filter((names) => !names.some((n) => deps.envValue(name, n)));
|
|
@@ -147,6 +151,35 @@ export async function createFromTemplateObject(template, deps) {
|
|
|
147
151
|
failed.push({ name: spec, error: installFailureReason(err) });
|
|
148
152
|
}
|
|
149
153
|
}
|
|
154
|
+
// Apps: a connected one is added with just the tools the template relies
|
|
155
|
+
// on; one not connected heads the checklist.
|
|
156
|
+
const appsToConnect = [];
|
|
157
|
+
for (const app of setup.apps ?? []) {
|
|
158
|
+
let spec;
|
|
159
|
+
try {
|
|
160
|
+
spec = deps.prepareApp ? await deps.prepareApp(app.id) : undefined;
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
failed.push({ name: app.title, error: err instanceof Error ? err.message : String(err) });
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (!spec) {
|
|
167
|
+
appsToConnect.push(app.title);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (!deps.installAbility) {
|
|
171
|
+
failed.push({ name: app.title, error: 'adding apps needs LOOPENGINE_ADMIN_AUTH set on the server' });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
// Just the tools the template relies on.
|
|
176
|
+
await deps.installAbility(name, spec, { mcpTools: app.tools });
|
|
177
|
+
installed.push(app.title);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
failed.push({ name: app.title, error: installFailureReason(err) });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
150
183
|
// 3. Its own skills and non-secret settings.
|
|
151
184
|
for (const skill of setup.skills ?? [])
|
|
152
185
|
writeSkill(name, skill.id, { description: skill.description, body: skill.body });
|
|
@@ -182,7 +215,7 @@ export async function createFromTemplateObject(template, deps) {
|
|
|
182
215
|
return {
|
|
183
216
|
name,
|
|
184
217
|
abilities: { installed, failed },
|
|
185
|
-
checklist: templateChecklist(template, name, deps, failed),
|
|
218
|
+
checklist: templateChecklist(template, name, deps, failed, appsToConnect),
|
|
186
219
|
...(candidate.agent.exampleMessage ? { exampleMessage: candidate.agent.exampleMessage } : {}),
|
|
187
220
|
...(schedule ? { schedule } : {}),
|
|
188
221
|
...(scheduleError ? { scheduleError } : {}),
|
|
@@ -111,6 +111,7 @@ ${consoleShellScript}
|
|
|
111
111
|
'<div><strong>Installs</strong> <span class="hint">(abilities: their tools, skills and permission rules)</span>' +
|
|
112
112
|
listHtml(p.abilities.map(function (a) { return '<li><code>' + esc(a) + '</code></li>'; })) +
|
|
113
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>' : '') +
|
|
114
115
|
'<div><strong>Permissions</strong> <span class="hint">(anything not listed asks you first)</span>' +
|
|
115
116
|
listHtml(p.rules.map(function (rule) { return '<li><code>' + esc(rule.tool) + '</code>: ' + esc(DECISIONS[rule.decision] || rule.decision) + '</li>'; })) + '</div>' +
|
|
116
117
|
'<div><strong>Keys it needs</strong>' +
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type CreatedTemplateAgent, type TemplateChecklistItem, type TemplateCreateDeps } from './templates-admin.js';
|
|
2
2
|
import { type WorkflowPlan, type WorkflowPlannerTools } from '../core/system-agents/workflow-planner.js';
|
|
3
|
+
import { type GeneratedMcpAbility, type McpAbilityRequest } from '../core/mcp-ability-generator.js';
|
|
4
|
+
import type { McpDirectoryServer } from '../core/mcp-directory.js';
|
|
3
5
|
import { type WorkflowRecord } from '../core/workflows.js';
|
|
4
6
|
import type { ModelCall } from '../core/run-agent.js';
|
|
5
7
|
export declare class WorkflowError extends Error {
|
|
@@ -45,6 +47,43 @@ export declare function checkWorkflowForAgent(plan: WorkflowPlan, agent: Workflo
|
|
|
45
47
|
* (whatever the model said) and drops that service from the abilities to
|
|
46
48
|
* install: it's copied over, not installed. */
|
|
47
49
|
export declare function resolveConnections(plan: WorkflowPlan, agents: WorkflowAgentInfo[]): void;
|
|
50
|
+
/** What planning and Create need for steps done by an MCP server. */
|
|
51
|
+
export interface WorkflowMcpDeps {
|
|
52
|
+
getServer(id: string): Promise<McpDirectoryServer | undefined>;
|
|
53
|
+
/** The workspace ability already made from the server at `url`, with
|
|
54
|
+
* its tools (the server's names and the installed ones). */
|
|
55
|
+
existingFor(url: string): {
|
|
56
|
+
spec: string;
|
|
57
|
+
tools: {
|
|
58
|
+
name: string;
|
|
59
|
+
remote: string;
|
|
60
|
+
}[];
|
|
61
|
+
} | undefined;
|
|
62
|
+
/** A setting's value, project-wide. */
|
|
63
|
+
keyValue(name: string): string | undefined;
|
|
64
|
+
}
|
|
65
|
+
export interface WorkflowMcpCreateDeps extends WorkflowMcpDeps {
|
|
66
|
+
generate(request: Omit<McpAbilityRequest, 'modelCall'>): Promise<GeneratedMcpAbility>;
|
|
67
|
+
/** Stores a generated package as a workspace ability; returns its spec. */
|
|
68
|
+
store(dir: string, source: string): string;
|
|
69
|
+
/** Saves a key project-wide. */
|
|
70
|
+
saveKey(name: string, value: string): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
/** Looks up each MCP server the plan's steps name: a server that can't
|
|
73
|
+
* be found leaves its steps missing; the rest are listed on the plan
|
|
74
|
+
* with the ability each would become and whether its key is set. */
|
|
75
|
+
export declare function resolveMcpServers(plan: WorkflowPlan, deps: WorkflowMcpDeps): Promise<void>;
|
|
76
|
+
/** On Create: each MCP server on the plan becomes a private ability (or
|
|
77
|
+
* reuses the one already made from it), and its steps point at that
|
|
78
|
+
* ability's tools. A server that can't be set up leaves its steps
|
|
79
|
+
* missing and is reported. `keys` are what the person typed per server. */
|
|
80
|
+
export declare function prepareMcpAbilities(plan: WorkflowPlan, keys: Record<string, string>, deps: WorkflowMcpCreateDeps | undefined): Promise<{
|
|
81
|
+
made: string[];
|
|
82
|
+
failed: {
|
|
83
|
+
name: string;
|
|
84
|
+
error: string;
|
|
85
|
+
}[];
|
|
86
|
+
}>;
|
|
48
87
|
export interface PlannedWorkflow {
|
|
49
88
|
plan: WorkflowPlan;
|
|
50
89
|
/** The check against the suggested agent, when the plan names one. */
|
|
@@ -57,6 +96,7 @@ export interface PlannedWorkflow {
|
|
|
57
96
|
* agent instead, saying why. */
|
|
58
97
|
export declare function planWorkflow(description: unknown, modelCall: ModelCall, tools: WorkflowPlannerTools & {
|
|
59
98
|
agentInfo(): Promise<WorkflowAgentInfo[]>;
|
|
99
|
+
mcp?: WorkflowMcpDeps;
|
|
60
100
|
}): Promise<PlannedWorkflow>;
|
|
61
101
|
export interface PlanJob {
|
|
62
102
|
id: string;
|
|
@@ -92,6 +132,11 @@ export interface WorkflowCreateDeps extends TemplateCreateDeps {
|
|
|
92
132
|
agentInfo(): Promise<WorkflowAgentInfo[]>;
|
|
93
133
|
/** Makes sure the agent is loaded once its new tools and skill are in place. */
|
|
94
134
|
reloadAgent(name: string): Promise<void>;
|
|
135
|
+
/** Sets up the plan's MCP servers as private abilities. */
|
|
136
|
+
mcp?: WorkflowMcpCreateDeps;
|
|
137
|
+
/** Gives an agent that already has an app more of its tools (at their
|
|
138
|
+
* default permission), keeping the ones it has. */
|
|
139
|
+
addAppTools?: (agent: string, spec: string, tools: string[]) => Promise<void>;
|
|
95
140
|
}
|
|
96
141
|
/** Creates the workflow: in a new agent (as a template would), or in an
|
|
97
142
|
* existing one. An existing agent keeps every decision it already has: a
|
|
@@ -100,6 +145,7 @@ export declare function createWorkflow(input: {
|
|
|
100
145
|
plan?: unknown;
|
|
101
146
|
target?: unknown;
|
|
102
147
|
description?: unknown;
|
|
148
|
+
mcpKeys?: unknown;
|
|
103
149
|
}, deps: WorkflowCreateDeps): Promise<CreatedWorkflow>;
|
|
104
150
|
export interface AgentWorkflowView extends WorkflowRecord {
|
|
105
151
|
schedule?: {
|