@boardwalk-labs/workflow 0.1.3 → 0.1.5

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 CHANGED
@@ -6,10 +6,11 @@ Author **Boardwalk workflows** in plain TypeScript — agent loops, schedules, d
6
6
  import { agent, output, secrets, type WorkflowMeta } from "@boardwalk-labs/workflow";
7
7
 
8
8
  export const meta = {
9
- name: "morning-digest",
9
+ slug: "morning-digest",
10
+ title: "Morning Digest",
10
11
  description: "Summarize my open issues every weekday at 9am",
11
12
  triggers: [{ kind: "cron", expr: "0 9 * * 1-5" }],
12
- secrets: [{ name: "GITHUB_TOKEN" }],
13
+ permissions: { secrets: [{ name: "GITHUB_TOKEN" }] },
13
14
  } satisfies WorkflowMeta;
14
15
 
15
16
  const token = await secrets.get("GITHUB_TOKEN");
@@ -35,8 +36,8 @@ A workflow is **a script**: the `meta` export is a **pure literal** (engines der
35
36
 
36
37
  - **`agent(prompt, opts?)`** — run an agent loop and get its final text (or `schema`-validated JSON). `model` is optional: name one explicitly, or let the engine resolve it. Loops can use **tools** (built-in or program-defined), **MCP servers**, **skills**, and **memory** — each brought **per call** on `agent()`; the manifest declares none of them.
37
38
  - **`sleep(ms | { until })`** — durable wait; the run holds, locals survive.
38
- - **`workflows.call(name, input)`** — durably invoke another workflow and await its result; idempotent across restarts. `workflows.run` is the fire-and-forget sibling.
39
- - **`secrets.get(name)`** — read a secret declared in `meta.secrets`. Resolved from your `.env` locally, from the encrypted vault on hosted Boardwalk. Secret values never reach model context — the SDK contract requires engines to redact them.
39
+ - **`workflows.call(slug, input)`** — durably invoke another workflow by its slug and await its result; idempotent across restarts. `workflows.run` is the fire-and-forget sibling.
40
+ - **`secrets.get(name)`** — read a secret declared in `permissions.secrets`. Resolved from your `.env` locally, from the encrypted vault on hosted Boardwalk. Secret values never reach model context — the SDK contract requires engines to redact them.
40
41
  - **`output(value)`** — declare the run's result.
41
42
  - **Memory = a persistent directory, per agent.** `agent(prompt, { memory: "memory/triager" })` names any workspace-relative directory; the engine auto-persists it across runs — no declaration needed. The loop gets read/write file tools scoped to it, and your code can read and write the same files. (`workspace.persist` is the separate knob for non-memory state your program manages directly.)
42
43
 
package/dist/host.d.ts CHANGED
@@ -16,7 +16,7 @@ export interface WorkflowHost {
16
16
  callWorkflow(slug: string, input: unknown, opts: CallOptions | undefined): Promise<unknown>;
17
17
  /** Hold the run for the requested duration (the run stays held while it waits; locals survive). */
18
18
  sleep(arg: SleepArg): Promise<void>;
19
- /** Resolve a granted secret to its plaintext value (fail-closed against `meta.secrets`). */
19
+ /** Resolve a granted secret to its plaintext value (fail-closed against `permissions.secrets`). */
20
20
  getSecret(name: string): Promise<string>;
21
21
  /**
22
22
  * Fire-and-forget trigger of another workflow; resolve to the new run's id WITHOUT holding for
package/dist/index.d.ts CHANGED
@@ -38,7 +38,7 @@ export declare const workflows: {
38
38
  };
39
39
  /** Hold the run for a duration or until a timestamp (the run stays held while it waits; locals survive). */
40
40
  export declare function sleep(arg: SleepArg): Promise<void>;
41
- /** Granted secrets, resolved lazily and fail-closed against `meta.secrets`. */
41
+ /** Granted secrets, resolved lazily and fail-closed against `permissions.secrets`. */
42
42
  export declare const secrets: {
43
43
  /** Resolve a granted secret to its plaintext value. */
44
44
  readonly get: (name: string) => Promise<string>;
@@ -67,7 +67,7 @@ export declare function parallel<T>(thunks: readonly (() => Promise<T>)[]): Prom
67
67
  */
68
68
  export declare function output(value: JsonValue): void;
69
69
  export { input, config } from "./host.js";
70
- export type { WorkflowMeta, Trigger, CronTrigger, WebhookTrigger, ManualTrigger, ToolGrant, McpServerRef, Concurrency, CallableBy, OrgRole, RunsOn, HostedRunsOn, HostedRunsOnObject, HostedRunnerSize, SelfHostedRunsOn, Container, SecretRef, EnvVars, EgressPolicy, RunPermissions, RunPermissionAccess, Budget, Notification, Workspace, } from "./meta.js";
70
+ export type { WorkflowMeta, Trigger, CronTrigger, WebhookTrigger, ManualTrigger, McpServerRef, Concurrency, CallableBy, OrgRole, RunsOn, HostedRunsOn, HostedRunsOnObject, HostedRunnerSize, SelfHostedRunsOn, Container, SecretRef, EnvVars, EgressPolicy, RunPermissions, RunPermissionAccess, Budget, Notification, Workspace, } from "./meta.js";
71
71
  export type { AgentOptions, ToolDef, ArtifactBody, ArtifactRef, CallOptions, PhaseOptions, SleepArg, JsonSchema, JsonValue, } from "./types.js";
72
72
  export { workflowManifestSchema, validateMeta, MetaValidationError, type WorkflowManifest, } from "./manifest.js";
73
73
  export { type RunEvent, type RunEventKind, type RunStatus, type Channel, type EventEnvelope, type TokenUsage, type ToolReturn, runEventSchema, CHANNELS, DEFAULT_CHANNELS, channelOf, matchesChannels, makeCursor, TURN_CURSOR_STRIDE, } from "./events.js";
package/dist/index.js CHANGED
@@ -57,7 +57,7 @@ export const workflows = {
57
57
  export async function sleep(arg) {
58
58
  await requireHost().sleep(arg);
59
59
  }
60
- /** Granted secrets, resolved lazily and fail-closed against `meta.secrets`. */
60
+ /** Granted secrets, resolved lazily and fail-closed against `permissions.secrets`. */
61
61
  export const secrets = {
62
62
  /** Resolve a granted secret to its plaintext value. */
63
63
  async get(name) {
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  export declare const workflowManifestSchema: z.ZodObject<{
3
- name: z.ZodString;
3
+ slug: z.ZodString;
4
+ title: z.ZodOptional<z.ZodString>;
4
5
  description: z.ZodOptional<z.ZodString>;
5
6
  triggers: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
6
7
  kind: z.ZodLiteral<"cron">;
@@ -15,9 +16,6 @@ export declare const workflowManifestSchema: z.ZodObject<{
15
16
  }, z.core.$strict>, z.ZodObject<{
16
17
  kind: z.ZodLiteral<"manual">;
17
18
  }, z.core.$strict>], "kind">>;
18
- secrets: z.ZodOptional<z.ZodArray<z.ZodObject<{
19
- name: z.ZodString;
20
- }, z.core.$strict>>>;
21
19
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
22
20
  input_schema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
23
21
  output_schema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -81,11 +79,6 @@ export declare const workflowManifestSchema: z.ZodObject<{
81
79
  secrets: z.ZodOptional<z.ZodArray<z.ZodObject<{
82
80
  name: z.ZodString;
83
81
  }, z.core.$strict>>>;
84
- tools: z.ZodOptional<z.ZodArray<z.ZodObject<{
85
- name: z.ZodString;
86
- config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
87
- scope: z.ZodOptional<z.ZodArray<z.ZodString>>;
88
- }, z.core.$strict>>>;
89
82
  }, z.core.$strict>>;
90
83
  callable_by: z.ZodDefault<z.ZodUnion<readonly [z.ZodObject<{
91
84
  roles: z.ZodArray<z.ZodEnum<{
package/dist/manifest.js CHANGED
@@ -12,12 +12,21 @@ import { z } from "zod";
12
12
  // ============================================================================
13
13
  // Shared scalars
14
14
  // ============================================================================
15
- const NAME_RE = /^[a-zA-Z0-9-]+$/;
16
- const workflowName = z
15
+ const SLUG_RE = /^[a-zA-Z0-9-]+$/;
16
+ /** The workflow's identity: a URL-safe slug, stable across the program's life (referenced by the
17
+ * CLI, `workflows.call`, and the API). The human-readable label is `title`, not this. */
18
+ const workflowSlug = z
17
19
  .string()
18
20
  .min(1)
19
21
  .max(100)
20
- .regex(NAME_RE, "name must be alphanumeric with hyphens");
22
+ .regex(SLUG_RE, "slug must be alphanumeric with hyphens");
23
+ /** The workflow's display label — free text, author-controlled. Falls back to a title-cased slug
24
+ * in UIs when omitted. One line only. */
25
+ const workflowTitle = z
26
+ .string()
27
+ .min(1)
28
+ .max(200)
29
+ .refine((s) => !s.includes("\n"), "title must be a single line");
21
30
  /** A short identifier (tool/MCP/skill/secret names). */
22
31
  const shortName = z.string().min(1).max(120);
23
32
  /** Loosely-typed JSON Schema objects (input_schema / output_schema / tool inputSchema). */
@@ -115,15 +124,6 @@ const concurrencySchema = z.union([
115
124
  z.strictObject({ mode: z.literal("unlimited") }),
116
125
  ]);
117
126
  // ============================================================================
118
- // Agent capabilities: NONE on the manifest — tools/mcp/skills/memory are all per-agent
119
- // ============================================================================
120
- // Used only by the platform-extension permissions.tools (hosted run-permission scoping).
121
- const toolGrantSchema = z.strictObject({
122
- name: shortName,
123
- config: z.record(z.string(), z.unknown()).optional(),
124
- scope: z.array(z.string().min(1).max(200)).optional(),
125
- });
126
- // ============================================================================
127
127
  // Runner selection
128
128
  // ============================================================================
129
129
  const hostedRunsOnLabel = z.enum([
@@ -149,16 +149,20 @@ const runsOnSchema = z.union([
149
149
  // ============================================================================
150
150
  const containerSchema = z.strictObject({ image: z.string().min(1).max(512) });
151
151
  const permissionAccess = z.enum(["none", "read", "write"]);
152
+ // `permissions` is the run's access-grant surface: what the workflow is ALLOWED to access or do.
153
+ // Access-level knobs (id_token/artifacts/contents) plus the SECRET allowlist — a secret a program
154
+ // may read is a grant, so it lives here, not as a top-level field (a top-level `secrets` next to
155
+ // `env` reads like injection; it isn't). There is NO `tools` grant: tool selection is per-agent
156
+ // (AgentOptions.tools), declared on the `agent()` call that uses it — one place, no run-level ceiling.
152
157
  const permissionsSchema = z.strictObject({
153
158
  id_token: z.enum(["none", "write"]).optional(),
154
159
  artifacts: permissionAccess.optional(),
155
160
  contents: permissionAccess.optional(),
156
161
  secrets: z.array(secretRefSchema).optional(),
157
- tools: z.array(toolGrantSchema).optional(),
158
162
  });
159
163
  const callableBySchema = z.union([
160
164
  z.strictObject({ roles: z.array(z.enum(["owner", "admin", "member", "viewer"])).min(1) }),
161
- z.strictObject({ workflows: z.array(workflowName).min(1) }),
165
+ z.strictObject({ workflows: z.array(workflowSlug).min(1) }),
162
166
  z.enum(["anyone_in_org", "users_only", "workflows_only"]),
163
167
  ]);
164
168
  const egressSchema = z.union([
@@ -186,10 +190,12 @@ const notificationSchema = z.union([
186
190
  // The manifest
187
191
  // ============================================================================
188
192
  export const workflowManifestSchema = z.strictObject({
189
- name: workflowName,
193
+ slug: workflowSlug,
194
+ title: workflowTitle.optional(),
190
195
  description: z.string().max(1000).optional(),
191
196
  triggers: z.array(triggerSchema).min(1),
192
- secrets: z.array(secretRefSchema).optional(),
197
+ // NO top-level `secrets` — the secret allowlist is `permissions.secrets` (a secret you may read
198
+ // is an access grant). `env` is for value injection (incl. `${{ secrets.NAME }}` of a permitted secret).
193
199
  env: envVarsSchema.optional(),
194
200
  input_schema: jsonSchemaObject.optional(),
195
201
  output_schema: jsonSchemaObject.optional(),
package/dist/meta.d.ts CHANGED
@@ -14,15 +14,6 @@ export interface ManualTrigger {
14
14
  kind: "manual";
15
15
  }
16
16
  export type Trigger = CronTrigger | WebhookTrigger | ManualTrigger;
17
- /**
18
- * A built-in tool grant, with optional configuration. Used only by the platform-extension
19
- * `permissions.tools` (hosted run-permission scoping) — agent tool selection is per-call.
20
- */
21
- export interface ToolGrant {
22
- name: string;
23
- config?: Record<string, unknown>;
24
- scope?: readonly string[];
25
- }
26
17
  /**
27
18
  * An MCP server an `agent()` call connects to (inline in `AgentOptions.mcp` — per-agent, no
28
19
  * meta declaration). The program is the trusted layer: put credentials in `env`/`headers`
@@ -66,8 +57,9 @@ export interface Container {
66
57
  }
67
58
  /**
68
59
  * A secret the program may read with `secrets.get(name)` — an allowlist entry, never a value.
69
- * Resolution is engine-dependent: environment/`.env` on local engines, the encrypted vault on
70
- * the Boardwalk platform. Secrets + env vars are the entire credential story.
60
+ * Declared in `permissions.secrets` (a readable secret is an access grant). Resolution is
61
+ * engine-dependent: environment/`.env` on local engines, the encrypted vault on the Boardwalk
62
+ * platform. Secrets + env vars are the entire credential story.
71
63
  */
72
64
  export interface SecretRef {
73
65
  name: string;
@@ -75,8 +67,8 @@ export interface SecretRef {
75
67
  /**
76
68
  * Environment variables for the run. A value is either non-secret plaintext, or a whole-value
77
69
  * secret reference `"${{ secrets.NAME }}"` resolved at run time (never stored in the manifest).
78
- * Referencing a secret here also grants the run access to it. Reserved `BOARDWALK_*` / `AWS_*`
79
- * keys are not allowed.
70
+ * A referenced secret must also be declared in `permissions.secrets` (env injection is the
71
+ * delivery, the permission is the grant). Reserved `BOARDWALK_*` / `AWS_*` keys are not allowed.
80
72
  */
81
73
  export type EnvVars = Record<string, string>;
82
74
  export type EgressPolicy = {
@@ -91,12 +83,17 @@ export type EgressPolicy = {
91
83
  include_defaults?: boolean;
92
84
  };
93
85
  export type RunPermissionAccess = "none" | "read" | "write";
86
+ /**
87
+ * The run's access-grant surface — what the workflow is allowed to access or do. Access-level
88
+ * knobs (`id_token`/`artifacts`/`contents`) plus the secret allowlist (`secrets`). No `tools`
89
+ * grant: tool selection is per-agent (`AgentOptions.tools`), never a manifest-level ceiling.
90
+ */
94
91
  export interface RunPermissions {
95
92
  id_token?: "none" | "write";
96
93
  artifacts?: RunPermissionAccess;
97
94
  contents?: RunPermissionAccess;
95
+ /** Names of secrets the program may read with `secrets.get` — an allowlist, not values. */
98
96
  secrets?: readonly SecretRef[];
99
- tools?: readonly ToolGrant[];
100
97
  }
101
98
  export type OrgRole = "owner" | "admin" | "member" | "viewer";
102
99
  export type CallableBy = "anyone_in_org" | "users_only" | "workflows_only" | {
@@ -144,11 +141,15 @@ export interface Workspace {
144
141
  * the program. Validated by `workflowManifestSchema`; unknown fields are errors.
145
142
  */
146
143
  export interface WorkflowMeta {
147
- name: string;
144
+ /** The workflow's identity: a URL-safe slug (alphanumeric + hyphens), stable across the program's
145
+ * life. Referenced by the CLI, `workflows.call`, and the API. The human label is `title`. */
146
+ slug: string;
147
+ /** Optional human-readable display label (free text, one line). UIs fall back to a title-cased
148
+ * slug when this is omitted, so set it only when the slug doesn't read well. */
149
+ title?: string;
148
150
  description?: string;
149
151
  /** At least one trigger is required. */
150
152
  triggers: readonly Trigger[];
151
- secrets?: readonly SecretRef[];
152
153
  env?: EnvVars;
153
154
  input_schema?: Record<string, unknown>;
154
155
  output_schema?: Record<string, unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boardwalk-labs/workflow",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Author Boardwalk workflows in TypeScript: agent(), sleep(), workflows.call(), secrets, the manifest schema, and the run-event wire format.",
5
5
  "license": "MIT",
6
6
  "repository": {