@53able/pi-agent-browser 0.2.0 → 0.3.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/README.ja-JP.md +2 -0
- package/README.md +2 -0
- package/index.ts +60 -2
- package/package.json +1 -1
- package/skills/agent-browser/SKILL.md +4 -0
package/README.ja-JP.md
CHANGED
|
@@ -167,6 +167,8 @@ https://example.com をブラウザ経由で読み、outputs/browser/example.md
|
|
|
167
167
|
|
|
168
168
|
どちらのツールも対話的なセッション(TUI、またはUI対応のRPC)を必要とします。無人・headless実行では、黙って進めるのではなく拒否します。
|
|
169
169
|
|
|
170
|
+
`agent_browser_open`は、各セッションが`headed: true`で起動されたかどうかも記録します。`agent_browser_handoff`が、headedで開かれていないセッションに対して呼ばれた場合、人間への警告に加えて、可視ウィンドウを開き直すための`agent_browser_open`の引数(セッション名、`headed: true`、`restore: true`、最後に開いていたURL)をそのまま提示します — どのコマンドを打ち直せばよいか推測する必要はありません。ログインや2FA、CAPTCHA、本人確認に差し掛かる可能性があるタスクでは、最初から`headed: true`で開くことを推奨します。一部の確認チャレンジは使い切り・期限付きのため、途中で開き直すとログインからやり直しになることがあります。
|
|
171
|
+
|
|
170
172
|
## Troubleshooting
|
|
171
173
|
|
|
172
174
|
```bash
|
package/README.md
CHANGED
|
@@ -167,6 +167,8 @@ Not every step should run unattended. Two tools hand control back to a human at
|
|
|
167
167
|
|
|
168
168
|
Both tools require an interactive session (TUI or RPC with UI support); in headless/unattended runs they refuse rather than proceed silently.
|
|
169
169
|
|
|
170
|
+
`agent_browser_open` also remembers whether each named session was launched with `headed: true`. If `agent_browser_handoff` is called for a session that was not opened headed, it automatically warns the human and includes the exact `agent_browser_open` arguments (session name, `headed: true`, `restore: true`, and the last known URL) needed to reopen a visible window — no more guessing which command to rerun. Prefer `headed: true` up front for any task that might hit a login, 2FA, CAPTCHA, or identity-verification step, since some challenges are single-use and reopening mid-flow may require restarting the login.
|
|
171
|
+
|
|
170
172
|
## Troubleshooting
|
|
171
173
|
|
|
172
174
|
Run:
|
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, AgentToolUpdateCallback, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { constants, existsSync } from "node:fs";
|
|
5
5
|
import { join, resolve, basename } from "node:path";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
@@ -35,6 +35,51 @@ function scanForSensitiveContent(text: string): string[] {
|
|
|
35
35
|
return hits;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// Tracks whether each agent-browser session was last opened headed, so
|
|
39
|
+
// agent_browser_handoff / agent_browser_commit can tell a human definitively
|
|
40
|
+
// whether a visible browser window exists for them to act in, instead of guessing.
|
|
41
|
+
const SESSION_STATE_DIR = ".pi-agent-browser";
|
|
42
|
+
const SESSION_STATE_FILE = "sessions.json";
|
|
43
|
+
const DEFAULT_SESSION_KEY = "__default__";
|
|
44
|
+
|
|
45
|
+
type SessionState = { headed: boolean; lastUrl?: string; updatedAt: string };
|
|
46
|
+
|
|
47
|
+
function sessionKey(params: JsonObject): string {
|
|
48
|
+
return typeof params.session === "string" && params.session.trim() ? params.session.trim() : DEFAULT_SESSION_KEY;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function readSessionStates(cwd: string): Promise<Record<string, SessionState>> {
|
|
52
|
+
try {
|
|
53
|
+
const raw = await readFile(join(cwd, SESSION_STATE_DIR, SESSION_STATE_FILE), "utf8");
|
|
54
|
+
return JSON.parse(raw) as Record<string, SessionState>;
|
|
55
|
+
} catch {
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function writeSessionState(cwd: string, session: string, patch: Partial<Omit<SessionState, "updatedAt">>): Promise<void> {
|
|
61
|
+
const dir = join(cwd, SESSION_STATE_DIR);
|
|
62
|
+
await mkdir(dir, { recursive: true });
|
|
63
|
+
const states = await readSessionStates(cwd);
|
|
64
|
+
const previous = states[session];
|
|
65
|
+
states[session] = {
|
|
66
|
+
headed: patch.headed ?? previous?.headed ?? false,
|
|
67
|
+
lastUrl: patch.lastUrl ?? previous?.lastUrl,
|
|
68
|
+
updatedAt: new Date().toISOString(),
|
|
69
|
+
};
|
|
70
|
+
await writeFile(join(dir, SESSION_STATE_FILE), JSON.stringify(states, null, 2), "utf8");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function reopenHeadedGuidance(session: string, lastUrl: string | undefined): string[] {
|
|
74
|
+
const sessionArg = session === DEFAULT_SESSION_KEY ? {} : { session };
|
|
75
|
+
const reopenArgs = JSON.stringify({ ...sessionArg, headed: true, restore: true, ...(lastUrl ? { url: lastUrl } : {}) });
|
|
76
|
+
return [
|
|
77
|
+
`⚠ このセッション(${session === DEFAULT_SESSION_KEY ? "既定セッション" : session})はheadless(ウィンドウ非表示)で開かれています。`,
|
|
78
|
+
`人間が直接操作できるようにするには、agent_browser_open を次の引数で呼び直してください: ${reopenArgs}`,
|
|
79
|
+
"同じセッション名なのでcookie等は復元されますが、2FA/CAPTCHAなど使い切り・期限付きのチャレンジは、再度最初からやり直しになる場合があります。",
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
|
|
38
83
|
type JsonObject = Record<string, unknown>;
|
|
39
84
|
|
|
40
85
|
type RunResult = {
|
|
@@ -346,6 +391,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
346
391
|
promptGuidelines: [
|
|
347
392
|
"Use agent_browser_snapshot after opening or navigating before clicking by @ref.",
|
|
348
393
|
"Pass allowedDomains for untrusted or task-scoped browsing when possible.",
|
|
394
|
+
"Prefer headed: true whenever the task might hit a login, 2FA, CAPTCHA, or identity-verification step — a human can only intervene through agent_browser_handoff if a visible window already exists.",
|
|
349
395
|
],
|
|
350
396
|
parameters: OpenSchema,
|
|
351
397
|
async execute(_id, rawParams, signal, onUpdate, ctx) {
|
|
@@ -365,6 +411,12 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
365
411
|
if (typeof params.url === "string" && params.url.trim()) args.push(params.url.trim());
|
|
366
412
|
pushExtraArgs(args, params);
|
|
367
413
|
const result = await runAgentBrowser(args, { signal, timeoutMs: params.timeoutMs as number | undefined, cwd: ctx.cwd, onUpdate });
|
|
414
|
+
if (result.ok) {
|
|
415
|
+
await writeSessionState(ctx.cwd, sessionKey(params), {
|
|
416
|
+
headed: params.headed === true,
|
|
417
|
+
lastUrl: typeof params.url === "string" && params.url.trim() ? params.url.trim() : undefined,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
368
420
|
return buildTextResult(result);
|
|
369
421
|
},
|
|
370
422
|
});
|
|
@@ -582,6 +634,9 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
582
634
|
const category = String(params.category);
|
|
583
635
|
const reason = String(params.reason);
|
|
584
636
|
const artifacts: string[] = [];
|
|
637
|
+
const session = sessionKey(params);
|
|
638
|
+
const sessionState = (await readSessionStates(ctx.cwd))[session];
|
|
639
|
+
const headlessNotes = sessionState?.headed === true ? [] : reopenHeadedGuidance(session, sessionState?.lastUrl);
|
|
585
640
|
|
|
586
641
|
if (params.screenshot !== false) {
|
|
587
642
|
const path = resolveOutputPath(ctx, undefined, "handoff.png");
|
|
@@ -601,6 +656,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
601
656
|
`Human handoff required (${category}) but no interactive UI is available in this run mode.`,
|
|
602
657
|
`Reason: ${reason}`,
|
|
603
658
|
artifacts.length ? `Evidence: ${artifacts.join(", ")}` : undefined,
|
|
659
|
+
...headlessNotes,
|
|
604
660
|
"Stop this task now and ask the operator to rerun it interactively so the handoff can be completed.",
|
|
605
661
|
].filter(Boolean).join("\n"),
|
|
606
662
|
}],
|
|
@@ -609,8 +665,9 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
609
665
|
};
|
|
610
666
|
}
|
|
611
667
|
|
|
668
|
+
if (headlessNotes.length) ctx.ui.notify(headlessNotes.join(" "), "warning");
|
|
612
669
|
ctx.ui.notify(`Human handoff needed (${category}): ${reason}`, "warning");
|
|
613
|
-
ctx.ui.setStatus("agent-browser-handoff", `⏸ Waiting for human: ${reason}`);
|
|
670
|
+
ctx.ui.setStatus("agent-browser-handoff", headlessNotes.length ? `⏸ ブラウザ非表示 — ${reason}` : `⏸ Waiting for human: ${reason}`);
|
|
614
671
|
let choice: string | undefined;
|
|
615
672
|
try {
|
|
616
673
|
choice = await ctx.ui.select(
|
|
@@ -626,6 +683,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
626
683
|
content: [{
|
|
627
684
|
type: "text",
|
|
628
685
|
text: [
|
|
686
|
+
...headlessNotes,
|
|
629
687
|
resumed
|
|
630
688
|
? "Human confirmed the handoff step is complete. Take a fresh agent_browser_snapshot before continuing."
|
|
631
689
|
: "Human aborted the task during handoff. Stop and report back; do not retry automatically.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@53able/pi-agent-browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Typed Pi tools for browser automation, rendered-page context extraction, screenshots, and saved session state via agent-browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,6 +36,10 @@ Two tools exist so the agent stops and hands control back to a human instead of
|
|
|
36
36
|
|
|
37
37
|
`agent_browser_read` and `agent_browser_snapshot` also run a lightweight heuristic scan for 2FA/CAPTCHA/identity-verification keywords and prepend a warning to their output when detected — treat that warning as a signal to call `agent_browser_handoff` next, not as a blocker by itself.
|
|
38
38
|
|
|
39
|
+
### If the human can't find the browser window
|
|
40
|
+
|
|
41
|
+
`agent_browser_open` records whether each named session was launched with `headed: true`. When `agent_browser_handoff` runs for a session that was not opened headed, it automatically prepends a warning naming the session and the exact `agent_browser_open` arguments (including the last known URL) needed to reopen it visibly, instead of leaving the human to guess. Prefer `headed: true` from the start for any task that might hit a login, 2FA, CAPTCHA, or identity-verification step — a human can only intervene through `agent_browser_handoff` if a visible window already exists. Note that some verification challenges are single-use or session-bound, so reopening may require restarting the login flow rather than resuming mid-challenge.
|
|
42
|
+
|
|
39
43
|
## Common examples
|
|
40
44
|
|
|
41
45
|
Read a public page:
|