Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ HUB_STATIC_DIR=../web/dist
# personal bench gets the default workflow set deployed at first login.
# ANTHROPIC_API_KEY=

# Set to 1 to make `workbench seed` also deploy the zero-cost
# catalog-test workflows (heartbeat, channel-digest), which exist only
# to exercise the platform's scheduling and channel-mail paths
# continuously. Leave unset for a real bench — these are dev/CI
# tooling, never part of a real user's workflow set.
# WORKBENCH_SEED_CATALOG_TEST_WORKFLOWS=

# Behind a reverse proxy (e.g. tailscale serve), BASE_URL is the public
# origin and PORT is the local port the proxy forwards to.
# PORT=3000
Expand Down
30 changes: 30 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions packages/cli/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ const SeedEnv = type({
"ANTHROPIC_API_KEY?": type("string > 0").describe(
"your Anthropic API key; optional, but required for the tenant catalog to be launchable",
),
"WORKBENCH_SEED_CATALOG_TEST_WORKFLOWS?": type("string").describe(
"set to 1 to also deploy the zero-cost catalog-test workflows (heartbeat, channel-digest); a dev/CI-only opt-in, never set for a real bench",
),
});

const DEFAULT_MODEL_PROVIDER = "anthropic";
Expand Down Expand Up @@ -80,6 +83,13 @@ export type SeedConfig = {
* deploys. */
readonly modelSource: ModelSource;
readonly anthropicApiKeyConfigured: boolean;
/**
* Opt-in, from WORKBENCH_SEED_CATALOG_TEST_WORKFLOWS: also deploy
* the zero-cost catalog-test workflows (heartbeat, channel-digest)
* alongside the real default set. Unset for a real bench — these
* exist only to exercise the platform, not for a real user.
*/
readonly seedCatalogTestWorkflows: boolean;
};

function environmentError(command: string, problems: string[]): CliError {
Expand Down Expand Up @@ -170,6 +180,8 @@ export function readSeedConfig(
apiKey: apiKey ?? PLACEHOLDER_CATALOG_API_KEY,
},
anthropicApiKeyConfigured: apiKey !== undefined,
seedCatalogTestWorkflows:
parsed.WORKBENCH_SEED_CATALOG_TEST_WORKFLOWS === "1",
};
}

Expand Down
27 changes: 25 additions & 2 deletions packages/cli/src/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
parseAs,
seedCatalog,
seedTenant,
CATALOG_TEST_WORKFLOWS,
DEFAULT_WORKFLOWS,
type ApiCall,
type DefaultWorkflow,
Expand Down Expand Up @@ -73,16 +74,38 @@ async function resolveTenant(
};
}

/**
* The workflow set a plain `workbench seed` deploys: the real default
* set every tenant gets, plus the zero-cost catalog-test workflows
* only when the caller has explicitly opted in via
* `WORKBENCH_SEED_CATALOG_TEST_WORKFLOWS`. A real bench never sets
* that variable, so it only ever gets `DEFAULT_WORKFLOWS` — the same
* set `provisionPersonalTenantIfNeeded` deploys on first login.
*/
export function resolveSeedWorkflows(
config: Pick<SeedConfig, "seedCatalogTestWorkflows">,
): readonly DefaultWorkflow[] {
return config.seedCatalogTestWorkflows
? [...DEFAULT_WORKFLOWS, ...CATALOG_TEST_WORKFLOWS]
: DEFAULT_WORKFLOWS;
}

export async function runSeed(
deps: SeedDeps,
workflows: readonly DefaultWorkflow[] = DEFAULT_WORKFLOWS,
workflows?: readonly DefaultWorkflow[],
): Promise<void> {
const { config, api, log } = deps;
if (config.adminDefaulted) {
log(
"using default admin alice@example.com — set HUB_ADMIN_EMAIL and HUB_ADMIN_PASSWORD for real deployments",
);
}
const resolvedWorkflows = workflows ?? resolveSeedWorkflows(config);
if (config.seedCatalogTestWorkflows) {
log(
"WORKBENCH_SEED_CATALOG_TEST_WORKFLOWS=1: also deploying the zero-cost catalog-test workflows (heartbeat, channel-digest)",
);
}

const session = await authenticate(api, {
email: config.adminEmail,
Expand All @@ -100,7 +123,7 @@ export async function runSeed(
model: config.modelSource,
pushWorkflow: deps.pushWorkflow,
log,
workflows,
workflows: resolvedWorkflows,
};
if (deps.sleep !== undefined) seedArgs.sleep = deps.sleep;
if (deps.runStartTimeoutMs !== undefined)
Expand Down
20 changes: 20 additions & 0 deletions packages/cli/test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,24 @@ describe("readSeedConfig", () => {
expect(MODEL_CREDENTIAL_VARIABLES.length).toBe(1);
expect(MODEL_CREDENTIAL_VARIABLES[0]).toContain("ANTHROPIC_API_KEY");
});

test("the catalog-test workflow opt-in defaults off", () => {
expect(readSeedConfig(VALID_SHARED).seedCatalogTestWorkflows).toBe(false);
});

test("WORKBENCH_SEED_CATALOG_TEST_WORKFLOWS=1 opts into the catalog-test workflows", () => {
const config = readSeedConfig({
...VALID_SHARED,
WORKBENCH_SEED_CATALOG_TEST_WORKFLOWS: "1",
});
expect(config.seedCatalogTestWorkflows).toBe(true);
});

test("any other value for WORKBENCH_SEED_CATALOG_TEST_WORKFLOWS stays opted out", () => {
const config = readSeedConfig({
...VALID_SHARED,
WORKBENCH_SEED_CATALOG_TEST_WORKFLOWS: "true",
});
expect(config.seedCatalogTestWorkflows).toBe(false);
});
});
19 changes: 18 additions & 1 deletion packages/cli/test/seed.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test";
import { CliError } from "@workbench/hub-client";
import type { SeedConfig } from "../src/config";
import { runSeed, type SeedDeps } from "../src/seed";
import { resolveSeedWorkflows, runSeed, type SeedDeps } from "../src/seed";
import {
collector,
fakeAPI,
Expand All @@ -25,6 +25,7 @@ const CONFIG: SeedConfig = {
apiKey: "placeholder-not-a-real-key",
},
anthropicApiKeyConfigured: false,
seedCatalogTestWorkflows: false,
};

function deps(overrides: Partial<SeedDeps> & Pick<SeedDeps, "api">): SeedDeps {
Expand All @@ -37,6 +38,22 @@ function deps(overrides: Partial<SeedDeps> & Pick<SeedDeps, "api">): SeedDeps {
};
}

describe("resolveSeedWorkflows", () => {
test("without the opt-in, only the real default workflow set is deployed", () => {
const names = resolveSeedWorkflows({
seedCatalogTestWorkflows: false,
}).map((w) => w.assetName);
expect(names).toEqual(["echo", "assistant"]);
});

test("with the opt-in, the catalog-test workflows are appended", () => {
const names = resolveSeedWorkflows({
seedCatalogTestWorkflows: true,
}).map((w) => w.assetName);
expect(names).toEqual(["echo", "assistant", "heartbeat", "channel-digest"]);
});
});

describe("runSeed", () => {
test("authenticates, resolves the bench by slug, and starts seeding it", async () => {
const { lines, log } = collector();
Expand Down
4 changes: 3 additions & 1 deletion packages/hub-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
"@intx/types": "workspace:*",
"arktype": "catalog:",
"@corbits/assistant-workflow": "workspace:*",
"@corbits/echo-workflow": "workspace:*"
"@corbits/channel-digest-workflow": "workspace:*",
"@corbits/echo-workflow": "workspace:*",
"@corbits/heartbeat-workflow": "workspace:*"
},
"devDependencies": {
"@types/bun": "catalog:",
Expand Down
1 change: 1 addition & 0 deletions packages/hub-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type {
WorkflowPusher,
} from "./seed";
export {
CATALOG_TEST_WORKFLOWS,
DEFAULT_WORKFLOWS,
PLACEHOLDER_CATALOG_API_KEY,
seedCatalog,
Expand Down
Loading
Loading