loopengine 0.1.97 → 0.1.99
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 +24 -10
- package/dist/core/workspace-abilities.js +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- 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
|
@@ -11,6 +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 { planWorkflowWithModel, slugify, workflowTemplate } from '../core/system-agents/workflow-planner.js';
|
|
14
|
+
import { mcpAbilityNames } from '../core/mcp-ability-generator.js';
|
|
14
15
|
import { readWorkflows, removeWorkflow, saveWorkflow } from '../core/workflows.js';
|
|
15
16
|
import { createScheduledTask, listScheduledTasks, pauseScheduledTask, runScheduledTaskNow } from '../core/scheduler.js';
|
|
16
17
|
import { workspaceTimeZone } from '../core/time-zone.js';
|
|
@@ -81,6 +82,155 @@ function specNames(spec) {
|
|
|
81
82
|
const named = spec.match(/^([^@/:][^@]*)(@.*)?$/)?.[1];
|
|
82
83
|
return [...new Set([scoped, named, last.replace(/@.*$/, '')].filter((n) => Boolean(n)))];
|
|
83
84
|
}
|
|
85
|
+
/** Looks up each MCP server the plan's steps name: a server that can't
|
|
86
|
+
* be found leaves its steps missing; the rest are listed on the plan
|
|
87
|
+
* with the ability each would become and whether its key is set. */
|
|
88
|
+
export async function resolveMcpServers(plan, deps) {
|
|
89
|
+
const ids = [...new Set(plan.steps.flatMap((s) => (s.mcp ? [s.mcp] : [])))];
|
|
90
|
+
const servers = [];
|
|
91
|
+
for (const id of ids) {
|
|
92
|
+
const server = await deps.getServer(id).catch(() => undefined);
|
|
93
|
+
if (!server) {
|
|
94
|
+
for (const step of plan.steps) {
|
|
95
|
+
if (step.mcp !== id)
|
|
96
|
+
continue;
|
|
97
|
+
delete step.mcp;
|
|
98
|
+
step.missing = `the MCP server ${id} couldn't be found`;
|
|
99
|
+
}
|
|
100
|
+
plan.gaps = [...plan.gaps, `The MCP server ${id} couldn't be found, so its steps have no tool yet.`].slice(0, 10);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const names = mcpAbilityNames(server);
|
|
104
|
+
const existing = deps.existingFor(server.url);
|
|
105
|
+
servers.push({
|
|
106
|
+
id,
|
|
107
|
+
title: server.title,
|
|
108
|
+
url: server.url,
|
|
109
|
+
description: server.description,
|
|
110
|
+
verified: server.verified,
|
|
111
|
+
source: server.source,
|
|
112
|
+
ability: names.name,
|
|
113
|
+
keyEnv: names.keyEnv,
|
|
114
|
+
keySet: deps.keyValue(names.keyEnv) !== undefined,
|
|
115
|
+
...(existing ? { existing: existing.spec } : {}),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (servers.length)
|
|
119
|
+
plan.mcpServers = servers;
|
|
120
|
+
else
|
|
121
|
+
delete plan.mcpServers;
|
|
122
|
+
}
|
|
123
|
+
/** On Create: each MCP server on the plan becomes a private ability (or
|
|
124
|
+
* reuses the one already made from it), and its steps point at that
|
|
125
|
+
* ability's tools. A server that can't be set up leaves its steps
|
|
126
|
+
* missing and is reported. `keys` are what the person typed per server. */
|
|
127
|
+
export async function prepareMcpAbilities(plan, keys, deps) {
|
|
128
|
+
const made = [];
|
|
129
|
+
const failed = [];
|
|
130
|
+
for (const planned of plan.mcpServers ?? []) {
|
|
131
|
+
const steps = plan.steps.map((step, index) => ({ step, index })).filter(({ step }) => step.mcp === planned.id);
|
|
132
|
+
if (!steps.length)
|
|
133
|
+
continue;
|
|
134
|
+
const fail = (error) => {
|
|
135
|
+
failed.push({ name: planned.title, error });
|
|
136
|
+
for (const { step } of steps) {
|
|
137
|
+
delete step.mcp;
|
|
138
|
+
step.missing = `${planned.title}: ${error}`;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
if (!deps) {
|
|
142
|
+
fail('setting up MCP servers isn\'t available on this server');
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
// The app, connected already or connected now; and the server tool for
|
|
146
|
+
// each step (the planner's when the app has it, else worked out).
|
|
147
|
+
const existing = deps.existingFor(planned.url);
|
|
148
|
+
const known = existing && steps.every(({ step }) => step.tool && existing.tools.some((t) => t.remote === step.tool));
|
|
149
|
+
let spec;
|
|
150
|
+
let stepTools;
|
|
151
|
+
let names;
|
|
152
|
+
if (existing && known) {
|
|
153
|
+
spec = existing.spec;
|
|
154
|
+
stepTools = Object.fromEntries(steps.map(({ step, index }) => [index, step.tool]));
|
|
155
|
+
names = existing.tools;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const server = await deps.getServer(planned.id).catch(() => undefined);
|
|
159
|
+
if (!server) {
|
|
160
|
+
fail('the server couldn\'t be found any more');
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const typed = keys[planned.id]?.trim();
|
|
164
|
+
const key = typed || deps.keyValue(planned.keyEnv);
|
|
165
|
+
let generated;
|
|
166
|
+
try {
|
|
167
|
+
generated = await deps.generate({
|
|
168
|
+
server,
|
|
169
|
+
...(key ? { key } : {}),
|
|
170
|
+
workflow: { name: plan.name, summary: plan.summary, steps: steps.map(({ step, index }) => ({ index, title: step.title, kind: step.kind, ...(step.detail ? { detail: step.detail } : {}), ...(step.tool ? { tool: step.tool } : {}) })) },
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
stepTools = generated.stepTools;
|
|
178
|
+
names = generated.tools;
|
|
179
|
+
if (existing) {
|
|
180
|
+
// Connected already: only the step matching was needed.
|
|
181
|
+
spec = existing.spec;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
if (typed && generated.keyEnv) {
|
|
185
|
+
try {
|
|
186
|
+
await deps.saveKey(generated.keyEnv, typed);
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
failed.push({ name: planned.title, error: `its key wasn't saved (${err instanceof Error ? err.message : String(err)}); add ${generated.keyEnv} from the checklist` });
|
|
190
|
+
if (!plan.keys.includes(generated.keyEnv))
|
|
191
|
+
plan.keys.push(generated.keyEnv);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
spec = deps.store(generated.dir, `mcp:${server.url}`);
|
|
195
|
+
made.push(generated.name);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const installedName = (remote) => names.find((t) => t.remote === remote)?.name ?? remote;
|
|
199
|
+
const tools = [];
|
|
200
|
+
for (const { step, index } of steps) {
|
|
201
|
+
delete step.mcp;
|
|
202
|
+
step.ability = spec;
|
|
203
|
+
const remote = stepTools[index];
|
|
204
|
+
if (!remote) {
|
|
205
|
+
delete step.tool;
|
|
206
|
+
step.missing = `${planned.title} has no tool for this`;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
step.tool = installedName(remote);
|
|
210
|
+
if (!tools.includes(remote))
|
|
211
|
+
tools.push(remote);
|
|
212
|
+
// An approval step's tool asks first, whatever its default.
|
|
213
|
+
if (step.kind === 'approval') {
|
|
214
|
+
const rule = plan.rules.find((r) => r.tool === step.tool);
|
|
215
|
+
if (rule)
|
|
216
|
+
rule.decision = 'ask';
|
|
217
|
+
else
|
|
218
|
+
plan.rules.push({ tool: step.tool, decision: 'ask' });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// The skill was written with the server's own tool names.
|
|
222
|
+
for (const { remote, name } of names) {
|
|
223
|
+
if (remote === name)
|
|
224
|
+
continue;
|
|
225
|
+
plan.skill.body = plan.skill.body.replace(new RegExp(`(?<![A-Za-z0-9_])${remote.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![A-Za-z0-9_])`, 'g'), name);
|
|
226
|
+
}
|
|
227
|
+
// Added to the agent as an app, with just these tools.
|
|
228
|
+
if (tools.length)
|
|
229
|
+
plan.apps = [...(plan.apps ?? []).filter((a) => a.id !== planned.id), { id: planned.id, title: planned.title, tools }];
|
|
230
|
+
}
|
|
231
|
+
delete plan.mcpServers;
|
|
232
|
+
return { made, failed };
|
|
233
|
+
}
|
|
84
234
|
/** Plans a workflow from a description, then checks the suggested agent:
|
|
85
235
|
* one that would have a decision changed, or doesn't exist, becomes a new
|
|
86
236
|
* agent instead, saying why. */
|
|
@@ -96,6 +246,14 @@ export async function planWorkflow(description, modelCall, tools) {
|
|
|
96
246
|
const plan = result.plan;
|
|
97
247
|
const agents = await tools.agentInfo();
|
|
98
248
|
resolveConnections(plan, agents);
|
|
249
|
+
if (tools.mcp)
|
|
250
|
+
await resolveMcpServers(plan, tools.mcp);
|
|
251
|
+
else
|
|
252
|
+
for (const step of plan.steps)
|
|
253
|
+
if (step.mcp) {
|
|
254
|
+
delete step.mcp;
|
|
255
|
+
step.missing = step.missing ?? 'no ability does this';
|
|
256
|
+
}
|
|
99
257
|
if (plan.target.kind !== 'existing' || !plan.target.agent)
|
|
100
258
|
return { plan };
|
|
101
259
|
const agent = agents.find((a) => a.name === plan.target.agent);
|
|
@@ -160,6 +318,8 @@ export async function createWorkflow(input, deps) {
|
|
|
160
318
|
});
|
|
161
319
|
const agents = await deps.agentInfo();
|
|
162
320
|
resolveConnections(plan, agents);
|
|
321
|
+
const keys = input.mcpKeys && typeof input.mcpKeys === 'object' ? Object.fromEntries(Object.entries(input.mcpKeys).flatMap(([k, v]) => (typeof v === 'string' && v.trim() ? [[k, v.trim()]] : []))) : {};
|
|
322
|
+
const mcp = await prepareMcpAbilities(plan, keys, deps.mcp);
|
|
163
323
|
const template = workflowTemplate(plan);
|
|
164
324
|
if (target.kind !== 'existing') {
|
|
165
325
|
const created = await createFromTemplateObject(template, deps);
|
|
@@ -171,7 +331,7 @@ export async function createWorkflow(input, deps) {
|
|
|
171
331
|
agent: created.name,
|
|
172
332
|
newAgent: true,
|
|
173
333
|
workflow: plan.name,
|
|
174
|
-
abilities: { installed: [...created.abilities.installed, ...connected.copied], failed: [...created.abilities.failed, ...connected.failed] },
|
|
334
|
+
abilities: { installed: [...created.abilities.installed, ...connected.copied], failed: [...mcp.failed, ...created.abilities.failed, ...connected.failed] },
|
|
175
335
|
keptDecisions: [],
|
|
176
336
|
checklist: withMissingSteps(plan, created.name, created.checklist),
|
|
177
337
|
...(created.schedule ? { schedule: created.schedule } : {}),
|
|
@@ -196,7 +356,7 @@ export async function createWorkflow(input, deps) {
|
|
|
196
356
|
}
|
|
197
357
|
// 2. Abilities it doesn't have yet.
|
|
198
358
|
const installed = [];
|
|
199
|
-
const failed = [];
|
|
359
|
+
const failed = [...mcp.failed];
|
|
200
360
|
for (const spec of check.toInstall) {
|
|
201
361
|
if (!deps.installAbility) {
|
|
202
362
|
failed.push({ name: spec, error: 'installing abilities needs LOOPENGINE_ADMIN_AUTH set on the server' });
|
|
@@ -213,6 +373,38 @@ export async function createWorkflow(input, deps) {
|
|
|
213
373
|
failed.push({ name: spec, error: installFailureReason(err) });
|
|
214
374
|
}
|
|
215
375
|
}
|
|
376
|
+
// Apps: added with just the tools the steps use, or those tools added
|
|
377
|
+
// to the agent's when it has the app already.
|
|
378
|
+
const appsToConnect = [];
|
|
379
|
+
for (const app of plan.apps ?? []) {
|
|
380
|
+
const spec = deps.prepareApp ? await deps.prepareApp(app.id) : undefined;
|
|
381
|
+
if (!spec) {
|
|
382
|
+
appsToConnect.push(app.title);
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (!deps.installAbility) {
|
|
386
|
+
failed.push({ name: app.title, error: 'adding apps needs LOOPENGINE_ADMIN_AUTH set on the server' });
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
try {
|
|
390
|
+
await deps.installAbility(agent.name, spec, { mcpTools: app.tools });
|
|
391
|
+
installed.push(app.title);
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
395
|
+
if (/already installed/i.test(message) && deps.addAppTools) {
|
|
396
|
+
try {
|
|
397
|
+
await deps.addAppTools(agent.name, spec, app.tools);
|
|
398
|
+
installed.push(app.title);
|
|
399
|
+
}
|
|
400
|
+
catch (e) {
|
|
401
|
+
failed.push({ name: app.title, error: e instanceof Error ? e.message : String(e) });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
else
|
|
405
|
+
failed.push({ name: app.title, error: installFailureReason(err) });
|
|
406
|
+
}
|
|
407
|
+
}
|
|
216
408
|
// Connected-service tools it doesn't have yet.
|
|
217
409
|
const connected = copyConnectedTools(plan, agent.name, agents, deps.agentDir);
|
|
218
410
|
installed.push(...connected.copied);
|
|
@@ -252,7 +444,7 @@ export async function createWorkflow(input, deps) {
|
|
|
252
444
|
workflow: plan.name,
|
|
253
445
|
abilities: { installed, failed },
|
|
254
446
|
keptDecisions: check.conflicts,
|
|
255
|
-
checklist: withMissingSteps(plan, agent.name, templateChecklist(template, agent.name, deps, failed)),
|
|
447
|
+
checklist: withMissingSteps(plan, agent.name, templateChecklist(template, agent.name, deps, failed, appsToConnect)),
|
|
256
448
|
...(schedule ? { schedule } : {}),
|
|
257
449
|
...(scheduleError ? { scheduleError } : {}),
|
|
258
450
|
...(loadError ? { loadError } : {}),
|
|
@@ -36,6 +36,9 @@ export const workflowPageHtml = `<!doctype html>
|
|
|
36
36
|
.wf-btn { display: inline-block; padding: 7px 14px; border-radius: 6px; text-decoration: none; font-size: 13px; font-weight: 600; background: var(--accent); color: var(--on-accent); border: 0; cursor: pointer; }
|
|
37
37
|
.wf-btn.plain { background: none; color: var(--ink); border: 1px solid var(--line); font-weight: 500; }
|
|
38
38
|
.wf-btn:disabled { opacity: 0.6; cursor: progress; }
|
|
39
|
+
.wf-mcp { display: grid; gap: 10px; }
|
|
40
|
+
.wf-mcp-row { border: 1px solid var(--line); border-radius: var(--radius); padding: 10px 12px; display: grid; gap: 6px; background: var(--surface); }
|
|
41
|
+
.wf-mcp-row input { font: inherit; padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--bg); color: var(--ink); width: 100%; max-width: 360px; box-sizing: border-box; }
|
|
39
42
|
.hint { font-size: 12px; color: var(--ink-muted); }
|
|
40
43
|
.error { font-size: 13px; color: var(--bad); }
|
|
41
44
|
</style>
|
|
@@ -134,8 +137,16 @@ ${consoleShellScript}
|
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
// ---- 2. Check the plan ----
|
|
140
|
+
function mcpTitle(id) {
|
|
141
|
+
var servers = (state.planned && state.planned.plan.mcpServers) || [];
|
|
142
|
+
for (var i = 0; i < servers.length; i++) if (servers[i].id === id) return servers[i].title;
|
|
143
|
+
return id;
|
|
144
|
+
}
|
|
145
|
+
|
|
137
146
|
function stepRow(step, i) {
|
|
138
|
-
var becomes = step.
|
|
147
|
+
var becomes = step.mcp
|
|
148
|
+
? (step.tool ? '<code>' + esc(step.tool) + '</code> ' : '') + '<span class="hint">from ' + esc(mcpTitle(step.mcp)) + ' (MCP server)' + (step.tool ? '' : ', tool picked on Create') + '</span>'
|
|
149
|
+
: step.tool
|
|
139
150
|
? '<code>' + esc(step.tool) + '</code>' + (step.ability ? ' <span class="hint">from ' + esc(step.ability) + '</span>' : step.connection ? ' <span class="hint">connected service: ' + esc(step.connection) + '</span>' : (step.tool === 'system_ask_user' ? ' <span class="hint">built in</span>' : ''))
|
|
140
151
|
: '<span class="hint">the agent' + String.fromCharCode(8217) + 's own work</span>';
|
|
141
152
|
var badge = step.missing
|
|
@@ -148,6 +159,21 @@ ${consoleShellScript}
|
|
|
148
159
|
return '<tr><td class="wf-num">' + (i + 1) + '</td><td><strong>' + esc(step.title) + '</strong>' + (step.detail ? '<div class="hint">' + esc(step.detail) + '</div>' : '') + '</td><td>' + becomes + '</td><td>' + badge + '</td></tr>';
|
|
149
160
|
}
|
|
150
161
|
|
|
162
|
+
function mcpRow(s) {
|
|
163
|
+
var badge = s.verified ? '<span class="le-pill allow">' + (s.source === 'saved' ? 'Saved server' : 'Verified') + '</span>' : '<span class="le-pill ask">Unverified</span>';
|
|
164
|
+
var what = s.existing
|
|
165
|
+
? 'Uses <code>' + esc(s.ability) + '</code>, already in your workspace.'
|
|
166
|
+
: 'Connected to the workspace when you create this, as <code>' + esc(s.ability) + '</code>; the agent gets just the tools its steps use (anything that changes data asks you first).';
|
|
167
|
+
var key = s.existing ? '' :
|
|
168
|
+
'<label class="hint" for="wfKey-' + esc(s.ability) + '">API key' + (s.keySet ? ' (one is saved; leave blank to use it)' : ' (if it needs one)') + '</label>' +
|
|
169
|
+
'<input type="password" autocomplete="off" id="wfKey-' + esc(s.ability) + '" data-mcp-key="' + esc(s.id) + '" placeholder="' + esc(s.keyEnv) + '">';
|
|
170
|
+
return '<div class="wf-mcp-row"><div><strong>' + esc(s.title) + '</strong> ' + badge + '</div>' +
|
|
171
|
+
'<div class="hint">' + esc(s.url) + (s.description ? ' ' + DASH + ' ' + esc(s.description) : '') + '</div>' +
|
|
172
|
+
'<div class="hint">' + what + '</div>' + key +
|
|
173
|
+
(s.verified ? '' : '<div class="hint">Not published on the app' + String.fromCharCode(8217) + 's own domain: only add a key if you trust who runs it.</div>') +
|
|
174
|
+
'</div>';
|
|
175
|
+
}
|
|
176
|
+
|
|
151
177
|
function renderPlan(error) {
|
|
152
178
|
var p = state.planned.plan;
|
|
153
179
|
var suggested = p.target.kind === 'existing' && p.target.agent ? p.target.agent : '';
|
|
@@ -160,6 +186,7 @@ ${consoleShellScript}
|
|
|
160
186
|
'<div class="le-table-wrap"><table class="wf-steps"><thead><tr><th></th><th>Step</th><th>Becomes</th><th>Checkpoint</th></tr></thead><tbody>' + p.steps.map(stepRow).join('') + '</tbody></table></div>' +
|
|
161
187
|
(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>' : '') +
|
|
162
188
|
(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>') : '') +
|
|
189
|
+
(p.mcpServers && p.mcpServers.length ? '<h3>Apps it connects to</h3><div class="wf-mcp">' + p.mcpServers.map(mcpRow).join('') + '</div>' : '') +
|
|
163
190
|
(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>' : '') +
|
|
164
191
|
(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>' : '') +
|
|
165
192
|
'</div>' +
|
|
@@ -184,8 +211,11 @@ ${consoleShellScript}
|
|
|
184
211
|
var target = chosen.value === 'new' ? { kind: 'new' } : { kind: 'existing', agent: chosen.value === 'other' ? document.getElementById('wfOther').value : chosen.dataset.agent };
|
|
185
212
|
var btn = document.getElementById('wfCreate');
|
|
186
213
|
btn.disabled = true;
|
|
187
|
-
|
|
188
|
-
|
|
214
|
+
var mcpKeys = {};
|
|
215
|
+
var keyInputs = page.querySelectorAll('[data-mcp-key]');
|
|
216
|
+
for (var k = 0; k < keyInputs.length; k++) if (keyInputs[k].value.trim()) mcpKeys[keyInputs[k].dataset.mcpKey] = keyInputs[k].value.trim();
|
|
217
|
+
document.getElementById('wfStatus').textContent = 'Creating' + ELLIPSIS + (p.mcpServers && p.mcpServers.length ? ' connecting to its apps and installing abilities can take a minute or two.' : ' installing abilities can take a minute.');
|
|
218
|
+
post('/workflows/create', { plan: p, target: target, description: state.description, mcpKeys: mcpKeys }).then(function (r) {
|
|
189
219
|
if (!r.ok) { renderPlan(r.body.error || 'Could not create it.'); return; }
|
|
190
220
|
renderDone(r.body);
|
|
191
221
|
}).catch(function (err) { renderPlan(err.message); });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loopengine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.99",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -62,6 +62,14 @@
|
|
|
62
62
|
"dist/core/ability-sources.d.ts",
|
|
63
63
|
"dist/core/ability-details.js",
|
|
64
64
|
"dist/core/ability-details.d.ts",
|
|
65
|
+
"dist/core/mcp-abilities.js",
|
|
66
|
+
"dist/core/mcp-abilities.d.ts",
|
|
67
|
+
"dist/core/mcp-directory.js",
|
|
68
|
+
"dist/core/mcp-directory.d.ts",
|
|
69
|
+
"dist/core/mcp-ability-generator.js",
|
|
70
|
+
"dist/core/mcp-ability-generator.d.ts",
|
|
71
|
+
"dist/core/mcp-apps.js",
|
|
72
|
+
"dist/core/mcp-apps.d.ts",
|
|
65
73
|
"dist/core/workspace-abilities.js",
|
|
66
74
|
"dist/core/workspace-abilities.d.ts",
|
|
67
75
|
"dist/core/time-zone.d.ts",
|
|
@@ -129,6 +137,7 @@
|
|
|
129
137
|
},
|
|
130
138
|
"dependencies": {
|
|
131
139
|
"@anthropic-ai/sdk": "^0.116.0",
|
|
140
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
132
141
|
"actauth": "^0.0.12",
|
|
133
142
|
"ioredis": "^6.0.0",
|
|
134
143
|
"openai": "^7.4.0",
|