@f-o-h/cli 0.1.64 → 0.1.66
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 +16 -11
- package/dist/foh.js +190 -141
- package/package.json +41 -41
package/README.md
CHANGED
|
@@ -86,19 +86,23 @@ prints the fallback URL. `auth login --web` starts browser device
|
|
|
86
86
|
authorization, opens `/cli-auth`, waits for console approval, and stores the
|
|
87
87
|
returned short-lived token. Credential auth remains available as fallback.
|
|
88
88
|
|
|
89
|
-
`foh prove` produces a compact signed proof report across auth, org context,
|
|
90
|
-
agent validation, contact phone readiness, voice provider health, widget
|
|
91
|
-
channel/embed readiness, widget smoke
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
`--
|
|
89
|
+
`foh prove` produces a compact signed proof report across auth, org context,
|
|
90
|
+
agent validation, contact phone readiness, voice provider health, widget
|
|
91
|
+
channel/embed readiness, and widget smoke. It does not run release
|
|
92
|
+
certification by default; run `foh certify run --agent <id> --profile release`
|
|
93
|
+
before publish, or pass `--include-certification` only when you intentionally
|
|
94
|
+
want the slower certification check inside proof. It is read-only by default;
|
|
95
|
+
pass `--mutation-mode ensure` or `--repair` only when you explicitly want proof
|
|
96
|
+
to ensure missing widget state. Use `--strict` in automation when holds should
|
|
97
|
+
fail the command, and `--mission voice` or `--require-phone` when a
|
|
98
|
+
voice/contact number is mandatory for the demo.
|
|
96
99
|
Use `--contact-path byon` when the proof is meant to validate a
|
|
97
100
|
customer-owned/BYON phone route; missing BYON config then reports
|
|
98
101
|
`byon_voice_number_not_configured` instead of suggesting FOH number purchase.
|
|
99
|
-
For repeated or parallel proof missions in AI-agent
|
|
100
|
-
`--proof-cache-dir .foh/proof-cache` so
|
|
101
|
-
sibling proofs reuse the same signed
|
|
102
|
+
For repeated or parallel explicit-certification proof missions in AI-agent
|
|
103
|
+
evals, pass `--include-certification --proof-cache-dir .foh/proof-cache` so
|
|
104
|
+
simulation certification runs once and sibling proofs reuse the same signed
|
|
105
|
+
certification detail.
|
|
102
106
|
|
|
103
107
|
For mass AI-agent evals and repeated demo rehearsals, keep setup on the free
|
|
104
108
|
scaffold lane:
|
|
@@ -176,8 +180,9 @@ than creating a second bronze-tier agent.
|
|
|
176
180
|
| Start | `foh start` |
|
|
177
181
|
| Setup | `foh setup --phone-mode observe --json` |
|
|
178
182
|
| Prove | `foh prove --agent <agent_id> --mission widget --json` |
|
|
183
|
+
| Certify | `foh certify run --agent <agent_id> --profile release --json` |
|
|
179
184
|
| Debug | `foh debug --out test-results/foh-cli-diag.latest.json --json` |
|
|
180
|
-
|
|
|
185
|
+
| Platform feedback | `foh bug improve --from-file <artifact.json> --json` |
|
|
181
186
|
| Publish | `foh agent publish --agent <agent_id> --json` |
|
|
182
187
|
|
|
183
188
|
For a planted knowledge-miss benchmark:
|
package/dist/foh.js
CHANGED
|
@@ -14311,12 +14311,6 @@ function registerAgentPreviewCommands(agent) {
|
|
|
14311
14311
|
}));
|
|
14312
14312
|
}
|
|
14313
14313
|
|
|
14314
|
-
// src/lib/cert-mode.ts
|
|
14315
|
-
var agentCertModeValues = ["quick", "full", "stress"];
|
|
14316
|
-
function normalizeAgentCertMode(value) {
|
|
14317
|
-
return agentCertModeValues.includes(value) ? value : "quick";
|
|
14318
|
-
}
|
|
14319
|
-
|
|
14320
14314
|
// src/lib/setup-api.ts
|
|
14321
14315
|
function resolvePublishOptions(options) {
|
|
14322
14316
|
if (typeof options === "string") return { apiUrlOverride: options };
|
|
@@ -14389,61 +14383,7 @@ async function runSetupCertifyLoop(agentId, params) {
|
|
|
14389
14383
|
}
|
|
14390
14384
|
|
|
14391
14385
|
// src/lib/agent-publish-gate.ts
|
|
14392
|
-
function boundedInt(value, params) {
|
|
14393
|
-
const parsed = Number(value);
|
|
14394
|
-
if (!Number.isFinite(parsed)) return params.fallback;
|
|
14395
|
-
return Math.max(params.min, Math.min(params.max, Math.trunc(parsed)));
|
|
14396
|
-
}
|
|
14397
|
-
function certModeFlag(mode) {
|
|
14398
|
-
if (mode === "full") return ["--full"];
|
|
14399
|
-
if (mode === "stress") return ["--stress"];
|
|
14400
|
-
return [];
|
|
14401
|
-
}
|
|
14402
|
-
function buildCertificationFailureCommands(params) {
|
|
14403
|
-
const modeFlags = certModeFlag(params.certMode);
|
|
14404
|
-
const orgFlags = params.orgId ? ["--org", params.orgId] : [];
|
|
14405
|
-
const scenarioFlags = params.topBlocker?.scenario_id ? ["--scenario-ids", params.topBlocker.scenario_id] : [];
|
|
14406
|
-
const command = [
|
|
14407
|
-
"foh",
|
|
14408
|
-
"sim",
|
|
14409
|
-
"certify-loop",
|
|
14410
|
-
"--agent",
|
|
14411
|
-
params.agentId,
|
|
14412
|
-
...orgFlags,
|
|
14413
|
-
...modeFlags,
|
|
14414
|
-
...scenarioFlags,
|
|
14415
|
-
"--json"
|
|
14416
|
-
].join(" ");
|
|
14417
|
-
return [
|
|
14418
|
-
command,
|
|
14419
|
-
`foh bug improve --from external-agent-run --file <run_dir>/run.json --json`
|
|
14420
|
-
];
|
|
14421
|
-
}
|
|
14422
|
-
function resolveCertifiedPublishOptions(opts) {
|
|
14423
|
-
const rawMode = String(opts.certMode || "quick").toLowerCase();
|
|
14424
|
-
const certMode = normalizeAgentCertMode(rawMode);
|
|
14425
|
-
if (certMode !== rawMode) {
|
|
14426
|
-
throw new FohError({
|
|
14427
|
-
step: "agent.publish",
|
|
14428
|
-
error: `Invalid cert mode: ${opts.certMode}`,
|
|
14429
|
-
remediation: "Use --cert-mode quick, full, or stress.",
|
|
14430
|
-
statusCode: 400
|
|
14431
|
-
});
|
|
14432
|
-
}
|
|
14433
|
-
return {
|
|
14434
|
-
certMode,
|
|
14435
|
-
adaptiveRuns: boundedInt(opts.adaptiveRuns, { min: 1, max: 120, fallback: 30 }),
|
|
14436
|
-
maxImprovementRounds: boundedInt(opts.maxImprovementRounds, { min: 0, max: 5, fallback: 1 }),
|
|
14437
|
-
scenarioIds: Array.isArray(opts.scenarioIds) ? opts.scenarioIds.map((item) => String(item).trim()).filter(Boolean) : String(opts.scenarioIds || "").split(",").map((item) => item.trim()).filter(Boolean)
|
|
14438
|
-
};
|
|
14439
|
-
}
|
|
14440
14386
|
async function validateCertifyAndPublishAgent(opts) {
|
|
14441
|
-
const { certMode, adaptiveRuns, maxImprovementRounds, scenarioIds } = resolveCertifiedPublishOptions({
|
|
14442
|
-
certMode: opts.certMode,
|
|
14443
|
-
adaptiveRuns: opts.adaptiveRuns,
|
|
14444
|
-
maxImprovementRounds: opts.maxImprovementRounds,
|
|
14445
|
-
scenarioIds: opts.scenarioIds
|
|
14446
|
-
});
|
|
14447
14387
|
const validation = await apiFetch(
|
|
14448
14388
|
`/v1/console/agents/${opts.agentId}/validate`,
|
|
14449
14389
|
{
|
|
@@ -14459,44 +14399,18 @@ async function validateCertifyAndPublishAgent(opts) {
|
|
|
14459
14399
|
remediation: `Run: foh agent validate --agent ${opts.agentId} to see details.`
|
|
14460
14400
|
});
|
|
14461
14401
|
}
|
|
14462
|
-
const certification = await runSetupCertifyLoop(opts.agentId, {
|
|
14463
|
-
mode: certMode,
|
|
14464
|
-
adaptiveRuns,
|
|
14465
|
-
maxImprovementRounds,
|
|
14466
|
-
scenarioIds,
|
|
14467
|
-
orgId: opts.orgId,
|
|
14468
|
-
apiUrlOverride: opts.apiUrlOverride
|
|
14469
|
-
});
|
|
14470
|
-
const certificate = certification.certificate;
|
|
14471
|
-
if (!certification.ok || !certification.overall_pass || !certificate) {
|
|
14472
|
-
const topBlocker = certificate?.blockers?.[0];
|
|
14473
|
-
const blockerLabel = topBlocker?.invariant && topBlocker?.scenario_id ? `${topBlocker.invariant} in ${topBlocker.scenario_id}` : "unknown";
|
|
14474
|
-
const nextCommands = buildCertificationFailureCommands({
|
|
14475
|
-
agentId: opts.agentId,
|
|
14476
|
-
orgId: opts.orgId,
|
|
14477
|
-
certMode,
|
|
14478
|
-
topBlocker
|
|
14479
|
-
});
|
|
14480
|
-
throw new FohError({
|
|
14481
|
-
step: "agent.publish",
|
|
14482
|
-
error: `Simulation certification failed before publish: ${certificate?.scenario_summary?.failed ?? "unknown"}/${certificate?.scenario_summary?.total ?? "unknown"} scenario(s) failed. Top blocker: ${blockerLabel}.`,
|
|
14483
|
-
remediation: [
|
|
14484
|
-
topBlocker?.suggested_fix ?? certificate?.recommendations?.[0] ?? "Fix the top simulation blocker before publishing.",
|
|
14485
|
-
`Re-run: ${nextCommands[0]}`
|
|
14486
|
-
].filter(Boolean).join(" "),
|
|
14487
|
-
reasonCode: "simulation_certification_failed",
|
|
14488
|
-
nextCommands,
|
|
14489
|
-
detail: {
|
|
14490
|
-
certification,
|
|
14491
|
-
top_blocker: topBlocker ?? null
|
|
14492
|
-
}
|
|
14493
|
-
});
|
|
14494
|
-
}
|
|
14495
14402
|
await publishAgentFromCurrentDraft(opts.agentId, {
|
|
14496
14403
|
apiUrlOverride: opts.apiUrlOverride,
|
|
14497
14404
|
orgId: opts.orgId
|
|
14498
14405
|
});
|
|
14499
|
-
return {
|
|
14406
|
+
return {
|
|
14407
|
+
validation,
|
|
14408
|
+
certification: {
|
|
14409
|
+
status: "not_run",
|
|
14410
|
+
reason_code: "publish_consumes_existing_certification_evidence"
|
|
14411
|
+
},
|
|
14412
|
+
publish: { ok: true }
|
|
14413
|
+
};
|
|
14500
14414
|
}
|
|
14501
14415
|
|
|
14502
14416
|
// src/commands/agent-validation.ts
|
|
@@ -14545,7 +14459,7 @@ function registerAgentValidationCommands(agent) {
|
|
|
14545
14459
|
format(data, { json: opts.json ?? false });
|
|
14546
14460
|
if (Array.isArray(data.issues) && data.issues.length > 0) markCommandFailed(1);
|
|
14547
14461
|
}));
|
|
14548
|
-
agent.command("publish").description("Validate
|
|
14462
|
+
agent.command("publish").description("Validate and publish an agent using existing certification evidence").requiredOption("--agent <id>", "Agent ID").option("--cert-mode <m>", "Deprecated compatibility flag; publish consumes existing certification evidence", "quick").option("--cert-scenario-ids <csv>", "Deprecated compatibility flag; run foh sim certify before publish").option("--cert-adaptive-runs <n>", "Deprecated compatibility flag; run foh sim certify before publish", "30").option("--cert-max-improvement-rounds <n>", "Deprecated compatibility flag; run foh sim certify before publish", "1").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--break-glass-reason <reason>", "Break-glass reason for publish override").option("--break-glass-incident <id>", "Break-glass incident ID for publish override").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
14549
14463
|
if (opts.breakGlassReason || opts.breakGlassIncident) {
|
|
14550
14464
|
if (!opts.breakGlassReason || !opts.breakGlassIncident) {
|
|
14551
14465
|
throw new FohError({
|
|
@@ -14568,7 +14482,7 @@ function registerAgentValidationCommands(agent) {
|
|
|
14568
14482
|
});
|
|
14569
14483
|
format({
|
|
14570
14484
|
status: "published_with_break_glass",
|
|
14571
|
-
warning: "break-glass bypassed the normal validate ->
|
|
14485
|
+
warning: "break-glass bypassed the normal validate -> publish evidence gate CLI sequence",
|
|
14572
14486
|
publish: data2
|
|
14573
14487
|
}, { json: opts.json ?? false });
|
|
14574
14488
|
return;
|
|
@@ -14583,13 +14497,8 @@ function registerAgentValidationCommands(agent) {
|
|
|
14583
14497
|
maxImprovementRounds: Number(opts.certMaxImprovementRounds)
|
|
14584
14498
|
});
|
|
14585
14499
|
format({
|
|
14586
|
-
status: "
|
|
14587
|
-
certification:
|
|
14588
|
-
mode: data.certification.mode,
|
|
14589
|
-
overall_pass: data.certification.overall_pass,
|
|
14590
|
-
attempts: data.certification.attempts?.length ?? 0,
|
|
14591
|
-
improvement_runs: data.certification.improvement_runs
|
|
14592
|
-
},
|
|
14500
|
+
status: "validated_published",
|
|
14501
|
+
certification: data.certification,
|
|
14593
14502
|
publish: data.publish
|
|
14594
14503
|
}, { json: opts.json ?? false });
|
|
14595
14504
|
}));
|
|
@@ -14810,9 +14719,9 @@ function registerAgent(program3) {
|
|
|
14810
14719
|
process.stdout.write(yaml);
|
|
14811
14720
|
return;
|
|
14812
14721
|
}
|
|
14813
|
-
const { writeFileSync:
|
|
14722
|
+
const { writeFileSync: writeFileSync12 } = await import("fs");
|
|
14814
14723
|
const outputPath = opts.output ?? "tenant.yaml";
|
|
14815
|
-
|
|
14724
|
+
writeFileSync12(
|
|
14816
14725
|
outputPath,
|
|
14817
14726
|
`# tenant.yaml - Front Of House agent manifest
|
|
14818
14727
|
# Edit this file and run: foh plan tenant.yaml
|
|
@@ -14954,7 +14863,7 @@ function registerTemplates(program3) {
|
|
|
14954
14863
|
const data = await apiFetch(`/v1/console/templates/${opts.template}`, { apiUrlOverride: opts.apiUrl });
|
|
14955
14864
|
format(data, { json: opts.json ?? false });
|
|
14956
14865
|
}));
|
|
14957
|
-
templates.command("apply").description("Create a new agent in your org from a template (clones draft config, leaves unpublished unless --publish)").requiredOption("--template <id>", "Template ID").requiredOption("--name <name>", "Name for the new agent").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--publish", "Validate
|
|
14866
|
+
templates.command("apply").description("Create a new agent in your org from a template (clones draft config, leaves unpublished unless --publish)").requiredOption("--template <id>", "Template ID").requiredOption("--name <name>", "Name for the new agent").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--publish", "Validate and publish using existing certification evidence after creating").option("--cert-mode <m>", "Deprecated compatibility flag; run foh sim certify before publish", "full").option("--cert-adaptive-runs <n>", "Deprecated compatibility flag; run foh sim certify before publish", "30").option("--cert-max-improvement-rounds <n>", "Deprecated compatibility flag; run foh sim certify before publish", "1").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
14958
14867
|
const result = await apiFetch(`/v1/console/templates/${opts.template}/apply`, {
|
|
14959
14868
|
method: "POST",
|
|
14960
14869
|
body: JSON.stringify({ name: opts.name }),
|
|
@@ -16025,9 +15934,9 @@ function buildCommonOptions(command) {
|
|
|
16025
15934
|
return command.option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON");
|
|
16026
15935
|
}
|
|
16027
15936
|
function registerChannel(program3) {
|
|
16028
|
-
const
|
|
16029
|
-
const whatsapp =
|
|
16030
|
-
const instagram =
|
|
15937
|
+
const channel2 = program3.command("channel").description("Manage external channel onboarding and readiness");
|
|
15938
|
+
const whatsapp = channel2.command("whatsapp").description("Manage WhatsApp channel onboarding");
|
|
15939
|
+
const instagram = channel2.command("instagram").description("Manage Instagram channel onboarding");
|
|
16031
15940
|
registerWhatsAppChannelCommands(whatsapp, buildCommonOptions);
|
|
16032
15941
|
registerInstagramChannelCommands(instagram, buildCommonOptions);
|
|
16033
15942
|
}
|
|
@@ -16260,11 +16169,11 @@ function registerVoice(program3) {
|
|
|
16260
16169
|
}
|
|
16261
16170
|
const outputPath = String(opts.out || `foh-voice-preview-${provider}-${voiceId}.mp3`).trim();
|
|
16262
16171
|
const audio = Buffer.from(await res.arrayBuffer());
|
|
16263
|
-
const { mkdirSync: mkdirSync8, writeFileSync:
|
|
16172
|
+
const { mkdirSync: mkdirSync8, writeFileSync: writeFileSync12 } = await import("fs");
|
|
16264
16173
|
const { dirname: dirname8, resolve: resolve13 } = await import("path");
|
|
16265
16174
|
const absolutePath = resolve13(outputPath);
|
|
16266
16175
|
mkdirSync8(dirname8(absolutePath), { recursive: true });
|
|
16267
|
-
|
|
16176
|
+
writeFileSync12(absolutePath, audio);
|
|
16268
16177
|
format({
|
|
16269
16178
|
status: "ok",
|
|
16270
16179
|
provider,
|
|
@@ -16285,7 +16194,7 @@ function registerVoice(program3) {
|
|
|
16285
16194
|
const allReady = providers.length > 0 && providers.every((provider) => provider?.ready === true);
|
|
16286
16195
|
if (!allReady) markCommandFailed(1);
|
|
16287
16196
|
}));
|
|
16288
|
-
voice.command("configure").description("Configure voice settings for an agent (does not publish unless --publish)").requiredOption("--agent <id>", "Agent ID").requiredOption("--provider <p>", "TTS provider: openai, azure, twilio").requiredOption("--voice <id>", "Voice ID").option("--stt-provider <p>", "STT provider (default: best available, preferring deepgram)").option("--publish", "Validate
|
|
16197
|
+
voice.command("configure").description("Configure voice settings for an agent (does not publish unless --publish)").requiredOption("--agent <id>", "Agent ID").requiredOption("--provider <p>", "TTS provider: openai, azure, twilio").requiredOption("--voice <id>", "Voice ID").option("--stt-provider <p>", "STT provider (default: best available, preferring deepgram)").option("--publish", "Validate and publish using existing certification evidence after configuring").option("--cert-mode <m>", "Deprecated compatibility flag; run foh sim certify before publish", "full").option("--cert-adaptive-runs <n>", "Deprecated compatibility flag; run foh sim certify before publish", "30").option("--cert-max-improvement-rounds <n>", "Deprecated compatibility flag; run foh sim certify before publish", "1").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
16289
16198
|
const provider = String(opts.provider || "").trim().toLowerCase();
|
|
16290
16199
|
const voiceId = String(opts.voice || "").trim();
|
|
16291
16200
|
const catalog = await getSpeechCatalog(opts.apiUrl);
|
|
@@ -16330,7 +16239,7 @@ function registerVoice(program3) {
|
|
|
16330
16239
|
apiUrlOverride: opts.apiUrl
|
|
16331
16240
|
});
|
|
16332
16241
|
if (!opts.publish) {
|
|
16333
|
-
format({ status: "configured", note: "Run: foh
|
|
16242
|
+
format({ status: "configured", note: "Run: foh sim certify --agent " + opts.agent + " --full, then foh agent publish --agent " + opts.agent + " to make live." }, { json: opts.json ?? false });
|
|
16334
16243
|
return;
|
|
16335
16244
|
}
|
|
16336
16245
|
const pub = await validateCertifyAndPublishAgent({
|
|
@@ -32877,7 +32786,7 @@ var StdioServerTransport = class {
|
|
|
32877
32786
|
};
|
|
32878
32787
|
|
|
32879
32788
|
// src/lib/cli-version.ts
|
|
32880
|
-
var CLI_VERSION = "0.1.
|
|
32789
|
+
var CLI_VERSION = "0.1.66";
|
|
32881
32790
|
|
|
32882
32791
|
// src/commands/mcp-serve.ts
|
|
32883
32792
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -33266,7 +33175,7 @@ var TYPED_TOOL_SPECS = [
|
|
|
33266
33175
|
{
|
|
33267
33176
|
name: "foh_agent_publish",
|
|
33268
33177
|
title: "FOH Agent Publish",
|
|
33269
|
-
description: "
|
|
33178
|
+
description: "Validate and publish an agent draft using existing certification evidence.",
|
|
33270
33179
|
commandKey: "agent publish",
|
|
33271
33180
|
risk: "write",
|
|
33272
33181
|
inputSchema: {
|
|
@@ -34364,7 +34273,7 @@ function registerManifest(program3) {
|
|
|
34364
34273
|
throw e;
|
|
34365
34274
|
}
|
|
34366
34275
|
});
|
|
34367
|
-
program3.command("apply").description("Apply a tenant.yaml manifest to an agent").argument("[file]", "Path to manifest file", "tenant.yaml").option("--agent <id>", "Agent ID (overrides agent_id in manifest)").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--publish", "Validate
|
|
34276
|
+
program3.command("apply").description("Apply a tenant.yaml manifest to an agent").argument("[file]", "Path to manifest file", "tenant.yaml").option("--agent <id>", "Agent ID (overrides agent_id in manifest)").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--publish", "Validate and publish using existing certification evidence after applying").option("--cert-mode <m>", "Deprecated compatibility flag; run foh sim certify before publish", "full").option("--cert-adaptive-runs <n>", "Deprecated compatibility flag; run foh sim certify before publish", "30").option("--cert-max-improvement-rounds <n>", "Deprecated compatibility flag; run foh sim certify before publish", "1").option("--dry-run", "Show diff without making any changes (same as: foh plan)").option("--api-url <url>", "API base URL override").option("--json", "Output result as JSON").action(async (file2, opts) => {
|
|
34368
34277
|
try {
|
|
34369
34278
|
const manifest = loadManifestFile(file2);
|
|
34370
34279
|
const agentId = resolveAgentId(manifest, opts.agent);
|
|
@@ -34463,6 +34372,14 @@ function registerManifest(program3) {
|
|
|
34463
34372
|
|
|
34464
34373
|
// src/commands/setup.ts
|
|
34465
34374
|
var import_picocolors4 = __toESM(require_picocolors());
|
|
34375
|
+
|
|
34376
|
+
// src/lib/cert-mode.ts
|
|
34377
|
+
var agentCertModeValues = ["quick", "full", "stress"];
|
|
34378
|
+
function normalizeAgentCertMode(value) {
|
|
34379
|
+
return agentCertModeValues.includes(value) ? value : "quick";
|
|
34380
|
+
}
|
|
34381
|
+
|
|
34382
|
+
// src/commands/setup.ts
|
|
34466
34383
|
var SETUP_STEP_ORDER = [
|
|
34467
34384
|
"check_credentials",
|
|
34468
34385
|
"check_org_access",
|
|
@@ -34567,6 +34484,32 @@ function isAgentLimitReachedError(error2) {
|
|
|
34567
34484
|
function shouldReuseSingleAgentForEval() {
|
|
34568
34485
|
return Boolean(process.env.FOH_EXTERNAL_AGENT_RUN_DIR) && isNoSpendPolicy();
|
|
34569
34486
|
}
|
|
34487
|
+
async function rebaseEvalAgentDraftFromTemplate(params) {
|
|
34488
|
+
const preview = await apiFetch(`/v1/console/templates/${params.templateId}`, {
|
|
34489
|
+
orgId: params.orgId,
|
|
34490
|
+
apiUrlOverride: params.apiUrlOverride
|
|
34491
|
+
});
|
|
34492
|
+
const draft = preview.template?.draft_config;
|
|
34493
|
+
if (!draft || typeof draft !== "object" || Array.isArray(draft)) {
|
|
34494
|
+
throw new FohError({
|
|
34495
|
+
step: "create_agent",
|
|
34496
|
+
error: "Template preview did not return a draft_config for eval reuse.",
|
|
34497
|
+
remediation: `Run: foh templates show --template ${params.templateId} --json, then retry setup.`,
|
|
34498
|
+
reasonCode: "eval_agent_template_rebase_failed"
|
|
34499
|
+
});
|
|
34500
|
+
}
|
|
34501
|
+
await apiFetch(`/v1/console/agents/${params.agentId}/draft`, {
|
|
34502
|
+
method: "PATCH",
|
|
34503
|
+
body: JSON.stringify({ ...draft, name: params.agentName }),
|
|
34504
|
+
orgId: params.orgId,
|
|
34505
|
+
apiUrlOverride: params.apiUrlOverride
|
|
34506
|
+
});
|
|
34507
|
+
return {
|
|
34508
|
+
template_rebased: true,
|
|
34509
|
+
template_id: params.templateId,
|
|
34510
|
+
draft_keys: Object.keys(draft).sort()
|
|
34511
|
+
};
|
|
34512
|
+
}
|
|
34570
34513
|
function buildMissingOptionsPlan(missing, opts) {
|
|
34571
34514
|
const missingFlags = missing.map(optionNameToFlag);
|
|
34572
34515
|
const signInUrl = buildConsoleSignInUrl(resolveConsoleBaseUrl(opts.consoleUrl));
|
|
@@ -34948,6 +34891,13 @@ function registerSetup(program3) {
|
|
|
34948
34891
|
if (!(error2 instanceof FohError)) throw error2;
|
|
34949
34892
|
if (isAgentLimitReachedError(error2) && shouldReuseSingleAgentForEval() && existingAgents.length === 1) {
|
|
34950
34893
|
const reusable = existingAgents[0];
|
|
34894
|
+
const rebase = await rebaseEvalAgentDraftFromTemplate({
|
|
34895
|
+
agentId: reusable.id,
|
|
34896
|
+
agentName: opts.agentName,
|
|
34897
|
+
templateId: opts.agentTemplate,
|
|
34898
|
+
orgId: opts.org,
|
|
34899
|
+
apiUrlOverride: opts.apiUrl
|
|
34900
|
+
});
|
|
34951
34901
|
agentId = reusable.id;
|
|
34952
34902
|
return {
|
|
34953
34903
|
step: "create_agent",
|
|
@@ -34959,7 +34909,8 @@ function registerSetup(program3) {
|
|
|
34959
34909
|
desired_agent_name: opts.agentName,
|
|
34960
34910
|
reused_agent_id: reusable.id,
|
|
34961
34911
|
reused_agent_name: reusable.name,
|
|
34962
|
-
|
|
34912
|
+
...rebase,
|
|
34913
|
+
operator_note: "External-agent no-spend eval reused the single existing agent and rebased its draft onto the requested template instead of creating a second bronze-tier agent."
|
|
34963
34914
|
}
|
|
34964
34915
|
};
|
|
34965
34916
|
}
|
|
@@ -35189,8 +35140,8 @@ function registerSetup(program3) {
|
|
|
35189
35140
|
}
|
|
35190
35141
|
try {
|
|
35191
35142
|
const manifest = await agentExport(resolvedAgentId, { apiUrlOverride: opts.apiUrl });
|
|
35192
|
-
const { writeFileSync:
|
|
35193
|
-
|
|
35143
|
+
const { writeFileSync: writeFileSync12 } = await import("fs");
|
|
35144
|
+
writeFileSync12(
|
|
35194
35145
|
"tenant.yaml",
|
|
35195
35146
|
`# tenant.yaml - Front Of House agent manifest
|
|
35196
35147
|
# Edit this file and run: foh plan tenant.yaml
|
|
@@ -35340,14 +35291,14 @@ function registerSim(program3) {
|
|
|
35340
35291
|
const adaptiveRuns = Math.max(1, Math.min(120, Number(opts.adaptiveRuns ?? 30)));
|
|
35341
35292
|
const journeys = opts.journeys ? String(opts.journeys).split(",").map((j) => j.trim()).filter(Boolean) : void 0;
|
|
35342
35293
|
const scenarioIds = opts.scenarioIds ? String(opts.scenarioIds).split(",").map((id) => id.trim()).filter(Boolean) : void 0;
|
|
35343
|
-
const
|
|
35294
|
+
const channel2 = ["chat", "voice", "mixed"].includes(String(opts.channel ?? "")) ? opts.channel : "mixed";
|
|
35344
35295
|
process.stderr.write(` Running ${mode} simulation certification for agent ${opts.agent}...
|
|
35345
35296
|
`);
|
|
35346
35297
|
const response = await apiFetch(
|
|
35347
35298
|
`/v1/console/agents/${opts.agent}/sim-certify`,
|
|
35348
35299
|
{
|
|
35349
35300
|
method: "POST",
|
|
35350
|
-
body: JSON.stringify({ mode, adaptive_runs: adaptiveRuns, journeys, scenario_ids: scenarioIds, channel }),
|
|
35301
|
+
body: JSON.stringify({ mode, adaptive_runs: adaptiveRuns, journeys, scenario_ids: scenarioIds, channel: channel2 }),
|
|
35351
35302
|
apiUrlOverride: opts.apiUrl
|
|
35352
35303
|
}
|
|
35353
35304
|
);
|
|
@@ -35360,8 +35311,8 @@ function registerSim(program3) {
|
|
|
35360
35311
|
}
|
|
35361
35312
|
const cert = response.certificate;
|
|
35362
35313
|
if (opts.out) {
|
|
35363
|
-
const { writeFileSync:
|
|
35364
|
-
|
|
35314
|
+
const { writeFileSync: writeFileSync12 } = await import("fs");
|
|
35315
|
+
writeFileSync12(opts.out, JSON.stringify(cert, null, 2) + "\n", "utf-8");
|
|
35365
35316
|
process.stderr.write(` Certificate written to ${opts.out}
|
|
35366
35317
|
`);
|
|
35367
35318
|
}
|
|
@@ -35385,7 +35336,7 @@ function registerSim(program3) {
|
|
|
35385
35336
|
const maxImprovementRounds = Math.max(0, Math.min(5, Number(opts.maxImprovementRounds ?? 1)));
|
|
35386
35337
|
const journeys = opts.journeys ? String(opts.journeys).split(",").map((j) => j.trim()).filter(Boolean) : void 0;
|
|
35387
35338
|
const scenarioIds = opts.scenarioIds ? String(opts.scenarioIds).split(",").map((id) => id.trim()).filter(Boolean) : void 0;
|
|
35388
|
-
const
|
|
35339
|
+
const channel2 = ["chat", "voice", "mixed"].includes(String(opts.channel ?? "")) ? opts.channel : "mixed";
|
|
35389
35340
|
process.stderr.write(` Running ${mode} certification loop for agent ${opts.agent}...
|
|
35390
35341
|
`);
|
|
35391
35342
|
const response = await apiFetch(
|
|
@@ -35398,7 +35349,7 @@ function registerSim(program3) {
|
|
|
35398
35349
|
max_improvement_rounds: maxImprovementRounds,
|
|
35399
35350
|
journeys,
|
|
35400
35351
|
scenario_ids: scenarioIds,
|
|
35401
|
-
channel
|
|
35352
|
+
channel: channel2
|
|
35402
35353
|
}),
|
|
35403
35354
|
apiUrlOverride: opts.apiUrl
|
|
35404
35355
|
}
|
|
@@ -35411,8 +35362,8 @@ function registerSim(program3) {
|
|
|
35411
35362
|
});
|
|
35412
35363
|
}
|
|
35413
35364
|
if (opts.out) {
|
|
35414
|
-
const { writeFileSync:
|
|
35415
|
-
|
|
35365
|
+
const { writeFileSync: writeFileSync12 } = await import("fs");
|
|
35366
|
+
writeFileSync12(opts.out, JSON.stringify(response.certificate, null, 2) + "\n", "utf-8");
|
|
35416
35367
|
process.stderr.write(` Final certificate written to ${opts.out}
|
|
35417
35368
|
`);
|
|
35418
35369
|
}
|
|
@@ -35447,6 +35398,95 @@ ${passIcon} Certification loop summary
|
|
|
35447
35398
|
});
|
|
35448
35399
|
}
|
|
35449
35400
|
|
|
35401
|
+
// src/commands/certify.ts
|
|
35402
|
+
var import_node_fs2 = require("node:fs");
|
|
35403
|
+
function normalizeProfile(raw) {
|
|
35404
|
+
const value = String(raw || "release").trim().toLowerCase();
|
|
35405
|
+
if (value === "smoke" || value === "release" || value === "stress") return value;
|
|
35406
|
+
throw new FohError({
|
|
35407
|
+
step: "certify.run",
|
|
35408
|
+
error: `Invalid certification profile: ${raw}`,
|
|
35409
|
+
remediation: "Use --profile smoke, release, or stress.",
|
|
35410
|
+
statusCode: 400
|
|
35411
|
+
});
|
|
35412
|
+
}
|
|
35413
|
+
function modeForProfile(profile) {
|
|
35414
|
+
if (profile === "smoke") return "quick";
|
|
35415
|
+
if (profile === "stress") return "stress";
|
|
35416
|
+
return "full";
|
|
35417
|
+
}
|
|
35418
|
+
function csv(raw) {
|
|
35419
|
+
if (!raw) return void 0;
|
|
35420
|
+
const values = String(raw).split(",").map((value) => value.trim()).filter(Boolean);
|
|
35421
|
+
return values.length > 0 ? values : void 0;
|
|
35422
|
+
}
|
|
35423
|
+
function channel(raw) {
|
|
35424
|
+
const value = String(raw || "mixed").trim().toLowerCase();
|
|
35425
|
+
if (value === "chat" || value === "voice" || value === "mixed") return value;
|
|
35426
|
+
return "mixed";
|
|
35427
|
+
}
|
|
35428
|
+
function registerCertify(program3) {
|
|
35429
|
+
const certify = program3.command("certify").description("Produce release certification evidence for an agent");
|
|
35430
|
+
certify.command("run").description("Run certification for an exact agent draft/profile and emit release evidence").requiredOption("--agent <id>", "Agent ID to certify").option("--profile <profile>", "Certification profile: smoke, release, or stress", "release").option("--adaptive-runs <n>", "Adaptive runs for release/stress profiles", "30").option("--journeys <list>", "Comma-separated journey allowlist").option("--scenario-ids <list>", "Comma-separated scenario ID allowlist").option("--channel <channel>", "Channel filter: chat, voice, or mixed", "mixed").option("--out <path>", "Write certification run JSON to this file path").option("--api-url <url>", "API base URL override").option("--json", "Output as machine-readable JSON").action(async (opts) => {
|
|
35431
|
+
try {
|
|
35432
|
+
const profile = normalizeProfile(opts.profile);
|
|
35433
|
+
const mode = modeForProfile(profile);
|
|
35434
|
+
const adaptiveRuns = Math.max(1, Math.min(120, Number(opts.adaptiveRuns ?? 30) || 30));
|
|
35435
|
+
const response = await apiFetch(
|
|
35436
|
+
`/v1/console/agents/${opts.agent}/sim-certify`,
|
|
35437
|
+
{
|
|
35438
|
+
method: "POST",
|
|
35439
|
+
body: JSON.stringify({
|
|
35440
|
+
mode,
|
|
35441
|
+
adaptive_runs: adaptiveRuns,
|
|
35442
|
+
journeys: csv(opts.journeys),
|
|
35443
|
+
scenario_ids: csv(opts.scenarioIds),
|
|
35444
|
+
channel: channel(opts.channel)
|
|
35445
|
+
}),
|
|
35446
|
+
apiUrlOverride: opts.apiUrl
|
|
35447
|
+
}
|
|
35448
|
+
);
|
|
35449
|
+
if (!response.ok || !response.certificate) {
|
|
35450
|
+
throw new FohError({
|
|
35451
|
+
step: "certify.run",
|
|
35452
|
+
error: "API did not return a certificate",
|
|
35453
|
+
remediation: "Check that the agent ID is valid and the API is reachable."
|
|
35454
|
+
});
|
|
35455
|
+
}
|
|
35456
|
+
const passed = response.certificate.overall_pass === true;
|
|
35457
|
+
const result = {
|
|
35458
|
+
schema_version: "foh_certification_run.v1",
|
|
35459
|
+
ok: passed,
|
|
35460
|
+
status: passed ? "pass" : "hold",
|
|
35461
|
+
reason_code: passed ? "certification_passed" : "certification_failed",
|
|
35462
|
+
profile,
|
|
35463
|
+
mode,
|
|
35464
|
+
certificate: response.certificate,
|
|
35465
|
+
next_commands: passed ? [`foh agent publish --agent ${opts.agent} --json`] : [`foh sim certify --agent ${opts.agent} --${mode === "quick" ? "full" : mode} --json`]
|
|
35466
|
+
};
|
|
35467
|
+
if (opts.out) {
|
|
35468
|
+
(0, import_node_fs2.writeFileSync)(opts.out, JSON.stringify(result, null, 2) + "\n", "utf-8");
|
|
35469
|
+
}
|
|
35470
|
+
if (opts.json ?? false) {
|
|
35471
|
+
format(result, { json: true });
|
|
35472
|
+
} else {
|
|
35473
|
+
const summary = response.certificate.scenario_summary;
|
|
35474
|
+
process.stderr.write(`${passed ? "PASS" : "HOLD"} certification ${profile} (${mode})`);
|
|
35475
|
+
if (summary) process.stderr.write(`: ${summary.passed}/${summary.total} scenarios passed`);
|
|
35476
|
+
process.stderr.write("\n");
|
|
35477
|
+
}
|
|
35478
|
+
if (!passed) markCommandFailed(1);
|
|
35479
|
+
} catch (error2) {
|
|
35480
|
+
if (error2 instanceof FohError) {
|
|
35481
|
+
formatError(error2, { json: opts.json ?? false });
|
|
35482
|
+
markCommandFailed(1);
|
|
35483
|
+
return;
|
|
35484
|
+
}
|
|
35485
|
+
throw error2;
|
|
35486
|
+
}
|
|
35487
|
+
});
|
|
35488
|
+
}
|
|
35489
|
+
|
|
35450
35490
|
// src/commands/conversations.ts
|
|
35451
35491
|
var import_crypto4 = require("crypto");
|
|
35452
35492
|
function registerConversations(program3) {
|
|
@@ -37389,7 +37429,7 @@ function registerBug(program3) {
|
|
|
37389
37429
|
|
|
37390
37430
|
// src/lib/proof-cache.ts
|
|
37391
37431
|
var import_node_crypto2 = require("node:crypto");
|
|
37392
|
-
var
|
|
37432
|
+
var import_node_fs3 = require("node:fs");
|
|
37393
37433
|
var import_node_path = require("node:path");
|
|
37394
37434
|
var DEFAULT_MAX_AGE_MS = 15 * 60 * 1e3;
|
|
37395
37435
|
var DEFAULT_WAIT_MS = 180 * 1e3;
|
|
@@ -37413,7 +37453,7 @@ function publicPath(filePath) {
|
|
|
37413
37453
|
}
|
|
37414
37454
|
function readFreshCache(filePath, maxAgeMs) {
|
|
37415
37455
|
try {
|
|
37416
|
-
const payload = JSON.parse((0,
|
|
37456
|
+
const payload = JSON.parse((0, import_node_fs3.readFileSync)(filePath, "utf8"));
|
|
37417
37457
|
const createdAt = Date.parse(String(payload.created_at || ""));
|
|
37418
37458
|
if (!Number.isFinite(createdAt)) return null;
|
|
37419
37459
|
if (Date.now() - createdAt > maxAgeMs) return null;
|
|
@@ -37423,8 +37463,8 @@ function readFreshCache(filePath, maxAgeMs) {
|
|
|
37423
37463
|
}
|
|
37424
37464
|
}
|
|
37425
37465
|
function writeCache(filePath, value) {
|
|
37426
|
-
(0,
|
|
37427
|
-
(0,
|
|
37466
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path.dirname)(filePath), { recursive: true });
|
|
37467
|
+
(0, import_node_fs3.writeFileSync)(filePath, `${JSON.stringify({
|
|
37428
37468
|
schema_version: "foh_proof_cache_entry.v1",
|
|
37429
37469
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37430
37470
|
value
|
|
@@ -37450,7 +37490,7 @@ async function withProofCache(options, run) {
|
|
|
37450
37490
|
const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
37451
37491
|
const waitMs = options.waitMs ?? Number(process.env.FOH_PROOF_CACHE_WAIT_MS || DEFAULT_WAIT_MS);
|
|
37452
37492
|
const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
|
|
37453
|
-
(0,
|
|
37493
|
+
(0, import_node_fs3.mkdirSync)(resolvedDir, { recursive: true });
|
|
37454
37494
|
const existing = readFreshCache(cachePath, maxAgeMs);
|
|
37455
37495
|
if (existing) {
|
|
37456
37496
|
return {
|
|
@@ -37460,7 +37500,7 @@ async function withProofCache(options, run) {
|
|
|
37460
37500
|
}
|
|
37461
37501
|
let lockOwner = false;
|
|
37462
37502
|
try {
|
|
37463
|
-
(0,
|
|
37503
|
+
(0, import_node_fs3.mkdirSync)(lockPath);
|
|
37464
37504
|
lockOwner = true;
|
|
37465
37505
|
} catch {
|
|
37466
37506
|
const started = Date.now();
|
|
@@ -37483,7 +37523,7 @@ async function withProofCache(options, run) {
|
|
|
37483
37523
|
metadata: { hit: false, key, cache_path: publicPath(cachePath), waited_ms: 0 }
|
|
37484
37524
|
};
|
|
37485
37525
|
} finally {
|
|
37486
|
-
if (lockOwner) (0,
|
|
37526
|
+
if (lockOwner) (0, import_node_fs3.rmSync)(lockPath, { recursive: true, force: true });
|
|
37487
37527
|
}
|
|
37488
37528
|
}
|
|
37489
37529
|
|
|
@@ -37514,8 +37554,8 @@ function hasBlockingChecks(checks) {
|
|
|
37514
37554
|
}
|
|
37515
37555
|
function publicKeyFromEnsureResponse(response) {
|
|
37516
37556
|
const record2 = response && typeof response === "object" ? response : {};
|
|
37517
|
-
const
|
|
37518
|
-
const publicKey =
|
|
37557
|
+
const channel2 = record2.channel && typeof record2.channel === "object" ? record2.channel : {};
|
|
37558
|
+
const publicKey = channel2.public_key ?? record2.widget_public_key ?? record2.public_key;
|
|
37519
37559
|
return typeof publicKey === "string" && publicKey.trim() ? publicKey.trim() : void 0;
|
|
37520
37560
|
}
|
|
37521
37561
|
function publicKeyFromEmbedResponse(response) {
|
|
@@ -37582,7 +37622,7 @@ function isProviderCapacityBlocked(onboarding) {
|
|
|
37582
37622
|
return /maximum number of subaccounts|subaccount limit|reserve[- ]number pool|reserve pool exhausted|global safety limit/.test(message);
|
|
37583
37623
|
}
|
|
37584
37624
|
function registerProve(program3) {
|
|
37585
|
-
program3.command("prove").description("Produce one setup/runtime proof bundle for an agent").option("--agent <id>", "Agent ID to prove").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--cert-mode <m>", "Simulation cert mode: quick, full, stress", "quick").option("--cert-adaptive-runs <n>", "Adaptive runs for full/stress certification", "30").option("--cert-max-improvement-rounds <n>", "Max prompt improvement rounds in cert loop (0-5)", "1").option("--mission <mission>", "Proof mission: setup, widget, voice, publish", "setup").option("--contact-path <mode>", "Voice contact path: auto, managed, or byon", "auto").option("--mutation-mode <mode>", "Proof mutation mode: read-only or ensure", "read-only").option("--repair", "Alias for --mutation-mode ensure").option("--require-phone", "Hold proof if no phone/contact number is provisioned").option("--skip-cert", "
|
|
37625
|
+
program3.command("prove").description("Produce one setup/runtime proof bundle for an agent").option("--agent <id>", "Agent ID to prove").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--include-certification", "Run explicit simulation certification check (slow)").option("--cert-mode <m>", "Simulation cert mode when --include-certification is set: quick, full, stress", "quick").option("--cert-adaptive-runs <n>", "Adaptive runs for full/stress certification when included", "30").option("--cert-max-improvement-rounds <n>", "Max prompt improvement rounds in included cert loop (0-5)", "1").option("--mission <mission>", "Proof mission: setup, widget, voice, publish", "setup").option("--contact-path <mode>", "Voice contact path: auto, managed, or byon", "auto").option("--mutation-mode <mode>", "Proof mutation mode: read-only or ensure", "read-only").option("--repair", "Alias for --mutation-mode ensure").option("--require-phone", "Hold proof if no phone/contact number is provisioned").option("--skip-cert", "Deprecated compatibility flag; certification is skipped unless --include-certification is set").option("--skip-smoke", "Skip widget runtime smoke check").option("--skip-voice-health", "Skip realtime voice provider health check").option("--proof-cache-dir <path>", "Optional local proof cache directory for shared certification results").option("--out <path>", "Write signed proof report JSON to this path").option("--strict", "Exit non-zero unless all non-skipped checks pass").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
37586
37626
|
const checks = [];
|
|
37587
37627
|
const mission = normalizeMission(opts.mission);
|
|
37588
37628
|
const contactPath = normalizeContactPath(opts.contactPath);
|
|
@@ -37838,7 +37878,14 @@ function registerProve(program3) {
|
|
|
37838
37878
|
}
|
|
37839
37879
|
}
|
|
37840
37880
|
if (opts.skipCert) {
|
|
37841
|
-
checks.push(skipped("simulation_certification", "operator_skipped", "Skipped by --skip-cert.", `foh sim certify
|
|
37881
|
+
checks.push(skipped("simulation_certification", "operator_skipped", "Skipped by --skip-cert.", `foh sim certify --agent ${ctx.agentId} --full --json`));
|
|
37882
|
+
} else if (!opts.includeCertification) {
|
|
37883
|
+
checks.push(skipped(
|
|
37884
|
+
"simulation_certification",
|
|
37885
|
+
"certification_explicitly_required",
|
|
37886
|
+
"Runtime proof does not run release certification by default.",
|
|
37887
|
+
`foh sim certify --agent ${ctx.agentId} --full --json`
|
|
37888
|
+
));
|
|
37842
37889
|
} else {
|
|
37843
37890
|
try {
|
|
37844
37891
|
const certMode = normalizeAgentCertMode(opts.certMode);
|
|
@@ -37870,7 +37917,7 @@ function registerProve(program3) {
|
|
|
37870
37917
|
proof_cache: cached2.metadata
|
|
37871
37918
|
};
|
|
37872
37919
|
if (!loop.overall_pass) {
|
|
37873
|
-
checks.push(hold("simulation_certification", "simulation_certification_failed", "Simulation certification did not pass.", `foh sim certify
|
|
37920
|
+
checks.push(hold("simulation_certification", "simulation_certification_failed", "Simulation certification did not pass.", `foh sim certify --agent ${agentId} --${certMode === "quick" ? "full" : certMode} --json`, loopWithCache));
|
|
37874
37921
|
} else {
|
|
37875
37922
|
checks.push(pass("simulation_certification", "Simulation certification passed.", {
|
|
37876
37923
|
mode: loop.mode,
|
|
@@ -37881,7 +37928,7 @@ function registerProve(program3) {
|
|
|
37881
37928
|
}));
|
|
37882
37929
|
}
|
|
37883
37930
|
} catch (error2) {
|
|
37884
|
-
checks.push(fail("simulation_certification", "simulation_certification_failed", error2, `foh sim certify
|
|
37931
|
+
checks.push(fail("simulation_certification", "simulation_certification_failed", error2, `foh sim certify --agent ${ctx.agentId} --full --json`));
|
|
37885
37932
|
}
|
|
37886
37933
|
}
|
|
37887
37934
|
} else {
|
|
@@ -40367,7 +40414,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
40367
40414
|
var DEFAULT_PROMPT_VERSION = "blank-setup.v1";
|
|
40368
40415
|
var DEFAULT_BATCH_MODELS = "openai/codex,anthropic/claude,cursor/agent";
|
|
40369
40416
|
var PROMPTS = {
|
|
40370
|
-
"blank-setup.v1": "Go to https://frontofhouse.okii.uk. Use only public docs, public API docs, and the public npm CLI package. Always invoke the CLI with `npx --yes @f-o-h/cli@latest ...`; do not use unpinned `npx @f-o-h/cli ...`, because cached older packages can produce invalid evidence. Install or verify the FOH CLI, authenticate or reach a deterministic auth blocker, then create or configure a Front Of House voice agent and website widget. Mass evals reuse existing eval state: run `npx --yes @f-o-h/cli@latest org status --json` and `npx --yes @f-o-h/cli@latest agent list --json` before trying to create a fresh agent; if an existing eval agent is present, configure and prove that agent instead of creating a second bronze-tier agent. Prefer the certification-oriented buyer templates: run `npx --yes @f-o-h/cli@latest templates list --category buyer --json` and use `UK Buyer Qualification` or `Viewing Booking` when available; do not use a greeting-only template for proof/certification. Prefer `npx --yes @f-o-h/cli@latest setup --phone-mode observe` for the free scaffold path: agent, widget, voice config, smoke test, certification, and publish readiness together. Treat phone-number purchasing as an explicit paid/scarce contact-path step, not part of high-volume eval setup. If `FOH_CLI_SPEND_POLICY=no_spend` is active and a command returns `paid_resource_blocked_by_spend_policy`, do not try to bypass it; continue widget/setup proof and report that exact reason code for the phone path. If the customer/operator explicitly owns a number and asks for real PSTN proof, use `npx --yes @f-o-h/cli@latest provision byon attach --phone-number <e164> --confirm-owned --json`; do not invent ownership or buy a FOH-owned number. Run proof/smoke/certification where available, including widget proof
|
|
40417
|
+
"blank-setup.v1": "Go to https://frontofhouse.okii.uk. Use only public docs, public API docs, and the public npm CLI package. Always invoke the CLI with `npx --yes @f-o-h/cli@latest ...`; do not use unpinned `npx @f-o-h/cli ...`, because cached older packages can produce invalid evidence. Install or verify the FOH CLI, authenticate or reach a deterministic auth blocker, then create or configure a Front Of House voice agent and website widget. Mass evals reuse existing eval state: run `npx --yes @f-o-h/cli@latest org status --json` and `npx --yes @f-o-h/cli@latest agent list --json` before trying to create a fresh agent; if an existing eval agent is present, configure and prove that agent instead of creating a second bronze-tier agent. Prefer the certification-oriented buyer templates: run `npx --yes @f-o-h/cli@latest templates list --category buyer --json` and use `UK Buyer Qualification` or `Viewing Booking` when available; do not use a greeting-only template for proof/certification. Prefer `npx --yes @f-o-h/cli@latest setup --phone-mode observe` for the free scaffold path: agent, widget, voice config, smoke test, certification, and publish readiness together. Treat phone-number purchasing as an explicit paid/scarce contact-path step, not part of high-volume eval setup. If `FOH_CLI_SPEND_POLICY=no_spend` is active and a command returns `paid_resource_blocked_by_spend_policy`, do not try to bypass it; continue widget/setup proof and report that exact reason code for the phone path. If the customer/operator explicitly owns a number and asks for real PSTN proof, use `npx --yes @f-o-h/cli@latest provision byon attach --phone-number <e164> --confirm-owned --json`; do not invent ownership or buy a FOH-owned number. Run proof/smoke/certification where available, including widget proof, voice proof, and one explicit `foh sim certify --agent <id> --full --json` before publish. `foh prove` does not run release certification by default; only pass `--include-certification --proof-cache-dir .foh/proof-cache` when an explicit combined proof/certification run is required. If voice proof returns `contact_phone_missing` or `voice_contact_expected_no_spend_hold`, report that exact reason code unless a BYON/customer-approved phone path already exists. If `FOH_EXTERNAL_AGENT_RUN_DIR` is set, write `${FOH_EXTERNAL_AGENT_RUN_DIR}/external-agent-metadata.json` with `schema_version`, `docs_pages_used`, key decisions, and blocker reason codes before finishing. Produce a final evidence summary with commands run, docs used, artifacts created, and any blocker reason codes. Do not assume access to the private source repository.",
|
|
40371
40418
|
"debug-proof-failure.v1": "You are given a FOH proof or debug artifact. Use public docs and FOH CLI/API behavior to classify whether the blocker is docs, auth, org setup, agent config, widget, channel, runtime, or product bug. Produce a redacted improvement packet or the exact command needed to produce one. Do not ask the human to interpret logs manually unless no machine-readable artifact exists.",
|
|
40372
40419
|
"knowledge-miss.v1": "A FOH agent failed to answer a business question. Use CLI/API/docs to determine whether this is a knowledge-ingestion issue, retrieval issue, config issue, prompt/behavior issue, or runtime issue. Prefer foh knowledge query, transcript export, replay, and foh bug improve artifacts over screenshots.",
|
|
40373
40420
|
"replay-failure.v1": "You are given a FOH transcript or replay artifact. Use CLI/API/docs to replay or inspect the failed interaction, identify expected vs actual behavior, and produce a scenario-test or improvement-packet candidate."
|
|
@@ -40944,6 +40991,7 @@ var CLI_MISSION_EXAMPLES = [
|
|
|
40944
40991
|
{ mission: "Start", command: "foh start", description: "guided setup and next action selector" },
|
|
40945
40992
|
{ mission: "Setup", command: "foh setup --phone-mode observe --json", description: "create or update agent, widget, voice config, and proof scaffold" },
|
|
40946
40993
|
{ mission: "Prove", command: "foh prove --agent <agent_id> --mission widget --json", description: "produce a machine-readable proof report" },
|
|
40994
|
+
{ mission: "Certify", command: "foh certify run --agent <agent_id> --profile release --json", description: "produce release evidence before publish" },
|
|
40947
40995
|
{ mission: "Debug", command: "foh debug --out test-results/foh-cli-diag.latest.json --json", description: "collect auth/org/API diagnostics" },
|
|
40948
40996
|
{ mission: "Improve", command: "foh bug improve --from-file <artifact.json> --json", description: "convert a failure artifact into a redacted improvement packet" },
|
|
40949
40997
|
{ mission: "Publish", command: "foh agent publish --agent <agent_id> --json", description: "publish after proof gates pass" }
|
|
@@ -41043,6 +41091,7 @@ registerOps(program2);
|
|
|
41043
41091
|
registerSetup(program2);
|
|
41044
41092
|
registerManifest(program2);
|
|
41045
41093
|
registerSim(program2);
|
|
41094
|
+
registerCertify(program2);
|
|
41046
41095
|
registerDiag(program2);
|
|
41047
41096
|
registerBug(program2);
|
|
41048
41097
|
registerProve(program2);
|
package/package.json
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@f-o-h/cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "FOH CLI - AI-operator provisioning tool for Front Of House",
|
|
5
|
-
"license": "UNLICENSED",
|
|
6
|
-
"bin": {
|
|
7
|
-
"foh": "dist/foh.js"
|
|
8
|
-
},
|
|
9
|
-
"main": "dist/foh.js",
|
|
10
|
-
"files": [
|
|
11
|
-
"dist/",
|
|
12
|
-
"examples/",
|
|
13
|
-
"schemas/",
|
|
14
|
-
"README.md",
|
|
15
|
-
"package.json"
|
|
16
|
-
],
|
|
17
|
-
"publishConfig": {
|
|
18
|
-
"access": "public"
|
|
19
|
-
},
|
|
20
|
-
"engines": {
|
|
21
|
-
"node": ">=18"
|
|
22
|
-
},
|
|
23
|
-
"scripts": {
|
|
24
|
-
"build": "node build.mjs",
|
|
25
|
-
"test": "vitest run",
|
|
26
|
-
"typecheck": "tsc --noEmit"
|
|
27
|
-
},
|
|
28
|
-
"dependencies": {
|
|
29
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
30
|
-
"commander": "^12.1.0",
|
|
31
|
-
"js-yaml": "^4.1.1",
|
|
32
|
-
"picocolors": "^1.1.1",
|
|
33
|
-
"zod": "^4.3.6"
|
|
34
|
-
},
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@types/js-yaml": "^4.0.9",
|
|
37
|
-
"@types/node": "^22.0.0",
|
|
38
|
-
"esbuild": "^0.24.0",
|
|
39
|
-
"vitest": "^2.0.0"
|
|
40
|
-
}
|
|
41
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@f-o-h/cli",
|
|
3
|
+
"version": "0.1.66",
|
|
4
|
+
"description": "FOH CLI - AI-operator provisioning tool for Front Of House",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"bin": {
|
|
7
|
+
"foh": "dist/foh.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/foh.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"examples/",
|
|
13
|
+
"schemas/",
|
|
14
|
+
"README.md",
|
|
15
|
+
"package.json"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "node build.mjs",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"typecheck": "tsc --noEmit"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
30
|
+
"commander": "^12.1.0",
|
|
31
|
+
"js-yaml": "^4.1.1",
|
|
32
|
+
"picocolors": "^1.1.1",
|
|
33
|
+
"zod": "^4.3.6"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/js-yaml": "^4.0.9",
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"esbuild": "^0.24.0",
|
|
39
|
+
"vitest": "^2.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|