@floless/app 0.25.1 → 0.26.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/floless-server.cjs +30 -4
- package/dist/web/app.css +11 -2
- package/dist/web/app.js +9 -5
- package/dist/web/aware.js +76 -23
- package/package.json +1 -1
package/dist/floless-server.cjs
CHANGED
|
@@ -52777,7 +52777,7 @@ function appVersion() {
|
|
|
52777
52777
|
return resolveVersion({
|
|
52778
52778
|
isSea: isSea2(),
|
|
52779
52779
|
sqVersionXml: readSqVersionXml(),
|
|
52780
|
-
define: true ? "0.
|
|
52780
|
+
define: true ? "0.26.0" : 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.
|
|
52790
|
+
return resolveChannel({ isSea: isSea2(), define: true ? "0.26.0" : void 0 });
|
|
52791
52791
|
}
|
|
52792
52792
|
|
|
52793
52793
|
// oauth-presets.ts
|
|
@@ -54211,6 +54211,14 @@ async function runRoutine(id) {
|
|
|
54211
54211
|
runId: res.runId ?? void 0,
|
|
54212
54212
|
durationMs: Date.now() - startedAt.getTime()
|
|
54213
54213
|
});
|
|
54214
|
+
try {
|
|
54215
|
+
const report = withBadge(extractReportHtml(events, r.workflow));
|
|
54216
|
+
if (report?.html) {
|
|
54217
|
+
broadcast({ type: "routine-report", id: r.id, workflow: r.workflow, nodeId: report.nodeId, html: report.html });
|
|
54218
|
+
}
|
|
54219
|
+
} catch (err) {
|
|
54220
|
+
console.warn(`[floless] routine "${r.id}" report surfacing failed (run still recorded ${outcome.status}):`, err instanceof Error ? err.message : err);
|
|
54221
|
+
}
|
|
54214
54222
|
} catch (err) {
|
|
54215
54223
|
const msg = err instanceof Error ? err.message : String(err);
|
|
54216
54224
|
recordRun(r, {
|
|
@@ -57665,11 +57673,29 @@ async function startServer() {
|
|
|
57665
57673
|
throw err;
|
|
57666
57674
|
}
|
|
57667
57675
|
});
|
|
57676
|
+
const installingAgents = /* @__PURE__ */ new Set();
|
|
57677
|
+
let installQueue = Promise.resolve();
|
|
57668
57678
|
app.post("/api/agents/install", async (req, reply) => {
|
|
57669
57679
|
const id = req.body?.id;
|
|
57670
57680
|
if (!id || typeof id !== "string") return reply.status(400).send({ ok: false, error: "id is required" });
|
|
57671
|
-
|
|
57672
|
-
|
|
57681
|
+
if (installingAgents.has(id)) return reply.status(202).send({ ok: true, started: true });
|
|
57682
|
+
installingAgents.add(id);
|
|
57683
|
+
installQueue = installQueue.then(async () => {
|
|
57684
|
+
let result;
|
|
57685
|
+
try {
|
|
57686
|
+
const res = await aware.ensureAgentInstalled(id);
|
|
57687
|
+
result = { type: "agent-install-result", id, ok: true, installed: res.installed ?? id };
|
|
57688
|
+
} catch (err) {
|
|
57689
|
+
result = { type: "agent-install-result", id, ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
57690
|
+
} finally {
|
|
57691
|
+
installingAgents.delete(id);
|
|
57692
|
+
}
|
|
57693
|
+
try {
|
|
57694
|
+
broadcast(result);
|
|
57695
|
+
} catch {
|
|
57696
|
+
}
|
|
57697
|
+
});
|
|
57698
|
+
return reply.status(202).send({ ok: true, started: true });
|
|
57673
57699
|
});
|
|
57674
57700
|
app.get("/api/integrations", async () => {
|
|
57675
57701
|
let integrations = listIntegrations();
|
package/dist/web/app.css
CHANGED
|
@@ -344,6 +344,12 @@
|
|
|
344
344
|
overflow: hidden;
|
|
345
345
|
min-width: 0;
|
|
346
346
|
position: relative;
|
|
347
|
+
/* The whole canvas is a pannable infinite surface (drag the background / middle-mouse),
|
|
348
|
+
so the grab affordance covers the entire grid-area — not just .topology, which is
|
|
349
|
+
sized around the node cards and left distant background areas as a plain arrow (#115).
|
|
350
|
+
Interactive controls (toolbar buttons, template chips, find input, node cards) carry
|
|
351
|
+
their own cursor, so grab never reads as draggable over a clickable target. */
|
|
352
|
+
cursor: grab;
|
|
347
353
|
}
|
|
348
354
|
/* While middle-mouse panning, force the grab cursor over the whole canvas. */
|
|
349
355
|
.canvas.panning, .canvas.panning * { cursor: grabbing !important; }
|
|
@@ -378,9 +384,9 @@
|
|
|
378
384
|
overflow:visible also means no scrollbar — drag the background / middle-mouse
|
|
379
385
|
to pan, zoom, or Fit this infinite canvas. */
|
|
380
386
|
overflow: visible;
|
|
381
|
-
cursor: grab;
|
|
382
387
|
}
|
|
383
|
-
/*
|
|
388
|
+
/* The grab cursor lives on the parent `.canvas` (covers the full surface, #115); the
|
|
389
|
+
grabbing cursor during a pan is forced by `.canvas.panning *` above. */
|
|
384
390
|
|
|
385
391
|
/* Canvas chrome (top label, bottom hint + Templates bar) paints ABOVE the
|
|
386
392
|
transform-panned topology with an OPAQUE background, so dragging the canvas
|
|
@@ -2864,6 +2870,9 @@ body {
|
|
|
2864
2870
|
.canvas.view-dashboard .fav-bar,
|
|
2865
2871
|
.canvas.view-dashboard .notes-strip,
|
|
2866
2872
|
.canvas.view-dashboard .find-overlay { display: none; }
|
|
2873
|
+
/* In dashboard view the pannable topology is hidden, so the canvas isn't a drag surface —
|
|
2874
|
+
drop the grab affordance the .canvas rule sets for the normal view (#115 follow-up). */
|
|
2875
|
+
.canvas.view-dashboard { cursor: default; }
|
|
2867
2876
|
.dashboard { flex: 1; min-height: 0; overflow-y: auto; padding: 18px 24px 24px; }
|
|
2868
2877
|
.dashboard[hidden] { display: none; }
|
|
2869
2878
|
|
package/dist/web/app.js
CHANGED
|
@@ -1422,11 +1422,15 @@ $canvasEl.addEventListener('mousedown', (e) => {
|
|
|
1422
1422
|
startPan(e);
|
|
1423
1423
|
});
|
|
1424
1424
|
// Left-drag the empty canvas background also pans — so the infinite, scrollbar-less
|
|
1425
|
-
// canvas is navigable with any pointer (trackpads have no middle button).
|
|
1426
|
-
//
|
|
1427
|
-
//
|
|
1428
|
-
|
|
1429
|
-
|
|
1425
|
+
// canvas is navigable with any pointer (trackpads have no middle button). On the full
|
|
1426
|
+
// `.canvas` (not just `.topology`) so panning reaches every background area, matching the
|
|
1427
|
+
// middle-mouse handler above (#115). Bail on interactive chrome so clicks/drags there still
|
|
1428
|
+
// work: node cards (their own select + HTML5 drag), the zoom toolbar, the Templates
|
|
1429
|
+
// fav-bar, and the find input. Non-interactive chrome (panel label, hint, notes) still pans.
|
|
1430
|
+
// `.dashboard` (the custom-panels view that replaces the topology) is excluded too — it has
|
|
1431
|
+
// its own scroll/click surface and the topology it would pan is hidden in that view.
|
|
1432
|
+
$canvasEl.addEventListener('mousedown', (e) => {
|
|
1433
|
+
if (e.button !== 0 || e.target.closest('.agent-card, .canvas-toolbar, .fav-bar, .find-overlay, .dashboard')) return;
|
|
1430
1434
|
e.preventDefault();
|
|
1431
1435
|
startPan(e);
|
|
1432
1436
|
});
|
package/dist/web/aware.js
CHANGED
|
@@ -1240,6 +1240,9 @@
|
|
|
1240
1240
|
const ovStop = document.querySelector('.overlay-stop');
|
|
1241
1241
|
if (ovStop) { ovStop.disabled = true; ovStop.textContent = 'Stopping…'; }
|
|
1242
1242
|
try { await api(`/api/trigger-run/${encodeURIComponent(ft.appId)}/stop`, { method: 'POST' }); } catch { /* best effort — the session may already be gone */ }
|
|
1243
|
+
// Clear the canvas "running" pulse just like the regular-run stop below — the
|
|
1244
|
+
// early return here previously skipped it, leaving nodes stuck on `running` (#116).
|
|
1245
|
+
clearNodeStatus();
|
|
1243
1246
|
// A deliberate stop needs no toast — the subtitle change is the confirmation.
|
|
1244
1247
|
paintForegroundStopped('stopped');
|
|
1245
1248
|
syncRunControls();
|
|
@@ -3292,6 +3295,23 @@
|
|
|
3292
3295
|
loadAgentCatalog().then(() => {
|
|
3293
3296
|
if ($libModal && $libModal.classList.contains('show')) renderLibrary();
|
|
3294
3297
|
}).catch(() => {});
|
|
3298
|
+
} else if (m.type === 'agent-install-result') {
|
|
3299
|
+
// A background `aware agent install` (from the Available tab) finished. Clear
|
|
3300
|
+
// its in-flight state, then on success refresh so it drops out of Available and
|
|
3301
|
+
// appears under Installed; on failure restore its Install button + surface why.
|
|
3302
|
+
installingIds.delete(m.id);
|
|
3303
|
+
if (m.ok) {
|
|
3304
|
+
const a = availableCatalog.find((x) => x.id === m.id);
|
|
3305
|
+
showToast(`Installed “${a && a.display_name ? a.display_name : m.id}” — find it in Installed`, 'ok');
|
|
3306
|
+
loadAgentCatalog().then(() => {
|
|
3307
|
+
if (!($libModal && $libModal.classList.contains('show'))) return;
|
|
3308
|
+
renderAvailable();
|
|
3309
|
+
if (libTab === 'installed') renderLibrary();
|
|
3310
|
+
}).catch(() => {});
|
|
3311
|
+
} else {
|
|
3312
|
+
showToast(`Install failed: ${String(m.error || '').slice(0, 80)}`, 'err');
|
|
3313
|
+
if ($libModal && $libModal.classList.contains('show')) renderAvailable();
|
|
3314
|
+
}
|
|
3295
3315
|
} else if (m.type === 'request-added' || m.type === 'requests-changed') {
|
|
3296
3316
|
loadRequests();
|
|
3297
3317
|
if (window.flolessPanels) window.flolessPanels.refreshPending(); // composer's "queued" line
|
|
@@ -3319,6 +3339,8 @@
|
|
|
3319
3339
|
else applyTriggerSnapshot(m.id, m.snapshot);
|
|
3320
3340
|
} else if (m.type === 'trigger-report') {
|
|
3321
3341
|
applyTriggerReport(m);
|
|
3342
|
+
} else if (m.type === 'routine-report') {
|
|
3343
|
+
applyRoutineReport(m);
|
|
3322
3344
|
} else if (m.type === 'connect-result') {
|
|
3323
3345
|
// A run's credential-reconnect card may be waiting on this integration —
|
|
3324
3346
|
// drive its success/fail state from the same SSE result.
|
|
@@ -3489,7 +3511,7 @@
|
|
|
3489
3511
|
if (!tpls.length) { $favChipRow.innerHTML = ''; $favBarEmpty.style.display = 'block'; return; }
|
|
3490
3512
|
$favBarEmpty.style.display = 'none';
|
|
3491
3513
|
$favChipRow.innerHTML = tpls.map((t) => `
|
|
3492
|
-
<div class="fav-chip" data-tpl="${escapeAttr(t.id)}" data-tip="Click to use ·
|
|
3514
|
+
<div class="fav-chip" data-tpl="${escapeAttr(t.id)}" data-tip="Click to use · ${escapeAttr(t.category)} · ${escapeAttr((t.node.agent || t.node.kind) + (t.node.command ? '/' + t.node.command : ''))}">
|
|
3493
3515
|
<span class="cat">${escapeHtml(t.category)}</span>
|
|
3494
3516
|
<span class="name">${escapeHtml(t.name)}</span>
|
|
3495
3517
|
<span class="edit" data-tip="Rename / recategorize" aria-label="Edit template">✎</span>
|
|
@@ -3796,6 +3818,7 @@
|
|
|
3796
3818
|
let availableLoading = false; // a fetch is in flight (prevents a double-fetch race)
|
|
3797
3819
|
let availableUnsupported = false; // CLI too old to know `agent catalog`
|
|
3798
3820
|
let libTab = 'installed';
|
|
3821
|
+
const installingIds = new Set(); // ids with an install in flight (server runs it async)
|
|
3799
3822
|
|
|
3800
3823
|
async function loadAvailableAgents() {
|
|
3801
3824
|
const { agents, unsupported } = await api('/api/agents/available');
|
|
@@ -3817,6 +3840,11 @@
|
|
|
3817
3840
|
// renders "undefined command". (Numbers, so no escaping needed.)
|
|
3818
3841
|
const cmds = Number(a.commands) || 0;
|
|
3819
3842
|
const skills = Number(a.skills) || 0;
|
|
3843
|
+
// The install runs server-side and can take a minute; the button persists its
|
|
3844
|
+
// "Installing…" state across re-renders (search/tab-switch) via installingIds.
|
|
3845
|
+
const installBtn = installingIds.has(a.id)
|
|
3846
|
+
? `<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>`
|
|
3847
|
+
: `<button class="lib-install" data-install="${escapeAttr(a.id)}" aria-label="Install ${escapeAttr(a.display_name || a.id)}">Install</button>`;
|
|
3820
3848
|
return `
|
|
3821
3849
|
<div class="lib-card" data-agent="${escapeAttr(a.id)}">
|
|
3822
3850
|
<div class="lib-item">
|
|
@@ -3825,7 +3853,7 @@
|
|
|
3825
3853
|
<div class="meta">${cmds} command${cmds === 1 ? '' : 's'} · ${skills} skill${skills === 1 ? '' : 's'}</div>
|
|
3826
3854
|
${blurb}
|
|
3827
3855
|
</div>
|
|
3828
|
-
|
|
3856
|
+
${installBtn}
|
|
3829
3857
|
</div>
|
|
3830
3858
|
</div>`;
|
|
3831
3859
|
}
|
|
@@ -3896,29 +3924,28 @@
|
|
|
3896
3924
|
});
|
|
3897
3925
|
}
|
|
3898
3926
|
|
|
3899
|
-
|
|
3900
|
-
|
|
3927
|
+
// Kick off an install. The server runs `aware agent install` asynchronously (it can
|
|
3928
|
+
// take a minute — it downloads from the registry) and reports completion over SSE
|
|
3929
|
+
// (`agent-install-result`), so we DON'T await it here — that's what made the button
|
|
3930
|
+
// look hung. We flip the card to "Installing…" and let the SSE handler finish it.
|
|
3931
|
+
function installAgent(id, btn) {
|
|
3932
|
+
if (installingIds.has(id)) return; // already in flight (server dedupes too)
|
|
3933
|
+
installingIds.add(id);
|
|
3901
3934
|
const a = availableCatalog.find((x) => x.id === id);
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
btn.removeAttribute('aria-busy');
|
|
3909
|
-
btn.setAttribute('aria-label', label);
|
|
3910
|
-
btn.textContent = 'Install';
|
|
3911
|
-
reportErr(e);
|
|
3912
|
-
return;
|
|
3935
|
+
const nice = a && a.display_name ? a.display_name : id;
|
|
3936
|
+
if (btn) { // immediate feedback before the next render
|
|
3937
|
+
btn.setAttribute('aria-busy', 'true');
|
|
3938
|
+
btn.setAttribute('aria-label', `Installing ${nice}…`);
|
|
3939
|
+
btn.removeAttribute('data-install'); // a stray re-wire can't re-trigger it
|
|
3940
|
+
btn.innerHTML = '<span class="rtn-spinner"></span>';
|
|
3913
3941
|
}
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
if (libTab === 'installed') renderLibrary();
|
|
3942
|
+
showToast(`Installing “${nice}” — this can take a minute or two`, 'info');
|
|
3943
|
+
api('/api/agents/install', { method: 'POST', body: JSON.stringify({ id }) })
|
|
3944
|
+
.catch((e) => { // the START failed (bad id / server) — completion errors arrive via SSE
|
|
3945
|
+
installingIds.delete(id);
|
|
3946
|
+
if ($libModal && $libModal.classList.contains('show')) renderAvailable();
|
|
3947
|
+
reportErr(e);
|
|
3948
|
+
});
|
|
3922
3949
|
}
|
|
3923
3950
|
|
|
3924
3951
|
function switchLibTab(tab) {
|
|
@@ -4273,6 +4300,32 @@
|
|
|
4273
4300
|
pulseReportFrame();
|
|
4274
4301
|
}
|
|
4275
4302
|
|
|
4303
|
+
// A SCHEDULED routine (a timed `aware app run`) produced a report (#113). Mirrors
|
|
4304
|
+
// applyTriggerReport for the timed path: always refresh the cache so "View report" shows
|
|
4305
|
+
// the latest result, and if THIS app's viewer is open, live-repaint it. An unattended
|
|
4306
|
+
// routine (e.g. hourly) shouldn't beep on every tick, so the toast fires ONLY when the
|
|
4307
|
+
// report HTML actually changed (e.g. an exception-monitor flips from "all clear" to
|
|
4308
|
+
// "exception detected"). Never auto-opens the viewer, never hijacks another app's open
|
|
4309
|
+
// viewer, never stomps an in-flight manual run or a live foreground session.
|
|
4310
|
+
function applyRoutineReport(m) {
|
|
4311
|
+
if (!m || !m.workflow || typeof m.html !== 'string') return;
|
|
4312
|
+
const prev = lastReportByApp.get(m.workflow);
|
|
4313
|
+
const changed = !prev || prev.html !== m.html;
|
|
4314
|
+
lastReportByApp.set(m.workflow, { nodeId: m.nodeId || null, label: 'scheduled', html: m.html });
|
|
4315
|
+
const open = $reportModal && $reportModal.classList.contains('show');
|
|
4316
|
+
if (open && currentId === m.workflow && !reportRunning && !foregroundTrigger) {
|
|
4317
|
+
paintReport(m.html); // unhides Share + clears the overlay
|
|
4318
|
+
const t = new Date().toLocaleTimeString();
|
|
4319
|
+
$reportSub.innerHTML =
|
|
4320
|
+
`<span class="live-pip" aria-hidden="true"></span>Scheduled · updated ${escapeHtml(t)} — rendered from the node's output, never composed by the UI.`;
|
|
4321
|
+
pulseReportFrame();
|
|
4322
|
+
}
|
|
4323
|
+
if (changed) {
|
|
4324
|
+
const app = apps.get(m.workflow);
|
|
4325
|
+
showToast(`Report updated — ${app ? app.displayName : m.workflow}`, 'info');
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4276
4329
|
// Brief edge-flash on the report stage when a live update lands, so the change
|
|
4277
4330
|
// is noticeable. Forced reflow re-arms it on rapid repeats. CSS honors
|
|
4278
4331
|
// prefers-reduced-motion.
|