omnius 1.0.664 → 1.0.665

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.
@@ -7,8 +7,10 @@ compaction_strategy: structured
7
7
  # browser-interaction-validation
8
8
 
9
9
  Validate user-facing browser flows with `playwright_browser` as the canonical
10
- runtime. A page loading, a successful click receipt, a screenshot, or static
11
- source assertions alone do **not** prove that a requested interaction worked.
10
+ runtime. A page loading, a successful click receipt, an immediate screenshot,
11
+ or static source assertions alone do **not** prove that a requested interaction
12
+ worked. Some failures appear only after the page remains active. GPU and WebGL
13
+ failures can also differ between headless and headed browsers.
12
14
 
13
15
  ## Triggers
14
16
 
@@ -39,9 +41,12 @@ static checks separately as static evidence.
39
41
  1. Use `playwright_browser` for the entire flow. Do not switch to
40
42
  `browser_action` (Selenium) mid-flow; it is a separate browser session and
41
43
  loses page state. Use Selenium only as an explicit legacy fallback.
42
- 2. Navigate to the live URL, then call `observe_bundle` before interacting.
43
- Preserve URL, relevant DOM/a11y selectors, screenshot, page errors, console,
44
- and network observations.
44
+ 2. Start with `validate_lifecycle`. Give it the live URL, an explicit
45
+ postcondition, and a finite `observation_ms`. This starts diagnostics before
46
+ navigation and holds the page open after load. For GUI, GPU, WebGL, WebGPU,
47
+ canvas, or animation claims, set `headless=false` and `force_new=true`. If a
48
+ graphical display is unavailable, report the headed check as blocked. Do not
49
+ substitute a clean headless run as proof.
45
50
  3. Choose a stable locator: prefer `data-testid`, then accessible role/name,
46
51
  then a specific CSS selector. Avoid ordinal or broad selectors that can race
47
52
  reactive re-renders.
@@ -52,9 +57,11 @@ static checks separately as static evidence.
52
57
  Re-query after a detached-element/re-render failure; do not retry a stale
53
58
  handle blindly. If a stable locator is unavailable, report that as a
54
59
  product-testability finding.
55
- 6. Wait for the specific state, then capture post-action `observe_bundle` and
56
- a focused DOM/text/a11y observation that proves the postcondition. Include
57
- page errors, console errors, and failed network requests in the result.
60
+ 6. After the action, call `observe_window` with the expected state and a finite
61
+ `observation_ms`. Keep the same browser session alive while delayed failures
62
+ surface. Review the window-scoped page errors, console errors, failed network
63
+ requests, DOM state, and screenshot. Use `observe_bundle` only for an
64
+ instantaneous session summary when a timed observation is not required.
58
65
  7. Close by listing every requested interaction with its evidence outcome,
59
66
  receipt/artifact references, and any precise remediation. Call
60
67
  `task_complete` when the implementation work is done; verifier feedback is
@@ -289947,7 +289947,6 @@ init_process_lifecycle();
289947
289947
  import { dirname, join, resolve as resolve2 } from "node:path";
289948
289948
  import { homedir as homedir2, tmpdir } from "node:os";
289949
289949
  init_vision();
289950
- var dynamicImport = new Function("mod", "return import(mod)");
289951
289950
  var PLAYWRIGHT_RUNTIME_DIR = join(homedir2(), ".omnius", "playwright-runtime");
289952
289951
  var PLAYWRIGHT_BROWSERS_DIR = join(PLAYWRIGHT_RUNTIME_DIR, "browsers");
289953
289952
 
package/dist/index.js CHANGED
@@ -63552,6 +63552,30 @@ function pushBounded(buf, item) {
63552
63552
  if (buf.length > MAX_BUFFER)
63553
63553
  buf.splice(0, buf.length - MAX_BUFFER);
63554
63554
  }
63555
+ function captureBrowserDiagnosticCursor() {
63556
+ return {
63557
+ startedAtMs: Date.now(),
63558
+ consoleIndex: consoleBuffer.length,
63559
+ networkIndex: networkBuffer.length,
63560
+ errorIndex: errorBuffer.length,
63561
+ websocketIndex: wsBuffer.length,
63562
+ crashCount
63563
+ };
63564
+ }
63565
+ function diagnosticEntriesAfter(entries2, index, startedAtMs) {
63566
+ return index <= entries2.length ? entries2.slice(index) : entries2.filter((entry) => entry.ts >= startedAtMs);
63567
+ }
63568
+ function browserDiagnosticWindow(cursor) {
63569
+ return {
63570
+ startedAtMs: cursor.startedAtMs,
63571
+ endedAtMs: Date.now(),
63572
+ console: diagnosticEntriesAfter(consoleBuffer, cursor.consoleIndex, cursor.startedAtMs),
63573
+ network: diagnosticEntriesAfter(networkBuffer, cursor.networkIndex, cursor.startedAtMs),
63574
+ errors: diagnosticEntriesAfter(errorBuffer, cursor.errorIndex, cursor.startedAtMs),
63575
+ websockets: diagnosticEntriesAfter(wsBuffer, cursor.websocketIndex, cursor.startedAtMs),
63576
+ crashCount: Math.max(0, crashCount - cursor.crashCount)
63577
+ };
63578
+ }
63555
63579
  function which(cmd) {
63556
63580
  try {
63557
63581
  const out = execFileSync("which", [cmd], {
@@ -63663,6 +63687,11 @@ function asOptionalBoolean(value2) {
63663
63687
  function defaultPlaywrightHeadless() {
63664
63688
  return asOptionalBoolean(process.env["OMNIUS_PLAYWRIGHT_HEADLESS"] ?? process.env["OMNIUS_BROWSER_HEADLESS"]) ?? true;
63665
63689
  }
63690
+ function hasGraphicalBrowserDisplay() {
63691
+ if (process.platform !== "linux")
63692
+ return true;
63693
+ return Boolean(process.env["DISPLAY"]?.trim() || process.env["WAYLAND_DISPLAY"]?.trim());
63694
+ }
63666
63695
  async function installPlaywrightBrowser() {
63667
63696
  try {
63668
63697
  mkdirSync18(PLAYWRIGHT_RUNTIME_DIR, { recursive: true });
@@ -63927,37 +63956,6 @@ function attachDiagnosticListeners(p2) {
63927
63956
  } catch {
63928
63957
  }
63929
63958
  });
63930
- if (!initScriptInstalled) {
63931
- try {
63932
- p2.addInitScript(`(() => {
63933
- if (window.__browserEvents) return;
63934
- window.__browserEvents = [];
63935
- const push = (evt) => {
63936
- window.__browserEvents.push(evt);
63937
- if (window.__browserEvents.length > 2000) window.__browserEvents.splice(0, window.__browserEvents.length - 2000);
63938
- };
63939
- const origError = console.error;
63940
- console.error = function() {
63941
- push({ ts: Date.now(), type: "console:error", text: Array.from(arguments).map(String).join(" ").slice(0, 2000) });
63942
- return origError.apply(console, arguments);
63943
- };
63944
- const origWarn = console.warn;
63945
- console.warn = function() {
63946
- push({ ts: Date.now(), type: "console:warn", text: Array.from(arguments).map(String).join(" ").slice(0, 2000) });
63947
- return origWarn.apply(console, arguments);
63948
- };
63949
- window.onerror = function(msg, source, lineno, colno, err) {
63950
- push({ ts: Date.now(), type: "onerror", text: String(msg).slice(0, 2000), stack: err instanceof Error ? (err.stack || "").slice(0, 3000) : "" });
63951
- };
63952
- window.onunhandledrejection = function(evt) {
63953
- const r = evt.reason;
63954
- push({ ts: Date.now(), type: "unhandledRejection", text: String(r?.message ?? r ?? "unknown").slice(0, 2000), stack: typeof r?.stack === "string" ? r.stack.slice(0, 3000) : "" });
63955
- };
63956
- })()`);
63957
- initScriptInstalled = true;
63958
- } catch {
63959
- }
63960
- }
63961
63959
  }
63962
63960
  function clearDiagnosticBuffers() {
63963
63961
  consoleBuffer = [];
@@ -64145,6 +64143,9 @@ async function runBrowserLocatorAction(pageHandle, selector, index, timeout2, ac
64145
64143
  async function waitForBrowserExpectation(pageHandle, expectation, timeout2) {
64146
64144
  await pageHandle.locator(expectation).first().waitFor({ state: "visible", timeout: timeout2 });
64147
64145
  }
64146
+ async function browserExpectationIsVisible(pageHandle, expectation) {
64147
+ return Boolean(await pageHandle.locator(expectation).first().isVisible().catch(() => false));
64148
+ }
64148
64149
  async function runBrowserInteractionWithEvidence(input) {
64149
64150
  const before = await captureBrowserInteractionSnapshot(input.pageHandle);
64150
64151
  let performed = null;
@@ -64736,6 +64737,169 @@ async function findBrowserVisualCandidate(pageHandle, target, visualX, visualY,
64736
64737
  }
64737
64738
  return candidates.filter((candidate) => includeOffscreen || candidate["visible"] === true).sort((a2, b) => Number(b["score"] ?? 0) - Number(a2["score"] ?? 0))[0] ?? null;
64738
64739
  }
64740
+ async function captureBrowserObservation(input) {
64741
+ const html = await input.pageHandle.content();
64742
+ const readableText = await input.pageHandle.evaluate(`(() => {
64743
+ const clone = document.body ? document.body.cloneNode(true) : document.documentElement.cloneNode(true);
64744
+ clone.querySelectorAll("script, style, noscript, svg, path").forEach(el => el.remove());
64745
+ return clone.innerText || clone.textContent || "";
64746
+ })()`);
64747
+ const summary = downsampleDom(html);
64748
+ lastDomSummarySelectors = extractDomSummarySelectors(summary);
64749
+ const screenshotBuffer = await input.pageHandle.screenshot({
64750
+ fullPage: input.fullPage
64751
+ });
64752
+ const screenshotPath = saveBuffer(input.outputPath || defaultArtifactPath(input.action, "png"), screenshotBuffer);
64753
+ const viewport = input.pageHandle.viewportSize?.() ?? {
64754
+ width: 0,
64755
+ height: 0
64756
+ };
64757
+ const diagnostics = input.cursor ? browserDiagnosticWindow(input.cursor) : {
64758
+ startedAtMs: input.startMs,
64759
+ endedAtMs: Date.now(),
64760
+ console: [...consoleBuffer],
64761
+ network: [...networkBuffer],
64762
+ errors: [...errorBuffer],
64763
+ websockets: [...wsBuffer],
64764
+ crashCount
64765
+ };
64766
+ const consoleErrors = diagnostics.console.filter((entry) => /error/i.test(entry.type)).slice(-30);
64767
+ const consoleWarnings = diagnostics.console.filter((entry) => /warn/i.test(entry.type)).slice(-30);
64768
+ const networkFailures = diagnostics.network.filter((entry) => entry.ok === false || (entry.status ?? 0) >= 400).slice(-30);
64769
+ const pageErrors = diagnostics.errors.slice(-30);
64770
+ const gate = classifyBrowserGate({
64771
+ url: input.pageHandle.url(),
64772
+ html,
64773
+ text: String(readableText ?? ""),
64774
+ network: diagnostics.network,
64775
+ errors: diagnostics.errors
64776
+ });
64777
+ const actualObservationMs = Math.max(0, diagnostics.endedAtMs - diagnostics.startedAtMs);
64778
+ const runtimeFailureCount = consoleErrors.length + pageErrors.length + networkFailures.length + diagnostics.crashCount;
64779
+ const blocked = gate.kind === "captcha" || gate.kind === "bot_challenge";
64780
+ const contradicted = input.expectationSatisfied === false || runtimeFailureCount > 0;
64781
+ const verification = blocked ? {
64782
+ status: "blocked",
64783
+ confidence: gate.confidence,
64784
+ reason: `Browser observation was blocked by gate=${gate.kind}.`
64785
+ } : contradicted ? {
64786
+ status: "contradicted",
64787
+ confidence: 0.96,
64788
+ reason: input.expectationSatisfied === false ? `The declared postcondition was not observed: ${input.expectation}.` : `The browser emitted ${runtimeFailureCount} runtime failure signal(s) during the observation window.`,
64789
+ nextSuggestedObservation: "Fix the concrete console/page/network failure, then rerun validate_lifecycle or observe_window in the same browser mode."
64790
+ } : input.expectationSatisfied === true ? {
64791
+ status: "proven",
64792
+ confidence: 0.9,
64793
+ reason: `The declared postcondition remained visible and no browser runtime failures appeared during the ${actualObservationMs}ms observation window.`
64794
+ } : {
64795
+ status: "inconclusive",
64796
+ confidence: 0.58,
64797
+ reason: `The browser remained under observation for ${actualObservationMs}ms, but no explicit postcondition was evaluated.`,
64798
+ nextSuggestedObservation: "Repeat with expect set to a state-specific selector before making a user-visible success claim."
64799
+ };
64800
+ const mode = browserHeadless === false ? "headed" : "headless";
64801
+ const evidenceEvent = createEvidenceEvent({
64802
+ sessionId: playwrightSessionId ?? void 0,
64803
+ tool: "playwright_browser",
64804
+ action: input.action,
64805
+ surface: "browser",
64806
+ claim: input.expectation ? `Browser postcondition: ${input.expectation}` : "Timed browser runtime observation",
64807
+ after: createEvidenceObservation({
64808
+ screenshotPath,
64809
+ screenshotHash: sha256Buffer(screenshotBuffer),
64810
+ width: viewport.width,
64811
+ height: viewport.height,
64812
+ url: input.pageHandle.url(),
64813
+ title: await input.pageHandle.title(),
64814
+ domSummaryHash: sha256Text(summary),
64815
+ readableTextHash: sha256Text(String(readableText ?? "")),
64816
+ visibleTextExcerpt: clipEvidenceText(readableText, 500),
64817
+ consoleErrorCount: consoleErrors.length,
64818
+ pageErrorCount: pageErrors.length,
64819
+ networkFailureCount: networkFailures.length,
64820
+ gateKind: gate.kind,
64821
+ gateConfidence: gate.confidence,
64822
+ gateEvidence: gate.evidence,
64823
+ metadata: {
64824
+ mode,
64825
+ engine: "playwright",
64826
+ requestedObservationMs: input.requestedObservationMs,
64827
+ actualObservationMs,
64828
+ diagnosticsScope: input.cursor ? "window" : "session",
64829
+ consoleWarningCount: consoleWarnings.length,
64830
+ websocketEventCount: diagnostics.websockets.length,
64831
+ crashCount: diagnostics.crashCount,
64832
+ expectation: input.expectation,
64833
+ expectationSatisfied: input.expectationSatisfied
64834
+ }
64835
+ }),
64836
+ verification
64837
+ });
64838
+ const bundle = {
64839
+ schema: "omnius.browser-observation-window.v1",
64840
+ id: timestampSlug(),
64841
+ engine: "playwright",
64842
+ mode,
64843
+ url: input.pageHandle.url(),
64844
+ title: await input.pageHandle.title(),
64845
+ viewport,
64846
+ screenshotPath,
64847
+ observationWindow: {
64848
+ requestedMs: input.requestedObservationMs,
64849
+ actualMs: actualObservationMs,
64850
+ startedAt: new Date(diagnostics.startedAtMs).toISOString(),
64851
+ endedAt: new Date(diagnostics.endedAtMs).toISOString(),
64852
+ diagnosticsScope: input.cursor ? "window" : "session"
64853
+ },
64854
+ expectation: input.expectation ?? null,
64855
+ expectationSatisfied: input.expectationSatisfied ?? null,
64856
+ expectationError: input.expectationError ?? null,
64857
+ gate,
64858
+ consoleErrors,
64859
+ consoleWarnings,
64860
+ networkFailures,
64861
+ pageErrors,
64862
+ websocketEvents: diagnostics.websockets.slice(-30),
64863
+ crashes: diagnostics.crashCount,
64864
+ domSummary: summary.slice(0, 12e3),
64865
+ evidenceEvents: [evidenceEvent]
64866
+ };
64867
+ const failureDetails = [
64868
+ ...consoleErrors.map((entry) => `console ${entry.type}: ${entry.text}${entry.loc ? ` (${entry.loc})` : ""}`),
64869
+ ...pageErrors.map((entry) => `page error: ${entry.message}`),
64870
+ ...networkFailures.map((entry) => `network: ${entry.method} ${entry.status ?? "FAILED"} ${entry.url}${entry.failure ? ` (${entry.failure})` : ""}`)
64871
+ ].slice(0, 30);
64872
+ const output2 = [
64873
+ "Browser observation bundle",
64874
+ `URL: ${bundle.url}`,
64875
+ `Title: ${bundle.title}`,
64876
+ `Mode: ${mode}`,
64877
+ `Observation window: ${actualObservationMs}ms (${input.cursor ? "window-scoped" : "session-scoped"} diagnostics)`,
64878
+ `Viewport: ${viewport.width}x${viewport.height}`,
64879
+ `Screenshot: ${screenshotPath}`,
64880
+ `Gate: ${gate.kind} (${gate.confidence.toFixed(2)})${gate.evidence.length ? ` — ${gate.evidence.join("; ")}` : ""}`,
64881
+ `Console errors: ${consoleErrors.length}`,
64882
+ `Console warnings: ${consoleWarnings.length}`,
64883
+ `Page errors: ${pageErrors.length}`,
64884
+ `Network failures: ${networkFailures.length}`,
64885
+ `Verification: ${verification.status} — ${verification.reason}`,
64886
+ ...failureDetails.length > 0 ? ["", "--- Runtime failures ---", ...failureDetails] : [],
64887
+ "",
64888
+ "--- DOM Summary ---",
64889
+ summary.slice(0, 6e3)
64890
+ ].join("\n");
64891
+ const shouldFail = input.failOnRuntimeErrors && contradicted;
64892
+ return {
64893
+ success: !shouldFail,
64894
+ output: output2,
64895
+ ...shouldFail ? {
64896
+ error: `Browser lifecycle validation failed: ${runtimeFailureCount} runtime failure signal(s)${input.expectationSatisfied === false ? "; declared postcondition was not observed" : ""}.`
64897
+ } : {},
64898
+ llmContent: JSON.stringify(bundle, null, 2),
64899
+ evidenceEvents: [evidenceEvent],
64900
+ durationMs: Date.now() - input.startMs
64901
+ };
64902
+ }
64739
64903
  function ok(output2, start2, skipDiag = false) {
64740
64904
  const diag = skipDiag ? "" : formatDiagnosticSummary();
64741
64905
  return {
@@ -64748,7 +64912,7 @@ ${diag}` : output2,
64748
64912
  function fail(error, start2) {
64749
64913
  return { success: false, output: "", error, durationMs: Date.now() - start2 };
64750
64914
  }
64751
- var pw, browser, context, page, browserHeadless, playwrightSessionId, playwrightSessionSequence, MAX_BUFFER, consoleBuffer, networkBuffer, errorBuffer, wsBuffer, crashCount, initScriptInstalled, lastDomSummarySelectors, dynamicImport, PLAYWRIGHT_RUNTIME_DIR, PLAYWRIGHT_BROWSERS_DIR, PLAYWRIGHT_CHROMIUM_INSTALL_ARGS, PlaywrightBrowserTool;
64915
+ var pw, browser, context, page, browserHeadless, playwrightSessionId, playwrightSessionSequence, MAX_BUFFER, consoleBuffer, networkBuffer, errorBuffer, wsBuffer, crashCount, lastDomSummarySelectors, dynamicImport, PLAYWRIGHT_RUNTIME_DIR, PLAYWRIGHT_BROWSERS_DIR, PLAYWRIGHT_CHROMIUM_INSTALL_ARGS, PlaywrightBrowserTool;
64752
64916
  var init_playwright_browser = __esm({
64753
64917
  "packages/execution/dist/tools/playwright-browser.js"() {
64754
64918
  "use strict";
@@ -64769,15 +64933,18 @@ var init_playwright_browser = __esm({
64769
64933
  errorBuffer = [];
64770
64934
  wsBuffer = [];
64771
64935
  crashCount = 0;
64772
- initScriptInstalled = false;
64773
64936
  lastDomSummarySelectors = /* @__PURE__ */ new Map();
64774
- dynamicImport = new Function("mod", "return import(mod)");
64937
+ dynamicImport = (mod3) => import(
64938
+ /* @vite-ignore */
64939
+ mod3
64940
+ );
64775
64941
  PLAYWRIGHT_RUNTIME_DIR = join22(homedir7(), ".omnius", "playwright-runtime");
64776
64942
  PLAYWRIGHT_BROWSERS_DIR = join22(PLAYWRIGHT_RUNTIME_DIR, "browsers");
64777
64943
  PLAYWRIGHT_CHROMIUM_INSTALL_ARGS = ["playwright", "install", "chromium"];
64778
64944
  PlaywrightBrowserTool = class {
64779
64945
  name = "playwright_browser";
64780
- description = "Full-scope Playwright browser automation + diagnostic capture. Launches a persistent headless Chromium session by default, with optional visible/headed mode when a GUI display is available. Beyond navigation/interaction, this tool buffers everything the running app emits (console messages, network requests, JS exceptions, accessibility tree) so the agent can verify what is ACTUALLY happening — not just what the build/test reports. Auto-installs Playwright + Chromium on first use without sudo or OS package manager escalation. Diagnostic actions: observe_bundle, dom_summary, dom, console_logs, network_log, page_errors, websocket_log, a11y_snapshot, bounding_box, query_all, performance, cookies, storage, viewport, clear_diagnostics. Interaction actions: navigate, click, visual_click, fill, type, press, select, check, hover. Use fill with a selector or natural-language target for form fields; avoid raw evaluate for form filling because direct .value assignment does not fire app input/change events. For state-changing interactions, pass expect as a Playwright selector for the visible post-action state (for example text=Stop Simulation). A dispatched click without an asserted postcondition is recorded as inconclusive, not completion proof. Selectors are strict by default: when a selector matches more than one element, use a more specific selector or index (zero-based). Detached/re-rendered elements are re-queried once before failing. This is a separate browser/runtime from browser_action; once you start a workflow here, continue here unless you intentionally navigate browser_action to the same URL. Every result carries a typed runtime-session receipt; use hardware_prerequisite before declaring a WebSerial, WebUSB, camera, or microphone interaction blocked by a real device/permission boundary. Capture actions: screenshot, pdf, content, innerText, innerHTML, getAttribute, evaluate. Loopback URLs (localhost, 127.0.0.1, ::1) are allowed for local development servers; private LAN and metadata URLs remain blocked. Workflow for user-facing work: start/serve the system with the stack-native tool, navigate to the real URL, then inspect page_errors, console_logs, network_log, DOM/accessibility, and screenshot evidence before completion. Build/typecheck/test output is only one layer; runtime browser evidence is required when the delivered artifact is a page, app, dashboard, game, form, visualization, or other UI. Repeat navigate/act/observe until the actual user flow is clean.";
64946
+ modelFacingSummary = "Validate a live UI in one persistent Playwright session. For GUI, GPU, WebGL, WebGPU, canvas, or animation claims, use validate_lifecycle with headless=false, force_new=true, an explicit expect, and a finite observation_ms. Use observe_window after interactions. Inspect delayed console, page, network, DOM, and screenshot evidence. A clean headless or immediate load is not headed lifecycle proof.";
64947
+ description = "Persistent Playwright browser lifecycle validation with timed console, page-error, network, DOM, and screenshot capture. Launches a persistent headless Chromium session by default, with optional visible/headed mode when a GUI display is available. Beyond navigation/interaction, this tool buffers everything the running app emits (console messages, network requests, JS exceptions, accessibility tree) so the agent can verify what is ACTUALLY happening — not just what the build/test reports. Auto-installs Playwright + Chromium on first use without sudo or OS package manager escalation. Diagnostic actions: validate_lifecycle, observe_window, observe_bundle, dom_summary, dom, console_logs, network_log, page_errors, websocket_log, a11y_snapshot, bounding_box, query_all, performance, cookies, storage, viewport, clear_diagnostics. Interaction actions: navigate, click, visual_click, fill, type, press, select, check, hover. Use fill with a selector or natural-language target for form fields; avoid raw evaluate for form filling because direct .value assignment does not fire app input/change events. For state-changing interactions, pass expect as a Playwright selector for the visible post-action state (for example text=Stop Simulation). A dispatched click without an asserted postcondition is recorded as inconclusive, not completion proof. Selectors are strict by default: when a selector matches more than one element, use a more specific selector or index (zero-based). Detached/re-rendered elements are re-queried once before failing. This is a separate browser/runtime from browser_action; once you start a workflow here, continue here unless you intentionally navigate browser_action to the same URL. Every result carries a typed runtime-session receipt; use hardware_prerequisite before declaring a WebSerial, WebUSB, camera, or microphone interaction blocked by a real device/permission boundary. Capture actions: screenshot, pdf, content, innerText, innerHTML, getAttribute, evaluate. Loopback URLs (localhost, 127.0.0.1, ::1) are allowed for local development servers; private LAN and metadata URLs remain blocked. Workflow for user-facing work: start/serve the system with the stack-native tool, then run validate_lifecycle against the real URL with an explicit expect and observation_ms. For GPU, WebGL, WebGPU, canvas, animation, or failures seen in a normal GUI browser, use headless=false and force_new=true; a clean headless run does not verify headed GPU behavior. Use observe_window after interactions to keep the same browser alive while delayed failures surface, then inspect page_errors, console_logs, network_log, DOM/accessibility, and screenshot evidence before completion. Build/typecheck/test output is only one layer; runtime browser evidence is required when the delivered artifact is a page, app, dashboard, game, form, visualization, or other UI. Repeat navigate/act/observe until the actual user flow is clean.";
64781
64948
  parameters = {
64782
64949
  type: "object",
64783
64950
  properties: {
@@ -64824,12 +64991,14 @@ var init_playwright_browser = __esm({
64824
64991
  "storage",
64825
64992
  "viewport",
64826
64993
  "observe_bundle",
64994
+ "observe_window",
64995
+ "validate_lifecycle",
64827
64996
  "visual_click",
64828
64997
  "hardware_prerequisite",
64829
64998
  "clear_diagnostics",
64830
64999
  "close"
64831
65000
  ],
64832
- description: "Action to perform:\n- navigate: go to a URL\n- click: click element by selector\n- fill: clear input and type text by selector, or by natural-language target when selector is absent\n- type: type text character by character into a selector, or into the currently focused element after visual_click\n- press: press a key (Enter, Tab, Escape, etc.)\n- screenshot: capture the headless browser page, not the desktop; use value to choose the output file path\n- observe_bundle: capture URL/title/viewport, DOM summary, a11y, diagnostics, screenshot, and gate assessment\n- visual_click: browser screenshot -> Moondream point -> elementFromPoint -> human-like Playwright mouse click -> post-action screenshot\n- hardware_prerequisite: record whether a WebSerial/WebUSB/media flow has the browser capability, secure context, and required physical/user-gesture prerequisite; it never pretends hardware was exercised\n- evaluate: run JavaScript in page context\n- content: get page text content (readable, stripped)\n- dom: get raw page HTML (truncated)\n- dom_summary: compact interactive DOM summary with selectors\n- innerText: get innerText of a specific element\n- select: select dropdown option by value\n- check/uncheck: toggle checkbox\n- hover: hover over element\n- wait: wait for a selector to appear, or sleep for timeout ms when no selector is provided\n- waitForNavigation: wait for page navigation to complete\n- waitForSelector: wait for element matching selector\n- title: get page title\n- url: get current URL\n- getAttribute: get element attribute value\n- innerHTML: get element's innerHTML\n- textContent: get element's textContent\n- goBack/goForward/reload: browser navigation\n- pdf: save page as PDF\n- close: close browser session"
65001
+ description: "Action to perform:\n- navigate: go to a URL\n- click: click element by selector\n- fill: clear input and type text by selector, or by natural-language target when selector is absent\n- type: type text character by character into a selector, or into the currently focused element after visual_click\n- press: press a key (Enter, Tab, Escape, etc.)\n- screenshot: capture the headless browser page, not the desktop; use value to choose the output file path\n- observe_bundle: capture URL/title/viewport, DOM summary, a11y, diagnostics, screenshot, and gate assessment\n- observe_window: keep the current browser/page alive for observation_ms, then return only diagnostics emitted during that time window plus DOM/screenshot evidence\n- validate_lifecycle: start a fresh headed browser by default, clear diagnostics, navigate to url, hold it for observation_ms, and fail on delayed console/page/network errors; use this for GUI/GPU/WebGL validation\n- visual_click: browser screenshot -> Moondream point -> elementFromPoint -> human-like Playwright mouse click -> post-action screenshot\n- hardware_prerequisite: record whether a WebSerial/WebUSB/media flow has the browser capability, secure context, and required physical/user-gesture prerequisite; it never pretends hardware was exercised\n- evaluate: run JavaScript in page context\n- content: get page text content (readable, stripped)\n- dom: get raw page HTML (truncated)\n- dom_summary: compact interactive DOM summary with selectors\n- innerText: get innerText of a specific element\n- select: select dropdown option by value\n- check/uncheck: toggle checkbox\n- hover: hover over element\n- wait: wait for a selector to appear, or sleep for timeout ms when no selector is provided\n- waitForNavigation: wait for page navigation to complete\n- waitForSelector: wait for element matching selector\n- title: get page title\n- url: get current URL\n- getAttribute: get element attribute value\n- innerHTML: get element's innerHTML\n- textContent: get element's textContent\n- goBack/goForward/reload: browser navigation\n- pdf: save page as PDF\n- close: close browser session"
64833
65002
  },
64834
65003
  url: {
64835
65004
  type: "string",
@@ -64895,6 +65064,14 @@ var init_playwright_browser = __esm({
64895
65064
  type: "number",
64896
65065
  description: "Milliseconds to wait for expect after the action (defaults to timeout)."
64897
65066
  },
65067
+ observation_ms: {
65068
+ type: "number",
65069
+ description: "Milliseconds to keep the instantiated page alive while collecting delayed diagnostics. Defaults to 8000 for validate_lifecycle and 5000 for observe_window; maximum 120000."
65070
+ },
65071
+ start_delay_ms: {
65072
+ type: "number",
65073
+ description: "Optional delay before opening an observe_window diagnostic window. Use this only when the requested lifecycle has an explicit warm-up phase; maximum 60000."
65074
+ },
64898
65075
  hardware_capability: {
64899
65076
  type: "string",
64900
65077
  enum: ["web_serial", "web_usb", "camera", "microphone"],
@@ -64906,7 +65083,7 @@ var init_playwright_browser = __esm({
64906
65083
  },
64907
65084
  headless: {
64908
65085
  type: "boolean",
64909
- description: "Launch mode for a new browser session. Defaults to true. Set false to use a visible GUI Chromium when DISPLAY/desktop access is available; changing this restarts the browser session."
65086
+ description: "Launch mode for a new browser session. Normal actions default to true; validate_lifecycle defaults to false. For GUI/GPU/WebGL claims, set false explicitly. A headless result cannot verify headed graphics behavior."
64910
65087
  },
64911
65088
  force_new: {
64912
65089
  type: "boolean",
@@ -64948,8 +65125,12 @@ var init_playwright_browser = __esm({
64948
65125
  const expectationTimeout = typeof args.expect_timeout === "number" ? Math.max(1, Math.min(12e4, Math.round(args.expect_timeout))) : timeout2;
64949
65126
  const width = typeof args.width === "number" ? Math.max(320, Math.min(3840, Math.round(args.width))) : void 0;
64950
65127
  const height = typeof args.height === "number" ? Math.max(240, Math.min(2160, Math.round(args.height))) : void 0;
64951
- const headless = asOptionalBoolean(args.headless);
64952
- const forceNew = asOptionalBoolean(args.force_new) === true;
65128
+ const requestedHeadless = asOptionalBoolean(args.headless);
65129
+ const validatesFullLifecycle = action === "validate_lifecycle";
65130
+ const headless = requestedHeadless ?? (validatesFullLifecycle ? false : void 0);
65131
+ const forceNew = asOptionalBoolean(args.force_new) === true || validatesFullLifecycle;
65132
+ const observationMs = typeof args.observation_ms === "number" ? Math.max(0, Math.min(12e4, Math.round(args.observation_ms))) : validatesFullLifecycle ? 8e3 : 5e3;
65133
+ const startDelayMs = typeof args.start_delay_ms === "number" ? Math.max(0, Math.min(6e4, Math.round(args.start_delay_ms))) : 0;
64953
65134
  const visualTarget = typeof args.target === "string" && args.target.trim() ? args.target.trim() : typeof args.text === "string" && args.text.trim() ? args.text.trim() : "";
64954
65135
  const preferredVisionModel = normalizeVisionModelName(args.vision_model);
64955
65136
  const delayMs = typeof args.delay_ms === "number" ? Math.max(0, Math.min(3e4, Math.round(args.delay_ms))) : 700;
@@ -64964,6 +65145,9 @@ var init_playwright_browser = __esm({
64964
65145
  durationMs: Date.now() - start2
64965
65146
  };
64966
65147
  }
65148
+ if (headless === false && !hasGraphicalBrowserDisplay()) {
65149
+ return fail("Headed Chromium was required, but no graphical DISPLAY or WAYLAND_DISPLAY is available. Report headed GUI/GPU validation as blocked; do not substitute a clean headless run.", start2);
65150
+ }
64967
65151
  const err = await ensureBrowser({ headless, forceNew });
64968
65152
  if (err)
64969
65153
  return {
@@ -65689,7 +65873,14 @@ ${JSON.stringify(data, null, 2)}`, start2);
65689
65873
  }
65690
65874
  return fail(`viewport: provide "WxH" (e.g. "375x667") or a Playwright device name (e.g. "iPhone 13")`, start2);
65691
65875
  }
65692
- case "observe_bundle": {
65876
+ case "validate_lifecycle": {
65877
+ if (!url)
65878
+ return fail("url is required", start2);
65879
+ try {
65880
+ await validateNetworkEgressUrl(url, { allowLoopback: true });
65881
+ } catch (err2) {
65882
+ return fail(networkEgressErrorMessage(err2), start2);
65883
+ }
65693
65884
  if (width || height) {
65694
65885
  const current = page.viewportSize?.() ?? {
65695
65886
  width: 1280,
@@ -65700,87 +65891,107 @@ ${JSON.stringify(data, null, 2)}`, start2);
65700
65891
  height: height ?? current.height ?? 720
65701
65892
  });
65702
65893
  }
65703
- const html = await page.content();
65704
- const readableText = await page.evaluate(`(() => {
65705
- const clone = document.body ? document.body.cloneNode(true) : document.documentElement.cloneNode(true);
65706
- clone.querySelectorAll("script, style, noscript, svg, path").forEach(el => el.remove());
65707
- return clone.innerText || clone.textContent || "";
65708
- })()`);
65709
- const summary = downsampleDom(html);
65710
- lastDomSummarySelectors = extractDomSummarySelectors(summary);
65711
- const screenshotPath = saveBuffer(outputPath3 || defaultArtifactPath("observe", "png"), await page.screenshot({ fullPage: args.full_page === true }));
65712
- const viewport = page.viewportSize?.() ?? { width: 0, height: 0 };
65713
- const gate = classifyBrowserGate({
65714
- url: page.url(),
65715
- html,
65716
- text: String(readableText ?? ""),
65717
- network: networkBuffer,
65718
- errors: errorBuffer
65894
+ clearDiagnosticBuffers();
65895
+ const cursor = captureBrowserDiagnosticCursor();
65896
+ await page.goto(url, {
65897
+ waitUntil: "domcontentloaded",
65898
+ timeout: timeout2
65719
65899
  });
65720
- const errors = errorBuffer.slice(-20).map((entry) => entry.message);
65721
- const networkFailures = networkBuffer.filter((entry) => entry.ok === false || (entry.status ?? 0) >= 400).slice(-30).map((entry) => `${entry.method} ${entry.status ?? "FAILED"} ${entry.url}${entry.failure ? ` (${entry.failure})` : ""}`);
65722
- const bundle = {
65723
- id: timestampSlug(),
65724
- engine: "playwright",
65725
- mode: browserHeadless === false ? "headed" : "headless",
65726
- url: page.url(),
65727
- title: await page.title(),
65728
- viewport,
65729
- screenshotPath,
65730
- gate,
65731
- consoleErrors: consoleBuffer.filter((entry) => /error|warning/i.test(entry.type)).slice(-20),
65732
- networkFailures,
65733
- pageErrors: errors,
65734
- domSummary: summary.slice(0, 12e3)
65735
- };
65736
- const evidenceEvent = createEvidenceEvent({
65737
- tool: "playwright_browser",
65900
+ let expectationSatisfied;
65901
+ let expectationError;
65902
+ const expectationProbe = expectation ? waitForBrowserExpectation(page, expectation, Math.min(expectationTimeout, Math.max(1, observationMs))).then(() => {
65903
+ expectationSatisfied = true;
65904
+ }).catch((err2) => {
65905
+ expectationSatisfied = false;
65906
+ expectationError = err2 instanceof Error ? err2.message : String(err2);
65907
+ }) : Promise.resolve();
65908
+ await Promise.all([
65909
+ page.waitForTimeout(observationMs),
65910
+ expectationProbe
65911
+ ]);
65912
+ if (expectation && expectationSatisfied === true && !await browserExpectationIsVisible(page, expectation)) {
65913
+ expectationSatisfied = false;
65914
+ expectationError = "The declared postcondition was visible during the window but was not visible at the end of it.";
65915
+ }
65916
+ return captureBrowserObservation({
65917
+ pageHandle: page,
65918
+ action: "validate_lifecycle",
65919
+ startMs: start2,
65920
+ outputPath: outputPath3,
65921
+ fullPage: args.full_page === true,
65922
+ cursor,
65923
+ requestedObservationMs: observationMs,
65924
+ expectation,
65925
+ expectationSatisfied,
65926
+ expectationError,
65927
+ failOnRuntimeErrors: true
65928
+ });
65929
+ }
65930
+ case "observe_window": {
65931
+ if (startDelayMs > 0)
65932
+ await page.waitForTimeout(startDelayMs);
65933
+ if (width || height) {
65934
+ const current = page.viewportSize?.() ?? {
65935
+ width: 1280,
65936
+ height: 720
65937
+ };
65938
+ await page.setViewportSize({
65939
+ width: width ?? current.width ?? 1280,
65940
+ height: height ?? current.height ?? 720
65941
+ });
65942
+ }
65943
+ const cursor = captureBrowserDiagnosticCursor();
65944
+ let expectationSatisfied;
65945
+ let expectationError;
65946
+ const expectationProbe = expectation ? waitForBrowserExpectation(page, expectation, Math.min(expectationTimeout, Math.max(1, observationMs))).then(() => {
65947
+ expectationSatisfied = true;
65948
+ }).catch((err2) => {
65949
+ expectationSatisfied = false;
65950
+ expectationError = err2 instanceof Error ? err2.message : String(err2);
65951
+ }) : Promise.resolve();
65952
+ await Promise.all([
65953
+ page.waitForTimeout(observationMs),
65954
+ expectationProbe
65955
+ ]);
65956
+ if (expectation && expectationSatisfied === true && !await browserExpectationIsVisible(page, expectation)) {
65957
+ expectationSatisfied = false;
65958
+ expectationError = "The declared postcondition was visible during the window but was not visible at the end of it.";
65959
+ }
65960
+ return captureBrowserObservation({
65961
+ pageHandle: page,
65962
+ action: "observe_window",
65963
+ startMs: start2,
65964
+ outputPath: outputPath3,
65965
+ fullPage: args.full_page === true,
65966
+ cursor,
65967
+ requestedObservationMs: observationMs,
65968
+ expectation,
65969
+ expectationSatisfied,
65970
+ expectationError,
65971
+ failOnRuntimeErrors: true
65972
+ });
65973
+ }
65974
+ case "observe_bundle": {
65975
+ if (width || height) {
65976
+ const current = page.viewportSize?.() ?? {
65977
+ width: 1280,
65978
+ height: 720
65979
+ };
65980
+ await page.setViewportSize({
65981
+ width: width ?? current.width ?? 1280,
65982
+ height: height ?? current.height ?? 720
65983
+ });
65984
+ }
65985
+ return captureBrowserObservation({
65986
+ pageHandle: page,
65738
65987
  action: "observe_bundle",
65739
- surface: "browser",
65740
- after: createEvidenceObservation({
65741
- screenshotPath,
65742
- width: viewport.width,
65743
- height: viewport.height,
65744
- url: bundle.url,
65745
- title: bundle.title,
65746
- domSummaryHash: sha256Text(summary),
65747
- readableTextHash: sha256Text(String(readableText ?? "")),
65748
- visibleTextExcerpt: clipEvidenceText(readableText, 500),
65749
- consoleErrorCount: bundle.consoleErrors.length,
65750
- pageErrorCount: errors.length,
65751
- networkFailureCount: networkFailures.length,
65752
- gateKind: gate.kind,
65753
- gateConfidence: gate.confidence,
65754
- gateEvidence: gate.evidence,
65755
- metadata: { mode: bundle.mode, engine: "playwright" }
65756
- }),
65757
- verification: {
65758
- status: gate.kind === "captcha" || gate.kind === "bot_challenge" ? "blocked" : "inconclusive",
65759
- confidence: gate.kind === "captcha" || gate.kind === "bot_challenge" ? gate.confidence : 0.5,
65760
- reason: gate.kind === "none" ? "Fresh browser observation captured; no specific completion claim was evaluated." : `Fresh browser observation captured with gate=${gate.kind}.`
65761
- }
65988
+ startMs: start2,
65989
+ outputPath: outputPath3,
65990
+ fullPage: args.full_page === true,
65991
+ requestedObservationMs: 0,
65992
+ expectation,
65993
+ failOnRuntimeErrors: false
65762
65994
  });
65763
- const modelBundle = { ...bundle, evidenceEvents: [evidenceEvent] };
65764
- return {
65765
- success: true,
65766
- output: [
65767
- "Browser observation bundle",
65768
- `URL: ${bundle.url}`,
65769
- `Title: ${bundle.title}`,
65770
- `Mode: ${bundle.mode}`,
65771
- `Viewport: ${viewport.width}x${viewport.height}`,
65772
- `Screenshot: ${screenshotPath}`,
65773
- `Gate: ${gate.kind} (${gate.confidence.toFixed(2)})${gate.evidence.length ? ` — ${gate.evidence.join("; ")}` : ""}`,
65774
- `Console warnings/errors: ${bundle.consoleErrors.length}`,
65775
- `Network failures: ${networkFailures.length}`,
65776
- "",
65777
- "--- DOM Summary ---",
65778
- summary.slice(0, 6e3)
65779
- ].join("\n"),
65780
- llmContent: JSON.stringify(modelBundle, null, 2),
65781
- evidenceEvents: [evidenceEvent],
65782
- durationMs: Date.now() - start2
65783
- };
65784
65995
  }
65785
65996
  case "visual_click": {
65786
65997
  if (!visualTarget)
@@ -66068,7 +66279,9 @@ ${JSON.stringify(data, null, 2)}`, start2);
66068
66279
  ].filter(Boolean).join("\n"),
66069
66280
  llmContent: JSON.stringify(bundle, null, 2),
66070
66281
  evidenceEvents: [evidenceEvent],
66071
- ...expectationSatisfied === false ? { error: `Postcondition not observed: ${expectation}. ${expectationError?.slice(0, 500) ?? ""}`.trim() } : {},
66282
+ ...expectationSatisfied === false ? {
66283
+ error: `Postcondition not observed: ${expectation}. ${expectationError?.slice(0, 500) ?? ""}`.trim()
66284
+ } : {},
66072
66285
  durationMs: Date.now() - start2
66073
66286
  };
66074
66287
  }
@@ -659396,13 +659609,13 @@ ${context2 ?? ""}`;
659396
659609
  critique2 = this._buildVisualAdversaryCritique({
659397
659610
  evidence: `${inconclusiveEvent.tool}/${inconclusiveEvent.action} produced only inconclusive proof: ${inconclusiveEvent.verification?.reason ?? "no reason"}`,
659398
659611
  hypothesis: "The browser action may have worked, but the current evidence is not strong enough to prove the user-visible event.",
659399
- correctiveAction: 'Run playwright_browser({action:"observe_bundle"}) after the action and verify URL, visible text, DOM, console, network, and gate state.'
659612
+ correctiveAction: 'Run playwright_browser({action:"observe_window", observation_ms:5000, expect:"..."}) after the action. Verify the expected state plus window-scoped console, page, network, DOM, and screenshot evidence.'
659400
659613
  });
659401
659614
  } else if (events.length === 0) {
659402
659615
  critique2 = this._buildVisualAdversaryCritique({
659403
659616
  evidence: `${toolName} changed ${surface} state but did not return a structured evidence event.`,
659404
659617
  hypothesis: "The agent may be acting from stale visual state after a click/type/submit/navigation action.",
659405
- correctiveAction: surface === "browser" ? 'Run playwright_browser({action:"observe_bundle"}) or a post-action screenshot/DOM check before claiming success.' : 'Run vision_action_loop({action:"observe", goal:"..."}) or desktop_describe before claiming success.'
659618
+ correctiveAction: surface === "browser" ? 'Run playwright_browser({action:"observe_window", observation_ms:5000, expect:"..."}) before claiming success. Use a fresh headed validate_lifecycle run when the claim concerns GUI, GPU, WebGL, WebGPU, canvas, or animation behavior.' : 'Run vision_action_loop({action:"observe", goal:"..."}) or desktop_describe before claiming success.'
659406
659619
  });
659407
659620
  }
659408
659621
  if (critique2) {
@@ -678995,6 +679208,9 @@ Example: ${tool.name}(${JSON.stringify(meta.examples[0].args ?? {})})` : "";
678995
679208
  "file_explore",
678996
679209
  "web_search",
678997
679210
  "web_fetch",
679211
+ // Browser evidence must be available without a lexical relevance guess.
679212
+ // Surfaces that do not register this tool do not expose it.
679213
+ "playwright_browser",
678998
679214
  "memory_read",
678999
679215
  "memory_write",
679000
679216
  "working_notes",
@@ -679112,11 +679328,12 @@ Example: ${tool.name}(${JSON.stringify(meta.examples[0].args ?? {})})` : "";
679112
679328
  const compressDesc = true;
679113
679329
  const defs = dedupedInline.map((tool) => {
679114
679330
  const desc = getDesc(tool);
679331
+ const modelFacingDescription = tool.modelFacingSummary?.trim();
679115
679332
  return {
679116
679333
  type: "function",
679117
679334
  function: {
679118
679335
  name: tool.name,
679119
- description: compressDesc ? (desc.split(/\.\s/)[0]?.slice(0, 120) ?? desc.slice(0, 120)) + "." : desc,
679336
+ description: modelFacingDescription ? modelFacingDescription.slice(0, 420) : compressDesc ? (desc.split(/\.\s/)[0]?.slice(0, 120) ?? desc.slice(0, 120)) + "." : desc,
679120
679337
  parameters: this._publicToolParameters(tool.parameters)
679121
679338
  }
679122
679339
  };
@@ -686617,11 +686834,11 @@ var init_workerSkillStandard = __esm({
686617
686834
  {
686618
686835
  stepNumber: 3,
686619
686836
  title: "Validate every requested browser flow in one Playwright session",
686620
- description: "Use playwright_browser navigate -> observe_bundle -> action -> state-based postcondition -> observe_bundle. Prefer data-testid or role/name locators. A click receipt or screenshot alone is not a pass. Re-query after a detached-element error; record each requested interaction as passed, failed, blocked, not_applicable, or inconclusive with the concrete state and diagnostics.",
686837
+ description: "Use playwright_browser validate_lifecycle -> action -> state-based postcondition -> observe_window. Set a finite observation_ms so delayed errors can surface. For GUI, GPU, WebGL, WebGPU, canvas, or animation claims, use a fresh headed browser. A clean headless load is not headed rendering evidence. Prefer data-testid or role/name locators. A click receipt or screenshot alone is not a pass. Re-query after a detached-element error; record each requested interaction as passed, failed, blocked, not_applicable, or inconclusive with the concrete state and diagnostics.",
686621
686838
  tools: ["playwright_browser"],
686622
686839
  skills: ["browser-interaction-validation"],
686623
686840
  output: "One claim-scoped browser evidence entry per requested interaction, separate from static test evidence",
686624
- verification: "Every interaction has an observed postcondition or a precise failed/blocked/not-applicable outcome; page errors, console errors, and failed network requests are reviewed"
686841
+ verification: "Every interaction has an observed postcondition or a precise failed/blocked/not-applicable outcome; delayed page errors, console errors, failed network requests, DOM state, and screenshots are reviewed from the relevant observation window"
686625
686842
  },
686626
686843
  {
686627
686844
  stepNumber: 4,
@@ -692429,10 +692646,12 @@ FOR SCREEN / DESKTOP UI (what is on this computer display, clicking, navigating)
692429
692646
  desktop_click({ target: "element" }) - Click a UI element by description.
692430
692647
  vision_action_loop({ goal: "task", action: "observe" }) - Persistent screenshot/OCR/point/action loop with previewable screenshots.
692431
692648
 
692432
- FOR BROWSER PAGE / WEB GUI RENDERS (headless, deterministic viewport):
692649
+ FOR BROWSER PAGE / WEB GUI RENDERS:
692433
692650
  browser_action({ action: "screenshot", width: 1280, height: 720, output_path: ".omnius/browser/page.png" }) - Render the current headless browser page and return an image input.
692434
692651
  playwright_browser({ action: "viewport", text: "1280x720" }) then playwright_browser({ action: "screenshot", value: ".omnius/browser/page.png" }) - Playwright diagnostic browser capture.
692435
- playwright_browser({ action: "observe_bundle" }) - Capture screenshot + DOM summary + diagnostics + gate assessment in one browser observation.
692652
+ playwright_browser({ action: "validate_lifecycle", url: "http://127.0.0.1:3000", headless: false, force_new: true, observation_ms: 8000, expect: "text=Ready" }) - Start diagnostics before navigation and hold a fresh GUI browser open for delayed rendering failures.
692653
+ playwright_browser({ action: "observe_window", observation_ms: 5000, expect: "text=Updated" }) - Keep the current page alive and return diagnostics emitted during the post-action window.
692654
+ playwright_browser({ action: "observe_bundle" }) - Capture current session diagnostics and page state when a timed window is not required.
692436
692655
  playwright_browser({ action: "visual_click", target: "button or field", vision_model: "moondream" }) - Browser screenshot -> Moondream point -> DOM candidate -> click -> post-action screenshot.
692437
692656
 
692438
692657
  RULES:
@@ -692441,6 +692660,7 @@ RULES:
692441
692660
  - For seeing the screen, use screenshot, desktop_describe, or vision_action_loop, not camera_capture.
692442
692661
  - For image files, use image_read or vision, not desktop_describe.
692443
692662
  - For browser GUI action when selectors are unknown, use playwright_browser visual_click before desktop-level clicking.
692663
+ - Match the browser mode to the claim. A clean immediate or headless run does not verify delayed headed GPU/WebGL behavior.
692444
692664
  - Do not claim you cannot see visual content until the relevant visual tool has failed.
692445
692665
  </visual-and-sensor-tools>`;
692446
692666
  }
@@ -701137,6 +701357,7 @@ function adaptExecutionTool(tool, options2 = {}) {
701137
701357
  name: tool.name,
701138
701358
  aliases: tool.aliases,
701139
701359
  description: tool.description,
701360
+ modelFacingSummary: tool.modelFacingSummary,
701140
701361
  parameters: tool.parameters,
701141
701362
  inputSchema: tool.inputSchema,
701142
701363
  maxResultSizeChars: tool.maxResultSizeChars,
@@ -781438,6 +781659,30 @@ function parseTelegramSilentReflectionNotes(text3) {
781438
781659
  }
781439
781660
  return null;
781440
781661
  }
781662
+ function telegramInteractionDecisionResponseFormatForPacket(packet) {
781663
+ const format3 = structuredClone(
781664
+ TELEGRAM_INTERACTION_DECISION_RESPONSE_FORMAT
781665
+ );
781666
+ const properties = format3.json_schema.schema.properties;
781667
+ properties.input_id = {
781668
+ type: "string",
781669
+ enum: [packet.inputId]
781670
+ };
781671
+ properties.addressed_actor_keys = {
781672
+ type: "array",
781673
+ items: { type: "string", enum: packet.actorKeys },
781674
+ maxItems: 12
781675
+ };
781676
+ properties.evidence_ids = {
781677
+ type: "array",
781678
+ items: {
781679
+ type: "string",
781680
+ enum: packet.evidence.map((entry) => entry.id)
781681
+ },
781682
+ maxItems: 16
781683
+ };
781684
+ return format3;
781685
+ }
781441
781686
  function extractPartialTelegramReplyJson(buffer2) {
781442
781687
  const stripped = stripTelegramHiddenThinking(buffer2).trimStart();
781443
781688
  if (!stripped.startsWith("{")) {
@@ -786166,8 +786411,8 @@ ${context2}` : context2;
786166
786411
  }
786167
786412
  await this.transitionTelegramIntake(
786168
786413
  intakeRecord,
786169
- "consumed",
786170
- "attention router retained active-runner input as context without reply authority",
786414
+ decision2.source === "inference-unavailable" ? "failed" : "consumed",
786415
+ decision2.source === "inference-unavailable" ? "attention router could not produce a valid input-bound decision" : "attention router retained active-runner input as context without reply authority",
786171
786416
  { runId: sameRunner ? currentSubAgent.runId : expectedSubAgent.runId }
786172
786417
  );
786173
786418
  return;
@@ -789246,6 +789491,8 @@ ${antecedents.join("\n")}` : "Immediate antecedents: none.",
789246
789491
  scenarioConfidence: decision2.scenarioConfidence,
789247
789492
  scenarioObjective: decision2.scenarioObjective,
789248
789493
  scenarioStateLoop: decision2.scenarioStateLoop,
789494
+ diagnosticNote: decision2.diagnosticNote,
789495
+ validationIssueCodes: decision2.validationIssueCodes,
789249
789496
  salienceSignals,
789250
789497
  self: this.telegramSelfSocialActorInput(),
789251
789498
  daydreamOpportunities
@@ -791646,10 +791893,7 @@ ${this.quoteTelegramContextBlock(msg.text, 1200)}`
791646
791893
  id2,
791647
791894
  `broker queue admission deadline expired for ${deadline.request_id}${queueSummary ? ` (${queueSummary})` : ""}; re-entering after ${(delayMs / 1e3).toFixed(1)}s, attempt ${brokerQueueRetryAttempt}`
791648
791895
  );
791649
- await this.waitForTelegramBrokerQueueRetry(
791650
- delayMs,
791651
- lifecycleSignal
791652
- );
791896
+ await this.waitForTelegramBrokerQueueRetry(delayMs, lifecycleSignal);
791653
791897
  }
791654
791898
  }
791655
791899
  const usage = result.usage;
@@ -792032,6 +792276,15 @@ arguments=` : ""}` + (chunk.toolCallArgs ?? "");
792032
792276
  getTelegramThinkingVisible() {
792033
792277
  return this.telegramThinkingVisible;
792034
792278
  }
792279
+ telegramMentionActorIsSelf(actor) {
792280
+ const selfUserId = this.currentTelegramBotUserId();
792281
+ if (selfUserId !== void 0 && actor.userId !== void 0 && actor.userId === selfUserId) {
792282
+ return true;
792283
+ }
792284
+ const selfUsername = this.state.botUsername.trim().replace(/^@/, "").toLowerCase();
792285
+ const actorUsername = actor.username?.trim().replace(/^@/, "").toLowerCase();
792286
+ return Boolean(selfUsername && actorUsername === selfUsername);
792287
+ }
792035
792288
  buildTelegramInteractionEvidencePacket(msg) {
792036
792289
  const sessionKey = this.sessionKeyForMessage(msg);
792037
792290
  const selfActorKey = telegramSocialActorKey(
@@ -792057,7 +792310,7 @@ arguments=` : ""}` + (chunk.toolCallArgs ?? "");
792057
792310
  ];
792058
792311
  const mentionedActors = msg.mentionedActors ?? (msg.mentionedUsernames ?? []).map((username) => ({ username }));
792059
792312
  mentionedActors.forEach((actor, index) => {
792060
- const actorKey = telegramSocialActorKey(actor);
792313
+ const actorKey = this.telegramMentionActorIsSelf(actor) ? selfActorKey : telegramSocialActorKey(actor);
792061
792314
  if (actorKey === "unknown") return;
792062
792315
  actorKeys.add(actorKey);
792063
792316
  evidence.push({
@@ -792097,33 +792350,103 @@ arguments=` : ""}` + (chunk.toolCallArgs ?? "");
792097
792350
  evidence
792098
792351
  };
792099
792352
  }
792100
- validateTelegramInteractionDecision(decision2, packet) {
792101
- if (decision2.inputId !== packet.inputId) return null;
792102
- if (!decision2.selfRole || !decision2.replyBasis) return null;
792353
+ validateTelegramInteractionDecisionDetailed(decision2, packet) {
792354
+ const issues = [];
792355
+ if (!decision2.inputId) {
792356
+ issues.push({
792357
+ code: "missing_input_id",
792358
+ message: `input_id must be ${JSON.stringify(packet.inputId)}.`
792359
+ });
792360
+ } else if (decision2.inputId !== packet.inputId) {
792361
+ issues.push({
792362
+ code: "input_id_mismatch",
792363
+ message: `input_id must be ${JSON.stringify(packet.inputId)}.`
792364
+ });
792365
+ }
792366
+ if (!decision2.selfRole) {
792367
+ issues.push({
792368
+ code: "missing_self_role",
792369
+ message: "self_role is required."
792370
+ });
792371
+ }
792372
+ if (!decision2.replyBasis) {
792373
+ issues.push({
792374
+ code: "missing_reply_basis",
792375
+ message: "reply_basis is required."
792376
+ });
792377
+ }
792103
792378
  const addressed = decision2.addressedActorKeys ?? [];
792104
792379
  const evidenceIds = decision2.evidenceIds ?? [];
792105
792380
  const allowedActors = new Set(packet.actorKeys);
792106
792381
  const allowedEvidence = new Set(packet.evidence.map((entry) => entry.id));
792107
- if (addressed.some((key) => !allowedActors.has(key))) return null;
792108
- if (evidenceIds.some((id2) => !allowedEvidence.has(id2))) return null;
792382
+ const unknownActors = addressed.filter((key) => !allowedActors.has(key));
792383
+ if (unknownActors.length > 0) {
792384
+ issues.push({
792385
+ code: "unknown_addressed_actor",
792386
+ message: `addressed_actor_keys contains unknown values ${JSON.stringify(unknownActors)}. Allowed values: ${JSON.stringify(packet.actorKeys)}.`
792387
+ });
792388
+ }
792389
+ const unknownEvidence = evidenceIds.filter(
792390
+ (id2) => !allowedEvidence.has(id2)
792391
+ );
792392
+ if (unknownEvidence.length > 0) {
792393
+ issues.push({
792394
+ code: "unknown_evidence_id",
792395
+ message: `evidence_ids contains unknown values ${JSON.stringify(unknownEvidence)}. Allowed values: ${JSON.stringify([...allowedEvidence])}.`
792396
+ });
792397
+ }
792109
792398
  if (decision2.selfRole === "addressee") {
792110
- if (!addressed.includes(packet.selfActorKey)) return null;
792399
+ if (!addressed.includes(packet.selfActorKey)) {
792400
+ issues.push({
792401
+ code: "addressee_missing_self_actor",
792402
+ message: `self_role=addressee requires addressed_actor_keys to include ${JSON.stringify(packet.selfActorKey)}.`
792403
+ });
792404
+ }
792111
792405
  } else if (decision2.selfRole === "observer") {
792112
- if (addressed.includes(packet.selfActorKey)) return null;
792406
+ if (addressed.includes(packet.selfActorKey)) {
792407
+ issues.push({
792408
+ code: "observer_addresses_self",
792409
+ message: `self_role=observer cannot address ${JSON.stringify(packet.selfActorKey)}.`
792410
+ });
792411
+ }
792113
792412
  }
792114
792413
  if (decision2.shouldReply) {
792115
- if (decision2.replyBasis === "none" || decision2.replyBasis === "uncertain" || evidenceIds.length === 0) {
792116
- return null;
792414
+ if (decision2.replyBasis === "none" || decision2.replyBasis === "uncertain") {
792415
+ issues.push({
792416
+ code: "reply_basis_not_authorized",
792417
+ message: "should_reply=true requires reply_basis direct_turn, ongoing_self_task, or social_intervention."
792418
+ });
792117
792419
  }
792118
- if (decision2.replyBasis === "direct_turn" && (decision2.selfRole !== "addressee" || !addressed.includes(packet.selfActorKey))) {
792119
- return null;
792420
+ if (evidenceIds.length === 0) {
792421
+ issues.push({
792422
+ code: "reply_missing_evidence",
792423
+ message: "should_reply=true requires at least one evidence_id."
792424
+ });
792425
+ }
792426
+ if (decision2.replyBasis === "direct_turn") {
792427
+ if (decision2.selfRole !== "addressee") {
792428
+ issues.push({
792429
+ code: "direct_turn_self_not_addressee",
792430
+ message: "reply_basis=direct_turn requires self_role=addressee."
792431
+ });
792432
+ }
792433
+ if (!addressed.includes(packet.selfActorKey)) {
792434
+ issues.push({
792435
+ code: "direct_turn_missing_self_actor",
792436
+ message: `reply_basis=direct_turn requires addressed_actor_keys to include ${JSON.stringify(packet.selfActorKey)}.`
792437
+ });
792438
+ }
792120
792439
  }
792121
792440
  if (decision2.replyBasis === "ongoing_self_task" && !packet.evidence.some(
792122
792441
  (entry) => entry.kind === "active_task" && evidenceIds.includes(entry.id)
792123
792442
  )) {
792124
- return null;
792443
+ issues.push({
792444
+ code: "ongoing_task_missing_active_task_evidence",
792445
+ message: "reply_basis=ongoing_self_task requires the active_task evidence ID."
792446
+ });
792125
792447
  }
792126
792448
  }
792449
+ if (issues.length > 0) return { decision: null, issues };
792127
792450
  const decisionId = `tgdec-${createHash74("sha256").update(
792128
792451
  JSON.stringify({
792129
792452
  inputId: packet.inputId,
@@ -792135,7 +792458,10 @@ arguments=` : ""}` + (chunk.toolCallArgs ?? "");
792135
792458
  evidenceIds
792136
792459
  })
792137
792460
  ).digest("hex").slice(0, 20)}`;
792138
- return { ...decision2, decisionId };
792461
+ return { decision: { ...decision2, decisionId }, issues: [] };
792462
+ }
792463
+ validateTelegramInteractionDecision(decision2, packet) {
792464
+ return this.validateTelegramInteractionDecisionDetailed(decision2, packet).decision;
792139
792465
  }
792140
792466
  bindTrustedTelegramDecision(decision2, packet) {
792141
792467
  const bound = {
@@ -792148,13 +792474,20 @@ arguments=` : ""}` + (chunk.toolCallArgs ?? "");
792148
792474
  };
792149
792475
  return this.validateTelegramInteractionDecision(bound, packet) ?? bound;
792150
792476
  }
792151
- async retryTelegramInteractionDecisionStrict(backend, userPrompt, forcedRoute, timeoutMs, diagnostics, sessionKey = "__router__", evidencePacket) {
792477
+ async retryTelegramInteractionDecisionStrict(backend, userPrompt, forcedRoute, timeoutMs, diagnostics, sessionKey = "__router__", evidencePacket, validationIssues = []) {
792152
792478
  const routeInstruction = forcedRoute ? `The operator selected Telegram mode "${forcedRoute}". The route field must be "${forcedRoute}", but should_reply must still be inferred from context.` : `Infer route live from context.`;
792153
792479
  const retryPrompt = [
792154
792480
  `A prior attempt failed the typed Telegram attention-decision contract and was discarded.`,
792155
792481
  `Make a fresh model-derived attention decision from the complete clean context below.`,
792156
792482
  `Return exactly one JSON object and no prose. No <think> tags. No commentary.`,
792157
792483
  routeInstruction,
792484
+ ...validationIssues.length > 0 ? [
792485
+ `The host rejected the prior semantic contract for these exact reasons:`,
792486
+ ...validationIssues.map(
792487
+ (issue2) => `- ${issue2.code}: ${issue2.message}`
792488
+ ),
792489
+ `Correct every listed issue. Do not repeat a rejected identifier.`
792490
+ ] : [],
792158
792491
  ``,
792159
792492
  `Required schema: ${TELEGRAM_INTERACTION_DECISION_MINIMAL_SCHEMA}`,
792160
792493
  ``,
@@ -792177,7 +792510,7 @@ arguments=` : ""}` + (chunk.toolCallArgs ?? "");
792177
792510
  maxTokens: 700,
792178
792511
  timeoutMs: telegramRouterTimeoutMs(timeoutMs, 8e3, 15e3),
792179
792512
  think: false,
792180
- responseFormat: TELEGRAM_INTERACTION_DECISION_RESPONSE_FORMAT
792513
+ responseFormat: evidencePacket ? telegramInteractionDecisionResponseFormatForPacket(evidencePacket) : TELEGRAM_INTERACTION_DECISION_RESPONSE_FORMAT
792181
792514
  },
792182
792515
  diagnostics,
792183
792516
  "router-strict-retry",
@@ -792193,11 +792526,19 @@ arguments=` : ""}` + (chunk.toolCallArgs ?? "");
792193
792526
  defaultShouldReply: false,
792194
792527
  requireShouldReply: true
792195
792528
  });
792196
- const validated = parsed && evidencePacket ? this.validateTelegramInteractionDecision(parsed, evidencePacket) : parsed;
792529
+ if (parsed && evidencePacket) parsed.inputId = evidencePacket.inputId;
792530
+ const validation = parsed && evidencePacket ? this.validateTelegramInteractionDecisionDetailed(
792531
+ parsed,
792532
+ evidencePacket
792533
+ ) : { decision: parsed, issues: [] };
792534
+ const validated = validation.decision;
792197
792535
  if (!validated) {
792198
792536
  if (diagnostics) {
792199
792537
  const cleaned = stripTelegramHiddenThinking(retryText).trim();
792200
- diagnostics.strictRetryStatus = cleaned ? "unparseable" : "empty";
792538
+ diagnostics.strictRetryStatus = parsed && validation.issues.length > 0 ? "semantic-invalid" : cleaned ? "unparseable" : "empty";
792539
+ diagnostics.strictRetryValidationIssues = validation.issues.map(
792540
+ (issue2) => issue2.code
792541
+ );
792201
792542
  }
792202
792543
  return null;
792203
792544
  }
@@ -792750,7 +793091,7 @@ ${this.quoteTelegramContextBlock(msg.text, 1200)}`
792750
793091
  maxTokens: 360,
792751
793092
  timeoutMs: telegramRouterTimeoutMs(config.timeoutMs, 8e3, 3e4),
792752
793093
  think: false,
792753
- responseFormat: TELEGRAM_INTERACTION_DECISION_RESPONSE_FORMAT
793094
+ responseFormat: telegramInteractionDecisionResponseFormatForPacket(evidencePacket)
792754
793095
  },
792755
793096
  diagnostics,
792756
793097
  "router",
@@ -792780,7 +793121,20 @@ ${this.quoteTelegramContextBlock(msg.text, 1200)}`
792780
793121
  defaultShouldReply: false,
792781
793122
  requireShouldReply: true
792782
793123
  });
792783
- const validated = parsed ? this.validateTelegramInteractionDecision(parsed, evidencePacket) : null;
793124
+ if (parsed) parsed.inputId = evidencePacket.inputId;
793125
+ const initialValidation = parsed ? this.validateTelegramInteractionDecisionDetailed(
793126
+ parsed,
793127
+ evidencePacket
793128
+ ) : {
793129
+ decision: null,
793130
+ issues: []
793131
+ };
793132
+ if (diagnostics) {
793133
+ diagnostics.validationIssues = initialValidation.issues.map(
793134
+ (issue2) => issue2.code
793135
+ );
793136
+ }
793137
+ const validated = initialValidation.decision;
792784
793138
  if (validated) {
792785
793139
  return withRouterTelemetry(
792786
793140
  this.applyTelegramSilentReflectionNotes(validated, reflectionNotes)
@@ -792812,7 +793166,8 @@ ${this.quoteTelegramContextBlock(msg.text, 1200)}`
792812
793166
  config.timeoutMs ?? 3e4,
792813
793167
  diagnostics,
792814
793168
  sessionKey,
792815
- evidencePacket
793169
+ evidencePacket,
793170
+ initialValidation.issues
792816
793171
  );
792817
793172
  if (strictRetry) {
792818
793173
  return withRouterTelemetry(
@@ -792826,20 +793181,26 @@ ${this.quoteTelegramContextBlock(msg.text, 1200)}`
792826
793181
  ) || telegramRouterErrorLooksLikeBackendLiveness(
792827
793182
  diagnostics.strictRetryError ?? ""
792828
793183
  );
792829
- const fallback = this.applyTelegramSilentReflectionNotes(
792830
- this.buildTelegramRouterUnavailableDecision(msg, toolContext, {
792831
- reason: backendLivenessFailure ? "router recovery hit a backend liveness failure; no model-derived reply decision" : dualEmptyVisible ? "router returned no visible decision content in JSON or plain mode; no model-derived reply decision" : "router output failed the typed decision contract and the fresh retry; no model-derived reply decision",
792832
- silentDisposition: reflectionNotes.silentDisposition,
792833
- diagnosticNote: this.composeTelegramRouterDiagnosticNote(
792834
- invalidRouterPreview,
792835
- failureNarrative,
792836
- backendLivenessFailure ? "router backend failed during attention-decision recovery; no usable router decision was available" : dualEmptyVisible ? "router returned no visible decision content in JSON or plain mode; fresh typed retry did not recover it" : invalidRouterPreview ? "router produced an invalid attention decision payload; the quarantined fresh retry did not recover it" : "router produced an empty attention decision payload; the fresh typed retry did not recover it",
792837
- diagnostics
792838
- ),
792839
- raw: text3
792840
- }),
792841
- reflectionNotes
792842
- );
793184
+ const fallback = {
793185
+ ...this.applyTelegramSilentReflectionNotes(
793186
+ this.buildTelegramRouterUnavailableDecision(msg, toolContext, {
793187
+ reason: backendLivenessFailure ? "router recovery hit a backend liveness failure; no model-derived reply decision" : dualEmptyVisible ? "router returned no visible decision content in JSON or plain mode; no model-derived reply decision" : "router output failed the typed decision contract and the fresh retry; no model-derived reply decision",
793188
+ silentDisposition: reflectionNotes.silentDisposition,
793189
+ diagnosticNote: this.composeTelegramRouterDiagnosticNote(
793190
+ invalidRouterPreview,
793191
+ failureNarrative,
793192
+ backendLivenessFailure ? "router backend failed during attention-decision recovery; no usable router decision was available" : dualEmptyVisible ? "router returned no visible decision content in JSON or plain mode; fresh typed retry did not recover it" : invalidRouterPreview ? "router produced an invalid attention decision payload; the quarantined fresh retry did not recover it" : "router produced an empty attention decision payload; the fresh typed retry did not recover it",
793193
+ diagnostics
793194
+ ),
793195
+ raw: text3
793196
+ }),
793197
+ reflectionNotes
793198
+ ),
793199
+ validationIssueCodes: [
793200
+ ...diagnostics.validationIssues ?? [],
793201
+ ...diagnostics.strictRetryValidationIssues ?? []
793202
+ ].filter((code8, index, all2) => all2.indexOf(code8) === index)
793203
+ };
792843
793204
  return withRouterTelemetry(fallback);
792844
793205
  } catch (err) {
792845
793206
  if (err instanceof TelegramBridgeStoppedError) throw err;
@@ -792918,7 +793279,7 @@ ${this.quoteTelegramContextBlock(msg.text, 1200)}`
792918
793279
  `empty visible content after <think> strip during ${affected}`
792919
793280
  );
792920
793281
  }
792921
- if (visibleAttempts.length > 0 && diag.strictRetryStatus !== "recovered") {
793282
+ if (visibleAttempts.length > 0 && (diag.validationIssues ?? []).length === 0 && diag.strictRetryStatus !== "recovered") {
792922
793283
  parts.push(`visible router text remained unparseable`);
792923
793284
  }
792924
793285
  if (skippedAttempts.length > 0) {
@@ -792969,6 +793330,10 @@ ${this.quoteTelegramContextBlock(msg.text, 1200)}`
792969
793330
  detailParts.push(
792970
793331
  `strict-retry: unparseable (preview="${diag.strictRetryPreview ?? ""}")`
792971
793332
  );
793333
+ } else if (diag.strictRetryStatus === "semantic-invalid") {
793334
+ detailParts.push(
793335
+ `strict-retry: semantic-invalid (${(diag.strictRetryValidationIssues ?? []).join(", ") || "no issue codes"})`
793336
+ );
792972
793337
  } else if (diag.strictRetryStatus === "threw") {
792973
793338
  detailParts.push(
792974
793339
  `strict-retry: threw (${diag.strictRetryError ?? "no detail"})`
@@ -792980,6 +793345,19 @@ ${this.quoteTelegramContextBlock(msg.text, 1200)}`
792980
793345
  } else if (diag.strictRetryStatus === "recovered") {
792981
793346
  detailParts.push(`strict-retry: recovered`);
792982
793347
  }
793348
+ if ((diag.validationIssues ?? []).length > 0) {
793349
+ parts.push(
793350
+ `router semantic contract rejected (${diag.validationIssues.join(", ")})`
793351
+ );
793352
+ detailParts.push(
793353
+ `router-validation: ${diag.validationIssues.join(", ")}`
793354
+ );
793355
+ }
793356
+ if ((diag.strictRetryValidationIssues ?? []).length > 0) {
793357
+ detailParts.push(
793358
+ `strict-retry-validation: ${diag.strictRetryValidationIssues.join(", ")}`
793359
+ );
793360
+ }
792983
793361
  let operatorHint;
792984
793362
  if (networkErrorSeen) {
792985
793363
  operatorHint = this.telegramRouterBackendLivenessHint(timeoutSeen);
@@ -795145,8 +795523,8 @@ Join: ${newUrl}`
795145
795523
  work.intakeRecords.map(
795146
795524
  (intake) => this.transitionTelegramIntake(
795147
795525
  intake,
795148
- "consumed",
795149
- "Telegram attention router retained intake without visible reply"
795526
+ decision2.source === "inference-unavailable" ? "failed" : "consumed",
795527
+ decision2.source === "inference-unavailable" ? "Telegram attention router could not produce a valid input-bound decision" : "Telegram attention router retained intake without visible reply"
795150
795528
  )
795151
795529
  )
795152
795530
  );
@@ -290365,7 +290365,6 @@ init_process_lifecycle();
290365
290365
  import { dirname as dirname2, join as join2, resolve as resolve3 } from "node:path";
290366
290366
  import { homedir as homedir3, tmpdir } from "node:os";
290367
290367
  init_vision();
290368
- var dynamicImport = new Function("mod", "return import(mod)");
290369
290368
  var PLAYWRIGHT_RUNTIME_DIR = join2(homedir3(), ".omnius", "playwright-runtime");
290370
290369
  var PLAYWRIGHT_BROWSERS_DIR = join2(PLAYWRIGHT_RUNTIME_DIR, "browsers");
290371
290370
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.664",
3
+ "version": "1.0.665",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.664",
9
+ "version": "1.0.665",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.664",
3
+ "version": "1.0.665",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/library.js",