@pixelml/agenticflow-cli 1.5.1 → 1.5.3
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/cli/changelog.d.ts.map +1 -1
- package/dist/cli/changelog.js +33 -0
- package/dist/cli/changelog.js.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +185 -33
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/utils/models.d.ts +31 -0
- package/dist/cli/utils/models.d.ts.map +1 -0
- package/dist/cli/utils/models.js +58 -0
- package/dist/cli/utils/models.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/cli/changelog.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,eAAO,MAAM,SAAS,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/cli/changelog.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,eAAO,MAAM,SAAS,EAAE,cAAc,EAoJrC,CAAC;AAEF,wBAAgB,kBAAkB,IAAI,cAAc,CAEnD;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAInE"}
|
package/dist/cli/changelog.js
CHANGED
|
@@ -5,6 +5,39 @@
|
|
|
5
5
|
* and displayed after upgrade.
|
|
6
6
|
*/
|
|
7
7
|
export const CHANGELOG = [
|
|
8
|
+
{
|
|
9
|
+
version: "1.5.3",
|
|
10
|
+
date: "2026-04-14",
|
|
11
|
+
highlights: [
|
|
12
|
+
"Model preflight on `af agent create/update` — invalid model strings (typos, missing slash) fail fast BEFORE the agent gets created with a broken config. Unknown-but-plausible models warn but proceed (so new models work between CLI releases)",
|
|
13
|
+
"Structured error hints on 401/403/404/409/422/429 — every common HTTP failure now carries an actionable `hint` in --json output pointing at the right recovery command (`af whoami`, `af agent list`, fetch-and-reconcile, etc.)",
|
|
14
|
+
"`af agent update` emits a stderr `[info]` line naming which null-valued fields got auto-stripped. Closes the footgun where bots thought they'd cleared a field but the server never saw null. Silenced with --json (keeps stdout clean for piping)",
|
|
15
|
+
],
|
|
16
|
+
for_ai: [
|
|
17
|
+
"If you're iterating on an agent's system prompt with `--patch`, don't also clear optional fields by sending null — the CLI strips them and the stderr info line tells you which ones were dropped",
|
|
18
|
+
"When a command fails, check the `hint` field in the error envelope before retrying — 404s point you at the matching `list` command, 422s point you at `details.payload` for field-level errors",
|
|
19
|
+
"Pass only models from `af bootstrap --json > models[]` — typos fail at validation time, not at next `agent run`. If you're trying a brand-new model and hit a warning, you can proceed (CLI is conservative — warns but doesn't block)",
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
version: "1.5.2",
|
|
24
|
+
date: "2026-04-14",
|
|
25
|
+
highlights: [
|
|
26
|
+
"`af workforce run --trigger-data '{...}'` now auto-wraps in the server's required `{trigger_data: ...}` envelope. Previously you had to pass `{\"trigger_data\":{\"topic\":\"AI\"}}` which felt like a CLI bug. Explicit wrapping still works (pass-through)",
|
|
27
|
+
"`af workforce delete` returns the consistent `agenticflow.delete.v1` envelope instead of bare `null`. Scripts now get the same shape as `af agent delete`",
|
|
28
|
+
"`af mcp-clients list --name-contains <substr> --fields id,name` — same filter + projection flags as `af agent list`. Essential for workspaces with dozens of MCP clients",
|
|
29
|
+
"`af mcp-clients inspect` surfaces the underlying `fetch_error` + `classification_reason` when tools can't be enumerated. Stops returning a misleading `pattern: unknown` that callers might treat as 'safe to attach'",
|
|
30
|
+
"`af schema <resource> --field <name>` now resolves against top-level schema keys too (not just create.optional). Lets you drill into `schema`, `update`, `stream` subtrees — e.g. `af schema workforce --field schema --json` returns the node_shape + edge_shape + agent_node_input docs",
|
|
31
|
+
"`af schema agent` clarifies that `project_id` is REQUIRED on create (server does NOT auto-inject for agents, unlike workforces). Separates the contract per resource",
|
|
32
|
+
],
|
|
33
|
+
for_ai: [
|
|
34
|
+
"When you hit a 422 `body.trigger_data missing` on `workforce run`, upgrade to 1.5.2 — the CLI now auto-wraps",
|
|
35
|
+
"If `mcp-clients inspect` returns `classification_reason: 'fetch_failed'` or `'unauthenticated'`, DO NOT attach that client to an agent — re-auth in the web UI first",
|
|
36
|
+
"Filter MCP clients with `af mcp-clients list --name-contains 'google docs' --fields id,name --json` — no more grep-piping",
|
|
37
|
+
"Drill into any schema subtree: `af schema workforce --field schema --json` returns graph shape; `af schema agent --field mcp_clients --json` returns attach shape",
|
|
38
|
+
"Consistent delete envelope: every `af <resource> delete` returns `{schema:'agenticflow.delete.v1', deleted:true, id, resource}` on success",
|
|
39
|
+
],
|
|
40
|
+
},
|
|
8
41
|
{
|
|
9
42
|
version: "1.5.1",
|
|
10
43
|
date: "2026-04-14",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changelog.js","sourceRoot":"","sources":["../../src/cli/changelog.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,CAAC,MAAM,SAAS,GAAqB;IACzC;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,gSAAgS;YAChS,0HAA0H;YAC1H,2KAA2K;YAC3K,sSAAsS;YACtS,wOAAwO;YACxO,yLAAyL;SAC1L;QACD,MAAM,EAAE;YACN,0UAA0U;YAC1U,+IAA+I;YAC/I,4IAA4I;YAC5I,mKAAmK;SACpK;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,iKAAiK;YACjK,mIAAmI;YACnI,4JAA4J;YAC5J,qJAAqJ;YACrJ,8GAA8G;YAC9G,yGAAyG;SAC1G;QACD,MAAM,EAAE;YACN,mMAAmM;YACnM,wLAAwL;YACxL,wGAAwG;YACxG,gGAAgG;YAChG,iIAAiI;YACjI,oIAAoI;SACrI;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,2GAA2G;YAC3G,+MAA+M;YAC/M,2LAA2L;YAC3L,kJAAkJ;YAClJ,2GAA2G;YAC3G,2GAA2G;YAC3G,iKAAiK;YACjK,kHAAkH;YAClH,uHAAuH;SACxH;QACD,MAAM,EAAE;YACN,6LAA6L;YAC7L,uLAAuL;YACvL,4PAA4P;YAC5P,+JAA+J;YAC/J,2GAA2G;SAC5G;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,uEAAuE;YACvE,mFAAmF;YACnF,yFAAyF;YACzF,gFAAgF;YAChF,kEAAkE;YAClE,uFAAuF;YACvF,iEAAiE;YACjE,uEAAuE;YACvE,yDAAyD;YACzD,wDAAwD;YACxD,6EAA6E;YAC7E,6GAA6G;SAC9G;QACD,MAAM,EAAE;YACN,6FAA6F;YAC7F,+GAA+G;YAC/G,qFAAqF;YACrF,6EAA6E;YAC7E,qEAAqE;YACrE,oHAAoH;YACpH,wGAAwG;YACxG,mGAAmG;SACpG;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,gEAAgE;YAChE,uEAAuE;YACvE,mDAAmD;YACnD,4CAA4C;SAC7C;QACD,MAAM,EAAE;YACN,6CAA6C;YAC7C,6DAA6D;SAC9D;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,kCAAkC;YAClC,6CAA6C;YAC7C,qCAAqC;SACtC;QACD,MAAM,EAAE;YACN,0CAA0C;SAC3C;KACF;CACF,CAAC;AAEF,MAAM,UAAU,kBAAkB;IAChC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,MAAM,GAAG,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;IAC9D,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACjC,CAAC"}
|
|
1
|
+
{"version":3,"file":"changelog.js","sourceRoot":"","sources":["../../src/cli/changelog.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,CAAC,MAAM,SAAS,GAAqB;IACzC;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,kPAAkP;YAClP,kOAAkO;YAClO,oPAAoP;SACrP;QACD,MAAM,EAAE;YACN,mMAAmM;YACnM,gMAAgM;YAChM,wOAAwO;SACzO;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,8PAA8P;YAC9P,2JAA2J;YAC3J,0KAA0K;YAC1K,uNAAuN;YACvN,2RAA2R;YAC3R,sKAAsK;SACvK;QACD,MAAM,EAAE;YACN,8GAA8G;YAC9G,sKAAsK;YACtK,2HAA2H;YAC3H,mKAAmK;YACnK,4IAA4I;SAC7I;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,gSAAgS;YAChS,0HAA0H;YAC1H,2KAA2K;YAC3K,sSAAsS;YACtS,wOAAwO;YACxO,yLAAyL;SAC1L;QACD,MAAM,EAAE;YACN,0UAA0U;YAC1U,+IAA+I;YAC/I,4IAA4I;YAC5I,mKAAmK;SACpK;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,iKAAiK;YACjK,mIAAmI;YACnI,4JAA4J;YAC5J,qJAAqJ;YACrJ,8GAA8G;YAC9G,yGAAyG;SAC1G;QACD,MAAM,EAAE;YACN,mMAAmM;YACnM,wLAAwL;YACxL,wGAAwG;YACxG,gGAAgG;YAChG,iIAAiI;YACjI,oIAAoI;SACrI;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,2GAA2G;YAC3G,+MAA+M;YAC/M,2LAA2L;YAC3L,kJAAkJ;YAClJ,2GAA2G;YAC3G,2GAA2G;YAC3G,iKAAiK;YACjK,kHAAkH;YAClH,uHAAuH;SACxH;QACD,MAAM,EAAE;YACN,6LAA6L;YAC7L,uLAAuL;YACvL,4PAA4P;YAC5P,+JAA+J;YAC/J,2GAA2G;SAC5G;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,uEAAuE;YACvE,mFAAmF;YACnF,yFAAyF;YACzF,gFAAgF;YAChF,kEAAkE;YAClE,uFAAuF;YACvF,iEAAiE;YACjE,uEAAuE;YACvE,yDAAyD;YACzD,wDAAwD;YACxD,6EAA6E;YAC7E,6GAA6G;SAC9G;QACD,MAAM,EAAE;YACN,6FAA6F;YAC7F,+GAA+G;YAC/G,qFAAqF;YACrF,6EAA6E;YAC7E,qEAAqE;YACrE,oHAAoH;YACpH,wGAAwG;YACxG,mGAAmG;SACpG;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,gEAAgE;YAChE,uEAAuE;YACvE,mDAAmD;YACnD,4CAA4C;SAC7C;QACD,MAAM,EAAE;YACN,6CAA6C;YAC7C,6DAA6D;SAC9D;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,kCAAkC;YAClC,6CAA6C;YAC7C,qCAAqC;SACtC;QACD,MAAM,EAAE;YACN,0CAA0C;SAC3C;KACF;CACF,CAAC;AAEF,MAAM,UAAU,kBAAkB;IAChC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,MAAM,GAAG,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;IAC9D,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACjC,CAAC"}
|
package/dist/cli/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAi8BpC,wBAAgB,aAAa,IAAI,OAAO,CAs2KvC;AAED,wBAAsB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB3D"}
|
package/dist/cli/main.js
CHANGED
|
@@ -17,9 +17,10 @@ import { LinearConnector } from "./gateway/connectors/linear.js";
|
|
|
17
17
|
import { WebhookConnector } from "./gateway/connectors/webhook.js";
|
|
18
18
|
import { listBlueprints, getBlueprint } from "./company-blueprints.js";
|
|
19
19
|
import { CHANGELOG, getLatestChangelog } from "./changelog.js";
|
|
20
|
-
import { stripNullFields } from "./utils/patch.js";
|
|
20
|
+
import { stripNullFields, AGENT_UPDATE_STRIP_NULL_FIELDS } from "./utils/patch.js";
|
|
21
21
|
import { inspectMcpToolsPattern } from "./utils/mcp-inspect.js";
|
|
22
22
|
import { emitDeprecation } from "./utils/deprecation.js";
|
|
23
|
+
import { validateModel } from "./utils/models.js";
|
|
23
24
|
import { OperationRegistry, defaultSpecPath, loadOpenapiSpec, isPublic, } from "./spec.js";
|
|
24
25
|
import { listPlaybooks, getPlaybook } from "./playbooks.js";
|
|
25
26
|
import { loadPolicy, writeDefaultPolicy, policyFilePath, } from "./policy.js";
|
|
@@ -438,6 +439,19 @@ function buildClient(parentOpts) {
|
|
|
438
439
|
projectId: resolveProjectId(parentOpts.projectId),
|
|
439
440
|
});
|
|
440
441
|
}
|
|
442
|
+
/**
|
|
443
|
+
* Map of HTTP status codes to actionable hints AI operators can follow without
|
|
444
|
+
* additional context. Keeps error responses useful when the server's error
|
|
445
|
+
* message is terse (e.g. "Agent not found" with no follow-up).
|
|
446
|
+
*/
|
|
447
|
+
const STATUS_HINT_MAP = {
|
|
448
|
+
401: "Authentication failed. Run `af whoami` to check current auth, or `af login --api-key <key>` to refresh.",
|
|
449
|
+
403: "You don't have permission for this operation. Check the resource's workspace/project or your API key's scopes.",
|
|
450
|
+
404: "Resource not found. Run the matching `list` command (e.g. `af agent list --json`) to see available IDs, or double-check the ID you passed.",
|
|
451
|
+
409: "Conflict — the resource state disagrees with your request. Fetch the current state with `get` and reconcile before retrying.",
|
|
452
|
+
422: "Validation failed. Check `details.payload` for the specific field errors — pydantic returns a list with field name + expected type per issue.",
|
|
453
|
+
429: "Rate limited. Back off and retry with exponential delay.",
|
|
454
|
+
};
|
|
441
455
|
/** Wrap an async SDK call with error handling. */
|
|
442
456
|
async function run(fn) {
|
|
443
457
|
try {
|
|
@@ -458,11 +472,47 @@ async function run(fn) {
|
|
|
458
472
|
details["request_id"] = err.requestId;
|
|
459
473
|
if (err.payload !== null && err.payload !== undefined)
|
|
460
474
|
details["payload"] = err.payload;
|
|
461
|
-
|
|
475
|
+
const hint = STATUS_HINT_MAP[err.statusCode];
|
|
476
|
+
fail("request_failed", message, hint, details);
|
|
462
477
|
}
|
|
463
478
|
fail("request_failed", message);
|
|
464
479
|
}
|
|
465
480
|
}
|
|
481
|
+
/**
|
|
482
|
+
* Validate the `model` field on an agent create/update payload, if present.
|
|
483
|
+
* Fail-fast on implausible strings; warn (stderr) on plausible-but-unknown
|
|
484
|
+
* strings so new models work without CLI updates.
|
|
485
|
+
*/
|
|
486
|
+
function preflightModel(payload, context) {
|
|
487
|
+
if (!("model" in payload))
|
|
488
|
+
return;
|
|
489
|
+
const res = validateModel(payload["model"]);
|
|
490
|
+
if (!res.valid) {
|
|
491
|
+
fail("invalid_option_value", `Invalid model in ${context}: ${String(payload["model"])}.`, res.suggestion);
|
|
492
|
+
}
|
|
493
|
+
if (!res.known && res.suggestion && !isJsonFlagEnabled()) {
|
|
494
|
+
console.error(`[warn] ${res.suggestion}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Report which keys got stripped by stripNullFields() so callers don't think
|
|
499
|
+
* they successfully cleared a field when the CLI silently dropped it.
|
|
500
|
+
* Emitted to stderr only (keeps stdout JSON clean for piping).
|
|
501
|
+
*/
|
|
502
|
+
function warnOnStrippedNulls(original, stripped) {
|
|
503
|
+
if (isJsonFlagEnabled())
|
|
504
|
+
return; // don't pollute stderr on bot-driven runs
|
|
505
|
+
const dropped = [];
|
|
506
|
+
for (const key of AGENT_UPDATE_STRIP_NULL_FIELDS) {
|
|
507
|
+
if (key in original && original[key] === null && !(key in stripped)) {
|
|
508
|
+
dropped.push(key);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (dropped.length > 0) {
|
|
512
|
+
console.error(`[info] Stripped ${dropped.length} null-valued field(s) the server rejects on update: ${dropped.join(", ")}. ` +
|
|
513
|
+
"This is expected — server-required shape. See `af schema agent --field update --json` for the full list.");
|
|
514
|
+
}
|
|
515
|
+
}
|
|
466
516
|
// ═══════════════════════════════════════════════════════════════════
|
|
467
517
|
// Spec-based helpers (for generic commands: call, ops, catalog, doctor)
|
|
468
518
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -758,6 +808,7 @@ export function createProgram() {
|
|
|
758
808
|
const SCHEMAS = {
|
|
759
809
|
agent: {
|
|
760
810
|
resource: "agent",
|
|
811
|
+
note: "`project_id` is REQUIRED in the body on agent create (server does NOT auto-inject from client config, unlike workforces). The CLI's local validator enforces this — grab the value from `af bootstrap --json > auth.project_id`. The `workspace_id` scoping is handled server-side via API key.",
|
|
761
812
|
create: {
|
|
762
813
|
required: ["name", "tools", "project_id"],
|
|
763
814
|
optional: {
|
|
@@ -1094,39 +1145,59 @@ export function createProgram() {
|
|
|
1094
1145
|
if (!schema) {
|
|
1095
1146
|
fail("schema_not_found", `Unknown resource: ${resource}`, `Available: ${Object.keys(SCHEMAS).join(", ")}`);
|
|
1096
1147
|
}
|
|
1097
|
-
// Field drilldown:
|
|
1148
|
+
// Field drilldown: resolve `--field X` against multiple schema locations.
|
|
1149
|
+
// We look in this order: top-level sibling key (e.g. `schema`, `update`,
|
|
1150
|
+
// `stream`), then create.required, then create.optional. This lets the
|
|
1151
|
+
// drilldown return rich subtree docs (like `schema` node/edge shapes on
|
|
1152
|
+
// the workforce resource) rather than "not found".
|
|
1098
1153
|
if (opts?.field) {
|
|
1099
1154
|
const fieldName = opts.field;
|
|
1100
1155
|
const s = schema;
|
|
1101
|
-
const create = s.create ?? {};
|
|
1102
|
-
const required = create.required ?? [];
|
|
1103
|
-
const optional = create.optional ?? {};
|
|
1104
1156
|
let doc = null;
|
|
1157
|
+
let location = null;
|
|
1105
1158
|
let isRequired = false;
|
|
1106
|
-
if (
|
|
1107
|
-
doc =
|
|
1108
|
-
|
|
1159
|
+
if (fieldName in s && fieldName !== "resource" && fieldName !== "fields") {
|
|
1160
|
+
doc = s[fieldName];
|
|
1161
|
+
location = "top_level";
|
|
1109
1162
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1163
|
+
else {
|
|
1164
|
+
const create = s.create ?? {};
|
|
1165
|
+
const required = create.required ?? [];
|
|
1166
|
+
const optional = create.optional ?? {};
|
|
1167
|
+
if (required.includes(fieldName)) {
|
|
1168
|
+
doc = "required";
|
|
1169
|
+
isRequired = true;
|
|
1170
|
+
location = "create.required";
|
|
1171
|
+
}
|
|
1172
|
+
else if (fieldName in optional) {
|
|
1173
|
+
doc = optional[fieldName] ?? null;
|
|
1174
|
+
location = "create.optional";
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
const found = doc !== null && doc !== undefined;
|
|
1112
1178
|
const result = {
|
|
1113
1179
|
schema: "agenticflow.schema.field.v1",
|
|
1114
1180
|
resource: s.resource,
|
|
1115
1181
|
field: fieldName,
|
|
1116
1182
|
required: isRequired,
|
|
1183
|
+
location,
|
|
1117
1184
|
doc,
|
|
1118
|
-
found
|
|
1119
|
-
hint:
|
|
1120
|
-
? `Field '${fieldName}' has no documented shape in the static schema. For live introspection, fetch an existing instance via 'af ${s.resource} get --${s.resource}-id <id> --json' and inspect the returned value for that field.`
|
|
1185
|
+
found,
|
|
1186
|
+
hint: !found
|
|
1187
|
+
? `Field '${fieldName}' has no documented shape in the static schema. Candidates: top-level keys (${Object.keys(s).filter((k) => !["resource", "fields"].includes(k)).join(", ")}); create.required; create.optional. For live introspection, fetch an existing instance via 'af ${s.resource} get --${s.resource}-id <id> --json' and inspect the returned value for that field.`
|
|
1121
1188
|
: undefined,
|
|
1122
1189
|
};
|
|
1123
1190
|
if (isJsonFlagEnabled()) {
|
|
1124
1191
|
printResult(result);
|
|
1125
1192
|
}
|
|
1126
1193
|
else {
|
|
1127
|
-
console.log(`${s.resource}.${fieldName}${isRequired ? " (required)" : "
|
|
1128
|
-
if (doc)
|
|
1194
|
+
console.log(`${s.resource}.${fieldName}${isRequired ? " (required)" : ""}${location ? ` [${location}]` : ""}`);
|
|
1195
|
+
if (typeof doc === "string") {
|
|
1129
1196
|
console.log(` ${doc}`);
|
|
1197
|
+
}
|
|
1198
|
+
else if (doc !== null && doc !== undefined) {
|
|
1199
|
+
console.log(JSON.stringify(doc, null, 2).split("\n").map((l) => " " + l).join("\n"));
|
|
1200
|
+
}
|
|
1130
1201
|
if (result.hint)
|
|
1131
1202
|
console.log(` ${result.hint}`);
|
|
1132
1203
|
}
|
|
@@ -3466,6 +3537,7 @@ export function createProgram() {
|
|
|
3466
3537
|
const body = loadJsonPayload(opts.body);
|
|
3467
3538
|
hardenInput(JSON.stringify(body), "agent create body");
|
|
3468
3539
|
ensureLocalValidation("agent.create", validateAgentCreatePayload(body));
|
|
3540
|
+
preflightModel(body, "agent create");
|
|
3469
3541
|
if (opts.dryRun) {
|
|
3470
3542
|
printResult({ schema: "agenticflow.dry_run.v1", valid: true, target: "agent.create", payload: body });
|
|
3471
3543
|
return;
|
|
@@ -3483,15 +3555,22 @@ export function createProgram() {
|
|
|
3483
3555
|
const client = buildClient(program.opts());
|
|
3484
3556
|
const body = loadJsonPayload(opts.body);
|
|
3485
3557
|
ensureLocalValidation("agent.update", validateAgentUpdatePayload(body));
|
|
3558
|
+
preflightModel(body, "agent update");
|
|
3486
3559
|
if (opts.patch) {
|
|
3487
3560
|
await run(() => client.agents.patch(opts.agentId, body, {
|
|
3488
|
-
prepare: (merged) =>
|
|
3561
|
+
prepare: (merged) => {
|
|
3562
|
+
const stripped = stripNullFields(merged);
|
|
3563
|
+
warnOnStrippedNulls(merged, stripped);
|
|
3564
|
+
return stripped;
|
|
3565
|
+
},
|
|
3489
3566
|
}));
|
|
3490
3567
|
}
|
|
3491
3568
|
else {
|
|
3492
3569
|
// Full replace, but strip server-rejected nulls so a round-tripped
|
|
3493
3570
|
// `af agent get | af agent update --body @-` workflow doesn't 422.
|
|
3494
|
-
const
|
|
3571
|
+
const original = body;
|
|
3572
|
+
const prepared = stripNullFields(original);
|
|
3573
|
+
warnOnStrippedNulls(original, prepared);
|
|
3495
3574
|
await run(() => client.agents.update(opts.agentId, prepared));
|
|
3496
3575
|
}
|
|
3497
3576
|
});
|
|
@@ -4040,28 +4119,54 @@ export function createProgram() {
|
|
|
4040
4119
|
.option("--project-id <id>", "Project ID")
|
|
4041
4120
|
.option("--limit <n>", "Limit")
|
|
4042
4121
|
.option("--offset <n>", "Offset")
|
|
4122
|
+
.option("--name-contains <substr>", "Client-side case-insensitive substring filter on client `name`. Essential in busy workspaces with dozens of MCP clients.")
|
|
4123
|
+
.option("--fields <fields>", "Comma-separated fields to return (e.g. id,name,is_authenticated). Applies after --name-contains filter.")
|
|
4043
4124
|
.option("--verify-auth", "Reconcile is_authenticated by calling `get` for each client — slower " +
|
|
4044
4125
|
"but catches the case where list() reports auth=true but get() reveals " +
|
|
4045
4126
|
"the underlying provider session is expired. N+1 call; use sparingly.")
|
|
4046
4127
|
.action(async (opts) => {
|
|
4047
4128
|
const client = buildClient(program.opts());
|
|
4129
|
+
// Helper: apply client-side name filter + fields projection to a list response
|
|
4130
|
+
const postFilter = (rows) => {
|
|
4131
|
+
let out = rows;
|
|
4132
|
+
const nameContains = opts.nameContains;
|
|
4133
|
+
if (nameContains && Array.isArray(out)) {
|
|
4134
|
+
const needle = nameContains.toLowerCase();
|
|
4135
|
+
out = out.filter((r) => {
|
|
4136
|
+
const n = r["name"];
|
|
4137
|
+
return typeof n === "string" && n.toLowerCase().includes(needle);
|
|
4138
|
+
});
|
|
4139
|
+
}
|
|
4140
|
+
return applyFieldsFilter(out, opts.fields);
|
|
4141
|
+
};
|
|
4048
4142
|
if (!opts.verifyAuth) {
|
|
4049
|
-
await run(() =>
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4143
|
+
await run(async () => {
|
|
4144
|
+
const rows = await client.mcpClients.list({
|
|
4145
|
+
workspaceId: opts.workspaceId,
|
|
4146
|
+
projectId: opts.projectId,
|
|
4147
|
+
limit: parseOptionalInteger(opts.limit, "--limit", 1),
|
|
4148
|
+
offset: parseOptionalInteger(opts.offset, "--offset", 0),
|
|
4149
|
+
});
|
|
4150
|
+
return postFilter(rows);
|
|
4151
|
+
});
|
|
4055
4152
|
return;
|
|
4056
4153
|
}
|
|
4057
|
-
// --verify-auth: list, then re-check each row's auth via get()
|
|
4154
|
+
// --verify-auth: list, filter, then re-check each remaining row's auth via get()
|
|
4058
4155
|
await run(async () => {
|
|
4059
|
-
|
|
4156
|
+
let rows = (await client.mcpClients.list({
|
|
4060
4157
|
workspaceId: opts.workspaceId,
|
|
4061
4158
|
projectId: opts.projectId,
|
|
4062
4159
|
limit: parseOptionalInteger(opts.limit, "--limit", 1),
|
|
4063
4160
|
offset: parseOptionalInteger(opts.offset, "--offset", 0),
|
|
4064
4161
|
}));
|
|
4162
|
+
const nameContains = opts.nameContains;
|
|
4163
|
+
if (nameContains) {
|
|
4164
|
+
const needle = nameContains.toLowerCase();
|
|
4165
|
+
rows = rows.filter((r) => {
|
|
4166
|
+
const n = r["name"];
|
|
4167
|
+
return typeof n === "string" && n.toLowerCase().includes(needle);
|
|
4168
|
+
});
|
|
4169
|
+
}
|
|
4065
4170
|
const verified = await Promise.all(rows.map(async (row) => {
|
|
4066
4171
|
const id = row["id"];
|
|
4067
4172
|
if (!id)
|
|
@@ -4079,7 +4184,7 @@ export function createProgram() {
|
|
|
4079
4184
|
return { ...row, verified_auth_error: err instanceof Error ? err.message : String(err) };
|
|
4080
4185
|
}
|
|
4081
4186
|
}));
|
|
4082
|
-
return verified;
|
|
4187
|
+
return applyFieldsFilter(verified, opts.fields);
|
|
4083
4188
|
});
|
|
4084
4189
|
});
|
|
4085
4190
|
mcpClientsCmd
|
|
@@ -4118,16 +4223,38 @@ export function createProgram() {
|
|
|
4118
4223
|
? (toolsBox.tools)
|
|
4119
4224
|
: [];
|
|
4120
4225
|
const report = inspectMcpToolsPattern(tools);
|
|
4226
|
+
// Surface the underlying fetch/auth error when the tools list couldn't
|
|
4227
|
+
// be enumerated — previously we silently returned pattern="unknown",
|
|
4228
|
+
// which callers mistook for "safe to attach." Now the `fetch_error`
|
|
4229
|
+
// + `classification_reason` make the failure explicit.
|
|
4230
|
+
const fetchError = raw["error"];
|
|
4231
|
+
const isAuth = raw["is_authenticated"];
|
|
4232
|
+
const classification_reason = tools.length > 0
|
|
4233
|
+
? "tools_enumerated"
|
|
4234
|
+
: fetchError
|
|
4235
|
+
? "fetch_failed"
|
|
4236
|
+
: isAuth === false
|
|
4237
|
+
? "unauthenticated"
|
|
4238
|
+
: "unknown";
|
|
4239
|
+
const additional_quirks = [...report.quirks];
|
|
4240
|
+
if (tools.length === 0 && (fetchError || isAuth === false)) {
|
|
4241
|
+
additional_quirks.unshift(`Cannot classify this MCP client's tools — ${classification_reason}. ` +
|
|
4242
|
+
(fetchError ? `Server reported: ${fetchError}. ` : "") +
|
|
4243
|
+
`Do NOT attach this client to an agent until the underlying issue is resolved. ` +
|
|
4244
|
+
`Re-auth via the AgenticFlow web UI (workspaces/<ws>/mcp/${clientId}).`);
|
|
4245
|
+
}
|
|
4121
4246
|
return {
|
|
4122
4247
|
schema: "agenticflow.mcp_client.inspect.v1",
|
|
4123
4248
|
client_id: clientId,
|
|
4124
4249
|
client_name: raw["name"] ?? null,
|
|
4125
|
-
is_authenticated:
|
|
4250
|
+
is_authenticated: isAuth ?? null,
|
|
4126
4251
|
tool_count: tools.length,
|
|
4127
4252
|
pattern: report.pattern, // "pipedream" | "composio" | "mixed" | "unknown"
|
|
4253
|
+
classification_reason,
|
|
4254
|
+
fetch_error: fetchError ?? null,
|
|
4128
4255
|
write_capable_tools: report.writeCapable,
|
|
4129
4256
|
pipedream_instruction_only_tools: report.pipedreamTools,
|
|
4130
|
-
known_quirks:
|
|
4257
|
+
known_quirks: additional_quirks,
|
|
4131
4258
|
playbook: "af playbook mcp-client-quirks",
|
|
4132
4259
|
};
|
|
4133
4260
|
});
|
|
@@ -4216,7 +4343,21 @@ export function createProgram() {
|
|
|
4216
4343
|
.option("--workspace-id <id>", "Workspace ID (overrides env)")
|
|
4217
4344
|
.action(async (opts) => {
|
|
4218
4345
|
const client = buildClient(program.opts());
|
|
4219
|
-
|
|
4346
|
+
try {
|
|
4347
|
+
await client.workforces.delete(opts.workforceId, { workspaceId: opts.workspaceId });
|
|
4348
|
+
// Server returns 204/null on success — wrap in the same delete envelope
|
|
4349
|
+
// that `af agent delete` uses so scripts get a consistent shape.
|
|
4350
|
+
printResult({
|
|
4351
|
+
schema: "agenticflow.delete.v1",
|
|
4352
|
+
deleted: true,
|
|
4353
|
+
id: opts.workforceId,
|
|
4354
|
+
resource: "workforce",
|
|
4355
|
+
});
|
|
4356
|
+
}
|
|
4357
|
+
catch (err) {
|
|
4358
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4359
|
+
fail("request_failed", message);
|
|
4360
|
+
}
|
|
4220
4361
|
});
|
|
4221
4362
|
workforceCmd
|
|
4222
4363
|
.command("schema")
|
|
@@ -4275,13 +4416,24 @@ export function createProgram() {
|
|
|
4275
4416
|
.description("Execute a workforce. Streams SSE events — each event prints as one JSON line. " +
|
|
4276
4417
|
"Exits when the stream closes.")
|
|
4277
4418
|
.requiredOption("--workforce-id <id>", "Workforce ID")
|
|
4278
|
-
.option("--trigger-data <json>", "Trigger
|
|
4419
|
+
.option("--trigger-data <json>", "Trigger input as inline JSON or @file. Pass the data your trigger node expects " +
|
|
4420
|
+
"(e.g. `{\"topic\":\"AI\"}`) — the CLI automatically wraps it in the server's " +
|
|
4421
|
+
"required `{trigger_data: ...}` envelope. If you pass `{\"trigger_data\":{...}}` " +
|
|
4422
|
+
"explicitly, it's left as-is.", "{}")
|
|
4279
4423
|
.option("--workspace-id <id>", "Workspace ID (overrides env)")
|
|
4280
4424
|
.action(async (opts) => {
|
|
4281
4425
|
const client = buildClient(program.opts());
|
|
4282
|
-
const
|
|
4426
|
+
const raw = loadJsonPayload(opts.triggerData);
|
|
4427
|
+
// Server accepts `{trigger_data: {...}}`. If the caller already wrapped
|
|
4428
|
+
// it (explicitly nested under trigger_data), pass through. Otherwise,
|
|
4429
|
+
// wrap the user's payload. This is the ergonomics fix for friction S6 —
|
|
4430
|
+
// previously passing `{topic:"..."}` returned `422: body.trigger_data
|
|
4431
|
+
// missing` which looked like a CLI bug, not a payload shape bug.
|
|
4432
|
+
const triggerBody = "trigger_data" in raw && typeof raw["trigger_data"] === "object" && raw["trigger_data"] !== null
|
|
4433
|
+
? raw
|
|
4434
|
+
: { trigger_data: raw };
|
|
4283
4435
|
try {
|
|
4284
|
-
const response = await client.workforces.run(opts.workforceId,
|
|
4436
|
+
const response = await client.workforces.run(opts.workforceId, triggerBody, {
|
|
4285
4437
|
workspaceId: opts.workspaceId,
|
|
4286
4438
|
});
|
|
4287
4439
|
if (!response.ok) {
|