Skip to content

Commit 3fbf756

Browse files
committed
fix(v1.6.4): codex round-1 + doc-bug — bootstrap degraded mode + workforce list filter
First codex (GPT-5) operator session in tmux produced 4 concrete friction points. Addresses 3; F2 (local node-type schema) is bigger scope, deferred. Also caught a real doc/code mismatch I shipped earlier. Fixes: 1. (F1, codex) `af bootstrap` hid backend failure Previously returned `auth.health: false` alongside an empty `agents` and `workforces` array — indistinguishable from a genuinely empty workspace. Codex's exact words: "either a hard stop up front or a much louder degraded-mode signal." Now returns: - `auth.health_error` with the underlying fetch error message - `data_fresh: boolean` at top level - `data_fresh_hint` string when the backend is unreachable Plus a new `--strict` flag that exits non-zero on health failure, so CI pipelines don't race into mutations on a degraded workspace. 2. (F3, codex) `af workforce init --help` stale paperclip ref Help text said `--blueprint <slug> Blueprint id (run 'af paperclip blueprints' to list)`. Replaced with inline blueprint slugs + a pointer to `af bootstrap --json > blueprints[]`. 3. (Own bug caught during doc pass) `af workforce list` missing `--name-contains` + `--fields` flags. Documented in v1.6.1 changelog and in both README and command-reference gitbook pages, but the code never shipped the flags — every invocation returned 'unknown option'. Now implemented as client-side filter + projection, matching the `agent list` / `mcp-clients list` pattern. Deferred (F2): bundling workforce node-type schemas locally so `af workforce node-types` works offline. Non-trivial — requires versioning the node-type catalog and syncing with the backend. Tests: 392 pass, 14 skip, 10 todo. Zero regressions. Notes: - Codex validated all prior v1.6 improvements: bootstrap/schema/--dry-run called "genuinely useful", "strong", "excellent". Consistent error envelope (agenticflow.error.v1) acknowledged. --skeleton-only deemed a "smart discovery path". - Codex's sandbox blocked outbound HTTPS entirely, so the journey was local-shape + dry-run. The network-failure signal itself is what drove the bootstrap degraded-mode fix.
1 parent d9e7f57 commit 3fbf756

3 files changed

Lines changed: 59 additions & 10 deletions

File tree

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pixelml/agenticflow-cli",
3-
"version": "1.6.3",
3+
"version": "1.6.4",
44
"description": "AgenticFlow CLI for agent-native API operations.",
55
"license": "Apache-2.0",
66
"repository": {

packages/cli/src/cli/changelog.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,22 @@ export interface ChangelogEntry {
1313
}
1414

1515
export const CHANGELOG: ChangelogEntry[] = [
16+
{
17+
version: "1.6.4",
18+
date: "2026-04-14",
19+
highlights: [
20+
"`af bootstrap` now surfaces a `data_fresh` boolean and `data_fresh_hint` when the backend health check fails — empty `agents`/`workforces` arrays no longer look identical to 'workspace is empty'. Also added `auth.health_error` with the underlying fetch error message",
21+
"`af bootstrap --strict` exits non-zero when backend health is false — use in CI to prevent downstream mutations against a degraded workspace",
22+
"`af workforce list --name-contains <substr> --fields id,name --json` — same client-side filter + projection as `af agent list` and `af mcp-clients list`. Closes a documentation lie where the flag was advertised but not implemented",
23+
"`af workforce init --blueprint --help` no longer references the deprecated `af paperclip blueprints` as the canonical list source. Points at `af bootstrap --json > blueprints[]` with inline slug names",
24+
"README + CONTEXT.md + the gitbook-hosted `docs/09-developers/cli/` pages updated to v1.6 surface (native workforce lead, `--patch`, MCP inspect, paperclip deprecation notice, error-envelope shape)",
25+
],
26+
for_ai: [
27+
"After `af bootstrap --json`, check `data_fresh`. If false, the empty lists are UNVERIFIED — don't assume the workspace is empty. Fix network/auth before mutating",
28+
"In CI, run `af bootstrap --strict` instead of the bare form — non-zero exit guards the rest of the pipeline",
29+
"`af workforce list --fields id,name --name-contains <substr> --json` now works (was documented but broken in prior versions) — use it to find your own test workforces before bulk delete",
30+
],
31+
},
1632
{
1733
version: "1.6.3",
1834
date: "2026-04-14",

packages/cli/src/cli/main.ts

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,22 +1139,27 @@ export function createProgram(): Command {
11391139
program
11401140
.command("bootstrap")
11411141
.description("Single-command AI agent setup: verify auth, list agents, return schemas. Combines context + doctor + schema + agent list.")
1142-
.action(async () => {
1142+
.option("--strict", "Exit non-zero when the backend health check fails. Useful in CI / for scripts that shouldn't proceed into a degraded workspace.")
1143+
.action(async (opts) => {
11431144
// Combine everything an AI needs in one response
11441145
const client = buildClient(program.opts());
11451146
const token = resolveToken(program.opts());
11461147

11471148
let health = false;
1149+
let healthError: string | null = null;
11481150
let agents: unknown[] = [];
11491151
try {
11501152
const sdk = client.sdk;
11511153
const resp = await sdk.get("/v1/health");
11521154
health = resp.ok;
1153-
} catch { /* unhealthy */ }
1155+
if (!resp.ok) healthError = `HTTP ${resp.statusCode}`;
1156+
} catch (err) {
1157+
healthError = err instanceof Error ? err.message : String(err);
1158+
}
11541159

11551160
try {
11561161
agents = (await client.agents.list({ limit: 10 })) as unknown[];
1157-
} catch { /* no agents or unauth */ }
1162+
} catch { /* no agents or unauth — list stays empty */ }
11581163

11591164
// Workforces are the AgenticFlow-native multi-agent primitive. Fetch
11601165
// the first 10 for the bootstrap snapshot. Tolerate failure (endpoint
@@ -1164,14 +1169,24 @@ export function createProgram(): Command {
11641169
workforces = (await client.workforces.list({ limit: 10 })) as unknown[];
11651170
} catch { /* no workforces or unauth */ }
11661171

1172+
// Annotate data-freshness so callers know whether the empty arrays mean
1173+
// "nothing there" or "couldn't verify because backend was unreachable".
1174+
// Matches codex-round-1 friction point F1 — previously an empty agents[]
1175+
// looked identical whether the workspace was empty or the API was down.
1176+
const dataFresh = health;
11671177
printResult({
11681178
schema: "agenticflow.bootstrap.v1",
11691179
auth: {
11701180
authenticated: !!token,
11711181
health,
1182+
health_error: healthError,
11721183
workspace_id: client.sdk.workspaceId,
11731184
project_id: client.sdk.projectId,
11741185
},
1186+
data_fresh: dataFresh,
1187+
data_fresh_hint: dataFresh
1188+
? undefined
1189+
: "Backend unreachable — `agents`, `workforces`, and the `blueprints` array are the local/bundled shape only. Empty lists DO NOT mean 'nothing in your workspace'. Fix network/auth before mutating.",
11751190
agents: Array.isArray(agents)
11761191
? agents.slice(0, 10).map((a) => {
11771192
const ag = a as Record<string, unknown>;
@@ -1228,6 +1243,11 @@ export function createProgram(): Command {
12281243
datasets: webUrl("datasets", { workspaceId: client.sdk.workspaceId }),
12291244
},
12301245
});
1246+
// --strict turns degraded-backend into a non-zero exit so CI / automation
1247+
// doesn't race ahead into mutations against an unreachable API.
1248+
if (opts.strict && !health) {
1249+
process.exit(1);
1250+
}
12311251
});
12321252

12331253
// ═════════════════════════════════════════════════════════════════
@@ -4833,13 +4853,26 @@ export function createProgram(): Command {
48334853
.option("--workspace-id <id>", "Workspace ID (overrides env)")
48344854
.option("--limit <n>", "Limit")
48354855
.option("--offset <n>", "Offset")
4856+
.option("--name-contains <substr>", "Client-side case-insensitive substring filter on workforce `name`.")
4857+
.option("--fields <fields>", "Comma-separated fields to return (e.g. id,name,is_public). Applies after --name-contains.")
48364858
.action(async (opts) => {
48374859
const client = buildClient(program.opts());
4838-
await run(() => client.workforces.list({
4839-
workspaceId: opts.workspaceId,
4840-
limit: parseOptionalInteger(opts.limit as string | undefined, "--limit", 1),
4841-
offset: parseOptionalInteger(opts.offset as string | undefined, "--offset", 0),
4842-
}));
4860+
await run(async () => {
4861+
const rows = await client.workforces.list({
4862+
workspaceId: opts.workspaceId,
4863+
limit: parseOptionalInteger(opts.limit as string | undefined, "--limit", 1),
4864+
offset: parseOptionalInteger(opts.offset as string | undefined, "--offset", 0),
4865+
});
4866+
let out = rows;
4867+
const needle = (opts.nameContains as string | undefined)?.toLowerCase();
4868+
if (needle && Array.isArray(out)) {
4869+
out = (out as Array<Record<string, unknown>>).filter((r) => {
4870+
const n = r["name"];
4871+
return typeof n === "string" && n.toLowerCase().includes(needle);
4872+
});
4873+
}
4874+
return applyFieldsFilter(out, opts.fields as string | undefined);
4875+
});
48434876
});
48444877

48454878
workforceCmd
@@ -5228,7 +5261,7 @@ export function createProgram(): Command {
52285261
"them into a runnable DAG. Use --skeleton-only for the old v1.5 behavior " +
52295262
"(trigger + output + blueprint metadata, no agents).",
52305263
)
5231-
.requiredOption("--blueprint <slug>", "Blueprint id (run `af paperclip blueprints` to list)")
5264+
.requiredOption("--blueprint <slug>", "Blueprint id. See `af bootstrap --json > blueprints[]` for available ids (dev-shop, marketing-agency, sales-team, content-studio, support-center, amazon-seller).")
52325265
.option("--name <name>", "Workforce name (defaults to blueprint name)")
52335266
.option("--workspace-id <id>", "Workspace ID (overrides env)")
52345267
.option("--project-id <id>", "Project ID to use for agent creation (defaults to env / client config)")

0 commit comments

Comments
 (0)