@dynamicagents/plugins 0.17.0 → 0.18.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 +2 -2
- package/dist/browser/index.d.ts +13 -16
- package/dist/browser/index.js +88 -26
- package/dist/claude-code/verified.d.ts +1 -1
- package/dist/claude-code/verified.js +1 -1
- package/dist/repo-github/sandbox-egress.d.ts +5 -1
- package/dist/repo-github/sandbox-egress.js +9 -3
- package/package.json +12 -10
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ export class Coder extends StepAgent<Env> {
|
|
|
38
38
|
|
|
39
39
|
override getPlugins() {
|
|
40
40
|
return [
|
|
41
|
-
browser({ binding: this.env.BROWSER }),
|
|
41
|
+
browser({ binding: this.env.BROWSER, loader: this.env.LOADER }),
|
|
42
42
|
computer({ shell: "bash" }),
|
|
43
43
|
repo(repoConfig(this.env))
|
|
44
44
|
];
|
|
@@ -66,7 +66,7 @@ plugin to the tools an install names.
|
|
|
66
66
|
|
|
67
67
|
| Subpath | What it adds | Needs |
|
|
68
68
|
| ---------------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
|
|
69
|
-
| [`/browser`](src/browser/) |
|
|
69
|
+
| [`/browser`](src/browser/) | Drive a persistent browser by writing code, over Browser Run | `BROWSER` (paid plan), `LOADER` |
|
|
70
70
|
| [`/claude-code`](src/claude-code/) | Sub-agents whose model is a Claude Code session in a sandbox | one or more `claude setup-token` credentials |
|
|
71
71
|
| [`/computer`](src/computer/) | An agent's tools over a sandbox — `exec`, `grep`, `edit` — and the workspace Think's file tools use | a `/sandbox` object |
|
|
72
72
|
| [`/repo-github`](src/repo-github/) | Clone, commit, push a branch, open a pull request on GitHub — over any container | `GITHUB_TOKEN` |
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -1,28 +1,25 @@
|
|
|
1
1
|
import type { AgentPlugin } from "@dynamicagents/core";
|
|
2
|
-
import type {
|
|
3
|
-
import type { QuickActionBinding, QuickActionCommonOptions } from "agents/browser";
|
|
2
|
+
import type { BrowserBinding, BrowserRunOptions } from "agents/browser";
|
|
4
3
|
export interface BrowserConfig {
|
|
5
4
|
/**
|
|
6
5
|
* The `BROWSER` binding. Browser Rendering is a **paid-plan** feature, and
|
|
7
6
|
* `wrangler dev` needs `remote: true` to reach it.
|
|
8
7
|
*/
|
|
9
|
-
binding:
|
|
8
|
+
binding: BrowserBinding;
|
|
10
9
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* The `LOADER` binding, declared as `"worker_loaders": [{ "binding":
|
|
11
|
+
* "LOADER" }]`. Each call gets a fresh Dynamic Worker to run the model's
|
|
12
|
+
* code in, with no network access of its own.
|
|
14
13
|
*/
|
|
15
|
-
|
|
14
|
+
loader: WorkerLoader;
|
|
15
|
+
/** How long one call may take, in milliseconds. The SDK defaults to 60,000. */
|
|
16
|
+
timeoutMs?: number;
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
18
|
+
* Browser Run launch options: how long an idle browser lives, hostname
|
|
19
|
+
* guardrails, session recording, internal hostnames. Fixed when the browser
|
|
20
|
+
* starts rather than per call, and host-supplied, so the model never sees or
|
|
21
|
+
* chooses any of it.
|
|
19
22
|
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Browser Rendering options merged into every request — cookies, auth headers,
|
|
23
|
-
* viewport. Host-supplied and never exposed to the model, which only ever
|
|
24
|
-
* chooses the page and the action-specific fields.
|
|
25
|
-
*/
|
|
26
|
-
options?: QuickActionCommonOptions;
|
|
23
|
+
provider?: BrowserRunOptions;
|
|
27
24
|
}
|
|
28
25
|
export declare function browser(config: BrowserConfig): AgentPlugin;
|
package/dist/browser/index.js
CHANGED
|
@@ -1,42 +1,104 @@
|
|
|
1
1
|
import { definePlugin } from "@dynamicagents/core";
|
|
2
|
-
import {
|
|
2
|
+
import { Browser, DurableBrowserSessionStore, browserRun } from "agents/browser";
|
|
3
|
+
import { browserTool } from "agents/browser/ai-sdk";
|
|
3
4
|
/**
|
|
4
|
-
* `@dynamicagents/plugins/browser` —
|
|
5
|
-
*
|
|
5
|
+
* `@dynamicagents/plugins/browser` — drive a real browser, via Cloudflare
|
|
6
|
+
* Browser Run.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* One tool, `browser`: the model writes JavaScript that drives a persistent
|
|
9
|
+
* browser over CDP, and that code runs in a Dynamic Worker with no network of
|
|
10
|
+
* its own. The browser outlives each call, so tabs, cookies and logins carry
|
|
11
|
+
* over between them, and the model never starts, closes or resets it.
|
|
11
12
|
*
|
|
12
|
-
* The binding
|
|
13
|
+
* The binding and the loader are closed over at instantiation, so neither is
|
|
14
|
+
* ever model input.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The browser's name in its own store. Two `Browser` objects on one Durable
|
|
18
|
+
* Object need different names, so this says which one is the plugin's rather
|
|
19
|
+
* than taking the SDK's default and colliding with a host that also built one.
|
|
20
|
+
*/
|
|
21
|
+
const BROWSER_NAME = "browser";
|
|
22
|
+
/**
|
|
23
|
+
* What the model is told about the domain. Deliberately *not* the `browser.*`
|
|
24
|
+
* function reference: `browserTool` puts that in the tool's own description and
|
|
25
|
+
* keeps it in step with the SDK, and a copy here would be a second home for
|
|
26
|
+
* someone else's text. What is left is what this plugin knows and the SDK
|
|
27
|
+
* cannot: when reaching for a browser is the right move at all.
|
|
13
28
|
*/
|
|
14
|
-
/** How many characters of a page result the model is allowed to see. */
|
|
15
|
-
const DEFAULT_MAX_CHARS = 20_000;
|
|
16
29
|
const CONTEXT = [
|
|
17
|
-
"You can
|
|
18
|
-
"-
|
|
19
|
-
"-
|
|
20
|
-
"-
|
|
21
|
-
"-
|
|
22
|
-
"
|
|
30
|
+
"You can drive a real browser by writing JavaScript, with the `browser` tool. The tool's own description carries the API; what matters in choosing it:",
|
|
31
|
+
"- The browser is **persistent**. A later call lands on the same page, logged in, with the same cookies. Navigate in one call and read in the next rather than rebuilding state each time.",
|
|
32
|
+
"- Because it is persistent, read the page before acting on it. A call that submits a form or clicks through a checkout may be a repeat of one that already succeeded, and the page in front of you is the evidence of which.",
|
|
33
|
+
"- Prefer one call that does several steps over several calls that each do one: every call pays a round trip to the browser, and the page is still there either way.",
|
|
34
|
+
"- Return the part of the page you need rather than its whole text or markup. The tool's rules say where the ceiling is.",
|
|
35
|
+
"- A plain HTTP fetch is cheaper than a browser. Reach for this when a page needs rendering, a session, or interaction."
|
|
23
36
|
].join("\n");
|
|
24
37
|
export function browser(config) {
|
|
25
|
-
const { binding,
|
|
38
|
+
const { binding, loader, timeoutMs, provider } = config;
|
|
39
|
+
/**
|
|
40
|
+
* One `Browser` per installing object, and the lock is why.
|
|
41
|
+
*
|
|
42
|
+
* `browserTool` opens every run as a locking session, so runs in one scope
|
|
43
|
+
* wait for each other rather than driving one tab at once. But the SDK keeps
|
|
44
|
+
* its lock queues in a `WeakMap` keyed by the **`Browser` object itself**, so
|
|
45
|
+
* two instances over the same stored session hold two unrelated locks and
|
|
46
|
+
* serialise nothing. `getTools()` runs per turn, so a fresh `Browser` each
|
|
47
|
+
* time would leave overlapping turns in one object free to navigate each
|
|
48
|
+
* other's page.
|
|
49
|
+
*
|
|
50
|
+
* Keyed on the installing object's storage, which is the same granularity the
|
|
51
|
+
* SDK's locks have ("per isolate, which is per Durable Object"), and weak so
|
|
52
|
+
* an evicted object's browser handle goes with it. A `WeakMap` rather than one
|
|
53
|
+
* slot because nothing stops a host from hoisting this plugin to module scope
|
|
54
|
+
* and sharing it between objects in one isolate.
|
|
55
|
+
*/
|
|
56
|
+
const browsers = new WeakMap();
|
|
57
|
+
const browserFor = (storage) => {
|
|
58
|
+
const known = browsers.get(storage);
|
|
59
|
+
if (known !== undefined)
|
|
60
|
+
return known;
|
|
61
|
+
const made = new Browser({
|
|
62
|
+
name: BROWSER_NAME,
|
|
63
|
+
provider: browserRun(binding, provider),
|
|
64
|
+
// **The store is what keeps this out of the host's Lifecycle.** A
|
|
65
|
+
// `Browser` with no store defaults to one over the host object's storage
|
|
66
|
+
// and requires `Lifecycle.use()` to install it, which a plugin has no way
|
|
67
|
+
// to ask for: the contract offers tools, actions, context and `requires`
|
|
68
|
+
// and nothing else. Passing the store explicitly is the SDK's own answer,
|
|
69
|
+
// and it is what lets this capability stay whole inside the plugin.
|
|
70
|
+
store: new DurableBrowserSessionStore(storage)
|
|
71
|
+
});
|
|
72
|
+
browsers.set(storage, made);
|
|
73
|
+
return made;
|
|
74
|
+
};
|
|
26
75
|
return definePlugin({
|
|
27
76
|
name: "browser",
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
77
|
+
// **No `scope`.** A scope separates callers sharing one browser, and there
|
|
78
|
+
// is only ever one here: the record lives in the installing object's own
|
|
79
|
+
// storage, a parent object is keyed by its caller, and a sub-agent runs in
|
|
80
|
+
// its own facet with storage of its own. Naming a scope would imply an
|
|
81
|
+
// isolation the store already provides, and the default one still locks.
|
|
82
|
+
//
|
|
83
|
+
// **Nothing truncates here**, and the tool is handed over as the SDK built
|
|
84
|
+
// it. `browserTool` bounds its own output in two places: `execute` cuts the
|
|
85
|
+
// result and the error *structurally*, leaving valid JSON of the original
|
|
86
|
+
// shape with the largest values cut first and markers where content went,
|
|
87
|
+
// and `toModelOutput` then redacts base64 payloads, renders a screenshot as
|
|
88
|
+
// a note, and bounds the logs the same way. Wrapping either to add a flat
|
|
89
|
+
// ceiling of our own would collapse that JSON into a string, which is
|
|
90
|
+
// strictly worse than the bound it would be enforcing.
|
|
91
|
+
tools: (ctx) => ({
|
|
92
|
+
browser: browserTool({
|
|
93
|
+
browser: browserFor(ctx.storage),
|
|
94
|
+
loader,
|
|
95
|
+
...(timeoutMs === undefined ? {} : { timeoutMs })
|
|
96
|
+
})
|
|
35
97
|
}),
|
|
36
98
|
context: [{ provider: { get: async () => CONTEXT } }],
|
|
37
|
-
// A plugin cannot add its own wrangler binding, so declaring
|
|
99
|
+
// A plugin cannot add its own wrangler binding, so declaring them turns a
|
|
38
100
|
// missing one into a startup error naming this plugin, rather than a failed
|
|
39
101
|
// tool call inside a request someone is waiting on.
|
|
40
|
-
requires: { bindings: ["BROWSER"] }
|
|
102
|
+
requires: { bindings: ["BROWSER", "LOADER"] }
|
|
41
103
|
});
|
|
42
104
|
}
|
|
@@ -39,7 +39,11 @@ export interface GitHubSandboxEgressOptions {
|
|
|
39
39
|
tokenBinding: string;
|
|
40
40
|
/** Absent: any repository and any work branch the token reaches. */
|
|
41
41
|
push?: GitPushPolicy;
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Intercept `api.github.com` as well, so `gh` works as the deployment's
|
|
44
|
+
* account. Nothing here reads those requests, so GitHub alone bounds them:
|
|
45
|
+
* `./README.md`, under "Protect the repositories", says how to set that up.
|
|
46
|
+
*/
|
|
43
47
|
api?: boolean;
|
|
44
48
|
}
|
|
45
49
|
/** The egress rule of a sandbox whose processes use GitHub. */
|
|
@@ -74,9 +74,15 @@ async function githubEgress(call, options) {
|
|
|
74
74
|
return githubError("no GitHub token is configured for this deployment, so nothing can be done as its account", 401);
|
|
75
75
|
}
|
|
76
76
|
const policy = options.push;
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
const onGitHost = new URL(call.request.url).hostname === GIT_HOST;
|
|
78
|
+
const git = onGitHost ? gitTarget(call.target.pathname) : undefined;
|
|
79
|
+
// GitHub serves spellings of a git path that `gitTarget` does not read, and a
|
|
80
|
+
// push it cannot read is one it cannot hold to the rules below, so what it
|
|
81
|
+
// does not recognise never gets the token. Git LFS is on such a path: a
|
|
82
|
+
// sandbox that needs it has to have it recognised first.
|
|
83
|
+
if (onGitHost && !git) {
|
|
84
|
+
return githubError(`this sandbox may use ${GIT_HOST} as the deployment's account for git alone, and ${call.target.pathname} is not a path git uses`, 403);
|
|
85
|
+
}
|
|
80
86
|
let body;
|
|
81
87
|
if (git) {
|
|
82
88
|
if (policy &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamicagents/plugins",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Optional, composable capabilities for a Dynamic Agent: browser rendering, a Linux container, git and pull requests on GitHub, a scratchpad, and Claude Code sessions. One subpath per plugin, so a bundle grows only with what it imports.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"test": "vitest run",
|
|
76
76
|
"test:watch": "vitest",
|
|
77
77
|
"verify:exports": "node scripts/verify-exports.mjs",
|
|
78
|
-
"verify:references": "
|
|
78
|
+
"verify:references": "da-verify-references",
|
|
79
79
|
"verify:peer-ranges": "node scripts/verify-peer-ranges.mjs",
|
|
80
80
|
"probe:claude-code": "npm run build && node scripts/probe-claude-code.mjs",
|
|
81
81
|
"probe:github-mcp": "node scripts/probe-github-mcp.mjs",
|
|
@@ -84,12 +84,13 @@
|
|
|
84
84
|
"prepare": "husky || true && npm run build"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
|
-
"@cloudflare/computer": "^0.
|
|
87
|
+
"@cloudflare/computer": "^0.8.0",
|
|
88
88
|
"@cloudflare/sandbox": "^1.0.1",
|
|
89
|
-
"@cloudflare/think": ">=0.
|
|
90
|
-
"@dynamicagents/core": ">=0.
|
|
91
|
-
"agents": ">=0.
|
|
89
|
+
"@cloudflare/think": ">=0.23.0 <0.24.0",
|
|
90
|
+
"@dynamicagents/core": ">=0.18.0 <0.19.0",
|
|
91
|
+
"agents": ">=0.33.0 <0.34.0",
|
|
92
92
|
"ai": "^7.0.88",
|
|
93
|
+
"just-bash": "^3.0.1",
|
|
93
94
|
"zod": "^4.4.3"
|
|
94
95
|
},
|
|
95
96
|
"peerDependenciesMeta": {
|
|
@@ -98,16 +99,17 @@
|
|
|
98
99
|
}
|
|
99
100
|
},
|
|
100
101
|
"devDependencies": {
|
|
101
|
-
"@cloudflare/computer": "^0.
|
|
102
|
+
"@cloudflare/computer": "^0.8.0",
|
|
102
103
|
"@cloudflare/sandbox": "^1.0.1",
|
|
103
|
-
"@cloudflare/think": "^0.
|
|
104
|
+
"@cloudflare/think": "^0.23.0",
|
|
104
105
|
"@cloudflare/vitest-plugin": "^1.4.4",
|
|
105
|
-
"@dynamicagents/core": "^0.
|
|
106
|
+
"@dynamicagents/core": "^0.18.0",
|
|
106
107
|
"@types/node": "^26.5.1",
|
|
107
|
-
"agents": "^0.
|
|
108
|
+
"agents": "^0.33.0",
|
|
108
109
|
"ai": "^7.0.101",
|
|
109
110
|
"eslint": "^10.10.0",
|
|
110
111
|
"husky": "^9.1.7",
|
|
112
|
+
"just-bash": "^3.0.1",
|
|
111
113
|
"prettier": "^3.9.6",
|
|
112
114
|
"semver": "^7.8.5",
|
|
113
115
|
"typescript": "^6.0.3",
|