@proagentstore/cli 0.3.3 → 0.3.5

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.
@@ -194,3 +194,79 @@ export async function performBrowserAction(page, action, clickRobustly, resumeFi
194
194
  }
195
195
  }
196
196
  }
197
+ /**
198
+ * After a WRITE action (type/select/check), read back what the field actually
199
+ * holds now + any validation error rendered near it. This is the semantic
200
+ * feedback the brain otherwise lacks: `fill()` succeeding doesn't mean the value
201
+ * "took" (masked/intl widgets concatenate; validators reject a format). Returns a
202
+ * short human string for the action log, or "" when there's nothing notable.
203
+ * Never throws.
204
+ */
205
+ export async function inspectField(page, action) {
206
+ if (action.action !== "type" && action.action !== "select" && action.action !== "check")
207
+ return "";
208
+ if (!action.name)
209
+ return "";
210
+ try {
211
+ const role = action.role;
212
+ let loc = role ? page.getByRole(role, { name: action.name }) : page.getByText(action.name, { exact: false });
213
+ loc = typeof action.nth === "number" ? loc.nth(action.nth) : loc.first();
214
+ const el = await loc.elementHandle({ timeout: 1_500 }).catch(() => null);
215
+ if (!el)
216
+ return "";
217
+ const info = await el
218
+ .evaluate((node) => {
219
+ const e = node;
220
+ const raw = typeof e.value === "string" ? e.value : (e.getAttribute("aria-checked") ?? e.textContent ?? "");
221
+ const value = String(raw).trim().slice(0, 120);
222
+ const invalid = e.getAttribute("aria-invalid") === "true" || (typeof e.matches === "function" && e.matches(":invalid"));
223
+ let err = "";
224
+ const rx = /invalid|required|must|valid|error|format|enter a|please/i;
225
+ const describedby = e.getAttribute("aria-describedby");
226
+ if (describedby) {
227
+ for (const id of describedby.split(/\s+/)) {
228
+ const d = document.getElementById(id);
229
+ const t = (d?.textContent || "").trim();
230
+ if (t && rx.test(t)) {
231
+ err = t;
232
+ break;
233
+ }
234
+ }
235
+ }
236
+ if (!err) {
237
+ const scope = e.closest("[class*=field], [class*=form-group], [class*=form-item], fieldset") || e.parentElement;
238
+ const cand = scope?.querySelector('[role="alert"], [class*=error i], [class*=invalid i], [class*=danger i], [class*=help-block i]');
239
+ const t = (cand?.textContent || "").trim();
240
+ if (t && rx.test(t) && t.length < 180)
241
+ err = t;
242
+ }
243
+ return { value, invalid, err: err.replace(/\s+/g, " ").slice(0, 180) };
244
+ })
245
+ .catch(() => null);
246
+ if (!info)
247
+ return "";
248
+ const typed = String(action.text ?? "").trim();
249
+ const stuck = !!typed && info.value === typed; // our value actually took
250
+ const parts = [];
251
+ // Report the field's real value when it differs from what we sent, or when the
252
+ // field is authoritatively invalid.
253
+ if (info.value && (info.invalid || !stuck)) {
254
+ parts.push(`"${action.name}" now reads "${info.value}"${typed && info.value !== typed ? ` (you sent "${typed}")` : ""}`);
255
+ }
256
+ // Flag REJECTED ONLY on an authoritative per-field signal (aria-invalid / :invalid)
257
+ // or when our value clearly did NOT take. A nearby error while the value stuck is
258
+ // often stale (widgets show a "format" error mid-type that clears on blur/submit)
259
+ // or belongs to another field — reporting it as REJECTED sent the brain into a
260
+ // false-retry loop on a value that was actually accepted.
261
+ if (info.invalid || (typed && !stuck)) {
262
+ if (info.err)
263
+ parts.unshift(`⚠ "${action.name}" REJECTED: "${info.err}"`);
264
+ else if (info.invalid)
265
+ parts.unshift(`⚠ "${action.name}" is marked invalid`);
266
+ }
267
+ return parts.join("; ");
268
+ }
269
+ catch {
270
+ return "";
271
+ }
272
+ }
@@ -5,7 +5,7 @@ import { basename, dirname, join, resolve } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { captureScreenshotDataUrl, challengeSolved, detectHumanChallenge } from "./challenge.js";
7
7
  import { resolveRealChromeProfileDir, seedProfileCopy } from "./browser-profile.js";
8
- import { performBrowserAction } from "./browser-actions.js";
8
+ import { inspectField, performBrowserAction } from "./browser-actions.js";
9
9
  import { HumanHandoffError, RunnerInputError } from "./errors.js";
10
10
  import { RunnerStore } from "./store.js";
11
11
  import { CodingRuntime } from "./coding/runtime.js";
@@ -664,11 +664,15 @@ export class LocalRunner {
664
664
  if (action.action === "click" || action.action === "navigate" || action.action === "key") {
665
665
  await active.waitForLoadState("networkidle", { timeout: 3_500 }).catch(() => undefined);
666
666
  }
667
+ // Read back what a write action actually did (real field value + any validation
668
+ // error) so the brain gets semantic feedback, not just "Playwright didn't throw".
669
+ const feedback = await inspectField(active, action).catch(() => "");
667
670
  return {
668
671
  ok: true,
669
672
  url: active.url(),
670
673
  title: await active.title().catch(() => ""),
671
674
  challenge: await detectHumanChallenge(active),
675
+ feedback: feedback || undefined,
672
676
  };
673
677
  }
674
678
  /**
package/package.json CHANGED
@@ -1,46 +1,46 @@
1
1
  {
2
- "name": "@proagentstore/cli",
3
- "version": "0.3.3",
4
- "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
- "license": "MIT",
6
- "type": "module",
7
- "bin": {
8
- "pags": "dist/index.js"
9
- },
10
- "homepage": "https://github.com/ProAgentStore/platform#readme",
11
- "repository": {
12
- "type": "git",
13
- "url": "git+https://github.com/ProAgentStore/platform.git",
14
- "directory": "packages/cli"
15
- },
16
- "keywords": [
17
- "proagentstore",
18
- "pags",
19
- "cli",
20
- "agents",
21
- "mcp",
22
- "browser-runner"
23
- ],
24
- "publishConfig": {
25
- "access": "public"
26
- },
27
- "scripts": {
28
- "build": "pnpm --filter @proagentstore/browser-runner build && tsup src/index.ts --format esm --dts && node scripts/copy-browser-runner.mjs",
29
- "dev": "tsx src/index.ts",
30
- "typecheck": "tsc --noEmit"
31
- },
32
- "dependencies": {
33
- "chalk": "^5.6.2",
34
- "commander": "^13.0.0",
35
- "playwright": "^1.60.0"
36
- },
37
- "devDependencies": {
38
- "@types/node": "^22.0.0",
39
- "tsup": "^8.4.0",
40
- "tsx": "^4.21.0",
41
- "typescript": "^5.7.0"
42
- },
43
- "files": [
44
- "dist"
45
- ]
2
+ "name": "@proagentstore/cli",
3
+ "version": "0.3.5",
4
+ "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "pags": "dist/index.js"
9
+ },
10
+ "homepage": "https://github.com/ProAgentStore/platform#readme",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/ProAgentStore/platform.git",
14
+ "directory": "packages/cli"
15
+ },
16
+ "keywords": [
17
+ "proagentstore",
18
+ "pags",
19
+ "cli",
20
+ "agents",
21
+ "mcp",
22
+ "browser-runner"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "scripts": {
28
+ "build": "pnpm --filter @proagentstore/browser-runner build && tsup src/index.ts --format esm --dts && node scripts/copy-browser-runner.mjs",
29
+ "dev": "tsx src/index.ts",
30
+ "typecheck": "tsc --noEmit"
31
+ },
32
+ "dependencies": {
33
+ "chalk": "^5.6.2",
34
+ "commander": "^13.0.0",
35
+ "playwright": "^1.60.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.0.0",
39
+ "tsup": "^8.4.0",
40
+ "tsx": "^4.21.0",
41
+ "typescript": "^5.7.0"
42
+ },
43
+ "files": [
44
+ "dist"
45
+ ]
46
46
  }