@floless/app 0.25.1 → 0.25.2

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.
@@ -52777,7 +52777,7 @@ function appVersion() {
52777
52777
  return resolveVersion({
52778
52778
  isSea: isSea2(),
52779
52779
  sqVersionXml: readSqVersionXml(),
52780
- define: true ? "0.25.1" : void 0,
52780
+ define: true ? "0.25.2" : void 0,
52781
52781
  pkgVersion: readPkgVersion()
52782
52782
  });
52783
52783
  }
@@ -52787,7 +52787,7 @@ function resolveChannel(s) {
52787
52787
  return "dev";
52788
52788
  }
52789
52789
  function appChannel() {
52790
- return resolveChannel({ isSea: isSea2(), define: true ? "0.25.1" : void 0 });
52790
+ return resolveChannel({ isSea: isSea2(), define: true ? "0.25.2" : void 0 });
52791
52791
  }
52792
52792
 
52793
52793
  // oauth-presets.ts
@@ -57665,11 +57665,29 @@ async function startServer() {
57665
57665
  throw err;
57666
57666
  }
57667
57667
  });
57668
+ const installingAgents = /* @__PURE__ */ new Set();
57669
+ let installQueue = Promise.resolve();
57668
57670
  app.post("/api/agents/install", async (req, reply) => {
57669
57671
  const id = req.body?.id;
57670
57672
  if (!id || typeof id !== "string") return reply.status(400).send({ ok: false, error: "id is required" });
57671
- const result = await aware.ensureAgentInstalled(id);
57672
- return { ok: true, id: result.installed ?? id };
57673
+ if (installingAgents.has(id)) return reply.status(202).send({ ok: true, started: true });
57674
+ installingAgents.add(id);
57675
+ installQueue = installQueue.then(async () => {
57676
+ let result;
57677
+ try {
57678
+ const res = await aware.ensureAgentInstalled(id);
57679
+ result = { type: "agent-install-result", id, ok: true, installed: res.installed ?? id };
57680
+ } catch (err) {
57681
+ result = { type: "agent-install-result", id, ok: false, error: err instanceof Error ? err.message : String(err) };
57682
+ } finally {
57683
+ installingAgents.delete(id);
57684
+ }
57685
+ try {
57686
+ broadcast(result);
57687
+ } catch {
57688
+ }
57689
+ });
57690
+ return reply.status(202).send({ ok: true, started: true });
57673
57691
  });
57674
57692
  app.get("/api/integrations", async () => {
57675
57693
  let integrations = listIntegrations();
package/dist/web/aware.js CHANGED
@@ -3292,6 +3292,23 @@
3292
3292
  loadAgentCatalog().then(() => {
3293
3293
  if ($libModal && $libModal.classList.contains('show')) renderLibrary();
3294
3294
  }).catch(() => {});
3295
+ } else if (m.type === 'agent-install-result') {
3296
+ // A background `aware agent install` (from the Available tab) finished. Clear
3297
+ // its in-flight state, then on success refresh so it drops out of Available and
3298
+ // appears under Installed; on failure restore its Install button + surface why.
3299
+ installingIds.delete(m.id);
3300
+ if (m.ok) {
3301
+ const a = availableCatalog.find((x) => x.id === m.id);
3302
+ showToast(`Installed “${a && a.display_name ? a.display_name : m.id}” — find it in Installed`, 'ok');
3303
+ loadAgentCatalog().then(() => {
3304
+ if (!($libModal && $libModal.classList.contains('show'))) return;
3305
+ renderAvailable();
3306
+ if (libTab === 'installed') renderLibrary();
3307
+ }).catch(() => {});
3308
+ } else {
3309
+ showToast(`Install failed: ${String(m.error || '').slice(0, 80)}`, 'err');
3310
+ if ($libModal && $libModal.classList.contains('show')) renderAvailable();
3311
+ }
3295
3312
  } else if (m.type === 'request-added' || m.type === 'requests-changed') {
3296
3313
  loadRequests();
3297
3314
  if (window.flolessPanels) window.flolessPanels.refreshPending(); // composer's "queued" line
@@ -3796,6 +3813,7 @@
3796
3813
  let availableLoading = false; // a fetch is in flight (prevents a double-fetch race)
3797
3814
  let availableUnsupported = false; // CLI too old to know `agent catalog`
3798
3815
  let libTab = 'installed';
3816
+ const installingIds = new Set(); // ids with an install in flight (server runs it async)
3799
3817
 
3800
3818
  async function loadAvailableAgents() {
3801
3819
  const { agents, unsupported } = await api('/api/agents/available');
@@ -3817,6 +3835,11 @@
3817
3835
  // renders "undefined command". (Numbers, so no escaping needed.)
3818
3836
  const cmds = Number(a.commands) || 0;
3819
3837
  const skills = Number(a.skills) || 0;
3838
+ // The install runs server-side and can take a minute; the button persists its
3839
+ // "Installing…" state across re-renders (search/tab-switch) via installingIds.
3840
+ const installBtn = installingIds.has(a.id)
3841
+ ? `<button class="lib-install" aria-busy="true" aria-label="Installing ${escapeAttr(a.display_name || a.id)}…" data-tip="Downloading from the registry — this can take a minute"><span class="rtn-spinner"></span></button>`
3842
+ : `<button class="lib-install" data-install="${escapeAttr(a.id)}" aria-label="Install ${escapeAttr(a.display_name || a.id)}">Install</button>`;
3820
3843
  return `
3821
3844
  <div class="lib-card" data-agent="${escapeAttr(a.id)}">
3822
3845
  <div class="lib-item">
@@ -3825,7 +3848,7 @@
3825
3848
  <div class="meta">${cmds} command${cmds === 1 ? '' : 's'} · ${skills} skill${skills === 1 ? '' : 's'}</div>
3826
3849
  ${blurb}
3827
3850
  </div>
3828
- <button class="lib-install" data-install="${escapeAttr(a.id)}" aria-label="Install ${escapeAttr(a.display_name || a.id)}">Install</button>
3851
+ ${installBtn}
3829
3852
  </div>
3830
3853
  </div>`;
3831
3854
  }
@@ -3896,29 +3919,28 @@
3896
3919
  });
3897
3920
  }
3898
3921
 
3899
- async function installAgent(id, btn) {
3900
- const label = btn.getAttribute('aria-label') || `Install ${id}`;
3922
+ // Kick off an install. The server runs `aware agent install` asynchronously (it can
3923
+ // take a minute — it downloads from the registry) and reports completion over SSE
3924
+ // (`agent-install-result`), so we DON'T await it here — that's what made the button
3925
+ // look hung. We flip the card to "Installing…" and let the SSE handler finish it.
3926
+ function installAgent(id, btn) {
3927
+ if (installingIds.has(id)) return; // already in flight (server dedupes too)
3928
+ installingIds.add(id);
3901
3929
  const a = availableCatalog.find((x) => x.id === id);
3902
- btn.setAttribute('aria-busy', 'true');
3903
- btn.setAttribute('aria-label', label.replace(/^Install/, 'Installing') + '…');
3904
- btn.innerHTML = '<span class="rtn-spinner"></span>';
3905
- try {
3906
- await api('/api/agents/install', { method: 'POST', body: JSON.stringify({ id }) });
3907
- } catch (e) {
3908
- btn.removeAttribute('aria-busy');
3909
- btn.setAttribute('aria-label', label);
3910
- btn.textContent = 'Install';
3911
- reportErr(e);
3912
- return;
3930
+ const nice = a && a.display_name ? a.display_name : id;
3931
+ if (btn) { // immediate feedback before the next render
3932
+ btn.setAttribute('aria-busy', 'true');
3933
+ btn.setAttribute('aria-label', `Installing ${nice}…`);
3934
+ btn.removeAttribute('data-install'); // a stray re-wire can't re-trigger it
3935
+ btn.innerHTML = '<span class="rtn-spinner"></span>';
3913
3936
  }
3914
- // Installed now signal success BEFORE the catalog refresh so a transient refresh
3915
- // failure can't swallow the confirmation of an install that already succeeded.
3916
- showToast(`Installed “${a && a.display_name ? a.display_name : id}”find it in Installed`, 'ok');
3917
- // Refresh the Installed catalog (footer count + Installed tab) best-effort, then
3918
- // re-render Available so the now-installed agent drops out (= not-yet-installed).
3919
- try { await loadAgentCatalog(); } catch (e) { console.warn('post-install catalog refresh failed', e); }
3920
- renderAvailable();
3921
- if (libTab === 'installed') renderLibrary();
3937
+ showToast(`Installing “${nice}”this can take a minute or two`, 'info');
3938
+ api('/api/agents/install', { method: 'POST', body: JSON.stringify({ id }) })
3939
+ .catch((e) => { // the START failed (bad id / server) completion errors arrive via SSE
3940
+ installingIds.delete(id);
3941
+ if ($libModal && $libModal.classList.contains('show')) renderAvailable();
3942
+ reportErr(e);
3943
+ });
3922
3944
  }
3923
3945
 
3924
3946
  function switchLibTab(tab) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floless/app",
3
- "version": "0.25.1",
3
+ "version": "0.25.2",
4
4
  "type": "module",
5
5
  "description": "Thin localhost host for floless.app — serves web/ and shells the aware CLI. No engine, no LLM.",
6
6
  "bin": {