@proagentstore/cli 0.4.1 → 0.4.3
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.
|
@@ -680,6 +680,45 @@ export class LocalRunner {
|
|
|
680
680
|
const head = (cut >= 0 ? text.slice(0, cut) : text).replace(/```[a-z]*\n?|```/g, " ").replace(/\s+/g, " ").trim();
|
|
681
681
|
return head.slice(0, 400);
|
|
682
682
|
}
|
|
683
|
+
/**
|
|
684
|
+
* After a WRITE action, read the field's REAL value back via the standard
|
|
685
|
+
* browser_evaluate tool (evaluates on the element by ref — no reinvented DOM
|
|
686
|
+
* traversal). Masked/intl fields transform input (e.g. "0404453580" → "+61404453580"),
|
|
687
|
+
* so without this signal the brain can't tell the value "took" and oscillates
|
|
688
|
+
* between formats. Returns the prompt's expected "…now reads…" / "REJECTED…" line.
|
|
689
|
+
* Never throws — best-effort feedback.
|
|
690
|
+
*/
|
|
691
|
+
async readBackField(mcp, action) {
|
|
692
|
+
if (!action.ref)
|
|
693
|
+
return "";
|
|
694
|
+
const fn = "el => ({ v: el.value !== undefined ? String(el.value) : (el.getAttribute('aria-checked') ?? el.textContent ?? '').trim(), " +
|
|
695
|
+
"bad: el.getAttribute('aria-invalid') === 'true' || !!(el.validity && !el.validity.valid), " +
|
|
696
|
+
"msg: el.validationMessage || '' })";
|
|
697
|
+
const res = await mcp.callTool("browser_evaluate", { element: action.name || "field", target: action.ref, function: fn }).catch(() => null);
|
|
698
|
+
if (!res || res.isError)
|
|
699
|
+
return "";
|
|
700
|
+
const txt = mcp.textOf(res);
|
|
701
|
+
const i = txt.indexOf("### Result");
|
|
702
|
+
if (i < 0)
|
|
703
|
+
return "";
|
|
704
|
+
const after = txt.slice(i + "### Result".length).trim();
|
|
705
|
+
const end = after.indexOf("\n###");
|
|
706
|
+
const block = (end >= 0 ? after.slice(0, end) : after).trim();
|
|
707
|
+
let parsed;
|
|
708
|
+
try {
|
|
709
|
+
parsed = JSON.parse(block);
|
|
710
|
+
}
|
|
711
|
+
catch {
|
|
712
|
+
return "";
|
|
713
|
+
}
|
|
714
|
+
if (typeof parsed?.v !== "string")
|
|
715
|
+
return "";
|
|
716
|
+
const name = action.name || "field";
|
|
717
|
+
const shown = parsed.v.length > 80 ? `${parsed.v.slice(0, 80)}…` : parsed.v;
|
|
718
|
+
if (parsed.bad)
|
|
719
|
+
return `⚠ "${name}" REJECTED: now reads "${shown}"${parsed.msg ? ` — ${parsed.msg.slice(0, 80)}` : ""}`;
|
|
720
|
+
return `"${name}" now reads "${shown}"`;
|
|
721
|
+
}
|
|
683
722
|
/** The snapshot ref the brain must target the element by (standard-tool `target`). */
|
|
684
723
|
refOf(action) {
|
|
685
724
|
const ref = (action.ref || "").trim();
|
|
@@ -712,8 +751,7 @@ export class LocalRunner {
|
|
|
712
751
|
return mcp.callTool("browser_select_option", { element: label, target: this.refOf(a), values: [a.text ?? ""] });
|
|
713
752
|
case "key":
|
|
714
753
|
return mcp.callTool("browser_press_key", { key: a.key || "Enter" });
|
|
715
|
-
|
|
716
|
-
return mcp.callTool("browser_evaluate", { function: `() => window.scrollBy(0, ${Math.round(a.dy ?? 600)})` });
|
|
754
|
+
// scroll is handled before this mapper (mouse-wheel on the live page).
|
|
717
755
|
case "wait":
|
|
718
756
|
return mcp.callTool("browser_wait_for", { time: Math.min(5, Math.max(0.1, (a.ms ?? 1000) / 1000)) });
|
|
719
757
|
default:
|
|
@@ -764,6 +802,22 @@ export class LocalRunner {
|
|
|
764
802
|
}
|
|
765
803
|
if (this.applyResumePath)
|
|
766
804
|
this.armFileAutoAttach(page, this.applyResumePath);
|
|
805
|
+
// Scroll is a VIEWPORT primitive, not an element action — no ref, no standard
|
|
806
|
+
// tool in @playwright/mcp 0.0.77. Use the mouse wheel on the live page (as the
|
|
807
|
+
// prior runtime did): it scrolls the actual hovered/inner scroll container,
|
|
808
|
+
// whereas window.scrollBy is a no-op on sites that scroll an inner panel (e.g.
|
|
809
|
+
// PageUp/Coles), which made the brain scroll-loop forever.
|
|
810
|
+
if (action.action === "scroll") {
|
|
811
|
+
await page.mouse.wheel(0, Math.round(action.dy ?? 600)).catch(() => undefined);
|
|
812
|
+
await page.waitForTimeout(300).catch(() => undefined);
|
|
813
|
+
const active = await this.getActivePage();
|
|
814
|
+
return {
|
|
815
|
+
ok: true,
|
|
816
|
+
url: active.url(),
|
|
817
|
+
title: await active.title().catch(() => ""),
|
|
818
|
+
challenge: await detectHumanChallenge(active),
|
|
819
|
+
};
|
|
820
|
+
}
|
|
767
821
|
const mcp = await this.getMcp();
|
|
768
822
|
const res = await this.callBrowserTool(mcp, action);
|
|
769
823
|
const text = mcp.textOf(res).trim();
|
|
@@ -802,12 +856,21 @@ export class LocalRunner {
|
|
|
802
856
|
if (action.action === "click" || action.action === "navigate" || action.action === "key") {
|
|
803
857
|
await active.waitForLoadState("networkidle", { timeout: 3_500 }).catch(() => undefined);
|
|
804
858
|
}
|
|
859
|
+
// For a WRITE, read the field's real value back (masked/intl fields transform
|
|
860
|
+
// input, e.g. "0404453580" → "+61404453580") so the brain knows it took and
|
|
861
|
+
// stops oscillating between formats — the signal inspectField used to give.
|
|
862
|
+
let feedback = this.conciseFeedback(text);
|
|
863
|
+
if (action.action === "type" || action.action === "select" || action.action === "check") {
|
|
864
|
+
const rb = await this.readBackField(mcp, action);
|
|
865
|
+
if (rb)
|
|
866
|
+
feedback = rb;
|
|
867
|
+
}
|
|
805
868
|
return {
|
|
806
869
|
ok: true,
|
|
807
870
|
url: active.url(),
|
|
808
871
|
title: await active.title().catch(() => ""),
|
|
809
872
|
challenge: await detectHumanChallenge(active),
|
|
810
|
-
feedback:
|
|
873
|
+
feedback: feedback || undefined,
|
|
811
874
|
};
|
|
812
875
|
}
|
|
813
876
|
// ── Agent-driven application lifecycle (called by the remote Workflow brain) ──
|