feat(browser): vision automation loop with screenshots, coordinate actions and bounded challenge solving
- browser.screenshot returns the page as an image content block (≤1280px wide, JPEG quality configurable) for vision-capable upstreams; non-vision upstreams get a redacted text snapshot. Login pages / password fields are marked sensitive and withheld unless a browser.sensitive_screenshot grant exists and allowSensitive is passed. - browser.click_at / type_at / scroll / wait act in screenshot pixels; the server converts via viewport/DPR, requires automation control and an allowlisted host, and writes a thumbnail evidence file (DATA_DIR/tmp/evidence) referenced from agent_run_event. - browser.solve_challenge runs a bounded child agent_run (vision caps only, maxSteps ≤ 12, budget), judges success from page facts, stops immediately on 2FA / SMS / device confirmation, and otherwise moves the session to waiting with evidence and a takeover link. - PiAgentRuntime passes modelImage outputs as pi-ai image content when model.input has image; the ledger never stores image data; coordinate actions use per-invocation idempotency keys. - Tests: image codec, runtime image content, E14 e2e (login solved, 2FA stop, step limit, sensitive withheld). Real camofox 1.17.0 check on example.com recorded in the runbook. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
0765264a47
commit
c437c6bff6
34 files changed
+2194
-35
No files matched your search
@@ -25,6 +25,7 @@ import {
|
||||
import { type AgentRunRow, type DownloaderInstanceRow, sql } from "@umt/db";
|
||||
import { type RunUpstream, resolveRunUpstream } from "../pi/index.ts";
|
||||
import type { ServerContext } from "../types.ts";
|
||||
import { PER_INVOCATION } from "./capability-registry.ts";
|
||||
import { DEMO_SCRIPTS, parseFakeScripts } from "./fake-scripts.ts";
|
||||
import { canAskUser, canRequestApproval, sessionOptions } from "./interaction.ts";
|
||||
import { buildGoalReport } from "./report.ts";
|
||||
@@ -125,7 +126,12 @@ export interface DriveResult {
|
||||
* 设置 → @umt/agent 的运行参数。按上游运行时(W7-A)凭据由上游的 CredentialStore 在请求时解密注入,这里不取明文;
|
||||
* 只走上游:v0.1 的旧字段(pi.provider / model / apiKeySecretRef)已弃用,不再读取(W8-A)。
|
||||
*/
|
||||
function agentSettings(s: GlobalSettings, budget: AgentBudget, fake: boolean, upstream: RunUpstream): AgentSettings {
|
||||
export function agentSettings(
|
||||
s: GlobalSettings,
|
||||
budget: AgentBudget,
|
||||
fake: boolean,
|
||||
upstream: RunUpstream,
|
||||
): AgentSettings {
|
||||
const pi = s.pi;
|
||||
const maxTokens = budget.maxTokens ?? s.agent.maxTokensPerRun ?? undefined;
|
||||
const maxCost = budget.maxCostUsd ?? s.agent.maxCostUsdPerRun ?? undefined;
|
||||
@@ -154,7 +160,7 @@ function agentSettings(s: GlobalSettings, budget: AgentBudget, fake: boolean, up
|
||||
}
|
||||
|
||||
/** 事件:先落库(run_id, seq 唯一)再推全局 SSE;未授权的工具失败在总线上标为 denied */
|
||||
function eventSink(env: AgentEnv) {
|
||||
export function eventSink(env: AgentEnv) {
|
||||
const { ctx } = env;
|
||||
return async (e: AgentRunEvent) => {
|
||||
const row = agentRunEventToRow(e);
|
||||
@@ -226,7 +232,8 @@ export async function reconcilePending(
|
||||
.where("status", "=", "running")
|
||||
.orderBy("created_at")
|
||||
.execute();
|
||||
for (const r of rows) {
|
||||
// 页面交互(坐标点击 / 输入 / 视觉子流程)崩溃后不重放:页面状态未知,由模型重新截图判断
|
||||
for (const r of rows.filter((x) => !PER_INVOCATION.has(x.capability_id))) {
|
||||
try {
|
||||
await registry.invoke(r.capability_id, r.input, {
|
||||
actorId: r.actor_id,
|
||||
@@ -385,7 +392,7 @@ export async function drive(
|
||||
}
|
||||
}
|
||||
const runtime = fake
|
||||
? createFakeRuntime({ registry, scripts, onEvent, onEventError }).runtime
|
||||
? createFakeRuntime({ registry, scripts, onEvent, onEventError, fake: { vision: true } }).runtime
|
||||
: new PiAgentRuntime({
|
||||
registry,
|
||||
onEvent,
|
||||
|
||||
Reference in new issue
Block a user