@agent-compose/sdk 0.4.0 → 0.5.1

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/dist/client.d.ts CHANGED
@@ -50,6 +50,10 @@ export interface RegisterWorkflowInput {
50
50
  version?: string;
51
51
  schedule?: string;
52
52
  runtimes?: RuntimeSourceInput[];
53
+ /** Human-readable description declared via
54
+ * `defineWorkflow({ description })`. Stored in template metadata and
55
+ * surfaced on the dashboard template card. */
56
+ description?: string;
53
57
  networkPolicy?: unknown;
54
58
  placeholders?: Record<string, string>;
55
59
  /** All snapshot config — `bootFrom` (where to restore at run start),
package/dist/index.js CHANGED
@@ -547,10 +547,12 @@ function isWorkflow(value) {
547
547
  // src/types/workflow.ts
548
548
  function compileRunForm(def, metadata) {
549
549
  const unknownSchema = z2.unknown();
550
+ const inputSchema = def.input ?? unknownSchema;
551
+ const outputSchema = def.output ?? unknownSchema;
550
552
  const step = {
551
553
  name: "run",
552
- input: unknownSchema,
553
- output: unknownSchema,
554
+ input: inputSchema,
555
+ output: outputSchema,
554
556
  run: async (stepCtx) => {
555
557
  const workflowCtx = {
556
558
  input: stepCtx.input,
@@ -570,8 +572,8 @@ function compileRunForm(def, metadata) {
570
572
  };
571
573
  const workflow = {
572
574
  id: "@run-form",
573
- input: unknownSchema,
574
- output: unknownSchema,
575
+ input: inputSchema,
576
+ output: outputSchema,
575
577
  steps: Object.freeze([step]),
576
578
  metadata
577
579
  };
@@ -1067,6 +1069,7 @@ async function bundleWorkflow(workflowPath, overrides) {
1067
1069
  workflowPlan: plan,
1068
1070
  networkPolicy: overrides?.networkPolicy ?? metadata.networkPolicy,
1069
1071
  placeholders: overrides?.placeholders ?? metadata.placeholders,
1072
+ ...metadata.description !== undefined ? { description: metadata.description } : {},
1070
1073
  ...inputSchema !== undefined ? { inputSchema } : {},
1071
1074
  ...outputSchema !== undefined ? { outputSchema } : {},
1072
1075
  ...metadata.snapshots !== undefined ? { snapshots: metadata.snapshots } : {},
@@ -1596,13 +1599,14 @@ function makeVercelSandboxProvider(sb, globalEnvs) {
1596
1599
  sandboxId: sb.sandboxId,
1597
1600
  commands: {
1598
1601
  async run(cmd, opts) {
1602
+ const sudo = opts?.sudo === true;
1599
1603
  if (opts?.background) {
1600
- sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts.cwd, env: mergeEnvs(opts.envs), detached: true }).catch((err) => console.error(`[sandbox] background command failed: ${err instanceof Error ? err.message : String(err)}`));
1604
+ sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts.cwd, env: mergeEnvs(opts.envs), detached: true, ...sudo ? { sudo: true } : {} }).catch((err) => console.error(`[sandbox] background command failed: ${err instanceof Error ? err.message : String(err)}`));
1601
1605
  return { exitCode: 0, stdout: "" };
1602
1606
  }
1603
1607
  const signal = opts?.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined;
1604
1608
  let stdout = "";
1605
- const handle = await sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts?.cwd, env: mergeEnvs(opts?.envs), detached: true, signal });
1609
+ const handle = await sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts?.cwd, env: mergeEnvs(opts?.envs), detached: true, signal, ...sudo ? { sudo: true } : {} });
1606
1610
  await pRetry(async (attempt) => {
1607
1611
  const h = attempt === 1 ? handle : await sb.getCommand(handle.cmdId);
1608
1612
  for await (const log of h.logs()) {
@@ -1641,8 +1645,9 @@ function makeLocalSandboxProvider() {
1641
1645
  sandboxId: "local",
1642
1646
  commands: {
1643
1647
  run(cmd, opts) {
1648
+ const finalCmd = opts?.sudo ? `sudo ${cmd}` : cmd;
1644
1649
  return new Promise((resolve, reject) => {
1645
- const proc = spawn("sh", ["-c", cmd], {
1650
+ const proc = spawn("sh", ["-c", finalCmd], {
1646
1651
  ...opts?.cwd ? { cwd: opts.cwd } : {},
1647
1652
  env: { ...process.env, ...opts?.envs ?? {} },
1648
1653
  stdio: ["ignore", "pipe", "pipe"]
@@ -547,10 +547,12 @@ function isWorkflow(value) {
547
547
  // src/types/workflow.ts
548
548
  function compileRunForm(def, metadata) {
549
549
  const unknownSchema = z2.unknown();
550
+ const inputSchema = def.input ?? unknownSchema;
551
+ const outputSchema = def.output ?? unknownSchema;
550
552
  const step = {
551
553
  name: "run",
552
- input: unknownSchema,
553
- output: unknownSchema,
554
+ input: inputSchema,
555
+ output: outputSchema,
554
556
  run: async (stepCtx) => {
555
557
  const workflowCtx = {
556
558
  input: stepCtx.input,
@@ -570,8 +572,8 @@ function compileRunForm(def, metadata) {
570
572
  };
571
573
  const workflow = {
572
574
  id: "@run-form",
573
- input: unknownSchema,
574
- output: unknownSchema,
575
+ input: inputSchema,
576
+ output: outputSchema,
575
577
  steps: Object.freeze([step]),
576
578
  metadata
577
579
  };
@@ -1067,6 +1069,7 @@ async function bundleWorkflow(workflowPath, overrides) {
1067
1069
  workflowPlan: plan,
1068
1070
  networkPolicy: overrides?.networkPolicy ?? metadata.networkPolicy,
1069
1071
  placeholders: overrides?.placeholders ?? metadata.placeholders,
1072
+ ...metadata.description !== undefined ? { description: metadata.description } : {},
1070
1073
  ...inputSchema !== undefined ? { inputSchema } : {},
1071
1074
  ...outputSchema !== undefined ? { outputSchema } : {},
1072
1075
  ...metadata.snapshots !== undefined ? { snapshots: metadata.snapshots } : {},
@@ -1596,13 +1599,14 @@ function makeVercelSandboxProvider(sb, globalEnvs) {
1596
1599
  sandboxId: sb.sandboxId,
1597
1600
  commands: {
1598
1601
  async run(cmd, opts) {
1602
+ const sudo = opts?.sudo === true;
1599
1603
  if (opts?.background) {
1600
- sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts.cwd, env: mergeEnvs(opts.envs), detached: true }).catch((err) => console.error(`[sandbox] background command failed: ${err instanceof Error ? err.message : String(err)}`));
1604
+ sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts.cwd, env: mergeEnvs(opts.envs), detached: true, ...sudo ? { sudo: true } : {} }).catch((err) => console.error(`[sandbox] background command failed: ${err instanceof Error ? err.message : String(err)}`));
1601
1605
  return { exitCode: 0, stdout: "" };
1602
1606
  }
1603
1607
  const signal = opts?.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined;
1604
1608
  let stdout = "";
1605
- const handle = await sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts?.cwd, env: mergeEnvs(opts?.envs), detached: true, signal });
1609
+ const handle = await sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts?.cwd, env: mergeEnvs(opts?.envs), detached: true, signal, ...sudo ? { sudo: true } : {} });
1606
1610
  await pRetry(async (attempt) => {
1607
1611
  const h = attempt === 1 ? handle : await sb.getCommand(handle.cmdId);
1608
1612
  for await (const log of h.logs()) {
@@ -1641,8 +1645,9 @@ function makeLocalSandboxProvider() {
1641
1645
  sandboxId: "local",
1642
1646
  commands: {
1643
1647
  run(cmd, opts) {
1648
+ const finalCmd = opts?.sudo ? `sudo ${cmd}` : cmd;
1644
1649
  return new Promise((resolve, reject) => {
1645
- const proc = spawn("sh", ["-c", cmd], {
1650
+ const proc = spawn("sh", ["-c", finalCmd], {
1646
1651
  ...opts?.cwd ? { cwd: opts.cwd } : {},
1647
1652
  env: { ...process.env, ...opts?.envs ?? {} },
1648
1653
  stdio: ["ignore", "pipe", "pipe"]
@@ -10,6 +10,11 @@ export interface SandboxCommandRunOptions {
10
10
  onStdout?: (data: string) => void;
11
11
  onStderr?: (data: string) => void;
12
12
  background?: boolean;
13
+ /** Run the command with root privileges. Vercel maps this to its native
14
+ * `sudo` flag; E2B runs the command as `user: "root"`; the local provider
15
+ * prepends `sudo`. Defaults to false. Requires the sandbox image to grant
16
+ * the command root (Vercel's runtimes do — passwordless). */
17
+ sudo?: boolean;
13
18
  }
14
19
  export interface SandboxCommandResult {
15
20
  exitCode: number;
@@ -13,6 +13,7 @@
13
13
  * LLM agent loops live in `agent(opts)` (sdk/src/agent/run-agent.ts).
14
14
  * Invoking other workflows uses `AgentComposeClient.invoke[AndWait](...)`.
15
15
  */
16
+ import { z } from "zod";
16
17
  import type { SandboxNetworkPolicy } from "../sandbox.js";
17
18
  import type { SandboxProvider } from "./sandbox.js";
18
19
  import type { AgentLifecycleEvent } from "../agent/agent-loop.js";
@@ -70,6 +71,17 @@ export interface WorkflowDefinition<TOutput = unknown, TInput extends Record<str
70
71
  /** One-line, human-readable description of what this workflow does.
71
72
  * Surfaced on the dashboard template tile + run page header. */
72
73
  description?: string;
74
+ /** Zod schema for the workflow's `input`. When declared, the SDK
75
+ * bundler captures it as JSON-Schema-shaped `inputSchema` in
76
+ * template metadata, the dashboard playground renders a typed
77
+ * form, and the engine validates dispatched payloads against it
78
+ * at the step boundary. Omit to leave inputs as `unknown` (the
79
+ * legacy default — playground falls back to a freeform JSON
80
+ * textarea). */
81
+ input?: z.ZodType<TInput>;
82
+ /** Same as `input`, for the workflow's return value. Captured into
83
+ * `outputSchema` metadata and rendered in the IO panel. */
84
+ output?: z.ZodType<TOutput>;
73
85
  run: WorkflowFn<TOutput, TInput>;
74
86
  /**
75
87
  * All snapshot config — boot source plus capture mode.
@@ -49,6 +49,10 @@ export declare class WorkflowSourceValidationError extends Error {
49
49
  export interface BundledWorkflow {
50
50
  source: string;
51
51
  manifest: WorkflowManifest;
52
+ /** One-line, human-readable description declared via
53
+ * `defineWorkflow({ description })`. Surfaced on the dashboard
54
+ * template card + run header. */
55
+ description?: string;
52
56
  networkPolicy?: SandboxNetworkPolicy;
53
57
  placeholders?: Record<string, string>;
54
58
  /** Snapshot config from the workflow definition — `bootFrom` (where to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-compose/sdk",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
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": {
package/src/client.ts CHANGED
@@ -85,6 +85,10 @@ export interface RegisterWorkflowInput {
85
85
  version?: string;
86
86
  schedule?: string;
87
87
  runtimes?: RuntimeSourceInput[];
88
+ /** Human-readable description declared via
89
+ * `defineWorkflow({ description })`. Stored in template metadata and
90
+ * surfaced on the dashboard template card. */
91
+ description?: string;
88
92
  networkPolicy?: unknown;
89
93
  placeholders?: Record<string, string>;
90
94
  /** All snapshot config — `bootFrom` (where to restore at run start),
package/src/sandbox.ts CHANGED
@@ -200,16 +200,20 @@ function makeVercelSandboxProvider(sb: any, globalEnvs?: Record<string, string>)
200
200
  sandboxId: sb.sandboxId,
201
201
  commands: {
202
202
  async run(cmd, opts) {
203
+ // Vercel's runCommand takes a native `sudo` flag — pass it on the `sh`
204
+ // invocation so the whole shell (and every command it spawns) runs as
205
+ // root, matching `sb.runCommand({ cmd, args, sudo: true })` semantics.
206
+ const sudo = opts?.sudo === true;
203
207
  if (opts?.background) {
204
208
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
205
- void (sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts.cwd, env: mergeEnvs(opts.envs), detached: true }) as Promise<any>)
209
+ void (sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts.cwd, env: mergeEnvs(opts.envs), detached: true, ...(sudo ? { sudo: true } : {}) }) as Promise<any>)
206
210
  .catch((err: unknown) => console.error(`[sandbox] background command failed: ${err instanceof Error ? err.message : String(err)}`));
207
211
  return { exitCode: 0, stdout: "" };
208
212
  }
209
213
  const signal = opts?.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined;
210
214
  let stdout = "";
211
215
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
212
- const handle: any = await sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts?.cwd, env: mergeEnvs(opts?.envs), detached: true, signal });
216
+ const handle: any = await sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts?.cwd, env: mergeEnvs(opts?.envs), detached: true, signal, ...(sudo ? { sudo: true } : {}) });
213
217
  // Reconnect to the already-running command on transient stream failures (e.g. BrotliDecompressionError).
214
218
  await pRetry(async (attempt) => {
215
219
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -261,8 +265,13 @@ export function makeLocalSandboxProvider(): SandboxProvider {
261
265
  sandboxId: "local",
262
266
  commands: {
263
267
  run(cmd, opts) {
268
+ // Prepend `sudo` so the command runs as root. The local provider
269
+ // targets the runner's own host VM (used by defineSandboxEnvironment
270
+ // recipes); harmless when the host is already root or has passwordless
271
+ // sudo, which is the only context it runs in.
272
+ const finalCmd = opts?.sudo ? `sudo ${cmd}` : cmd;
264
273
  return new Promise((resolve, reject) => {
265
- const proc = spawn("sh", ["-c", cmd], {
274
+ const proc = spawn("sh", ["-c", finalCmd], {
266
275
  ...(opts?.cwd ? { cwd: opts.cwd } : {}),
267
276
  env: { ...process.env, ...(opts?.envs ?? {}) },
268
277
  stdio: ["ignore", "pipe", "pipe"],
@@ -11,6 +11,11 @@ export interface SandboxCommandRunOptions {
11
11
  onStdout?: (data: string) => void;
12
12
  onStderr?: (data: string) => void;
13
13
  background?: boolean;
14
+ /** Run the command with root privileges. Vercel maps this to its native
15
+ * `sudo` flag; E2B runs the command as `user: "root"`; the local provider
16
+ * prepends `sudo`. Defaults to false. Requires the sandbox image to grant
17
+ * the command root (Vercel's runtimes do — passwordless). */
18
+ sudo?: boolean;
14
19
  }
15
20
 
16
21
  export interface SandboxCommandResult {
@@ -92,6 +92,17 @@ export interface WorkflowDefinition<
92
92
  /** One-line, human-readable description of what this workflow does.
93
93
  * Surfaced on the dashboard template tile + run page header. */
94
94
  description?: string;
95
+ /** Zod schema for the workflow's `input`. When declared, the SDK
96
+ * bundler captures it as JSON-Schema-shaped `inputSchema` in
97
+ * template metadata, the dashboard playground renders a typed
98
+ * form, and the engine validates dispatched payloads against it
99
+ * at the step boundary. Omit to leave inputs as `unknown` (the
100
+ * legacy default — playground falls back to a freeform JSON
101
+ * textarea). */
102
+ input?: z.ZodType<TInput>;
103
+ /** Same as `input`, for the workflow's return value. Captured into
104
+ * `outputSchema` metadata and rendered in the IO panel. */
105
+ output?: z.ZodType<TOutput>;
95
106
  run: WorkflowFn<TOutput, TInput>;
96
107
  /**
97
108
  * All snapshot config — boot source plus capture mode.
@@ -168,15 +179,25 @@ export interface WorkflowDefinition<
168
179
  * under that one step. Authors keep the same source; the dashboard sees
169
180
  * the same timeline shape.
170
181
  */
182
+ // TODO: collapse run-form into pure sugar over step-form. Run-form
183
+ // already compiles to step-form-with-one-step here, so maintaining two
184
+ // authoring surfaces is API duplication that periodically drifts (see
185
+ // the input/output thread-through bug fixed on 2026-05-20: step-form
186
+ // preserved schemas, run-form silently stamped z.unknown()). Cleaner:
187
+ // turn `defineWorkflow({ run })` into a thin wrapper that calls the
188
+ // step builder with a synthesised `{name:"run", input, output, run}`
189
+ // step, then delete this function. Runtime stays identical.
171
190
  function compileRunForm<TOutput, TInput extends Record<string, unknown>>(
172
191
  def: WorkflowDefinition<TOutput, TInput>,
173
192
  metadata: WorkflowMetadata,
174
193
  ): Workflow<TInput, TOutput> {
175
194
  const unknownSchema = z.unknown() as z.ZodType<unknown>;
195
+ const inputSchema: z.ZodType<TInput> = def.input ?? (unknownSchema as z.ZodType<TInput>);
196
+ const outputSchema: z.ZodType<TOutput> = def.output ?? (unknownSchema as z.ZodType<TOutput>);
176
197
  const step: Step<TInput, TOutput> = {
177
198
  name: "run",
178
- input: unknownSchema as z.ZodType<TInput>,
179
- output: unknownSchema as z.ZodType<TOutput>,
199
+ input: inputSchema,
200
+ output: outputSchema,
180
201
  run: async (stepCtx) => {
181
202
  // Proxy the legacy WorkflowCtx hooks to the step's collector-backed
182
203
  // implementations. The engine harvests the snapshot after execute
@@ -198,8 +219,8 @@ function compileRunForm<TOutput, TInput extends Record<string, unknown>>(
198
219
  };
199
220
  const workflow: Workflow<TInput, TOutput> = {
200
221
  id: "@run-form",
201
- input: unknownSchema as z.ZodType<TInput>,
202
- output: unknownSchema as z.ZodType<TOutput>,
222
+ input: inputSchema,
223
+ output: outputSchema,
203
224
  steps: Object.freeze([step]),
204
225
  metadata,
205
226
  };
@@ -63,6 +63,10 @@ export class WorkflowSourceValidationError extends Error {
63
63
  export interface BundledWorkflow {
64
64
  source: string;
65
65
  manifest: WorkflowManifest;
66
+ /** One-line, human-readable description declared via
67
+ * `defineWorkflow({ description })`. Surfaced on the dashboard
68
+ * template card + run header. */
69
+ description?: string;
66
70
  networkPolicy?: SandboxNetworkPolicy;
67
71
  placeholders?: Record<string, string>;
68
72
  /** Snapshot config from the workflow definition — `bootFrom` (where to
@@ -292,6 +296,7 @@ export async function bundleWorkflow(
292
296
  workflowPlan: plan,
293
297
  networkPolicy: overrides?.networkPolicy ?? metadata.networkPolicy,
294
298
  placeholders: overrides?.placeholders ?? metadata.placeholders,
299
+ ...(metadata.description !== undefined ? { description: metadata.description } : {}),
295
300
  ...(inputSchema !== undefined ? { inputSchema } : {}),
296
301
  ...(outputSchema !== undefined ? { outputSchema } : {}),
297
302
  ...(metadata.snapshots !== undefined ? { snapshots: metadata.snapshots } : {}),