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.
Files changed (35) hide show
  1. package/dist/bin/ability-manager.d.ts +35 -2
  2. package/dist/bin/ability-manager.js +168 -35
  3. package/dist/core/ability-details.js +11 -0
  4. package/dist/core/mcp-abilities.d.ts +91 -0
  5. package/dist/core/mcp-abilities.js +400 -0
  6. package/dist/core/mcp-ability-generator.d.ts +59 -0
  7. package/dist/core/mcp-ability-generator.js +193 -0
  8. package/dist/core/mcp-apps.d.ts +95 -0
  9. package/dist/core/mcp-apps.js +254 -0
  10. package/dist/core/mcp-directory.d.ts +32 -0
  11. package/dist/core/mcp-directory.js +191 -0
  12. package/dist/core/run-agent.js +5 -2
  13. package/dist/core/system-agents/interviewer.d.ts +1 -1
  14. package/dist/core/system-agents/interviewer.js +1 -1
  15. package/dist/core/system-agents/templates.d.ts +11 -0
  16. package/dist/core/system-agents/templates.js +22 -16
  17. package/dist/core/system-agents/workflow-planner.d.ts +36 -3
  18. package/dist/core/system-agents/workflow-planner.js +23 -6
  19. package/dist/core/workspace-abilities.js +1 -1
  20. package/dist/index.d.ts +7 -3
  21. package/dist/index.js +6 -2
  22. package/dist/web/abilities-admin.d.ts +1 -1
  23. package/dist/web/abilities-admin.js +1 -1
  24. package/dist/web/agents-config-page.js +187 -11
  25. package/dist/web/console-shell.js +7 -5
  26. package/dist/web/dev-ui-styles.js +1 -1
  27. package/dist/web/global-config-page.js +293 -13
  28. package/dist/web/interview-admin.js +2 -0
  29. package/dist/web/templates-admin.d.ts +10 -2
  30. package/dist/web/templates-admin.js +36 -3
  31. package/dist/web/templates-page.js +1 -0
  32. package/dist/web/workflow-admin.d.ts +46 -0
  33. package/dist/web/workflow-admin.js +195 -3
  34. package/dist/web/workflow-page.js +33 -3
  35. package/package.json +10 -1
@@ -335,6 +335,16 @@ export const agentsConfigPageHtml = `<!doctype html>
335
335
  #httpToolAddFieldBtn, #httpToolAddHeaderBtn {
336
336
  padding: 6px 12px;
337
337
  }
338
+ .app-pick { border: 1px solid var(--line); border-radius: var(--radius); padding: 10px 12px; margin: 6px 0 12px; display: grid; gap: 8px; background: var(--surface); }
339
+ .app-pick-bar { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
340
+ .app-pick-bar input[type=search] { font: inherit; font-size: 12px; padding: 5px 7px; max-width: 240px; }
341
+ .app-pick-list { display: grid; gap: 2px; max-height: 380px; overflow: auto; border: 1px solid var(--line); border-radius: var(--radius); padding: 4px; background: var(--bg); }
342
+ .app-pick-tool { display: flex; gap: 8px; align-items: flex-start; padding: 5px 6px; border-radius: 6px; font-size: 12.5px; }
343
+ .app-pick-tool:hover { background: var(--surface); }
344
+ .app-pick-tool input { margin-top: 3px; }
345
+ .app-pick-tool code { overflow-wrap: anywhere; }
346
+ .app-pick-tool select { font: inherit; font-size: 11.5px; padding: 1px 4px; }
347
+ .app-pick-desc { display: block; }
338
348
  </style>
339
349
  </head>
340
350
  <body data-le-page="agent-config">
@@ -361,7 +371,7 @@ ${consoleShellScript}
361
371
  var actauthLoadedFor = null;
362
372
  // Same idea again, for the Environment tab's own GET .../env.
363
373
  var envLoadedFor = null;
364
- // Same idea again, for the Abilities tab's own GET .../abilities.
374
+ // Same idea again, for the Apps tab's own GET .../abilities.
365
375
  var abilitiesLoadedFor = null;
366
376
  // The full /agents/:name/config response the currently-open agent was
367
377
  // last rendered from — kept around so refreshActauthDependentPanels/
@@ -637,8 +647,8 @@ ${consoleShellScript}
637
647
  return '<div class="le-cards" style="margin-bottom:28px">' +
638
648
  card(null, 'Model', escapeHtml(modelValue), escapeHtml(modelDetail)) +
639
649
  (cfg.abilityCount != null
640
- ? card('abilities', 'Abilities', String(cfg.abilityCount), cfg.abilityCount ? 'Installed for this agent' : 'Add tools, skills and rules in one install')
641
- : card('abilities', 'Abilities', 'Browse', 'Add tools, skills and rules in one install')) +
650
+ ? card('abilities', 'Apps', String(cfg.abilityCount), cfg.abilityCount ? 'Apps and abilities added to this agent' : 'Add apps and abilities, with the tools you choose')
651
+ : card('abilities', 'Apps', 'Browse', 'Add apps and abilities, with the tools you choose')) +
642
652
  card('tools', 'Tools', String(cfg.tools.length), escapeHtml(toolNames.slice(0, 3).join(', ') + (toolNames.length > 3 ? ', \u2026' : '')) || 'None yet') +
643
653
  card('skills', 'Skills', String(cfg.skills.length), escapeHtml(cfg.skills.map(function (k) { return k.name; }).join(', ')) || 'None yet') +
644
654
  card('actauth', 'Permissions', rules.length + (rules.length === 1 ? ' rule' : ' rules'),
@@ -2308,7 +2318,7 @@ ${consoleShellScript}
2308
2318
  });
2309
2319
  }
2310
2320
 
2311
- // ---- Abilities tab: what's already installed for this agent, a
2321
+ // ---- Apps tab: what's already installed for this agent, a
2312
2322
  // search box over public (npm, tagged loopengine-ability) packages,
2313
2323
  // and a plain spec field for a private ability (a git URL, a local
2314
2324
  // file:../path, or a private registry name) — both installs go
@@ -2349,7 +2359,7 @@ ${consoleShellScript}
2349
2359
  '<td><code>' + escapeHtml(a.name) + '</code></td>' +
2350
2360
  '<td>' + escapeHtml(a.version) + '</td>' +
2351
2361
  '<td>' + leShell.abilityOriginBadge(a.origin) + '</td>' +
2352
- '<td class="hint">' + escapeHtml((a.tools || []).join(', ')) + '</td>' +
2362
+ '<td class="hint">' + escapeHtml((a.tools || []).concat(a.mcp ? a.mcp.tools.map(function (t) { return t.name; }) : []).join(', ')) + '</td>' +
2353
2363
  '<td style="white-space:nowrap">' + detailsHtml + ' ' + actionHtml + '</td>' +
2354
2364
  '</tr>';
2355
2365
  }
@@ -2374,11 +2384,15 @@ ${consoleShellScript}
2374
2384
  var installedHtml = installed.length
2375
2385
  ? '<table><thead><tr><th>Name</th><th>Version</th><th>Source</th><th>Tools</th><th></th></tr></thead><tbody>' + installed.map(renderInstalledAbilityRow).join('') + '</tbody></table>' +
2376
2386
  '<p id="abilityUpgradeStatus" class="hint"></p>'
2377
- : '<p class="hint">No abilities installed for this agent yet.</p>';
2387
+ : '<p class="hint">Nothing added to this agent yet.</p>';
2378
2388
 
2379
- return '<h3>Installed</h3>' + installedHtml +
2389
+ // What this agent has first; then the workspace's connected apps (each
2390
+ // with the tools this agent gets), then public and private abilities.
2391
+ return '<h3 style="justify-content:flex-start;align-items:baseline;gap:8px">Installed <span class="hint" style="font-weight:400">the apps and abilities this agent has</span></h3>' + installedHtml +
2380
2392
  // Installing by name or spec is on the console's Abilities page.
2381
2393
  '<p id="abilityInstallStatus" class="hint"></p>' +
2394
+ '<h3 style="margin-top:24px;justify-content:flex-start;align-items:baseline;gap:8px">Apps <span class="hint" style="font-weight:400">connected in this workspace; choose which of their tools this agent gets, and each one' + String.fromCharCode(8217) + 's permission</span></h3>' +
2395
+ '<div id="agentApps" style="margin-bottom:32px"><p class="hint">Loading&hellip;</p></div>' +
2382
2396
  '<h3 style="margin-top:24px;justify-content:flex-start;align-items:baseline;gap:8px">Public abilities <span class="hint" style="font-weight:400">npm packages tagged loopengine-ability</span></h3>' +
2383
2397
  '<form id="abilitySearchForm" style="display:flex;gap:6px;margin:0 0 10px;max-width:560px">' +
2384
2398
  '<input type="search" name="q" placeholder="Search abilities, e.g. gmail" aria-label="Search public abilities" style="flex:1">' +
@@ -2456,7 +2470,8 @@ ${consoleShellScript}
2456
2470
  .then(function (result) {
2457
2471
  if (!result.ok) throw new Error(result.body.error || 'request failed');
2458
2472
  var files = result.body.files || [];
2459
- var updated = files.filter(function (f) { return f.status === 'updated'; });
2473
+ // added/removed: new rules, and tool files a version dropped (its tools now come from the app's MCP server).
2474
+ var updated = files.filter(function (f) { return f.status === 'updated' || f.status === 'added' || f.status === 'removed'; });
2460
2475
  var conflicts = files.filter(function (f) { return f.status === 'conflict'; });
2461
2476
  var message;
2462
2477
  if (!files.length || (!updated.length && !conflicts.length)) {
@@ -2565,7 +2580,8 @@ ${consoleShellScript}
2565
2580
  .then(function (r) { return r.json(); })
2566
2581
  .then(function (d) {
2567
2582
  if (currentName !== name) return;
2568
- var abilities = d.abilities || [];
2583
+ // Connected apps are listed under Apps, above.
2584
+ var abilities = (d.abilities || []).filter(function (a) { return !a.app; });
2569
2585
  if (!abilities.length) {
2570
2586
  el.innerHTML = '<p><a href="/config?section=abilities#private-abilities">Install private abilities</a></p>';
2571
2587
  return;
@@ -2665,6 +2681,165 @@ ${consoleShellScript}
2665
2681
  runAbilitySearch(name, '', content, resultsEl, installedNames);
2666
2682
  }
2667
2683
  loadPrivateAbilitiesFor(name, content, installedNames);
2684
+ loadAgentApps(name, content);
2685
+ }
2686
+
2687
+ // ---- Apps: a connected app's tools for this agent ----
2688
+ // The workspace's connected apps (the console's Apps page). Adding one,
2689
+ // or Choose tools on one already added, opens a picker: each tool with a
2690
+ // checkbox and this agent's permission for it; Save installs the app
2691
+ // with just those tools, or changes the agent's choice.
2692
+
2693
+ var agentApps = { name: null, connected: [], pickers: {} };
2694
+ var DECISION_NAMES = { allow: 'Allow', ask: 'Ask me', deny: 'Never' };
2695
+
2696
+ function loadAgentApps(name, content) {
2697
+ var el = content.querySelector('#agentApps');
2698
+ if (!el) return;
2699
+ if (agentApps.name !== name) agentApps = { name: name, connected: [], pickers: {} };
2700
+ fetch('/config/apps', { cache: 'no-store' })
2701
+ .then(function (r) { return r.json(); })
2702
+ .then(function (d) {
2703
+ if (currentName !== name) return;
2704
+ agentApps.connected = d.connected || [];
2705
+ agentApps.canChange = Boolean(d.canConnect);
2706
+ renderAgentApps();
2707
+ })
2708
+ .catch(function (err) { el.innerHTML = '<p class="error">Could not list apps: ' + escapeHtml(err.message) + '</p>'; });
2709
+ if (!content.dataset.appsWired) {
2710
+ content.dataset.appsWired = '1';
2711
+ content.addEventListener('click', onAgentAppsClick);
2712
+ content.addEventListener('change', onAgentAppsChange);
2713
+ content.addEventListener('input', onAgentAppsInput);
2714
+ }
2715
+ }
2716
+
2717
+ function renderAgentApps() {
2718
+ var content = abilitiesContentEl();
2719
+ var el = content && content.querySelector('#agentApps');
2720
+ if (!el) return;
2721
+ var name = agentApps.name;
2722
+ if (!agentApps.connected.length) {
2723
+ el.innerHTML = '<p class="hint">No apps connected yet. <a href="/config?section=apps">Connect one on the Apps page</a>, then add it here.</p>';
2724
+ return;
2725
+ }
2726
+ el.innerHTML = '<table><thead><tr><th>App</th><th>For this agent</th><th></th></tr></thead><tbody>' + agentApps.connected.map(function (c) {
2727
+ var added = c.agents.indexOf(name) !== -1;
2728
+ var open = agentApps.pickers[c.ability];
2729
+ return '<tr><td><strong>' + escapeHtml(c.title) + '</strong> <span class="hint">' + escapeHtml(c.ability) + '</span></td>' +
2730
+ '<td class="hint">' + (added ? 'Added' : 'Not added') + '</td>' +
2731
+ '<td style="white-space:nowrap"><button type="button" class="app-pick-open" data-ability="' + escapeHtml(c.ability) + '">' + (open ? 'Close' : added ? 'Choose tools' : 'Add') + '</button></td></tr>' +
2732
+ (open ? '<tr><td colspan="3">' + pickerHtml(c.ability) + '</td></tr>' : '');
2733
+ }).join('') + '</tbody></table>' +
2734
+ '<p class="hint" style="margin:6px 0 0"><a href="/config?section=apps">Connect more apps</a></p>';
2735
+ }
2736
+
2737
+ function pickerHtml(ability) {
2738
+ var p = agentApps.pickers[ability];
2739
+ if (p.error) return '<div class="app-pick"><p class="error" style="margin:0">' + escapeHtml(p.error) + '</p></div>';
2740
+ if (!p.tools) return '<div class="app-pick"><p class="hint" style="margin:0">Asking the app for its tools' + String.fromCharCode(8230) + '</p></div>';
2741
+ var chosen = p.tools.filter(function (t) { return t.selected; }).length;
2742
+ var f = (p.filter || '').toLowerCase();
2743
+ var shown = p.tools.filter(function (t) { return !f || (t.remote + ' ' + t.description).toLowerCase().indexOf(f) !== -1; });
2744
+ var disabled = agentApps.canChange && !p.busy ? '' : ' disabled';
2745
+ return '<div class="app-pick">' +
2746
+ '<p class="hint" style="margin:0">Choose the tools this agent can use, and what each may do on its own: Allow runs it, Ask me waits for your OK, Never blocks it. Each starts at the app' + String.fromCharCode(8217) + 's default.</p>' +
2747
+ '<div class="app-pick-bar">' +
2748
+ (p.tools.length > 12 ? '<input type="search" class="app-pick-filter" data-ability="' + escapeHtml(ability) + '" placeholder="Filter tools" value="' + escapeHtml(p.filter || '') + '" aria-label="Filter tools">' : '') +
2749
+ '<button type="button" class="app-pick-readonly" data-ability="' + escapeHtml(ability) + '"' + disabled + '>Select read-only tools</button>' +
2750
+ '<button type="button" class="app-pick-none" data-ability="' + escapeHtml(ability) + '"' + disabled + '>Clear</button>' +
2751
+ '<span class="hint">' + chosen + ' of ' + p.tools.length + ' selected' + (chosen > 20 ? '. That' + String.fromCharCode(8217) + 's a lot: more tools make an agent slower to choose and likelier to pick the wrong one.' : '') + '</span>' +
2752
+ '</div>' +
2753
+ '<div class="app-pick-list">' + shown.map(function (t) {
2754
+ return '<label class="app-pick-tool"><input type="checkbox" class="app-pick-check" data-ability="' + escapeHtml(ability) + '" data-tool="' + escapeHtml(t.remote) + '"' + (t.selected ? ' checked' : '') + disabled + '>' +
2755
+ '<span><code>' + escapeHtml(t.name) + '</code> ' +
2756
+ '<select class="app-pick-decision" data-ability="' + escapeHtml(ability) + '" data-tool="' + escapeHtml(t.remote) + '" aria-label="Permission for ' + escapeHtml(t.name) + '"' + disabled + '>' +
2757
+ ['allow', 'ask', 'deny'].map(function (d) { return '<option value="' + d + '"' + (d === t.decision ? ' selected' : '') + '>' + DECISION_NAMES[d] + '</option>'; }).join('') + '</select>' +
2758
+ (t.destructive ? ' <span class="hint">destructive</span>' : t.readOnly ? ' <span class="hint">read-only</span>' : '') +
2759
+ (t.description ? '<span class="hint app-pick-desc">' + escapeHtml(t.description) + '</span>' : '') +
2760
+ '</span></label>';
2761
+ }).join('') + '</div>' +
2762
+ '<div class="app-pick-bar"><button type="button" class="app-pick-save" data-ability="' + escapeHtml(ability) + '"' + (chosen && !p.busy && agentApps.canChange ? '' : ' disabled') + '>' + (p.installed ? 'Save' : 'Add with ' + chosen + ' tool' + (chosen === 1 ? '' : 's')) + '</button>' +
2763
+ (agentApps.canChange ? '' : '<span class="error">Changing an agent' + String.fromCharCode(8217) + 's tools needs LOOPENGINE_ADMIN_AUTH set.</span>') +
2764
+ (p.message ? '<span class="' + (p.messageBad ? 'error' : 'hint') + '">' + escapeHtml(p.message) + '</span>' : '') + '</div>' +
2765
+ '</div>';
2766
+ }
2767
+
2768
+ function pickerTool(ability, remote) {
2769
+ var tools = agentApps.pickers[ability].tools;
2770
+ for (var i = 0; i < tools.length; i++) if (tools[i].remote === remote) return tools[i];
2771
+ return null;
2772
+ }
2773
+
2774
+ function openPicker(ability) {
2775
+ var name = agentApps.name;
2776
+ agentApps.pickers[ability] = { tools: null, filter: '' };
2777
+ renderAgentApps();
2778
+ fetch('/agents/' + encodeURIComponent(name) + '/apps/' + encodeURIComponent(ability), { cache: 'no-store' })
2779
+ .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
2780
+ .then(function (r) {
2781
+ var p = agentApps.pickers[ability];
2782
+ if (!p || agentApps.name !== name) return;
2783
+ if (!r.ok) p.error = r.body.error || 'Could not list its tools.';
2784
+ else { p.tools = r.body.tools; p.installed = r.body.installed; }
2785
+ renderAgentApps();
2786
+ })
2787
+ .catch(function (err) { var p = agentApps.pickers[ability]; if (p) { p.error = err.message; renderAgentApps(); } });
2788
+ }
2789
+
2790
+ function savePicker(ability) {
2791
+ var name = agentApps.name;
2792
+ var p = agentApps.pickers[ability];
2793
+ var tools = p.tools.filter(function (t) { return t.selected; }).map(function (t) { return { remote: t.remote, decision: t.decision }; });
2794
+ p.busy = true;
2795
+ p.message = 'Saving' + String.fromCharCode(8230);
2796
+ p.messageBad = false;
2797
+ renderAgentApps();
2798
+ fetch('/agents/' + encodeURIComponent(name) + '/apps/' + encodeURIComponent(ability), { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ tools: tools }) })
2799
+ .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
2800
+ .then(function (r) {
2801
+ p.busy = false;
2802
+ if (!r.ok) { p.message = r.body.error || 'Could not save.'; p.messageBad = true; renderAgentApps(); return; }
2803
+ delete agentApps.pickers[ability];
2804
+ // The Installed table and the counts change too.
2805
+ loadAbilitiesTab(name);
2806
+ })
2807
+ .catch(function (err) { p.busy = false; p.message = err.message; p.messageBad = true; renderAgentApps(); });
2808
+ }
2809
+
2810
+ function onAgentAppsClick(ev) {
2811
+ var t = ev.target;
2812
+ if (!t.dataset || !t.dataset.ability) return;
2813
+ var ability = t.dataset.ability;
2814
+ if (t.classList.contains('app-pick-open')) {
2815
+ if (agentApps.pickers[ability]) { delete agentApps.pickers[ability]; renderAgentApps(); }
2816
+ else openPicker(ability);
2817
+ } else if (t.classList.contains('app-pick-readonly') || t.classList.contains('app-pick-none')) {
2818
+ var readOnly = t.classList.contains('app-pick-readonly');
2819
+ agentApps.pickers[ability].tools.forEach(function (tool) { tool.selected = readOnly ? tool.default === 'allow' : false; });
2820
+ renderAgentApps();
2821
+ } else if (t.classList.contains('app-pick-save')) {
2822
+ savePicker(ability);
2823
+ }
2824
+ }
2825
+
2826
+ function onAgentAppsChange(ev) {
2827
+ var t = ev.target;
2828
+ if (!t.dataset || !t.dataset.tool || !agentApps.pickers[t.dataset.ability]) return;
2829
+ var tool = pickerTool(t.dataset.ability, t.dataset.tool);
2830
+ if (!tool) return;
2831
+ if (t.classList.contains('app-pick-check')) tool.selected = t.checked;
2832
+ if (t.classList.contains('app-pick-decision')) { tool.decision = t.value; tool.selected = true; }
2833
+ renderAgentApps();
2834
+ }
2835
+
2836
+ function onAgentAppsInput(ev) {
2837
+ var t = ev.target;
2838
+ if (!t.classList.contains('app-pick-filter')) return;
2839
+ agentApps.pickers[t.dataset.ability].filter = t.value;
2840
+ renderAgentApps();
2841
+ var again = abilitiesContentEl().querySelector('.app-pick-filter[data-ability="' + t.dataset.ability + '"]');
2842
+ if (again) { again.focus(); again.setSelectionRange(again.value.length, again.value.length); }
2668
2843
  }
2669
2844
 
2670
2845
  function applyAbilitiesConfig(name, data) {
@@ -3470,7 +3645,8 @@ ${consoleShellScript}
3470
3645
  var CONFIG_TABS = ['overview', 'abilities', 'actauth', 'messaging', 'schedules', 'env', 'advanced'];
3471
3646
  // The old Tools and Skills tabs: now parts of Advanced. A link to one
3472
3647
  // opens Advanced at that part.
3473
- var TAB_ALIASES = { tools: 'advanced', skills: 'advanced' };
3648
+ // The Apps tab keeps the id 'abilities'; ?tab=apps opens it too.
3649
+ var TAB_ALIASES = { tools: 'advanced', skills: 'advanced', apps: 'abilities' };
3474
3650
 
3475
3651
  function resolveTab(tab) {
3476
3652
  tab = TAB_ALIASES[tab] || tab;
@@ -3607,7 +3783,7 @@ ${consoleShellScript}
3607
3783
  // own skills. Each part keeps its old data-tab-panel name, which the
3608
3784
  // code that redraws it looks for; only .tab-panel is shown or hidden.
3609
3785
  '<div class="tab-panel" data-tab-panel="advanced">' +
3610
- '<p class="muted" style="margin:0 0 20px">For building your own: tools and skills that don&#39;t come from an ability, Composio tools, subagents and permission rules in full. What each ability adds is in its Details on the Abilities tab.</p>' +
3786
+ '<p class="muted" style="margin:0 0 20px">For building your own: tools and skills that don&#39;t come from an ability, Composio tools, subagents and permission rules in full. What each ability adds is in its Details on the Apps tab.</p>' +
3611
3787
  '<h2 class="advanced-part" id="advanced-tools">Tools</h2>' +
3612
3788
  '<div data-tab-panel="tools">' + renderToolsTabHtml(cfg) + '</div>' +
3613
3789
  '<h2 class="advanced-part" id="advanced-skills">Skills</h2>' +
@@ -32,6 +32,7 @@ export const consoleSidebarHtml = `<aside class="le-sidebar" aria-label="Console
32
32
  <a class="le-find" href="/workflows/new" data-le-nav="workflows">Describe a workflow</a>
33
33
  <nav class="le-foot" aria-label="Settings">
34
34
  <a href="/config?section=models" data-le-nav="models">Models</a>
35
+ <a href="/config?section=apps" data-le-nav="apps">Apps</a>
35
36
  <a href="/config?section=abilities" data-le-nav="abilities">Abilities</a>
36
37
  <a href="/config?section=settings" data-le-nav="settings">Settings</a>
37
38
  <div class="le-who" id="leWho" hidden></div>
@@ -51,10 +52,11 @@ export const consoleShellScript = `<script>
51
52
  // after the list loads.
52
53
  var toSet = {};
53
54
  // Tools and Skills live under Advanced (an agent's own tools and skills,
54
- // Composio, subagents): what an agent can do is shown per ability in
55
- // Abilities. ?tab=tools and ?tab=skills still open Advanced.
55
+ // Composio, subagents): what an agent can do is shown per app and
56
+ // ability on the Apps tab (id 'abilities', so older links still work).
57
+ // ?tab=tools and ?tab=skills still open Advanced.
56
58
  var TABS = [
57
- ['overview', 'Overview'], ['chat', 'Chat'], ['abilities', 'Abilities'], ['actauth', 'Permissions'],
59
+ ['overview', 'Overview'], ['chat', 'Chat'], ['abilities', 'Apps'], ['actauth', 'Permissions'],
58
60
  ['messaging', 'Messaging'], ['schedules', 'Schedules'], ['env', 'Environment'], ['advanced', 'Advanced']
59
61
  ];
60
62
 
@@ -170,7 +172,7 @@ export const consoleShellScript = `<script>
170
172
  }
171
173
 
172
174
  var section = params.get('section');
173
- var foot = page === 'config' ? (section === 'abilities' || section === 'gateways' ? 'abilities' : section === 'settings' ? 'settings' : 'models') : page === 'interview' || page === 'templates' ? page : '';
175
+ var foot = page === 'config' ? (section === 'abilities' || section === 'gateways' ? 'abilities' : section === 'apps' ? 'apps' : section === 'settings' ? 'settings' : 'models') : page === 'interview' || page === 'templates' ? page : '';
174
176
  var footLinks = document.querySelectorAll('[data-le-nav]');
175
177
  for (var i = 0; i < footLinks.length; i++) {
176
178
  if (footLinks[i].getAttribute('data-le-nav') === foot) footLinks[i].setAttribute('aria-current', 'page');
@@ -348,7 +350,7 @@ export const consoleShellScript = `<script>
348
350
  }
349
351
 
350
352
  // ---- Abilities: where one came from, and what it can do ----
351
- // Used by the Abilities page and each agent's Abilities tab.
353
+ // Used by the Abilities page and each agent's Apps tab.
352
354
 
353
355
  var DECISION_LABELS = { allow: 'Allow', ask: 'Ask me', deny: 'Never' };
354
356
 
@@ -108,7 +108,7 @@ export const devUiCss = `
108
108
  .le-pill.ask, .le-pill.warn { background: var(--warn-soft); color: var(--warn); }
109
109
  .le-pill.deny, .le-pill.bad { background: var(--bad-soft); color: var(--bad); }
110
110
  /* An ability's details (leShell.toggleAbilityDetails), on the Abilities
111
- page and each agent's Abilities tab. */
111
+ page and each agent's Apps tab. */
112
112
  .ability-details-row > td { padding: 0 10px 14px; border-top: 0; background: transparent; }
113
113
  .ability-details { border: 1px solid var(--accent); border-left-width: 4px; border-radius: var(--radius); background: var(--accent-soft); padding: 12px 16px 14px; }
114
114
  .ability-details-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 6px 16px; margin: 0 0 8px; padding-bottom: 8px; border-bottom: 1px solid var(--line); }