Oracle's wave-2 watch-outs:
1. Wizard config persistence — the follow-up PUT was silently swallowed.
Empirical curl reproducer confirmed config.wizard DOES land after the
split create/PUT (config.wizard.marker=EA2_DRAFT_PROCESS visible on
re-GET). But to satisfy the 'no silent loss' concern: the catch now
warns to console AND surfaces a soft toast 'Draft saved, but its
wizard state didn't attach. You can keep working — save will retry
on Confirm.' so failure is loud without blocking the user.
2. LLM telemetry — fallback was masking infra health. llmClient now
logs every outcome with elapsed time: 503/404/502/504/non-2xx/parse
failures all console.warn with reason + ms. Success path
console.info with provider + content length. Friendly fallback to
the user stays the same; ops/devs see the real story.
3. Catalog hygiene — duplicate 'Laptop Procurement' rows. list_processes
now dedupes by normalized display_name after the existing _key
dedupe. EA2 seeds + tenant imports both publishing the same name
collapse to one row in the assistant output.
4. Mission intuitiveness — user said 'I don't understand anything that's
going on there.' Added a one-line first-visit primer strip
('What you're looking at. Each tab below is a process running in
your company...') dismissible with an X, sticky to localStorage.
Also fixed the stale 'showing snapshot' fallback copy in the
liveError banner (snapshot mode was removed in wave 1; banner now
says 'last known state shown').
30/30 vitest pass. tsc + vite build green.
45 lines
1.7 KiB
JavaScript
45 lines
1.7 KiB
JavaScript
// Targeted LLM path verification — ask a question that no deterministic tool matches.
|
|
import { chromium } from "playwright";
|
|
|
|
const b = await chromium.launch({ headless: true });
|
|
const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } });
|
|
const p = await ctx.newPage();
|
|
await ctx.addInitScript(() => localStorage.setItem("fm.canvas.tour.seen", String(Date.now())));
|
|
|
|
const calls = [];
|
|
p.on("response", (r) => {
|
|
const u = r.url();
|
|
if (u.includes("/api/v1/llm/")) calls.push({ status: r.status(), url: u.replace("https://canvas.flow-master.ai", "") });
|
|
});
|
|
|
|
await p.goto("https://canvas.flow-master.ai/", { waitUntil: "domcontentloaded" });
|
|
await p.waitForTimeout(2500);
|
|
await p.locator(".persona-chip").filter({ hasText: /Mariana/ }).click();
|
|
await p.waitForSelector(".topbar", { timeout: 30000 });
|
|
await p.waitForTimeout(2000);
|
|
|
|
await p.locator(".tab").filter({ hasText: /Assistant/i }).first().click();
|
|
await p.waitForTimeout(3000);
|
|
|
|
// A question no tool matches
|
|
const input = p.locator(".agent-composer textarea").first();
|
|
const queries = [
|
|
"Explain what EA2 means in two sentences.",
|
|
"What is the difference between a flow and a view?",
|
|
"Tell me a joke about procurement.",
|
|
];
|
|
for (const q of queries) {
|
|
console.log(`\n→ "${q}"`);
|
|
await input.fill(q);
|
|
await input.press("Enter");
|
|
await p.waitForTimeout(8000);
|
|
const replies = await p.locator(".agent-turn-body").allTextContents();
|
|
console.log(" last reply:", replies.slice(-1)[0]?.slice(0, 280));
|
|
}
|
|
|
|
console.log("\n=== /api/v1/llm/* calls ===");
|
|
for (const c of calls) console.log(` ${c.status} ${c.url}`);
|
|
|
|
await p.screenshot({ path: "/tmp/canvas-evidence/llm-path.png" });
|
|
await b.close();
|