@e2edev/e2e 0.12.0 → 0.14.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.md +4 -4
- package/dist/agent/act.d.ts +13 -7
- package/dist/agent/act.d.ts.map +1 -1
- package/dist/agent/act.js +125 -730
- package/dist/agent/act.js.map +1 -1
- package/dist/agent/action-dispatcher.d.ts +98 -0
- package/dist/agent/action-dispatcher.d.ts.map +1 -0
- package/dist/agent/action-dispatcher.js +319 -0
- package/dist/agent/action-dispatcher.js.map +1 -0
- package/dist/agent/actions.d.ts +16 -1
- package/dist/agent/actions.d.ts.map +1 -1
- package/dist/agent/actions.js +5 -1
- package/dist/agent/actions.js.map +1 -1
- package/dist/agent/default-agent.d.ts +1 -1
- package/dist/agent/default-agent.d.ts.map +1 -1
- package/dist/agent/default-agent.js +30 -9
- package/dist/agent/default-agent.js.map +1 -1
- package/dist/agent/error.d.ts.map +1 -1
- package/dist/agent/error.js +1 -0
- package/dist/agent/error.js.map +1 -1
- package/dist/agent/executor.d.ts +33 -5
- package/dist/agent/executor.d.ts.map +1 -1
- package/dist/agent/executor.js.map +1 -1
- package/dist/agent/index.js +1 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/invocation.d.ts +4 -9
- package/dist/agent/invocation.d.ts.map +1 -1
- package/dist/agent/invocation.js +1 -6
- package/dist/agent/invocation.js.map +1 -1
- package/dist/agent/model/sdk.d.ts +1 -1
- package/dist/agent/model/sdk.js +1 -1
- package/dist/agent/observation-feed.d.ts +123 -0
- package/dist/agent/observation-feed.d.ts.map +1 -0
- package/dist/agent/observation-feed.js +281 -0
- package/dist/agent/observation-feed.js.map +1 -0
- package/dist/agent/observation.d.ts +14 -1
- package/dist/agent/observation.d.ts.map +1 -1
- package/dist/agent/observation.js +58 -2
- package/dist/agent/observation.js.map +1 -1
- package/dist/agent/operation-queue.d.ts +21 -0
- package/dist/agent/operation-queue.d.ts.map +1 -0
- package/dist/agent/operation-queue.js +27 -0
- package/dist/agent/operation-queue.js.map +1 -0
- package/dist/agent/phases.js +1 -1
- package/dist/agent/phases.js.map +1 -1
- package/dist/agent/pixels.d.ts +21 -0
- package/dist/agent/pixels.d.ts.map +1 -0
- package/dist/agent/pixels.js +85 -0
- package/dist/agent/pixels.js.map +1 -0
- package/dist/agent/point-tap.d.ts +52 -0
- package/dist/agent/point-tap.d.ts.map +1 -0
- package/dist/agent/point-tap.js +88 -0
- package/dist/agent/point-tap.js.map +1 -0
- package/dist/agent/primitives.d.ts +7 -1
- package/dist/agent/primitives.d.ts.map +1 -1
- package/dist/agent/primitives.js +99 -27
- package/dist/agent/primitives.js.map +1 -1
- package/dist/agent/replay.d.ts +8 -3
- package/dist/agent/replay.d.ts.map +1 -1
- package/dist/agent/replay.js +44 -15
- package/dist/agent/replay.js.map +1 -1
- package/dist/agent/screen-update.d.ts +50 -18
- package/dist/agent/screen-update.d.ts.map +1 -1
- package/dist/agent/screen-update.js +76 -94
- package/dist/agent/screen-update.js.map +1 -1
- package/dist/agent/secrets.d.ts.map +1 -1
- package/dist/agent/secrets.js +13 -9
- package/dist/agent/secrets.js.map +1 -1
- package/dist/agent/step-accounting.d.ts +106 -0
- package/dist/agent/step-accounting.d.ts.map +1 -0
- package/dist/agent/step-accounting.js +216 -0
- package/dist/agent/step-accounting.js.map +1 -0
- package/dist/agent/step-cache.d.ts.map +1 -1
- package/dist/agent/step-cache.js +2 -2
- package/dist/agent/step-cache.js.map +1 -1
- package/dist/agent/tool-loop.d.ts.map +1 -1
- package/dist/agent/tool-loop.js +9 -2
- package/dist/agent/tool-loop.js.map +1 -1
- package/dist/agent/transcript-compaction.d.ts +38 -0
- package/dist/agent/transcript-compaction.d.ts.map +1 -0
- package/dist/agent/transcript-compaction.js +165 -0
- package/dist/agent/transcript-compaction.js.map +1 -0
- package/dist/cache/recorder.d.ts.map +1 -1
- package/dist/cache/recorder.js +20 -0
- package/dist/cache/recorder.js.map +1 -1
- package/dist/cache/trace.d.ts +28 -1
- package/dist/cache/trace.d.ts.map +1 -1
- package/dist/cache/trace.js +44 -0
- package/dist/cache/trace.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +55 -32
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/engine-versions.json +1 -1
- package/dist/cli/init/engines.d.ts +29 -10
- package/dist/cli/init/engines.d.ts.map +1 -1
- package/dist/cli/init/engines.js +34 -12
- package/dist/cli/init/engines.js.map +1 -1
- package/dist/cli/init/gateways.js +3 -3
- package/dist/cli/init/mcp-config.js +1 -1
- package/dist/cli/init/mcp-config.js.map +1 -1
- package/dist/cli/init/scaffold.d.ts.map +1 -1
- package/dist/cli/init/scaffold.js +1 -3
- package/dist/cli/init/scaffold.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +3 -2
- package/dist/cli/init.js.map +1 -1
- package/dist/collect/collect.d.ts +8 -3
- package/dist/collect/collect.d.ts.map +1 -1
- package/dist/collect/collect.js +29 -5
- package/dist/collect/collect.js.map +1 -1
- package/dist/collect/registry.d.ts +20 -5
- package/dist/collect/registry.d.ts.map +1 -1
- package/dist/collect/registry.js +139 -54
- package/dist/collect/registry.js.map +1 -1
- package/dist/collect/select.js +3 -1
- package/dist/collect/select.js.map +1 -1
- package/dist/config/agent.d.ts +1 -7
- package/dist/config/agent.d.ts.map +1 -1
- package/dist/config/agent.js +5 -8
- package/dist/config/agent.js.map +1 -1
- package/dist/config/app.d.ts +24 -2
- package/dist/config/app.d.ts.map +1 -1
- package/dist/config/app.js +82 -15
- package/dist/config/app.js.map +1 -1
- package/dist/config/diagnose.d.ts.map +1 -1
- package/dist/config/diagnose.js +2 -0
- package/dist/config/diagnose.js.map +1 -1
- package/dist/config/resolve.d.ts +26 -3
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +66 -23
- package/dist/config/resolve.js.map +1 -1
- package/dist/config/secrets.d.ts +89 -0
- package/dist/config/secrets.d.ts.map +1 -0
- package/dist/config/secrets.js +67 -0
- package/dist/config/secrets.js.map +1 -0
- package/dist/engine/contract.d.ts +17 -2
- package/dist/engine/contract.d.ts.map +1 -1
- package/dist/engine/contract.js.map +1 -1
- package/dist/engine/index.d.ts +18 -4
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +8 -0
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/session.d.ts.map +1 -1
- package/dist/engine/session.js +3 -0
- package/dist/engine/session.js.map +1 -1
- package/dist/engine/surface.d.ts +6 -2
- package/dist/engine/surface.d.ts.map +1 -1
- package/dist/engine/surface.js.map +1 -1
- package/dist/expect/attempt.d.ts +15 -0
- package/dist/expect/attempt.d.ts.map +1 -0
- package/dist/expect/attempt.js +33 -0
- package/dist/expect/attempt.js.map +1 -0
- package/dist/expect/index.d.ts +7 -5
- package/dist/expect/index.d.ts.map +1 -1
- package/dist/expect/index.js +8 -1
- package/dist/expect/index.js.map +1 -1
- package/dist/expect/poll.d.ts +5 -0
- package/dist/expect/poll.d.ts.map +1 -0
- package/dist/expect/poll.js +118 -0
- package/dist/expect/poll.js.map +1 -0
- package/dist/expect/values.d.ts.map +1 -1
- package/dist/expect/values.js +6 -1
- package/dist/expect/values.js.map +1 -1
- package/dist/explore/body.d.ts.map +1 -1
- package/dist/explore/body.js +3 -1
- package/dist/explore/body.js.map +1 -1
- package/dist/explore/executor.js +16 -11
- package/dist/explore/executor.js.map +1 -1
- package/dist/explore/index.d.ts +3 -2
- package/dist/explore/index.d.ts.map +1 -1
- package/dist/explore/index.js +5 -5
- package/dist/explore/index.js.map +1 -1
- package/dist/explore/plan.d.ts.map +1 -1
- package/dist/explore/plan.js +16 -5
- package/dist/explore/plan.js.map +1 -1
- package/dist/explore/progress.d.ts +43 -0
- package/dist/explore/progress.d.ts.map +1 -0
- package/dist/explore/progress.js +9 -0
- package/dist/explore/progress.js.map +1 -0
- package/dist/explore/state.d.ts +13 -12
- package/dist/explore/state.d.ts.map +1 -1
- package/dist/explore/state.js +28 -11
- package/dist/explore/state.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/brands.d.ts +2 -0
- package/dist/internal/brands.d.ts.map +1 -1
- package/dist/internal/brands.js +2 -0
- package/dist/internal/brands.js.map +1 -1
- package/dist/internal/geometry.d.ts +13 -0
- package/dist/internal/geometry.d.ts.map +1 -0
- package/dist/internal/geometry.js +16 -0
- package/dist/internal/geometry.js.map +1 -0
- package/dist/internal/urls.d.ts +6 -0
- package/dist/internal/urls.d.ts.map +1 -1
- package/dist/internal/urls.js +28 -0
- package/dist/internal/urls.js.map +1 -1
- package/dist/locator/engine.d.ts.map +1 -1
- package/dist/locator/engine.js +1 -0
- package/dist/locator/engine.js.map +1 -1
- package/dist/mcp/catalog.d.ts +5 -4
- package/dist/mcp/catalog.d.ts.map +1 -1
- package/dist/mcp/catalog.js +5 -36
- package/dist/mcp/catalog.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +1 -1
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +12 -6
- package/dist/mcp/session.js.map +1 -1
- package/dist/report/builtin.d.ts +14 -0
- package/dist/report/builtin.d.ts.map +1 -0
- package/dist/report/builtin.js +22 -0
- package/dist/report/builtin.js.map +1 -0
- package/dist/report/explore-text.d.ts +17 -0
- package/dist/report/explore-text.d.ts.map +1 -0
- package/dist/report/explore-text.js +37 -0
- package/dist/report/explore-text.js.map +1 -0
- package/dist/report/format.d.ts +2 -0
- package/dist/report/format.d.ts.map +1 -1
- package/dist/report/format.js +1 -1
- package/dist/report/format.js.map +1 -1
- package/dist/report/junit.d.ts +2 -4
- package/dist/report/junit.d.ts.map +1 -1
- package/dist/report/junit.js +7 -40
- package/dist/report/junit.js.map +1 -1
- package/dist/report/list-explore.d.ts +109 -0
- package/dist/report/list-explore.d.ts.map +1 -0
- package/dist/report/list-explore.js +368 -0
- package/dist/report/list-explore.js.map +1 -0
- package/dist/report/list.d.ts +21 -0
- package/dist/report/list.d.ts.map +1 -1
- package/dist/report/list.js +79 -3
- package/dist/report/list.js.map +1 -1
- package/dist/report/markdown.d.ts +39 -0
- package/dist/report/markdown.d.ts.map +1 -0
- package/dist/report/markdown.js +426 -0
- package/dist/report/markdown.js.map +1 -0
- package/dist/report/outcome.d.ts +29 -0
- package/dist/report/outcome.d.ts.map +1 -0
- package/dist/report/outcome.js +36 -0
- package/dist/report/outcome.js.map +1 -0
- package/dist/report/running-tree.d.ts +7 -7
- package/dist/report/running-tree.d.ts.map +1 -1
- package/dist/report/running-tree.js +12 -13
- package/dist/report/running-tree.js.map +1 -1
- package/dist/report/write.d.ts +10 -1
- package/dist/report/write.d.ts.map +1 -1
- package/dist/report/write.js +22 -1
- package/dist/report/write.js.map +1 -1
- package/dist/run/app-ports.d.ts +17 -0
- package/dist/run/app-ports.d.ts.map +1 -0
- package/dist/run/app-ports.js +74 -0
- package/dist/run/app-ports.js.map +1 -0
- package/dist/run/events.d.ts +10 -5
- package/dist/run/events.d.ts.map +1 -1
- package/dist/run/events.js.map +1 -1
- package/dist/run/execute.d.ts.map +1 -1
- package/dist/run/execute.js +24 -7
- package/dist/run/execute.js.map +1 -1
- package/dist/run/extended-fixtures.d.ts +33 -0
- package/dist/run/extended-fixtures.d.ts.map +1 -0
- package/dist/run/extended-fixtures.js +91 -0
- package/dist/run/extended-fixtures.js.map +1 -0
- package/dist/run/fixtures.d.ts.map +1 -1
- package/dist/run/fixtures.js +39 -11
- package/dist/run/fixtures.js.map +1 -1
- package/dist/run/managed-process.d.ts +18 -0
- package/dist/run/managed-process.d.ts.map +1 -1
- package/dist/run/managed-process.js +101 -6
- package/dist/run/managed-process.js.map +1 -1
- package/dist/run/realm.d.ts +5 -4
- package/dist/run/realm.d.ts.map +1 -1
- package/dist/run/realm.js +8 -7
- package/dist/run/realm.js.map +1 -1
- package/dist/run/runner.d.ts +9 -3
- package/dist/run/runner.d.ts.map +1 -1
- package/dist/run/runner.js +15 -12
- package/dist/run/runner.js.map +1 -1
- package/dist/run/secrecy.d.ts +3 -3
- package/dist/run/secrecy.d.ts.map +1 -1
- package/dist/run/secrecy.js +4 -4
- package/dist/run/secrecy.js.map +1 -1
- package/dist/run/standalone.js +6 -6
- package/dist/run/standalone.js.map +1 -1
- package/dist/run/worker/entry.js +4 -3
- package/dist/run/worker/entry.js.map +1 -1
- package/dist/run/worker/protocol.d.ts +7 -1
- package/dist/run/worker/protocol.d.ts.map +1 -1
- package/dist/run/worker/protocol.js.map +1 -1
- package/dist/secrets.d.ts +21 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +66 -0
- package/dist/secrets.js.map +1 -0
- package/dist/types.d.ts +90 -50
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -3
- package/skills/e2e/SKILL.md +42 -25
- package/skills/e2e/references/agent.md +80 -24
- package/skills/e2e/references/debugging.md +6 -5
- package/skills/e2e/references/explore.md +13 -7
- package/skills/e2e/references/mcp.md +4 -4
- package/skills/e2e/references/running.md +32 -21
- package/skills/e2e/references/setup.md +42 -16
- package/skills/e2e/references/writing-tests.md +32 -16
- package/dist/agent/model/router.d.ts +0 -36
- package/dist/agent/model/router.d.ts.map +0 -1
- package/dist/agent/model/router.js +0 -45
- package/dist/agent/model/router.js.map +0 -1
- package/dist/credentials.d.ts +0 -13
- package/dist/credentials.d.ts.map +0 -1
- package/dist/credentials.js +0 -51
- package/dist/credentials.js.map +0 -1
- package/dist/explore/reporter.d.ts +0 -11
- package/dist/explore/reporter.d.ts.map +0 -1
- package/dist/explore/reporter.js +0 -54
- package/dist/explore/reporter.js.map +0 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call-order serialization for one step's observations and actions.
|
|
3
|
+
*
|
|
4
|
+
* An executor (or an AI SDK loop running parallel tool calls) that issues a
|
|
5
|
+
* second action before the first settles would otherwise resolve both
|
|
6
|
+
* targets against the same pre-action observation — exactly the wrong-node
|
|
7
|
+
* hazard the staleness rule exists to prevent. Queued, the second call sees
|
|
8
|
+
* the newest observation and a stale id fails loud instead of acting on the
|
|
9
|
+
* wrong node.
|
|
10
|
+
*
|
|
11
|
+
* Invariant: a queued body must never enqueue another operation itself — the
|
|
12
|
+
* inner call would wait behind its own caller and deadlock. Action bodies
|
|
13
|
+
* call the engine session directly, secret authorization never observes, and
|
|
14
|
+
* a relocation captures raw rather than through the queued observe.
|
|
15
|
+
*/
|
|
16
|
+
export class OperationQueue {
|
|
17
|
+
chain = Promise.resolve();
|
|
18
|
+
/** Runs `body` after every operation queued before it, in call order. */
|
|
19
|
+
run(body) {
|
|
20
|
+
// The chain is always already settled-to-undefined, so failures propagate
|
|
21
|
+
// to their own caller and never poison the queue.
|
|
22
|
+
const run = this.chain.then(body);
|
|
23
|
+
this.chain = run.then(() => undefined, () => undefined);
|
|
24
|
+
return run;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=operation-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation-queue.js","sourceRoot":"","sources":["../../src/agent/operation-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,cAAc;IACjB,KAAK,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;IAEpD,yEAAyE;IACzE,GAAG,CAAI,IAAsB;QAC3B,0EAA0E;QAC1E,kDAAkD;QAClD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,IAAI,CACnB,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;QACF,OAAO,GAAG,CAAC;IACb,CAAC;CACF"}
|
package/dist/agent/phases.js
CHANGED
|
@@ -15,7 +15,7 @@ import { AgentError, toAgentError } from './error.js';
|
|
|
15
15
|
* a clearly attributed failure, not the test budget.
|
|
16
16
|
*/
|
|
17
17
|
export function boundedOperation(engine, actionTimeout, deadline) {
|
|
18
|
-
return engine.operation(Math.max(1, Math.min(actionTimeout, deadline.remaining())));
|
|
18
|
+
return { ...engine.operation(Math.max(1, Math.min(actionTimeout, deadline.remaining()))), origin: 'agent' };
|
|
19
19
|
}
|
|
20
20
|
/** Records one policy decision as a child event of the current step. */
|
|
21
21
|
export function recordPolicyEvent(steps, name, decision, code) {
|
package/dist/agent/phases.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phases.js","sourceRoot":"","sources":["../../src/agent/phases.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAiB,MAAM,qBAAqB,CAAC;AAG7E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAqB,EACrB,aAAqB,EACrB,QAAkB;IAElB,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"phases.js","sourceRoot":"","sources":["../../src/agent/phases.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The screenshot a model receives, sized for it. Engines capture at CSS or
|
|
3
|
+
* device scale; a 1280 by 720 web viewport or a 1170 by 2532 phone is more
|
|
4
|
+
* image than a model needs to place a tap, and every image pixel is paid
|
|
5
|
+
* for on every turn it stays in the conversation. The long side is capped
|
|
6
|
+
* and the image resampled, and `scale` (image pixels per CSS pixel) is
|
|
7
|
+
* reduced with it, so every coordinate a model reads off the image still
|
|
8
|
+
* maps onto the viewport through the same arithmetic. Masked regions stay
|
|
9
|
+
* masked: a box filter over black stays black.
|
|
10
|
+
*/
|
|
11
|
+
import type { ObservationPixels } from '../engine/surface.ts';
|
|
12
|
+
/** Sizes a captured screenshot for the model; the input is returned as is when it already fits. */
|
|
13
|
+
export declare function sizeForModel<P extends ObservationPixels>(pixels: P): P;
|
|
14
|
+
/**
|
|
15
|
+
* Resamples the image so its longer side is at most `maxLongSide`, with a
|
|
16
|
+
* box filter over the source pixels each target pixel covers. Returns the
|
|
17
|
+
* input untouched when it already fits or cannot be decoded; whatever else
|
|
18
|
+
* the caller's pixels carry (a masked-region count) rides along unchanged.
|
|
19
|
+
*/
|
|
20
|
+
export declare function downscalePixels<P extends ObservationPixels>(pixels: P, maxLongSide: number): P;
|
|
21
|
+
//# sourceMappingURL=pixels.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pixels.d.ts","sourceRoot":"","sources":["../../src/agent/pixels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAa9D,mGAAmG;AACnG,wBAAgB,YAAY,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAEtE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,EAAE,MAAM,GAAG,CAAC,CAgB9F"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The screenshot a model receives, sized for it. Engines capture at CSS or
|
|
3
|
+
* device scale; a 1280 by 720 web viewport or a 1170 by 2532 phone is more
|
|
4
|
+
* image than a model needs to place a tap, and every image pixel is paid
|
|
5
|
+
* for on every turn it stays in the conversation. The long side is capped
|
|
6
|
+
* and the image resampled, and `scale` (image pixels per CSS pixel) is
|
|
7
|
+
* reduced with it, so every coordinate a model reads off the image still
|
|
8
|
+
* maps onto the viewport through the same arithmetic. Masked regions stay
|
|
9
|
+
* masked: a box filter over black stays black.
|
|
10
|
+
*/
|
|
11
|
+
import { PNG } from 'pngjs';
|
|
12
|
+
/**
|
|
13
|
+
* Longest side a model-bound screenshot keeps, in image pixels. Measured on
|
|
14
|
+
* gpt-5.6 over a ten-key drawn keypad and an eight-screen drawn wizard, four
|
|
15
|
+
* runs per size: a 1280 by 720 capture costs about 1390 input tokens per
|
|
16
|
+
* turn, 1024 about 960, 768 about 680, 640 about 580, 512 about 500. Every
|
|
17
|
+
* size down to 640 passed every run; at 512 the model misread the keypad's
|
|
18
|
+
* display, needed retries, and once accepted a wrong code. 768 keeps a full
|
|
19
|
+
* step of margin above that cliff for 35% less per run than full size.
|
|
20
|
+
*/
|
|
21
|
+
const MAX_SCREENSHOT_LONG_SIDE = 768;
|
|
22
|
+
/** Sizes a captured screenshot for the model; the input is returned as is when it already fits. */
|
|
23
|
+
export function sizeForModel(pixels) {
|
|
24
|
+
return downscalePixels(pixels, MAX_SCREENSHOT_LONG_SIDE);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resamples the image so its longer side is at most `maxLongSide`, with a
|
|
28
|
+
* box filter over the source pixels each target pixel covers. Returns the
|
|
29
|
+
* input untouched when it already fits or cannot be decoded; whatever else
|
|
30
|
+
* the caller's pixels carry (a masked-region count) rides along unchanged.
|
|
31
|
+
*/
|
|
32
|
+
export function downscalePixels(pixels, maxLongSide) {
|
|
33
|
+
const longSide = Math.max(pixels.width, pixels.height);
|
|
34
|
+
if (longSide <= maxLongSide || maxLongSide <= 0)
|
|
35
|
+
return pixels;
|
|
36
|
+
const factor = maxLongSide / longSide;
|
|
37
|
+
const width = Math.max(1, Math.round(pixels.width * factor));
|
|
38
|
+
const height = Math.max(1, Math.round(pixels.height * factor));
|
|
39
|
+
let source;
|
|
40
|
+
try {
|
|
41
|
+
source = PNG.sync.read(Buffer.from(pixels.data));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return pixels;
|
|
45
|
+
}
|
|
46
|
+
const target = new PNG({ width, height });
|
|
47
|
+
boxResample(source.data, source.width, source.height, target.data, width, height);
|
|
48
|
+
const data = new Uint8Array(PNG.sync.write(target));
|
|
49
|
+
return { ...pixels, data, width, height, scale: pixels.scale * (width / pixels.width) };
|
|
50
|
+
}
|
|
51
|
+
/** Averages, per channel, every source pixel a target pixel covers. RGBA, 8 bits per channel. */
|
|
52
|
+
function boxResample(source, sourceWidth, sourceHeight, target, targetWidth, targetHeight) {
|
|
53
|
+
const xRatio = sourceWidth / targetWidth;
|
|
54
|
+
const yRatio = sourceHeight / targetHeight;
|
|
55
|
+
for (let ty = 0; ty < targetHeight; ty += 1) {
|
|
56
|
+
const y0 = Math.floor(ty * yRatio);
|
|
57
|
+
const y1 = Math.min(sourceHeight, Math.max(y0 + 1, Math.floor((ty + 1) * yRatio)));
|
|
58
|
+
for (let tx = 0; tx < targetWidth; tx += 1) {
|
|
59
|
+
const x0 = Math.floor(tx * xRatio);
|
|
60
|
+
const x1 = Math.min(sourceWidth, Math.max(x0 + 1, Math.floor((tx + 1) * xRatio)));
|
|
61
|
+
let r = 0;
|
|
62
|
+
let g = 0;
|
|
63
|
+
let b = 0;
|
|
64
|
+
let a = 0;
|
|
65
|
+
let count = 0;
|
|
66
|
+
for (let y = y0; y < y1; y += 1) {
|
|
67
|
+
let offset = (y * sourceWidth + x0) * 4;
|
|
68
|
+
for (let x = x0; x < x1; x += 1) {
|
|
69
|
+
r += source[offset];
|
|
70
|
+
g += source[offset + 1];
|
|
71
|
+
b += source[offset + 2];
|
|
72
|
+
a += source[offset + 3];
|
|
73
|
+
offset += 4;
|
|
74
|
+
count += 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const at = (ty * targetWidth + tx) * 4;
|
|
78
|
+
target[at] = Math.round(r / count);
|
|
79
|
+
target[at + 1] = Math.round(g / count);
|
|
80
|
+
target[at + 2] = Math.round(b / count);
|
|
81
|
+
target[at + 3] = Math.round(a / count);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=pixels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pixels.js","sourceRoot":"","sources":["../../src/agent/pixels.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Point taps: how a viewport point an executor names is routed onto the
|
|
3
|
+
* tree, and how the result reads back. A point that a listed, enabled
|
|
4
|
+
* control contains is tapped by that control's id, so policy, stale
|
|
5
|
+
* relocation, and the trace cache see an ordinary tap; a point on nothing
|
|
6
|
+
* listed goes to the engine as a bare point. Coordinates read off a
|
|
7
|
+
* screenshot are scaled into the observation's CSS pixels first.
|
|
8
|
+
*/
|
|
9
|
+
import type { SemanticNode, ViewportPoint } from '../engine/surface.ts';
|
|
10
|
+
import type { ExecutorPixels } from './executor.ts';
|
|
11
|
+
import { type AgentObservation } from './observation.ts';
|
|
12
|
+
/**
|
|
13
|
+
* Scales a point read off a screenshot into the observation's CSS pixels and
|
|
14
|
+
* clamps it to the viewport. `pixels.scale` is image pixels per CSS pixel
|
|
15
|
+
* (3 on a device screenshot, 1 on a CSS-scale capture); the observation's
|
|
16
|
+
* own `viewport.scale` is not consulted, because a device engine reports its
|
|
17
|
+
* geometry in logical points regardless of the image.
|
|
18
|
+
*/
|
|
19
|
+
export declare function imagePointToViewport(point: {
|
|
20
|
+
readonly x: number;
|
|
21
|
+
readonly y: number;
|
|
22
|
+
}, pixels: Pick<ExecutorPixels, 'width' | 'height' | 'scale'>, viewport: {
|
|
23
|
+
readonly width: number;
|
|
24
|
+
readonly height: number;
|
|
25
|
+
}): ViewportPoint;
|
|
26
|
+
export interface HitTest {
|
|
27
|
+
/** The innermost enabled control whose box contains the point, if any. */
|
|
28
|
+
readonly control: SemanticNode | undefined;
|
|
29
|
+
/** The innermost listed node of any role whose box contains the point, if any. */
|
|
30
|
+
readonly under: SemanticNode | undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Finds what the newest observation lists at a viewport point. Innermost
|
|
34
|
+
* wins (deepest in the tree, then the smallest box); hidden nodes and nodes
|
|
35
|
+
* without a box are skipped. Every box is in the top-level viewport's CSS
|
|
36
|
+
* pixels, nested documents included (`SemanticNode.rect`), so a control
|
|
37
|
+
* inside an iframe is found like any other. Only an enabled interactive
|
|
38
|
+
* role counts as the control: tapping a node taps its center, which is what
|
|
39
|
+
* the caller asked for only when the node is the control itself, not a
|
|
40
|
+
* paragraph or region that happens to contain the point.
|
|
41
|
+
*/
|
|
42
|
+
export declare function hitTest(observation: AgentObservation, point: ViewportPoint): HitTest;
|
|
43
|
+
/** The observation's own line for a node, as the model already reads it; the id alone when the line was cut. */
|
|
44
|
+
export declare function nodeLine(observation: Pick<AgentObservation, 'text'>, id: string): string;
|
|
45
|
+
/** What one point tap did, for the model that named the point. */
|
|
46
|
+
export declare function describePointTap(input: {
|
|
47
|
+
readonly point: ViewportPoint;
|
|
48
|
+
readonly control: SemanticNode | undefined;
|
|
49
|
+
readonly under: SemanticNode | undefined;
|
|
50
|
+
readonly observation: Pick<AgentObservation, 'text'>;
|
|
51
|
+
}): string;
|
|
52
|
+
//# sourceMappingURL=point-tap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"point-tap.d.ts","sourceRoot":"","sources":["../../src/agent/point-tap.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAExE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAE5E;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE;IAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACjD,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,EAC1D,QAAQ,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5D,aAAa,CASf;AAED,MAAM,WAAW,OAAO;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,CAAC;IAC3C,kFAAkF;IAClF,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC;CAC1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,WAAW,EAAE,gBAAgB,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAgBpF;AAiBD,gHAAgH;AAChH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAMxF;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;CACtD,GAAG,MAAM,CAOT"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Point taps: how a viewport point an executor names is routed onto the
|
|
3
|
+
* tree, and how the result reads back. A point that a listed, enabled
|
|
4
|
+
* control contains is tapped by that control's id, so policy, stale
|
|
5
|
+
* relocation, and the trace cache see an ordinary tap; a point on nothing
|
|
6
|
+
* listed goes to the engine as a bare point. Coordinates read off a
|
|
7
|
+
* screenshot are scaled into the observation's CSS pixels first.
|
|
8
|
+
*/
|
|
9
|
+
import { clamp, clampToViewport } from '../internal/geometry.js';
|
|
10
|
+
import { INTERACTIVE_ROLES } from './observation.js';
|
|
11
|
+
/**
|
|
12
|
+
* Scales a point read off a screenshot into the observation's CSS pixels and
|
|
13
|
+
* clamps it to the viewport. `pixels.scale` is image pixels per CSS pixel
|
|
14
|
+
* (3 on a device screenshot, 1 on a CSS-scale capture); the observation's
|
|
15
|
+
* own `viewport.scale` is not consulted, because a device engine reports its
|
|
16
|
+
* geometry in logical points regardless of the image.
|
|
17
|
+
*/
|
|
18
|
+
export function imagePointToViewport(point, pixels, viewport) {
|
|
19
|
+
const scale = pixels.scale > 0 ? pixels.scale : 1;
|
|
20
|
+
return clampToViewport({
|
|
21
|
+
x: clamp(point.x, 0, Math.max(0, pixels.width - 1)) / scale,
|
|
22
|
+
y: clamp(point.y, 0, Math.max(0, pixels.height - 1)) / scale,
|
|
23
|
+
}, viewport);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Finds what the newest observation lists at a viewport point. Innermost
|
|
27
|
+
* wins (deepest in the tree, then the smallest box); hidden nodes and nodes
|
|
28
|
+
* without a box are skipped. Every box is in the top-level viewport's CSS
|
|
29
|
+
* pixels, nested documents included (`SemanticNode.rect`), so a control
|
|
30
|
+
* inside an iframe is found like any other. Only an enabled interactive
|
|
31
|
+
* role counts as the control: tapping a node taps its center, which is what
|
|
32
|
+
* the caller asked for only when the node is the control itself, not a
|
|
33
|
+
* paragraph or region that happens to contain the point.
|
|
34
|
+
*/
|
|
35
|
+
export function hitTest(observation, point) {
|
|
36
|
+
let control;
|
|
37
|
+
let under;
|
|
38
|
+
for (const node of observation.nodes.values()) {
|
|
39
|
+
const rect = node.rect;
|
|
40
|
+
if (rect === undefined || rect.width <= 0 || rect.height <= 0)
|
|
41
|
+
continue;
|
|
42
|
+
if (node.states?.hidden === true)
|
|
43
|
+
continue;
|
|
44
|
+
if (point.x < rect.x || point.x >= rect.x + rect.width)
|
|
45
|
+
continue;
|
|
46
|
+
if (point.y < rect.y || point.y >= rect.y + rect.height)
|
|
47
|
+
continue;
|
|
48
|
+
const candidate = { node, depth: depthOf(node.ref.id, observation.parents), area: rect.width * rect.height };
|
|
49
|
+
if (inner(candidate, under))
|
|
50
|
+
under = candidate;
|
|
51
|
+
if (INTERACTIVE_ROLES.has(node.role ?? '') && node.states?.disabled !== true && inner(candidate, control)) {
|
|
52
|
+
control = candidate;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { control: control?.node, under: under?.node };
|
|
56
|
+
}
|
|
57
|
+
function inner(candidate, best) {
|
|
58
|
+
if (best === undefined)
|
|
59
|
+
return true;
|
|
60
|
+
if (candidate.depth !== best.depth)
|
|
61
|
+
return candidate.depth > best.depth;
|
|
62
|
+
return candidate.area < best.area;
|
|
63
|
+
}
|
|
64
|
+
function depthOf(id, parents) {
|
|
65
|
+
let depth = 0;
|
|
66
|
+
for (let cursor = parents.get(id); cursor !== undefined; cursor = parents.get(cursor))
|
|
67
|
+
depth += 1;
|
|
68
|
+
return depth;
|
|
69
|
+
}
|
|
70
|
+
/** The observation's own line for a node, as the model already reads it; the id alone when the line was cut. */
|
|
71
|
+
export function nodeLine(observation, id) {
|
|
72
|
+
for (const line of observation.text.split('\n')) {
|
|
73
|
+
const trimmed = line.trimStart();
|
|
74
|
+
if (trimmed.startsWith(`#${id} `) || trimmed === `#${id}`)
|
|
75
|
+
return trimmed;
|
|
76
|
+
}
|
|
77
|
+
return `#${id}`;
|
|
78
|
+
}
|
|
79
|
+
/** What one point tap did, for the model that named the point. */
|
|
80
|
+
export function describePointTap(input) {
|
|
81
|
+
const at = `(${String(input.point.x)}, ${String(input.point.y)})`;
|
|
82
|
+
if (input.control !== undefined) {
|
|
83
|
+
return `Tapped ${nodeLine(input.observation, input.control.ref.id)}, the control at ${at}.`;
|
|
84
|
+
}
|
|
85
|
+
const under = input.under === undefined ? '' : ` (under it: ${nodeLine(input.observation, input.under.ref.id)})`;
|
|
86
|
+
return `Tapped the point ${at}; no listed control is there${under}.`;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=point-tap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"point-tap.js","sourceRoot":"","sources":["../../src/agent/point-tap.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,7 +35,7 @@ export interface GrammarToolOptions {
|
|
|
35
35
|
* (budget, timeout, cancel) propagates out of the tool; the chassis's guard
|
|
36
36
|
* instead turns it into text and ends the loop.
|
|
37
37
|
*/
|
|
38
|
-
readonly guard?: (body: () => Promise<
|
|
38
|
+
readonly guard?: <T>(body: () => Promise<T>) => Promise<T | string>;
|
|
39
39
|
/**
|
|
40
40
|
* Renders screens for the model and remembers what it has seen, so every
|
|
41
41
|
* result after the first reports the changes rather than the whole tree.
|
|
@@ -44,6 +44,12 @@ export interface GrammarToolOptions {
|
|
|
44
44
|
*/
|
|
45
45
|
readonly screen?: ScreenPresenter;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Every name `createGrammarTools` may hand out. The grammar owns these in the
|
|
49
|
+
* model's vocabulary whatever the engine declares, so a project tool cannot
|
|
50
|
+
* take one: it would be silently shadowed on one engine and live on another.
|
|
51
|
+
*/
|
|
52
|
+
export declare const GRAMMAR_TOOL_NAMES: ReadonlySet<string>;
|
|
47
53
|
/**
|
|
48
54
|
* AI SDK tools over the harness action grammar, limited to the verbs the
|
|
49
55
|
* target's engine declared. A verb the surface cannot honor is not offered
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../src/agent/primitives.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAGpD,OAAO,EAAsC,KAAK,mBAAmB,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../src/agent/primitives.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAGpD,OAAO,EAAsC,KAAK,mBAAmB,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAI/G,OAAO,EAAE,eAAe,EAAqB,MAAM,oBAAoB,CAAC;AA4CxE,wEAAwE;AACxE,eAAO,MAAM,aAAa,umBAG4N,CAAC;AAqDvP,wDAAwD;AACxD,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,uEAAuE;IACvE,OAAO,IAAI,WAAW,GAAG,SAAS,CAAC;IACnC,yEAAyE;IACzE,SAAS,IAAI,OAAO,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,CA4C/C;AAED,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;IACpE;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;CACnC;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAWjD,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,mBAAmB,EAC5B,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAsLT;AAED,+EAA+E;AAC/E,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,WAAW,IAAI,IAAI,CAAC;IACpB;;;;OAIG;IACH,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAAC,GAAG,IAAI,CAAC;CAChF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,mBAAmB,EAC5B,KAAK,CAAC,EAAE;IAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAChE,gBAAgB,CAoBlB"}
|
package/dist/agent/primitives.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import { z } from 'zod';
|
|
12
12
|
import { isRuntimeHardStop, BLOCKABLE_CODES } from './executor.js';
|
|
13
13
|
import { cacheTokenFields, readCost } from './model/sdk.js';
|
|
14
|
+
import { OperationQueue } from './operation-queue.js';
|
|
15
|
+
import { imagePointToViewport } from './point-tap.js';
|
|
14
16
|
import { ScreenPresenter } from './screen-update.js';
|
|
15
17
|
/** Codes the model may pick when concluding; runtime codes are runtime-assigned. */
|
|
16
18
|
const MODEL_ERROR_CODES = [
|
|
@@ -20,6 +22,7 @@ const MODEL_ERROR_CODES = [
|
|
|
20
22
|
'AUTHENTICATION_FAILED',
|
|
21
23
|
'AUTH_CREDENTIAL_UNAVAILABLE',
|
|
22
24
|
'AUTH_CREDENTIAL_INVALID',
|
|
25
|
+
'SECRET_UNAVAILABLE',
|
|
23
26
|
'ENVIRONMENT_UNAVAILABLE',
|
|
24
27
|
'SEED_DATA_MISSING',
|
|
25
28
|
'TEST_SETUP_FAILED',
|
|
@@ -59,6 +62,30 @@ export const VERDICT_RULES = `Verdict rules:
|
|
|
59
62
|
function schemaTool(definition) {
|
|
60
63
|
return definition;
|
|
61
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* A grammar tool: its result is a rendered screen, text or text with the
|
|
67
|
+
* screenshot attached as a file part, so every one encodes its output the
|
|
68
|
+
* same way. A tool that skipped the encoder would hand the model the image
|
|
69
|
+
* bytes as JSON the first time an action ran in pixel mode.
|
|
70
|
+
*/
|
|
71
|
+
function screenTool(definition) {
|
|
72
|
+
return schemaTool({ ...definition, toModelOutput: screenModelOutput });
|
|
73
|
+
}
|
|
74
|
+
function screenModelOutput({ output }) {
|
|
75
|
+
if (typeof output === 'string')
|
|
76
|
+
return { type: 'text', value: output };
|
|
77
|
+
return {
|
|
78
|
+
type: 'content',
|
|
79
|
+
value: [
|
|
80
|
+
{ type: 'text', text: output.text },
|
|
81
|
+
{
|
|
82
|
+
type: 'file',
|
|
83
|
+
data: { type: 'data', data: Buffer.from(output.pixels.data).toString('base64') },
|
|
84
|
+
mediaType: output.pixels.mediaType,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
62
89
|
/**
|
|
63
90
|
* The `complete_step` verdict tool. A blocked verdict without a blockable
|
|
64
91
|
* code is rejected back to the model, a passed verdict never carries a code,
|
|
@@ -103,6 +130,23 @@ export function createVerdictTool() {
|
|
|
103
130
|
concluded: () => verdict !== undefined,
|
|
104
131
|
};
|
|
105
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Every name `createGrammarTools` may hand out. The grammar owns these in the
|
|
135
|
+
* model's vocabulary whatever the engine declares, so a project tool cannot
|
|
136
|
+
* take one: it would be silently shadowed on one engine and live on another.
|
|
137
|
+
*/
|
|
138
|
+
export const GRAMMAR_TOOL_NAMES = new Set([
|
|
139
|
+
'observe',
|
|
140
|
+
'tap',
|
|
141
|
+
'type',
|
|
142
|
+
'type_secret',
|
|
143
|
+
'press',
|
|
144
|
+
'select',
|
|
145
|
+
'scroll',
|
|
146
|
+
'navigate',
|
|
147
|
+
'screenshot',
|
|
148
|
+
'tap_at',
|
|
149
|
+
]);
|
|
106
150
|
/**
|
|
107
151
|
* AI SDK tools over the harness action grammar, limited to the verbs the
|
|
108
152
|
* target's engine declared. A verb the surface cannot honor is not offered
|
|
@@ -117,71 +161,71 @@ export function createGrammarTools(context, options = {}) {
|
|
|
117
161
|
const guard = options.guard ?? ((body) => body());
|
|
118
162
|
const screen = options.screen ?? new ScreenPresenter();
|
|
119
163
|
const { verbs } = context.target;
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
};
|
|
164
|
+
// One queue for the tool bodies: an action and its look at the result run
|
|
165
|
+
// together, so a batched turn gets one coherent result per action.
|
|
166
|
+
const queue = new OperationQueue();
|
|
167
|
+
const inOrder = (body) => queue.run(body);
|
|
168
|
+
/**
|
|
169
|
+
* The screen after an action, as the model reads it: the changes since the
|
|
170
|
+
* screen it holds and, once the step is showing pixels, a fresh screenshot.
|
|
171
|
+
*/
|
|
172
|
+
const present = async (lead, expectChange) => screen.present(await context.observe({ pixels: screen.showingPixels }), { lead, expectChange });
|
|
129
173
|
/**
|
|
130
174
|
* Performs one action and reads its result. A failed action still returns
|
|
131
175
|
* the screen, so the model can act on a stale-id or not-found failure at
|
|
132
176
|
* once instead of spending a turn to observe; runtime hard stops propagate
|
|
133
177
|
* to the guard, which ends the loop. The guard runs inside the queue, so a
|
|
134
178
|
* batched call that queued behind a hard stop or a verdict is skipped when
|
|
135
|
-
* its turn comes rather than acted on because it was queued in time.
|
|
179
|
+
* its turn comes rather than acted on because it was queued in time. The
|
|
180
|
+
* action may return its own lead line, for a result only it can describe.
|
|
136
181
|
*/
|
|
137
182
|
const acting = (description, action, expectChange = true) => inOrder(() => guard(async () => {
|
|
183
|
+
let lead = description;
|
|
138
184
|
try {
|
|
139
|
-
await action();
|
|
185
|
+
lead = (await action()) ?? description;
|
|
140
186
|
}
|
|
141
187
|
catch (cause) {
|
|
142
188
|
if (isRuntimeHardStop(cause))
|
|
143
189
|
throw cause;
|
|
144
190
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
145
|
-
|
|
146
|
-
return screen.update(observation, { lead: `${description} failed: ${message}` });
|
|
191
|
+
return present(`${description} failed: ${message}`);
|
|
147
192
|
}
|
|
148
|
-
|
|
149
|
-
return screen.update(observation, { lead: description, expectChange });
|
|
193
|
+
return present(lead, expectChange);
|
|
150
194
|
}));
|
|
151
195
|
const target = z
|
|
152
196
|
.string()
|
|
153
197
|
.min(1)
|
|
154
198
|
.describe('Node id from any screen in this conversation that is still present, e.g. "n42"');
|
|
155
199
|
const tools = {
|
|
156
|
-
observe:
|
|
200
|
+
observe: screenTool({
|
|
157
201
|
description: 'Look at the screen again and get what changed since the screen you last received. Action results already include their changes, so call this only after waiting for something in progress, never right after an action.',
|
|
158
202
|
inputSchema: z.object({}),
|
|
159
|
-
execute: () => inOrder(() => guard(
|
|
203
|
+
execute: () => inOrder(() => guard(() => present('Observed.', false))),
|
|
160
204
|
}),
|
|
161
205
|
};
|
|
162
206
|
if (verbs.has('tap')) {
|
|
163
|
-
tools['tap'] =
|
|
207
|
+
tools['tap'] = screenTool({
|
|
164
208
|
description: 'Tap or click one node. The result waits for the effect (a navigation, a route change, a submit) and reports what changed.',
|
|
165
209
|
inputSchema: z.object({ target }),
|
|
166
210
|
execute: ({ target: id }) => acting(`Tapped #${id}.`, () => context.actions.tap({ id })),
|
|
167
211
|
});
|
|
168
212
|
}
|
|
169
213
|
if (verbs.has('type')) {
|
|
170
|
-
tools['type'] =
|
|
214
|
+
tools['type'] = screenTool({
|
|
171
215
|
description: 'Type a plain-text value into one input node, replacing its current value. Several fields can be typed in one turn.',
|
|
172
216
|
inputSchema: z.object({ target, value: z.string() }),
|
|
173
217
|
execute: ({ target: id, value }) => acting(`Typed into #${id}.`, () => context.actions.type({ id }, value)),
|
|
174
218
|
});
|
|
175
219
|
}
|
|
176
220
|
if (verbs.has('press')) {
|
|
177
|
-
tools['press'] =
|
|
221
|
+
tools['press'] = screenTool({
|
|
178
222
|
description: 'Send one key (e.g. "Enter", "Escape", "Tab") to one node.',
|
|
179
223
|
inputSchema: z.object({ target, key: z.string().min(1).max(64) }),
|
|
180
224
|
execute: ({ target: id, key }) => acting(`Pressed ${key} on #${id}.`, () => context.actions.press({ id }, key), !movesFocusOnly(key)),
|
|
181
225
|
});
|
|
182
226
|
}
|
|
183
227
|
if (verbs.has('select')) {
|
|
184
|
-
tools['select'] =
|
|
228
|
+
tools['select'] = screenTool({
|
|
185
229
|
description: 'Pick one option from a select-like control by its visible label.',
|
|
186
230
|
inputSchema: z.object({ target, value: z.string().min(1) }),
|
|
187
231
|
execute: ({ target: id, value }) => acting(`Selected "${value}" in #${id}.`, () => context.actions.select({ id }, value)),
|
|
@@ -208,33 +252,61 @@ export function createGrammarTools(context, options = {}) {
|
|
|
208
252
|
const scrolled = (way, count) => count === 1 ? `Scrolled ${way}.` : `Scrolled ${way} ${String(count)} screens.`;
|
|
209
253
|
// Node-targeted scrolling rides `perform`; without it only the viewport scrolls.
|
|
210
254
|
tools['scroll'] = verbs.has('tap')
|
|
211
|
-
?
|
|
255
|
+
? screenTool({
|
|
212
256
|
description: 'Scroll the viewport, or one scrollable node when target is given. The result reports the rows that came into or left the tree.',
|
|
213
257
|
inputSchema: z.object({ direction, target: target.optional(), times }),
|
|
214
258
|
execute: ({ direction: way, target: id, times: count }) => acting(scrolled(way, count ?? 1), () => scrolling(way, id, count ?? 1), false),
|
|
215
259
|
})
|
|
216
|
-
:
|
|
260
|
+
: screenTool({
|
|
217
261
|
description: 'Scroll the viewport. The result reports the rows that came into or left the tree.',
|
|
218
262
|
inputSchema: z.object({ direction, times }),
|
|
219
263
|
execute: ({ direction: way, times: count }) => acting(scrolled(way, count ?? 1), () => scrolling(way, undefined, count ?? 1), false),
|
|
220
264
|
});
|
|
221
265
|
}
|
|
222
266
|
if (verbs.has('navigate')) {
|
|
223
|
-
tools['navigate'] =
|
|
267
|
+
tools['navigate'] = screenTool({
|
|
224
268
|
description: 'Navigate to a URL or app-relative path within the allowed origins.',
|
|
225
269
|
inputSchema: z.object({ url: z.string().min(1) }),
|
|
226
270
|
execute: ({ url }) => acting(`Navigated to ${url}.`, () => context.actions.navigate(url)),
|
|
227
271
|
});
|
|
228
272
|
}
|
|
273
|
+
// The pixel verbs are offered while pixels can still leave the runner. Once
|
|
274
|
+
// a secret was filled in the attempt they could only decline, and a verb
|
|
275
|
+
// that is absent costs the model nothing where one that declines costs a
|
|
276
|
+
// turn. tap_at lands either as a tap by id or as a bare point, so it needs
|
|
277
|
+
// one of the two; screenshot needs only the observation every step has.
|
|
278
|
+
if (!context.pixelsTainted) {
|
|
279
|
+
tools['screenshot'] = screenTool({
|
|
280
|
+
description: 'Attach a screenshot of the current viewport. Use it when the screen lists too little to act on (a canvas, a map, an image, a game, a system sheet) or contradicts what you expect. From then on every action result carries a fresh screenshot too, so you can see what each action did.',
|
|
281
|
+
inputSchema: z.object({}),
|
|
282
|
+
execute: () => inOrder(() => guard(async () => screen.present(await context.observe({ pixels: true })))),
|
|
283
|
+
});
|
|
284
|
+
if (verbs.has('tap') || verbs.has('tapAt')) {
|
|
285
|
+
tools['tap_at'] = screenTool({
|
|
286
|
+
description: 'Tap a point in the latest screenshot, given as pixel coordinates in that image (x from the left edge, y from the top edge). Aim for the center of the target. A listed control under the point is tapped by its id; otherwise the bare point is tapped' +
|
|
287
|
+
(verbs.has('tapAt') ? '.' : ', which this engine cannot do: the point must land on a listed control.') +
|
|
288
|
+
' Last resort: when the screen lists the target, tap it by id.',
|
|
289
|
+
inputSchema: z.object({ x: z.number(), y: z.number() }),
|
|
290
|
+
execute: ({ x, y }) => {
|
|
291
|
+
const shot = screen.latestScreenshot;
|
|
292
|
+
if (shot === undefined) {
|
|
293
|
+
return Promise.resolve('No screenshot has been taken in this step: tap_at coordinates are pixels of the latest screenshot. Call screenshot first, or tap a listed node by id.');
|
|
294
|
+
}
|
|
295
|
+
const point = imagePointToViewport({ x, y }, shot.pixels, shot.viewport);
|
|
296
|
+
return acting(`tap_at (${String(x)}, ${String(y)})`, async () => (await context.actions.tapAt(point)).summary);
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
229
301
|
// Offered only when the step declared secrets and the surface can fill: an
|
|
230
302
|
// empty vocabulary is better than a tool the model can only be rejected on.
|
|
231
303
|
if (verbs.has('typeSecret') && context.step.secrets.length > 0) {
|
|
232
|
-
tools['type_secret'] =
|
|
233
|
-
description: 'Fill one declared secret
|
|
304
|
+
tools['type_secret'] = screenTool({
|
|
305
|
+
description: 'Fill one declared secret into an input by its name; the plaintext never passes through you. A password fills only a password field; a generic-secret fills any editable input. Available: ' +
|
|
234
306
|
context.step.secrets.map((secret) => `"${secret.name}" (${secret.purpose})`).join(', ') +
|
|
235
307
|
'.',
|
|
236
308
|
inputSchema: z.object({ target, name: z.string().min(1) }),
|
|
237
|
-
execute: ({ target: id, name }) => acting(`Filled secret "${name}" into #${id};
|
|
309
|
+
execute: ({ target: id, name }) => acting(`Filled secret "${name}" into #${id}; its value is masked in every observation.`, () => context.actions.typeSecret({ id }, name), false),
|
|
238
310
|
});
|
|
239
311
|
}
|
|
240
312
|
return tools;
|