@dynamicagents/plugins 0.16.1 → 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 +3 -3
- package/dist/browser/index.d.ts +13 -16
- package/dist/browser/index.js +88 -26
- package/dist/claude-code/cards.d.ts +6 -1
- package/dist/claude-code/cards.js +21 -2
- package/dist/claude-code/model.d.ts +4 -0
- package/dist/claude-code/model.js +74 -7
- package/dist/claude-code/verified.d.ts +1 -1
- package/dist/claude-code/verified.js +1 -1
- package/dist/computer/client.d.ts +9 -1
- package/dist/computer/context.js +1 -1
- package/dist/computer/index.d.ts +10 -6
- package/dist/computer/index.js +34 -17
- package/dist/computer/paths.d.ts +26 -2
- package/dist/computer/paths.js +51 -5
- package/dist/computer/proxy.d.ts +10 -3
- package/dist/computer/proxy.js +77 -87
- package/dist/computer/render.d.ts +6 -35
- package/dist/computer/render.js +5 -43
- package/dist/computer/sandbox.d.ts +4 -3
- package/dist/computer/sandbox.js +9 -2
- package/dist/computer/shell.d.ts +8 -7
- package/dist/computer/shell.js +8 -7
- package/dist/computer/tools-exec.d.ts +39 -0
- package/dist/computer/tools-exec.js +357 -228
- package/dist/computer/tools-grep.js +10 -4
- package/dist/repo-github/sandbox-egress.d.ts +5 -1
- package/dist/repo-github/sandbox-egress.js +9 -3
- package/dist/sandbox/header.d.ts +13 -0
- package/dist/sandbox/header.js +19 -4
- package/dist/sandbox/install-plan.d.ts +1 -1
- package/dist/sandbox/install-plan.js +2 -4
- package/dist/sandbox/object.d.ts +1 -0
- package/dist/sandbox/object.js +21 -13
- package/package.json +12 -8
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { tool } from "ai";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
3
|
+
import { SANDBOX_ROOT } from "../sandbox/header.js";
|
|
4
|
+
import { guardPath, walkSkips } from "./paths.js";
|
|
4
5
|
import { renderGrepMatches } from "./render.js";
|
|
5
6
|
/**
|
|
6
7
|
* Searching file contents, under Think's name so it replaces Think's `grep` —
|
|
@@ -31,7 +32,7 @@ export function grepTools(ctx) {
|
|
|
31
32
|
/**
|
|
32
33
|
* Search, without the container.
|
|
33
34
|
*
|
|
34
|
-
* The alternative is `
|
|
35
|
+
* The alternative is `exec("grep -rn …")`, and the case for a native one is
|
|
35
36
|
* not that shelling out fails — it is where the search runs. `fs.grep` reads
|
|
36
37
|
* the Durable Object's SQLite, so it answers while the container is being
|
|
37
38
|
* replaced or an install is still running, which is exactly the window the
|
|
@@ -51,7 +52,7 @@ export function grepTools(ctx) {
|
|
|
51
52
|
"The query is matched literally — set `regex` to interpret it as a regular expression. " +
|
|
52
53
|
"Pass `include` to limit which files are searched, e.g. '**/*.ts' — without it every file under `path` is read, which is slower and rarely what you meant. " +
|
|
53
54
|
"A cut result reports the `offset` that continues it. Use `context` to see the lines around a match. " +
|
|
54
|
-
"Results from `.git` and `node_modules` are left out — search node_modules with
|
|
55
|
+
"Results from `.git` and `node_modules` are left out — search node_modules with exec.",
|
|
55
56
|
inputSchema: z.object({
|
|
56
57
|
query: z.string().describe("Text to find, e.g. 'buildComputerTools'"),
|
|
57
58
|
path: z
|
|
@@ -102,7 +103,12 @@ export function grepTools(ctx) {
|
|
|
102
103
|
context: around,
|
|
103
104
|
limit: DEFAULT_MAX_MATCHES + 1,
|
|
104
105
|
offset: from,
|
|
105
|
-
|
|
106
|
+
// The same skips the workspace's own walks hand on, anchored to
|
|
107
|
+
// the served root rather than to the target: this tool replaces
|
|
108
|
+
// Think's `grep`, so a search of the root that kept the agent's
|
|
109
|
+
// own directories would fill a bounded page with them before the
|
|
110
|
+
// checkout's matches. See `src/computer/paths.ts`.
|
|
111
|
+
exclude: walkSkips(target, SANDBOX_ROOT)
|
|
106
112
|
});
|
|
107
113
|
if (matches.length === 0)
|
|
108
114
|
return `no matches for ${JSON.stringify(query)} in ${target}${include ? ` (${include})` : ""}${from > 0 ? ` past offset ${from}` : ""}`;
|
|
@@ -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/dist/sandbox/header.d.ts
CHANGED
|
@@ -48,3 +48,16 @@ export declare function readRunHeader(text: string): {
|
|
|
48
48
|
};
|
|
49
49
|
/** Whether `path` is `root` or inside it, with no `..` escaping it. */
|
|
50
50
|
export declare function isUnder(path: string, root: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* `path` with its dot segments and repeated separators resolved, or `undefined`
|
|
53
|
+
* for one that is not absolute or that climbs above `/`.
|
|
54
|
+
*
|
|
55
|
+
* Shared with the tools rather than kept here, because anything comparing a
|
|
56
|
+
* path against this root has to compare what the path *means*: `/workspace/.`
|
|
57
|
+
* and `/workspace/repo/..` are both this root, and a comparison that read them
|
|
58
|
+
* literally would answer no to a path the sandbox itself answers yes to.
|
|
59
|
+
*
|
|
60
|
+
* A `..` with nothing left to pop is refused rather than clamped at `/`: a
|
|
61
|
+
* clamp would turn an attempt to climb out into a valid root path.
|
|
62
|
+
*/
|
|
63
|
+
export declare function normalizeAbsolute(path: string): string | undefined;
|
package/dist/sandbox/header.js
CHANGED
|
@@ -75,20 +75,35 @@ function assertRunHeader(value) {
|
|
|
75
75
|
}
|
|
76
76
|
/** Whether `path` is `root` or inside it, with no `..` escaping it. */
|
|
77
77
|
export function isUnder(path, root) {
|
|
78
|
+
const normal = normalizeAbsolute(path);
|
|
79
|
+
return (normal !== undefined && (normal === root || normal.startsWith(`${root}/`)));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* `path` with its dot segments and repeated separators resolved, or `undefined`
|
|
83
|
+
* for one that is not absolute or that climbs above `/`.
|
|
84
|
+
*
|
|
85
|
+
* Shared with the tools rather than kept here, because anything comparing a
|
|
86
|
+
* path against this root has to compare what the path *means*: `/workspace/.`
|
|
87
|
+
* and `/workspace/repo/..` are both this root, and a comparison that read them
|
|
88
|
+
* literally would answer no to a path the sandbox itself answers yes to.
|
|
89
|
+
*
|
|
90
|
+
* A `..` with nothing left to pop is refused rather than clamped at `/`: a
|
|
91
|
+
* clamp would turn an attempt to climb out into a valid root path.
|
|
92
|
+
*/
|
|
93
|
+
export function normalizeAbsolute(path) {
|
|
78
94
|
if (!path.startsWith("/"))
|
|
79
|
-
return
|
|
95
|
+
return undefined;
|
|
80
96
|
const parts = [];
|
|
81
97
|
for (const part of path.split("/")) {
|
|
82
98
|
if (part === "" || part === ".")
|
|
83
99
|
continue;
|
|
84
100
|
if (part === "..") {
|
|
85
101
|
if (parts.length === 0)
|
|
86
|
-
return
|
|
102
|
+
return undefined;
|
|
87
103
|
parts.pop();
|
|
88
104
|
continue;
|
|
89
105
|
}
|
|
90
106
|
parts.push(part);
|
|
91
107
|
}
|
|
92
|
-
|
|
93
|
-
return normal === root || normal.startsWith(`${root}/`);
|
|
108
|
+
return `/${parts.join("/")}`;
|
|
94
109
|
}
|
|
@@ -36,7 +36,7 @@ export interface InstallProbe {
|
|
|
36
36
|
* Where the install has got to, as the host reports it and the tools read it.
|
|
37
37
|
*
|
|
38
38
|
* Declared here rather than in the host because both sides depend on it and
|
|
39
|
-
* neither owns the other: the Durable Object runs the job, and
|
|
39
|
+
* neither owns the other: the Durable Object runs the job, and the shell tool refuses
|
|
40
40
|
* to run against a tree that is still being built. A shape they agreed on
|
|
41
41
|
* informally would drift, and the drift would show up as a shell command running
|
|
42
42
|
* halfway through an `npm ci`.
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* absent. So: an explicit override wins, then the repository's own
|
|
27
27
|
* `packageManager` pin, then lockfiles in the order the plan lists them.
|
|
28
28
|
*/
|
|
29
|
+
import { sha256Hex } from "@dynamicagents/core";
|
|
29
30
|
/**
|
|
30
31
|
* A sensible starting table, meant to be copied into a host and edited.
|
|
31
32
|
*
|
|
@@ -304,8 +305,5 @@ async function digestFiles(fs, dir, names, prefix) {
|
|
|
304
305
|
}
|
|
305
306
|
if (!found)
|
|
306
307
|
return null;
|
|
307
|
-
|
|
308
|
-
return [...new Uint8Array(digest)]
|
|
309
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
310
|
-
.join("");
|
|
308
|
+
return await sha256Hex(input);
|
|
311
309
|
}
|
package/dist/sandbox/object.d.ts
CHANGED
package/dist/sandbox/object.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DurableObject } from "cloudflare:workers";
|
|
2
2
|
import { ContainerHarness, containerAgent } from "agents/harness/container";
|
|
3
|
+
import { sha256Hex } from "@dynamicagents/core";
|
|
3
4
|
import { installScheduler, namedDeadline } from "@dynamicagents/core/alarm";
|
|
4
5
|
import { DirectoryBackup, Files } from "@cloudflare/sandbox";
|
|
5
6
|
import { asAgent, AGENT_GID, AGENT_UID, shellQuote } from "./agent-exec.js";
|
|
@@ -380,7 +381,11 @@ export class SandboxObjectBase extends DurableObject {
|
|
|
380
381
|
return {
|
|
381
382
|
type: stat.type,
|
|
382
383
|
size: Number(stat.size),
|
|
383
|
-
modifiedAt: stat.modifiedAt.getTime()
|
|
384
|
+
modifiedAt: stat.modifiedAt.getTime(),
|
|
385
|
+
// Reported because the file tools' own contract asks for one, and
|
|
386
|
+
// the container knows it. Nothing can set it back: the sandbox's
|
|
387
|
+
// write takes no mode.
|
|
388
|
+
mode: stat.mode
|
|
384
389
|
};
|
|
385
390
|
}
|
|
386
391
|
catch (err) {
|
|
@@ -1105,14 +1110,16 @@ function markOf(dir) {
|
|
|
1105
1110
|
function installMarkOf(dir) {
|
|
1106
1111
|
return `${INSTALL_MARKS}/${encodeURIComponent(dir)}`;
|
|
1107
1112
|
}
|
|
1108
|
-
/**
|
|
1113
|
+
/**
|
|
1114
|
+
* `env`, in key order, as a digest a fingerprint can carry.
|
|
1115
|
+
*
|
|
1116
|
+
* The width is load-bearing: this goes into an install mark stored on the
|
|
1117
|
+
* container's disk, so changing it makes every tree already marked look
|
|
1118
|
+
* uninstalled and costs each one an install it does not need.
|
|
1119
|
+
*/
|
|
1109
1120
|
async function digestEnv(env) {
|
|
1110
1121
|
const sorted = Object.entries(env ?? {}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
1111
|
-
|
|
1112
|
-
return [...new Uint8Array(bytes)]
|
|
1113
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
1114
|
-
.join("")
|
|
1115
|
-
.slice(0, 16);
|
|
1122
|
+
return (await sha256Hex(JSON.stringify(sorted))).slice(0, 16);
|
|
1116
1123
|
}
|
|
1117
1124
|
function isNotFound(err) {
|
|
1118
1125
|
return (typeof err === "object" &&
|
|
@@ -1129,11 +1136,12 @@ function isAbort(err) {
|
|
|
1129
1136
|
function sleep(ms) {
|
|
1130
1137
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1131
1138
|
}
|
|
1139
|
+
/**
|
|
1140
|
+
* `value`, as the digest a poll returns beside a message.
|
|
1141
|
+
*
|
|
1142
|
+
* Nothing stores it: a poll composes it again for every message it answers
|
|
1143
|
+
* with, so the width has only to tell two messages apart.
|
|
1144
|
+
*/
|
|
1132
1145
|
async function digest(value) {
|
|
1133
|
-
|
|
1134
|
-
const hash = await crypto.subtle.digest("SHA-256", bytes);
|
|
1135
|
-
return [...new Uint8Array(hash)]
|
|
1136
|
-
.slice(0, 12)
|
|
1137
|
-
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
1138
|
-
.join("");
|
|
1146
|
+
return (await sha256Hex(JSON.stringify(value))).slice(0, 24);
|
|
1139
1147
|
}
|
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,11 +84,13 @@
|
|
|
84
84
|
"prepare": "husky || true && npm run build"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
|
+
"@cloudflare/computer": "^0.8.0",
|
|
87
88
|
"@cloudflare/sandbox": "^1.0.1",
|
|
88
|
-
"@cloudflare/think": ">=0.
|
|
89
|
-
"@dynamicagents/core": ">=0.
|
|
90
|
-
"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",
|
|
91
92
|
"ai": "^7.0.88",
|
|
93
|
+
"just-bash": "^3.0.1",
|
|
92
94
|
"zod": "^4.4.3"
|
|
93
95
|
},
|
|
94
96
|
"peerDependenciesMeta": {
|
|
@@ -97,15 +99,17 @@
|
|
|
97
99
|
}
|
|
98
100
|
},
|
|
99
101
|
"devDependencies": {
|
|
102
|
+
"@cloudflare/computer": "^0.8.0",
|
|
100
103
|
"@cloudflare/sandbox": "^1.0.1",
|
|
101
|
-
"@cloudflare/think": "^0.
|
|
104
|
+
"@cloudflare/think": "^0.23.0",
|
|
102
105
|
"@cloudflare/vitest-plugin": "^1.4.4",
|
|
103
|
-
"@dynamicagents/core": "^0.
|
|
106
|
+
"@dynamicagents/core": "^0.18.0",
|
|
104
107
|
"@types/node": "^26.5.1",
|
|
105
|
-
"agents": "^0.
|
|
108
|
+
"agents": "^0.33.0",
|
|
106
109
|
"ai": "^7.0.101",
|
|
107
110
|
"eslint": "^10.10.0",
|
|
108
111
|
"husky": "^9.1.7",
|
|
112
|
+
"just-bash": "^3.0.1",
|
|
109
113
|
"prettier": "^3.9.6",
|
|
110
114
|
"semver": "^7.8.5",
|
|
111
115
|
"typescript": "^6.0.3",
|