@f-o-h/cli 0.1.36 → 0.1.38
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 +15 -4
- package/dist/foh.js +105 -6
- package/examples/external-agent-run.example.json +11 -0
- package/package.json +1 -1
- package/schemas/external-agent-run.schema.json +15 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ AI-operator provisioning CLI for Front Of House.
|
|
|
4
4
|
|
|
5
5
|
Public mirror: https://github.com/iiko38/front-of-house-cli
|
|
6
6
|
|
|
7
|
-
Current published baseline: `@f-o-h/cli@0.1.
|
|
7
|
+
Current published baseline: `@f-o-h/cli@0.1.38`
|
|
8
8
|
|
|
9
9
|
This mirror is a generated release artifact. The private product monorepo is not
|
|
10
10
|
published here, and no open-source license is granted unless stated separately.
|
|
@@ -87,6 +87,9 @@ read-only by default; pass `--mutation-mode ensure` or `--repair` only when you
|
|
|
87
87
|
explicitly want proof to ensure missing widget state. Use `--strict` in
|
|
88
88
|
automation when holds should fail the command, and `--mission voice` or
|
|
89
89
|
`--require-phone` when a voice/contact number is mandatory for the demo.
|
|
90
|
+
Use `--contact-path byon` when the proof is meant to validate a
|
|
91
|
+
customer-owned/BYON phone route; missing BYON config then reports
|
|
92
|
+
`byon_voice_number_not_configured` instead of suggesting FOH number purchase.
|
|
90
93
|
|
|
91
94
|
For mass AI-agent evals and repeated demo rehearsals, keep setup on the free
|
|
92
95
|
scaffold lane:
|
|
@@ -99,6 +102,10 @@ FOH_CLI_SPEND_POLICY=no_spend foh setup --org <org-id> --agent-template <templat
|
|
|
99
102
|
buying one. `--phone-mode skip` bypasses the phone step. `--phone-mode purchase`
|
|
100
103
|
is the explicit paid contact path and is fail-closed when
|
|
101
104
|
`FOH_CLI_SPEND_POLICY=no_spend` is set.
|
|
105
|
+
|
|
106
|
+
If managed-number provisioning is blocked by account/provider capacity or empty
|
|
107
|
+
reserve inventory, proof reports `provider_capacity_blocked`; fix capacity or
|
|
108
|
+
switch to BYON rather than retrying blindly.
|
|
102
109
|
|
|
103
110
|
The CLI defaults to the production API at `https://api.frontofhouse.okii.uk`.
|
|
104
111
|
|
|
@@ -123,9 +130,13 @@ foh eval external-agent run \
|
|
|
123
130
|
--prompt-version blank-setup.v1
|
|
124
131
|
```
|
|
125
132
|
|
|
126
|
-
The command writes a versioned prompt, launches an instrumented shell, captures
|
|
127
|
-
FOH CLI commands into `commands.ndjson`, and finalizes `run.json` as an
|
|
128
|
-
`external_agent_run.v1` artifact when the shell exits.
|
|
133
|
+
The command writes a versioned prompt, launches an instrumented shell, captures
|
|
134
|
+
FOH CLI commands into `commands.ndjson`, and finalizes `run.json` as an
|
|
135
|
+
`external_agent_run.v1` artifact when the shell exits.
|
|
136
|
+
|
|
137
|
+
Run artifacts include `eval_state` so repeated benchmark runs make reuse
|
|
138
|
+
explicit: org, agent, and widget reuse are expected; fresh paid phone-number
|
|
139
|
+
creation is not expected.
|
|
129
140
|
|
|
130
141
|
For guarded programmable-runner planning:
|
|
131
142
|
|
package/dist/foh.js
CHANGED
|
@@ -32755,7 +32755,7 @@ var StdioServerTransport = class {
|
|
|
32755
32755
|
};
|
|
32756
32756
|
|
|
32757
32757
|
// src/lib/cli-version.ts
|
|
32758
|
-
var CLI_VERSION = "0.1.
|
|
32758
|
+
var CLI_VERSION = "0.1.38";
|
|
32759
32759
|
|
|
32760
32760
|
// src/commands/mcp-serve.ts
|
|
32761
32761
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -37173,6 +37173,11 @@ function normalizeMutationMode(raw, repair) {
|
|
|
37173
37173
|
const value = String(raw || "read-only").trim().toLowerCase();
|
|
37174
37174
|
return value === "ensure" ? "ensure" : "read-only";
|
|
37175
37175
|
}
|
|
37176
|
+
function normalizeContactPath(raw) {
|
|
37177
|
+
const value = String(raw || "auto").trim().toLowerCase();
|
|
37178
|
+
if (value === "managed" || value === "byon") return value;
|
|
37179
|
+
return "auto";
|
|
37180
|
+
}
|
|
37176
37181
|
function agentIdFromList(response) {
|
|
37177
37182
|
const agents = Array.isArray(response.agents) ? response.agents : [];
|
|
37178
37183
|
const usable = agents.filter((agent) => typeof agent.id === "string" && agent.id.trim());
|
|
@@ -37186,10 +37191,41 @@ function firstUsableOrgId(response) {
|
|
|
37186
37191
|
const usable = orgs.map((org) => org && typeof org === "object" ? org : {}).map((org) => String(org.org_id ?? org.id ?? "").trim()).filter(Boolean);
|
|
37187
37192
|
return { orgId: usable.length === 1 ? usable[0] : void 0, count: usable.length };
|
|
37188
37193
|
}
|
|
37194
|
+
function readStringField(record2, keys) {
|
|
37195
|
+
for (const key of keys) {
|
|
37196
|
+
const value = record2[key];
|
|
37197
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
37198
|
+
}
|
|
37199
|
+
return "";
|
|
37200
|
+
}
|
|
37201
|
+
function isProviderCapacityBlocked(onboarding) {
|
|
37202
|
+
const code = readStringField(onboarding, [
|
|
37203
|
+
"provisioning_reason_code",
|
|
37204
|
+
"provisioning_error_code",
|
|
37205
|
+
"reason_code",
|
|
37206
|
+
"code"
|
|
37207
|
+
]).toLowerCase();
|
|
37208
|
+
if ([
|
|
37209
|
+
"provider_capacity_blocked",
|
|
37210
|
+
"twilio_subaccount_limit_reached",
|
|
37211
|
+
"reserve_pool_exhausted",
|
|
37212
|
+
"global_safety_limit_reached"
|
|
37213
|
+
].includes(code)) {
|
|
37214
|
+
return true;
|
|
37215
|
+
}
|
|
37216
|
+
const message = readStringField(onboarding, [
|
|
37217
|
+
"provisioning_error",
|
|
37218
|
+
"provisioning_message",
|
|
37219
|
+
"error",
|
|
37220
|
+
"message"
|
|
37221
|
+
]).toLowerCase();
|
|
37222
|
+
return /maximum number of subaccounts|subaccount limit|reserve[- ]number pool|reserve pool exhausted|global safety limit/.test(message);
|
|
37223
|
+
}
|
|
37189
37224
|
function registerProve(program3) {
|
|
37190
|
-
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("--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", "Skip simulation certification check").option("--skip-smoke", "Skip widget runtime smoke check").option("--skip-voice-health", "Skip realtime voice provider health check").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 () => {
|
|
37225
|
+
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", "Skip simulation certification check").option("--skip-smoke", "Skip widget runtime smoke check").option("--skip-voice-health", "Skip realtime voice provider health check").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 () => {
|
|
37191
37226
|
const checks = [];
|
|
37192
37227
|
const mission = normalizeMission(opts.mission);
|
|
37228
|
+
const contactPath = normalizeContactPath(opts.contactPath);
|
|
37193
37229
|
const mutationMode = normalizeMutationMode(opts.mutationMode, Boolean(opts.repair));
|
|
37194
37230
|
const ctx = {
|
|
37195
37231
|
tokenPresent: false,
|
|
@@ -37291,18 +37327,41 @@ function registerProve(program3) {
|
|
|
37291
37327
|
if (phoneNumber) {
|
|
37292
37328
|
checks.push(pass("contact_channel", "Contact phone number is provisioned.", {
|
|
37293
37329
|
phone_number_present: true,
|
|
37294
|
-
provisioning_status: provisioningStatus
|
|
37330
|
+
provisioning_status: provisioningStatus,
|
|
37331
|
+
contact_path: contactPath
|
|
37332
|
+
}));
|
|
37333
|
+
} else if (contactPath === "byon" && (opts.requirePhone || mission === "voice")) {
|
|
37334
|
+
checks.push(hold("contact_channel", "byon_voice_number_not_configured", "BYON/customer-owned voice contact path was requested, but no contact phone is configured for this org.", `foh provision status --org ${ctx.orgId} --json`, {
|
|
37335
|
+
provisioning_status: provisioningStatus,
|
|
37336
|
+
mission,
|
|
37337
|
+
contact_path: contactPath,
|
|
37338
|
+
spend_policy: resolveCliSpendPolicy(),
|
|
37339
|
+
spend_class: "customer_owned",
|
|
37340
|
+
safe_to_retry: false,
|
|
37341
|
+
operator_note: "Attach or configure the customer-owned voice number, then rerun proof. This path should not buy a FOH-owned number."
|
|
37342
|
+
}));
|
|
37343
|
+
} else if (provisioningStatus === "failed" && isProviderCapacityBlocked(onboarding) && (opts.requirePhone || mission === "voice")) {
|
|
37344
|
+
checks.push(hold("contact_channel", "provider_capacity_blocked", "Phone/contact provisioning is blocked by provider/account capacity or empty reserve inventory.", `foh provision status --org ${ctx.orgId} --json`, {
|
|
37345
|
+
provisioning_status: provisioningStatus,
|
|
37346
|
+
mission,
|
|
37347
|
+
contact_path: contactPath,
|
|
37348
|
+
spend_policy: resolveCliSpendPolicy(),
|
|
37349
|
+
spend_class: "paid_foh",
|
|
37350
|
+
safe_to_retry: false,
|
|
37351
|
+
operator_note: "Do not retry blindly. Resolve provider/account capacity or use BYON/customer-owned contact path."
|
|
37295
37352
|
}));
|
|
37296
37353
|
} else if (provisioningStatus === "failed" && (opts.requirePhone || mission === "voice")) {
|
|
37297
37354
|
checks.push(hold("contact_channel", "contact_phone_provisioning_failed", "Phone/contact provisioning failed for this org.", `foh provision status --org ${ctx.orgId} --json`, {
|
|
37298
37355
|
provisioning_status: provisioningStatus,
|
|
37299
37356
|
mission,
|
|
37357
|
+
contact_path: contactPath,
|
|
37300
37358
|
spend_policy: resolveCliSpendPolicy()
|
|
37301
37359
|
}));
|
|
37302
37360
|
} else if (isNoSpendPolicy() && (opts.requirePhone || mission === "voice")) {
|
|
37303
37361
|
checks.push(hold("contact_channel", "voice_contact_expected_no_spend_hold", "No phone/contact number is provisioned; this is expected in no-spend eval mode unless a BYON/customer-approved phone path exists.", `foh provision status --org ${ctx.orgId} --json`, {
|
|
37304
37362
|
provisioning_status: provisioningStatus,
|
|
37305
37363
|
mission,
|
|
37364
|
+
contact_path: contactPath,
|
|
37306
37365
|
spend_policy: resolveCliSpendPolicy(),
|
|
37307
37366
|
spend_class: "free",
|
|
37308
37367
|
safe_to_retry: true,
|
|
@@ -37312,6 +37371,7 @@ function registerProve(program3) {
|
|
|
37312
37371
|
checks.push(hold("contact_channel", "contact_phone_missing", "No phone/contact number is provisioned for this org.", `foh provision buy --org ${ctx.orgId} --json`, {
|
|
37313
37372
|
provisioning_status: provisioningStatus,
|
|
37314
37373
|
mission,
|
|
37374
|
+
contact_path: contactPath,
|
|
37315
37375
|
spend_policy: resolveCliSpendPolicy()
|
|
37316
37376
|
}));
|
|
37317
37377
|
} else {
|
|
@@ -37464,6 +37524,7 @@ function registerProve(program3) {
|
|
|
37464
37524
|
org_id: ctx.orgId ?? null,
|
|
37465
37525
|
agent_id: ctx.agentId ?? null,
|
|
37466
37526
|
mission,
|
|
37527
|
+
contact_path: contactPath,
|
|
37467
37528
|
mutation_mode: mutationMode,
|
|
37468
37529
|
widget_public_key_present: Boolean(ctx.widgetPublicKey),
|
|
37469
37530
|
conversation_id: ctx.conversationId ?? null,
|
|
@@ -39196,6 +39257,12 @@ function classifyRun(input) {
|
|
|
39196
39257
|
if (hasCommandReason(new RegExp(PAID_RESOURCE_BLOCKED_REASON_CODE, "i"))) {
|
|
39197
39258
|
return { status: "hold", reasonCode: PAID_RESOURCE_BLOCKED_REASON_CODE };
|
|
39198
39259
|
}
|
|
39260
|
+
if (hasCommandReason(/provider_capacity_blocked/i)) {
|
|
39261
|
+
return { status: "hold", reasonCode: "provider_capacity_blocked" };
|
|
39262
|
+
}
|
|
39263
|
+
if (hasCommandReason(/byon_voice_number_not_configured/i)) {
|
|
39264
|
+
return { status: "hold", reasonCode: "byon_voice_number_not_configured" };
|
|
39265
|
+
}
|
|
39199
39266
|
if (hasCommandReason(/contact_phone_provisioning_failed/i)) {
|
|
39200
39267
|
return { status: "hold", reasonCode: "voice_contact_phone_provisioning_failed" };
|
|
39201
39268
|
}
|
|
@@ -39230,12 +39297,18 @@ ${stderr}`;
|
|
|
39230
39297
|
if (/ENV_NETWORK_DNS_BLOCK|Could not resolve host|npm ping.*timeout|NO_EXECUTABLE_INSTALL/i.test(combined)) {
|
|
39231
39298
|
return { status: "hold", reasonCode: "codex_network_dns_blocked" };
|
|
39232
39299
|
}
|
|
39233
|
-
if (/contact_phone_provisioning_failed/i.test(combined)) {
|
|
39234
|
-
return { status: "hold", reasonCode: "voice_contact_phone_provisioning_failed" };
|
|
39235
|
-
}
|
|
39236
39300
|
if (new RegExp(PAID_RESOURCE_BLOCKED_REASON_CODE, "i").test(combined)) {
|
|
39237
39301
|
return { status: "hold", reasonCode: PAID_RESOURCE_BLOCKED_REASON_CODE };
|
|
39238
39302
|
}
|
|
39303
|
+
if (/provider_capacity_blocked/i.test(combined)) {
|
|
39304
|
+
return { status: "hold", reasonCode: "provider_capacity_blocked" };
|
|
39305
|
+
}
|
|
39306
|
+
if (/byon_voice_number_not_configured/i.test(combined)) {
|
|
39307
|
+
return { status: "hold", reasonCode: "byon_voice_number_not_configured" };
|
|
39308
|
+
}
|
|
39309
|
+
if (/contact_phone_provisioning_failed/i.test(combined)) {
|
|
39310
|
+
return { status: "hold", reasonCode: "voice_contact_phone_provisioning_failed" };
|
|
39311
|
+
}
|
|
39239
39312
|
if (/voice_contact_expected_no_spend_hold/i.test(combined)) {
|
|
39240
39313
|
return { status: "hold", reasonCode: "voice_contact_expected_no_spend_hold" };
|
|
39241
39314
|
}
|
|
@@ -39292,6 +39365,17 @@ function buildExecutedRunArtifact(input) {
|
|
|
39292
39365
|
],
|
|
39293
39366
|
commands_run: commands.map((command) => command.command),
|
|
39294
39367
|
docs_pages_used: [],
|
|
39368
|
+
eval_state: {
|
|
39369
|
+
org_reuse_expected: true,
|
|
39370
|
+
agent_reuse_expected: true,
|
|
39371
|
+
widget_reuse_expected: true,
|
|
39372
|
+
fresh_org_expected: false,
|
|
39373
|
+
fresh_agent_expected: false,
|
|
39374
|
+
phone_purchase_expected: false,
|
|
39375
|
+
paid_resource_creation_expected: false,
|
|
39376
|
+
spend_policy_expected: NO_SPEND_POLICY,
|
|
39377
|
+
rationale: "Mass external-agent evals benchmark public docs/CLI/API clarity; reuse avoids paid phone and Twilio inventory churn."
|
|
39378
|
+
},
|
|
39295
39379
|
artifacts: {
|
|
39296
39380
|
terminal_transcript: relativeArtifactName(input.run.outputs.jsonl),
|
|
39297
39381
|
command_log: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "commands.ndjson")) ? "commands.ndjson" : null,
|
|
@@ -39509,6 +39593,19 @@ function writeSession(runDir, session) {
|
|
|
39509
39593
|
`, "utf8");
|
|
39510
39594
|
return path2;
|
|
39511
39595
|
}
|
|
39596
|
+
function buildDefaultEvalState() {
|
|
39597
|
+
return {
|
|
39598
|
+
org_reuse_expected: true,
|
|
39599
|
+
agent_reuse_expected: true,
|
|
39600
|
+
widget_reuse_expected: true,
|
|
39601
|
+
fresh_org_expected: false,
|
|
39602
|
+
fresh_agent_expected: false,
|
|
39603
|
+
phone_purchase_expected: false,
|
|
39604
|
+
paid_resource_creation_expected: false,
|
|
39605
|
+
spend_policy_expected: "no_spend",
|
|
39606
|
+
rationale: "Mass external-agent evals benchmark public docs/CLI/API clarity; reuse avoids paid phone and Twilio inventory churn."
|
|
39607
|
+
};
|
|
39608
|
+
}
|
|
39512
39609
|
function buildRunArtifact(input) {
|
|
39513
39610
|
const commands = readCommandRecords(input.runDir);
|
|
39514
39611
|
const startedAt = String(input.session.started_at);
|
|
@@ -39545,6 +39642,7 @@ function buildRunArtifact(input) {
|
|
|
39545
39642
|
],
|
|
39546
39643
|
commands_run: commands.map((command) => command.command),
|
|
39547
39644
|
docs_pages_used: [],
|
|
39645
|
+
eval_state: buildDefaultEvalState(),
|
|
39548
39646
|
artifacts: {
|
|
39549
39647
|
terminal_transcript: null,
|
|
39550
39648
|
command_log: "commands.ndjson",
|
|
@@ -39651,6 +39749,7 @@ function registerEval(program3) {
|
|
|
39651
39749
|
manual_intervention_count: 0,
|
|
39652
39750
|
run_dir: runDir,
|
|
39653
39751
|
prompt_path: promptPath,
|
|
39752
|
+
eval_state: buildDefaultEvalState(),
|
|
39654
39753
|
capture_env: {
|
|
39655
39754
|
[EXTERNAL_AGENT_RUN_DIR_ENV]: runDir,
|
|
39656
39755
|
[EXTERNAL_AGENT_PROMPT_VERSION_ENV]: promptVersion
|
|
@@ -38,6 +38,17 @@
|
|
|
38
38
|
"https://frontofhouse.okii.uk/guides/cli-install-and-upgrade",
|
|
39
39
|
"https://frontofhouse.okii.uk/guides/error-handling-and-debugging"
|
|
40
40
|
],
|
|
41
|
+
"eval_state": {
|
|
42
|
+
"org_reuse_expected": true,
|
|
43
|
+
"agent_reuse_expected": true,
|
|
44
|
+
"widget_reuse_expected": true,
|
|
45
|
+
"fresh_org_expected": false,
|
|
46
|
+
"fresh_agent_expected": false,
|
|
47
|
+
"phone_purchase_expected": false,
|
|
48
|
+
"paid_resource_creation_expected": false,
|
|
49
|
+
"spend_policy_expected": "no_spend",
|
|
50
|
+
"rationale": "Mass external-agent evals benchmark public docs/CLI/API clarity; reuse avoids paid phone and Twilio inventory churn."
|
|
51
|
+
},
|
|
41
52
|
"artifacts": {
|
|
42
53
|
"terminal_transcript": "terminal-transcript.txt",
|
|
43
54
|
"proof_bundle": null,
|
package/package.json
CHANGED
|
@@ -67,6 +67,21 @@
|
|
|
67
67
|
"type": "array",
|
|
68
68
|
"items": { "type": "string" }
|
|
69
69
|
},
|
|
70
|
+
"eval_state": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"properties": {
|
|
73
|
+
"org_reuse_expected": { "type": "boolean" },
|
|
74
|
+
"agent_reuse_expected": { "type": "boolean" },
|
|
75
|
+
"widget_reuse_expected": { "type": "boolean" },
|
|
76
|
+
"fresh_org_expected": { "type": "boolean" },
|
|
77
|
+
"fresh_agent_expected": { "type": "boolean" },
|
|
78
|
+
"phone_purchase_expected": { "type": "boolean" },
|
|
79
|
+
"paid_resource_creation_expected": { "type": "boolean" },
|
|
80
|
+
"spend_policy_expected": { "type": "string" },
|
|
81
|
+
"rationale": { "type": "string" }
|
|
82
|
+
},
|
|
83
|
+
"additionalProperties": true
|
|
84
|
+
},
|
|
70
85
|
"artifacts": {
|
|
71
86
|
"type": "object",
|
|
72
87
|
"properties": {
|