@dynamicagents/plugins 0.14.0 → 0.15.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.
Files changed (141) hide show
  1. package/README.md +14 -14
  2. package/dist/claude-code/cards.d.ts +2 -0
  3. package/dist/claude-code/cards.js +11 -0
  4. package/dist/claude-code/config.d.ts +10 -83
  5. package/dist/claude-code/config.js +0 -2
  6. package/dist/claude-code/credentials.d.ts +32 -50
  7. package/dist/claude-code/credentials.js +115 -74
  8. package/dist/claude-code/daemon/index.d.ts +24 -0
  9. package/dist/claude-code/daemon/index.js +203 -0
  10. package/dist/claude-code/egress.d.ts +35 -117
  11. package/dist/claude-code/egress.js +80 -325
  12. package/dist/claude-code/events.d.ts +14 -17
  13. package/dist/claude-code/events.js +19 -15
  14. package/dist/claude-code/harness-wire.d.ts +45 -0
  15. package/dist/claude-code/harness-wire.js +47 -0
  16. package/dist/claude-code/index.d.ts +19 -29
  17. package/dist/claude-code/index.js +17 -25
  18. package/dist/claude-code/language-model.d.ts +19 -0
  19. package/dist/claude-code/language-model.js +71 -0
  20. package/dist/claude-code/model.d.ts +77 -70
  21. package/dist/claude-code/model.js +327 -327
  22. package/dist/claude-code/placeholder.d.ts +12 -0
  23. package/dist/claude-code/placeholder.js +12 -0
  24. package/dist/claude-code/spec.d.ts +4 -4
  25. package/dist/claude-code/spec.js +4 -4
  26. package/dist/computer/client.d.ts +88 -0
  27. package/dist/computer/client.js +6 -0
  28. package/dist/computer/context.d.ts +7 -7
  29. package/dist/computer/context.js +14 -18
  30. package/dist/computer/gate.d.ts +4 -23
  31. package/dist/computer/gate.js +1 -37
  32. package/dist/computer/index.d.ts +44 -43
  33. package/dist/computer/index.js +29 -50
  34. package/dist/computer/paths.d.ts +5 -27
  35. package/dist/computer/paths.js +7 -30
  36. package/dist/computer/proxy.d.ts +17 -31
  37. package/dist/computer/proxy.js +9 -44
  38. package/dist/computer/render.d.ts +3 -40
  39. package/dist/computer/render.js +3 -36
  40. package/dist/computer/sandbox.d.ts +55 -0
  41. package/dist/computer/sandbox.js +236 -0
  42. package/dist/computer/shell.d.ts +36 -0
  43. package/dist/computer/shell.js +41 -0
  44. package/dist/computer/tools-exec.js +3 -9
  45. package/dist/computer/tools-file.d.ts +1 -1
  46. package/dist/computer/tools-file.js +1 -1
  47. package/dist/{repo → repo-github}/checkout.d.ts +3 -4
  48. package/dist/{repo → repo-github}/checkout.js +3 -4
  49. package/dist/{repo → repo-github}/context.d.ts +14 -32
  50. package/dist/{repo → repo-github}/context.js +46 -81
  51. package/dist/{repo → repo-github}/index.d.ts +30 -13
  52. package/dist/{repo → repo-github}/index.js +25 -6
  53. package/dist/repo-github/mcp.d.ts +95 -0
  54. package/dist/repo-github/mcp.js +107 -0
  55. package/dist/repo-github/sandbox-egress.d.ts +68 -0
  56. package/dist/repo-github/sandbox-egress.js +272 -0
  57. package/dist/repo-github/sandbox-git.d.ts +30 -0
  58. package/dist/repo-github/sandbox-git.js +113 -0
  59. package/dist/{repo → repo-github}/tools-checks.js +43 -4
  60. package/dist/{repo → repo-github}/tools-clone.js +2 -21
  61. package/dist/{repo → repo-github}/tools-pr.d.ts +0 -1
  62. package/dist/{repo → repo-github}/tools-pr.js +85 -20
  63. package/dist/{repo → repo-github}/tools-review.d.ts +6 -0
  64. package/dist/{repo → repo-github}/tools-review.js +62 -176
  65. package/dist/{repo → repo-github}/url.d.ts +11 -0
  66. package/dist/{repo → repo-github}/url.js +12 -1
  67. package/dist/{workspace → sandbox}/advisory.d.ts +16 -35
  68. package/dist/{workspace → sandbox}/advisory.js +2 -29
  69. package/dist/sandbox/agent-exec.d.ts +31 -0
  70. package/dist/sandbox/agent-exec.js +62 -0
  71. package/dist/sandbox/ca-trust.d.ts +9 -0
  72. package/dist/sandbox/ca-trust.js +21 -0
  73. package/dist/sandbox/constants.d.ts +22 -0
  74. package/dist/sandbox/constants.js +22 -0
  75. package/dist/sandbox/daemon/composite.d.ts +9 -0
  76. package/dist/sandbox/daemon/composite.js +35 -0
  77. package/dist/sandbox/daemon/index.d.ts +47 -0
  78. package/dist/sandbox/daemon/index.js +116 -0
  79. package/dist/sandbox/egress.d.ts +81 -0
  80. package/dist/sandbox/egress.js +109 -0
  81. package/dist/sandbox/exec.d.ts +57 -0
  82. package/dist/sandbox/exec.js +52 -0
  83. package/dist/sandbox/format.d.ts +2 -0
  84. package/dist/sandbox/format.js +7 -0
  85. package/dist/sandbox/header.d.ts +50 -0
  86. package/dist/sandbox/header.js +94 -0
  87. package/dist/sandbox/index.d.ts +17 -0
  88. package/dist/sandbox/index.js +17 -0
  89. package/dist/{workspace/install.d.ts → sandbox/install-plan.d.ts} +2 -2
  90. package/dist/{workspace/install.js → sandbox/install-plan.js} +2 -2
  91. package/dist/sandbox/name.d.ts +27 -0
  92. package/dist/sandbox/name.js +32 -0
  93. package/dist/sandbox/object.d.ts +244 -0
  94. package/dist/sandbox/object.js +1139 -0
  95. package/dist/scratch/index.d.ts +1 -9
  96. package/dist/scratch/index.js +2 -18
  97. package/package.json +22 -18
  98. package/dist/claude-code/github.d.ts +0 -39
  99. package/dist/claude-code/github.js +0 -82
  100. package/dist/claude-code/run.d.ts +0 -491
  101. package/dist/claude-code/run.js +0 -913
  102. package/dist/claude-code/session.d.ts +0 -131
  103. package/dist/claude-code/session.js +0 -288
  104. package/dist/workspace/ca-trust.d.ts +0 -95
  105. package/dist/workspace/ca-trust.js +0 -312
  106. package/dist/workspace/container-deps.d.ts +0 -58
  107. package/dist/workspace/container-deps.js +0 -249
  108. package/dist/workspace/container-fault.d.ts +0 -38
  109. package/dist/workspace/container-fault.js +0 -73
  110. package/dist/workspace/deps-snapshot.d.ts +0 -110
  111. package/dist/workspace/deps-snapshot.js +0 -424
  112. package/dist/workspace/exec.d.ts +0 -94
  113. package/dist/workspace/exec.js +0 -153
  114. package/dist/workspace/format.d.ts +0 -36
  115. package/dist/workspace/format.js +0 -57
  116. package/dist/workspace/git-host.d.ts +0 -79
  117. package/dist/workspace/git-host.js +0 -226
  118. package/dist/workspace/git-identity.d.ts +0 -57
  119. package/dist/workspace/git-identity.js +0 -147
  120. package/dist/workspace/index.d.ts +0 -50
  121. package/dist/workspace/index.js +0 -58
  122. package/dist/workspace/install-job.d.ts +0 -170
  123. package/dist/workspace/install-job.js +0 -934
  124. package/dist/workspace/object.d.ts +0 -522
  125. package/dist/workspace/object.js +0 -1541
  126. package/dist/workspace/open.d.ts +0 -95
  127. package/dist/workspace/open.js +0 -63
  128. package/dist/workspace/read.d.ts +0 -16
  129. package/dist/workspace/read.js +0 -22
  130. package/dist/workspace/shell.d.ts +0 -45
  131. package/dist/workspace/shell.js +0 -80
  132. package/dist/workspace/sync.d.ts +0 -111
  133. package/dist/workspace/sync.js +0 -124
  134. package/dist/workspace/wake.d.ts +0 -21
  135. package/dist/workspace/wake.js +0 -10
  136. /package/dist/{repo → repo-github}/tools-checks.d.ts +0 -0
  137. /package/dist/{repo → repo-github}/tools-clone.d.ts +0 -0
  138. /package/dist/{repo → repo-github}/tools-worktree.d.ts +0 -0
  139. /package/dist/{repo → repo-github}/tools-worktree.js +0 -0
  140. /package/dist/{repo → repo-github}/tools-worktrees.d.ts +0 -0
  141. /package/dist/{repo → repo-github}/tools-worktrees.js +0 -0
@@ -0,0 +1,81 @@
1
+ import type { EgressRoute } from "agents/harness/container";
2
+ /**
3
+ * The container's way out, on the Worker side.
4
+ *
5
+ * A plugin that needs a host from inside the container declares it as a
6
+ * {@link SandboxEgressRule}: the hosts to intercept, the placeholders its
7
+ * processes see, and the handler that swaps a placeholder for the real
8
+ * credential. A consumer lists the rules a sandbox class carries once, and
9
+ * hands that list both to the class's config and to the entrypoint that calls
10
+ * {@link sandboxEgress}. This module names no host and holds no credential.
11
+ *
12
+ * An intercepted request reaches the entrypoint, a stateless Worker invocation
13
+ * outside the container. The sandbox object is on the path only when a handler
14
+ * calls it ({@link EgressCall.sandbox}).
15
+ */
16
+ export interface SandboxEgressRule {
17
+ /** Names the rule where a clash or a refusal is reported. */
18
+ readonly name: string;
19
+ /**
20
+ * Hosts intercepted over plain HTTP, each to its real `https:` upstream: a
21
+ * placeholder host the container is pointed at, so nothing in it terminates
22
+ * TLS.
23
+ */
24
+ readonly http?: Readonly<Record<string, string>>;
25
+ /**
26
+ * Real hosts intercepted over HTTPS, each to its `https:` upstream, for a
27
+ * client that cannot be pointed at a placeholder, or whose URLs other tools
28
+ * read back, as git's remotes are. The sandbox trusts the runtime's
29
+ * interception CA in every container that has one.
30
+ */
31
+ readonly https?: Readonly<Record<string, string>>;
32
+ /**
33
+ * What a process needs to reach those hosts — a placeholder credential, a
34
+ * base URL. Every command `exec` runs and every CLI turn gets it, over its
35
+ * own environment. Never a secret: the container can read it.
36
+ */
37
+ readonly env?: Readonly<Record<string, string>>;
38
+ /** Serve one intercepted request: swap the placeholder for the real credential, then {@link forward}. */
39
+ handle(call: EgressCall): Promise<Response>;
40
+ }
41
+ /** One request the container made, for the rule serving its host. */
42
+ export interface EgressCall {
43
+ readonly request: Request;
44
+ /** The rule's upstream for the request's host, with the request's path and query. */
45
+ readonly target: URL;
46
+ /** The Worker's `env`, where a credential is read at the moment it is used. */
47
+ readonly env: Readonly<Record<string, unknown>>;
48
+ /**
49
+ * The sandbox whose container made the request, by the name it was reached
50
+ * by — waking it if it is not in memory. Throws for a sandbox reached without
51
+ * a name.
52
+ */
53
+ sandbox(): DurableObjectStub;
54
+ }
55
+ /** What one sandbox registers for its rules. */
56
+ export interface EgressPlan {
57
+ /** Intercepted over HTTP by the harness, at every start. */
58
+ readonly http: readonly EgressRoute[];
59
+ /** Intercepted over HTTPS by the sandbox, once per container. */
60
+ readonly https: readonly EgressRoute[];
61
+ /** Every rule's `env`, merged. */
62
+ readonly env: Readonly<Record<string, string>>;
63
+ }
64
+ /** Headers that carry a credential, which a handler removes from what the container sent. */
65
+ export declare const CREDENTIAL_HEADERS: readonly string[];
66
+ /**
67
+ * `rules` as the sandbox `sandbox` (`<binding>/<name>`) registers them.
68
+ *
69
+ * Throws for a host two rules declare, an environment key two rules set, or an
70
+ * upstream that is not https: each would otherwise surface as a request that
71
+ * reaches the wrong handler, or none.
72
+ */
73
+ export declare function egressPlan(rules: readonly SandboxEgressRule[], sandbox?: string): EgressPlan;
74
+ /**
75
+ * Serve one request the container made: `routes` are what the sandbox
76
+ * registered for this container, which the harness hands the entrypoint as its
77
+ * props, and `rules` the same list the sandbox's config holds.
78
+ */
79
+ export declare function sandboxEgress(request: Request, routes: readonly EgressRoute[], env: Readonly<Record<string, unknown>>, rules: readonly SandboxEgressRule[]): Promise<Response>;
80
+ /** `call.request` at its target, with `rewrite` applied to its headers; never following a redirect. */
81
+ export declare function forward(call: EgressCall, rewrite: (headers: Headers) => void, body?: BodyInit): Promise<Response>;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * On each route, `<binding>/<name>` of the sandbox it was registered by. A
3
+ * route is the only thing the harness hands the entrypoint, so it is the only
4
+ * way the entrypoint can know which sandbox's container is calling.
5
+ */
6
+ const ROUTE_SANDBOX = "x-da-sandbox";
7
+ /** Headers that carry a credential, which a handler removes from what the container sent. */
8
+ export const CREDENTIAL_HEADERS = [
9
+ "authorization",
10
+ "x-api-key",
11
+ "proxy-authorization"
12
+ ];
13
+ /**
14
+ * `rules` as the sandbox `sandbox` (`<binding>/<name>`) registers them.
15
+ *
16
+ * Throws for a host two rules declare, an environment key two rules set, or an
17
+ * upstream that is not https: each would otherwise surface as a request that
18
+ * reaches the wrong handler, or none.
19
+ */
20
+ export function egressPlan(rules, sandbox) {
21
+ const hosts = new Map();
22
+ const keys = new Map();
23
+ const http = [];
24
+ const https = [];
25
+ const env = {};
26
+ const route = (rule, host, upstream) => {
27
+ const owner = hosts.get(host);
28
+ if (owner !== undefined)
29
+ throw new Error(`egress: ${host} is declared by both ${owner} and ${rule.name}`);
30
+ hosts.set(host, rule.name);
31
+ if (!upstream.startsWith("https://"))
32
+ throw new Error(`egress: ${rule.name} sends ${host} to ${upstream}, which is not https`);
33
+ return {
34
+ host,
35
+ upstream,
36
+ headers: sandbox === undefined ? {} : { [ROUTE_SANDBOX]: sandbox },
37
+ strip: []
38
+ };
39
+ };
40
+ for (const rule of rules) {
41
+ for (const [host, upstream] of Object.entries(rule.http ?? {}))
42
+ http.push(route(rule, host, upstream));
43
+ for (const [host, upstream] of Object.entries(rule.https ?? {}))
44
+ https.push(route(rule, host, upstream));
45
+ for (const [key, value] of Object.entries(rule.env ?? {})) {
46
+ const owner = keys.get(key);
47
+ if (owner !== undefined)
48
+ throw new Error(`egress: ${key} is set by both ${owner} and ${rule.name}`);
49
+ keys.set(key, rule.name);
50
+ env[key] = value;
51
+ }
52
+ }
53
+ return { http, https, env };
54
+ }
55
+ /**
56
+ * Serve one request the container made: `routes` are what the sandbox
57
+ * registered for this container, which the harness hands the entrypoint as its
58
+ * props, and `rules` the same list the sandbox's config holds.
59
+ */
60
+ export async function sandboxEgress(request, routes, env, rules) {
61
+ const url = new URL(request.url);
62
+ const route = routes.find((each) => each.host === url.hostname);
63
+ if (!route) {
64
+ return new Response(`No egress route for ${url.hostname}`, { status: 403 });
65
+ }
66
+ for (const rule of rules) {
67
+ const upstream = upstreamOf(rule.http, url.hostname) ??
68
+ upstreamOf(rule.https, url.hostname);
69
+ if (upstream === undefined)
70
+ continue;
71
+ const named = route.headers[ROUTE_SANDBOX];
72
+ return rule.handle({
73
+ request,
74
+ target: new URL(`${upstream.replace(/\/+$/, "")}${url.pathname}${url.search}`),
75
+ env,
76
+ sandbox: () => sandboxStub(env, named)
77
+ });
78
+ }
79
+ return new Response(`No egress rule serves ${url.hostname}`, {
80
+ status: 403
81
+ });
82
+ }
83
+ /** `call.request` at its target, with `rewrite` applied to its headers; never following a redirect. */
84
+ export async function forward(call, rewrite, body) {
85
+ const headers = new Headers(call.request.headers);
86
+ headers.delete("host");
87
+ rewrite(headers);
88
+ const hasBody = call.request.method !== "GET" && call.request.method !== "HEAD";
89
+ return fetch(call.target, {
90
+ method: call.request.method,
91
+ headers,
92
+ ...(hasBody ? { body: body ?? call.request.body } : {}),
93
+ redirect: "manual"
94
+ });
95
+ }
96
+ function upstreamOf(hosts, host) {
97
+ return hosts && Object.hasOwn(hosts, host) ? hosts[host] : undefined;
98
+ }
99
+ function sandboxStub(env, named) {
100
+ const slash = named?.indexOf("/") ?? -1;
101
+ const namespace = named !== undefined && slash > 0 ? env[named.slice(0, slash)] : undefined;
102
+ if (named === undefined ||
103
+ !namespace ||
104
+ typeof namespace.getByName !== "function") {
105
+ throw new Error(`egress: this route names no sandbox the Worker can reach (${JSON.stringify(named ?? null)}); ` +
106
+ "a sandbox is reached by name, and its config's `egress.self` is its binding");
107
+ }
108
+ return namespace.getByName(named.slice(slash + 1));
109
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * A shell in a sandbox, for a plugin that needs one but does not own the
3
+ * container: `/repo-github`'s git, `/scratch`, a host's own scripts. It answers
4
+ * the contract those plugins take — `success`, `stdout`, `stderr` and
5
+ * `exitCode`.
6
+ *
7
+ * Every command runs as the agent, with every egress rule's `env`, and under
8
+ * `timeout` at its limit: the sandbox's own `exec` can only stop waiting, and a
9
+ * command it stopped waiting for would go on running.
10
+ *
11
+ * It merges no host environment of its own: this is a raw shell for another
12
+ * plugin's commands, and a host environment underneath would let through what
13
+ * git honours — `GIT_PROXY_COMMAND`, `GIT_CONFIG_GLOBAL`, `http_proxy`. What a
14
+ * host merges into a plugin's commands it composes where the merge is visible.
15
+ */
16
+ /** The sandbox RPC this uses. */
17
+ export interface SandboxExecHost {
18
+ exec(argv: string[], options?: {
19
+ cwd?: string;
20
+ env?: Record<string, string>;
21
+ timeoutMs?: number;
22
+ }): Promise<{
23
+ exitCode: number;
24
+ stdout: string;
25
+ stderr: string;
26
+ }>;
27
+ }
28
+ export interface SandboxExecConfig {
29
+ /** Addressed by name only: the sandbox's lifecycle needs it. */
30
+ binding: DurableObjectNamespace<SandboxExecHost & Rpc.DurableObjectBranded>;
31
+ /** The sandbox a parent's own commands run in. A sub-agent's comes from its `runtime()`. */
32
+ sandboxName: () => string;
33
+ /** Default `/workspace`. */
34
+ cwd?: string;
35
+ /**
36
+ * The shell a command string runs in. Default `bash`: a model writing shell
37
+ * writes bash, and `/bin/sh` is dash on Debian, which fails `${PIPESTATUS[0]}`
38
+ * and the rest of that family after the command they wrapped has run.
39
+ */
40
+ shell?: string;
41
+ /** Per command. Default {@link DEFAULT_EXEC_TIMEOUT_MS}. */
42
+ timeoutMs?: number;
43
+ }
44
+ /** A command that has not finished in this long is a hung command. */
45
+ export declare const DEFAULT_EXEC_TIMEOUT_MS: number;
46
+ export declare function sandboxExec(config: SandboxExecConfig): (command: string, options?: {
47
+ cwd?: string;
48
+ env?: Record<string, string | undefined>;
49
+ timeout?: number;
50
+ /** A sub-agent's `runtime()`, forwarded opaquely by the plugin calling this. */
51
+ runtime?: unknown;
52
+ }) => Promise<{
53
+ success: boolean;
54
+ stdout: string;
55
+ stderr: string;
56
+ exitCode: number;
57
+ }>;
@@ -0,0 +1,52 @@
1
+ import { humanMs } from "./format.js";
2
+ import { SANDBOX_ROOT } from "./header.js";
3
+ import { sandboxNameFromRuntime } from "./name.js";
4
+ /** A command that has not finished in this long is a hung command. */
5
+ export const DEFAULT_EXEC_TIMEOUT_MS = 10 * 60 * 1000;
6
+ /** After `timeout`'s TERM, how long before its KILL. */
7
+ const KILL_AFTER_S = 5;
8
+ /**
9
+ * `timeout`'s exits for a command it ended. Read as the limit only once the
10
+ * limit has passed: any SIGKILL is a 137, an out-of-memory kill included.
11
+ */
12
+ const TIMED_OUT = new Set([124, 137]);
13
+ export function sandboxExec(config) {
14
+ return async (command, options) => {
15
+ const name = sandboxNameFromRuntime(options?.runtime) ?? config.sandboxName();
16
+ const limitMs = options?.timeout ?? config.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
17
+ const env = options?.env
18
+ ? Object.fromEntries(Object.entries(options.env).filter((entry) => entry[1] !== undefined))
19
+ : undefined;
20
+ const startedAt = Date.now();
21
+ const run = await config.binding
22
+ .getByName(name)
23
+ .exec([
24
+ "timeout",
25
+ `--kill-after=${KILL_AFTER_S}`,
26
+ String(Math.ceil(limitMs / 1000)),
27
+ config.shell ?? "bash",
28
+ "-c",
29
+ command
30
+ ], {
31
+ cwd: options?.cwd ?? config.cwd ?? SANDBOX_ROOT,
32
+ ...(env ? { env } : {}),
33
+ timeoutMs: limitMs + (KILL_AFTER_S + 5) * 1000
34
+ });
35
+ const killed = TIMED_OUT.has(run.exitCode) && Date.now() - startedAt >= limitMs;
36
+ return {
37
+ success: run.exitCode === 0,
38
+ stdout: run.stdout,
39
+ // Appended rather than substituted: a command killed at its limit may
40
+ // have written plenty first, and `stdout` is a channel callers parse.
41
+ stderr: killed
42
+ ? [
43
+ run.stderr,
44
+ `the command was killed at its time limit of ${humanMs(limitMs)}, so anything it had not yet written is gone`
45
+ ]
46
+ .filter(Boolean)
47
+ .join("\n")
48
+ : run.stderr,
49
+ exitCode: run.exitCode
50
+ };
51
+ };
52
+ }
@@ -0,0 +1,2 @@
1
+ /** Elapsed time in the shape a sentence wants. */
2
+ export declare function humanMs(ms: number): string;
@@ -0,0 +1,7 @@
1
+ /** Elapsed time in the shape a sentence wants. */
2
+ export function humanMs(ms) {
3
+ const s = Math.round(ms / 1000);
4
+ return s < 60
5
+ ? `${s}s`
6
+ : `${Math.floor(s / 60)}m${String(s % 60).padStart(2, "0")}s`;
7
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The run header: how a host tells the daemon how to launch one run.
3
+ *
4
+ * `ContainerHarness` takes its adapter options once, for every session it holds,
5
+ * and its adapters run every turn in one fixed working directory. A sandbox runs
6
+ * a planner, a writer and a reviser in the same object, each in its own
7
+ * checkout and under its own permission mode, so neither is enough. The
8
+ * harness passes a prompt through untouched, so the run's own settings travel
9
+ * as the prompt's first line, and the daemon takes that line off before the CLI
10
+ * reads its stdin.
11
+ *
12
+ * Parsed on both sides from this one module, so the spelling cannot drift.
13
+ */
14
+ /** The first characters of a prompt that carries a header. */
15
+ export declare const RUN_HEADER_PREFIX = "@@da ";
16
+ /** Where every run's working directory must sit. */
17
+ export declare const SANDBOX_ROOT = "/workspace";
18
+ export interface RunHeader {
19
+ /** The checkout the run works in. Fixed for a session's life: the CLI files a transcript by directory. */
20
+ cwd: string;
21
+ /** `plan` runs read-only and answers to {@link schema}. */
22
+ mode?: "plan" | "build";
23
+ /** A schema the daemon was built with, by name. Not the schema itself: a prompt is stored. */
24
+ schema?: string;
25
+ /** The CLI's effort level, passed through. */
26
+ effort?: string;
27
+ /** Continue the session's conversation as a new branch of it. */
28
+ fork?: boolean;
29
+ /** The CLI's model, when the run needs one other than the session's. */
30
+ model?: string;
31
+ }
32
+ /** A header the daemon cannot honour, refused rather than run with defaults. */
33
+ export declare class RunHeaderError extends Error {
34
+ readonly name = "RunHeaderError";
35
+ }
36
+ /** `prompt` with `header` as its first line. */
37
+ export declare function withRunHeader(header: RunHeader, prompt: string): string;
38
+ /**
39
+ * The header and the prompt beneath it. A prompt with no header line is
40
+ * returned whole with `header: undefined`: control prompts and a session's own
41
+ * follow-ups carry none.
42
+ *
43
+ * @throws {RunHeaderError} the first line claims to be a header and is not one.
44
+ */
45
+ export declare function readRunHeader(text: string): {
46
+ header: RunHeader | undefined;
47
+ prompt: string;
48
+ };
49
+ /** Whether `path` is `root` or inside it, with no `..` escaping it. */
50
+ export declare function isUnder(path: string, root: string): boolean;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The run header: how a host tells the daemon how to launch one run.
3
+ *
4
+ * `ContainerHarness` takes its adapter options once, for every session it holds,
5
+ * and its adapters run every turn in one fixed working directory. A sandbox runs
6
+ * a planner, a writer and a reviser in the same object, each in its own
7
+ * checkout and under its own permission mode, so neither is enough. The
8
+ * harness passes a prompt through untouched, so the run's own settings travel
9
+ * as the prompt's first line, and the daemon takes that line off before the CLI
10
+ * reads its stdin.
11
+ *
12
+ * Parsed on both sides from this one module, so the spelling cannot drift.
13
+ */
14
+ /** The first characters of a prompt that carries a header. */
15
+ export const RUN_HEADER_PREFIX = "@@da ";
16
+ /** Where every run's working directory must sit. */
17
+ export const SANDBOX_ROOT = "/workspace";
18
+ /** A header the daemon cannot honour, refused rather than run with defaults. */
19
+ export class RunHeaderError extends Error {
20
+ name = "RunHeaderError";
21
+ }
22
+ /** `prompt` with `header` as its first line. */
23
+ export function withRunHeader(header, prompt) {
24
+ assertRunHeader(header);
25
+ return `${RUN_HEADER_PREFIX}${JSON.stringify(header)}\n${prompt}`;
26
+ }
27
+ /**
28
+ * The header and the prompt beneath it. A prompt with no header line is
29
+ * returned whole with `header: undefined`: control prompts and a session's own
30
+ * follow-ups carry none.
31
+ *
32
+ * @throws {RunHeaderError} the first line claims to be a header and is not one.
33
+ */
34
+ export function readRunHeader(text) {
35
+ if (!text.startsWith(RUN_HEADER_PREFIX))
36
+ return { header: undefined, prompt: text };
37
+ const end = text.indexOf("\n");
38
+ const line = end === -1 ? text : text.slice(0, end);
39
+ const prompt = end === -1 ? "" : text.slice(end + 1);
40
+ let value;
41
+ try {
42
+ value = JSON.parse(line.slice(RUN_HEADER_PREFIX.length));
43
+ }
44
+ catch {
45
+ throw new RunHeaderError(`the run header is not JSON: ${line.slice(0, 120)}`);
46
+ }
47
+ assertRunHeader(value);
48
+ return { header: value, prompt };
49
+ }
50
+ function assertRunHeader(value) {
51
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
52
+ throw new RunHeaderError("the run header must be an object");
53
+ }
54
+ const header = value;
55
+ const cwd = header.cwd;
56
+ if (typeof cwd !== "string" || !isUnder(cwd, SANDBOX_ROOT)) {
57
+ throw new RunHeaderError(`the run header's cwd must be an absolute path under ${SANDBOX_ROOT}, got ${JSON.stringify(cwd)}`);
58
+ }
59
+ if (header.mode !== undefined &&
60
+ header.mode !== "plan" &&
61
+ header.mode !== "build") {
62
+ throw new RunHeaderError(`unknown run mode ${JSON.stringify(header.mode)}`);
63
+ }
64
+ for (const key of ["schema", "effort", "model"]) {
65
+ if (header[key] !== undefined && typeof header[key] !== "string") {
66
+ throw new RunHeaderError(`the run header's ${key} must be a string`);
67
+ }
68
+ }
69
+ if (header.fork !== undefined && typeof header.fork !== "boolean") {
70
+ throw new RunHeaderError("the run header's fork must be a boolean");
71
+ }
72
+ if (header.mode === "plan" && header.schema === undefined) {
73
+ throw new RunHeaderError("a plan run names the schema its answer follows");
74
+ }
75
+ }
76
+ /** Whether `path` is `root` or inside it, with no `..` escaping it. */
77
+ export function isUnder(path, root) {
78
+ if (!path.startsWith("/"))
79
+ return false;
80
+ const parts = [];
81
+ for (const part of path.split("/")) {
82
+ if (part === "" || part === ".")
83
+ continue;
84
+ if (part === "..") {
85
+ if (parts.length === 0)
86
+ return false;
87
+ parts.pop();
88
+ continue;
89
+ }
90
+ parts.push(part);
91
+ }
92
+ const normal = `/${parts.join("/")}`;
93
+ return normal === root || normal.startsWith(`${root}/`);
94
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `@dynamicagents/plugins/sandbox` — a container whose disk is the checkout,
3
+ * driven from a Durable Object through `agents`' `ContainerHarness`.
4
+ *
5
+ * The README has the model. The daemon an image runs is the separate,
6
+ * Node-only `@dynamicagents/plugins/sandbox/daemon`.
7
+ */
8
+ export { SandboxObjectBase, type CheckoutRecord, type ExecResult, type PolledMessage, type PollResult, type SandboxObjectConfig, type SnapshotSeed } from "./object.js";
9
+ export { CONTROL_SESSION, CONTROL_WAKE, EGRESS_READY, RUN_DIR, SANDBOX_CONTRACT } from "./constants.js";
10
+ export { readRunHeader, RUN_HEADER_PREFIX, RunHeaderError, SANDBOX_ROOT, withRunHeader, type RunHeader } from "./header.js";
11
+ export { AGENT_GID, AGENT_HOME, AGENT_UID, agentEnv, asAgent } from "./agent-exec.js";
12
+ export { sandboxName, sandboxNameFromRuntime, SANDBOX_RUNTIME_KEY } from "./name.js";
13
+ export { DEFAULT_EXEC_TIMEOUT_MS, sandboxExec, type SandboxExecConfig, type SandboxExecHost } from "./exec.js";
14
+ export { CREDENTIAL_HEADERS, egressPlan, forward, sandboxEgress, type EgressCall, type EgressPlan, type SandboxEgressRule } from "./egress.js";
15
+ export { deriveAdvisories, renderAdvisory, sessionAdvisory, shapeOf, type AdvisoryAudience, type AdvisoryShape, type SandboxAdvisory } from "./advisory.js";
16
+ export { DEFAULT_INSTALL_PLAN, resolveInstallCommand, type InstallPlan, type InstallRule, type InstallState } from "./install-plan.js";
17
+ export { DirectoryBackupGateway } from "@cloudflare/sandbox";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `@dynamicagents/plugins/sandbox` — a container whose disk is the checkout,
3
+ * driven from a Durable Object through `agents`' `ContainerHarness`.
4
+ *
5
+ * The README has the model. The daemon an image runs is the separate,
6
+ * Node-only `@dynamicagents/plugins/sandbox/daemon`.
7
+ */
8
+ export { SandboxObjectBase } from "./object.js";
9
+ export { CONTROL_SESSION, CONTROL_WAKE, EGRESS_READY, RUN_DIR, SANDBOX_CONTRACT } from "./constants.js";
10
+ export { readRunHeader, RUN_HEADER_PREFIX, RunHeaderError, SANDBOX_ROOT, withRunHeader } from "./header.js";
11
+ export { AGENT_GID, AGENT_HOME, AGENT_UID, agentEnv, asAgent } from "./agent-exec.js";
12
+ export { sandboxName, sandboxNameFromRuntime, SANDBOX_RUNTIME_KEY } from "./name.js";
13
+ export { DEFAULT_EXEC_TIMEOUT_MS, sandboxExec } from "./exec.js";
14
+ export { CREDENTIAL_HEADERS, egressPlan, forward, sandboxEgress } from "./egress.js";
15
+ export { deriveAdvisories, renderAdvisory, sessionAdvisory, shapeOf } from "./advisory.js";
16
+ export { DEFAULT_INSTALL_PLAN, resolveInstallCommand } from "./install-plan.js";
17
+ export { DirectoryBackupGateway } from "@cloudflare/sandbox";
@@ -184,7 +184,7 @@ export declare function resolveInstallCommand(fs: InstallProbe, dir: string, pla
184
184
  * A matching fingerprint means "the same install would produce the same tree".
185
185
  * It does **not** mean the tree is there: it is on the container's disk, and
186
186
  * goes with the container. The caller also needs a record that this container
187
- * finished it — see `./install-job.ts`.
187
+ * finished it — see `startInstall` in `./object.ts`.
188
188
  */
189
189
  export declare function installFingerprint(fs: InstallProbe, dir: string, resolution: InstallResolution): Promise<string | null>;
190
190
  /**
@@ -192,7 +192,7 @@ export declare function installFingerprint(fs: InstallProbe, dir: string, resolu
192
192
  * at it — a submodule a bootstrap installs, say — where there is no command to
193
193
  * fold in, and every lockfile the plan knows stands in for the one it would pick.
194
194
  *
195
- * For `./deps-snapshot.ts`, which restores every root's tree at once and so has
195
+ * For a caller restoring every root's tree at once from one snapshot, which has
196
196
  * to know none of them moved.
197
197
  */
198
198
  export declare function rootFingerprint(fs: InstallProbe, dir: string, plan: InstallPlan): Promise<string | null>;
@@ -262,7 +262,7 @@ async function firstPresent(fs, dir, names) {
262
262
  * A matching fingerprint means "the same install would produce the same tree".
263
263
  * It does **not** mean the tree is there: it is on the container's disk, and
264
264
  * goes with the container. The caller also needs a record that this container
265
- * finished it — see `./install-job.ts`.
265
+ * finished it — see `startInstall` in `./object.ts`.
266
266
  */
267
267
  export async function installFingerprint(fs, dir, resolution) {
268
268
  if (resolution.kind !== "run")
@@ -283,7 +283,7 @@ export async function installFingerprint(fs, dir, resolution) {
283
283
  * at it — a submodule a bootstrap installs, say — where there is no command to
284
284
  * fold in, and every lockfile the plan knows stands in for the one it would pick.
285
285
  *
286
- * For `./deps-snapshot.ts`, which restores every root's tree at once and so has
286
+ * For a caller restoring every root's tree at once from one snapshot, which has
287
287
  * to know none of them moved.
288
288
  */
289
289
  export async function rootFingerprint(fs, dir, plan) {
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Which sandbox a caller reaches: its name, and how a parent hands it to a
3
+ * sub-agent.
4
+ *
5
+ * One sandbox is one container is one repository, so a caller's sandboxes are
6
+ * keyed on the repository it works in. A module of its own, with no imports,
7
+ * because every path into a sandbox — a plugin's tools, a host's shell, a
8
+ * sub-agent's `prepare` — must spell it the same way.
9
+ */
10
+ /**
11
+ * The Durable Object name for one caller's sandbox for one repository.
12
+ *
13
+ * A pipe rather than a slash, so the caller half cannot forge a repository
14
+ * boundary by containing one. `repo` may be a sentinel rather than a
15
+ * repository — a scratchpad, a worktree — as long as no caller could clone
16
+ * something that collides with it. Undefined is the window before a task has
17
+ * chosen one, which resolves to a caller-level sandbox nothing works in.
18
+ */
19
+ export declare function sandboxName(callerKey: string, repo?: string): string;
20
+ /** The key a parent's `prepare` puts a sub-agent's sandbox name under, in its `runtime()`. */
21
+ export declare const SANDBOX_RUNTIME_KEY = "sandboxName";
22
+ /**
23
+ * A parent-resolved sandbox name, read off a sub-agent's `runtime()`.
24
+ * `undefined` for anything else: a parent has no runtime, and falls back to its
25
+ * own name.
26
+ */
27
+ export declare function sandboxNameFromRuntime(runtime: unknown): string | undefined;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Which sandbox a caller reaches: its name, and how a parent hands it to a
3
+ * sub-agent.
4
+ *
5
+ * One sandbox is one container is one repository, so a caller's sandboxes are
6
+ * keyed on the repository it works in. A module of its own, with no imports,
7
+ * because every path into a sandbox — a plugin's tools, a host's shell, a
8
+ * sub-agent's `prepare` — must spell it the same way.
9
+ */
10
+ /**
11
+ * The Durable Object name for one caller's sandbox for one repository.
12
+ *
13
+ * A pipe rather than a slash, so the caller half cannot forge a repository
14
+ * boundary by containing one. `repo` may be a sentinel rather than a
15
+ * repository — a scratchpad, a worktree — as long as no caller could clone
16
+ * something that collides with it. Undefined is the window before a task has
17
+ * chosen one, which resolves to a caller-level sandbox nothing works in.
18
+ */
19
+ export function sandboxName(callerKey, repo) {
20
+ return repo ? `${callerKey}|${repo}` : `${callerKey}|<unassigned>`;
21
+ }
22
+ /** The key a parent's `prepare` puts a sub-agent's sandbox name under, in its `runtime()`. */
23
+ export const SANDBOX_RUNTIME_KEY = "sandboxName";
24
+ /**
25
+ * A parent-resolved sandbox name, read off a sub-agent's `runtime()`.
26
+ * `undefined` for anything else: a parent has no runtime, and falls back to its
27
+ * own name.
28
+ */
29
+ export function sandboxNameFromRuntime(runtime) {
30
+ const value = runtime?.[SANDBOX_RUNTIME_KEY];
31
+ return typeof value === "string" && value.length > 0 ? value : undefined;
32
+ }