@agent-compose/sdk 0.5.5 → 0.5.7
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/agent-loop-contract.test.d.ts +1 -0
- package/dist/agent/agent-loop.d.ts +1 -1
- package/dist/client.d.ts +65 -23
- package/dist/index.d.ts +4 -2
- package/dist/index.js +389 -109
- package/dist/runtimes/_cli-agent.d.ts +25 -8
- package/dist/runtimes/amp.d.ts +7 -6
- package/dist/runtimes/claude.d.ts +9 -1
- package/dist/runtimes/cli-agent.test.d.ts +9 -0
- package/dist/runtimes/codex.d.ts +6 -5
- package/dist/runtimes/openai-desktop.js +387 -109
- package/dist/sandbox-errors.d.ts +49 -0
- package/dist/sandbox.d.ts +68 -13
- package/dist/step-invocation/protocol.d.ts +6 -0
- package/dist/types/sandbox-environment.d.ts +1 -10
- package/dist/types/sandbox.d.ts +27 -3
- package/dist/types/workflow-metadata.d.ts +88 -23
- package/dist/types/workflow.d.ts +38 -10
- package/dist/utils/bundler.d.ts +38 -9
- package/dist/workflow-steps/workflow.d.ts +1 -3
- package/package.json +2 -2
- package/src/agent/agent-loop.ts +56 -7
- package/src/client.ts +144 -25
- package/src/index.ts +4 -2
- package/src/runtimes/_cli-agent.ts +75 -42
- package/src/runtimes/amp.ts +11 -6
- package/src/runtimes/claude.ts +66 -8
- package/src/runtimes/codex.ts +10 -5
- package/src/sandbox-errors.ts +53 -0
- package/src/sandbox.ts +378 -56
- package/src/step-invocation/invoker.ts +66 -8
- package/src/step-invocation/protocol.ts +9 -0
- package/src/step-invocation/server.ts +27 -4
- package/src/types/sandbox-environment.ts +1 -11
- package/src/types/sandbox.ts +28 -3
- package/src/types/workflow-metadata.ts +97 -26
- package/src/types/workflow.ts +38 -11
- package/src/utils/bundler.ts +43 -13
- package/src/workflow-steps/workflow.ts +1 -3
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox-infrastructure error.
|
|
3
|
+
*
|
|
4
|
+
* Distinct from `StepExecutionError` (the user/runner step-failure wrapper):
|
|
5
|
+
* that's the workflow author's problem. A `SandboxUnavailableError` means the
|
|
6
|
+
* sandbox itself couldn't carry the step — the provider refused a command, the
|
|
7
|
+
* sandbox was reclaimed (idle/lifetime timeout, eviction), an API blip, or the
|
|
8
|
+
* connection dropped mid-stream. None of these are a fault in the customer's
|
|
9
|
+
* code, so they're surfaced as "infrastructure issue, retry" rather than
|
|
10
|
+
* blaming the user.
|
|
11
|
+
*
|
|
12
|
+
* ## Retryability is about *when*, not *which error*
|
|
13
|
+
*
|
|
14
|
+
* The Vercel provider (`sandbox.ts`) decides `retryable` purely from *where* it
|
|
15
|
+
* caught the error — not by inspecting status codes or error vocabularies:
|
|
16
|
+
* - `retryable: true` — caught before the runner launched the step (file
|
|
17
|
+
* write / command launch / reconnect refused). No user code ran, so
|
|
18
|
+
* re-provisioning a fresh sandbox and re-running the step is safe. ANY
|
|
19
|
+
* error here qualifies (sandbox reclaimed, 429, 5xx, network blip).
|
|
20
|
+
* - `retryable: false` — caught while/after the runner streamed, so user code
|
|
21
|
+
* may already have executed side effects. Surfaced honestly as an infra
|
|
22
|
+
* failure, but NOT auto-retried.
|
|
23
|
+
*
|
|
24
|
+
* ## Cross-process contract
|
|
25
|
+
*
|
|
26
|
+
* The thrown error crosses the Temporal activity→workflow serialisation
|
|
27
|
+
* boundary, which preserves the error **message** but discards custom instance
|
|
28
|
+
* fields. So the retryability signal is encoded IN the message via a stable
|
|
29
|
+
* prefix — `[sandbox-unavailable:retryable]` or `[sandbox-unavailable:terminal]`
|
|
30
|
+
* — following the same `[kind] …` convention `StepExecutionError` uses. The
|
|
31
|
+
* server (`utils/transient-errors.ts`) matches this prefix with a pure regex to
|
|
32
|
+
* classify the failure and decide whether the workflow re-provisions and
|
|
33
|
+
* retries. A unit test pins the SDK-produced string against the server matcher
|
|
34
|
+
* so the two can't drift.
|
|
35
|
+
*/
|
|
36
|
+
/** Stable message prefix. Both variants share this leading token so a single
|
|
37
|
+
* server-side regex recognises the class; the `:retryable` / `:terminal`
|
|
38
|
+
* suffix carries the recovery decision. */
|
|
39
|
+
export declare const SANDBOX_UNAVAILABLE_PREFIX = "[sandbox-unavailable";
|
|
40
|
+
export declare class SandboxUnavailableError extends Error {
|
|
41
|
+
/** True when caught before any user code ran (safe to re-provision + retry);
|
|
42
|
+
* false when the sandbox died mid/after execution. */
|
|
43
|
+
readonly retryable: boolean;
|
|
44
|
+
readonly sandboxId: string | undefined;
|
|
45
|
+
constructor(detail: string, opts: {
|
|
46
|
+
retryable: boolean;
|
|
47
|
+
sandboxId?: string;
|
|
48
|
+
});
|
|
49
|
+
}
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -7,20 +7,29 @@
|
|
|
7
7
|
import { Sandbox } from "e2b";
|
|
8
8
|
import { Sandbox as Desktop } from "@e2b/desktop";
|
|
9
9
|
import type { SandboxProvider, DesktopSandboxProvider, SandboxCommandResult } from "./types/sandbox.js";
|
|
10
|
+
import type { ConnectorRequestRules } from "./types/workflow-metadata.js";
|
|
11
|
+
import type { NetworkPolicy as VercelNetworkPolicy } from "@vercel/sandbox";
|
|
10
12
|
export type { SandboxProvider, DesktopSandboxProvider, SandboxCommandRunOptions, SandboxCommandResult } from "./types/sandbox.js";
|
|
11
13
|
export type SandboxProviderName = "vercel" | "e2b" | "e2b-desktop";
|
|
12
14
|
export declare const AGENT_COMPOSE_TAG: string;
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
* When a sandbox makes a request matching
|
|
16
|
-
*
|
|
17
|
-
* exist inside the VM.
|
|
16
|
+
* Network policy for outbound HTTPS requests — ONE shape for every provider;
|
|
17
|
+
* only the enforcement point differs. When a sandbox makes a request matching
|
|
18
|
+
* a domain in `allow`, the egress layer injects the specified headers before
|
|
19
|
+
* forwarding — credentials never exist inside the VM. Vercel's firewall
|
|
20
|
+
* enforces this natively (`requestRules` translate to native `match` rules);
|
|
21
|
+
* E2B enforces it via the embedded iron-proxy, which pulls the identical
|
|
22
|
+
* resolved policy from the server.
|
|
18
23
|
*/
|
|
19
24
|
export interface SandboxNetworkHeaderTransform {
|
|
20
25
|
headers?: Record<string, string>;
|
|
21
26
|
}
|
|
22
27
|
export interface SandboxNetworkAllowRule {
|
|
23
28
|
transform?: SandboxNetworkHeaderTransform[];
|
|
29
|
+
/** Tier-2 request gate (method/path) — the transform (and, on iron-proxy,
|
|
30
|
+
* the request itself) only applies when the request matches. Present on
|
|
31
|
+
* connector-auth rules; see `ConnectorRequestRules`. */
|
|
32
|
+
requestRules?: ConnectorRequestRules;
|
|
24
33
|
}
|
|
25
34
|
export interface SandboxNetworkSubnetPolicy {
|
|
26
35
|
allow?: string[];
|
|
@@ -34,15 +43,22 @@ export interface SandboxCreateOpts {
|
|
|
34
43
|
envs: Record<string, string>;
|
|
35
44
|
metadata: Record<string, string>;
|
|
36
45
|
timeoutMs: number;
|
|
37
|
-
/** Provider-specific template/snapshot identifier. E2B: template
|
|
46
|
+
/** Provider-specific template/snapshot identifier. E2B: template id or
|
|
47
|
+
* snapshot id (omit → E2B's default base). Vercel: snapshot id (omit → node24). */
|
|
38
48
|
template?: string;
|
|
39
|
-
/** Outbound request policy
|
|
49
|
+
/** Outbound request policy with header transforms — ONE shape for every
|
|
50
|
+
* provider; only the enforcement point differs. Vercel's firewall
|
|
51
|
+
* enforces + injects natively from this value. E2B enforces it via an
|
|
52
|
+
* EMBEDDED iron-proxy inside the VM (loopback DNS + iptables, started
|
|
53
|
+
* at boot), which fetches the identical resolved policy from the
|
|
54
|
+
* server's per-run egress-policy endpoint — so this field is not passed
|
|
55
|
+
* to E2B's API. See server/src/sandbox/iron-proxy.ts. */
|
|
40
56
|
networkPolicy?: SandboxNetworkPolicy;
|
|
41
57
|
}
|
|
42
58
|
/**
|
|
43
59
|
* What the provider reports as "currently alive" — the single input to orphan
|
|
44
60
|
* reconciliation. `metadata` is best-effort: E2B populates it from sandbox
|
|
45
|
-
* labels, Vercel
|
|
61
|
+
* labels, Vercel from sandbox tags (max 5, stamped at create). Callers
|
|
46
62
|
* that need runId correlation cross-reference `sandboxId` against their own
|
|
47
63
|
* state (server: `workflow_runs.sandbox_id` + `run_agent_sandboxes.provider_sandbox_id`).
|
|
48
64
|
*/
|
|
@@ -60,18 +76,17 @@ interface SandboxProviderDef {
|
|
|
60
76
|
* Returns the number of sandboxes currently running against this provider.
|
|
61
77
|
* Used for quota observability — account-wide, not per-instance.
|
|
62
78
|
*
|
|
63
|
-
*
|
|
64
|
-
* its own sandboxes; DD aggregates
|
|
65
|
-
*
|
|
66
|
-
* the configured project (assumed dedicated to agent-compose).
|
|
79
|
+
* Both providers scope by our AGENT_COMPOSE_TAG (E2B metadata labels,
|
|
80
|
+
* Vercel tags) so each fleet only reports its own sandboxes; DD aggregates
|
|
81
|
+
* across instances with `sum by {provider}`.
|
|
67
82
|
*/
|
|
68
83
|
getActiveCount?: (env: Record<string, string>) => Promise<number>;
|
|
69
84
|
/**
|
|
70
85
|
* Provider's view of what's currently alive for our account/fleet.
|
|
71
86
|
* The reconciliation SoT — a sandbox missing from this list IS dead,
|
|
72
87
|
* regardless of what our DB says. Implemented by listing the provider's
|
|
73
|
-
* running sandboxes; E2B filters by metadata tag, Vercel
|
|
74
|
-
*
|
|
88
|
+
* running sandboxes; E2B filters by metadata tag, Vercel by the `executor`
|
|
89
|
+
* sandbox tag.
|
|
75
90
|
*/
|
|
76
91
|
listOwned?: (env: Record<string, string>) => Promise<OwnedSandbox[]>;
|
|
77
92
|
/**
|
|
@@ -93,6 +108,46 @@ export interface ParseSseExecStreamOptions {
|
|
|
93
108
|
* Used by the agent sandbox broker in runner.ts.
|
|
94
109
|
*/
|
|
95
110
|
export declare function parseSseExecStream(body: ReadableStream<Uint8Array>, opts?: ParseSseExecStreamOptions): Promise<SandboxCommandResult>;
|
|
111
|
+
/** Matches a path containing a dot-dot segment — literal (`/../`) or
|
|
112
|
+
* percent-encoded (`%2e%2e`, `%2f` boundaries). `DOT_SEGMENT_PATH_RE2` is
|
|
113
|
+
* the RE2 form handed to Vercel's matcher: wrapped in `.*` so it behaves
|
|
114
|
+
* identically under search and full-match semantics (RE2 has no lookaround,
|
|
115
|
+
* so the guard is a positive match on the traversal pattern, used as a
|
|
116
|
+
* credential-stripping first rule). `DOT_SEGMENT_RE` is the local JS twin
|
|
117
|
+
* used to validate DECLARED prefixes. */
|
|
118
|
+
export declare const DOT_SEGMENT_PATH_RE2 = ".*(?:^|/|%2[fF])(?:\\.|%2[eE]){2}(?:/|%2[fF]|$).*";
|
|
119
|
+
/**
|
|
120
|
+
* Translate our policy shape into Vercel's native `NetworkPolicy`.
|
|
121
|
+
*
|
|
122
|
+
* The shapes are identical except for Tier-2: our `requestRules`
|
|
123
|
+
* (`{ methods, pathPrefixes }`) become Vercel `match` rules
|
|
124
|
+
* (`{ method, path: { startsWith } }`) — one rule per path prefix, since a
|
|
125
|
+
* Vercel matcher carries a single path.
|
|
126
|
+
*
|
|
127
|
+
* Dot-segment defense: prefix matching is raw — `/repos/acme/../../user`
|
|
128
|
+
* starts with `/repos/acme/` but the origin normalizes it to `/user`, riding
|
|
129
|
+
* the credential outside its declared scope. We don't get to assume the
|
|
130
|
+
* enforcer RFC-3986-normalizes before matching, so every domain with a path
|
|
131
|
+
* gate gets a transform-free FIRST rule matching dot-segment paths
|
|
132
|
+
* (first-match-wins ⇒ such requests go out credential-free), and declared
|
|
133
|
+
* prefixes themselves are validated (must start with "/", no dot-segments).
|
|
134
|
+
*
|
|
135
|
+
* Accepted divergence from iron-proxy (E2B): off-gate requests to an allowed
|
|
136
|
+
* domain pass WITHOUT the transform (Vercel terminates TLS only for
|
|
137
|
+
* transform-bearing domains and applies first-match-wins) — iron-proxy
|
|
138
|
+
* REFUSES them instead. Reachability differs; the property that matters is
|
|
139
|
+
* preserved on both substrates: the brokered credential rides only declared
|
|
140
|
+
* method/path combinations. Pinned by vercel-network-policy.test.ts.
|
|
141
|
+
*/
|
|
142
|
+
export declare function toVercelNetworkPolicy(policy: SandboxNetworkPolicy): VercelNetworkPolicy;
|
|
143
|
+
/** Vercel caps sandbox tags at 5. Build the tag set with the fleet `executor`
|
|
144
|
+
* tag always present and always winning over caller metadata — `listOwned` /
|
|
145
|
+
* orphan reconciliation scope on it, so a clobbered or dropped executor tag
|
|
146
|
+
* makes the sandbox invisible to cleanup. When metadata overflows the cap,
|
|
147
|
+
* keep executor + the lexicographically-first 4 metadata keys, so what gets
|
|
148
|
+
* dropped is deterministic rather than dependent on object insertion order. */
|
|
149
|
+
export declare const VERCEL_MAX_TAGS = 5;
|
|
150
|
+
export declare function buildVercelTags(metadata: Record<string, string>): Record<string, string>;
|
|
96
151
|
/** Minimal SandboxProvider that targets the current process's host VM.
|
|
97
152
|
* `commands.run` → `child_process.spawn`; `files.write` → `fs.writeFile`;
|
|
98
153
|
* `kill` is a no-op because the caller IS the VM. Used by
|
|
@@ -54,3 +54,9 @@ export declare function stepInputPath(stepIndex: number): string;
|
|
|
54
54
|
* context. The serveStep reads from this exact path; both halves use
|
|
55
55
|
* this helper. */
|
|
56
56
|
export declare function requestContextPath(stepIndex: number): string;
|
|
57
|
+
/** Sandbox-side path where the runner persists the sentinel line as a
|
|
58
|
+
* FILE, keyed by the per-invocation token (unique even across resumes
|
|
59
|
+
* of the same step index). stdout is the fast path, but providers can
|
|
60
|
+
* drop the tail of a heavy stdout stream — the invoker falls back to
|
|
61
|
+
* reading this file when no sentinel is found on stdout. */
|
|
62
|
+
export declare function stepResultFilePath(token: string): string;
|
|
@@ -45,17 +45,8 @@ export interface SandboxEnvironmentDefinition {
|
|
|
45
45
|
* useful — an env with no snapshot can't be referenced as a
|
|
46
46
|
* `bootFrom` on another workflow). */
|
|
47
47
|
snapshots?: SnapshotConfig;
|
|
48
|
-
/** Override the sugar's `memory: false` default. Setup workflows
|
|
49
|
-
* don't typically benefit from memory extraction; opt in explicitly
|
|
50
|
-
* when they do. */
|
|
51
|
-
memory?: boolean;
|
|
52
48
|
}
|
|
53
49
|
/** Sugar over `defineWorkflow` for setup-only workflows that exist to
|
|
54
50
|
* capture a snapshot. The workflow takes no meaningful input and returns
|
|
55
|
-
* nothing — its value is the side effect on the sandbox VM.
|
|
56
|
-
*
|
|
57
|
-
* Defaults `memory: false` because sandbox environments emit setup
|
|
58
|
-
* output (npm installs, command exit codes) rather than agent traces
|
|
59
|
-
* worth memorising. Authors can opt in explicitly via `env.memory:
|
|
60
|
-
* true` if their environment somehow does want extraction. */
|
|
51
|
+
* nothing — its value is the side effect on the sandbox VM. */
|
|
61
52
|
export declare function defineSandboxEnvironment(env: SandboxEnvironmentDefinition): Workflow<Record<string, unknown>, void>;
|
package/dist/types/sandbox.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Sandbox abstraction — the compute environment an agent runs inside.
|
|
3
3
|
* Provider-agnostic: E2B, Vercel, Docker, or any other backend implements this.
|
|
4
4
|
*/
|
|
5
|
+
import type { SandboxNetworkPolicy } from "../sandbox.js";
|
|
5
6
|
/** Base compute interface — pure I/O, no filesystem path or git concerns. */
|
|
6
7
|
export interface SandboxCommandRunOptions {
|
|
7
8
|
cwd?: string;
|
|
@@ -20,6 +21,22 @@ export interface SandboxCommandResult {
|
|
|
20
21
|
stdout: string;
|
|
21
22
|
stderr: string;
|
|
22
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* A sandbox provider implements the RAW provider operations only. It does NOT
|
|
26
|
+
* implement transient-failure retry/backoff: reconnecting and snapshotting both
|
|
27
|
+
* pause/resume/transition the sandbox and race with it being reclaimed/stopped
|
|
28
|
+
* under load (E2B "Paused sandbox not found", Vercel stopping-sandbox calls,
|
|
29
|
+
* 5xx/429, dropped connections). That resilience is supplied centrally —
|
|
30
|
+
* `reconnectSandbox` runs every provider's `reconnect` through
|
|
31
|
+
* `withSandboxRetry` (generic p-retry backoff, gated by
|
|
32
|
+
* `isTransientSandboxError`), and `createSandbox`/`reconnectSandbox` wrap the
|
|
33
|
+
* returned provider's `snapshot` the same way (via `withSnapshotRetry`).
|
|
34
|
+
* `create` is deliberately NOT retried: a create whose response is lost would
|
|
35
|
+
* mint a second billed sandbox and orphan the first; provision failures are
|
|
36
|
+
* re-attempted at the workflow layer instead. So: a NEW provider only writes
|
|
37
|
+
* the happy-path call; the abstraction guarantees the robust pattern, and no
|
|
38
|
+
* provider re-implements it.
|
|
39
|
+
*/
|
|
23
40
|
export interface SandboxProvider {
|
|
24
41
|
sandboxId: string;
|
|
25
42
|
/** Working directory for the agent process. Set by onStart after environment setup. */
|
|
@@ -31,9 +48,9 @@ export interface SandboxProvider {
|
|
|
31
48
|
write(path: string, content: string): Promise<void>;
|
|
32
49
|
};
|
|
33
50
|
kill(): Promise<void>;
|
|
34
|
-
/** Capture the running sandbox's state as a reusable snapshot. Vercel
|
|
35
|
-
*
|
|
36
|
-
*
|
|
51
|
+
/** Capture the running sandbox's state as a reusable snapshot. Vercel and E2B
|
|
52
|
+
* both support it natively (E2B via `createSnapshot()`); providers without a
|
|
53
|
+
* live-snapshot primitive return `undefined`. Used by the server's
|
|
37
54
|
* `--build` flow to stamp the snapshot id on the workflow row.
|
|
38
55
|
*
|
|
39
56
|
* `sizeBytes` is the on-disk footprint reported by the provider. May
|
|
@@ -43,6 +60,13 @@ export interface SandboxProvider {
|
|
|
43
60
|
snapshotId: string;
|
|
44
61
|
sizeBytes?: number;
|
|
45
62
|
}>;
|
|
63
|
+
/** Replace the live sandbox's egress policy in place. Vercel implements
|
|
64
|
+
* it via `sandbox.update({ networkPolicy })` (2.x) so the server can
|
|
65
|
+
* push a freshly resolved policy — with re-minted connector access
|
|
66
|
+
* tokens — before each step instead of relying on the policy baked at
|
|
67
|
+
* create. Providers whose enforcement lives inside the VM (E2B
|
|
68
|
+
* iron-proxy) leave it undefined. */
|
|
69
|
+
updateNetworkPolicy?(policy: SandboxNetworkPolicy): Promise<void>;
|
|
46
70
|
}
|
|
47
71
|
/** Stateless provider-level snapshot deletion — no live sandbox needed,
|
|
48
72
|
* called from customer-initiated `DELETE /workflows/:runId/snapshot`. */
|
|
@@ -18,28 +18,32 @@ import type { Processor } from "../processors/processor.js";
|
|
|
18
18
|
* The bundler reads these from the default export at registration time
|
|
19
19
|
* and forwards them to the server's POST /api/v1/templates payload.
|
|
20
20
|
*/
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
export type WorkflowMemoryConfig = boolean;
|
|
25
|
-
/** Where a run boots from. The snapshot id is the unit of identity —
|
|
26
|
-
* each captured snapshot already records the workflow + version it
|
|
27
|
-
* came from on the snapshot row, so there's no separate "latest of
|
|
28
|
-
* workflow X" resolution at dispatch time. Operators pick a snapshot
|
|
29
|
-
* from the dashboard snapshot list (or `agentc snapshot list`) and
|
|
30
|
-
* paste the id here.
|
|
31
|
-
*
|
|
32
|
-
* Omit `bootFrom` entirely to boot a fresh base sandbox. */
|
|
21
|
+
/** Boot from a specific captured snapshot, addressed by its id. Operators
|
|
22
|
+
* pick one from the dashboard snapshot list (or `agentc snapshot list`) and
|
|
23
|
+
* paste the id here. */
|
|
33
24
|
export type BootSnapshot = {
|
|
34
25
|
snapshotId: string;
|
|
35
26
|
};
|
|
27
|
+
/** Boot from this workflow's OWN most recent snapshot, scoped to its content
|
|
28
|
+
* hash. The first run — and the first after a re-register changes the source
|
|
29
|
+
* — finds none and boots a fresh base sandbox; `"reuse"` also implies
|
|
30
|
+
* `saveLatest`, so that run captures a snapshot and every run after it boots
|
|
31
|
+
* from it. This lets a runtime install its tooling once (e.g. a CLI-agent
|
|
32
|
+
* runtime `npm i -g`'ing its CLI) and skip the install on every later run,
|
|
33
|
+
* with no hardcoded snapshot id to manage. Re-registering with changed
|
|
34
|
+
* source rolls the content hash, which transparently invalidates the cache
|
|
35
|
+
* and re-installs on the next run. */
|
|
36
|
+
export type ReuseSnapshot = "reuse";
|
|
36
37
|
/** Snapshot configuration — boot source plus capture knobs. One object
|
|
37
38
|
* per workflow / per invocation; collapsing boot + capture under a
|
|
38
39
|
* single key reads as "all snapshot config lives here." */
|
|
39
40
|
export interface SnapshotConfig {
|
|
40
|
-
/** Where the runner restores from at run start
|
|
41
|
-
*
|
|
42
|
-
|
|
41
|
+
/** Where the runner restores from at run start:
|
|
42
|
+
* - `{ snapshotId }` — a specific captured snapshot.
|
|
43
|
+
* - `"reuse"` — this workflow's own latest snapshot (content-hash scoped);
|
|
44
|
+
* fresh on the first run / after a re-register. Implies `saveLatest`.
|
|
45
|
+
* - omitted — a fresh base sandbox. */
|
|
46
|
+
bootFrom?: BootSnapshot | ReuseSnapshot;
|
|
43
47
|
/** Capture the sandbox state on terminal success. The latest pointer
|
|
44
48
|
* on `workflow_runs.vercel_snapshot_id` always tracks the most
|
|
45
49
|
* recent capture; without `retainSteps`, prior captures are deleted
|
|
@@ -71,6 +75,65 @@ export interface IOSchema {
|
|
|
71
75
|
/** Alias kept for backwards source-compatibility with the original
|
|
72
76
|
* output-only release. New code should prefer `IOSchema`. */
|
|
73
77
|
export type OutputSchema = IOSchema;
|
|
78
|
+
/** Per-connector HTTP request matcher (Tier-2 capability narrowing). The
|
|
79
|
+
* iron-proxy / firewall consults these when deciding whether to attach the
|
|
80
|
+
* brokered Authorization header to an outbound request. A request to the
|
|
81
|
+
* connector host whose method is not in `methods`, or whose path matches no
|
|
82
|
+
* entry in `pathPrefixes`, is refused (403) and the token is WITHHELD. */
|
|
83
|
+
export interface ConnectorRequestRules {
|
|
84
|
+
/** Allowed HTTP methods (upper-case). Omit = any method (subject to
|
|
85
|
+
* `access`). */
|
|
86
|
+
methods?: string[];
|
|
87
|
+
/** Allowed path prefixes (matched against the request path, case-
|
|
88
|
+
* sensitive). Omit = any path. */
|
|
89
|
+
pathPrefixes?: string[];
|
|
90
|
+
}
|
|
91
|
+
/** One provider entry in a workflow's `connectors` declaration (ADR-0007).
|
|
92
|
+
* Scopes are the provider-side OAuth scopes the workflow's API calls
|
|
93
|
+
* need; registration warns when no installed grant covers them. */
|
|
94
|
+
export interface ConnectorRequirement {
|
|
95
|
+
scopes?: string[];
|
|
96
|
+
/** Coarse capability bound on the brokered token. `"read"` defaults the
|
|
97
|
+
* allowed methods to GET/HEAD/OPTIONS (unless `request.methods` overrides)
|
|
98
|
+
* and, for the GitHub App provider, narrows the minted installation token
|
|
99
|
+
* to `contents: read`. `"write"` permits all methods. Omit = `"write"`
|
|
100
|
+
* (unchanged broad behaviour). */
|
|
101
|
+
access?: "read" | "write";
|
|
102
|
+
/** Fine-grained per-request matcher enforced at the egress proxy. */
|
|
103
|
+
request?: ConnectorRequestRules;
|
|
104
|
+
}
|
|
105
|
+
/** Map of provider key (`github`, `slack`, …) → requirement. Declaring a
|
|
106
|
+
* provider here makes the server resolve an authorized grant at dispatch
|
|
107
|
+
* and inject a fresh access token at the network layer — workflow code
|
|
108
|
+
* calls the provider API with plain fetch/SDKs and never sees the token. */
|
|
109
|
+
export type ConnectorRequirements = Record<string, ConnectorRequirement>;
|
|
110
|
+
/** Marks this workflow as a catalogue OPERATION of a connector — e.g. the
|
|
111
|
+
* `create-issue` operation of the `github` connector. Operations are
|
|
112
|
+
* ordinary workflows (deterministic or agent-driven) that declare the
|
|
113
|
+
* matching `connectors` requirement; the tag is what groups them under
|
|
114
|
+
* the connector in the dashboard catalogue and the agent-facing
|
|
115
|
+
* operation listing. Together with `description` + `input`/`output`
|
|
116
|
+
* schemas this gives every operation MCP-tool-like self-description:
|
|
117
|
+
* what it does, what it takes, what it produces. */
|
|
118
|
+
export interface ConnectorOperationTag {
|
|
119
|
+
provider: string;
|
|
120
|
+
operation: string;
|
|
121
|
+
}
|
|
122
|
+
/** Tier-1 invoke ACL. When a workflow brokers a connector credential AND
|
|
123
|
+
* declares an `invokePolicy`, dispatch evaluates the calling principal
|
|
124
|
+
* against it BEFORE binding any grant. A caller matching ANY provided
|
|
125
|
+
* clause passes; matching none → HTTP 403. Omit = whole team (unchanged).
|
|
126
|
+
*
|
|
127
|
+
* Each clause is OR-combined:
|
|
128
|
+
* - `users`: `"owner"` (only the registering user) or an array of user
|
|
129
|
+
* ids / emails the caller must be one of.
|
|
130
|
+
* - `apiKeys`: api-key ids permitted to invoke.
|
|
131
|
+
* - `workflows`: parent workflow names permitted to child-invoke this one. */
|
|
132
|
+
export interface InvokePolicy {
|
|
133
|
+
users?: "owner" | string[];
|
|
134
|
+
apiKeys?: string[];
|
|
135
|
+
workflows?: string[];
|
|
136
|
+
}
|
|
74
137
|
export interface WorkflowMetadata {
|
|
75
138
|
/** One-line, human-readable description of what the workflow does.
|
|
76
139
|
* Surfaced on the dashboard template tile + run page header. Authors
|
|
@@ -92,14 +155,16 @@ export interface WorkflowMetadata {
|
|
|
92
155
|
/** All snapshot config — boot source + capture mode. */
|
|
93
156
|
snapshots?: SnapshotConfig;
|
|
94
157
|
processors?: readonly Processor[];
|
|
95
|
-
/**
|
|
96
|
-
*
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
*
|
|
101
|
-
|
|
102
|
-
|
|
158
|
+
/** Connector requirements — providers whose APIs this workflow calls.
|
|
159
|
+
* Dispatch resolves an authorized grant per provider and injects a
|
|
160
|
+
* fresh access token at the network layer (ADR-0007). */
|
|
161
|
+
connectors?: ConnectorRequirements;
|
|
162
|
+
/** Catalogue tag marking this workflow as an operation of a connector.
|
|
163
|
+
* See `ConnectorOperationTag`. */
|
|
164
|
+
connectorOperation?: ConnectorOperationTag;
|
|
165
|
+
/** Tier-1 invoke ACL — who may dispatch this connector-brokering workflow.
|
|
166
|
+
* See `InvokePolicy`. */
|
|
167
|
+
invokePolicy?: InvokePolicy;
|
|
103
168
|
}
|
|
104
169
|
/**
|
|
105
170
|
* Pull the server-readable declarations off a source object (run-form
|
package/dist/types/workflow.d.ts
CHANGED
|
@@ -21,13 +21,14 @@ import type { Processor } from "../processors/processor.js";
|
|
|
21
21
|
import type { StepWorkflowDefinition } from "../workflow-steps/workflow.js";
|
|
22
22
|
import type { Workflow } from "../workflow-steps/types.js";
|
|
23
23
|
import type { BaseExecutionContext } from "./execution-context.js";
|
|
24
|
+
import { type ConnectorRequirements, type ConnectorOperationTag, type InvokePolicy } from "./workflow-metadata.js";
|
|
24
25
|
export type { WorkflowRun, InvokeChild, BaseExecutionContext } from "./execution-context.js";
|
|
25
26
|
export type { WorkflowMetadata } from "./workflow-metadata.js";
|
|
26
27
|
export interface AgentEventSink {
|
|
27
28
|
emit(event: AgentLifecycleEvent): void | Promise<void>;
|
|
28
29
|
}
|
|
29
|
-
import type {
|
|
30
|
-
export type {
|
|
30
|
+
import type { SnapshotConfig, BootSnapshot, ReuseSnapshot, IOSchema, OutputSchema } from "./workflow-metadata.js";
|
|
31
|
+
export type { SnapshotConfig, BootSnapshot, ReuseSnapshot, IOSchema, OutputSchema };
|
|
31
32
|
/** Turn/iteration budget for `agent(opts)`. Re-exported here so authors
|
|
32
33
|
* can type per-invoke budget overrides they pass as workflow input. */
|
|
33
34
|
export interface AgentBudget {
|
|
@@ -135,14 +136,41 @@ export interface WorkflowDefinition<TOutput = unknown, TInput extends Record<str
|
|
|
135
136
|
* }
|
|
136
137
|
*/
|
|
137
138
|
placeholders?: Record<string, string>;
|
|
138
|
-
/**
|
|
139
|
-
*
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Connector requirements (ADR-0007). Declaring a provider makes the
|
|
141
|
+
* server resolve an authorized OAuth grant for the calling principal at
|
|
142
|
+
* dispatch time and inject a fresh access token at the network layer —
|
|
143
|
+
* workflow code talks to the provider API with plain fetch/SDKs and
|
|
144
|
+
* never holds the credential.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* connectors: { github: { scopes: ["repo"] } }
|
|
148
|
+
*/
|
|
149
|
+
connectors?: ConnectorRequirements;
|
|
150
|
+
/**
|
|
151
|
+
* Marks this workflow as a catalogue OPERATION of a connector — e.g.
|
|
152
|
+
* the `create-issue` operation of the `github` connector. Pair with
|
|
153
|
+
* `description` + `input`/`output` schemas so the operation is fully
|
|
154
|
+
* self-describing (MCP-tool-like) to humans and agents browsing the
|
|
155
|
+
* connector catalogue.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* connectorOperation: { provider: "github", operation: "create-issue" }
|
|
159
|
+
*/
|
|
160
|
+
connectorOperation?: ConnectorOperationTag;
|
|
161
|
+
/**
|
|
162
|
+
* Tier-1 invoke ACL (connector credential boundary). When this workflow
|
|
163
|
+
* declares `connectors` (it brokers a credential) AND an `invokePolicy`,
|
|
164
|
+
* the server evaluates the calling principal against the policy BEFORE
|
|
165
|
+
* binding any grant — a caller matching no clause is refused with HTTP
|
|
166
|
+
* 403. Has no effect on workflows that declare no connectors. Omit to
|
|
167
|
+
* leave the workflow invokable by the whole team.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* connectors: { github: { access: "read" } },
|
|
171
|
+
* invokePolicy: { users: "owner", workflows: ["nightly-orchestrator"] }
|
|
172
|
+
*/
|
|
173
|
+
invokePolicy?: InvokePolicy;
|
|
146
174
|
}
|
|
147
175
|
/**
|
|
148
176
|
* Declare a workflow. Two forms; both return a `Workflow` whose
|
package/dist/utils/bundler.d.ts
CHANGED
|
@@ -19,14 +19,40 @@
|
|
|
19
19
|
* this function returns alongside the bundled bytes, and cross-checks the
|
|
20
20
|
* manifest's `sourceHash` against the source it received.
|
|
21
21
|
*/
|
|
22
|
-
import type {
|
|
22
|
+
import type { SnapshotConfig } from "../types/workflow.js";
|
|
23
|
+
import type { IOSchema, ConnectorRequirements, ConnectorOperationTag, InvokePolicy } from "../types/workflow-metadata.js";
|
|
23
24
|
import type { SandboxNetworkPolicy } from "../sandbox.js";
|
|
24
25
|
import { type WorkflowPlan } from "../types/workflow-plan.js";
|
|
25
26
|
/** Bumped when the manifest contract changes in a way the server should
|
|
26
27
|
* notice. The server pins its manifest schema to this exact value (no
|
|
27
28
|
* `>=` accepted) so a manifest forged with a future version is refused
|
|
28
|
-
* by zod before any other validation runs.
|
|
29
|
-
|
|
29
|
+
* by zod before any other validation runs. NOTE: this gates NEW
|
|
30
|
+
* registrations only — nothing re-validates stored `workflows.metadata`
|
|
31
|
+
* rows, so a non-additive metadata change ALSO needs a server-side
|
|
32
|
+
* warning for already-registered workflows (see `warnRetiredMetadataKeys`
|
|
33
|
+
* in server/src/engine/dispatch.ts).
|
|
34
|
+
*
|
|
35
|
+
* v2: `memory` / `postRunHooks` registration options removed — bundles
|
|
36
|
+
* from CLIs that still emit them are refused at register.
|
|
37
|
+
*
|
|
38
|
+
* TODO(registration-versioning): turn this constant into a real migration
|
|
39
|
+
* policy so breaking changes start with a number, not an investigation:
|
|
40
|
+
* 1. Server keeps a MIN_SUPPORTED_BUNDLER_VERSION alongside the exact
|
|
41
|
+
* pin — versions in [min, current] keep dispatching; below min gets
|
|
42
|
+
* an ACTIONABLE 409 naming the agentc version to re-register with
|
|
43
|
+
* (today, pre-enforcement templates with no manifest at all 409 with
|
|
44
|
+
* no migration path — see dispatch.ts).
|
|
45
|
+
* 2. Stamp the SDK version into the manifest at registration, so
|
|
46
|
+
* runtime-compat breaks (not just bundler-format breaks) are also
|
|
47
|
+
* queryable per stored workflow.
|
|
48
|
+
* 3. Operator audit = one query: workflows whose
|
|
49
|
+
* metadata.manifest.bundlerVersion < current (or missing), grouped
|
|
50
|
+
* by team/owner — the definitive "who needs migration" list before
|
|
51
|
+
* any breaking release.
|
|
52
|
+
* 4. Dashboard badge on stale templates: "registered with an older
|
|
53
|
+
* toolchain — re-register to update" (pairs with the template
|
|
54
|
+
* connector/metadata card work). */
|
|
55
|
+
export declare const BUNDLER_VERSION = 2;
|
|
30
56
|
/**
|
|
31
57
|
* Manifest emitted alongside the bundled source. Fully serialisable JSON;
|
|
32
58
|
* the server validates its shape with zod and never inspects the source
|
|
@@ -59,16 +85,19 @@ export interface BundledWorkflow {
|
|
|
59
85
|
* restore at run start), `save`, `retain`. */
|
|
60
86
|
snapshots?: SnapshotConfig;
|
|
61
87
|
workflowPlan: WorkflowPlan;
|
|
62
|
-
/** Workflow Memory extractor config. */
|
|
63
|
-
memory?: WorkflowMemoryConfig;
|
|
64
|
-
/** Ordered post-hook workflow names declared on the workflow. */
|
|
65
|
-
postRunHooks?: readonly string[];
|
|
66
88
|
/** Compact JSON-Schema-shaped description of the workflow's input
|
|
67
89
|
* type. Extracted from the workflow's declared `input` zod schema
|
|
68
90
|
* at bundle time; undefined when the schema is `z.unknown()`. */
|
|
69
|
-
inputSchema?:
|
|
91
|
+
inputSchema?: IOSchema;
|
|
70
92
|
/** Same for the workflow's output zod schema. */
|
|
71
|
-
outputSchema?:
|
|
93
|
+
outputSchema?: IOSchema;
|
|
94
|
+
/** Connector requirements declared on the workflow (ADR-0007). */
|
|
95
|
+
connectors?: ConnectorRequirements;
|
|
96
|
+
/** Connector-catalogue operation tag, when this workflow is one. */
|
|
97
|
+
connectorOperation?: ConnectorOperationTag;
|
|
98
|
+
/** Tier-1 invoke ACL — who may dispatch this connector-brokering
|
|
99
|
+
* workflow (`defineWorkflow({ invokePolicy })`). */
|
|
100
|
+
invokePolicy?: InvokePolicy;
|
|
72
101
|
}
|
|
73
102
|
/**
|
|
74
103
|
* Parse the bundled source and assert the default export is a CallExpression
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import type { z } from "zod";
|
|
22
22
|
import type { Step, Workflow } from "./types.js";
|
|
23
|
-
import type { SnapshotConfig
|
|
23
|
+
import type { SnapshotConfig } from "../types/workflow-metadata.js";
|
|
24
24
|
import type { SandboxNetworkPolicy } from "../sandbox.js";
|
|
25
25
|
import type { Processor } from "../processors/processor.js";
|
|
26
26
|
export interface WorkflowBuilder<TInput, TCurrent> {
|
|
@@ -46,8 +46,6 @@ export interface StepWorkflowDefinition<TInput, TOutput> {
|
|
|
46
46
|
networkPolicy?: SandboxNetworkPolicy;
|
|
47
47
|
placeholders?: Record<string, string>;
|
|
48
48
|
snapshots?: SnapshotConfig;
|
|
49
|
-
memory?: WorkflowMemoryConfig;
|
|
50
|
-
postRunHooks?: readonly string[];
|
|
51
49
|
processors?: readonly Processor[];
|
|
52
50
|
}
|
|
53
51
|
export declare function createStepWorkflow<TInput, TOutput>(opts: StepWorkflowDefinition<TInput, TOutput>): WorkflowBuilder<TInput, TInput>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-compose/sdk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"description": "Client library for agent-compose — define agents, runtimes, and workflows, and invoke them against an agent-compose server.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@babel/parser": "^7.29.3",
|
|
65
65
|
"@babel/types": "^7.29.0",
|
|
66
66
|
"@e2b/desktop": "^1.1.2",
|
|
67
|
-
"@vercel/sandbox": "^
|
|
67
|
+
"@vercel/sandbox": "^2.2.0",
|
|
68
68
|
"ai": "^6.0.175",
|
|
69
69
|
"e2b": "^2.3.0",
|
|
70
70
|
"ofetch": "^1.5.1",
|