@agent-compose/sdk 0.5.6 → 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.
@@ -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
- * Vercel-compatible network policy for outbound HTTPS requests.
15
- * When a sandbox makes a request matching a domain in `allow`, the Vercel
16
- * firewall injects the specified headers before forwarding — credentials never
17
- * exist inside the VM. E2B ignores this field (future self-hosted mapping TBD).
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 ID; Vercel: snapshot ID. */
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. Vercel only — E2B silently ignores. */
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 leaves it empty (the API has no metadata field). Callers
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
- * E2B scopes by our AGENT_COMPOSE_TAG metadata so each machine only reports
64
- * its own sandboxes; DD aggregates across instances with `sum by {provider}`.
65
- * Vercel has no user metadata support, so it counts every running sandbox in
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 lists the whole
74
- * project (it has no metadata search). 200-row cap applies to Vercel.
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>;
@@ -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
- * supports it natively; E2B's model is Dockerfile-based and doesn't map
36
- * cleanly — `undefined` on providers that don't. Used by the server's
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,10 +18,6 @@ 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
- /** Whether the built-in Workflow Memory extractor should run after this
22
- * workflow completes. Boolean toggle — custom post-run workflows live
23
- * in the separate `postRunHooks` array on `WorkflowMetadata`. */
24
- export type WorkflowMemoryConfig = boolean;
25
21
  /** Boot from a specific captured snapshot, addressed by its id. Operators
26
22
  * pick one from the dashboard snapshot list (or `agentc snapshot list`) and
27
23
  * paste the id here. */
@@ -79,6 +75,65 @@ export interface IOSchema {
79
75
  /** Alias kept for backwards source-compatibility with the original
80
76
  * output-only release. New code should prefer `IOSchema`. */
81
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
+ }
82
137
  export interface WorkflowMetadata {
83
138
  /** One-line, human-readable description of what the workflow does.
84
139
  * Surfaced on the dashboard template tile + run page header. Authors
@@ -100,14 +155,16 @@ export interface WorkflowMetadata {
100
155
  /** All snapshot config — boot source + capture mode. */
101
156
  snapshots?: SnapshotConfig;
102
157
  processors?: readonly Processor[];
103
- /** Run the built-in memory extractor after this workflow completes.
104
- * Opt-in; defaults to false when omitted. */
105
- memory?: boolean;
106
- /** Ordered list of workflow names that run after this workflow
107
- * completes. The runtime dispatches them in declaration order; the
108
- * built-in memory extractor (when `memory: true`) runs as a separate
109
- * hook alongside whatever's declared here. */
110
- postRunHooks?: readonly string[];
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;
111
168
  }
112
169
  /**
113
170
  * Pull the server-readable declarations off a source object (run-form
@@ -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 { WorkflowMemoryConfig, SnapshotConfig, BootSnapshot, ReuseSnapshot, IOSchema, OutputSchema } from "./workflow-metadata.js";
30
- export type { WorkflowMemoryConfig, SnapshotConfig, BootSnapshot, ReuseSnapshot, IOSchema, OutputSchema };
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
- /** Run the built-in memory extractor after this workflow completes.
139
- * Opt-in; defaults to false when omitted. */
140
- memory?: WorkflowMemoryConfig;
141
- /** Ordered list of workflow names to dispatch as post-hooks after
142
- * this workflow completes. Each hook receives the source run's
143
- * context. The memory extractor (when `memory: true`) runs as an
144
- * additional hook alongside these. */
145
- postRunHooks?: readonly string[];
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
@@ -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 { WorkflowMemoryConfig, SnapshotConfig } from "../types/workflow.js";
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
- export declare const BUNDLER_VERSION = 1;
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?: import("../types/workflow-metadata.js").IOSchema;
91
+ inputSchema?: IOSchema;
70
92
  /** Same for the workflow's output zod schema. */
71
- outputSchema?: import("../types/workflow-metadata.js").IOSchema;
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, WorkflowMemoryConfig } from "../types/workflow-metadata.js";
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.6",
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": "^1.10.0",
67
+ "@vercel/sandbox": "^2.2.0",
68
68
  "ai": "^6.0.175",
69
69
  "e2b": "^2.3.0",
70
70
  "ofetch": "^1.5.1",