@f-o-h/cli 0.1.82 → 0.1.84
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/foh.js +1953 -998
- package/package.json +1 -1
package/dist/foh.js
CHANGED
|
@@ -6046,7 +6046,7 @@ var require_compile = __commonJS({
|
|
|
6046
6046
|
const schOrFunc = root.refs[ref];
|
|
6047
6047
|
if (schOrFunc)
|
|
6048
6048
|
return schOrFunc;
|
|
6049
|
-
let _sch =
|
|
6049
|
+
let _sch = resolve15.call(this, root, ref);
|
|
6050
6050
|
if (_sch === void 0) {
|
|
6051
6051
|
const schema2 = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
|
|
6052
6052
|
const { schemaId } = this.opts;
|
|
@@ -6073,7 +6073,7 @@ var require_compile = __commonJS({
|
|
|
6073
6073
|
function sameSchemaEnv(s1, s2) {
|
|
6074
6074
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
6075
6075
|
}
|
|
6076
|
-
function
|
|
6076
|
+
function resolve15(root, ref) {
|
|
6077
6077
|
let sch;
|
|
6078
6078
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
6079
6079
|
ref = sch;
|
|
@@ -6648,7 +6648,7 @@ var require_fast_uri = __commonJS({
|
|
|
6648
6648
|
}
|
|
6649
6649
|
return uri;
|
|
6650
6650
|
}
|
|
6651
|
-
function
|
|
6651
|
+
function resolve15(baseURI, relativeURI, options) {
|
|
6652
6652
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
6653
6653
|
const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
6654
6654
|
schemelessOptions.skipEscape = true;
|
|
@@ -6875,7 +6875,7 @@ var require_fast_uri = __commonJS({
|
|
|
6875
6875
|
var fastUri = {
|
|
6876
6876
|
SCHEMES,
|
|
6877
6877
|
normalize,
|
|
6878
|
-
resolve:
|
|
6878
|
+
resolve: resolve15,
|
|
6879
6879
|
resolveComponent,
|
|
6880
6880
|
equal,
|
|
6881
6881
|
serialize,
|
|
@@ -10183,21 +10183,21 @@ async function promptLine(label, {
|
|
|
10183
10183
|
allowEmpty = false,
|
|
10184
10184
|
defaultValue
|
|
10185
10185
|
} = {}) {
|
|
10186
|
-
return await new Promise((
|
|
10186
|
+
return await new Promise((resolve15) => {
|
|
10187
10187
|
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
10188
10188
|
const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout, terminal: true });
|
|
10189
10189
|
rl.question(`${label}${suffix}: `, (answer) => {
|
|
10190
10190
|
rl.close();
|
|
10191
10191
|
const value = String(answer ?? "").trim();
|
|
10192
10192
|
if (!value && typeof defaultValue === "string") {
|
|
10193
|
-
|
|
10193
|
+
resolve15(defaultValue);
|
|
10194
10194
|
return;
|
|
10195
10195
|
}
|
|
10196
10196
|
if (!value && !allowEmpty) {
|
|
10197
|
-
|
|
10197
|
+
resolve15("");
|
|
10198
10198
|
return;
|
|
10199
10199
|
}
|
|
10200
|
-
|
|
10200
|
+
resolve15(value);
|
|
10201
10201
|
});
|
|
10202
10202
|
});
|
|
10203
10203
|
}
|
|
@@ -10205,7 +10205,7 @@ async function promptSecret(label) {
|
|
|
10205
10205
|
if (!process.stdin.isTTY || !process.stdout.isTTY || typeof process.stdin.setRawMode !== "function") {
|
|
10206
10206
|
return await promptLine(label);
|
|
10207
10207
|
}
|
|
10208
|
-
return await new Promise((
|
|
10208
|
+
return await new Promise((resolve15) => {
|
|
10209
10209
|
const stdin = process.stdin;
|
|
10210
10210
|
const stdout = process.stdout;
|
|
10211
10211
|
const wasRaw = Boolean(stdin.isRaw);
|
|
@@ -10219,7 +10219,7 @@ async function promptSecret(label) {
|
|
|
10219
10219
|
const finish = () => {
|
|
10220
10220
|
cleanup();
|
|
10221
10221
|
stdout.write("\n");
|
|
10222
|
-
|
|
10222
|
+
resolve15(value);
|
|
10223
10223
|
};
|
|
10224
10224
|
const onData = (chunk) => {
|
|
10225
10225
|
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
@@ -10228,7 +10228,7 @@ async function promptSecret(label) {
|
|
|
10228
10228
|
cleanup();
|
|
10229
10229
|
process.exitCode = 130;
|
|
10230
10230
|
stdout.write("\n");
|
|
10231
|
-
return
|
|
10231
|
+
return resolve15("");
|
|
10232
10232
|
}
|
|
10233
10233
|
if (char === "\r" || char === "\n") {
|
|
10234
10234
|
finish();
|
|
@@ -10256,6 +10256,79 @@ async function promptSecret(label) {
|
|
|
10256
10256
|
});
|
|
10257
10257
|
}
|
|
10258
10258
|
|
|
10259
|
+
// src/tui/wizard.ts
|
|
10260
|
+
function resolveDefaultValue(defaultValue, state) {
|
|
10261
|
+
if (typeof defaultValue === "function") return defaultValue(state);
|
|
10262
|
+
return defaultValue;
|
|
10263
|
+
}
|
|
10264
|
+
async function readPromptValue(prompt, state) {
|
|
10265
|
+
const kind = prompt.kind ?? "line";
|
|
10266
|
+
const defaultValue = resolveDefaultValue(prompt.defaultValue, state);
|
|
10267
|
+
if (kind === "confirm") {
|
|
10268
|
+
const answer = await promptLine(prompt.label, { allowEmpty: true, defaultValue });
|
|
10269
|
+
return isAffirmative(answer, prompt.confirmDefault ?? false);
|
|
10270
|
+
}
|
|
10271
|
+
if (kind === "secret") {
|
|
10272
|
+
return (await promptSecret(prompt.label)).trim();
|
|
10273
|
+
}
|
|
10274
|
+
return await promptLine(prompt.label, {
|
|
10275
|
+
allowEmpty: prompt.allowEmpty ?? false,
|
|
10276
|
+
defaultValue
|
|
10277
|
+
});
|
|
10278
|
+
}
|
|
10279
|
+
async function runWizardSession(options) {
|
|
10280
|
+
const state = { ...options.initialState };
|
|
10281
|
+
if (!options.enabled) return state;
|
|
10282
|
+
ensureInteractive(options.step, options.remediation);
|
|
10283
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
10284
|
+
if (options.title) {
|
|
10285
|
+
process.stdout.write(`
|
|
10286
|
+
${options.title}
|
|
10287
|
+
`);
|
|
10288
|
+
process.stdout.write(`${"-".repeat(options.title.length)}
|
|
10289
|
+
`);
|
|
10290
|
+
}
|
|
10291
|
+
if (options.subtitle) process.stdout.write(`${options.subtitle}
|
|
10292
|
+
`);
|
|
10293
|
+
if (Array.isArray(options.notes) && options.notes.length > 0) {
|
|
10294
|
+
for (const note of options.notes) {
|
|
10295
|
+
process.stdout.write(`${note}
|
|
10296
|
+
`);
|
|
10297
|
+
}
|
|
10298
|
+
}
|
|
10299
|
+
if (options.title || options.subtitle || (options.notes?.length ?? 0) > 0) {
|
|
10300
|
+
process.stdout.write("\n");
|
|
10301
|
+
}
|
|
10302
|
+
}
|
|
10303
|
+
for (const prompt of options.prompts) {
|
|
10304
|
+
if (typeof prompt.shouldPrompt === "function" && !prompt.shouldPrompt(state)) continue;
|
|
10305
|
+
while (true) {
|
|
10306
|
+
const resolved = await readPromptValue(prompt, state);
|
|
10307
|
+
if (typeof resolved === "boolean") {
|
|
10308
|
+
state[prompt.key] = resolved;
|
|
10309
|
+
prompt.onResolved?.(resolved, state);
|
|
10310
|
+
break;
|
|
10311
|
+
}
|
|
10312
|
+
const normalized = prompt.normalize ? prompt.normalize(resolved, state) : String(resolved).trim();
|
|
10313
|
+
if (prompt.required && !normalized) {
|
|
10314
|
+
process.stdout.write(`${prompt.label} is required.
|
|
10315
|
+
`);
|
|
10316
|
+
continue;
|
|
10317
|
+
}
|
|
10318
|
+
const validationError = prompt.validate?.(normalized, state) ?? null;
|
|
10319
|
+
if (validationError) {
|
|
10320
|
+
process.stdout.write(`${validationError}
|
|
10321
|
+
`);
|
|
10322
|
+
continue;
|
|
10323
|
+
}
|
|
10324
|
+
state[prompt.key] = normalized;
|
|
10325
|
+
prompt.onResolved?.(normalized, state);
|
|
10326
|
+
break;
|
|
10327
|
+
}
|
|
10328
|
+
}
|
|
10329
|
+
return state;
|
|
10330
|
+
}
|
|
10331
|
+
|
|
10259
10332
|
// src/lib/console-url.ts
|
|
10260
10333
|
var DEFAULT_FOH_CONSOLE_URL = "https://app.frontofhouse.okii.uk";
|
|
10261
10334
|
function normalizeBaseUrl(value) {
|
|
@@ -10393,26 +10466,35 @@ function emitBrowserSignupLink(opts) {
|
|
|
10393
10466
|
}, { json: opts.json ?? false });
|
|
10394
10467
|
}
|
|
10395
10468
|
async function resolveLoginInputs(opts) {
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10469
|
+
const initial = {
|
|
10470
|
+
email: String(opts.email ?? "").trim(),
|
|
10471
|
+
password: String(opts.password ?? "").trim()
|
|
10472
|
+
};
|
|
10473
|
+
const needsPrompts = !initial.email || !initial.password;
|
|
10474
|
+
const resolved = await runWizardSession({
|
|
10475
|
+
step: "auth.login",
|
|
10476
|
+
remediation: "Pass --email and --password when running non-interactively.",
|
|
10477
|
+
title: !opts.json ? "Auth Login Wizard" : void 0,
|
|
10478
|
+
subtitle: !opts.json ? "Step 1/2: Enter account credentials." : void 0,
|
|
10479
|
+
enabled: needsPrompts,
|
|
10480
|
+
initialState: initial,
|
|
10481
|
+
prompts: [
|
|
10482
|
+
{
|
|
10483
|
+
key: "email",
|
|
10484
|
+
label: "Email",
|
|
10485
|
+
required: true,
|
|
10486
|
+
shouldPrompt: (state) => !String(state.email ?? "").trim()
|
|
10487
|
+
},
|
|
10488
|
+
{
|
|
10489
|
+
key: "password",
|
|
10490
|
+
label: "Password",
|
|
10491
|
+
kind: "secret",
|
|
10492
|
+
required: true,
|
|
10493
|
+
shouldPrompt: (state) => !String(state.password ?? "").trim()
|
|
10494
|
+
}
|
|
10495
|
+
]
|
|
10496
|
+
});
|
|
10497
|
+
if (!resolved.email || !resolved.password) {
|
|
10416
10498
|
throw new FohError({
|
|
10417
10499
|
step: "auth.login",
|
|
10418
10500
|
error: "Email and password are required",
|
|
@@ -10422,7 +10504,7 @@ async function resolveLoginInputs(opts) {
|
|
|
10422
10504
|
if ((opts.wizard || needsPrompts) && process.stdout.isTTY && process.stdin.isTTY && !opts.json) {
|
|
10423
10505
|
process.stdout.write("Step 2/2: Requesting service token from API.\n");
|
|
10424
10506
|
}
|
|
10425
|
-
return { email:
|
|
10507
|
+
return { email: String(resolved.email), password: String(resolved.password) };
|
|
10426
10508
|
}
|
|
10427
10509
|
async function maybeSelectDefaultOrg(orgs, jsonMode) {
|
|
10428
10510
|
if (jsonMode) return void 0;
|
|
@@ -10501,7 +10583,7 @@ async function storeAuthenticatedSession(params) {
|
|
|
10501
10583
|
return output;
|
|
10502
10584
|
}
|
|
10503
10585
|
function sleep(ms) {
|
|
10504
|
-
return new Promise((
|
|
10586
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
10505
10587
|
}
|
|
10506
10588
|
function hasExplicitTimeoutFlag(argv = process.argv) {
|
|
10507
10589
|
return argv.some((arg) => arg === "--timeout-seconds" || arg.startsWith("--timeout-seconds="));
|
|
@@ -11059,7 +11141,7 @@ async function pollUntil(check2, opts) {
|
|
|
11059
11141
|
}
|
|
11060
11142
|
}
|
|
11061
11143
|
function sleep2(ms) {
|
|
11062
|
-
return new Promise((
|
|
11144
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
11063
11145
|
}
|
|
11064
11146
|
|
|
11065
11147
|
// src/commands/compliance.ts
|
|
@@ -14183,8 +14265,8 @@ function registerAgentGuardrailCommands(agent) {
|
|
|
14183
14265
|
try {
|
|
14184
14266
|
rule = JSON.parse(opts.rule);
|
|
14185
14267
|
} catch {
|
|
14186
|
-
const { readFileSync:
|
|
14187
|
-
rule = JSON.parse(
|
|
14268
|
+
const { readFileSync: readFileSync19 } = await import("fs");
|
|
14269
|
+
rule = JSON.parse(readFileSync19(opts.rule, "utf-8"));
|
|
14188
14270
|
}
|
|
14189
14271
|
const data = await apiFetch(`/v1/console/agents/${opts.agent}/guardrails`, {
|
|
14190
14272
|
method: "POST",
|
|
@@ -14680,8 +14762,8 @@ function registerAgent(program3) {
|
|
|
14680
14762
|
});
|
|
14681
14763
|
format(data, { json: opts.json ?? false });
|
|
14682
14764
|
}));
|
|
14683
|
-
agent.command("rollback").description("Roll back an agent to a previous version").requiredOption("--agent <id>", "Agent ID").option("--version <
|
|
14684
|
-
const body = opts.
|
|
14765
|
+
agent.command("rollback").description("Roll back an agent to a previous version").requiredOption("--agent <id>", "Agent ID").option("--target-version <n>", "Target policy version number (defaults to previous)").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 () => {
|
|
14766
|
+
const body = opts.targetVersion ? { version: Number(opts.targetVersion) } : {};
|
|
14685
14767
|
const data = await apiFetch(`/v1/console/agents/${opts.agent}/rollback`, {
|
|
14686
14768
|
method: "POST",
|
|
14687
14769
|
body: JSON.stringify(body),
|
|
@@ -14753,9 +14835,9 @@ function registerAgent(program3) {
|
|
|
14753
14835
|
process.stdout.write(yaml);
|
|
14754
14836
|
return;
|
|
14755
14837
|
}
|
|
14756
|
-
const { writeFileSync:
|
|
14838
|
+
const { writeFileSync: writeFileSync15 } = await import("fs");
|
|
14757
14839
|
const outputPath = opts.output ?? "tenant.yaml";
|
|
14758
|
-
|
|
14840
|
+
writeFileSync15(
|
|
14759
14841
|
outputPath,
|
|
14760
14842
|
`# tenant.yaml - Front Of House agent manifest
|
|
14761
14843
|
# Edit this file and run: foh plan tenant.yaml
|
|
@@ -14779,8 +14861,8 @@ ${yaml}`,
|
|
|
14779
14861
|
format(data, { json: opts.json ?? false });
|
|
14780
14862
|
}));
|
|
14781
14863
|
agent.command("chat").description("Interactive REPL \u2014 send messages to an agent (human use only)").requiredOption("--agent <id>", "Agent ID").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").action(async (opts) => withCommandErrorHandling(async () => {
|
|
14782
|
-
const { createInterface:
|
|
14783
|
-
const rl =
|
|
14864
|
+
const { createInterface: createInterface3 } = await import("readline");
|
|
14865
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout, terminal: true });
|
|
14784
14866
|
const ask = (prompt) => new Promise((res) => rl.question(prompt, res));
|
|
14785
14867
|
console.log("Type your message and press Enter. Ctrl+C to exit.\n");
|
|
14786
14868
|
while (true) {
|
|
@@ -15286,6 +15368,9 @@ function registerInstagramChannelCommands(instagram, addCommonOptions) {
|
|
|
15286
15368
|
}));
|
|
15287
15369
|
}
|
|
15288
15370
|
|
|
15371
|
+
// src/commands/channel-whatsapp.ts
|
|
15372
|
+
var import_node_fs2 = require("node:fs");
|
|
15373
|
+
|
|
15289
15374
|
// src/commands/channel-whatsapp-helpers.ts
|
|
15290
15375
|
function parsePositiveNumber(value, fallback) {
|
|
15291
15376
|
if (value === void 0 || value === null || String(value).trim() === "") return fallback;
|
|
@@ -15299,82 +15384,6 @@ function parsePositiveNumber(value, fallback) {
|
|
|
15299
15384
|
}
|
|
15300
15385
|
return parsed;
|
|
15301
15386
|
}
|
|
15302
|
-
function readCapabilityFlag(capabilities, key) {
|
|
15303
|
-
if (!capabilities || typeof capabilities !== "object") return false;
|
|
15304
|
-
const direct = capabilities[key];
|
|
15305
|
-
if (typeof direct === "boolean") return direct;
|
|
15306
|
-
const lower = capabilities[key.toLowerCase()];
|
|
15307
|
-
if (typeof lower === "boolean") return lower;
|
|
15308
|
-
const upper = capabilities[key.toUpperCase()];
|
|
15309
|
-
if (typeof upper === "boolean") return upper;
|
|
15310
|
-
return false;
|
|
15311
|
-
}
|
|
15312
|
-
function resolveWhatsAppJourney(params) {
|
|
15313
|
-
const readinessCode = String(params.verificationReadinessCode || "").trim();
|
|
15314
|
-
const selectedMethod = String(params.selectedOtpMethod || "").trim().toLowerCase();
|
|
15315
|
-
const recordingUrl = String(params.verificationReadiness?.recent_voice_call?.recording_url || "").trim();
|
|
15316
|
-
if (params.telephonyReady === false || readinessCode === "twilio_number_not_configured") {
|
|
15317
|
-
return {
|
|
15318
|
-
stage: "provision_number",
|
|
15319
|
-
reason_code: readinessCode || "telephony_step_incomplete",
|
|
15320
|
-
explanation: "A dedicated Twilio phone number is required before WhatsApp verification can start.",
|
|
15321
|
-
do_this_now: params.commands.provisionBuy,
|
|
15322
|
-
after_this: params.commands.verificationReadiness
|
|
15323
|
-
};
|
|
15324
|
-
}
|
|
15325
|
-
if (readinessCode === "verification_voice_call_required") {
|
|
15326
|
-
return {
|
|
15327
|
-
stage: "verify_number_voice",
|
|
15328
|
-
reason_code: readinessCode,
|
|
15329
|
-
explanation: recordingUrl ? `Voice OTP path is active. Latest call recording is available for OTP recovery: ${recordingUrl}` : "Voice OTP path is active. Complete Meta phone-call verification first.",
|
|
15330
|
-
do_this_now: params.commands.verificationReadinessVoice,
|
|
15331
|
-
after_this: params.commands.connect
|
|
15332
|
-
};
|
|
15333
|
-
}
|
|
15334
|
-
if (readinessCode === "otp_method_sms_not_supported") {
|
|
15335
|
-
return {
|
|
15336
|
-
stage: "verify_number_voice",
|
|
15337
|
-
reason_code: readinessCode,
|
|
15338
|
-
explanation: "The current number is voice-only, so SMS verification is not supported.",
|
|
15339
|
-
do_this_now: params.commands.verificationReadinessVoice,
|
|
15340
|
-
after_this: params.commands.connect
|
|
15341
|
-
};
|
|
15342
|
-
}
|
|
15343
|
-
if (readinessCode === "otp_method_voice_not_supported") {
|
|
15344
|
-
return {
|
|
15345
|
-
stage: "verify_number_sms",
|
|
15346
|
-
reason_code: readinessCode,
|
|
15347
|
-
explanation: "The current number does not support voice verification, so SMS must be used.",
|
|
15348
|
-
do_this_now: params.commands.verificationReadinessSms,
|
|
15349
|
-
after_this: params.commands.connect
|
|
15350
|
-
};
|
|
15351
|
-
}
|
|
15352
|
-
if (readinessCode === "verification_code_not_found") {
|
|
15353
|
-
return {
|
|
15354
|
-
stage: selectedMethod === "voice" ? "verify_number_voice" : "verify_number_sms",
|
|
15355
|
-
reason_code: readinessCode,
|
|
15356
|
-
explanation: selectedMethod === "voice" ? "Waiting for an inbound verification call from Meta." : "Waiting for an inbound SMS verification code from Meta.",
|
|
15357
|
-
do_this_now: selectedMethod === "voice" ? params.commands.verificationReadinessVoice : params.commands.verificationReadiness,
|
|
15358
|
-
after_this: params.commands.connect
|
|
15359
|
-
};
|
|
15360
|
-
}
|
|
15361
|
-
if (!params.whatsappConfigured || !params.whatsappReady || readinessCode === "verification_code_detected") {
|
|
15362
|
-
return {
|
|
15363
|
-
stage: "connect_channel",
|
|
15364
|
-
reason_code: readinessCode || "whatsapp_channel_not_ready",
|
|
15365
|
-
explanation: "Phone verification is ready; now connect WhatsApp channel credentials.",
|
|
15366
|
-
do_this_now: params.commands.connect,
|
|
15367
|
-
after_this: params.commands.proof
|
|
15368
|
-
};
|
|
15369
|
-
}
|
|
15370
|
-
return {
|
|
15371
|
-
stage: "proof",
|
|
15372
|
-
reason_code: readinessCode || "ready_for_proof",
|
|
15373
|
-
explanation: "Channel is configured and ready for deterministic closure checks.",
|
|
15374
|
-
do_this_now: params.commands.proof,
|
|
15375
|
-
after_this: null
|
|
15376
|
-
};
|
|
15377
|
-
}
|
|
15378
15387
|
function withNumberedSteps(steps) {
|
|
15379
15388
|
return steps.filter((step) => step.trim().length > 0).map((step, index) => `${index + 1}. ${step}`).join(" ");
|
|
15380
15389
|
}
|
|
@@ -15596,85 +15605,6 @@ function buildWebhookUrl(apiBaseUrl) {
|
|
|
15596
15605
|
function generateVerifyToken() {
|
|
15597
15606
|
return `${WHATSAPP_VERIFY_TOKEN_PREFIX}-${(0, import_node_crypto.randomBytes)(12).toString("hex")}`;
|
|
15598
15607
|
}
|
|
15599
|
-
function readStepStatus(steps, id) {
|
|
15600
|
-
const matched = Array.isArray(steps) ? steps.find((step) => String(step?.id || "").trim() === id) : null;
|
|
15601
|
-
if (!matched) return null;
|
|
15602
|
-
return matched.status === true;
|
|
15603
|
-
}
|
|
15604
|
-
async function resolveWhatsAppSetupInputs(opts) {
|
|
15605
|
-
let phoneNumberId = String(opts.phoneNumberId ?? "").trim();
|
|
15606
|
-
let accessToken = String(opts.accessToken ?? "").trim();
|
|
15607
|
-
let verifyToken = String(opts.verifyToken ?? "").trim();
|
|
15608
|
-
let verifyTokenGenerated = false;
|
|
15609
|
-
let appSecret = String(opts.appSecret ?? "").trim();
|
|
15610
|
-
const canGenerateVerifyToken = Boolean(opts.generateVerifyToken);
|
|
15611
|
-
let audioEnabled = parseBooleanOption({
|
|
15612
|
-
value: opts.audioEnabled,
|
|
15613
|
-
fallback: true,
|
|
15614
|
-
optionName: "--audio-enabled",
|
|
15615
|
-
step: "channel.whatsapp.setup"
|
|
15616
|
-
});
|
|
15617
|
-
const needsPrompts = !phoneNumberId || !accessToken || !verifyToken && !canGenerateVerifyToken || !appSecret;
|
|
15618
|
-
if (opts.wizard || needsPrompts) {
|
|
15619
|
-
ensureInteractive(
|
|
15620
|
-
"channel.whatsapp.setup",
|
|
15621
|
-
"Pass --phone-number-id, --access-token, --verify-token and --app-secret when running non-interactively."
|
|
15622
|
-
);
|
|
15623
|
-
}
|
|
15624
|
-
if ((opts.wizard || needsPrompts) && process.stdin.isTTY && process.stdout.isTTY && !opts.json) {
|
|
15625
|
-
process.stdout.write("\nWhatsApp Setup Wizard\n");
|
|
15626
|
-
process.stdout.write("---------------------\n");
|
|
15627
|
-
process.stdout.write("This flow configures the channel and runs readiness checks before completion.\n");
|
|
15628
|
-
process.stdout.write("Use a verified WhatsApp Business sender for production traffic; Meta test numbers are sandbox-only.\n");
|
|
15629
|
-
process.stdout.write("\nRequired Meta values:\n");
|
|
15630
|
-
process.stdout.write(" - Phone Number ID (verified production sender for production; sandbox test sender only for temporary tests)\n");
|
|
15631
|
-
process.stdout.write(" - Access Token (temporary/system-user token)\n");
|
|
15632
|
-
process.stdout.write(" - Verify Token (your own secret string; can auto-generate)\n");
|
|
15633
|
-
process.stdout.write(" - App Secret (Meta App -> Settings -> Basic)\n\n");
|
|
15634
|
-
}
|
|
15635
|
-
while (!phoneNumberId) {
|
|
15636
|
-
phoneNumberId = (await promptLine("Meta Phone Number ID")).trim();
|
|
15637
|
-
if (!phoneNumberId) process.stdout.write("Phone Number ID is required.\n");
|
|
15638
|
-
}
|
|
15639
|
-
while (!accessToken) {
|
|
15640
|
-
accessToken = (await promptSecret("Meta Access Token")).trim();
|
|
15641
|
-
if (!accessToken) process.stdout.write("Access Token is required.\n");
|
|
15642
|
-
}
|
|
15643
|
-
if (!verifyToken && opts.generateVerifyToken) {
|
|
15644
|
-
verifyToken = generateVerifyToken();
|
|
15645
|
-
verifyTokenGenerated = true;
|
|
15646
|
-
}
|
|
15647
|
-
if (!verifyToken && process.stdin.isTTY && process.stdout.isTTY && !opts.json) {
|
|
15648
|
-
const answer = await promptLine("Generate webhook verify token automatically? [Y/n]", { allowEmpty: true });
|
|
15649
|
-
if (isAffirmative(answer, true)) {
|
|
15650
|
-
verifyToken = generateVerifyToken();
|
|
15651
|
-
verifyTokenGenerated = true;
|
|
15652
|
-
process.stdout.write(`Generated Verify Token: ${verifyToken}
|
|
15653
|
-
`);
|
|
15654
|
-
process.stdout.write("Copy this token into Meta webhook settings exactly.\n");
|
|
15655
|
-
}
|
|
15656
|
-
}
|
|
15657
|
-
while (!verifyToken) {
|
|
15658
|
-
verifyToken = (await promptSecret("Webhook Verify Token")).trim();
|
|
15659
|
-
if (!verifyToken) process.stdout.write("Verify Token is required.\n");
|
|
15660
|
-
}
|
|
15661
|
-
while (!appSecret) {
|
|
15662
|
-
appSecret = (await promptSecret("Meta App Secret")).trim();
|
|
15663
|
-
if (!appSecret) process.stdout.write("App Secret is required.\n");
|
|
15664
|
-
}
|
|
15665
|
-
if (opts.audioEnabled === void 0 && process.stdin.isTTY && process.stdout.isTTY && !opts.json) {
|
|
15666
|
-
const answer = await promptLine("Enable inbound WhatsApp audio fallback auto-reply? [Y/n]", { allowEmpty: true });
|
|
15667
|
-
audioEnabled = isAffirmative(answer, true);
|
|
15668
|
-
}
|
|
15669
|
-
return {
|
|
15670
|
-
phoneNumberId,
|
|
15671
|
-
accessToken,
|
|
15672
|
-
verifyToken,
|
|
15673
|
-
verifyTokenGenerated,
|
|
15674
|
-
appSecret,
|
|
15675
|
-
audioEnabled
|
|
15676
|
-
};
|
|
15677
|
-
}
|
|
15678
15608
|
async function runWebhookChallengeCheck({
|
|
15679
15609
|
apiBaseUrl,
|
|
15680
15610
|
verifyToken
|
|
@@ -15728,277 +15658,409 @@ function assertProofPass(strict, reasons) {
|
|
|
15728
15658
|
}
|
|
15729
15659
|
|
|
15730
15660
|
// src/commands/channel-whatsapp.ts
|
|
15731
|
-
function
|
|
15732
|
-
|
|
15733
|
-
|
|
15734
|
-
|
|
15735
|
-
|
|
15736
|
-
|
|
15737
|
-
|
|
15738
|
-
|
|
15661
|
+
async function runWhatsAppOnboardingSession(params) {
|
|
15662
|
+
return await apiFetch("/v1/console/channels/whatsapp/onboarding-session", {
|
|
15663
|
+
method: "POST",
|
|
15664
|
+
body: JSON.stringify({
|
|
15665
|
+
accessToken: params.accessToken,
|
|
15666
|
+
wabaId: params.wabaId,
|
|
15667
|
+
phoneNumberId: params.phoneNumberId,
|
|
15668
|
+
verifyToken: params.verifyToken,
|
|
15669
|
+
appSecret: params.appSecret,
|
|
15670
|
+
agentId: params.agentId,
|
|
15671
|
+
businessSlug: params.businessSlug,
|
|
15672
|
+
audioEnabled: params.audioEnabled,
|
|
15673
|
+
dryRun: params.dryRun
|
|
15674
|
+
}),
|
|
15675
|
+
orgId: params.orgId,
|
|
15676
|
+
apiUrlOverride: params.apiUrl
|
|
15677
|
+
});
|
|
15678
|
+
}
|
|
15679
|
+
function emitLegacyCommandNotice({
|
|
15680
|
+
command,
|
|
15681
|
+
canonical,
|
|
15682
|
+
jsonMode
|
|
15683
|
+
}) {
|
|
15684
|
+
if (!jsonMode) {
|
|
15685
|
+
process.stderr.write(
|
|
15686
|
+
`[deprecated] foh channel whatsapp ${command} is a compatibility wrapper.
|
|
15687
|
+
Use: ${canonical}
|
|
15688
|
+
`
|
|
15689
|
+
);
|
|
15690
|
+
}
|
|
15691
|
+
return {
|
|
15692
|
+
command,
|
|
15693
|
+
canonical,
|
|
15694
|
+
status: "deprecated_compat_wrapper"
|
|
15695
|
+
};
|
|
15696
|
+
}
|
|
15697
|
+
function parseBatchManifest(manifestPathRaw) {
|
|
15698
|
+
const manifestPath = String(manifestPathRaw || "").trim();
|
|
15699
|
+
if (!manifestPath) {
|
|
15700
|
+
throw new FohError({
|
|
15701
|
+
step: "channel.whatsapp.batch",
|
|
15702
|
+
error: "--manifest is required",
|
|
15703
|
+
remediation: "Provide a JSON manifest path with a businesses array."
|
|
15739
15704
|
});
|
|
15740
|
-
|
|
15741
|
-
|
|
15742
|
-
const
|
|
15743
|
-
const
|
|
15744
|
-
|
|
15745
|
-
|
|
15746
|
-
const generatedVerifyToken = Boolean(opts.generateVerifyToken && !opts.verifyToken);
|
|
15747
|
-
let onboarding = null;
|
|
15748
|
-
try {
|
|
15749
|
-
onboarding = await apiFetch(`/v1/console/org/${resolvedOrg}/onboarding`, {
|
|
15750
|
-
orgId: resolvedOrg,
|
|
15751
|
-
apiUrlOverride: opts.apiUrl
|
|
15752
|
-
});
|
|
15753
|
-
} catch (error2) {
|
|
15754
|
-
if (!(error2 instanceof FohError)) throw error2;
|
|
15705
|
+
}
|
|
15706
|
+
try {
|
|
15707
|
+
const raw = (0, import_node_fs2.readFileSync)(manifestPath, "utf8");
|
|
15708
|
+
const parsed = JSON.parse(raw);
|
|
15709
|
+
if (!Array.isArray(parsed.businesses) || parsed.businesses.length === 0) {
|
|
15710
|
+
throw new Error("manifest_missing_businesses");
|
|
15755
15711
|
}
|
|
15756
|
-
|
|
15757
|
-
|
|
15758
|
-
|
|
15759
|
-
|
|
15760
|
-
|
|
15761
|
-
|
|
15762
|
-
}
|
|
15763
|
-
}
|
|
15764
|
-
|
|
15765
|
-
|
|
15766
|
-
|
|
15767
|
-
|
|
15768
|
-
|
|
15769
|
-
|
|
15712
|
+
return parsed;
|
|
15713
|
+
} catch (error2) {
|
|
15714
|
+
throw new FohError({
|
|
15715
|
+
step: "channel.whatsapp.batch",
|
|
15716
|
+
error: "Failed to read --manifest JSON or businesses array is empty.",
|
|
15717
|
+
remediation: 'Provide a JSON file: { "businesses": [{ "businessSlug": "...", "accessToken": "..." }] }',
|
|
15718
|
+
detail: { cause: error2 instanceof Error ? error2.message : String(error2) }
|
|
15719
|
+
});
|
|
15720
|
+
}
|
|
15721
|
+
}
|
|
15722
|
+
function candidateLabel(candidate, index) {
|
|
15723
|
+
const phoneNumberId = String(candidate.phone_number_id || "").trim() || "<missing-phone-id>";
|
|
15724
|
+
const display = String(candidate.display_phone_number || "").trim();
|
|
15725
|
+
const verified = String(candidate.verified_name || "").trim();
|
|
15726
|
+
const wabaId = String(candidate.waba_id || "").trim();
|
|
15727
|
+
const displayPart = display ? ` ${display}` : "";
|
|
15728
|
+
const verifiedPart = verified ? ` (${verified})` : "";
|
|
15729
|
+
const wabaPart = wabaId ? ` [waba:${wabaId}]` : "";
|
|
15730
|
+
return `${index + 1}. ${phoneNumberId}${displayPart}${verifiedPart}${wabaPart}`;
|
|
15731
|
+
}
|
|
15732
|
+
async function runWhatsAppOnboardingWizard(opts) {
|
|
15733
|
+
const wizardState = await runWizardSession({
|
|
15734
|
+
step: "channel.whatsapp.onboard",
|
|
15735
|
+
remediation: "Pass --access-token (and optionally --waba-id / --phone-number-id) when running non-interactively.",
|
|
15736
|
+
title: "WhatsApp Onboarding Wizard",
|
|
15737
|
+
subtitle: "Paste your Meta access token first. The wizard will auto-recover from discovery blockers.",
|
|
15738
|
+
enabled: true,
|
|
15739
|
+
initialState: {
|
|
15740
|
+
accessToken: String(opts.accessToken || "").trim(),
|
|
15741
|
+
appSecret: String(opts.appSecret || "").trim(),
|
|
15742
|
+
provideAppSecret: false
|
|
15743
|
+
},
|
|
15744
|
+
prompts: [
|
|
15745
|
+
{
|
|
15746
|
+
key: "accessToken",
|
|
15747
|
+
label: "Meta Access Token",
|
|
15748
|
+
kind: "secret",
|
|
15749
|
+
required: true,
|
|
15750
|
+
shouldPrompt: (state) => !String(state.accessToken ?? "").trim()
|
|
15751
|
+
},
|
|
15752
|
+
{
|
|
15753
|
+
key: "provideAppSecret",
|
|
15754
|
+
label: "Provide Meta App Secret now for signature-ready closure? [Y/n]",
|
|
15755
|
+
kind: "confirm",
|
|
15756
|
+
confirmDefault: true,
|
|
15757
|
+
shouldPrompt: (state) => !String(state.appSecret ?? "").trim()
|
|
15758
|
+
},
|
|
15759
|
+
{
|
|
15760
|
+
key: "appSecret",
|
|
15761
|
+
label: "Meta App Secret",
|
|
15762
|
+
kind: "secret",
|
|
15763
|
+
required: true,
|
|
15764
|
+
shouldPrompt: (state) => !String(state.appSecret ?? "").trim() && state.provideAppSecret === true
|
|
15770
15765
|
}
|
|
15771
|
-
|
|
15772
|
-
|
|
15773
|
-
|
|
15774
|
-
|
|
15775
|
-
|
|
15776
|
-
|
|
15777
|
-
|
|
15778
|
-
|
|
15779
|
-
|
|
15780
|
-
|
|
15781
|
-
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
|
|
15786
|
-
|
|
15787
|
-
|
|
15788
|
-
|
|
15789
|
-
|
|
15790
|
-
|
|
15791
|
-
|
|
15792
|
-
|
|
15793
|
-
}).slice(0, 3).map((entry) => entry.number).filter((value) => typeof value === "string" && value.trim().length > 0);
|
|
15794
|
-
const telephonyReady = readStepStatus(onboarding?.steps, "telephony");
|
|
15795
|
-
const whatsappReady = Boolean(whatsappStatus?.channel?.ready);
|
|
15796
|
-
const areaSegment = areaCode ? ` --area-code ${areaCode}` : "";
|
|
15797
|
-
const provisionBuyCommand = `foh provision buy --country ${country}${areaSegment} --org ${resolvedOrg}`;
|
|
15798
|
-
const listNumbersCommand = `foh provision numbers --country ${country}${areaSegment} --org ${resolvedOrg}`;
|
|
15799
|
-
const verificationReadinessCommand = `foh provision whatsapp-verification --lookback-minutes 60 --org ${resolvedOrg}`;
|
|
15800
|
-
const verificationReadinessVoiceCommand = `foh provision whatsapp-verification --lookback-minutes 60 --otp-method voice --org ${resolvedOrg}`;
|
|
15801
|
-
const verificationReadinessSmsCommand = `foh provision whatsapp-verification --lookback-minutes 60 --otp-method sms --org ${resolvedOrg}`;
|
|
15802
|
-
const agentIdSegment = opts.agentId ? ` --agent-id ${opts.agentId}` : "";
|
|
15803
|
-
const connectCommand = `foh channel whatsapp connect${agentIdSegment} --phone-number-id <meta_phone_number_id> --access-token <meta_access_token> --verify-token ${verifyToken} --app-secret <meta_app_secret> --org ${resolvedOrg}`;
|
|
15804
|
-
const setupCommand = `foh channel whatsapp setup --wizard${agentIdSegment} --verify-token ${verifyToken} --org ${resolvedOrg}`;
|
|
15805
|
-
const proofCommand = `foh channel whatsapp proof --strict --verify-token ${verifyToken} --org ${resolvedOrg}`;
|
|
15806
|
-
const liveProofCommand = "corepack pnpm ops:whatsapp:proof:live";
|
|
15807
|
-
let verificationReadiness = null;
|
|
15808
|
-
let verificationReadinessCode = null;
|
|
15766
|
+
]
|
|
15767
|
+
});
|
|
15768
|
+
let accessToken = String(wizardState.accessToken || "").trim();
|
|
15769
|
+
let wabaId = String(opts.wabaId || "").trim();
|
|
15770
|
+
let phoneNumberId = String(opts.phoneNumberId || "").trim();
|
|
15771
|
+
let verifyToken = String(opts.verifyToken || "").trim();
|
|
15772
|
+
let appSecret = String(wizardState.appSecret || "").trim();
|
|
15773
|
+
if (!accessToken) {
|
|
15774
|
+
throw new FohError({
|
|
15775
|
+
step: "channel.whatsapp.onboard",
|
|
15776
|
+
error: "Meta access token is required.",
|
|
15777
|
+
remediation: "Run onboarding again and provide a valid token."
|
|
15778
|
+
});
|
|
15779
|
+
}
|
|
15780
|
+
const audioEnabled = parseBooleanOption({
|
|
15781
|
+
value: opts.audioEnabled,
|
|
15782
|
+
fallback: true,
|
|
15783
|
+
optionName: "--audio-enabled",
|
|
15784
|
+
step: "channel.whatsapp.onboard"
|
|
15785
|
+
});
|
|
15786
|
+
const dryRun = Boolean(opts.dryRun);
|
|
15787
|
+
for (let attempt = 1; attempt <= 4; attempt += 1) {
|
|
15809
15788
|
try {
|
|
15810
|
-
|
|
15811
|
-
orgId:
|
|
15812
|
-
|
|
15789
|
+
return await runWhatsAppOnboardingSession({
|
|
15790
|
+
orgId: opts.org,
|
|
15791
|
+
apiUrl: opts.apiUrl,
|
|
15792
|
+
accessToken,
|
|
15793
|
+
wabaId: wabaId || void 0,
|
|
15794
|
+
phoneNumberId: phoneNumberId || void 0,
|
|
15795
|
+
verifyToken: verifyToken || void 0,
|
|
15796
|
+
appSecret: appSecret || void 0,
|
|
15797
|
+
agentId: opts.agentId,
|
|
15798
|
+
businessSlug: opts.businessSlug,
|
|
15799
|
+
audioEnabled,
|
|
15800
|
+
dryRun
|
|
15813
15801
|
});
|
|
15814
|
-
verificationReadinessCode = typeof verificationReadiness.code === "string" ? verificationReadiness.code : null;
|
|
15815
15802
|
} catch (error2) {
|
|
15816
15803
|
if (!(error2 instanceof FohError)) throw error2;
|
|
15817
|
-
|
|
15818
|
-
|
|
15819
|
-
|
|
15820
|
-
|
|
15821
|
-
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
15833
|
-
|
|
15834
|
-
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
|
|
15838
|
-
|
|
15839
|
-
|
|
15840
|
-
|
|
15841
|
-
|
|
15842
|
-
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15804
|
+
const detail = error2.detail || {};
|
|
15805
|
+
const code = String(detail.code || "").trim();
|
|
15806
|
+
const discovery = detail.discovery || {};
|
|
15807
|
+
const candidates = Array.isArray(discovery.candidates) ? discovery.candidates : [];
|
|
15808
|
+
if (code === "whatsapp_multi_candidate_ambiguous" && candidates.length > 0) {
|
|
15809
|
+
process.stdout.write("\nMultiple senders found. Choose one:\n");
|
|
15810
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
15811
|
+
process.stdout.write(` ${candidateLabel(candidate, index)}
|
|
15812
|
+
`);
|
|
15813
|
+
}
|
|
15814
|
+
const pick2 = (await promptLine("Select number (1..n) or paste phone_number_id")).trim();
|
|
15815
|
+
const pickIndex = Number.parseInt(pick2, 10);
|
|
15816
|
+
if (Number.isFinite(pickIndex) && pickIndex >= 1 && pickIndex <= candidates.length) {
|
|
15817
|
+
phoneNumberId = String(candidates[pickIndex - 1]?.phone_number_id || "").trim();
|
|
15818
|
+
} else {
|
|
15819
|
+
phoneNumberId = pick2;
|
|
15820
|
+
}
|
|
15821
|
+
if (phoneNumberId) continue;
|
|
15822
|
+
}
|
|
15823
|
+
if (!wabaId) {
|
|
15824
|
+
const reasonCode = String(discovery.reason_code || code).trim();
|
|
15825
|
+
if (reasonCode === "whatsapp_discovery_failed" || reasonCode === "whatsapp_phone_discovery_blocked" || reasonCode === "whatsapp_no_phone_numbers_found") {
|
|
15826
|
+
const promptedWaba = (await promptLine("Paste WABA ID (optional, press Enter to skip)", { allowEmpty: true })).trim();
|
|
15827
|
+
if (promptedWaba) {
|
|
15828
|
+
wabaId = promptedWaba;
|
|
15829
|
+
continue;
|
|
15830
|
+
}
|
|
15831
|
+
}
|
|
15832
|
+
}
|
|
15833
|
+
if (!phoneNumberId) {
|
|
15834
|
+
const promptedPhone = (await promptLine("Paste phone_number_id directly (optional, press Enter to stop)", { allowEmpty: true })).trim();
|
|
15835
|
+
if (promptedPhone) {
|
|
15836
|
+
phoneNumberId = promptedPhone;
|
|
15837
|
+
continue;
|
|
15838
|
+
}
|
|
15849
15839
|
}
|
|
15840
|
+
throw error2;
|
|
15841
|
+
}
|
|
15842
|
+
}
|
|
15843
|
+
throw new FohError({
|
|
15844
|
+
step: "channel.whatsapp.onboard",
|
|
15845
|
+
error: "Unable to complete WhatsApp onboarding after guided retries.",
|
|
15846
|
+
remediation: "Run again with --phone-number-id and --waba-id, or use `foh channel whatsapp start` for deterministic next steps."
|
|
15847
|
+
});
|
|
15848
|
+
}
|
|
15849
|
+
function registerWhatsAppChannelCommands(whatsapp, addCommonOptions) {
|
|
15850
|
+
addCommonOptions(
|
|
15851
|
+
whatsapp.command("start").description("[Deprecated wrapper] Start onboarding using the canonical session flow")
|
|
15852
|
+
).option("--access-token <token>", "Meta access token with WhatsApp Business access").option("--waba-id <id>", "Optional explicit WhatsApp Business Account id when /me discovery is restricted").option("--phone-number-id <id>", "Optional explicit phone number id when discovery returns multiple candidates").option("--agent-id <id>", "Agent to bind to the WhatsApp channel").option("--verify-token <token>", "Optional webhook verify token (auto-generated when omitted)").option("--app-secret <secret>", "Optional Meta app secret (required for full signature-ready closure)").option("--business-slug <slug>", "Business identifier for operator-level targeting").option("--audio-enabled <bool>", "Enable inbound WhatsApp audio fallback auto-reply (true/false)", "true").option("--wizard", "Run guided onboarding with automatic recovery prompts").action(async (opts) => withCommandErrorHandling(async () => {
|
|
15853
|
+
const accessToken = String(opts.accessToken || "").trim();
|
|
15854
|
+
const useWizard = Boolean(opts.wizard) || !accessToken;
|
|
15855
|
+
const legacy = emitLegacyCommandNotice({
|
|
15856
|
+
command: "start",
|
|
15857
|
+
canonical: "foh channel whatsapp onboard --wizard",
|
|
15858
|
+
jsonMode: Boolean(opts.json)
|
|
15859
|
+
});
|
|
15860
|
+
const data = useWizard ? await runWhatsAppOnboardingWizard({
|
|
15861
|
+
...opts,
|
|
15862
|
+
dryRun: true
|
|
15863
|
+
}) : await runWhatsAppOnboardingSession({
|
|
15864
|
+
orgId: opts.org,
|
|
15865
|
+
apiUrl: opts.apiUrl,
|
|
15866
|
+
accessToken,
|
|
15867
|
+
wabaId: String(opts.wabaId || "").trim() || void 0,
|
|
15868
|
+
phoneNumberId: String(opts.phoneNumberId || "").trim() || void 0,
|
|
15869
|
+
verifyToken: String(opts.verifyToken || "").trim() || void 0,
|
|
15870
|
+
appSecret: String(opts.appSecret || "").trim() || void 0,
|
|
15871
|
+
agentId: opts.agentId,
|
|
15872
|
+
businessSlug: opts.businessSlug,
|
|
15873
|
+
audioEnabled: parseBooleanOption({
|
|
15874
|
+
value: opts.audioEnabled,
|
|
15875
|
+
fallback: true,
|
|
15876
|
+
optionName: "--audio-enabled",
|
|
15877
|
+
step: "channel.whatsapp.start"
|
|
15878
|
+
}),
|
|
15879
|
+
dryRun: true
|
|
15850
15880
|
});
|
|
15851
15881
|
format({
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
|
|
15858
|
-
|
|
15859
|
-
whatsapp_channel_configured: whatsappConfigured,
|
|
15860
|
-
whatsapp_channel_ready: whatsappReady
|
|
15861
|
-
},
|
|
15862
|
-
market_scan: {
|
|
15863
|
-
country,
|
|
15864
|
-
area_code: areaCode || null,
|
|
15865
|
-
candidate_count: availableNumbers.length,
|
|
15866
|
-
sms_voice_candidates: smsVoiceCandidates,
|
|
15867
|
-
voice_only_candidates: voiceOnlyCandidates
|
|
15868
|
-
},
|
|
15869
|
-
sender_model: WHATSAPP_SENDER_MODEL,
|
|
15870
|
-
commands: {
|
|
15871
|
-
list_numbers: listNumbersCommand,
|
|
15872
|
-
provision_buy: provisionBuyCommand,
|
|
15873
|
-
verification_readiness: verificationReadinessCommand,
|
|
15874
|
-
verification_readiness_sms: verificationReadinessSmsCommand,
|
|
15875
|
-
verification_readiness_voice: verificationReadinessVoiceCommand,
|
|
15876
|
-
connect: connectCommand,
|
|
15877
|
-
setup_wizard: setupCommand,
|
|
15878
|
-
proof: proofCommand,
|
|
15879
|
-
live_proof: liveProofCommand
|
|
15880
|
-
},
|
|
15881
|
-
verification_readiness: verificationReadiness || { code: verificationReadinessCode || "verification_readiness_unavailable" },
|
|
15882
|
-
journey,
|
|
15883
|
-
next_steps: nextSteps,
|
|
15884
|
-
notes
|
|
15882
|
+
...data,
|
|
15883
|
+
legacy_wrapper: legacy,
|
|
15884
|
+
next_steps: dedupeSteps([
|
|
15885
|
+
"Run canonical onboarding apply flow: foh channel whatsapp onboard --wizard",
|
|
15886
|
+
"Run deterministic closure: foh channel whatsapp proof --strict",
|
|
15887
|
+
"Capture live provider evidence: corepack pnpm ops:whatsapp:proof:live"
|
|
15888
|
+
])
|
|
15885
15889
|
}, { json: opts.json ?? false });
|
|
15886
15890
|
}));
|
|
15887
15891
|
addCommonOptions(
|
|
15888
|
-
whatsapp.command("
|
|
15889
|
-
).option("--
|
|
15890
|
-
const
|
|
15891
|
-
const
|
|
15892
|
-
const
|
|
15893
|
-
const webhookUrl = buildWebhookUrl(apiBaseUrl);
|
|
15894
|
-
const connect = await apiFetch("/v1/console/channels/whatsapp/connect", {
|
|
15895
|
-
method: "POST",
|
|
15896
|
-
body: JSON.stringify({
|
|
15897
|
-
phoneNumberId: inputs.phoneNumberId,
|
|
15898
|
-
accessToken: inputs.accessToken,
|
|
15899
|
-
agentId: opts.agentId,
|
|
15900
|
-
verifyToken: inputs.verifyToken,
|
|
15901
|
-
appSecret: inputs.appSecret,
|
|
15902
|
-
audioEnabled: inputs.audioEnabled
|
|
15903
|
-
}),
|
|
15904
|
-
orgId: opts.org,
|
|
15905
|
-
apiUrlOverride: opts.apiUrl
|
|
15906
|
-
});
|
|
15907
|
-
const checks = await runWhatsAppReadinessChecks({
|
|
15892
|
+
whatsapp.command("onboard").description("Run one-session WhatsApp onboarding (discover -> bind -> verify -> prove)")
|
|
15893
|
+
).option("--access-token <token>", "Meta access token with WhatsApp Business access").option("--waba-id <id>", "Optional explicit WhatsApp Business Account id when /me discovery is restricted").option("--phone-number-id <id>", "Optional explicit phone number id when discovery returns multiple candidates").option("--verify-token <token>", "Optional webhook verify token (auto-generated when omitted)").option("--app-secret <secret>", "Optional Meta app secret (required for full signature-ready closure)").option("--agent-id <id>", "Agent to bind to the WhatsApp channel").option("--business-slug <slug>", "Business identifier for operator-level targeting").option("--audio-enabled <bool>", "Enable inbound WhatsApp audio fallback auto-reply (true/false)", "true").option("--dry-run", "Run discovery/binding preflight without writing channel config").option("--wizard", "Run guided onboarding with automatic recovery prompts").action(async (opts) => withCommandErrorHandling(async () => {
|
|
15894
|
+
const accessToken = String(opts.accessToken || "").trim();
|
|
15895
|
+
const useWizard = Boolean(opts.wizard) || !accessToken;
|
|
15896
|
+
const data = useWizard ? await runWhatsAppOnboardingWizard(opts) : await runWhatsAppOnboardingSession({
|
|
15908
15897
|
orgId: opts.org,
|
|
15909
|
-
|
|
15910
|
-
|
|
15911
|
-
|
|
15912
|
-
|
|
15913
|
-
|
|
15914
|
-
|
|
15898
|
+
apiUrl: opts.apiUrl,
|
|
15899
|
+
accessToken,
|
|
15900
|
+
wabaId: String(opts.wabaId || "").trim() || void 0,
|
|
15901
|
+
phoneNumberId: String(opts.phoneNumberId || "").trim() || void 0,
|
|
15902
|
+
verifyToken: String(opts.verifyToken || "").trim() || void 0,
|
|
15903
|
+
appSecret: String(opts.appSecret || "").trim() || void 0,
|
|
15904
|
+
agentId: opts.agentId,
|
|
15905
|
+
businessSlug: opts.businessSlug,
|
|
15906
|
+
audioEnabled: parseBooleanOption({
|
|
15907
|
+
value: opts.audioEnabled,
|
|
15908
|
+
fallback: true,
|
|
15909
|
+
optionName: "--audio-enabled",
|
|
15910
|
+
step: "channel.whatsapp.onboard"
|
|
15911
|
+
}),
|
|
15912
|
+
dryRun: Boolean(opts.dryRun)
|
|
15915
15913
|
});
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
15919
|
-
|
|
15920
|
-
|
|
15921
|
-
|
|
15914
|
+
format(data, { json: opts.json ?? false });
|
|
15915
|
+
}));
|
|
15916
|
+
addCommonOptions(
|
|
15917
|
+
whatsapp.command("batch").description("Run manifest-driven WhatsApp onboarding across multiple businesses")
|
|
15918
|
+
).requiredOption("--manifest <path>", "Path to batch manifest JSON").option("--apply", "Apply writes (default is dry-run preflight)").option("--strict", "Exit non-zero when any business fails").action(async (opts) => withCommandErrorHandling(async () => {
|
|
15919
|
+
const manifest = parseBatchManifest(String(opts.manifest || ""));
|
|
15920
|
+
const applyMode = Boolean(opts.apply);
|
|
15921
|
+
const rows = manifest.businesses || [];
|
|
15922
|
+
const results = [];
|
|
15923
|
+
const failureTaxonomy = {};
|
|
15924
|
+
for (const [index, row] of rows.entries()) {
|
|
15925
|
+
const entry = row || {};
|
|
15926
|
+
const accessToken = String(entry.accessToken || "").trim();
|
|
15927
|
+
if (!accessToken) {
|
|
15928
|
+
const code = "whatsapp_access_token_required";
|
|
15929
|
+
failureTaxonomy[code] = (failureTaxonomy[code] || 0) + 1;
|
|
15930
|
+
results.push({
|
|
15931
|
+
index,
|
|
15932
|
+
business_slug: String(entry.businessSlug || "").trim() || null,
|
|
15933
|
+
ok: false,
|
|
15934
|
+
code,
|
|
15935
|
+
reason_codes: [code]
|
|
15936
|
+
});
|
|
15937
|
+
continue;
|
|
15938
|
+
}
|
|
15939
|
+
try {
|
|
15940
|
+
const response = await runWhatsAppOnboardingSession({
|
|
15941
|
+
orgId: opts.org,
|
|
15942
|
+
apiUrl: opts.apiUrl,
|
|
15943
|
+
accessToken,
|
|
15944
|
+
wabaId: String(entry.wabaId || "").trim() || void 0,
|
|
15945
|
+
phoneNumberId: String(entry.phoneNumberId || "").trim() || void 0,
|
|
15946
|
+
verifyToken: String(entry.verifyToken || "").trim() || void 0,
|
|
15947
|
+
appSecret: String(entry.appSecret || "").trim() || void 0,
|
|
15948
|
+
agentId: String(entry.agentId || "").trim() || void 0,
|
|
15949
|
+
businessSlug: String(entry.businessSlug || "").trim() || void 0,
|
|
15950
|
+
audioEnabled: parseBooleanOption({
|
|
15951
|
+
value: entry.audioEnabled,
|
|
15952
|
+
fallback: true,
|
|
15953
|
+
optionName: "batch.audioEnabled",
|
|
15954
|
+
step: "channel.whatsapp.batch"
|
|
15955
|
+
}),
|
|
15956
|
+
dryRun: entry.dryRun === void 0 ? !applyMode : Boolean(entry.dryRun)
|
|
15957
|
+
});
|
|
15958
|
+
results.push({
|
|
15959
|
+
index,
|
|
15960
|
+
business_slug: String(entry.businessSlug || "").trim() || null,
|
|
15961
|
+
ok: true,
|
|
15962
|
+
code: String(response.code || "whatsapp_onboarding_session_ready"),
|
|
15963
|
+
selected_candidate: response.selected_candidate || null
|
|
15964
|
+
});
|
|
15965
|
+
} catch (error2) {
|
|
15966
|
+
if (!(error2 instanceof FohError)) throw error2;
|
|
15967
|
+
const detail = error2.detail || {};
|
|
15968
|
+
const code = String(detail.code || error2.reasonCode || "whatsapp_batch_onboarding_failed").trim();
|
|
15969
|
+
failureTaxonomy[code] = (failureTaxonomy[code] || 0) + 1;
|
|
15970
|
+
results.push({
|
|
15971
|
+
index,
|
|
15972
|
+
business_slug: String(entry.businessSlug || "").trim() || null,
|
|
15973
|
+
ok: false,
|
|
15974
|
+
code,
|
|
15975
|
+
reason_codes: Array.isArray(detail.reason_codes) ? detail.reason_codes : [code]
|
|
15976
|
+
});
|
|
15977
|
+
}
|
|
15922
15978
|
}
|
|
15923
|
-
const
|
|
15924
|
-
|
|
15925
|
-
|
|
15926
|
-
|
|
15927
|
-
if (readinessReasons.length > 0) {
|
|
15928
|
-
const steps = buildReasonedNextSteps({
|
|
15929
|
-
reasons: readinessReasons,
|
|
15930
|
-
webhookUrl,
|
|
15931
|
-
verifyToken: inputs.verifyToken,
|
|
15932
|
-
includeLiveStep: false,
|
|
15933
|
-
liveArtifactPath: "test-results/whatsapp-live-proof.latest.json"
|
|
15934
|
-
});
|
|
15935
|
-
throw new FohError({
|
|
15936
|
-
step: "channel.whatsapp.setup",
|
|
15937
|
-
error: `Readiness checks failed: ${readinessReasons.join(", ")}`,
|
|
15938
|
-
remediation: withNumberedSteps(steps)
|
|
15939
|
-
});
|
|
15979
|
+
const successCount = results.filter((entry) => entry.ok === true).length;
|
|
15980
|
+
const failureCount = results.length - successCount;
|
|
15981
|
+
if (Boolean(opts.strict) && failureCount > 0) {
|
|
15982
|
+
process.exitCode = 1;
|
|
15940
15983
|
}
|
|
15941
|
-
const setupCommands = {
|
|
15942
|
-
verify: `foh channel whatsapp proof --strict --verify-token ${inputs.verifyToken}`,
|
|
15943
|
-
live: "corepack pnpm ops:whatsapp:proof:live"
|
|
15944
|
-
};
|
|
15945
15984
|
format({
|
|
15946
|
-
ok:
|
|
15985
|
+
ok: failureCount === 0,
|
|
15947
15986
|
channel: "whatsapp",
|
|
15948
|
-
|
|
15949
|
-
|
|
15950
|
-
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
|
|
15955
|
-
},
|
|
15956
|
-
channel_state: {
|
|
15957
|
-
channel_id: checks.status.channel?.channel_id ?? null,
|
|
15958
|
-
phone_number_id: checks.status.channel?.phone_number_id ?? null,
|
|
15959
|
-
ready: Boolean(checks.status.channel?.ready),
|
|
15960
|
-
verify_token_configured: Boolean(checks.status.channel?.verify_token_configured),
|
|
15961
|
-
app_secret_configured: Boolean(checks.status.channel?.app_secret_configured),
|
|
15962
|
-
audio_enabled: Boolean(checks.status.channel?.audio_enabled)
|
|
15963
|
-
},
|
|
15964
|
-
sender_model: WHATSAPP_SENDER_MODEL,
|
|
15965
|
-
generated_verify_token: inputs.verifyTokenGenerated ? inputs.verifyToken : null,
|
|
15966
|
-
meta_next_steps: [
|
|
15967
|
-
`Meta callback URL: ${webhookUrl}`,
|
|
15968
|
-
"Meta verify token: use the same value configured in this setup flow.",
|
|
15969
|
-
"Subscribe webhook events for WhatsApp messages.",
|
|
15970
|
-
"Use Meta test sender IDs only for sandbox testing; switch FOH channel config to a verified WhatsApp Business phone_number_id before production traffic.",
|
|
15971
|
-
"Set app to live mode when Meta requirements are complete."
|
|
15972
|
-
],
|
|
15973
|
-
operator_next_steps: [
|
|
15974
|
-
`Run \`${setupCommands.verify}\` for a deterministic closure check.`,
|
|
15975
|
-
"Run `corepack pnpm ops:whatsapp:proof:live` to capture production live-proof artifact."
|
|
15976
|
-
],
|
|
15977
|
-
setup_commands: setupCommands
|
|
15987
|
+
workflow: "batch_onboarding_session",
|
|
15988
|
+
apply_mode: applyMode,
|
|
15989
|
+
total: results.length,
|
|
15990
|
+
success_count: successCount,
|
|
15991
|
+
failure_count: failureCount,
|
|
15992
|
+
failure_taxonomy: failureTaxonomy,
|
|
15993
|
+
results
|
|
15978
15994
|
}, { json: opts.json ?? false });
|
|
15979
15995
|
}));
|
|
15980
15996
|
addCommonOptions(
|
|
15981
|
-
whatsapp.command("
|
|
15982
|
-
).
|
|
15983
|
-
const
|
|
15984
|
-
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
|
|
15993
|
-
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
+
whatsapp.command("setup").description("[Deprecated wrapper] Use canonical onboarding session flow")
|
|
15998
|
+
).option("--phone-number-id <id>", "Meta WhatsApp phone number id").option("--access-token <token>", "Meta access token").option("--verify-token <token>", "Webhook verify token").option("--app-secret <secret>", "Meta app secret for signature verification").option("--agent-id <id>", "Agent to bind to the WhatsApp channel").option("--audio-enabled <bool>", "Enable inbound WhatsApp audio fallback auto-reply (true/false)").option("--generate-verify-token", "Generate webhook verify token automatically when missing").option("--business-slug <slug>", "Business identifier for operator-level targeting").option("--wizard", "Run guided setup wizard prompts").action(async (opts) => withCommandErrorHandling(async () => {
|
|
15999
|
+
const legacy = emitLegacyCommandNotice({
|
|
16000
|
+
command: "setup",
|
|
16001
|
+
canonical: "foh channel whatsapp onboard --wizard",
|
|
16002
|
+
jsonMode: Boolean(opts.json)
|
|
16003
|
+
});
|
|
16004
|
+
const useWizard = Boolean(opts.wizard) || !String(opts.accessToken || "").trim();
|
|
16005
|
+
const generatedVerifyToken = String(opts.verifyToken || "").trim() || (Boolean(opts.generateVerifyToken) ? generateVerifyToken() : "");
|
|
16006
|
+
const data = useWizard ? await runWhatsAppOnboardingWizard({
|
|
16007
|
+
...opts,
|
|
16008
|
+
verifyToken: generatedVerifyToken || void 0,
|
|
16009
|
+
dryRun: false
|
|
16010
|
+
}) : await runWhatsAppOnboardingSession({
|
|
16011
|
+
orgId: opts.org,
|
|
16012
|
+
apiUrl: opts.apiUrl,
|
|
16013
|
+
accessToken: String(opts.accessToken || "").trim(),
|
|
16014
|
+
wabaId: void 0,
|
|
16015
|
+
phoneNumberId: String(opts.phoneNumberId || "").trim() || void 0,
|
|
16016
|
+
verifyToken: generatedVerifyToken || void 0,
|
|
16017
|
+
appSecret: String(opts.appSecret || "").trim() || void 0,
|
|
16018
|
+
agentId: opts.agentId,
|
|
16019
|
+
businessSlug: String(opts.businessSlug || "").trim() || void 0,
|
|
16020
|
+
audioEnabled: parseBooleanOption({
|
|
16021
|
+
value: opts.audioEnabled,
|
|
16022
|
+
fallback: true,
|
|
16023
|
+
optionName: "--audio-enabled",
|
|
16024
|
+
step: "channel.whatsapp.setup"
|
|
15997
16025
|
}),
|
|
16026
|
+
dryRun: false
|
|
16027
|
+
});
|
|
16028
|
+
format({
|
|
16029
|
+
...data,
|
|
16030
|
+
legacy_wrapper: legacy,
|
|
16031
|
+
generated_verify_token: generatedVerifyToken && !opts.verifyToken ? generatedVerifyToken : null
|
|
16032
|
+
}, { json: opts.json ?? false });
|
|
16033
|
+
}));
|
|
16034
|
+
addCommonOptions(
|
|
16035
|
+
whatsapp.command("connect").description("[Deprecated wrapper] Use canonical onboarding session flow")
|
|
16036
|
+
).requiredOption("--phone-number-id <id>", "Meta WhatsApp phone number id").requiredOption("--access-token <token>", "Meta access token").option("--agent-id <id>", "Agent to bind to the WhatsApp channel").option("--verify-token <token>", "Webhook verify token").option("--app-secret <secret>", "Meta app secret for signature verification").option("--business-slug <slug>", "Business identifier for operator-level targeting").option("--audio-enabled <bool>", "Enable inbound WhatsApp audio fallback auto-reply (true/false)", "true").action(async (opts) => withCommandErrorHandling(async () => {
|
|
16037
|
+
const legacy = emitLegacyCommandNotice({
|
|
16038
|
+
command: "connect",
|
|
16039
|
+
canonical: "foh channel whatsapp onboard --access-token <token> --phone-number-id <id>",
|
|
16040
|
+
jsonMode: Boolean(opts.json)
|
|
16041
|
+
});
|
|
16042
|
+
const data = await runWhatsAppOnboardingSession({
|
|
15998
16043
|
orgId: opts.org,
|
|
15999
|
-
|
|
16044
|
+
apiUrl: opts.apiUrl,
|
|
16045
|
+
accessToken: String(opts.accessToken || "").trim(),
|
|
16046
|
+
wabaId: void 0,
|
|
16047
|
+
phoneNumberId: String(opts.phoneNumberId || "").trim() || void 0,
|
|
16048
|
+
verifyToken: String(opts.verifyToken || "").trim() || void 0,
|
|
16049
|
+
appSecret: String(opts.appSecret || "").trim() || void 0,
|
|
16050
|
+
agentId: opts.agentId,
|
|
16051
|
+
businessSlug: String(opts.businessSlug || "").trim() || void 0,
|
|
16052
|
+
audioEnabled: parseBooleanOption({
|
|
16053
|
+
value: opts.audioEnabled,
|
|
16054
|
+
fallback: true,
|
|
16055
|
+
optionName: "--audio-enabled",
|
|
16056
|
+
step: "channel.whatsapp.connect"
|
|
16057
|
+
}),
|
|
16058
|
+
dryRun: false
|
|
16000
16059
|
});
|
|
16001
|
-
format(
|
|
16060
|
+
format({
|
|
16061
|
+
...data,
|
|
16062
|
+
legacy_wrapper: legacy
|
|
16063
|
+
}, { json: opts.json ?? false });
|
|
16002
16064
|
}));
|
|
16003
16065
|
addCommonOptions(
|
|
16004
16066
|
whatsapp.command("status").description("Get WhatsApp channel status")
|
|
@@ -16057,10 +16119,11 @@ function registerWhatsAppChannelCommands(whatsapp, addCommonOptions) {
|
|
|
16057
16119
|
],
|
|
16058
16120
|
sender_model: WHATSAPP_SENDER_MODEL,
|
|
16059
16121
|
commands: {
|
|
16060
|
-
onboarding_start: `foh channel whatsapp
|
|
16122
|
+
onboarding_start: `foh channel whatsapp onboard --wizard${agentIdSegment}${orgSegment}`,
|
|
16061
16123
|
verification_readiness: `foh provision whatsapp-verification --lookback-minutes 60${orgSegment}`,
|
|
16062
16124
|
verification_readiness_voice: `foh provision whatsapp-verification --lookback-minutes 60 --otp-method voice${orgSegment}`,
|
|
16063
|
-
|
|
16125
|
+
onboard: `foh channel whatsapp onboard${agentIdSegment} --access-token <meta_access_token> --verify-token ${verifyToken} --app-secret <meta_app_secret>${orgSegment}`,
|
|
16126
|
+
batch: `foh channel whatsapp batch --manifest ./whatsapp-batch.manifest.json --apply${orgSegment}`,
|
|
16064
16127
|
verify: `foh channel whatsapp proof --strict --verify-token ${verifyToken}${orgSegment}`,
|
|
16065
16128
|
live_proof: "corepack pnpm ops:whatsapp:proof:live",
|
|
16066
16129
|
release_gate: "corepack pnpm ops:channel:proof:gate:strict"
|
|
@@ -16375,11 +16438,11 @@ function registerVoice(program3) {
|
|
|
16375
16438
|
}
|
|
16376
16439
|
const outputPath = String(opts.out || `foh-voice-preview-${provider}-${voiceId}.mp3`).trim();
|
|
16377
16440
|
const audio = Buffer.from(await res.arrayBuffer());
|
|
16378
|
-
const { mkdirSync:
|
|
16379
|
-
const { dirname:
|
|
16380
|
-
const absolutePath =
|
|
16381
|
-
|
|
16382
|
-
|
|
16441
|
+
const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync15 } = await import("fs");
|
|
16442
|
+
const { dirname: dirname13, resolve: resolve15 } = await import("path");
|
|
16443
|
+
const absolutePath = resolve15(outputPath);
|
|
16444
|
+
mkdirSync10(dirname13(absolutePath), { recursive: true });
|
|
16445
|
+
writeFileSync15(absolutePath, audio);
|
|
16383
16446
|
format({
|
|
16384
16447
|
status: "ok",
|
|
16385
16448
|
provider,
|
|
@@ -30870,7 +30933,7 @@ var Protocol = class {
|
|
|
30870
30933
|
return;
|
|
30871
30934
|
}
|
|
30872
30935
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
30873
|
-
await new Promise((
|
|
30936
|
+
await new Promise((resolve15) => setTimeout(resolve15, pollInterval));
|
|
30874
30937
|
options?.signal?.throwIfAborted();
|
|
30875
30938
|
}
|
|
30876
30939
|
} catch (error2) {
|
|
@@ -30887,7 +30950,7 @@ var Protocol = class {
|
|
|
30887
30950
|
*/
|
|
30888
30951
|
request(request, resultSchema, options) {
|
|
30889
30952
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
30890
|
-
return new Promise((
|
|
30953
|
+
return new Promise((resolve15, reject) => {
|
|
30891
30954
|
const earlyReject = (error2) => {
|
|
30892
30955
|
reject(error2);
|
|
30893
30956
|
};
|
|
@@ -30965,7 +31028,7 @@ var Protocol = class {
|
|
|
30965
31028
|
if (!parseResult.success) {
|
|
30966
31029
|
reject(parseResult.error);
|
|
30967
31030
|
} else {
|
|
30968
|
-
|
|
31031
|
+
resolve15(parseResult.data);
|
|
30969
31032
|
}
|
|
30970
31033
|
} catch (error2) {
|
|
30971
31034
|
reject(error2);
|
|
@@ -31226,12 +31289,12 @@ var Protocol = class {
|
|
|
31226
31289
|
}
|
|
31227
31290
|
} catch {
|
|
31228
31291
|
}
|
|
31229
|
-
return new Promise((
|
|
31292
|
+
return new Promise((resolve15, reject) => {
|
|
31230
31293
|
if (signal.aborted) {
|
|
31231
31294
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
31232
31295
|
return;
|
|
31233
31296
|
}
|
|
31234
|
-
const timeoutId = setTimeout(
|
|
31297
|
+
const timeoutId = setTimeout(resolve15, interval);
|
|
31235
31298
|
signal.addEventListener("abort", () => {
|
|
31236
31299
|
clearTimeout(timeoutId);
|
|
31237
31300
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -32331,7 +32394,7 @@ var McpServer = class {
|
|
|
32331
32394
|
let task = createTaskResult.task;
|
|
32332
32395
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
32333
32396
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
32334
|
-
await new Promise((
|
|
32397
|
+
await new Promise((resolve15) => setTimeout(resolve15, pollInterval));
|
|
32335
32398
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
32336
32399
|
if (!updatedTask) {
|
|
32337
32400
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -32980,19 +33043,19 @@ var StdioServerTransport = class {
|
|
|
32980
33043
|
this.onclose?.();
|
|
32981
33044
|
}
|
|
32982
33045
|
send(message) {
|
|
32983
|
-
return new Promise((
|
|
33046
|
+
return new Promise((resolve15) => {
|
|
32984
33047
|
const json3 = serializeMessage(message);
|
|
32985
33048
|
if (this._stdout.write(json3)) {
|
|
32986
|
-
|
|
33049
|
+
resolve15();
|
|
32987
33050
|
} else {
|
|
32988
|
-
this._stdout.once("drain",
|
|
33051
|
+
this._stdout.once("drain", resolve15);
|
|
32989
33052
|
}
|
|
32990
33053
|
});
|
|
32991
33054
|
}
|
|
32992
33055
|
};
|
|
32993
33056
|
|
|
32994
33057
|
// src/lib/cli-version.ts
|
|
32995
|
-
var CLI_VERSION = "0.1.
|
|
33058
|
+
var CLI_VERSION = "0.1.83";
|
|
32996
33059
|
|
|
32997
33060
|
// src/commands/mcp-serve.ts
|
|
32998
33061
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -33177,7 +33240,7 @@ async function runFohCli(params) {
|
|
|
33177
33240
|
effectiveArgv.push("--json");
|
|
33178
33241
|
}
|
|
33179
33242
|
const command = `foh ${effectiveArgv.join(" ")}`;
|
|
33180
|
-
return await new Promise((
|
|
33243
|
+
return await new Promise((resolve15) => {
|
|
33181
33244
|
const child = (0, import_node_child_process.spawn)(process.execPath, [cliEntry, ...effectiveArgv], {
|
|
33182
33245
|
stdio: ["ignore", "pipe", "pipe"],
|
|
33183
33246
|
env: {
|
|
@@ -33202,7 +33265,7 @@ async function runFohCli(params) {
|
|
|
33202
33265
|
});
|
|
33203
33266
|
child.once("error", (error2) => {
|
|
33204
33267
|
clearTimeout(timeoutHandle);
|
|
33205
|
-
|
|
33268
|
+
resolve15({
|
|
33206
33269
|
ok: false,
|
|
33207
33270
|
command,
|
|
33208
33271
|
argv: effectiveArgv,
|
|
@@ -33218,7 +33281,7 @@ async function runFohCli(params) {
|
|
|
33218
33281
|
const stderrText = finalizeBoundedText(stderrBuffer);
|
|
33219
33282
|
const exitCode = Number.isFinite(code ?? NaN) ? Number(code) : 1;
|
|
33220
33283
|
const stdoutJson = tryParseJson(stdoutText);
|
|
33221
|
-
|
|
33284
|
+
resolve15({
|
|
33222
33285
|
ok: !timedOut && exitCode === 0,
|
|
33223
33286
|
command,
|
|
33224
33287
|
argv: effectiveArgv,
|
|
@@ -35388,8 +35451,8 @@ function registerSetup(program3) {
|
|
|
35388
35451
|
}
|
|
35389
35452
|
try {
|
|
35390
35453
|
const manifest = await agentExport(resolvedAgentId, { apiUrlOverride: opts.apiUrl });
|
|
35391
|
-
const { writeFileSync:
|
|
35392
|
-
|
|
35454
|
+
const { writeFileSync: writeFileSync15 } = await import("fs");
|
|
35455
|
+
writeFileSync15(
|
|
35393
35456
|
"tenant.yaml",
|
|
35394
35457
|
`# tenant.yaml - Front Of House agent manifest
|
|
35395
35458
|
# Edit this file and run: foh plan tenant.yaml
|
|
@@ -35559,8 +35622,8 @@ function registerSim(program3) {
|
|
|
35559
35622
|
}
|
|
35560
35623
|
const cert = response.certificate;
|
|
35561
35624
|
if (opts.out) {
|
|
35562
|
-
const { writeFileSync:
|
|
35563
|
-
|
|
35625
|
+
const { writeFileSync: writeFileSync15 } = await import("fs");
|
|
35626
|
+
writeFileSync15(opts.out, JSON.stringify(cert, null, 2) + "\n", "utf-8");
|
|
35564
35627
|
process.stderr.write(` Certificate written to ${opts.out}
|
|
35565
35628
|
`);
|
|
35566
35629
|
}
|
|
@@ -35610,8 +35673,8 @@ function registerSim(program3) {
|
|
|
35610
35673
|
});
|
|
35611
35674
|
}
|
|
35612
35675
|
if (opts.out) {
|
|
35613
|
-
const { writeFileSync:
|
|
35614
|
-
|
|
35676
|
+
const { writeFileSync: writeFileSync15 } = await import("fs");
|
|
35677
|
+
writeFileSync15(opts.out, JSON.stringify(response.certificate, null, 2) + "\n", "utf-8");
|
|
35615
35678
|
process.stderr.write(` Final certificate written to ${opts.out}
|
|
35616
35679
|
`);
|
|
35617
35680
|
}
|
|
@@ -35647,7 +35710,7 @@ ${passIcon} Certification loop summary
|
|
|
35647
35710
|
}
|
|
35648
35711
|
|
|
35649
35712
|
// src/commands/certify.ts
|
|
35650
|
-
var
|
|
35713
|
+
var import_node_fs3 = require("node:fs");
|
|
35651
35714
|
function normalizeProfile(raw) {
|
|
35652
35715
|
const value = String(raw || "release").trim().toLowerCase();
|
|
35653
35716
|
if (value === "smoke" || value === "release" || value === "stress") return value;
|
|
@@ -35725,7 +35788,7 @@ function registerCertify(program3) {
|
|
|
35725
35788
|
next_commands: passed ? [`foh agent publish --agent ${opts.agent} --json`] : [`foh certify run --agent ${opts.agent} --profile ${profile} --json`]
|
|
35726
35789
|
};
|
|
35727
35790
|
if (opts.out) {
|
|
35728
|
-
(0,
|
|
35791
|
+
(0, import_node_fs3.writeFileSync)(opts.out, JSON.stringify(result, null, 2) + "\n", "utf-8");
|
|
35729
35792
|
}
|
|
35730
35793
|
if (opts.json ?? false) {
|
|
35731
35794
|
format(result, { json: true });
|
|
@@ -36675,6 +36738,22 @@ function registerOpsAgencySetupCommands(reporting) {
|
|
|
36675
36738
|
});
|
|
36676
36739
|
format(data, { json: opts.json ?? false });
|
|
36677
36740
|
}));
|
|
36741
|
+
reporting.command("agency-setup-workflow").description("Run the one-shot any-agency setup workflow: dossier, launch packet, evidence packet, proof commands, and no-overclaim status").requiredOption("--agency-name <name>", "Estate agency trading name").option("--source-url <url>", "Official branch/contact/source URL").option("--branch-location <value>", "Branch/location this setup represents").option("--tools <csv>", "Requested tool surfaces", "widget,voice,email,callback,calendar,crm,webhook,whatsapp").option("--target-mode <value>", "Target exposure mode", "customer_owned_voice_trial").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 () => {
|
|
36742
|
+
const body = {
|
|
36743
|
+
agency_name: String(opts.agencyName),
|
|
36744
|
+
requested_tool_surface: parseCsvOption(opts.tools) ?? [],
|
|
36745
|
+
target_exposure_mode: String(opts.targetMode)
|
|
36746
|
+
};
|
|
36747
|
+
if (opts.sourceUrl) body.source_url = String(opts.sourceUrl);
|
|
36748
|
+
if (opts.branchLocation) body.branch_location = String(opts.branchLocation);
|
|
36749
|
+
const data = await apiFetch("/v1/console/agency-setup/workflow", {
|
|
36750
|
+
method: "POST",
|
|
36751
|
+
body: JSON.stringify(body),
|
|
36752
|
+
orgId: opts.org,
|
|
36753
|
+
apiUrlOverride: opts.apiUrl
|
|
36754
|
+
});
|
|
36755
|
+
format(data, { json: opts.json ?? false });
|
|
36756
|
+
}));
|
|
36678
36757
|
}
|
|
36679
36758
|
|
|
36680
36759
|
// src/commands/ops-diagnostics.ts
|
|
@@ -36929,6 +37008,16 @@ function registerOpsReportingCommands(reporting) {
|
|
|
36929
37008
|
});
|
|
36930
37009
|
format(data, { json: opts.json ?? false });
|
|
36931
37010
|
}));
|
|
37011
|
+
reporting.command("customer-live-status").description("Show one operator-grade customer-live status answer with actions, proof refs, and next commands").option("--environment <value>", "Environment filter").option("--agency-name <name>", "Agency name for setup-preview fallback when no release packet exists").option("--source-url <url>", "Official source URL for setup-preview fallback").option("--branch-location <value>", "Branch/location for setup-preview fallback").option("--tools <csv>", "Requested tool surface for setup-preview fallback").option("--target-mode <mode>", "Target exposure mode for setup-preview fallback").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 () => {
|
|
37012
|
+
const params = new URLSearchParams();
|
|
37013
|
+
if (opts.environment) params.set("environment", String(opts.environment));
|
|
37014
|
+
appendSetupPreviewContext(params, opts);
|
|
37015
|
+
const data = await apiFetch(withQuery("/v1/console/customer-live-status", params), {
|
|
37016
|
+
orgId: opts.org,
|
|
37017
|
+
apiUrlOverride: opts.apiUrl
|
|
37018
|
+
});
|
|
37019
|
+
format(data, { json: opts.json ?? false });
|
|
37020
|
+
}));
|
|
36932
37021
|
reporting.command("release-scorecard").description("Show deterministic release scorecard for an agent").requiredOption("--agent <id>", "Agent ID").option("--limit <n>", "Number of runs to include", "10").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
36933
37022
|
const limit = Math.max(1, Math.min(100, Number(opts.limit || 10) || 10));
|
|
36934
37023
|
const data = await apiFetch(`/v1/console/agents/${opts.agent}/release-scorecard?limit=${limit}`, {
|
|
@@ -37560,41 +37649,80 @@ function defaultImprovementArtifactPath() {
|
|
|
37560
37649
|
}
|
|
37561
37650
|
async function resolveBugReportWizardInputs(opts) {
|
|
37562
37651
|
if (!opts.wizard) return opts;
|
|
37563
|
-
|
|
37564
|
-
|
|
37565
|
-
|
|
37566
|
-
|
|
37567
|
-
|
|
37568
|
-
|
|
37569
|
-
|
|
37570
|
-
|
|
37571
|
-
|
|
37572
|
-
|
|
37573
|
-
|
|
37574
|
-
|
|
37575
|
-
|
|
37576
|
-
|
|
37577
|
-
|
|
37578
|
-
|
|
37579
|
-
|
|
37580
|
-
|
|
37581
|
-
|
|
37582
|
-
|
|
37583
|
-
|
|
37584
|
-
|
|
37585
|
-
|
|
37586
|
-
|
|
37587
|
-
|
|
37588
|
-
|
|
37589
|
-
|
|
37590
|
-
|
|
37591
|
-
|
|
37592
|
-
|
|
37593
|
-
|
|
37594
|
-
|
|
37595
|
-
|
|
37596
|
-
|
|
37597
|
-
|
|
37652
|
+
const resolved = await runWizardSession({
|
|
37653
|
+
step: "bug.report",
|
|
37654
|
+
remediation: "Run with --out and --command in non-interactive mode, or use --wizard in a TTY.",
|
|
37655
|
+
title: "Bug Report Wizard",
|
|
37656
|
+
initialState: { ...opts },
|
|
37657
|
+
enabled: true,
|
|
37658
|
+
prompts: [
|
|
37659
|
+
{
|
|
37660
|
+
key: "out",
|
|
37661
|
+
label: "Output file path",
|
|
37662
|
+
defaultValue: () => defaultArtifactPath(),
|
|
37663
|
+
required: true,
|
|
37664
|
+
shouldPrompt: (state) => !String(state.out ?? "").trim()
|
|
37665
|
+
},
|
|
37666
|
+
{
|
|
37667
|
+
key: "command",
|
|
37668
|
+
label: "Failing command",
|
|
37669
|
+
required: true,
|
|
37670
|
+
shouldPrompt: (state) => !String(state.command ?? "").trim()
|
|
37671
|
+
},
|
|
37672
|
+
{
|
|
37673
|
+
key: "requestMethod",
|
|
37674
|
+
label: "Request method",
|
|
37675
|
+
defaultValue: "GET",
|
|
37676
|
+
required: true,
|
|
37677
|
+
shouldPrompt: (state) => !String(state.requestMethod ?? "").trim()
|
|
37678
|
+
},
|
|
37679
|
+
{
|
|
37680
|
+
key: "requestLocation",
|
|
37681
|
+
label: "Request URL or path (for example /v1/console/agents)",
|
|
37682
|
+
required: true,
|
|
37683
|
+
shouldPrompt: (state) => !String(state.requestUrl ?? "").trim() && !String(state.requestPath ?? "").trim(),
|
|
37684
|
+
onResolved: (value, state) => {
|
|
37685
|
+
const location = String(value ?? "").trim();
|
|
37686
|
+
if (location.startsWith("http://") || location.startsWith("https://")) {
|
|
37687
|
+
state.requestUrl = location;
|
|
37688
|
+
state.requestPath = "";
|
|
37689
|
+
} else {
|
|
37690
|
+
state.requestPath = location;
|
|
37691
|
+
state.requestUrl = "";
|
|
37692
|
+
}
|
|
37693
|
+
}
|
|
37694
|
+
},
|
|
37695
|
+
{
|
|
37696
|
+
key: "responseStatus",
|
|
37697
|
+
label: "Response status",
|
|
37698
|
+
defaultValue: "500",
|
|
37699
|
+
required: true,
|
|
37700
|
+
shouldPrompt: (state) => !String(state.responseStatus ?? "").trim()
|
|
37701
|
+
},
|
|
37702
|
+
{
|
|
37703
|
+
key: "severity",
|
|
37704
|
+
label: "Severity",
|
|
37705
|
+
defaultValue: "medium",
|
|
37706
|
+
required: true,
|
|
37707
|
+
shouldPrompt: (state) => !String(state.severity ?? "").trim()
|
|
37708
|
+
},
|
|
37709
|
+
{
|
|
37710
|
+
key: "source",
|
|
37711
|
+
label: "Source",
|
|
37712
|
+
defaultValue: "cli",
|
|
37713
|
+
required: true,
|
|
37714
|
+
shouldPrompt: (state) => !String(state.source ?? "").trim()
|
|
37715
|
+
},
|
|
37716
|
+
{
|
|
37717
|
+
key: "submit",
|
|
37718
|
+
label: "Submit packet to FOH API now? [y/N]",
|
|
37719
|
+
kind: "confirm",
|
|
37720
|
+
confirmDefault: false,
|
|
37721
|
+
shouldPrompt: (state) => state.submit === void 0
|
|
37722
|
+
}
|
|
37723
|
+
]
|
|
37724
|
+
});
|
|
37725
|
+
delete resolved.requestLocation;
|
|
37598
37726
|
return resolved;
|
|
37599
37727
|
}
|
|
37600
37728
|
function ensureBugReportRequiredInputs(opts) {
|
|
@@ -37790,7 +37918,7 @@ function registerBug(program3) {
|
|
|
37790
37918
|
|
|
37791
37919
|
// src/lib/proof-cache.ts
|
|
37792
37920
|
var import_node_crypto2 = require("node:crypto");
|
|
37793
|
-
var
|
|
37921
|
+
var import_node_fs4 = require("node:fs");
|
|
37794
37922
|
var import_node_path = require("node:path");
|
|
37795
37923
|
var DEFAULT_MAX_AGE_MS = 15 * 60 * 1e3;
|
|
37796
37924
|
var DEFAULT_WAIT_MS = 180 * 1e3;
|
|
@@ -37814,7 +37942,7 @@ function publicPath(filePath) {
|
|
|
37814
37942
|
}
|
|
37815
37943
|
function readFreshCache(filePath, maxAgeMs) {
|
|
37816
37944
|
try {
|
|
37817
|
-
const payload = JSON.parse((0,
|
|
37945
|
+
const payload = JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf8"));
|
|
37818
37946
|
const createdAt = Date.parse(String(payload.created_at || ""));
|
|
37819
37947
|
if (!Number.isFinite(createdAt)) return null;
|
|
37820
37948
|
const ageMs = Date.now() - createdAt;
|
|
@@ -37826,8 +37954,8 @@ function readFreshCache(filePath, maxAgeMs) {
|
|
|
37826
37954
|
}
|
|
37827
37955
|
}
|
|
37828
37956
|
function writeCache(filePath, value) {
|
|
37829
|
-
(0,
|
|
37830
|
-
(0,
|
|
37957
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path.dirname)(filePath), { recursive: true });
|
|
37958
|
+
(0, import_node_fs4.writeFileSync)(filePath, `${JSON.stringify({
|
|
37831
37959
|
schema_version: "foh_proof_cache_entry.v1",
|
|
37832
37960
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37833
37961
|
value
|
|
@@ -37862,7 +37990,7 @@ async function withProofCache(options, run) {
|
|
|
37862
37990
|
const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
37863
37991
|
const waitMs = options.waitMs ?? Number(process.env.FOH_PROOF_CACHE_WAIT_MS || DEFAULT_WAIT_MS);
|
|
37864
37992
|
const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
|
|
37865
|
-
(0,
|
|
37993
|
+
(0, import_node_fs4.mkdirSync)(resolvedDir, { recursive: true });
|
|
37866
37994
|
const existing = readFreshCache(cachePath, maxAgeMs);
|
|
37867
37995
|
if (existing) {
|
|
37868
37996
|
return {
|
|
@@ -37881,7 +38009,7 @@ async function withProofCache(options, run) {
|
|
|
37881
38009
|
}
|
|
37882
38010
|
let lockOwner = false;
|
|
37883
38011
|
try {
|
|
37884
|
-
(0,
|
|
38012
|
+
(0, import_node_fs4.mkdirSync)(lockPath);
|
|
37885
38013
|
lockOwner = true;
|
|
37886
38014
|
} catch {
|
|
37887
38015
|
const started = Date.now();
|
|
@@ -37922,7 +38050,7 @@ async function withProofCache(options, run) {
|
|
|
37922
38050
|
}
|
|
37923
38051
|
};
|
|
37924
38052
|
} finally {
|
|
37925
|
-
if (lockOwner) (0,
|
|
38053
|
+
if (lockOwner) (0, import_node_fs4.rmSync)(lockPath, { recursive: true, force: true });
|
|
37926
38054
|
}
|
|
37927
38055
|
}
|
|
37928
38056
|
|
|
@@ -38412,7 +38540,582 @@ function registerProve(program3) {
|
|
|
38412
38540
|
}));
|
|
38413
38541
|
}
|
|
38414
38542
|
|
|
38415
|
-
// src/
|
|
38543
|
+
// src/commands/interactive.ts
|
|
38544
|
+
var import_readline2 = require("readline");
|
|
38545
|
+
var import_child_process2 = require("child_process");
|
|
38546
|
+
var import_fs11 = require("fs");
|
|
38547
|
+
var import_os2 = require("os");
|
|
38548
|
+
var import_path9 = require("path");
|
|
38549
|
+
|
|
38550
|
+
// src/commands/command-graph.ts
|
|
38551
|
+
var COMMAND_SURFACE_DEFINITIONS = [
|
|
38552
|
+
{
|
|
38553
|
+
id: "home",
|
|
38554
|
+
commandPath: ["home"],
|
|
38555
|
+
label: "home",
|
|
38556
|
+
descriptionFallback: "Open onboarding home",
|
|
38557
|
+
mutatesState: "read",
|
|
38558
|
+
shellSlash: "/home",
|
|
38559
|
+
includeInSuggestions: false,
|
|
38560
|
+
homeContexts: []
|
|
38561
|
+
},
|
|
38562
|
+
{
|
|
38563
|
+
id: "start",
|
|
38564
|
+
commandPath: ["home"],
|
|
38565
|
+
invokeArgs: ["start"],
|
|
38566
|
+
label: "start",
|
|
38567
|
+
descriptionFallback: "Run guided onboarding start",
|
|
38568
|
+
mutatesState: "read",
|
|
38569
|
+
shellSlash: "/start",
|
|
38570
|
+
includeInSuggestions: false,
|
|
38571
|
+
homeContexts: []
|
|
38572
|
+
},
|
|
38573
|
+
{
|
|
38574
|
+
id: "auth_login",
|
|
38575
|
+
commandPath: ["auth", "login"],
|
|
38576
|
+
label: "login now (recommended)",
|
|
38577
|
+
descriptionFallback: "Run interactive login flow.",
|
|
38578
|
+
mutatesState: "write",
|
|
38579
|
+
examples: ["foh auth login --wizard"],
|
|
38580
|
+
homeContexts: ["unauthenticated"],
|
|
38581
|
+
homeAliases: ["start", "login", "auth", "auth login"],
|
|
38582
|
+
includeInSuggestions: true
|
|
38583
|
+
},
|
|
38584
|
+
{
|
|
38585
|
+
id: "auth_login_help",
|
|
38586
|
+
commandPath: ["auth", "login"],
|
|
38587
|
+
invokeArgs: ["auth", "login", "--help"],
|
|
38588
|
+
label: "auth login help",
|
|
38589
|
+
descriptionFallback: "Show auth login examples.",
|
|
38590
|
+
mutatesState: "read",
|
|
38591
|
+
homeContexts: ["unauthenticated"],
|
|
38592
|
+
includeInSuggestions: true
|
|
38593
|
+
},
|
|
38594
|
+
{
|
|
38595
|
+
id: "auth_login_wizard",
|
|
38596
|
+
commandPath: ["auth", "login"],
|
|
38597
|
+
invokeArgs: ["auth", "login", "--wizard"],
|
|
38598
|
+
label: "auth login wizard",
|
|
38599
|
+
descriptionFallback: "Run login wizard",
|
|
38600
|
+
mutatesState: "write",
|
|
38601
|
+
shellSlash: "/login",
|
|
38602
|
+
includeInSuggestions: true
|
|
38603
|
+
},
|
|
38604
|
+
{
|
|
38605
|
+
id: "auth_whoami",
|
|
38606
|
+
commandPath: ["auth", "whoami"],
|
|
38607
|
+
label: "auth whoami",
|
|
38608
|
+
descriptionFallback: "Show current auth context",
|
|
38609
|
+
requiresAuth: true,
|
|
38610
|
+
mutatesState: "read",
|
|
38611
|
+
homeContexts: ["authenticated_no_org"],
|
|
38612
|
+
homeAliases: ["whoami"],
|
|
38613
|
+
shellSlash: "/auth",
|
|
38614
|
+
includeInSuggestions: true
|
|
38615
|
+
},
|
|
38616
|
+
{
|
|
38617
|
+
id: "org_list",
|
|
38618
|
+
commandPath: ["org", "list"],
|
|
38619
|
+
label: "list my orgs",
|
|
38620
|
+
descriptionFallback: "View org memberships.",
|
|
38621
|
+
requiresAuth: true,
|
|
38622
|
+
mutatesState: "read",
|
|
38623
|
+
homeContexts: ["authenticated_no_org"],
|
|
38624
|
+
homeAliases: ["org"],
|
|
38625
|
+
shellSlash: "/orgs",
|
|
38626
|
+
includeInSuggestions: true
|
|
38627
|
+
},
|
|
38628
|
+
{
|
|
38629
|
+
id: "org_use_help",
|
|
38630
|
+
commandPath: ["org", "use"],
|
|
38631
|
+
invokeArgs: ["org", "use", "--help"],
|
|
38632
|
+
label: "org use help",
|
|
38633
|
+
descriptionFallback: "Set default org guidance.",
|
|
38634
|
+
requiresAuth: true,
|
|
38635
|
+
mutatesState: "read",
|
|
38636
|
+
homeContexts: ["authenticated_no_org"],
|
|
38637
|
+
includeInSuggestions: true
|
|
38638
|
+
},
|
|
38639
|
+
{
|
|
38640
|
+
id: "tenant_status",
|
|
38641
|
+
commandPath: ["tenant", "status"],
|
|
38642
|
+
label: "tenant status",
|
|
38643
|
+
descriptionFallback: "Check tenant status and readiness.",
|
|
38644
|
+
requiresAuth: true,
|
|
38645
|
+
requiresOrg: true,
|
|
38646
|
+
mutatesState: "read",
|
|
38647
|
+
homeContexts: ["authenticated_with_org"],
|
|
38648
|
+
homeAliases: ["tenant"],
|
|
38649
|
+
shellSlash: "/status",
|
|
38650
|
+
includeInSuggestions: true
|
|
38651
|
+
},
|
|
38652
|
+
{
|
|
38653
|
+
id: "templates_list",
|
|
38654
|
+
commandPath: ["templates", "list"],
|
|
38655
|
+
label: "template list",
|
|
38656
|
+
descriptionFallback: "List templates.",
|
|
38657
|
+
requiresAuth: true,
|
|
38658
|
+
requiresOrg: true,
|
|
38659
|
+
mutatesState: "read",
|
|
38660
|
+
homeContexts: ["authenticated_with_org"],
|
|
38661
|
+
homeAliases: ["templates", "template list"],
|
|
38662
|
+
shellSlash: "/templates",
|
|
38663
|
+
includeInSuggestions: true
|
|
38664
|
+
},
|
|
38665
|
+
{
|
|
38666
|
+
id: "agent_list_all",
|
|
38667
|
+
commandPath: ["agent", "list"],
|
|
38668
|
+
invokeArgs: ["agent", "list", "--segment", "all"],
|
|
38669
|
+
label: "agent list",
|
|
38670
|
+
descriptionFallback: "List all agents.",
|
|
38671
|
+
requiresAuth: true,
|
|
38672
|
+
requiresOrg: true,
|
|
38673
|
+
mutatesState: "read",
|
|
38674
|
+
homeContexts: ["authenticated_with_org"],
|
|
38675
|
+
homeAliases: ["agent"],
|
|
38676
|
+
shellSlash: "/agent",
|
|
38677
|
+
includeInSuggestions: true
|
|
38678
|
+
},
|
|
38679
|
+
{
|
|
38680
|
+
id: "setup_help",
|
|
38681
|
+
commandPath: ["setup"],
|
|
38682
|
+
invokeArgs: ["setup", "--help"],
|
|
38683
|
+
label: "setup help",
|
|
38684
|
+
descriptionFallback: "Show setup flow options.",
|
|
38685
|
+
mutatesState: "read",
|
|
38686
|
+
homeContexts: ["authenticated_with_org"],
|
|
38687
|
+
homeAliases: ["setup"],
|
|
38688
|
+
shellSlash: "/setup",
|
|
38689
|
+
includeInSuggestions: true
|
|
38690
|
+
},
|
|
38691
|
+
{
|
|
38692
|
+
id: "bug_report_help",
|
|
38693
|
+
commandPath: ["bug", "report"],
|
|
38694
|
+
invokeArgs: ["bug", "report", "--help"],
|
|
38695
|
+
label: "bug report help",
|
|
38696
|
+
descriptionFallback: "Show bug-report guidance.",
|
|
38697
|
+
mutatesState: "read",
|
|
38698
|
+
homeContexts: ["authenticated_with_org"],
|
|
38699
|
+
homeAliases: ["bug report", "bug report help"],
|
|
38700
|
+
includeInSuggestions: false
|
|
38701
|
+
},
|
|
38702
|
+
{
|
|
38703
|
+
id: "bug_list",
|
|
38704
|
+
commandPath: ["bug", "list"],
|
|
38705
|
+
label: "bug list",
|
|
38706
|
+
descriptionFallback: "List centralized bug reports.",
|
|
38707
|
+
requiresAuth: true,
|
|
38708
|
+
requiresOrg: true,
|
|
38709
|
+
mutatesState: "read",
|
|
38710
|
+
homeContexts: ["authenticated_with_org"],
|
|
38711
|
+
homeAliases: ["bug list"],
|
|
38712
|
+
includeInSuggestions: false
|
|
38713
|
+
},
|
|
38714
|
+
{
|
|
38715
|
+
id: "interactive_shell",
|
|
38716
|
+
commandPath: ["interactive"],
|
|
38717
|
+
label: "interactive shell",
|
|
38718
|
+
descriptionFallback: "Open slash-command shell.",
|
|
38719
|
+
mutatesState: "read",
|
|
38720
|
+
homeContexts: ["unauthenticated", "authenticated_no_org", "authenticated_with_org"],
|
|
38721
|
+
homeAliases: ["interactive", "interactive shell", "shell"],
|
|
38722
|
+
includeInSuggestions: false
|
|
38723
|
+
},
|
|
38724
|
+
{
|
|
38725
|
+
id: "full_help",
|
|
38726
|
+
commandPath: ["home"],
|
|
38727
|
+
invokeArgs: ["--help"],
|
|
38728
|
+
label: "full help",
|
|
38729
|
+
descriptionFallback: "Show all CLI commands.",
|
|
38730
|
+
mutatesState: "read",
|
|
38731
|
+
homeContexts: ["unauthenticated", "authenticated_no_org"],
|
|
38732
|
+
homeAliases: ["full help"],
|
|
38733
|
+
includeInSuggestions: false
|
|
38734
|
+
},
|
|
38735
|
+
{
|
|
38736
|
+
id: "whatsapp_start",
|
|
38737
|
+
commandPath: ["channel", "whatsapp", "start"],
|
|
38738
|
+
label: "whatsapp start",
|
|
38739
|
+
descriptionFallback: "WhatsApp readiness path",
|
|
38740
|
+
mutatesState: "write",
|
|
38741
|
+
shellSlash: "/whatsapp",
|
|
38742
|
+
includeInSuggestions: true
|
|
38743
|
+
},
|
|
38744
|
+
{
|
|
38745
|
+
id: "voice_help",
|
|
38746
|
+
commandPath: ["voice"],
|
|
38747
|
+
invokeArgs: ["voice", "--help"],
|
|
38748
|
+
label: "voice help",
|
|
38749
|
+
descriptionFallback: "Voice command help",
|
|
38750
|
+
mutatesState: "read",
|
|
38751
|
+
shellSlash: "/voice",
|
|
38752
|
+
includeInSuggestions: true
|
|
38753
|
+
},
|
|
38754
|
+
{
|
|
38755
|
+
id: "widget_help",
|
|
38756
|
+
commandPath: ["widget"],
|
|
38757
|
+
invokeArgs: ["widget", "--help"],
|
|
38758
|
+
label: "widget help",
|
|
38759
|
+
descriptionFallback: "Widget command help",
|
|
38760
|
+
mutatesState: "read",
|
|
38761
|
+
shellSlash: "/widget",
|
|
38762
|
+
includeInSuggestions: true
|
|
38763
|
+
},
|
|
38764
|
+
{
|
|
38765
|
+
id: "diag_debug",
|
|
38766
|
+
commandPath: ["diag"],
|
|
38767
|
+
invokeArgs: ["debug"],
|
|
38768
|
+
label: "debug",
|
|
38769
|
+
descriptionFallback: "Run diagnostics",
|
|
38770
|
+
mutatesState: "read",
|
|
38771
|
+
shellSlash: "/debug",
|
|
38772
|
+
includeInSuggestions: true
|
|
38773
|
+
}
|
|
38774
|
+
];
|
|
38775
|
+
var SHELL_BUILTIN_COMMANDS = [
|
|
38776
|
+
{ slash: "/help", description: "Show shell help" },
|
|
38777
|
+
{ slash: "/?", description: "Show keymap help" },
|
|
38778
|
+
{ slash: "/keys", description: "Show keymap help" },
|
|
38779
|
+
{ slash: "/commands", description: "List available slash commands" },
|
|
38780
|
+
{ slash: "/again", description: "Replay last successful command" },
|
|
38781
|
+
{ slash: "/retry", description: "Replay last failed command" },
|
|
38782
|
+
{ slash: "/editlast", description: "Prefill last command for editing" },
|
|
38783
|
+
{ slash: "/snippet", description: "Manage named command snippets" },
|
|
38784
|
+
{ slash: "/clear", description: "Clear the terminal output" },
|
|
38785
|
+
{ slash: "/exit", description: "Exit shell" },
|
|
38786
|
+
{ slash: "/quit", description: "Exit shell" }
|
|
38787
|
+
];
|
|
38788
|
+
var HOME_QUICK_ACTION_ORDER = {
|
|
38789
|
+
unauthenticated: ["auth_login", "interactive_shell", "full_help", "auth_login_help"],
|
|
38790
|
+
authenticated_no_org: ["org_list", "interactive_shell", "org_use_help", "auth_whoami", "full_help"],
|
|
38791
|
+
authenticated_with_org: [
|
|
38792
|
+
"tenant_status",
|
|
38793
|
+
"interactive_shell",
|
|
38794
|
+
"templates_list",
|
|
38795
|
+
"agent_list_all",
|
|
38796
|
+
"setup_help",
|
|
38797
|
+
"bug_report_help",
|
|
38798
|
+
"bug_list"
|
|
38799
|
+
]
|
|
38800
|
+
};
|
|
38801
|
+
var commandPathIndex = /* @__PURE__ */ new Map();
|
|
38802
|
+
function contextFromState(state) {
|
|
38803
|
+
if (!state.authenticated) return "unauthenticated";
|
|
38804
|
+
if (!state.orgId) return "authenticated_no_org";
|
|
38805
|
+
return "authenticated_with_org";
|
|
38806
|
+
}
|
|
38807
|
+
function commandKey(parts) {
|
|
38808
|
+
return parts.join(" ");
|
|
38809
|
+
}
|
|
38810
|
+
function normalizeCommandToken(value) {
|
|
38811
|
+
return String(value ?? "").trim().toLowerCase();
|
|
38812
|
+
}
|
|
38813
|
+
function readCommandDescription(command) {
|
|
38814
|
+
try {
|
|
38815
|
+
return (command.description() ?? "").trim();
|
|
38816
|
+
} catch {
|
|
38817
|
+
return "";
|
|
38818
|
+
}
|
|
38819
|
+
}
|
|
38820
|
+
function collectCommandPaths(program3) {
|
|
38821
|
+
const paths = /* @__PURE__ */ new Map();
|
|
38822
|
+
const walk = (parentPath, scope) => {
|
|
38823
|
+
for (const child of scope.commands) {
|
|
38824
|
+
const childName = normalizeCommandToken(child.name());
|
|
38825
|
+
if (!childName) continue;
|
|
38826
|
+
const nextPath = [...parentPath, childName];
|
|
38827
|
+
const canonicalPathKey = commandKey(nextPath);
|
|
38828
|
+
const description = readCommandDescription(child);
|
|
38829
|
+
paths.set(canonicalPathKey, { pathKey: canonicalPathKey, description });
|
|
38830
|
+
const aliases = child.aliases().map((alias) => normalizeCommandToken(alias)).filter(Boolean);
|
|
38831
|
+
for (const alias of aliases) {
|
|
38832
|
+
const aliasPath = [...parentPath, alias];
|
|
38833
|
+
paths.set(commandKey(aliasPath), { pathKey: canonicalPathKey, description });
|
|
38834
|
+
}
|
|
38835
|
+
walk(nextPath, child);
|
|
38836
|
+
}
|
|
38837
|
+
};
|
|
38838
|
+
walk([], program3);
|
|
38839
|
+
return paths;
|
|
38840
|
+
}
|
|
38841
|
+
function resolveDescription(definition) {
|
|
38842
|
+
const record2 = commandPathIndex.get(commandKey(definition.commandPath));
|
|
38843
|
+
if (record2 && record2.description) return record2.description;
|
|
38844
|
+
return definition.descriptionFallback;
|
|
38845
|
+
}
|
|
38846
|
+
function buildEntry(definition) {
|
|
38847
|
+
return {
|
|
38848
|
+
id: definition.id,
|
|
38849
|
+
label: definition.label,
|
|
38850
|
+
description: resolveDescription(definition),
|
|
38851
|
+
args: [...definition.invokeArgs ?? definition.commandPath],
|
|
38852
|
+
commandPath: [...definition.commandPath],
|
|
38853
|
+
requiresAuth: definition.requiresAuth === true,
|
|
38854
|
+
requiresOrg: definition.requiresOrg === true,
|
|
38855
|
+
mutatesState: definition.mutatesState,
|
|
38856
|
+
examples: [...definition.examples ?? []],
|
|
38857
|
+
homeContexts: [...definition.homeContexts ?? []],
|
|
38858
|
+
homeAliases: [...definition.homeAliases ?? []],
|
|
38859
|
+
shellSlash: definition.shellSlash,
|
|
38860
|
+
includeInSuggestions: definition.includeInSuggestions !== false
|
|
38861
|
+
};
|
|
38862
|
+
}
|
|
38863
|
+
function initializeCommandGraph(program3) {
|
|
38864
|
+
commandPathIndex = collectCommandPaths(program3);
|
|
38865
|
+
const missing = COMMAND_SURFACE_DEFINITIONS.filter((definition) => !commandPathIndex.has(commandKey(definition.commandPath))).map((definition) => definition.id);
|
|
38866
|
+
if (missing.length > 0) {
|
|
38867
|
+
throw new Error(`command_graph_definition_missing_paths:${missing.join(",")}`);
|
|
38868
|
+
}
|
|
38869
|
+
}
|
|
38870
|
+
function getCommandGraphEntries() {
|
|
38871
|
+
return COMMAND_SURFACE_DEFINITIONS.map(buildEntry);
|
|
38872
|
+
}
|
|
38873
|
+
function getHomeQuickActions(state) {
|
|
38874
|
+
const context = contextFromState(state);
|
|
38875
|
+
const rank = new Map(HOME_QUICK_ACTION_ORDER[context].map((id, index) => [id, index]));
|
|
38876
|
+
return getCommandGraphEntries().filter((entry) => entry.homeContexts.includes(context)).sort((left, right) => {
|
|
38877
|
+
const leftRank = rank.get(left.id) ?? Number.MAX_SAFE_INTEGER;
|
|
38878
|
+
const rightRank = rank.get(right.id) ?? Number.MAX_SAFE_INTEGER;
|
|
38879
|
+
if (leftRank !== rightRank) return leftRank - rightRank;
|
|
38880
|
+
return left.label.localeCompare(right.label);
|
|
38881
|
+
}).map((entry) => ({
|
|
38882
|
+
label: entry.label,
|
|
38883
|
+
description: entry.description,
|
|
38884
|
+
args: [...entry.args],
|
|
38885
|
+
aliases: [...entry.homeAliases]
|
|
38886
|
+
}));
|
|
38887
|
+
}
|
|
38888
|
+
function getHomePaletteCommands(state) {
|
|
38889
|
+
return getCommandGraphEntries().filter((entry) => {
|
|
38890
|
+
if (entry.requiresAuth && !state.authenticated) return false;
|
|
38891
|
+
if (entry.requiresOrg && (!state.authenticated || !state.orgId)) return false;
|
|
38892
|
+
if (entry.homeContexts.length > 0) return true;
|
|
38893
|
+
return Boolean(entry.shellSlash || entry.includeInSuggestions);
|
|
38894
|
+
}).map((entry) => ({
|
|
38895
|
+
label: entry.args.join(" "),
|
|
38896
|
+
description: entry.description,
|
|
38897
|
+
args: [...entry.args],
|
|
38898
|
+
requiresAuth: entry.requiresAuth || void 0,
|
|
38899
|
+
requiresOrg: entry.requiresOrg || void 0,
|
|
38900
|
+
mutatesState: entry.mutatesState,
|
|
38901
|
+
examples: [...entry.examples]
|
|
38902
|
+
}));
|
|
38903
|
+
}
|
|
38904
|
+
function getInteractiveSlashCommands() {
|
|
38905
|
+
const generated = getCommandGraphEntries().filter((entry) => typeof entry.shellSlash === "string").map((entry) => ({
|
|
38906
|
+
slash: entry.shellSlash,
|
|
38907
|
+
description: entry.description,
|
|
38908
|
+
args: [...entry.args]
|
|
38909
|
+
})).sort((left, right) => left.slash.localeCompare(right.slash));
|
|
38910
|
+
return [...SHELL_BUILTIN_COMMANDS, ...generated];
|
|
38911
|
+
}
|
|
38912
|
+
function getInteractiveCommandSuggestions() {
|
|
38913
|
+
return getCommandGraphEntries().filter((entry) => entry.includeInSuggestions).map((entry) => entry.args.join(" ")).filter((value, index, values) => values.indexOf(value) === index);
|
|
38914
|
+
}
|
|
38915
|
+
function normalizeArgs(args) {
|
|
38916
|
+
return args.map((token) => normalizeCommandToken(token)).filter(Boolean);
|
|
38917
|
+
}
|
|
38918
|
+
function startsWithTokens(input, expected) {
|
|
38919
|
+
if (expected.length === 0 || input.length < expected.length) return false;
|
|
38920
|
+
for (let index = 0; index < expected.length; index += 1) {
|
|
38921
|
+
if (input[index] !== expected[index]) return false;
|
|
38922
|
+
}
|
|
38923
|
+
return true;
|
|
38924
|
+
}
|
|
38925
|
+
function getCommandGraphEntryForArgs(args) {
|
|
38926
|
+
const normalizedArgs = normalizeArgs(args);
|
|
38927
|
+
if (normalizedArgs.length === 0) return void 0;
|
|
38928
|
+
const entries = getCommandGraphEntries().map((entry) => ({
|
|
38929
|
+
entry,
|
|
38930
|
+
normalizedEntryArgs: normalizeArgs(entry.args),
|
|
38931
|
+
normalizedCommandPath: normalizeArgs(entry.commandPath),
|
|
38932
|
+
entryArgsMatch: 0,
|
|
38933
|
+
commandPathMatch: 0
|
|
38934
|
+
})).map((candidate) => ({
|
|
38935
|
+
...candidate,
|
|
38936
|
+
entryArgsMatch: startsWithTokens(normalizedArgs, candidate.normalizedEntryArgs) ? candidate.normalizedEntryArgs.length : 0,
|
|
38937
|
+
commandPathMatch: startsWithTokens(normalizedArgs, candidate.normalizedCommandPath) ? candidate.normalizedCommandPath.length : 0
|
|
38938
|
+
})).filter(({ entryArgsMatch, commandPathMatch }) => entryArgsMatch > 0 || commandPathMatch > 0).sort((left, right) => {
|
|
38939
|
+
if (left.entryArgsMatch !== right.entryArgsMatch) return right.entryArgsMatch - left.entryArgsMatch;
|
|
38940
|
+
if (left.commandPathMatch !== right.commandPathMatch) return right.commandPathMatch - left.commandPathMatch;
|
|
38941
|
+
return left.entry.id.localeCompare(right.entry.id);
|
|
38942
|
+
});
|
|
38943
|
+
return entries[0]?.entry;
|
|
38944
|
+
}
|
|
38945
|
+
function classifyCommandMutation(args) {
|
|
38946
|
+
return getCommandGraphEntryForArgs(args)?.mutatesState ?? "read";
|
|
38947
|
+
}
|
|
38948
|
+
|
|
38949
|
+
// src/commands/interactive.ts
|
|
38950
|
+
var SLASH_COMMANDS = getInteractiveSlashCommands();
|
|
38951
|
+
var COMMAND_SUGGESTIONS = getInteractiveCommandSuggestions();
|
|
38952
|
+
function nowIso2() {
|
|
38953
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
38954
|
+
}
|
|
38955
|
+
function interactiveSessionArtifactPath() {
|
|
38956
|
+
const override = String(process.env.FOH_CLI_INTERACTIVE_SESSION_OUT ?? "").trim();
|
|
38957
|
+
if (override.length > 0) return (0, import_path9.resolve)(override);
|
|
38958
|
+
return (0, import_path9.resolve)("test-results/interactive-shell-session.latest.json");
|
|
38959
|
+
}
|
|
38960
|
+
function createInteractiveSessionArtifact() {
|
|
38961
|
+
const seed = `${Date.now().toString(16)}${Math.random().toString(16).slice(2, 10)}`;
|
|
38962
|
+
return {
|
|
38963
|
+
schema_version: "foh_cli_interactive_session.v1",
|
|
38964
|
+
session_id: `shell_${seed}`,
|
|
38965
|
+
started_at: nowIso2(),
|
|
38966
|
+
status: "idle",
|
|
38967
|
+
command_count: 0,
|
|
38968
|
+
failure_count: 0,
|
|
38969
|
+
events: []
|
|
38970
|
+
};
|
|
38971
|
+
}
|
|
38972
|
+
function detectTerminalCapabilities() {
|
|
38973
|
+
const stdinTty = Boolean(process.stdin.isTTY);
|
|
38974
|
+
const stdoutTty = Boolean(process.stdout.isTTY);
|
|
38975
|
+
const supportsRawMode = stdinTty && typeof process.stdin.setRawMode === "function";
|
|
38976
|
+
const term = String(process.env.TERM ?? "").trim().toLowerCase();
|
|
38977
|
+
const supportsAnsiClear = stdoutTty && term !== "dumb";
|
|
38978
|
+
return {
|
|
38979
|
+
stdinTty,
|
|
38980
|
+
stdoutTty,
|
|
38981
|
+
supportsRawMode,
|
|
38982
|
+
supportsLinePrefill: supportsRawMode,
|
|
38983
|
+
supportsAnsiClear
|
|
38984
|
+
};
|
|
38985
|
+
}
|
|
38986
|
+
function recordInteractiveShellEvent(artifact, event) {
|
|
38987
|
+
artifact.events.push({
|
|
38988
|
+
at: nowIso2(),
|
|
38989
|
+
...event
|
|
38990
|
+
});
|
|
38991
|
+
if (event.type === "command_started") {
|
|
38992
|
+
artifact.status = "running";
|
|
38993
|
+
artifact.command_count += 1;
|
|
38994
|
+
artifact.last_command = event.command;
|
|
38995
|
+
} else if (event.type === "command_succeeded") {
|
|
38996
|
+
artifact.status = "idle";
|
|
38997
|
+
artifact.last_command = event.command;
|
|
38998
|
+
} else if (event.type === "command_failed" || event.type === "shell_error") {
|
|
38999
|
+
artifact.status = "failed";
|
|
39000
|
+
artifact.failure_count += 1;
|
|
39001
|
+
artifact.last_error = event.reason;
|
|
39002
|
+
artifact.last_command = event.command;
|
|
39003
|
+
} else if (event.type === "command_blocked") {
|
|
39004
|
+
artifact.status = "idle";
|
|
39005
|
+
artifact.last_error = event.reason;
|
|
39006
|
+
artifact.last_command = event.command;
|
|
39007
|
+
} else if (event.type === "shell_closed") {
|
|
39008
|
+
artifact.status = artifact.status === "failed" ? "failed" : "closed";
|
|
39009
|
+
artifact.completed_at = nowIso2();
|
|
39010
|
+
}
|
|
39011
|
+
}
|
|
39012
|
+
function flushInteractiveSessionArtifact(artifact) {
|
|
39013
|
+
const outputPath = interactiveSessionArtifactPath();
|
|
39014
|
+
(0, import_fs11.mkdirSync)((0, import_path9.dirname)(outputPath), { recursive: true });
|
|
39015
|
+
(0, import_fs11.writeFileSync)(outputPath, JSON.stringify(artifact, null, 2), "utf8");
|
|
39016
|
+
}
|
|
39017
|
+
function interactiveMemoryPath() {
|
|
39018
|
+
const override = String(process.env.FOH_CLI_INTERACTIVE_MEMORY_OUT ?? "").trim();
|
|
39019
|
+
if (override.length > 0) return (0, import_path9.resolve)(override);
|
|
39020
|
+
return (0, import_path9.resolve)((0, import_os2.homedir)(), ".foh", "interactive-shell-memory.json");
|
|
39021
|
+
}
|
|
39022
|
+
function loadInteractiveShellMemory() {
|
|
39023
|
+
const outputPath = interactiveMemoryPath();
|
|
39024
|
+
if (!(0, import_fs11.existsSync)(outputPath)) {
|
|
39025
|
+
return {
|
|
39026
|
+
schema_version: "foh_cli_interactive_memory.v1",
|
|
39027
|
+
updated_at: nowIso2(),
|
|
39028
|
+
history: [],
|
|
39029
|
+
snippets: {}
|
|
39030
|
+
};
|
|
39031
|
+
}
|
|
39032
|
+
try {
|
|
39033
|
+
const raw = JSON.parse((0, import_fs11.readFileSync)(outputPath, "utf8"));
|
|
39034
|
+
return {
|
|
39035
|
+
schema_version: "foh_cli_interactive_memory.v1",
|
|
39036
|
+
updated_at: String(raw.updated_at ?? nowIso2()),
|
|
39037
|
+
history: Array.isArray(raw.history) ? raw.history.map((entry) => String(entry)).filter(Boolean) : [],
|
|
39038
|
+
snippets: raw && typeof raw.snippets === "object" && raw.snippets !== null ? Object.fromEntries(
|
|
39039
|
+
Object.entries(raw.snippets).map(([key, value]) => [String(key), String(value)])
|
|
39040
|
+
) : {}
|
|
39041
|
+
};
|
|
39042
|
+
} catch {
|
|
39043
|
+
return {
|
|
39044
|
+
schema_version: "foh_cli_interactive_memory.v1",
|
|
39045
|
+
updated_at: nowIso2(),
|
|
39046
|
+
history: [],
|
|
39047
|
+
snippets: {}
|
|
39048
|
+
};
|
|
39049
|
+
}
|
|
39050
|
+
}
|
|
39051
|
+
function saveInteractiveShellMemory(memory) {
|
|
39052
|
+
const outputPath = interactiveMemoryPath();
|
|
39053
|
+
(0, import_fs11.mkdirSync)((0, import_path9.dirname)(outputPath), { recursive: true });
|
|
39054
|
+
(0, import_fs11.writeFileSync)(outputPath, JSON.stringify({ ...memory, updated_at: nowIso2() }, null, 2), "utf8");
|
|
39055
|
+
}
|
|
39056
|
+
function interactiveSessionReportPath() {
|
|
39057
|
+
const override = String(process.env.FOH_CLI_INTERACTIVE_SESSION_REPORT_OUT ?? "").trim();
|
|
39058
|
+
if (override.length > 0) return (0, import_path9.resolve)(override);
|
|
39059
|
+
return (0, import_path9.resolve)("test-results/interactive-cli-session-report.latest.json");
|
|
39060
|
+
}
|
|
39061
|
+
function median(values) {
|
|
39062
|
+
if (values.length === 0) return null;
|
|
39063
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
39064
|
+
const middle = Math.floor(sorted.length / 2);
|
|
39065
|
+
if (sorted.length % 2 === 1) return sorted[middle];
|
|
39066
|
+
return Math.round((sorted[middle - 1] + sorted[middle]) / 2);
|
|
39067
|
+
}
|
|
39068
|
+
function buildInteractiveSessionReport(artifact) {
|
|
39069
|
+
const commandStarts = [];
|
|
39070
|
+
const successDurations = [];
|
|
39071
|
+
const errorFamilies = {};
|
|
39072
|
+
let completedCommands = 0;
|
|
39073
|
+
let deadEndCount = 0;
|
|
39074
|
+
for (const event of artifact.events) {
|
|
39075
|
+
if (event.type === "command_started") {
|
|
39076
|
+
commandStarts.push(Date.parse(event.at));
|
|
39077
|
+
continue;
|
|
39078
|
+
}
|
|
39079
|
+
if (event.type === "command_succeeded") {
|
|
39080
|
+
completedCommands += 1;
|
|
39081
|
+
const startedAt = commandStarts.shift();
|
|
39082
|
+
if (startedAt !== void 0 && Number.isFinite(startedAt)) {
|
|
39083
|
+
const duration3 = Math.max(0, Date.parse(event.at) - startedAt);
|
|
39084
|
+
successDurations.push(duration3);
|
|
39085
|
+
}
|
|
39086
|
+
continue;
|
|
39087
|
+
}
|
|
39088
|
+
if (event.type === "command_failed" || event.type === "command_blocked" || event.type === "shell_error") {
|
|
39089
|
+
deadEndCount += 1;
|
|
39090
|
+
const key = String(event.reason ?? event.type).trim() || event.type;
|
|
39091
|
+
errorFamilies[key] = (errorFamilies[key] ?? 0) + 1;
|
|
39092
|
+
if (event.type === "command_failed") {
|
|
39093
|
+
commandStarts.shift();
|
|
39094
|
+
}
|
|
39095
|
+
}
|
|
39096
|
+
}
|
|
39097
|
+
const totalCommands = Math.max(artifact.command_count, 0);
|
|
39098
|
+
const completionRate = totalCommands > 0 ? Number((completedCommands / totalCommands).toFixed(4)) : 0;
|
|
39099
|
+
const topRemediationLoops = Object.entries(errorFamilies).sort((left, right) => right[1] - left[1]).slice(0, 5).map(([reason, count]) => ({ reason, count }));
|
|
39100
|
+
return {
|
|
39101
|
+
schema_version: "interactive_cli_session_report.v1",
|
|
39102
|
+
generated_at: nowIso2(),
|
|
39103
|
+
session_id: artifact.session_id,
|
|
39104
|
+
command_count: totalCommands,
|
|
39105
|
+
completion_rate: completionRate,
|
|
39106
|
+
dead_end_count: deadEndCount,
|
|
39107
|
+
command_error_families: errorFamilies,
|
|
39108
|
+
median_time_to_success_ms: median(successDurations),
|
|
39109
|
+
top_remediation_loops: topRemediationLoops,
|
|
39110
|
+
terminal_capabilities: artifact.terminal_capabilities
|
|
39111
|
+
};
|
|
39112
|
+
}
|
|
39113
|
+
function flushInteractiveSessionReport(artifact) {
|
|
39114
|
+
const outputPath = interactiveSessionReportPath();
|
|
39115
|
+
const report = buildInteractiveSessionReport(artifact);
|
|
39116
|
+
(0, import_fs11.mkdirSync)((0, import_path9.dirname)(outputPath), { recursive: true });
|
|
39117
|
+
(0, import_fs11.writeFileSync)(outputPath, JSON.stringify(report, null, 2), "utf8");
|
|
39118
|
+
}
|
|
38416
39119
|
function tokenizeInput(value) {
|
|
38417
39120
|
const tokens = [];
|
|
38418
39121
|
const matcher = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
@@ -38423,193 +39126,391 @@ function tokenizeInput(value) {
|
|
|
38423
39126
|
}
|
|
38424
39127
|
return tokens;
|
|
38425
39128
|
}
|
|
38426
|
-
function
|
|
38427
|
-
const
|
|
38428
|
-
if (!
|
|
38429
|
-
|
|
38430
|
-
const tokens = tokenizeInput(withoutLeadingSlash);
|
|
38431
|
-
if (tokens.length > 0 && tokens[0].toLowerCase() === "foh") {
|
|
38432
|
-
tokens.shift();
|
|
39129
|
+
async function runSelf(args, apiUrlOverride) {
|
|
39130
|
+
const spawnArgs = [...args];
|
|
39131
|
+
if (apiUrlOverride && !spawnArgs.includes("--api-url")) {
|
|
39132
|
+
spawnArgs.push("--api-url", apiUrlOverride);
|
|
38433
39133
|
}
|
|
38434
|
-
return
|
|
38435
|
-
|
|
38436
|
-
|
|
38437
|
-
|
|
38438
|
-
|
|
38439
|
-
|
|
38440
|
-
|
|
38441
|
-
|
|
39134
|
+
return await new Promise((resolve15, reject) => {
|
|
39135
|
+
const child = (0, import_child_process2.spawn)(process.execPath, [process.argv[1], ...spawnArgs], {
|
|
39136
|
+
stdio: "inherit",
|
|
39137
|
+
env: {
|
|
39138
|
+
...process.env,
|
|
39139
|
+
FOH_CLI_NO_HOME: "1",
|
|
39140
|
+
FOH_CLI_SUPPRESS_BANNER: "1"
|
|
39141
|
+
}
|
|
39142
|
+
});
|
|
39143
|
+
child.once("error", reject);
|
|
39144
|
+
child.once("close", (code) => resolve15(typeof code === "number" ? code : 1));
|
|
38442
39145
|
});
|
|
38443
39146
|
}
|
|
38444
|
-
function
|
|
38445
|
-
|
|
38446
|
-
|
|
38447
|
-
|
|
38448
|
-
|
|
38449
|
-
|
|
38450
|
-
|
|
38451
|
-
|
|
38452
|
-
|
|
38453
|
-
{ label: "auth login", description: "Authenticate the CLI session", args: ["auth", "login"] },
|
|
38454
|
-
{ label: "auth whoami", description: "Show current auth and org context", args: ["auth", "whoami"], requiresAuth: true },
|
|
38455
|
-
{ label: "org list", description: "List org memberships", args: ["org", "list"], requiresAuth: true },
|
|
38456
|
-
{ label: "org use --help", description: "See how to set default org", args: ["org", "use", "--help"], requiresAuth: true },
|
|
38457
|
-
{ label: "tenant status", description: "Check tenant readiness", args: ["tenant", "status"], requiresAuth: true, requiresOrg: true },
|
|
38458
|
-
{ label: "templates list", description: "List available templates", args: ["templates", "list"], requiresAuth: true, requiresOrg: true },
|
|
38459
|
-
{ label: "agent list --segment all", description: "List agents", args: ["agent", "list", "--segment", "all"], requiresAuth: true, requiresOrg: true },
|
|
38460
|
-
{ label: "tests run-all --help", description: "See test workflow command usage", args: ["tests", "run-all", "--help"], requiresAuth: true, requiresOrg: true },
|
|
38461
|
-
{ label: "sim certify --help", description: "See simulation certification options", args: ["sim", "certify", "--help"], requiresAuth: true, requiresOrg: true },
|
|
38462
|
-
{ label: "setup --help", description: "Show setup command help", args: ["setup", "--help"] },
|
|
38463
|
-
{ label: "bug report --help", description: "Show bug report capture help", args: ["bug", "report", "--help"] },
|
|
38464
|
-
{ label: "bug list", description: "List submitted bug reports", args: ["bug", "list"], requiresAuth: true, requiresOrg: true },
|
|
38465
|
-
{ label: "--help", description: "Show full CLI help", args: ["--help"] }
|
|
38466
|
-
];
|
|
38467
|
-
function getQuickActions(state) {
|
|
38468
|
-
if (!state.authenticated) {
|
|
38469
|
-
return [
|
|
38470
|
-
{ label: "login now (recommended)", description: "Run interactive login flow.", args: ["auth", "login"] },
|
|
38471
|
-
{ label: "full help", description: "Show all CLI commands.", args: ["--help"] },
|
|
38472
|
-
{ label: "auth login help", description: "Show auth login examples.", args: ["auth", "login", "--help"] }
|
|
38473
|
-
];
|
|
38474
|
-
}
|
|
38475
|
-
if (!state.orgId) {
|
|
38476
|
-
return [
|
|
38477
|
-
{ label: "list my orgs", description: "View org memberships.", args: ["org", "list"] },
|
|
38478
|
-
{ label: "org use help", description: "Set default org guidance.", args: ["org", "use", "--help"] },
|
|
38479
|
-
{ label: "auth whoami", description: "Inspect current auth context.", args: ["auth", "whoami"] },
|
|
38480
|
-
{ label: "full help", description: "Show all CLI commands.", args: ["--help"] }
|
|
38481
|
-
];
|
|
39147
|
+
function resolveInteractiveState(apiUrlOverride) {
|
|
39148
|
+
try {
|
|
39149
|
+
const creds = loadCredentials(apiUrlOverride);
|
|
39150
|
+
return {
|
|
39151
|
+
authenticated: true,
|
|
39152
|
+
orgId: creds.orgId
|
|
39153
|
+
};
|
|
39154
|
+
} catch {
|
|
39155
|
+
return { authenticated: false };
|
|
38482
39156
|
}
|
|
39157
|
+
}
|
|
39158
|
+
function nextRecommendedCommand(state) {
|
|
39159
|
+
const next = getHomeQuickActions(state)[0];
|
|
39160
|
+
if (!next) return "foh --help";
|
|
39161
|
+
return `foh ${next.args.join(" ")}`;
|
|
39162
|
+
}
|
|
39163
|
+
function keymapHelpText() {
|
|
38483
39164
|
return [
|
|
38484
|
-
|
|
38485
|
-
|
|
38486
|
-
|
|
38487
|
-
|
|
38488
|
-
|
|
38489
|
-
|
|
38490
|
-
|
|
39165
|
+
"",
|
|
39166
|
+
"Shell Keymap",
|
|
39167
|
+
"-----------",
|
|
39168
|
+
" Up / Down Recall command history",
|
|
39169
|
+
" Tab Complete ranked slash/command suggestions",
|
|
39170
|
+
" Enter Run current input",
|
|
39171
|
+
" Esc Cancel palette-style slash entry",
|
|
39172
|
+
" Ctrl+C Exit shell safely",
|
|
39173
|
+
"",
|
|
39174
|
+
"Fallback: if your terminal does not forward these keys, run equivalent slash commands (/help, /commands, /exit).",
|
|
39175
|
+
""
|
|
39176
|
+
].join("\n");
|
|
38491
39177
|
}
|
|
38492
|
-
function
|
|
38493
|
-
|
|
38494
|
-
|
|
38495
|
-
|
|
38496
|
-
|
|
38497
|
-
|
|
39178
|
+
function shellHelpText(state) {
|
|
39179
|
+
const slashLines = SLASH_COMMANDS.map((entry) => ` ${entry.slash.padEnd(12)} ${entry.description}`);
|
|
39180
|
+
return [
|
|
39181
|
+
"",
|
|
39182
|
+
"FOH Interactive Shell",
|
|
39183
|
+
"---------------------",
|
|
39184
|
+
"Use slash commands or type any normal `foh` command without the `foh` prefix.",
|
|
39185
|
+
"Keys: Up/Down history, Tab completion, Ctrl+C or /exit to leave. Run /keys for full mapping.",
|
|
39186
|
+
"",
|
|
39187
|
+
"Slash commands:",
|
|
39188
|
+
...slashLines,
|
|
39189
|
+
"",
|
|
39190
|
+
`Recommended next command: ${nextRecommendedCommand(state)}`,
|
|
39191
|
+
""
|
|
39192
|
+
].join("\n");
|
|
38498
39193
|
}
|
|
38499
|
-
function
|
|
38500
|
-
|
|
39194
|
+
function printPrompt(rl) {
|
|
39195
|
+
rl.setPrompt("foh> ");
|
|
39196
|
+
rl.prompt();
|
|
38501
39197
|
}
|
|
38502
|
-
function
|
|
38503
|
-
|
|
38504
|
-
|
|
38505
|
-
|
|
38506
|
-
|
|
38507
|
-
|
|
38508
|
-
|
|
38509
|
-
|
|
38510
|
-
|
|
38511
|
-
|
|
38512
|
-
|
|
38513
|
-
|
|
38514
|
-
|
|
38515
|
-
|
|
38516
|
-
|
|
38517
|
-
|
|
38518
|
-
|
|
38519
|
-
|
|
38520
|
-
|
|
38521
|
-
|
|
38522
|
-
|
|
38523
|
-
`
|
|
39198
|
+
function safePrintPrompt(rl) {
|
|
39199
|
+
if (rl.closed) return;
|
|
39200
|
+
printPrompt(rl);
|
|
39201
|
+
}
|
|
39202
|
+
function prefillCommand(rl, command) {
|
|
39203
|
+
rl.write(null, { ctrl: true, name: "u" });
|
|
39204
|
+
rl.write(command);
|
|
39205
|
+
}
|
|
39206
|
+
function resolveSlashCommand(input, state, terminal, context) {
|
|
39207
|
+
const normalized = input.trim().toLowerCase();
|
|
39208
|
+
if (normalized === "/exit" || normalized === "/quit") return { quit: true, args: null };
|
|
39209
|
+
if (normalized === "/?" || normalized === "/keys") return { quit: false, args: [], message: keymapHelpText() };
|
|
39210
|
+
if (normalized === "/help") return { quit: false, args: [], message: shellHelpText(state) };
|
|
39211
|
+
if (normalized === "/commands") {
|
|
39212
|
+
const rows = SLASH_COMMANDS.map((entry) => ` ${entry.slash.padEnd(12)} ${entry.description}`);
|
|
39213
|
+
return {
|
|
39214
|
+
quit: false,
|
|
39215
|
+
args: [],
|
|
39216
|
+
message: `
|
|
39217
|
+
Slash commands:
|
|
39218
|
+
${rows.join("\n")}
|
|
39219
|
+
`
|
|
39220
|
+
};
|
|
38524
39221
|
}
|
|
38525
|
-
|
|
38526
|
-
|
|
38527
|
-
|
|
38528
|
-
|
|
38529
|
-
|
|
38530
|
-
|
|
38531
|
-
|
|
38532
|
-
|
|
38533
|
-
|
|
38534
|
-
|
|
38535
|
-
|
|
38536
|
-
if (paletteOpen) {
|
|
38537
|
-
process.stdout.write("\nCommand palette\n");
|
|
38538
|
-
process.stdout.write("---------------\n");
|
|
38539
|
-
process.stdout.write(`/${paletteQuery}
|
|
38540
|
-
`);
|
|
38541
|
-
if (paletteSuggestions.length === 0) {
|
|
38542
|
-
process.stdout.write(" No matches. Press Enter to run typed command, or Esc to close.\n");
|
|
38543
|
-
} else {
|
|
38544
|
-
paletteSuggestions.slice(0, 8).forEach((entry, index) => {
|
|
38545
|
-
const isSelected = index === selectedPaletteIndex;
|
|
38546
|
-
const cursor = isSelected ? ">" : " ";
|
|
38547
|
-
process.stdout.write(` ${cursor} ${entry.label}
|
|
38548
|
-
`);
|
|
38549
|
-
process.stdout.write(` foh ${entry.args.join(" ")}
|
|
38550
|
-
`);
|
|
38551
|
-
});
|
|
39222
|
+
if (normalized === "/clear") {
|
|
39223
|
+
if (terminal.supportsAnsiClear) return { quit: false, args: [], message: "\x1B[2J\x1B[H" };
|
|
39224
|
+
return {
|
|
39225
|
+
quit: false,
|
|
39226
|
+
args: [],
|
|
39227
|
+
message: "\n\n\n\n\nTerminal does not support ANSI clear. Screen reset skipped.\n"
|
|
39228
|
+
};
|
|
39229
|
+
}
|
|
39230
|
+
if (normalized === "/again") {
|
|
39231
|
+
if (context.lastSuccessfulArgs && context.lastSuccessfulArgs.length > 0) {
|
|
39232
|
+
return { quit: false, args: [...context.lastSuccessfulArgs] };
|
|
38552
39233
|
}
|
|
38553
|
-
|
|
38554
|
-
process.stdout.write('\nPress "/" to open command palette.\n');
|
|
39234
|
+
return { quit: false, args: [], message: "No successful command in current session yet.\n" };
|
|
38555
39235
|
}
|
|
38556
|
-
if (
|
|
38557
|
-
|
|
38558
|
-
|
|
38559
|
-
|
|
38560
|
-
|
|
38561
|
-
`);
|
|
39236
|
+
if (normalized === "/retry") {
|
|
39237
|
+
if (context.lastFailedArgs && context.lastFailedArgs.length > 0) {
|
|
39238
|
+
return { quit: false, args: [...context.lastFailedArgs] };
|
|
39239
|
+
}
|
|
39240
|
+
return { quit: false, args: [], message: "No failed command in current session yet.\n" };
|
|
38562
39241
|
}
|
|
39242
|
+
if (normalized === "/editlast") {
|
|
39243
|
+
const candidate = context.lastFailedArgs ?? context.lastSuccessfulArgs;
|
|
39244
|
+
if (!candidate || candidate.length === 0) {
|
|
39245
|
+
return { quit: false, args: [], message: "No previous command to prefill.\n" };
|
|
39246
|
+
}
|
|
39247
|
+
return {
|
|
39248
|
+
quit: false,
|
|
39249
|
+
args: [],
|
|
39250
|
+
prefill: candidate.join(" "),
|
|
39251
|
+
message: terminal.supportsLinePrefill ? "Prefilled last command. Edit and press Enter to run.\n" : `Terminal cannot prefill input. Replay manually: foh ${candidate.join(" ")}
|
|
39252
|
+
`
|
|
39253
|
+
};
|
|
39254
|
+
}
|
|
39255
|
+
if (normalized.startsWith("/run ")) {
|
|
39256
|
+
const raw = input.trim().slice("/run ".length).trim();
|
|
39257
|
+
const args = tokenizeInput(raw);
|
|
39258
|
+
return { quit: false, args: args.length > 0 ? args : null };
|
|
39259
|
+
}
|
|
39260
|
+
if (normalized === "/snippet list") {
|
|
39261
|
+
const rows = Object.entries(context.snippets).sort((left, right) => left[0].localeCompare(right[0])).map(([name, command]) => ` ${name.padEnd(16)} ${command}`);
|
|
39262
|
+
if (rows.length === 0) return { quit: false, args: [], message: "No snippets saved.\n" };
|
|
39263
|
+
return { quit: false, args: [], message: `
|
|
39264
|
+
Saved snippets:
|
|
39265
|
+
${rows.join("\n")}
|
|
39266
|
+
` };
|
|
39267
|
+
}
|
|
39268
|
+
if (normalized.startsWith("/snippet save ")) {
|
|
39269
|
+
const parts = tokenizeInput(input.trim());
|
|
39270
|
+
const name = String(parts[2] ?? "").trim().toLowerCase();
|
|
39271
|
+
const command = parts.slice(3).join(" ").trim();
|
|
39272
|
+
if (!name || !command) {
|
|
39273
|
+
return { quit: false, args: [], message: "Usage: /snippet save <name> <command>\n" };
|
|
39274
|
+
}
|
|
39275
|
+
const normalizedCommand = command.toLowerCase().startsWith("foh ") ? command.slice(4).trim() : command;
|
|
39276
|
+
context.snippets[name] = normalizedCommand;
|
|
39277
|
+
return { quit: false, args: [], message: `Saved snippet "${name}": ${normalizedCommand}
|
|
39278
|
+
` };
|
|
39279
|
+
}
|
|
39280
|
+
if (normalized.startsWith("/snippet run ")) {
|
|
39281
|
+
const parts = tokenizeInput(input.trim());
|
|
39282
|
+
const name = String(parts[2] ?? "").trim().toLowerCase();
|
|
39283
|
+
const command = context.snippets[name];
|
|
39284
|
+
if (!command) return { quit: false, args: [], message: `Snippet "${name}" not found.
|
|
39285
|
+
` };
|
|
39286
|
+
return { quit: false, args: tokenizeInput(command) };
|
|
39287
|
+
}
|
|
39288
|
+
if (normalized.startsWith("/snippet edit ")) {
|
|
39289
|
+
const parts = tokenizeInput(input.trim());
|
|
39290
|
+
const name = String(parts[2] ?? "").trim().toLowerCase();
|
|
39291
|
+
const command = context.snippets[name];
|
|
39292
|
+
if (!command) return { quit: false, args: [], message: `Snippet "${name}" not found.
|
|
39293
|
+
` };
|
|
39294
|
+
return {
|
|
39295
|
+
quit: false,
|
|
39296
|
+
args: [],
|
|
39297
|
+
prefill: command,
|
|
39298
|
+
message: terminal.supportsLinePrefill ? `Prefilled snippet "${name}".
|
|
39299
|
+
` : `Terminal cannot prefill input. Snippet "${name}": ${command}
|
|
39300
|
+
`
|
|
39301
|
+
};
|
|
39302
|
+
}
|
|
39303
|
+
const matched = SLASH_COMMANDS.find((entry) => entry.slash.toLowerCase() === normalized);
|
|
39304
|
+
if (matched?.args && matched.args.length > 0) {
|
|
39305
|
+
return { quit: false, args: [...matched.args] };
|
|
39306
|
+
}
|
|
39307
|
+
if (normalized.startsWith("/")) {
|
|
39308
|
+
return { quit: false, args: [], message: `Unknown slash command: ${input.trim()}
|
|
39309
|
+
Use /help.
|
|
39310
|
+
` };
|
|
39311
|
+
}
|
|
39312
|
+
return { quit: false, args: null };
|
|
38563
39313
|
}
|
|
38564
|
-
function
|
|
38565
|
-
|
|
38566
|
-
|
|
38567
|
-
|
|
38568
|
-
|
|
38569
|
-
|
|
38570
|
-
|
|
38571
|
-
|
|
38572
|
-
|
|
38573
|
-
|
|
38574
|
-
|
|
38575
|
-
|
|
38576
|
-
|
|
38577
|
-
process.stdout.write(" [h] redraw home\n");
|
|
38578
|
-
process.stdout.write(" [q] exit\n");
|
|
38579
|
-
process.stdout.write(" Type any foh command (example: auth whoami)\n");
|
|
38580
|
-
return;
|
|
39314
|
+
function normalizeCommandInput(input) {
|
|
39315
|
+
const trimmed = input.trim();
|
|
39316
|
+
if (!trimmed) return [];
|
|
39317
|
+
const withoutPrefix = trimmed.toLowerCase().startsWith("foh ") ? trimmed.slice(4).trim() : trimmed;
|
|
39318
|
+
return tokenizeInput(withoutPrefix);
|
|
39319
|
+
}
|
|
39320
|
+
function hasYesFlag(args) {
|
|
39321
|
+
return args.some((token) => token === "--yes" || token === "-y");
|
|
39322
|
+
}
|
|
39323
|
+
async function confirmMutationIfNeeded(rl, args) {
|
|
39324
|
+
const mutationState = classifyCommandMutation(args);
|
|
39325
|
+
if (mutationState === "read" || hasYesFlag(args)) {
|
|
39326
|
+
return { approved: true };
|
|
38581
39327
|
}
|
|
38582
|
-
|
|
38583
|
-
|
|
38584
|
-
|
|
39328
|
+
const commandEntry = getCommandGraphEntryForArgs(args);
|
|
39329
|
+
const label = commandEntry ? `foh ${commandEntry.args.join(" ")}` : `foh ${args.join(" ")}`;
|
|
39330
|
+
const warning = mutationState === "high_risk" ? "high-risk" : "write";
|
|
39331
|
+
const approved = await new Promise((resolve15) => {
|
|
39332
|
+
rl.question(`Confirm ${warning} command ${label}? [y/N]: `, (answer) => {
|
|
39333
|
+
const normalized = answer.trim().toLowerCase();
|
|
39334
|
+
resolve15(normalized === "y" || normalized === "yes");
|
|
39335
|
+
});
|
|
39336
|
+
});
|
|
39337
|
+
if (approved) return { approved: true };
|
|
39338
|
+
return {
|
|
39339
|
+
approved: false,
|
|
39340
|
+
message: `Command blocked (interactive_confirmation_required): foh ${args.join(" ")}`
|
|
39341
|
+
};
|
|
39342
|
+
}
|
|
39343
|
+
function commandSuggestionsForState(state) {
|
|
39344
|
+
const contextual = getHomePaletteCommands(state).map((entry) => entry.args.join(" "));
|
|
39345
|
+
const merged = [...contextual, ...COMMAND_SUGGESTIONS];
|
|
39346
|
+
return merged.filter((entry, index, values) => values.indexOf(entry) === index);
|
|
39347
|
+
}
|
|
39348
|
+
function fuzzyScore(query, candidate) {
|
|
39349
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
39350
|
+
const normalizedCandidate = candidate.toLowerCase();
|
|
39351
|
+
if (!normalizedQuery) return 0;
|
|
39352
|
+
if (normalizedCandidate.startsWith(normalizedQuery)) return 0;
|
|
39353
|
+
const includesAt = normalizedCandidate.indexOf(normalizedQuery);
|
|
39354
|
+
if (includesAt >= 0) return 100 + includesAt;
|
|
39355
|
+
let queryIndex = 0;
|
|
39356
|
+
let gaps = 0;
|
|
39357
|
+
for (let i = 0; i < normalizedCandidate.length && queryIndex < normalizedQuery.length; i += 1) {
|
|
39358
|
+
if (normalizedCandidate[i] === normalizedQuery[queryIndex]) {
|
|
39359
|
+
queryIndex += 1;
|
|
39360
|
+
} else if (queryIndex > 0) {
|
|
39361
|
+
gaps += 1;
|
|
39362
|
+
}
|
|
39363
|
+
}
|
|
39364
|
+
if (queryIndex !== normalizedQuery.length) return Number.POSITIVE_INFINITY;
|
|
39365
|
+
return 200 + gaps;
|
|
39366
|
+
}
|
|
39367
|
+
function rankMatches(query, pool) {
|
|
39368
|
+
const scored = pool.map((candidate) => ({ candidate, score: fuzzyScore(query, candidate) })).filter((entry) => Number.isFinite(entry.score)).sort((left, right) => {
|
|
39369
|
+
if (left.score !== right.score) return left.score - right.score;
|
|
39370
|
+
return left.candidate.localeCompare(right.candidate);
|
|
39371
|
+
});
|
|
39372
|
+
return scored.map((entry) => entry.candidate);
|
|
39373
|
+
}
|
|
39374
|
+
function buildCompleter(apiUrlOverride) {
|
|
39375
|
+
const slashLabels = SLASH_COMMANDS.map((entry) => entry.slash);
|
|
39376
|
+
return (line) => {
|
|
39377
|
+
const raw = String(line ?? "");
|
|
39378
|
+
const lower = raw.toLowerCase();
|
|
39379
|
+
const state = resolveInteractiveState(apiUrlOverride);
|
|
39380
|
+
const commandLabels = commandSuggestionsForState(state);
|
|
39381
|
+
const pool = lower.trim().startsWith("/") ? slashLabels : commandLabels;
|
|
39382
|
+
const hits = rankMatches(lower, pool);
|
|
39383
|
+
return [hits.length > 0 ? hits : pool, raw];
|
|
39384
|
+
};
|
|
39385
|
+
}
|
|
39386
|
+
function registerInteractive(program3) {
|
|
39387
|
+
program3.command("interactive").alias("shell").description("Open interactive FOH shell with slash commands and key support").option("--api-url <url>", "Internal API base URL override (operators only)").action(async (opts) => {
|
|
39388
|
+
const sessionArtifact = createInteractiveSessionArtifact();
|
|
39389
|
+
const terminalCapabilities = detectTerminalCapabilities();
|
|
39390
|
+
sessionArtifact.terminal_capabilities = {
|
|
39391
|
+
stdin_tty: terminalCapabilities.stdinTty,
|
|
39392
|
+
stdout_tty: terminalCapabilities.stdoutTty,
|
|
39393
|
+
supports_raw_mode: terminalCapabilities.supportsRawMode,
|
|
39394
|
+
supports_line_prefill: terminalCapabilities.supportsLinePrefill,
|
|
39395
|
+
supports_ansi_clear: terminalCapabilities.supportsAnsiClear
|
|
39396
|
+
};
|
|
39397
|
+
const memory = loadInteractiveShellMemory();
|
|
39398
|
+
recordInteractiveShellEvent(sessionArtifact, { type: "shell_started" });
|
|
39399
|
+
ensureInteractive(
|
|
39400
|
+
"interactive.shell",
|
|
39401
|
+
"Run this in a TTY terminal. For automation use normal non-interactive commands with --json."
|
|
39402
|
+
);
|
|
39403
|
+
const rl = (0, import_readline2.createInterface)({
|
|
39404
|
+
input: process.stdin,
|
|
39405
|
+
output: process.stdout,
|
|
39406
|
+
terminal: true,
|
|
39407
|
+
historySize: 500,
|
|
39408
|
+
removeHistoryDuplicates: true,
|
|
39409
|
+
history: memory.history.slice(0, 500),
|
|
39410
|
+
completer: buildCompleter(opts.apiUrl)
|
|
39411
|
+
});
|
|
39412
|
+
process.stdout.write(shellHelpText(resolveInteractiveState(opts.apiUrl)));
|
|
39413
|
+
printPrompt(rl);
|
|
39414
|
+
let busy = false;
|
|
39415
|
+
let lastSuccessfulArgs = null;
|
|
39416
|
+
let lastFailedArgs = null;
|
|
39417
|
+
rl.on("line", (line) => {
|
|
39418
|
+
void (async () => {
|
|
39419
|
+
if (busy) return;
|
|
39420
|
+
const raw = String(line ?? "");
|
|
39421
|
+
const slash = resolveSlashCommand(raw, resolveInteractiveState(opts.apiUrl), terminalCapabilities, {
|
|
39422
|
+
lastSuccessfulArgs,
|
|
39423
|
+
lastFailedArgs,
|
|
39424
|
+
snippets: memory.snippets
|
|
39425
|
+
});
|
|
39426
|
+
if (slash.quit) {
|
|
39427
|
+
rl.close();
|
|
39428
|
+
return;
|
|
39429
|
+
}
|
|
39430
|
+
if (slash.message) {
|
|
39431
|
+
process.stdout.write(`${slash.message}${slash.message.endsWith("\n") ? "" : "\n"}`);
|
|
39432
|
+
}
|
|
39433
|
+
if (slash.prefill) {
|
|
39434
|
+
if (terminalCapabilities.supportsLinePrefill) {
|
|
39435
|
+
prefillCommand(rl, slash.prefill);
|
|
39436
|
+
}
|
|
39437
|
+
return;
|
|
39438
|
+
}
|
|
39439
|
+
const commandArgs = slash.args ?? normalizeCommandInput(raw);
|
|
39440
|
+
if (commandArgs.length === 0) {
|
|
39441
|
+
safePrintPrompt(rl);
|
|
39442
|
+
return;
|
|
39443
|
+
}
|
|
39444
|
+
const confirmation = await confirmMutationIfNeeded(rl, commandArgs);
|
|
39445
|
+
if (!confirmation.approved) {
|
|
39446
|
+
if (confirmation.message) {
|
|
39447
|
+
process.stdout.write(`${confirmation.message}
|
|
38585
39448
|
`);
|
|
38586
|
-
|
|
38587
|
-
|
|
38588
|
-
|
|
38589
|
-
|
|
38590
|
-
|
|
38591
|
-
|
|
38592
|
-
|
|
38593
|
-
|
|
38594
|
-
|
|
38595
|
-
|
|
38596
|
-
|
|
38597
|
-
|
|
38598
|
-
|
|
39449
|
+
recordInteractiveShellEvent(sessionArtifact, {
|
|
39450
|
+
type: "command_blocked",
|
|
39451
|
+
command: `foh ${commandArgs.join(" ")}`,
|
|
39452
|
+
reason: "interactive_confirmation_required"
|
|
39453
|
+
});
|
|
39454
|
+
}
|
|
39455
|
+
printPrompt(rl);
|
|
39456
|
+
return;
|
|
39457
|
+
}
|
|
39458
|
+
recordInteractiveShellEvent(sessionArtifact, {
|
|
39459
|
+
type: "command_started",
|
|
39460
|
+
command: `foh ${commandArgs.join(" ")}`
|
|
39461
|
+
});
|
|
39462
|
+
busy = true;
|
|
39463
|
+
try {
|
|
39464
|
+
const code = await runSelf(commandArgs, opts.apiUrl);
|
|
39465
|
+
if (code !== 0) {
|
|
39466
|
+
lastFailedArgs = [...commandArgs];
|
|
39467
|
+
process.stdout.write(`Command exited with code ${code}: foh ${commandArgs.join(" ")}
|
|
38599
39468
|
`);
|
|
38600
|
-
|
|
38601
|
-
|
|
39469
|
+
recordInteractiveShellEvent(sessionArtifact, {
|
|
39470
|
+
type: "command_failed",
|
|
39471
|
+
command: `foh ${commandArgs.join(" ")}`,
|
|
39472
|
+
exit_code: code,
|
|
39473
|
+
reason: "non_zero_exit"
|
|
39474
|
+
});
|
|
39475
|
+
} else {
|
|
39476
|
+
lastSuccessfulArgs = [...commandArgs];
|
|
39477
|
+
recordInteractiveShellEvent(sessionArtifact, {
|
|
39478
|
+
type: "command_succeeded",
|
|
39479
|
+
command: `foh ${commandArgs.join(" ")}`,
|
|
39480
|
+
exit_code: code
|
|
39481
|
+
});
|
|
39482
|
+
}
|
|
39483
|
+
} finally {
|
|
39484
|
+
busy = false;
|
|
39485
|
+
safePrintPrompt(rl);
|
|
39486
|
+
}
|
|
39487
|
+
})().catch((error2) => {
|
|
39488
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
39489
|
+
process.stdout.write(`Shell error: ${message}
|
|
38602
39490
|
`);
|
|
38603
|
-
|
|
38604
|
-
|
|
38605
|
-
|
|
38606
|
-
|
|
38607
|
-
|
|
38608
|
-
|
|
38609
|
-
|
|
38610
|
-
|
|
38611
|
-
|
|
38612
|
-
|
|
39491
|
+
recordInteractiveShellEvent(sessionArtifact, {
|
|
39492
|
+
type: "shell_error",
|
|
39493
|
+
reason: message
|
|
39494
|
+
});
|
|
39495
|
+
busy = false;
|
|
39496
|
+
safePrintPrompt(rl);
|
|
39497
|
+
});
|
|
39498
|
+
});
|
|
39499
|
+
rl.on("SIGINT", () => {
|
|
39500
|
+
rl.close();
|
|
39501
|
+
});
|
|
39502
|
+
await new Promise((resolve15) => {
|
|
39503
|
+
rl.on("close", () => {
|
|
39504
|
+
process.stdout.write("\n");
|
|
39505
|
+
memory.history = rl.history.slice(0, 500);
|
|
39506
|
+
recordInteractiveShellEvent(sessionArtifact, { type: "shell_closed" });
|
|
39507
|
+
flushInteractiveSessionArtifact(sessionArtifact);
|
|
39508
|
+
flushInteractiveSessionReport(sessionArtifact);
|
|
39509
|
+
saveInteractiveShellMemory(memory);
|
|
39510
|
+
resolve15();
|
|
39511
|
+
});
|
|
39512
|
+
});
|
|
39513
|
+
});
|
|
38613
39514
|
}
|
|
38614
39515
|
|
|
38615
39516
|
// src/commands/home-actions.ts
|
|
@@ -38625,65 +39526,6 @@ async function promptChoice(prompt) {
|
|
|
38625
39526
|
return await promptLine(`
|
|
38626
39527
|
${prompt}`, { allowEmpty: true });
|
|
38627
39528
|
}
|
|
38628
|
-
function resolveSelection(input, state) {
|
|
38629
|
-
const normalized = input.trim().toLowerCase();
|
|
38630
|
-
if (!normalized || normalized === "q" || normalized === "quit" || normalized === "exit") {
|
|
38631
|
-
return { kind: "exit" };
|
|
38632
|
-
}
|
|
38633
|
-
if (normalized === "h" || normalized === "home" || normalized === "?") {
|
|
38634
|
-
return { kind: "redraw" };
|
|
38635
|
-
}
|
|
38636
|
-
if (normalized === "help" || normalized === "--help") {
|
|
38637
|
-
return { kind: "run", args: ["--help"] };
|
|
38638
|
-
}
|
|
38639
|
-
if (!state.authenticated) {
|
|
38640
|
-
if (normalized === "1" || normalized === "start" || normalized === "login" || normalized === "auth" || normalized === "auth login") {
|
|
38641
|
-
return { kind: "run", args: ["auth", "login"] };
|
|
38642
|
-
}
|
|
38643
|
-
if (normalized === "2" || normalized === "full help") {
|
|
38644
|
-
return { kind: "run", args: ["--help"] };
|
|
38645
|
-
}
|
|
38646
|
-
if (normalized === "3" || normalized === "auth login help") {
|
|
38647
|
-
return { kind: "run", args: ["auth", "login", "--help"] };
|
|
38648
|
-
}
|
|
38649
|
-
} else if (!state.orgId) {
|
|
38650
|
-
if (normalized === "1" || normalized === "org" || normalized === "org list") {
|
|
38651
|
-
return { kind: "run", args: ["org", "list"] };
|
|
38652
|
-
}
|
|
38653
|
-
if (normalized === "2" || normalized === "org use help") {
|
|
38654
|
-
return { kind: "run", args: ["org", "use", "--help"] };
|
|
38655
|
-
}
|
|
38656
|
-
if (normalized === "3" || normalized === "auth whoami" || normalized === "whoami") {
|
|
38657
|
-
return { kind: "run", args: ["auth", "whoami"] };
|
|
38658
|
-
}
|
|
38659
|
-
if (normalized === "4" || normalized === "full help") {
|
|
38660
|
-
return { kind: "run", args: ["--help"] };
|
|
38661
|
-
}
|
|
38662
|
-
} else {
|
|
38663
|
-
if (normalized === "1" || normalized === "tenant" || normalized === "tenant status") {
|
|
38664
|
-
return { kind: "run", args: ["tenant", "status"] };
|
|
38665
|
-
}
|
|
38666
|
-
if (normalized === "2" || normalized === "templates" || normalized === "template list" || normalized === "templates list") {
|
|
38667
|
-
return { kind: "run", args: ["templates", "list"] };
|
|
38668
|
-
}
|
|
38669
|
-
if (normalized === "3" || normalized === "agent" || normalized === "agent list") {
|
|
38670
|
-
return { kind: "run", args: ["agent", "list", "--segment", "all"] };
|
|
38671
|
-
}
|
|
38672
|
-
if (normalized === "4" || normalized === "setup" || normalized === "setup help") {
|
|
38673
|
-
return { kind: "run", args: ["setup", "--help"] };
|
|
38674
|
-
}
|
|
38675
|
-
if (normalized === "5" || normalized === "bug report" || normalized === "bug report help") {
|
|
38676
|
-
return { kind: "run", args: ["bug", "report", "--help"] };
|
|
38677
|
-
}
|
|
38678
|
-
if (normalized === "6" || normalized === "bug list") {
|
|
38679
|
-
return { kind: "run", args: ["bug", "list"] };
|
|
38680
|
-
}
|
|
38681
|
-
}
|
|
38682
|
-
const tokens = tokenizeInput(input);
|
|
38683
|
-
if (tokens.length > 0 && tokens[0].toLowerCase() === "foh") tokens.shift();
|
|
38684
|
-
if (tokens.length > 0) return { kind: "run", args: tokens };
|
|
38685
|
-
return { kind: "invalid" };
|
|
38686
|
-
}
|
|
38687
39529
|
async function runGuidedStart(apiUrlOverride, executeCommand) {
|
|
38688
39530
|
process.stdout.write("\nGuided Start\n");
|
|
38689
39531
|
process.stdout.write("------------\n");
|
|
@@ -38798,13 +39640,153 @@ async function chooseDefaultOrg(orgs) {
|
|
|
38798
39640
|
`);
|
|
38799
39641
|
}
|
|
38800
39642
|
}
|
|
38801
|
-
function
|
|
38802
|
-
return
|
|
39643
|
+
function getHomeQuickActions2(state) {
|
|
39644
|
+
return getHomeQuickActions(state);
|
|
39645
|
+
}
|
|
39646
|
+
|
|
39647
|
+
// src/tui/command-palette.ts
|
|
39648
|
+
function tokenizeInput2(value) {
|
|
39649
|
+
const tokens = [];
|
|
39650
|
+
const matcher = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
39651
|
+
let match = matcher.exec(value);
|
|
39652
|
+
while (match) {
|
|
39653
|
+
tokens.push(match[1] ?? match[2] ?? match[3] ?? "");
|
|
39654
|
+
match = matcher.exec(value);
|
|
39655
|
+
}
|
|
39656
|
+
return tokens;
|
|
39657
|
+
}
|
|
39658
|
+
function normalizeTypedCommandInput(raw) {
|
|
39659
|
+
const normalized = String(raw ?? "").trim();
|
|
39660
|
+
if (!normalized) return [];
|
|
39661
|
+
const withoutLeadingSlash = normalized.startsWith("/") ? normalized.slice(1) : normalized;
|
|
39662
|
+
const tokens = tokenizeInput2(withoutLeadingSlash);
|
|
39663
|
+
if (tokens.length > 0 && tokens[0].toLowerCase() === "foh") {
|
|
39664
|
+
tokens.shift();
|
|
39665
|
+
}
|
|
39666
|
+
return tokens;
|
|
39667
|
+
}
|
|
39668
|
+
function filterPaletteCommands(commands, query) {
|
|
39669
|
+
const normalized = String(query ?? "").trim().toLowerCase();
|
|
39670
|
+
if (!normalized) return commands;
|
|
39671
|
+
return commands.filter((entry) => {
|
|
39672
|
+
const commandText = `foh ${entry.args.join(" ")}`.toLowerCase();
|
|
39673
|
+
return entry.label.toLowerCase().includes(normalized) || commandText.includes(normalized);
|
|
39674
|
+
});
|
|
39675
|
+
}
|
|
39676
|
+
function shouldStartPaletteFromHome(char) {
|
|
39677
|
+
if (typeof char !== "string" || char.length !== 1) return false;
|
|
39678
|
+
if (/\s/.test(char)) return false;
|
|
39679
|
+
if (char === "/") return false;
|
|
39680
|
+
return /^[0-9A-Za-z._:-]$/.test(char);
|
|
39681
|
+
}
|
|
39682
|
+
|
|
39683
|
+
// src/commands/home-render.ts
|
|
39684
|
+
function getQuickActions(state) {
|
|
39685
|
+
return getHomeQuickActions(state);
|
|
39686
|
+
}
|
|
39687
|
+
function getPaletteCommands(state) {
|
|
39688
|
+
return getHomePaletteCommands(state);
|
|
39689
|
+
}
|
|
39690
|
+
function filterHomePaletteCommands(state, query) {
|
|
39691
|
+
const commands = getPaletteCommands(state);
|
|
39692
|
+
const commandIndex = new Map(commands.map((entry) => [entry.args.join(" "), entry]));
|
|
39693
|
+
const filtered = filterPaletteCommands(commands, query);
|
|
39694
|
+
return filtered.map((entry) => commandIndex.get(entry.args.join(" "))).filter((entry) => entry !== void 0);
|
|
39695
|
+
}
|
|
39696
|
+
function renderInteractiveHomeScreen({
|
|
39697
|
+
state,
|
|
39698
|
+
actions,
|
|
39699
|
+
selectedActionIndex,
|
|
39700
|
+
paletteOpen,
|
|
39701
|
+
paletteQuery,
|
|
39702
|
+
paletteSuggestions,
|
|
39703
|
+
selectedPaletteIndex,
|
|
39704
|
+
notice,
|
|
39705
|
+
isBusy
|
|
39706
|
+
}) {
|
|
39707
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
39708
|
+
process.stdout.write("Front Of House CLI Home\n");
|
|
39709
|
+
process.stdout.write("=======================\n\n");
|
|
39710
|
+
if (!state.authenticated) {
|
|
39711
|
+
process.stdout.write("Status: Not authenticated\n");
|
|
39712
|
+
} else {
|
|
39713
|
+
process.stdout.write("Status: Authenticated\n");
|
|
39714
|
+
process.stdout.write(`API: ${state.apiUrl}
|
|
39715
|
+
`);
|
|
39716
|
+
process.stdout.write(`Default org: ${state.orgId ?? "(not set)"}
|
|
39717
|
+
`);
|
|
39718
|
+
}
|
|
39719
|
+
process.stdout.write("\nQuick actions (Up/Down, j/k, Enter, 1-9):\n");
|
|
39720
|
+
actions.forEach((action, index) => {
|
|
39721
|
+
const isSelected = !paletteOpen && index === selectedActionIndex;
|
|
39722
|
+
const cursor = isSelected ? ">" : " ";
|
|
39723
|
+
process.stdout.write(` ${cursor} [${index + 1}] ${action.label}
|
|
39724
|
+
`);
|
|
39725
|
+
process.stdout.write(` ${action.description}
|
|
39726
|
+
`);
|
|
39727
|
+
});
|
|
39728
|
+
process.stdout.write("\nShortcuts: [/] palette [h] redraw [q] exit [Ctrl+C] exit\n");
|
|
39729
|
+
process.stdout.write("Tip: start typing any command (for example auth, help, org list) to search instantly.\n");
|
|
39730
|
+
if (paletteOpen) {
|
|
39731
|
+
process.stdout.write("\nCommand palette\n");
|
|
39732
|
+
process.stdout.write("---------------\n");
|
|
39733
|
+
process.stdout.write(`/${paletteQuery}
|
|
39734
|
+
`);
|
|
39735
|
+
if (paletteSuggestions.length === 0) {
|
|
39736
|
+
process.stdout.write(" No matches. Press Enter to run typed command, or Esc to close.\n");
|
|
39737
|
+
} else {
|
|
39738
|
+
paletteSuggestions.slice(0, 8).forEach((entry, index) => {
|
|
39739
|
+
const isSelected = index === selectedPaletteIndex;
|
|
39740
|
+
const cursor = isSelected ? ">" : " ";
|
|
39741
|
+
process.stdout.write(` ${cursor} ${entry.label}
|
|
39742
|
+
`);
|
|
39743
|
+
process.stdout.write(` foh ${entry.args.join(" ")}
|
|
39744
|
+
`);
|
|
39745
|
+
});
|
|
39746
|
+
}
|
|
39747
|
+
} else {
|
|
39748
|
+
process.stdout.write('\nPress "/" to open command palette.\n');
|
|
39749
|
+
}
|
|
39750
|
+
if (isBusy) {
|
|
39751
|
+
process.stdout.write("\nRunning command...\n");
|
|
39752
|
+
} else if (notice) {
|
|
39753
|
+
process.stdout.write(`
|
|
39754
|
+
${notice}
|
|
39755
|
+
`);
|
|
39756
|
+
}
|
|
39757
|
+
}
|
|
39758
|
+
function printHome(state) {
|
|
39759
|
+
process.stdout.write("\nFront Of House CLI Home\n");
|
|
39760
|
+
process.stdout.write("-----------------------\n");
|
|
39761
|
+
if (!state.authenticated) {
|
|
39762
|
+
process.stdout.write("Status: Not authenticated\n\n");
|
|
39763
|
+
} else {
|
|
39764
|
+
process.stdout.write(`Status: Authenticated
|
|
39765
|
+
API: ${state.apiUrl}
|
|
39766
|
+
`);
|
|
39767
|
+
process.stdout.write(`Default org: ${state.orgId ?? "(not set)"}
|
|
39768
|
+
|
|
39769
|
+
`);
|
|
39770
|
+
}
|
|
39771
|
+
const actions = getQuickActions(state);
|
|
39772
|
+
if (actions.length > 0) {
|
|
39773
|
+
process.stdout.write("Quick options:\n");
|
|
39774
|
+
actions.forEach((action, index) => {
|
|
39775
|
+
process.stdout.write(` [${index + 1}] ${action.label}
|
|
39776
|
+
`);
|
|
39777
|
+
});
|
|
39778
|
+
process.stdout.write(" [h] redraw home\n");
|
|
39779
|
+
process.stdout.write(" [q] exit\n");
|
|
39780
|
+
}
|
|
39781
|
+
const exampleAction = actions.find((entry) => entry.args.length > 0);
|
|
39782
|
+
const example = exampleAction ? `Type any foh command (example: ${exampleAction.args.join(" ")})` : "Type any foh command (example: --help)";
|
|
39783
|
+
process.stdout.write(` ${example}
|
|
39784
|
+
`);
|
|
38803
39785
|
}
|
|
38804
39786
|
|
|
38805
39787
|
// src/commands/home-interaction.ts
|
|
38806
|
-
var
|
|
38807
|
-
var
|
|
39788
|
+
var import_child_process3 = require("child_process");
|
|
39789
|
+
var import_readline3 = require("readline");
|
|
38808
39790
|
|
|
38809
39791
|
// src/tui/keypress.ts
|
|
38810
39792
|
function normalizeInputChar(inputChar) {
|
|
@@ -38852,13 +39834,13 @@ function resolveNumericSelection(inputChar, length) {
|
|
|
38852
39834
|
}
|
|
38853
39835
|
|
|
38854
39836
|
// src/commands/home-interaction.ts
|
|
38855
|
-
async function
|
|
39837
|
+
async function runSelf2(args, apiUrlOverride) {
|
|
38856
39838
|
const spawnArgs = [...args];
|
|
38857
39839
|
if (apiUrlOverride && !spawnArgs.includes("--api-url")) {
|
|
38858
39840
|
spawnArgs.push("--api-url", apiUrlOverride);
|
|
38859
39841
|
}
|
|
38860
|
-
return await new Promise((
|
|
38861
|
-
const child = (0,
|
|
39842
|
+
return await new Promise((resolve15, reject) => {
|
|
39843
|
+
const child = (0, import_child_process3.spawn)(process.execPath, [process.argv[1], ...spawnArgs], {
|
|
38862
39844
|
stdio: "inherit",
|
|
38863
39845
|
env: {
|
|
38864
39846
|
...process.env,
|
|
@@ -38867,7 +39849,7 @@ async function runSelf(args, apiUrlOverride) {
|
|
|
38867
39849
|
}
|
|
38868
39850
|
});
|
|
38869
39851
|
child.once("error", reject);
|
|
38870
|
-
child.once("close", (code) =>
|
|
39852
|
+
child.once("close", (code) => resolve15(typeof code === "number" ? code : 1));
|
|
38871
39853
|
});
|
|
38872
39854
|
}
|
|
38873
39855
|
function shouldUseInteractiveHome(argv) {
|
|
@@ -38880,7 +39862,7 @@ function numericSelection(inputChar, actionCount) {
|
|
|
38880
39862
|
return resolveNumericSelection(inputChar, actionCount);
|
|
38881
39863
|
}
|
|
38882
39864
|
function renderHome(state, selectedActionIndex, paletteOpen, paletteQuery, paletteSuggestions, selectedPaletteIndex, notice, isBusy) {
|
|
38883
|
-
const actions =
|
|
39865
|
+
const actions = getHomeQuickActions2(state);
|
|
38884
39866
|
renderInteractiveHomeScreen({
|
|
38885
39867
|
state,
|
|
38886
39868
|
actions,
|
|
@@ -38893,31 +39875,6 @@ function renderHome(state, selectedActionIndex, paletteOpen, paletteQuery, palet
|
|
|
38893
39875
|
isBusy
|
|
38894
39876
|
});
|
|
38895
39877
|
}
|
|
38896
|
-
async function runLegacyHomeLoop(initialState, apiUrlOverride, runCommand, promptChoice2) {
|
|
38897
|
-
let state = initialState;
|
|
38898
|
-
printHome(state);
|
|
38899
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) return;
|
|
38900
|
-
while (true) {
|
|
38901
|
-
const selected = await promptChoice2("Select option/key/command: ");
|
|
38902
|
-
const resolved = resolveSelection(selected, state);
|
|
38903
|
-
if (resolved.kind === "exit") return;
|
|
38904
|
-
if (resolved.kind === "redraw") {
|
|
38905
|
-
printHome(state);
|
|
38906
|
-
continue;
|
|
38907
|
-
}
|
|
38908
|
-
if (resolved.kind === "invalid") {
|
|
38909
|
-
process.stdout.write("Unknown selection. Use keys shown above, or type a foh command.\n");
|
|
38910
|
-
continue;
|
|
38911
|
-
}
|
|
38912
|
-
const code = await runCommand(resolved.args, apiUrlOverride);
|
|
38913
|
-
if (code !== 0) {
|
|
38914
|
-
process.stdout.write(`Command exited with code ${code}.
|
|
38915
|
-
`);
|
|
38916
|
-
}
|
|
38917
|
-
state = resolveHomeState(apiUrlOverride);
|
|
38918
|
-
printHome(state);
|
|
38919
|
-
}
|
|
38920
|
-
}
|
|
38921
39878
|
async function runKeyDrivenHomeLoop(apiUrlOverride, runCommand) {
|
|
38922
39879
|
const stateAccessor = () => resolveHomeState(apiUrlOverride);
|
|
38923
39880
|
let state = stateAccessor();
|
|
@@ -38929,7 +39886,7 @@ async function runKeyDrivenHomeLoop(apiUrlOverride, runCommand) {
|
|
|
38929
39886
|
let notice = null;
|
|
38930
39887
|
let isBusy = false;
|
|
38931
39888
|
let finished = false;
|
|
38932
|
-
let actions =
|
|
39889
|
+
let actions = getHomeQuickActions2(state);
|
|
38933
39890
|
const stdin = process.stdin;
|
|
38934
39891
|
const clampPaletteIndex = () => {
|
|
38935
39892
|
if (paletteSuggestions.length === 0) {
|
|
@@ -38942,7 +39899,7 @@ async function runKeyDrivenHomeLoop(apiUrlOverride, runCommand) {
|
|
|
38942
39899
|
}
|
|
38943
39900
|
};
|
|
38944
39901
|
const refreshDerivedState = () => {
|
|
38945
|
-
actions =
|
|
39902
|
+
actions = getHomeQuickActions2(state);
|
|
38946
39903
|
if (actions.length === 0) {
|
|
38947
39904
|
selectedActionIndex = 0;
|
|
38948
39905
|
} else if (selectedActionIndex >= actions.length) {
|
|
@@ -39106,7 +40063,7 @@ async function runKeyDrivenHomeLoop(apiUrlOverride, runCommand) {
|
|
|
39106
40063
|
if (stdin.isTTY) stdin.setRawMode(false);
|
|
39107
40064
|
};
|
|
39108
40065
|
const attachInput = () => {
|
|
39109
|
-
(0,
|
|
40066
|
+
(0, import_readline3.emitKeypressEvents)(stdin);
|
|
39110
40067
|
stdin.on("keypress", onKeypress);
|
|
39111
40068
|
if (stdin.isTTY) stdin.setRawMode(true);
|
|
39112
40069
|
stdin.resume();
|
|
@@ -39129,18 +40086,14 @@ function registerHome(program3) {
|
|
|
39129
40086
|
try {
|
|
39130
40087
|
const explicitStart = process.argv.slice(2).some((token) => token.toLowerCase() === "start");
|
|
39131
40088
|
if (explicitStart) {
|
|
39132
|
-
await runGuidedStart(opts.apiUrl,
|
|
40089
|
+
await runGuidedStart(opts.apiUrl, runSelf2);
|
|
39133
40090
|
}
|
|
39134
40091
|
const state = resolveHomeState(opts.apiUrl);
|
|
39135
40092
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
39136
40093
|
printHome(state);
|
|
39137
40094
|
return;
|
|
39138
40095
|
}
|
|
39139
|
-
|
|
39140
|
-
await runLegacyHomeLoop(state, opts.apiUrl, runSelf, (prompt) => promptChoice(prompt));
|
|
39141
|
-
return;
|
|
39142
|
-
}
|
|
39143
|
-
await runKeyDrivenHomeLoop(opts.apiUrl, runSelf);
|
|
40096
|
+
await runKeyDrivenHomeLoop(opts.apiUrl, runSelf2);
|
|
39144
40097
|
} catch (e) {
|
|
39145
40098
|
if (e instanceof FohError) {
|
|
39146
40099
|
formatError(e);
|
|
@@ -39157,9 +40110,9 @@ function maybeDefaultToHome(argv = process.argv) {
|
|
|
39157
40110
|
}
|
|
39158
40111
|
|
|
39159
40112
|
// src/lib/update.ts
|
|
39160
|
-
var
|
|
39161
|
-
var
|
|
39162
|
-
var
|
|
40113
|
+
var import_fs12 = require("fs");
|
|
40114
|
+
var import_path10 = require("path");
|
|
40115
|
+
var import_child_process4 = require("child_process");
|
|
39163
40116
|
var import_crypto5 = require("crypto");
|
|
39164
40117
|
function parseSemver(version2) {
|
|
39165
40118
|
const trimmed = String(version2 ?? "").trim();
|
|
@@ -39179,7 +40132,7 @@ function compareSemver(a, b) {
|
|
|
39179
40132
|
}
|
|
39180
40133
|
function readPackageJsonVersion(path2) {
|
|
39181
40134
|
try {
|
|
39182
|
-
const raw = (0,
|
|
40135
|
+
const raw = (0, import_fs12.readFileSync)(path2, "utf-8");
|
|
39183
40136
|
const parsed = JSON.parse(raw);
|
|
39184
40137
|
const version2 = String(parsed.version ?? "").trim();
|
|
39185
40138
|
return version2 || void 0;
|
|
@@ -39188,13 +40141,13 @@ function readPackageJsonVersion(path2) {
|
|
|
39188
40141
|
}
|
|
39189
40142
|
}
|
|
39190
40143
|
function findRepoRoot(startCwd = process.cwd()) {
|
|
39191
|
-
let current = (0,
|
|
40144
|
+
let current = (0, import_path10.resolve)(startCwd);
|
|
39192
40145
|
while (true) {
|
|
39193
|
-
const rootPackageJsonPath = (0,
|
|
39194
|
-
const cliPackageJsonPath = (0,
|
|
39195
|
-
if ((0,
|
|
40146
|
+
const rootPackageJsonPath = (0, import_path10.join)(current, "package.json");
|
|
40147
|
+
const cliPackageJsonPath = (0, import_path10.join)(current, "packages", "cli", "package.json");
|
|
40148
|
+
if ((0, import_fs12.existsSync)(rootPackageJsonPath) && (0, import_fs12.existsSync)(cliPackageJsonPath)) {
|
|
39196
40149
|
try {
|
|
39197
|
-
const raw = (0,
|
|
40150
|
+
const raw = (0, import_fs12.readFileSync)(rootPackageJsonPath, "utf-8");
|
|
39198
40151
|
const parsed = JSON.parse(raw);
|
|
39199
40152
|
if (String(parsed.name ?? "").trim() === "front-of-house") {
|
|
39200
40153
|
return current;
|
|
@@ -39202,7 +40155,7 @@ function findRepoRoot(startCwd = process.cwd()) {
|
|
|
39202
40155
|
} catch {
|
|
39203
40156
|
}
|
|
39204
40157
|
}
|
|
39205
|
-
const parent = (0,
|
|
40158
|
+
const parent = (0, import_path10.dirname)(current);
|
|
39206
40159
|
if (parent === current) return void 0;
|
|
39207
40160
|
current = parent;
|
|
39208
40161
|
}
|
|
@@ -39216,7 +40169,7 @@ function detectUpdateAvailability(currentVersion, cwd = process.cwd()) {
|
|
|
39216
40169
|
remediation: "Run this command from the Front Of House repo root to compare/install the latest CLI."
|
|
39217
40170
|
};
|
|
39218
40171
|
}
|
|
39219
|
-
const cliPackageJsonPath = (0,
|
|
40172
|
+
const cliPackageJsonPath = (0, import_path10.join)(repoRoot, "packages", "cli", "package.json");
|
|
39220
40173
|
const latestVersion = readPackageJsonVersion(cliPackageJsonPath);
|
|
39221
40174
|
if (!latestVersion) {
|
|
39222
40175
|
return {
|
|
@@ -39243,20 +40196,20 @@ function detectUpdateAvailability(currentVersion, cwd = process.cwd()) {
|
|
|
39243
40196
|
};
|
|
39244
40197
|
}
|
|
39245
40198
|
async function applyRepoUpdate(repoRoot) {
|
|
39246
|
-
const scriptPath = (0,
|
|
40199
|
+
const scriptPath = (0, import_path10.join)(repoRoot, "scripts", "Install-FohCli.ps1");
|
|
39247
40200
|
if (process.platform === "win32") {
|
|
39248
|
-
return await new Promise((
|
|
39249
|
-
const child = (0,
|
|
40201
|
+
return await new Promise((resolve15, reject) => {
|
|
40202
|
+
const child = (0, import_child_process4.spawn)(
|
|
39250
40203
|
"powershell",
|
|
39251
40204
|
["-ExecutionPolicy", "Bypass", "-File", scriptPath],
|
|
39252
40205
|
{ stdio: "inherit" }
|
|
39253
40206
|
);
|
|
39254
40207
|
child.once("error", reject);
|
|
39255
|
-
child.once("close", (code) =>
|
|
40208
|
+
child.once("close", (code) => resolve15(typeof code === "number" ? code : 1));
|
|
39256
40209
|
});
|
|
39257
40210
|
}
|
|
39258
|
-
return await new Promise((
|
|
39259
|
-
const child = (0,
|
|
40211
|
+
return await new Promise((resolve15, reject) => {
|
|
40212
|
+
const child = (0, import_child_process4.spawn)(
|
|
39260
40213
|
"corepack",
|
|
39261
40214
|
["pnpm", "cli:install:global"],
|
|
39262
40215
|
{
|
|
@@ -39265,7 +40218,7 @@ async function applyRepoUpdate(repoRoot) {
|
|
|
39265
40218
|
}
|
|
39266
40219
|
);
|
|
39267
40220
|
child.once("error", reject);
|
|
39268
|
-
child.once("close", (code) =>
|
|
40221
|
+
child.once("close", (code) => resolve15(typeof code === "number" ? code : 1));
|
|
39269
40222
|
});
|
|
39270
40223
|
}
|
|
39271
40224
|
function shouldShowUpdateNotice(argv = process.argv) {
|
|
@@ -39279,7 +40232,7 @@ function shouldShowUpdateNotice(argv = process.argv) {
|
|
|
39279
40232
|
}
|
|
39280
40233
|
function hashFileSha256(filePath) {
|
|
39281
40234
|
try {
|
|
39282
|
-
const bytes = (0,
|
|
40235
|
+
const bytes = (0, import_fs12.readFileSync)(filePath);
|
|
39283
40236
|
return (0, import_crypto5.createHash)("sha256").update(bytes).digest("hex");
|
|
39284
40237
|
} catch {
|
|
39285
40238
|
return void 0;
|
|
@@ -39289,10 +40242,10 @@ function verifyCliArtifactIntegrity(params = {}) {
|
|
|
39289
40242
|
const cwd = params.cwd ?? process.cwd();
|
|
39290
40243
|
const argv = params.argv ?? process.argv;
|
|
39291
40244
|
const expectedSha256 = String(params.expectedSha256 ?? "").trim().toLowerCase() || void 0;
|
|
39292
|
-
const runtimePath = (0,
|
|
40245
|
+
const runtimePath = (0, import_path10.resolve)(String(argv[1] || ""));
|
|
39293
40246
|
const runtimeHash = runtimePath ? hashFileSha256(runtimePath) : void 0;
|
|
39294
40247
|
const warnings = [];
|
|
39295
|
-
if (!runtimePath || !(0,
|
|
40248
|
+
if (!runtimePath || !(0, import_fs12.existsSync)(runtimePath)) {
|
|
39296
40249
|
warnings.push("runtime_path_unreadable");
|
|
39297
40250
|
}
|
|
39298
40251
|
if (!runtimeHash) {
|
|
@@ -39310,8 +40263,8 @@ function verifyCliArtifactIntegrity(params = {}) {
|
|
|
39310
40263
|
let repoDistHash;
|
|
39311
40264
|
let runtimeMatchesRepoDist;
|
|
39312
40265
|
if (repoRoot) {
|
|
39313
|
-
repoDistPath = (0,
|
|
39314
|
-
if ((0,
|
|
40266
|
+
repoDistPath = (0, import_path10.join)(repoRoot, "packages", "cli", "dist", "foh.js");
|
|
40267
|
+
if ((0, import_fs12.existsSync)(repoDistPath)) {
|
|
39315
40268
|
repoDistHash = hashFileSha256(repoDistPath);
|
|
39316
40269
|
if (runtimeHash && repoDistHash) {
|
|
39317
40270
|
runtimeMatchesRepoDist = runtimeHash === repoDistHash;
|
|
@@ -39401,13 +40354,13 @@ function registerUpdate(program3) {
|
|
|
39401
40354
|
}
|
|
39402
40355
|
|
|
39403
40356
|
// src/commands/eval.ts
|
|
39404
|
-
var
|
|
39405
|
-
var
|
|
39406
|
-
var
|
|
40357
|
+
var import_fs21 = require("fs");
|
|
40358
|
+
var import_path20 = require("path");
|
|
40359
|
+
var import_child_process7 = require("child_process");
|
|
39407
40360
|
|
|
39408
40361
|
// src/lib/external-agent-artifact-safety.ts
|
|
39409
|
-
var
|
|
39410
|
-
var
|
|
40362
|
+
var import_fs13 = require("fs");
|
|
40363
|
+
var import_path11 = require("path");
|
|
39411
40364
|
var DEFAULT_MAX_BYTES_PER_FILE = 5 * 1024 * 1024;
|
|
39412
40365
|
var TEXT_ARTIFACT_NAMES = /* @__PURE__ */ new Set([
|
|
39413
40366
|
"codex-events.jsonl",
|
|
@@ -39431,7 +40384,7 @@ function escapeRegExp(value) {
|
|
|
39431
40384
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
39432
40385
|
}
|
|
39433
40386
|
function pathVariants(path2) {
|
|
39434
|
-
const resolved = (0,
|
|
40387
|
+
const resolved = (0, import_path11.resolve)(path2);
|
|
39435
40388
|
const slash = resolved.replace(/\\/g, "/");
|
|
39436
40389
|
const backslash = resolved.replace(/\//g, "\\");
|
|
39437
40390
|
return Array.from(new Set([
|
|
@@ -39474,9 +40427,9 @@ function redactExternalAgentSecretText(text) {
|
|
|
39474
40427
|
return text.replace(SECRET_QUERY_PARAM_RE, "$1[redacted_secret]").replace(SECRET_RE2, "[redacted_secret]");
|
|
39475
40428
|
}
|
|
39476
40429
|
function artifactFiles(runDir) {
|
|
39477
|
-
if (!(0,
|
|
39478
|
-
return (0,
|
|
39479
|
-
const stat = (0,
|
|
40430
|
+
if (!(0, import_fs13.existsSync)(runDir)) return [];
|
|
40431
|
+
return (0, import_fs13.readdirSync)(runDir).map((name) => (0, import_path11.join)(runDir, name)).filter((path2) => {
|
|
40432
|
+
const stat = (0, import_fs13.statSync)(path2);
|
|
39480
40433
|
const name = path2.split(/[\\/]/).pop() || "";
|
|
39481
40434
|
if (name.endsWith(".redacted")) return false;
|
|
39482
40435
|
return stat.isFile() && (TEXT_ARTIFACT_NAMES.has(name) || name.startsWith("command-output-cmd_"));
|
|
@@ -39502,15 +40455,15 @@ function scanText(input) {
|
|
|
39502
40455
|
].filter(Boolean);
|
|
39503
40456
|
}
|
|
39504
40457
|
function scanExternalAgentArtifacts(options) {
|
|
39505
|
-
const runDir = (0,
|
|
39506
|
-
const privateRepoRoot = options.privateRepoRoot ? (0,
|
|
40458
|
+
const runDir = (0, import_path11.resolve)(options.runDir);
|
|
40459
|
+
const privateRepoRoot = options.privateRepoRoot ? (0, import_path11.resolve)(options.privateRepoRoot) : void 0;
|
|
39507
40460
|
const homeDir = options.homeDir || process.env.USERPROFILE || process.env.HOME;
|
|
39508
40461
|
const maxBytes = options.maxBytesPerFile ?? DEFAULT_MAX_BYTES_PER_FILE;
|
|
39509
40462
|
const files = artifactFiles(runDir);
|
|
39510
40463
|
const findings = [];
|
|
39511
40464
|
const redactedFiles = [];
|
|
39512
40465
|
for (const file2 of files) {
|
|
39513
|
-
const stat = (0,
|
|
40466
|
+
const stat = (0, import_fs13.statSync)(file2);
|
|
39514
40467
|
if (stat.size > maxBytes) {
|
|
39515
40468
|
findings.push({
|
|
39516
40469
|
kind: "artifact_too_large",
|
|
@@ -39520,12 +40473,12 @@ function scanExternalAgentArtifacts(options) {
|
|
|
39520
40473
|
});
|
|
39521
40474
|
continue;
|
|
39522
40475
|
}
|
|
39523
|
-
const text = (0,
|
|
40476
|
+
const text = (0, import_fs13.readFileSync)(file2, "utf8");
|
|
39524
40477
|
findings.push(...scanText({ text, file: file2, privateRepoRoot, homeDir }));
|
|
39525
40478
|
if (options.writeRedacted) {
|
|
39526
40479
|
const redacted = redactExternalAgentArtifactText(text, { privateRepoRoot, homeDir });
|
|
39527
40480
|
const out = `${file2}.redacted`;
|
|
39528
|
-
(0,
|
|
40481
|
+
(0, import_fs13.writeFileSync)(out, redacted, "utf8");
|
|
39529
40482
|
redactedFiles.push(out);
|
|
39530
40483
|
}
|
|
39531
40484
|
}
|
|
@@ -39546,8 +40499,8 @@ function scanExternalAgentArtifacts(options) {
|
|
|
39546
40499
|
}
|
|
39547
40500
|
|
|
39548
40501
|
// src/lib/external-agent-capture.ts
|
|
39549
|
-
var
|
|
39550
|
-
var
|
|
40502
|
+
var import_fs14 = require("fs");
|
|
40503
|
+
var import_path12 = require("path");
|
|
39551
40504
|
var EXTERNAL_AGENT_RUN_DIR_ENV = "FOH_EXTERNAL_AGENT_RUN_DIR";
|
|
39552
40505
|
var EXTERNAL_AGENT_PROMPT_VERSION_ENV = "FOH_EXTERNAL_AGENT_PROMPT_VERSION";
|
|
39553
40506
|
var SECRET_RE3 = /\b(?:Bearer\s+)?(?:sk|pk|xai|whsec|EAAN|ghp|gho|github_pat|npm_)[A-Za-z0-9_\-.]{12,}\b/gi;
|
|
@@ -39566,7 +40519,7 @@ function safeJsonLine(value) {
|
|
|
39566
40519
|
function getExternalAgentRunDir() {
|
|
39567
40520
|
const raw = process.env[EXTERNAL_AGENT_RUN_DIR_ENV];
|
|
39568
40521
|
if (!raw || !raw.trim()) return null;
|
|
39569
|
-
return (0,
|
|
40522
|
+
return (0, import_path12.resolve)(raw);
|
|
39570
40523
|
}
|
|
39571
40524
|
function commandIdFor(input) {
|
|
39572
40525
|
const seed = `${input.startedAt}:${input.argv.join("\0")}:${process.pid}`;
|
|
@@ -39617,7 +40570,7 @@ function recordExternalAgentCliInvocation(input) {
|
|
|
39617
40570
|
const runDir = getExternalAgentRunDir();
|
|
39618
40571
|
if (!runDir) return null;
|
|
39619
40572
|
try {
|
|
39620
|
-
(0,
|
|
40573
|
+
(0, import_fs14.mkdirSync)(runDir, { recursive: true });
|
|
39621
40574
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
39622
40575
|
const args = input.argv.slice(2).map((arg) => redactText(String(arg)));
|
|
39623
40576
|
const commandId = commandIdFor({ startedAt, argv: args });
|
|
@@ -39636,7 +40589,7 @@ function recordExternalAgentCliInvocation(input) {
|
|
|
39636
40589
|
prompt_version: promptVersion,
|
|
39637
40590
|
started_at: startedAt
|
|
39638
40591
|
};
|
|
39639
|
-
(0,
|
|
40592
|
+
(0, import_fs14.appendFileSync)((0, import_path12.join)(runDir, "commands.ndjson"), safeJsonLine(record2), "utf8");
|
|
39640
40593
|
return {
|
|
39641
40594
|
commandId,
|
|
39642
40595
|
runDir,
|
|
@@ -39697,7 +40650,7 @@ function completeExternalAgentCliInvocation(capture, input) {
|
|
|
39697
40650
|
let artifact = null;
|
|
39698
40651
|
if (output.trim()) {
|
|
39699
40652
|
artifact = outputArtifactName(capture.commandId);
|
|
39700
|
-
(0,
|
|
40653
|
+
(0, import_fs14.writeFileSync)((0, import_path12.join)(capture.runDir, artifact), output, "utf8");
|
|
39701
40654
|
}
|
|
39702
40655
|
const record2 = {
|
|
39703
40656
|
schema_version: "external_agent_cli_command.v2",
|
|
@@ -39720,12 +40673,12 @@ function completeExternalAgentCliInvocation(capture, input) {
|
|
|
39720
40673
|
output_artifact: artifact,
|
|
39721
40674
|
output_bytes: Buffer.byteLength(output, "utf8")
|
|
39722
40675
|
};
|
|
39723
|
-
(0,
|
|
40676
|
+
(0, import_fs14.appendFileSync)((0, import_path12.join)(capture.runDir, "commands.ndjson"), safeJsonLine(record2), "utf8");
|
|
39724
40677
|
}
|
|
39725
40678
|
function readCommandRecords(runDir) {
|
|
39726
|
-
const commandLogPath = (0,
|
|
39727
|
-
if (!(0,
|
|
39728
|
-
const records = (0,
|
|
40679
|
+
const commandLogPath = (0, import_path12.join)(runDir, "commands.ndjson");
|
|
40680
|
+
if (!(0, import_fs14.existsSync)(commandLogPath)) return [];
|
|
40681
|
+
const records = (0, import_fs14.readFileSync)(commandLogPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
|
|
39729
40682
|
const byId = /* @__PURE__ */ new Map();
|
|
39730
40683
|
const ordered = [];
|
|
39731
40684
|
for (const record2 of records) {
|
|
@@ -39737,13 +40690,13 @@ function readCommandRecords(runDir) {
|
|
|
39737
40690
|
}
|
|
39738
40691
|
|
|
39739
40692
|
// src/lib/external-agent-executor.ts
|
|
39740
|
-
var
|
|
39741
|
-
var
|
|
39742
|
-
var
|
|
39743
|
-
var
|
|
40693
|
+
var import_fs20 = require("fs");
|
|
40694
|
+
var import_os3 = require("os");
|
|
40695
|
+
var import_path19 = require("path");
|
|
40696
|
+
var import_child_process6 = require("child_process");
|
|
39744
40697
|
|
|
39745
40698
|
// src/lib/external-agent-executor-env.ts
|
|
39746
|
-
var
|
|
40699
|
+
var import_path13 = require("path");
|
|
39747
40700
|
var CODEX_EXECUTOR_DENIED_ENV_PREFIXES = [
|
|
39748
40701
|
"SUPABASE_",
|
|
39749
40702
|
"DATABASE_",
|
|
@@ -39807,7 +40760,7 @@ function buildCodexExecutorEnv(input) {
|
|
|
39807
40760
|
env[childKey] = value;
|
|
39808
40761
|
}
|
|
39809
40762
|
}
|
|
39810
|
-
env.npm_config_cache = (0,
|
|
40763
|
+
env.npm_config_cache = (0, import_path13.join)((0, import_path13.dirname)(input.runDir), ".npm-cache");
|
|
39811
40764
|
env.npm_config_prefer_online = "true";
|
|
39812
40765
|
env.npm_config_update_notifier = "false";
|
|
39813
40766
|
env.npm_config_yes = "true";
|
|
@@ -39819,12 +40772,12 @@ function buildCodexExecutorEnv(input) {
|
|
|
39819
40772
|
}
|
|
39820
40773
|
|
|
39821
40774
|
// src/lib/external-agent-executor-artifacts.ts
|
|
39822
|
-
var
|
|
39823
|
-
var
|
|
40775
|
+
var import_fs16 = require("fs");
|
|
40776
|
+
var import_path15 = require("path");
|
|
39824
40777
|
|
|
39825
40778
|
// src/lib/external-agent-metadata.ts
|
|
39826
|
-
var
|
|
39827
|
-
var
|
|
40779
|
+
var import_fs15 = require("fs");
|
|
40780
|
+
var import_path14 = require("path");
|
|
39828
40781
|
var EXTERNAL_AGENT_METADATA_FILENAMES = [
|
|
39829
40782
|
"external-agent-metadata.json",
|
|
39830
40783
|
"agent-metadata.json"
|
|
@@ -39846,10 +40799,10 @@ function collectDocsFrom(value, docs) {
|
|
|
39846
40799
|
}
|
|
39847
40800
|
function readExternalAgentMetadata(runDir) {
|
|
39848
40801
|
for (const filename of EXTERNAL_AGENT_METADATA_FILENAMES) {
|
|
39849
|
-
const path2 = (0,
|
|
39850
|
-
if (!(0,
|
|
40802
|
+
const path2 = (0, import_path14.join)(runDir, filename);
|
|
40803
|
+
if (!(0, import_fs15.existsSync)(path2)) continue;
|
|
39851
40804
|
try {
|
|
39852
|
-
const parsed = JSON.parse((0,
|
|
40805
|
+
const parsed = JSON.parse((0, import_fs15.readFileSync)(path2, "utf8"));
|
|
39853
40806
|
const docs = /* @__PURE__ */ new Set();
|
|
39854
40807
|
collectDocsFrom(parsed.docs_pages_used, docs);
|
|
39855
40808
|
collectDocsFrom(parsed.docs_pages_observed, docs);
|
|
@@ -39878,44 +40831,44 @@ function readExternalAgentMetadata(runDir) {
|
|
|
39878
40831
|
|
|
39879
40832
|
// src/lib/external-agent-executor-artifacts.ts
|
|
39880
40833
|
function redactArtifactFile(path2, input = {}) {
|
|
39881
|
-
if (!(0,
|
|
39882
|
-
const original = (0,
|
|
40834
|
+
if (!(0, import_fs16.existsSync)(path2)) return;
|
|
40835
|
+
const original = (0, import_fs16.readFileSync)(path2, "utf8");
|
|
39883
40836
|
const redacted = redactExternalAgentArtifactText(original, input);
|
|
39884
|
-
if (redacted !== original) (0,
|
|
40837
|
+
if (redacted !== original) (0, import_fs16.writeFileSync)(path2, redacted, "utf8");
|
|
39885
40838
|
}
|
|
39886
40839
|
function redactExternalAgentOutputArtifacts(run, input = {}) {
|
|
39887
40840
|
redactArtifactFile(run.outputs.jsonl, input);
|
|
39888
40841
|
redactArtifactFile(run.outputs.last_message, input);
|
|
39889
40842
|
redactArtifactFile(run.outputs.stderr, input);
|
|
39890
|
-
redactArtifactFile((0,
|
|
39891
|
-
if (!(0,
|
|
39892
|
-
for (const name of (0,
|
|
40843
|
+
redactArtifactFile((0, import_path15.join)(run.run_dir, "commands.ndjson"), input);
|
|
40844
|
+
if (!(0, import_fs16.existsSync)(run.run_dir)) return;
|
|
40845
|
+
for (const name of (0, import_fs16.readdirSync)(run.run_dir)) {
|
|
39893
40846
|
if (name.startsWith("command-output-cmd_") && !name.endsWith(".redacted")) {
|
|
39894
|
-
redactArtifactFile((0,
|
|
40847
|
+
redactArtifactFile((0, import_path15.join)(run.run_dir, name), input);
|
|
39895
40848
|
}
|
|
39896
40849
|
}
|
|
39897
40850
|
}
|
|
39898
40851
|
function copyExternalAgentCommandCaptureArtifacts(input) {
|
|
39899
|
-
const commandLog = (0,
|
|
39900
|
-
if ((0,
|
|
39901
|
-
(0,
|
|
40852
|
+
const commandLog = (0, import_path15.join)(input.captureDir, "commands.ndjson");
|
|
40853
|
+
if ((0, import_fs16.existsSync)(commandLog)) {
|
|
40854
|
+
(0, import_fs16.writeFileSync)((0, import_path15.join)(input.runDir, "commands.ndjson"), (0, import_fs16.readFileSync)(commandLog, "utf8"), "utf8");
|
|
39902
40855
|
}
|
|
39903
|
-
for (const name of (0,
|
|
40856
|
+
for (const name of (0, import_fs16.readdirSync)(input.captureDir)) {
|
|
39904
40857
|
if (name.startsWith("command-output-cmd_")) {
|
|
39905
|
-
(0,
|
|
40858
|
+
(0, import_fs16.copyFileSync)((0, import_path15.join)(input.captureDir, name), (0, import_path15.join)(input.runDir, name));
|
|
39906
40859
|
} else if (EXTERNAL_AGENT_METADATA_FILENAMES.includes(name)) {
|
|
39907
|
-
(0,
|
|
40860
|
+
(0, import_fs16.copyFileSync)((0, import_path15.join)(input.captureDir, name), (0, import_path15.join)(input.runDir, name));
|
|
39908
40861
|
}
|
|
39909
40862
|
}
|
|
39910
40863
|
}
|
|
39911
40864
|
|
|
39912
40865
|
// src/lib/external-agent-executor-classification.ts
|
|
39913
|
-
var
|
|
39914
|
-
var
|
|
40866
|
+
var import_fs18 = require("fs");
|
|
40867
|
+
var import_path17 = require("path");
|
|
39915
40868
|
|
|
39916
40869
|
// src/lib/external-agent-run-summary.ts
|
|
39917
|
-
var
|
|
39918
|
-
var
|
|
40870
|
+
var import_fs17 = require("fs");
|
|
40871
|
+
var import_path16 = require("path");
|
|
39919
40872
|
var REQUIRED_RUN_FIELDS = [
|
|
39920
40873
|
"schema_version",
|
|
39921
40874
|
"run_id",
|
|
@@ -39939,8 +40892,8 @@ function quoteShellArg(value) {
|
|
|
39939
40892
|
return `"${text.replace(/(["$`])/g, "\\$1")}"`;
|
|
39940
40893
|
}
|
|
39941
40894
|
function externalAgentSummaryCommand(root) {
|
|
39942
|
-
const summaryPath = (0,
|
|
39943
|
-
const reportPath = (0,
|
|
40895
|
+
const summaryPath = (0, import_path16.join)(root, "latest-summary.json");
|
|
40896
|
+
const reportPath = (0, import_path16.join)(root, "summary.report.json");
|
|
39944
40897
|
return [
|
|
39945
40898
|
"foh",
|
|
39946
40899
|
"eval",
|
|
@@ -39956,11 +40909,11 @@ function externalAgentSummaryCommand(root) {
|
|
|
39956
40909
|
].join(" ");
|
|
39957
40910
|
}
|
|
39958
40911
|
function readJson(filePath) {
|
|
39959
|
-
return JSON.parse((0,
|
|
40912
|
+
return JSON.parse((0, import_fs17.readFileSync)(filePath, "utf8").replace(/^\uFEFF/, ""));
|
|
39960
40913
|
}
|
|
39961
40914
|
function readNdjson(filePath) {
|
|
39962
|
-
if (!(0,
|
|
39963
|
-
return (0,
|
|
40915
|
+
if (!(0, import_fs17.existsSync)(filePath)) return [];
|
|
40916
|
+
return (0, import_fs17.readFileSync)(filePath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
39964
40917
|
try {
|
|
39965
40918
|
const parsed = JSON.parse(line);
|
|
39966
40919
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
@@ -39986,7 +40939,7 @@ function collectDocUrls(text) {
|
|
|
39986
40939
|
return Array.from(new Set((String(text || "").match(DOC_URL_RE) || []).map((url2) => url2.replace(/[.?!:]+$/g, "")).filter((url2) => url2.startsWith("https://frontofhouse.okii.uk/")))).sort();
|
|
39987
40940
|
}
|
|
39988
40941
|
function findRunCandidates(root) {
|
|
39989
|
-
if (!(0,
|
|
40942
|
+
if (!(0, import_fs17.existsSync)(root)) return [];
|
|
39990
40943
|
const candidates = [];
|
|
39991
40944
|
const seenRunDirs = /* @__PURE__ */ new Set();
|
|
39992
40945
|
const captureDirs = [];
|
|
@@ -39994,13 +40947,13 @@ function findRunCandidates(root) {
|
|
|
39994
40947
|
while (stack.length > 0) {
|
|
39995
40948
|
const current = stack.pop();
|
|
39996
40949
|
if (!current) continue;
|
|
39997
|
-
for (const entry of (0,
|
|
39998
|
-
const absolute = (0,
|
|
40950
|
+
for (const entry of (0, import_fs17.readdirSync)(current, { withFileTypes: true })) {
|
|
40951
|
+
const absolute = (0, import_path16.join)(current, entry.name);
|
|
39999
40952
|
if (entry.isDirectory()) {
|
|
40000
40953
|
stack.push(absolute);
|
|
40001
40954
|
} else if (entry.isFile() && entry.name === "run.json") {
|
|
40002
40955
|
candidates.push({ path: absolute, synthetic: false });
|
|
40003
|
-
seenRunDirs.add((0,
|
|
40956
|
+
seenRunDirs.add((0, import_path16.dirname)(absolute));
|
|
40004
40957
|
} else if (entry.isFile() && entry.name === "commands.ndjson") {
|
|
40005
40958
|
captureDirs.push(current);
|
|
40006
40959
|
}
|
|
@@ -40008,7 +40961,7 @@ function findRunCandidates(root) {
|
|
|
40008
40961
|
}
|
|
40009
40962
|
for (const captureDir of captureDirs) {
|
|
40010
40963
|
if (seenRunDirs.has(captureDir)) continue;
|
|
40011
|
-
candidates.push({ path: (0,
|
|
40964
|
+
candidates.push({ path: (0, import_path16.join)(captureDir, "run.json"), synthetic: true });
|
|
40012
40965
|
}
|
|
40013
40966
|
return candidates.sort((a, b) => a.path.localeCompare(b.path));
|
|
40014
40967
|
}
|
|
@@ -40086,9 +41039,9 @@ function syntheticStatusFromCommands(commands) {
|
|
|
40086
41039
|
return { status: "pass", reasonCode: null };
|
|
40087
41040
|
}
|
|
40088
41041
|
function synthesizeRunFromCapture(runPath) {
|
|
40089
|
-
const runDir = (0,
|
|
40090
|
-
const commands = collapseCommandRecords(readNdjson((0,
|
|
40091
|
-
const metadata = asObject((0,
|
|
41042
|
+
const runDir = (0, import_path16.dirname)(runPath);
|
|
41043
|
+
const commands = collapseCommandRecords(readNdjson((0, import_path16.join)(runDir, "commands.ndjson")));
|
|
41044
|
+
const metadata = asObject((0, import_fs17.existsSync)((0, import_path16.join)(runDir, "external-agent-metadata.json")) ? readJson((0, import_path16.join)(runDir, "external-agent-metadata.json")) : {});
|
|
40092
41045
|
const blockerCodes = toArray2(metadata?.blocker_reason_codes).map(String).filter(Boolean);
|
|
40093
41046
|
const commandClassification = syntheticStatusFromCommands(commands);
|
|
40094
41047
|
const status = commandClassification.status === "fail" ? "fail" : blockerCodes.length > 0 ? "hold" : commandClassification.status;
|
|
@@ -40097,7 +41050,7 @@ function synthesizeRunFromCapture(runPath) {
|
|
|
40097
41050
|
const startedAt = firstCommandTime(commands) || (/* @__PURE__ */ new Date(0)).toISOString();
|
|
40098
41051
|
const endedAt = latestCommandTime(commands) || startedAt;
|
|
40099
41052
|
const docs = toArray2(metadata?.docs_pages_used).map(String).filter(Boolean);
|
|
40100
|
-
const runId = (0,
|
|
41053
|
+
const runId = (0, import_path16.dirname)(runPath).split(/[\\/]/).filter(Boolean).slice(-3).join("-") || "capture-only-run";
|
|
40101
41054
|
return {
|
|
40102
41055
|
schema_version: "external_agent_run.v1",
|
|
40103
41056
|
run_id: runId,
|
|
@@ -40121,14 +41074,14 @@ function synthesizeRunFromCapture(runPath) {
|
|
|
40121
41074
|
docs_pages_used: docs,
|
|
40122
41075
|
artifacts: {
|
|
40123
41076
|
command_log: "commands.ndjson",
|
|
40124
|
-
agent_metadata: (0,
|
|
41077
|
+
agent_metadata: (0, import_fs17.existsSync)((0, import_path16.join)(runDir, "external-agent-metadata.json")) ? "external-agent-metadata.json" : null,
|
|
40125
41078
|
capture_only: true
|
|
40126
41079
|
},
|
|
40127
41080
|
summary: "Synthetic run record created from external-agent capture artifacts because run.json was missing."
|
|
40128
41081
|
};
|
|
40129
41082
|
}
|
|
40130
41083
|
function cohortIdForRunPath(root, runPath) {
|
|
40131
|
-
const normalized = (0,
|
|
41084
|
+
const normalized = (0, import_path16.relative)(root, (0, import_path16.dirname)(runPath)).replaceAll("\\", "/");
|
|
40132
41085
|
const parts = normalized.split("/").filter(Boolean);
|
|
40133
41086
|
if (parts.length === 0) return ".";
|
|
40134
41087
|
if (/^\d{4}-\d{2}-\d{2}$/.test(parts[0]) && parts[1]) return `${parts[0]}/${parts[1]}`;
|
|
@@ -40143,7 +41096,7 @@ function readRunRecords(root, cwd) {
|
|
|
40143
41096
|
const parsed = candidate.synthetic ? synthesizeRunFromCapture(file2) : readJson(file2);
|
|
40144
41097
|
const findings = validateExternalAgentRun(parsed);
|
|
40145
41098
|
if (findings.length > 0) {
|
|
40146
|
-
invalid_runs.push({ path: (0,
|
|
41099
|
+
invalid_runs.push({ path: (0, import_path16.relative)(cwd, file2).replaceAll("\\", "/"), findings });
|
|
40147
41100
|
continue;
|
|
40148
41101
|
}
|
|
40149
41102
|
const run = parsed;
|
|
@@ -40155,7 +41108,7 @@ function readRunRecords(root, cwd) {
|
|
|
40155
41108
|
});
|
|
40156
41109
|
} catch (error2) {
|
|
40157
41110
|
invalid_runs.push({
|
|
40158
|
-
path: (0,
|
|
41111
|
+
path: (0, import_path16.relative)(cwd, file2).replaceAll("\\", "/"),
|
|
40159
41112
|
findings: [{ id: "json_parse_failed", detail: error2 instanceof Error ? error2.message : String(error2) }]
|
|
40160
41113
|
});
|
|
40161
41114
|
}
|
|
@@ -40200,10 +41153,10 @@ function collapseCommandRecords(records) {
|
|
|
40200
41153
|
function readCommandOutputJson(runDir, command) {
|
|
40201
41154
|
const artifact = typeof command.output_artifact === "string" && command.output_artifact.trim() ? command.output_artifact.trim() : "";
|
|
40202
41155
|
if (!artifact || artifact.includes("..") || artifact.includes("/") || artifact.includes("\\")) return null;
|
|
40203
|
-
const artifactPath = (0,
|
|
40204
|
-
if (!(0,
|
|
41156
|
+
const artifactPath = (0, import_path16.join)(runDir, artifact);
|
|
41157
|
+
if (!(0, import_fs17.existsSync)(artifactPath)) return null;
|
|
40205
41158
|
try {
|
|
40206
|
-
const text = (0,
|
|
41159
|
+
const text = (0, import_fs17.readFileSync)(artifactPath, "utf8");
|
|
40207
41160
|
const firstObject = text.indexOf("{");
|
|
40208
41161
|
const lastObject = text.lastIndexOf("}");
|
|
40209
41162
|
if (firstObject < 0 || lastObject <= firstObject) return null;
|
|
@@ -40257,8 +41210,8 @@ function commandTimingBreakdown(command, output) {
|
|
|
40257
41210
|
return null;
|
|
40258
41211
|
}
|
|
40259
41212
|
function analyzeRunArtifacts(runPath, run, cwd) {
|
|
40260
|
-
const runDir = (0,
|
|
40261
|
-
const commands = collapseCommandRecords(readNdjson((0,
|
|
41213
|
+
const runDir = (0, import_path16.dirname)(runPath);
|
|
41214
|
+
const commands = collapseCommandRecords(readNdjson((0, import_path16.join)(runDir, "commands.ndjson")));
|
|
40262
41215
|
const reasonCounts = /* @__PURE__ */ new Map();
|
|
40263
41216
|
const slowSteps = [];
|
|
40264
41217
|
const timingBreakdowns = [];
|
|
@@ -40270,7 +41223,7 @@ function analyzeRunArtifacts(runPath, run, cwd) {
|
|
|
40270
41223
|
const breakdown = commandTimingBreakdown(command, output);
|
|
40271
41224
|
if (breakdown) timingBreakdowns.push({
|
|
40272
41225
|
run_id: run.run_id,
|
|
40273
|
-
run_path: (0,
|
|
41226
|
+
run_path: (0, import_path16.relative)(cwd, runPath).replaceAll("\\", "/"),
|
|
40274
41227
|
...breakdown
|
|
40275
41228
|
});
|
|
40276
41229
|
if (command.phase === "completed" || command.completed_at) completed += 1;
|
|
@@ -40279,7 +41232,7 @@ function analyzeRunArtifacts(runPath, run, cwd) {
|
|
|
40279
41232
|
totalDuration += command.duration_ms;
|
|
40280
41233
|
slowSteps.push({
|
|
40281
41234
|
run_id: run.run_id,
|
|
40282
|
-
run_path: (0,
|
|
41235
|
+
run_path: (0, import_path16.relative)(cwd, runPath).replaceAll("\\", "/"),
|
|
40283
41236
|
command: command.command || "",
|
|
40284
41237
|
duration_ms: command.duration_ms,
|
|
40285
41238
|
status: command.status || null,
|
|
@@ -40292,7 +41245,7 @@ function analyzeRunArtifacts(runPath, run, cwd) {
|
|
|
40292
41245
|
if (reasonCode) increment(reasonCounts, reasonCode);
|
|
40293
41246
|
}
|
|
40294
41247
|
}
|
|
40295
|
-
const codexEvents = readNdjson((0,
|
|
41248
|
+
const codexEvents = readNdjson((0, import_path16.join)(runDir, "codex-exec.jsonl"));
|
|
40296
41249
|
const codexDocs = /* @__PURE__ */ new Set();
|
|
40297
41250
|
let codexCommandExecutions = 0;
|
|
40298
41251
|
let codexFailedExitCodes = 0;
|
|
@@ -40309,7 +41262,7 @@ function analyzeRunArtifacts(runPath, run, cwd) {
|
|
|
40309
41262
|
...Array.from(codexDocs)
|
|
40310
41263
|
]);
|
|
40311
41264
|
return {
|
|
40312
|
-
command_log_present: (0,
|
|
41265
|
+
command_log_present: (0, import_fs17.existsSync)((0, import_path16.join)(runDir, "commands.ndjson")),
|
|
40313
41266
|
command_count: commands.length,
|
|
40314
41267
|
completed_command_count: completed,
|
|
40315
41268
|
missing_completion_count: Math.max(0, commands.length - completed),
|
|
@@ -40324,8 +41277,8 @@ function analyzeRunArtifacts(runPath, run, cwd) {
|
|
|
40324
41277
|
};
|
|
40325
41278
|
}
|
|
40326
41279
|
function summarizeExternalAgentRuns(options) {
|
|
40327
|
-
const cwd = (0,
|
|
40328
|
-
const root = (0,
|
|
41280
|
+
const cwd = (0, import_path16.resolve)(options.cwd || process.cwd());
|
|
41281
|
+
const root = (0, import_path16.resolve)(cwd, options.root);
|
|
40329
41282
|
const loaded = readRunRecords(root, cwd);
|
|
40330
41283
|
const selectedCohortId = options.cohortId || (options.currentBaselineOnly ? latestCohortId(loaded.records) : null);
|
|
40331
41284
|
const records = selectedCohortId ? loaded.records.filter((record2) => record2.cohort_id === selectedCohortId) : loaded.records;
|
|
@@ -40380,7 +41333,7 @@ function summarizeExternalAgentRuns(options) {
|
|
|
40380
41333
|
return {
|
|
40381
41334
|
schema_version: "external_agent_run_summary.v1",
|
|
40382
41335
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
40383
|
-
root: (0,
|
|
41336
|
+
root: (0, import_path16.relative)(cwd, root).replaceAll("\\", "/") || ".",
|
|
40384
41337
|
cohort_id: selectedCohortId,
|
|
40385
41338
|
current_baseline_only: Boolean(selectedCohortId),
|
|
40386
41339
|
run_count: records.length,
|
|
@@ -40415,7 +41368,7 @@ function summarizeExternalAgentRuns(options) {
|
|
|
40415
41368
|
},
|
|
40416
41369
|
next_commands: nextRecommendedFix ? [`foh bug improve --from external-agent-run --file <run_dir>/run.json --json`] : [],
|
|
40417
41370
|
invalid_runs: selectedCohortId ? [] : loaded.invalid_runs,
|
|
40418
|
-
run_paths: records.map((record2) => (0,
|
|
41371
|
+
run_paths: records.map((record2) => (0, import_path16.relative)(cwd, record2.path).replaceAll("\\", "/")).sort()
|
|
40419
41372
|
};
|
|
40420
41373
|
}
|
|
40421
41374
|
function runExternalAgentRunSummary(options) {
|
|
@@ -40435,13 +41388,13 @@ function runExternalAgentRunSummary(options) {
|
|
|
40435
41388
|
report: summary
|
|
40436
41389
|
};
|
|
40437
41390
|
if (options.out) {
|
|
40438
|
-
(0,
|
|
40439
|
-
(0,
|
|
41391
|
+
(0, import_fs17.mkdirSync)((0, import_path16.dirname)((0, import_path16.resolve)(options.cwd || process.cwd(), options.out)), { recursive: true });
|
|
41392
|
+
(0, import_fs17.writeFileSync)((0, import_path16.resolve)(options.cwd || process.cwd(), options.out), `${JSON.stringify(summary, null, 2)}
|
|
40440
41393
|
`, "utf8");
|
|
40441
41394
|
}
|
|
40442
41395
|
if (options.report) {
|
|
40443
|
-
(0,
|
|
40444
|
-
(0,
|
|
41396
|
+
(0, import_fs17.mkdirSync)((0, import_path16.dirname)((0, import_path16.resolve)(options.cwd || process.cwd(), options.report)), { recursive: true });
|
|
41397
|
+
(0, import_fs17.writeFileSync)((0, import_path16.resolve)(options.cwd || process.cwd(), options.report), `${JSON.stringify(report, null, 2)}
|
|
40445
41398
|
`, "utf8");
|
|
40446
41399
|
}
|
|
40447
41400
|
return { summary, report };
|
|
@@ -40449,20 +41402,20 @@ function runExternalAgentRunSummary(options) {
|
|
|
40449
41402
|
|
|
40450
41403
|
// src/lib/external-agent-executor-classification.ts
|
|
40451
41404
|
function proofArtifactPasses(runDir) {
|
|
40452
|
-
const proofPath = (0,
|
|
40453
|
-
if (!(0,
|
|
41405
|
+
const proofPath = (0, import_path17.join)(runDir, "proof.json");
|
|
41406
|
+
if (!(0, import_fs18.existsSync)(proofPath)) return false;
|
|
40454
41407
|
try {
|
|
40455
|
-
const parsed = JSON.parse((0,
|
|
41408
|
+
const parsed = JSON.parse((0, import_fs18.readFileSync)(proofPath, "utf8"));
|
|
40456
41409
|
return parsed.ok === true || parsed.status === "pass" || parsed.status === "passed";
|
|
40457
41410
|
} catch {
|
|
40458
41411
|
return false;
|
|
40459
41412
|
}
|
|
40460
41413
|
}
|
|
40461
41414
|
function readIfExists(path2) {
|
|
40462
|
-
return (0,
|
|
41415
|
+
return (0, import_fs18.existsSync)(path2) ? (0, import_fs18.readFileSync)(path2, "utf8") : "";
|
|
40463
41416
|
}
|
|
40464
41417
|
function relativeArtifactName(path2) {
|
|
40465
|
-
return (0,
|
|
41418
|
+
return (0, import_path17.basename)(path2);
|
|
40466
41419
|
}
|
|
40467
41420
|
function classifyExternalAgentRun(input) {
|
|
40468
41421
|
if (input.timedOut) return { status: "hold", reasonCode: `${input.run.command}_runner_timeout` };
|
|
@@ -40617,13 +41570,13 @@ function buildExecutedExternalAgentRunArtifact(input) {
|
|
|
40617
41570
|
},
|
|
40618
41571
|
artifacts: {
|
|
40619
41572
|
terminal_transcript: relativeArtifactName(input.run.outputs.jsonl),
|
|
40620
|
-
command_log: (0,
|
|
40621
|
-
proof_bundle: (0,
|
|
40622
|
-
replay_packet: (0,
|
|
40623
|
-
knowledge_packet: (0,
|
|
41573
|
+
command_log: (0, import_fs18.existsSync)((0, import_path17.join)(input.run.run_dir, "commands.ndjson")) ? "commands.ndjson" : null,
|
|
41574
|
+
proof_bundle: (0, import_fs18.existsSync)((0, import_path17.join)(input.run.run_dir, "proof.json")) ? "proof.json" : null,
|
|
41575
|
+
replay_packet: (0, import_fs18.existsSync)((0, import_path17.join)(input.run.run_dir, "replay.json")) ? "replay.json" : null,
|
|
41576
|
+
knowledge_packet: (0, import_fs18.existsSync)((0, import_path17.join)(input.run.run_dir, "knowledge.json")) ? "knowledge.json" : null,
|
|
40624
41577
|
improvement_packet: input.status === "pass" ? null : "improvement-packet.json",
|
|
40625
41578
|
agent_metadata: agentMetadata.path,
|
|
40626
|
-
notes: (0,
|
|
41579
|
+
notes: (0, import_fs18.existsSync)((0, import_path17.join)(input.run.run_dir, "notes.md")) ? "notes.md" : null,
|
|
40627
41580
|
runner_last_message: relativeArtifactName(input.run.outputs.last_message),
|
|
40628
41581
|
runner_stderr: relativeArtifactName(input.run.outputs.stderr),
|
|
40629
41582
|
codex_last_message: input.run.command === "codex" ? relativeArtifactName(input.run.outputs.last_message) : null,
|
|
@@ -40631,25 +41584,25 @@ function buildExecutedExternalAgentRunArtifact(input) {
|
|
|
40631
41584
|
artifact_safety: relativeArtifactName(input.run.outputs.artifact_safety)
|
|
40632
41585
|
},
|
|
40633
41586
|
summary: input.status === "pass" ? `Controlled ${input.run.command} external-agent run produced passing proof evidence.` : `Controlled ${input.run.command} external-agent run ended as ${input.status} with reason ${input.reasonCode}.`,
|
|
40634
|
-
next_commands: input.status === "pass" ? [externalAgentSummaryCommand((0,
|
|
41587
|
+
next_commands: input.status === "pass" ? [externalAgentSummaryCommand((0, import_path17.dirname)(input.run.run_dir))] : [
|
|
40635
41588
|
"foh eval external-agent scan-artifacts --run-dir <run_dir> --private-repo-root <private_repo_root> --write-redacted --json",
|
|
40636
41589
|
"foh bug improve --from external-agent-run --file <run_dir>/run.json --out <run_dir>/improvement-packet.json --json",
|
|
40637
|
-
externalAgentSummaryCommand((0,
|
|
41590
|
+
externalAgentSummaryCommand((0, import_path17.dirname)(input.run.run_dir))
|
|
40638
41591
|
]
|
|
40639
41592
|
};
|
|
40640
41593
|
}
|
|
40641
41594
|
|
|
40642
41595
|
// src/lib/external-agent-runner-execution.ts
|
|
40643
|
-
var
|
|
40644
|
-
var
|
|
40645
|
-
var
|
|
41596
|
+
var import_child_process5 = require("child_process");
|
|
41597
|
+
var import_fs19 = require("fs");
|
|
41598
|
+
var import_path18 = require("path");
|
|
40646
41599
|
function buildCommandInvocation(command, args) {
|
|
40647
41600
|
if (process.platform === "win32" && command.toLowerCase().endsWith(".cmd")) {
|
|
40648
|
-
const binDir = (0,
|
|
40649
|
-
const codexEntrypoint = (0,
|
|
40650
|
-
if ((0,
|
|
40651
|
-
const geminiEntrypoint = (0,
|
|
40652
|
-
if ((0,
|
|
41601
|
+
const binDir = (0, import_path18.dirname)(command);
|
|
41602
|
+
const codexEntrypoint = (0, import_path18.join)(binDir, "node_modules", "@openai", "codex", "bin", "codex.js");
|
|
41603
|
+
if ((0, import_fs19.existsSync)(codexEntrypoint)) return { command: process.execPath, args: [codexEntrypoint, ...args] };
|
|
41604
|
+
const geminiEntrypoint = (0, import_path18.join)(binDir, "node_modules", "@google", "gemini-cli", "bundle", "gemini.js");
|
|
41605
|
+
if ((0, import_fs19.existsSync)(geminiEntrypoint)) return { command: process.execPath, args: ["--no-warnings=DEP0040", geminiEntrypoint, ...args] };
|
|
40653
41606
|
}
|
|
40654
41607
|
return { command, args };
|
|
40655
41608
|
}
|
|
@@ -40657,15 +41610,15 @@ function spawnExternalAgentRunner(input) {
|
|
|
40657
41610
|
return new Promise((resolveRun) => {
|
|
40658
41611
|
const started = Date.now();
|
|
40659
41612
|
const commandInvocation = buildCommandInvocation(input.command, input.args);
|
|
40660
|
-
const child = (0,
|
|
41613
|
+
const child = (0, import_child_process5.spawn)(commandInvocation.command, commandInvocation.args, {
|
|
40661
41614
|
cwd: input.cwd,
|
|
40662
41615
|
env: input.env,
|
|
40663
41616
|
shell: false,
|
|
40664
41617
|
stdio: ["pipe", "pipe", "pipe"],
|
|
40665
41618
|
windowsHide: true
|
|
40666
41619
|
});
|
|
40667
|
-
const stdout = (0,
|
|
40668
|
-
const stderr = (0,
|
|
41620
|
+
const stdout = (0, import_fs19.createWriteStream)(input.stdoutPath, { flags: "w" });
|
|
41621
|
+
const stderr = (0, import_fs19.createWriteStream)(input.stderrPath, { flags: "w" });
|
|
40669
41622
|
child.stdout.pipe(stdout);
|
|
40670
41623
|
child.stderr.pipe(stderr);
|
|
40671
41624
|
child.stdin.end(input.prompt);
|
|
@@ -40673,7 +41626,7 @@ function spawnExternalAgentRunner(input) {
|
|
|
40673
41626
|
const timer = setTimeout(() => {
|
|
40674
41627
|
timedOut = true;
|
|
40675
41628
|
if (child.pid && process.platform === "win32") {
|
|
40676
|
-
(0,
|
|
41629
|
+
(0, import_child_process5.spawnSync)("taskkill.exe", ["/pid", String(child.pid), "/t", "/f"], { stdio: "ignore" });
|
|
40677
41630
|
} else {
|
|
40678
41631
|
child.kill("SIGKILL");
|
|
40679
41632
|
}
|
|
@@ -40777,14 +41730,14 @@ async function runExternalAgentEvalAuthPreflight(env = process.env, options = {}
|
|
|
40777
41730
|
};
|
|
40778
41731
|
}
|
|
40779
41732
|
function normalizeForCompare(path2) {
|
|
40780
|
-
const resolved = (0,
|
|
41733
|
+
const resolved = (0, import_path19.resolve)(path2);
|
|
40781
41734
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
40782
41735
|
}
|
|
40783
41736
|
function isPathInside(childPath, parentPath) {
|
|
40784
41737
|
const child = normalizeForCompare(childPath);
|
|
40785
41738
|
const parent = normalizeForCompare(parentPath);
|
|
40786
|
-
const rel = (0,
|
|
40787
|
-
return rel === "" || !!rel && !rel.startsWith("..") && !(0,
|
|
41739
|
+
const rel = (0, import_path19.relative)(parent, child);
|
|
41740
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !(0, import_path19.isAbsolute)(rel);
|
|
40788
41741
|
}
|
|
40789
41742
|
function requireString(value, field) {
|
|
40790
41743
|
if (typeof value !== "string" || value.trim() === "") {
|
|
@@ -40793,10 +41746,10 @@ function requireString(value, field) {
|
|
|
40793
41746
|
return value;
|
|
40794
41747
|
}
|
|
40795
41748
|
function readBatch(batchPath) {
|
|
40796
|
-
if (!(0,
|
|
41749
|
+
if (!(0, import_fs20.existsSync)(batchPath)) {
|
|
40797
41750
|
throw new ExternalAgentExecutorError("external_agent_batch_not_found", `Batch file not found: ${batchPath}`);
|
|
40798
41751
|
}
|
|
40799
|
-
const parsed = JSON.parse((0,
|
|
41752
|
+
const parsed = JSON.parse((0, import_fs20.readFileSync)(batchPath, "utf8"));
|
|
40800
41753
|
if (parsed.schema_version !== "external_agent_batch_plan.v1") {
|
|
40801
41754
|
throw new ExternalAgentExecutorError("invalid_external_agent_batch", "Batch schema_version must be external_agent_batch_plan.v1.");
|
|
40802
41755
|
}
|
|
@@ -40811,11 +41764,11 @@ function defaultRunnerProbe(command, args) {
|
|
|
40811
41764
|
encoding: "utf8",
|
|
40812
41765
|
timeout: isGeminiHeadlessSmoke ? GEMINI_HEADLESS_PROBE_TIMEOUT_MS : void 0
|
|
40813
41766
|
};
|
|
40814
|
-
const result = process.platform === "win32" && command.toLowerCase().endsWith(".cmd") ? (0,
|
|
41767
|
+
const result = process.platform === "win32" && command.toLowerCase().endsWith(".cmd") ? (0, import_child_process6.spawnSync)(
|
|
40815
41768
|
"powershell.exe",
|
|
40816
41769
|
["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", `& ${[command, ...args].map(quotePowerShellArg).join(" ")}`],
|
|
40817
41770
|
spawnOptions
|
|
40818
|
-
) : (0,
|
|
41771
|
+
) : (0, import_child_process6.spawnSync)(command, args, spawnOptions);
|
|
40819
41772
|
return {
|
|
40820
41773
|
status: typeof result.status === "number" ? result.status : null,
|
|
40821
41774
|
stdout: String(result.stdout || ""),
|
|
@@ -40833,8 +41786,8 @@ function resolveCodexProbeCommand() {
|
|
|
40833
41786
|
if (process.platform !== "win32") return "codex";
|
|
40834
41787
|
const appData = process.env.APPDATA;
|
|
40835
41788
|
if (appData) {
|
|
40836
|
-
const appDataShim = (0,
|
|
40837
|
-
if ((0,
|
|
41789
|
+
const appDataShim = (0, import_path19.join)(appData, "npm", "codex.cmd");
|
|
41790
|
+
if ((0, import_fs20.existsSync)(appDataShim)) return appDataShim;
|
|
40838
41791
|
}
|
|
40839
41792
|
return "codex.cmd";
|
|
40840
41793
|
}
|
|
@@ -40845,8 +41798,8 @@ function resolveGeminiProbeCommand() {
|
|
|
40845
41798
|
if (process.platform !== "win32") return "gemini";
|
|
40846
41799
|
const appData = process.env.APPDATA;
|
|
40847
41800
|
if (appData) {
|
|
40848
|
-
const appDataShim = (0,
|
|
40849
|
-
if ((0,
|
|
41801
|
+
const appDataShim = (0, import_path19.join)(appData, "npm", "gemini.cmd");
|
|
41802
|
+
if ((0, import_fs20.existsSync)(appDataShim)) return appDataShim;
|
|
40850
41803
|
}
|
|
40851
41804
|
return "gemini.cmd";
|
|
40852
41805
|
}
|
|
@@ -41117,35 +42070,35 @@ function safeRunId(value) {
|
|
|
41117
42070
|
return value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "run";
|
|
41118
42071
|
}
|
|
41119
42072
|
function resolveWorkspaceRoot(input) {
|
|
41120
|
-
if (input.workspaceRoot) return (0,
|
|
41121
|
-
const batchStem = (0,
|
|
41122
|
-
const repoStem = (0,
|
|
41123
|
-
return (0,
|
|
42073
|
+
if (input.workspaceRoot) return (0, import_path19.resolve)(input.workspaceRoot);
|
|
42074
|
+
const batchStem = (0, import_path19.basename)((0, import_path19.resolve)(input.batchPath)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
|
|
42075
|
+
const repoStem = (0, import_path19.basename)((0, import_path19.resolve)(input.privateRepoRoot)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
|
|
42076
|
+
return (0, import_path19.resolve)((0, import_os3.tmpdir)(), "foh-external-agent-workspaces", repoStem, batchStem);
|
|
41124
42077
|
}
|
|
41125
42078
|
function findNearestGitRoot(startPath) {
|
|
41126
|
-
let current = (0,
|
|
42079
|
+
let current = (0, import_path19.resolve)(startPath);
|
|
41127
42080
|
while (true) {
|
|
41128
|
-
if ((0,
|
|
41129
|
-
const parent = (0,
|
|
42081
|
+
if ((0, import_fs20.existsSync)((0, import_path19.join)(current, ".git"))) return current;
|
|
42082
|
+
const parent = (0, import_path19.dirname)(current);
|
|
41130
42083
|
if (parent === current) return null;
|
|
41131
42084
|
current = parent;
|
|
41132
42085
|
}
|
|
41133
42086
|
}
|
|
41134
42087
|
function resolvePrivateRepoRoot(input) {
|
|
41135
42088
|
if (input.explicitPrivateRepoRoot) {
|
|
41136
|
-
return { root: (0,
|
|
42089
|
+
return { root: (0, import_path19.resolve)(input.explicitPrivateRepoRoot), explicit: true };
|
|
41137
42090
|
}
|
|
41138
|
-
const cwd = (0,
|
|
42091
|
+
const cwd = (0, import_path19.resolve)(input.cwd || process.cwd());
|
|
41139
42092
|
const gitRoot = findNearestGitRoot(cwd);
|
|
41140
42093
|
if (gitRoot) return { root: gitRoot, explicit: false };
|
|
41141
42094
|
return {
|
|
41142
|
-
root: (0,
|
|
42095
|
+
root: (0, import_path19.join)(cwd, ".foh-no-private-repo-root-sentinel"),
|
|
41143
42096
|
explicit: false
|
|
41144
42097
|
};
|
|
41145
42098
|
}
|
|
41146
42099
|
function promptVersionFromPath(promptPath) {
|
|
41147
|
-
const raw = (0,
|
|
41148
|
-
if (raw.includes("arbitrary-agency setup context") || raw.includes("Mission: prove whether an arbitrary estate agency") || raw.includes("ops reporting agency-setup-preview")) return "arbitrary-agency-setup-release.v1";
|
|
42100
|
+
const raw = (0, import_fs20.readFileSync)(promptPath, "utf8");
|
|
42101
|
+
if (raw.includes("arbitrary-agency setup context") || raw.includes("Mission: prove whether an arbitrary estate agency") || raw.includes("ops reporting agency-setup-preview") || raw.includes("ops reporting agency-setup-workflow")) return "arbitrary-agency-setup-release.v1";
|
|
41149
42102
|
if (raw.includes("Do not assume access to the private source repository")) return "blank-setup.v1";
|
|
41150
42103
|
return "unknown";
|
|
41151
42104
|
}
|
|
@@ -41154,7 +42107,7 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
41154
42107
|
if (runner !== "codex" && runner !== "gemini") {
|
|
41155
42108
|
throw new ExternalAgentExecutorError("unsupported_external_agent_runner", `Unsupported runner: ${runner}`);
|
|
41156
42109
|
}
|
|
41157
|
-
const batchPath = (0,
|
|
42110
|
+
const batchPath = (0, import_path19.resolve)(options.batchPath);
|
|
41158
42111
|
const batch = readBatch(batchPath);
|
|
41159
42112
|
const runnerProbe = validateRunner(options, runner);
|
|
41160
42113
|
const codexSandboxBackend = normalizeCodexSandboxBackend(options.codexSandboxBackend);
|
|
@@ -41173,17 +42126,17 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
41173
42126
|
`Workspace root must be outside the private repository. workspace=${workspaceRoot} repo=${privateRepoRoot}`
|
|
41174
42127
|
);
|
|
41175
42128
|
}
|
|
41176
|
-
(0,
|
|
41177
|
-
const batchDir = (0,
|
|
42129
|
+
(0, import_fs20.mkdirSync)(workspaceRoot, { recursive: true });
|
|
42130
|
+
const batchDir = (0, import_path19.resolve)(String(batch.batch_dir || (0, import_path19.resolve)(batchPath, "..")));
|
|
41178
42131
|
const timeoutMinutes = Number.isFinite(options.timeoutMinutes) && Number(options.timeoutMinutes) > 0 ? Number(options.timeoutMinutes) : 30;
|
|
41179
42132
|
const runs = batch.runs.map((run) => {
|
|
41180
42133
|
const runId = safeRunId(requireString(run.run_id, "runs[].run_id"));
|
|
41181
|
-
const runDir = (0,
|
|
41182
|
-
const promptPath = (0,
|
|
41183
|
-
const workspaceDir = (0,
|
|
41184
|
-
(0,
|
|
41185
|
-
(0,
|
|
41186
|
-
(0,
|
|
42134
|
+
const runDir = (0, import_path19.resolve)(requireString(run.run_dir, `runs[${runId}].run_dir`));
|
|
42135
|
+
const promptPath = (0, import_path19.resolve)(requireString(run.prompt_path, `runs[${runId}].prompt_path`));
|
|
42136
|
+
const workspaceDir = (0, import_path19.join)(workspaceRoot, runId);
|
|
42137
|
+
(0, import_fs20.mkdirSync)(workspaceDir, { recursive: true });
|
|
42138
|
+
(0, import_fs20.writeFileSync)(
|
|
42139
|
+
(0, import_path19.join)(workspaceDir, "README.md"),
|
|
41187
42140
|
[
|
|
41188
42141
|
"# FOH External-Agent Workspace",
|
|
41189
42142
|
"",
|
|
@@ -41201,11 +42154,11 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
41201
42154
|
});
|
|
41202
42155
|
const promptVersion = String(env[EXTERNAL_AGENT_PROMPT_VERSION_ENV] || "unknown");
|
|
41203
42156
|
const outputStem = runner === "gemini" ? "gemini" : "codex";
|
|
41204
|
-
const jsonlPath = (0,
|
|
41205
|
-
const lastMessagePath = (0,
|
|
41206
|
-
const stderrPath = (0,
|
|
41207
|
-
const runPath = (0,
|
|
41208
|
-
const artifactSafetyPath = (0,
|
|
42157
|
+
const jsonlPath = (0, import_path19.join)(runDir, `${outputStem}-exec.jsonl`);
|
|
42158
|
+
const lastMessagePath = (0, import_path19.join)(runDir, `${outputStem}-last-message.md`);
|
|
42159
|
+
const stderrPath = (0, import_path19.join)(runDir, `${outputStem}-stderr.txt`);
|
|
42160
|
+
const runPath = (0, import_path19.join)(runDir, "run.json");
|
|
42161
|
+
const artifactSafetyPath = (0, import_path19.join)(runDir, "artifact-safety.json");
|
|
41209
42162
|
const args = runner === "gemini" ? [
|
|
41210
42163
|
...runnerProbe.globalArgs,
|
|
41211
42164
|
...runnerProbe.execArgs
|
|
@@ -41296,9 +42249,9 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
41296
42249
|
};
|
|
41297
42250
|
}
|
|
41298
42251
|
function writeExternalAgentExecutorPlan(plan) {
|
|
41299
|
-
const path2 = (0,
|
|
41300
|
-
(0,
|
|
41301
|
-
(0,
|
|
42252
|
+
const path2 = (0, import_path19.join)(plan.batch_dir, "executor-plan.json");
|
|
42253
|
+
(0, import_fs20.mkdirSync)(plan.batch_dir, { recursive: true });
|
|
42254
|
+
(0, import_fs20.writeFileSync)(path2, `${JSON.stringify(plan, null, 2)}
|
|
41302
42255
|
`, "utf8");
|
|
41303
42256
|
return path2;
|
|
41304
42257
|
}
|
|
@@ -41313,7 +42266,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
41313
42266
|
if (authPreflight && !authPreflight.ok) {
|
|
41314
42267
|
const endedAt2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
41315
42268
|
const blockedResults = plan.runs.map((run) => {
|
|
41316
|
-
(0,
|
|
42269
|
+
(0, import_fs20.mkdirSync)(run.run_dir, { recursive: true });
|
|
41317
42270
|
const runArtifact = buildExecutedExternalAgentRunArtifact({
|
|
41318
42271
|
run,
|
|
41319
42272
|
startedAt,
|
|
@@ -41324,7 +42277,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
41324
42277
|
timedOut: false,
|
|
41325
42278
|
durationMs: 0
|
|
41326
42279
|
});
|
|
41327
|
-
(0,
|
|
42280
|
+
(0, import_fs20.writeFileSync)(run.outputs.run, `${JSON.stringify(runArtifact, null, 2)}
|
|
41328
42281
|
`, "utf8");
|
|
41329
42282
|
return {
|
|
41330
42283
|
run_id: run.run_id,
|
|
@@ -41351,8 +42304,8 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
41351
42304
|
}
|
|
41352
42305
|
for (const run of plan.runs) {
|
|
41353
42306
|
const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
41354
|
-
const commandCaptureDir = (0,
|
|
41355
|
-
(0,
|
|
42307
|
+
const commandCaptureDir = (0, import_path19.join)(run.workspace_dir, ".foh-capture");
|
|
42308
|
+
(0, import_fs20.mkdirSync)(commandCaptureDir, { recursive: true });
|
|
41356
42309
|
const env = buildCodexExecutorEnv({
|
|
41357
42310
|
sourceEnv: options.env,
|
|
41358
42311
|
runDir: commandCaptureDir,
|
|
@@ -41363,7 +42316,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
41363
42316
|
args: run.args,
|
|
41364
42317
|
cwd: run.workspace_dir,
|
|
41365
42318
|
env,
|
|
41366
|
-
prompt: (0,
|
|
42319
|
+
prompt: (0, import_fs20.readFileSync)(run.prompt_path, "utf8"),
|
|
41367
42320
|
stdoutPath: run.outputs.jsonl,
|
|
41368
42321
|
stderrPath: run.outputs.stderr,
|
|
41369
42322
|
timeoutMs: plan.timeout_minutes * 60 * 1e3
|
|
@@ -41376,7 +42329,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
41376
42329
|
privateRepoRoot,
|
|
41377
42330
|
writeRedacted: true
|
|
41378
42331
|
});
|
|
41379
|
-
(0,
|
|
42332
|
+
(0, import_fs20.writeFileSync)(run.outputs.artifact_safety, `${JSON.stringify(artifactSafety, null, 2)}
|
|
41380
42333
|
`, "utf8");
|
|
41381
42334
|
const runEndedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
41382
42335
|
const classification = classifyExternalAgentRun({
|
|
@@ -41395,7 +42348,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
41395
42348
|
timedOut: spawned.timedOut,
|
|
41396
42349
|
durationMs: spawned.durationMs
|
|
41397
42350
|
});
|
|
41398
|
-
(0,
|
|
42351
|
+
(0, import_fs20.writeFileSync)(run.outputs.run, `${JSON.stringify(runArtifact, null, 2)}
|
|
41399
42352
|
`, "utf8");
|
|
41400
42353
|
results.push({
|
|
41401
42354
|
run_id: run.run_id,
|
|
@@ -41434,7 +42387,7 @@ var PROMPTS = {
|
|
|
41434
42387
|
"real-estate-seller-valuation.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 ...`. Do not assume access to the private source repository. Mission: configure a seller-valuation real-estate agent, prove valuation lead capture on widget and voice, and keep strict no-spend behavior. Required path: 1) verify auth/org scope and reuse existing eval state; 2) select/apply seller valuation template; 3) configure widget + voice; 4) run widget smoke and release certification; 5) run voice proof and classify holds. Do not claim precise valuation output without approved tooling; safe fallback or handoff must remain explicit. Do not buy numbers. Final artifact must include: selected template id/slug, lead fields observed, tool/action behavior, reason codes, docs_pages_used, and next fix commands.",
|
|
41435
42388
|
"real-estate-viewing-and-qa.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 ...`. Do not assume access to the private source repository. Mission: configure a viewing-booking/property-QA real-estate agent and prove booking-safe behavior under no-spend policy. Required path: 1) verify auth/org scope and reuse existing eval state; 2) select/apply viewing template; 3) configure widget + voice; 4) run widget smoke, release certification, and voice proof; 5) explicitly check no-duplicate-side-effect behavior and safe fallback/handoff when booking tooling or contact path is unavailable. Do not buy numbers. Final artifact must include: selected template id/slug, booking/fallback evidence, reason codes, docs_pages_used, and next fix commands.",
|
|
41436
42389
|
"real-estate-landlord-enquiry.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 ...`. Do not assume access to the private source repository. Mission: configure a landlord-enquiry real-estate agent and prove lead capture + safe escalation under no-spend constraints. Required path: 1) verify auth/org scope and reuse existing eval state; 2) select/apply landlord template; 3) configure widget + voice; 4) run widget smoke, release certification, and voice proof; 5) ensure handoff/escalation remains reason-coded and no-silent-drop. Do not buy numbers. Final artifact must include: selected template id/slug, lead/action/handoff evidence summary, reason codes, docs_pages_used, and next fix commands.",
|
|
41437
|
-
"arbitrary-agency-setup-release.v1": "Go to https://frontofhouse.okii.uk. Use only public docs, public API docs, the deployed production API, 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 ...`, local source, private repo scripts, or unpublished commands. Do not assume access to the private source repository. Mission: prove whether an arbitrary estate agency can be set up or accurately held using the public launch evidence workflow. Required path: 1) verify CLI version and auth/org scope; 2) run `foh ops reporting agency-setup-
|
|
42390
|
+
"arbitrary-agency-setup-release.v1": "Go to https://frontofhouse.okii.uk. Use only public docs, public API docs, the deployed production API, 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 ...`, local source, private repo scripts, or unpublished commands. Do not assume access to the private source repository. Mission: prove whether an arbitrary estate agency can be set up or accurately held using the public launch evidence workflow. Required path: 1) verify CLI version and auth/org scope; 2) run `foh ops reporting agency-setup-workflow` with the supplied agency name, official source URL, branch location, requested tool surface, and target mode; 3) inspect the returned setup dossier, launch packet, evidence packet, operator status, no-overclaim decision, and proof commands; 4) run `foh ops reporting launch-packet-evidence-check --json` only with redacted synthetic or customer-provided evidence, never raw credentials or secrets; 5) run `foh ops reporting customer-live-status --json`; 6) classify the outcome as pass, expected hold, or product failure. No-spend boundary: do not buy phone numbers, provision paid resources, scrape private systems, bypass credential holds, or claim live/customer-ready/production unless the launch packet explicitly allows it. Expected holds such as missing customer credentials, missing behavior approval, missing live voice validation, or missing production signoff are acceptable only when the commands expose clear reason codes and next actions. Final artifact must include commands executed, docs_pages_used, manual_intervention_count, CLI/API version readback, launch mode observed, reason codes, evidence artifacts created, and exact next commands. 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.",
|
|
41438
42391
|
"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.",
|
|
41439
42392
|
"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.",
|
|
41440
42393
|
"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."
|
|
@@ -41449,13 +42402,13 @@ function defaultRunDir(modelName, promptVersion) {
|
|
|
41449
42402
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 23);
|
|
41450
42403
|
const safeModel = String(modelName || "unknown-model").toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
|
|
41451
42404
|
const safePrompt = String(promptVersion || DEFAULT_PROMPT_VERSION).toLowerCase().replace(/[^a-z0-9_.-]+/g, "-");
|
|
41452
|
-
return (0,
|
|
42405
|
+
return (0, import_path20.resolve)("test-results", "external-agent-runs", date4, `${safeModel}-${safePrompt}-${stamp}`);
|
|
41453
42406
|
}
|
|
41454
42407
|
function defaultBatchDir(promptVersion) {
|
|
41455
42408
|
const date4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
41456
42409
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 23);
|
|
41457
42410
|
const safePrompt = String(promptVersion || DEFAULT_PROMPT_VERSION).toLowerCase().replace(/[^a-z0-9_.-]+/g, "-");
|
|
41458
|
-
return (0,
|
|
42411
|
+
return (0, import_path20.resolve)("test-results", "external-agent-runs", date4, `batch-${safePrompt}-${stamp}`);
|
|
41459
42412
|
}
|
|
41460
42413
|
function safeSlug(value) {
|
|
41461
42414
|
return String(value || "unknown").toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
|
|
@@ -41572,7 +42525,7 @@ function agencySetupPromptContext(context = {}) {
|
|
|
41572
42525
|
const targetMode = String(context.targetMode || "").trim();
|
|
41573
42526
|
if (!agencyName && !sourceUrl && !branchLocation && !targetMode) return "";
|
|
41574
42527
|
const previewArgs = [
|
|
41575
|
-
"ops reporting agency-setup-
|
|
42528
|
+
"ops reporting agency-setup-workflow",
|
|
41576
42529
|
agencyName ? `--agency-name ${quoteArg(agencyName)}` : "--agency-name <agency-name>",
|
|
41577
42530
|
sourceUrl ? `--source-url ${quoteArg(sourceUrl)}` : "--source-url <official-source-url>",
|
|
41578
42531
|
branchLocation ? `--branch-location ${quoteArg(branchLocation)}` : "--branch-location <branch-location>",
|
|
@@ -41587,7 +42540,7 @@ function agencySetupPromptContext(context = {}) {
|
|
|
41587
42540
|
...branchLocation ? [`- Branch/location: ${branchLocation}`] : [],
|
|
41588
42541
|
...targetMode ? [`- Target mode: ${targetMode}`] : [],
|
|
41589
42542
|
`- Start with: npx --yes @f-o-h/cli@latest ${previewArgs}`,
|
|
41590
|
-
"- Then
|
|
42543
|
+
"- Then inspect the workflow output and run only the returned proof/evidence commands using public CLI commands.",
|
|
41591
42544
|
"- Treat missing customer credentials or approvals as expected holds only if the CLI gives machine-readable reason codes and next actions."
|
|
41592
42545
|
].join("\n");
|
|
41593
42546
|
}
|
|
@@ -41598,14 +42551,14 @@ function writePrompt(runDir, promptVersion, context = {}) {
|
|
|
41598
42551
|
knowledgeMissPromptContext(context.knowledgeQuestion, context.expectedAnswer),
|
|
41599
42552
|
agencySetupPromptContext(context)
|
|
41600
42553
|
].join("");
|
|
41601
|
-
const path2 = (0,
|
|
41602
|
-
(0,
|
|
42554
|
+
const path2 = (0, import_path20.join)(runDir, "prompt.txt");
|
|
42555
|
+
(0, import_fs21.writeFileSync)(path2, `${prompt}
|
|
41603
42556
|
`, "utf8");
|
|
41604
42557
|
return path2;
|
|
41605
42558
|
}
|
|
41606
42559
|
function writeSession(runDir, session) {
|
|
41607
|
-
const path2 = (0,
|
|
41608
|
-
(0,
|
|
42560
|
+
const path2 = (0, import_path20.join)(runDir, "session.json");
|
|
42561
|
+
(0, import_fs21.writeFileSync)(path2, `${JSON.stringify(session, null, 2)}
|
|
41609
42562
|
`, "utf8");
|
|
41610
42563
|
return path2;
|
|
41611
42564
|
}
|
|
@@ -41685,9 +42638,9 @@ function buildRunArtifact(input) {
|
|
|
41685
42638
|
notes: "notes.md"
|
|
41686
42639
|
},
|
|
41687
42640
|
summary: status === "pass" ? "External-agent capture session completed and was marked pass." : `External-agent capture session completed with ${commands.length} captured FOH command(s); classify and improve reason ${reasonCode}.`,
|
|
41688
|
-
next_commands: status === "pass" ? [externalAgentSummaryCommand((0,
|
|
41689
|
-
`foh bug improve --from external-agent-run --file ${(0,
|
|
41690
|
-
externalAgentSummaryCommand((0,
|
|
42641
|
+
next_commands: status === "pass" ? [externalAgentSummaryCommand((0, import_path20.dirname)(input.runDir))] : [
|
|
42642
|
+
`foh bug improve --from external-agent-run --file ${(0, import_path20.join)(input.runDir, "run.json")} --out ${(0, import_path20.join)(input.runDir, "improvement-packet.json")} --json`,
|
|
42643
|
+
externalAgentSummaryCommand((0, import_path20.dirname)(input.runDir))
|
|
41691
42644
|
]
|
|
41692
42645
|
};
|
|
41693
42646
|
}
|
|
@@ -41696,8 +42649,8 @@ function registerEval(program3) {
|
|
|
41696
42649
|
const external = evalCommand.command("external-agent").description("Capture clean external coding-agent setup attempts");
|
|
41697
42650
|
external.command("batch").description("Create a deterministic multi-model external-agent batch plan").option("--models <list>", "Comma-separated provider/model list", DEFAULT_BATCH_MODELS).option("--prompt-version <version>", "Prompt version", DEFAULT_PROMPT_VERSION).option("--replay-file <path>", "Local transcript/replay artifact to seed replay-failure prompts").option("--knowledge-question <text>", "Question to seed knowledge-miss prompts").option("--expected-answer <text>", "Expected answer or missing fact for planted knowledge-miss prompts").option("--agency-name <name>", "Agency name to seed arbitrary-agency setup prompts").option("--source-url <url>", "Official source URL to seed arbitrary-agency setup prompts").option("--branch-location <value>", "Branch/location to seed arbitrary-agency setup prompts").option("--target-mode <mode>", "Target launch/exposure mode for arbitrary-agency setup prompts").option("--workspace-type <type>", "Workspace type label", "clean-no-repo").option("--agent-shell <name>", "Agent shell label", "vscode-terminal").option("--out-dir <path>", "Batch output directory").option("--json", "Output as JSON").action(async (opts) => {
|
|
41698
42651
|
const promptVersion = String(opts.promptVersion || DEFAULT_PROMPT_VERSION);
|
|
41699
|
-
const batchDir = (0,
|
|
41700
|
-
const replayFile = opts.replayFile ? (0,
|
|
42652
|
+
const batchDir = (0, import_path20.resolve)(String(opts.outDir || defaultBatchDir(promptVersion)));
|
|
42653
|
+
const replayFile = opts.replayFile ? (0, import_path20.resolve)(String(opts.replayFile)) : void 0;
|
|
41701
42654
|
const knowledgeQuestion = opts.knowledgeQuestion ? String(opts.knowledgeQuestion) : void 0;
|
|
41702
42655
|
const expectedAnswer = opts.expectedAnswer ? String(opts.expectedAnswer) : void 0;
|
|
41703
42656
|
const agencyName = opts.agencyName ? String(opts.agencyName) : void 0;
|
|
@@ -41705,11 +42658,11 @@ function registerEval(program3) {
|
|
|
41705
42658
|
const branchLocation = opts.branchLocation ? String(opts.branchLocation) : void 0;
|
|
41706
42659
|
const targetMode = opts.targetMode ? String(opts.targetMode) : void 0;
|
|
41707
42660
|
const models = parseModelList(String(opts.models || DEFAULT_BATCH_MODELS));
|
|
41708
|
-
(0,
|
|
42661
|
+
(0, import_fs21.mkdirSync)(batchDir, { recursive: true });
|
|
41709
42662
|
const runs2 = models.map((model, index) => {
|
|
41710
42663
|
const runId = `${String(index + 1).padStart(2, "0")}-${safeSlug(model.provider)}-${safeSlug(model.name)}`;
|
|
41711
|
-
const runDir = (0,
|
|
41712
|
-
(0,
|
|
42664
|
+
const runDir = (0, import_path20.join)(batchDir, runId);
|
|
42665
|
+
(0, import_fs21.mkdirSync)(runDir, { recursive: true });
|
|
41713
42666
|
const promptPath = writePrompt(runDir, promptVersion, {
|
|
41714
42667
|
replayFile,
|
|
41715
42668
|
knowledgeQuestion,
|
|
@@ -41772,8 +42725,8 @@ function registerEval(program3) {
|
|
|
41772
42725
|
runs: runs2,
|
|
41773
42726
|
summary_command: externalAgentSummaryCommand(batchDir)
|
|
41774
42727
|
};
|
|
41775
|
-
const batchPath = (0,
|
|
41776
|
-
(0,
|
|
42728
|
+
const batchPath = (0, import_path20.join)(batchDir, "batch.json");
|
|
42729
|
+
(0, import_fs21.writeFileSync)(batchPath, `${JSON.stringify(batch, null, 2)}
|
|
41777
42730
|
`, "utf8");
|
|
41778
42731
|
format(cliEnvelope({
|
|
41779
42732
|
schemaVersion: "external_agent_batch_plan_result.v1",
|
|
@@ -41793,15 +42746,15 @@ function registerEval(program3) {
|
|
|
41793
42746
|
external.command("run").description("Launch an instrumented shell and emit external_agent_run.v1 when it exits").option("--model-provider <name>", "Model provider label", "unknown").option("--model-name <name>", "Model name label", "unknown-model").option("--prompt-version <version>", "Prompt version", DEFAULT_PROMPT_VERSION).option("--replay-file <path>", "Local transcript/replay artifact to seed replay-failure prompts").option("--knowledge-question <text>", "Question to seed knowledge-miss prompts").option("--expected-answer <text>", "Expected answer or missing fact for planted knowledge-miss prompts").option("--agency-name <name>", "Agency name to seed arbitrary-agency setup prompts").option("--source-url <url>", "Official source URL to seed arbitrary-agency setup prompts").option("--branch-location <value>", "Branch/location to seed arbitrary-agency setup prompts").option("--target-mode <mode>", "Target launch/exposure mode for arbitrary-agency setup prompts").option("--workspace-type <type>", "Workspace type label", "clean-no-repo").option("--agent-shell <name>", "Agent shell label", "vscode-terminal").option("--out-dir <path>", "Run output directory").option("--status <status>", "Final status when not interactively classified: pass|hold|fail", "hold").option("--reason-code <code>", "Failure/hold reason code", "external_agent_run_needs_review").option("--shell <command>", "Shell command to launch for capture").option("--no-shell", "Do not launch a shell; create/finalize artifacts immediately").option("--json", "Output as JSON").action(async (opts) => {
|
|
41794
42747
|
const status = normalizeStatus(opts.status);
|
|
41795
42748
|
const promptVersion = String(opts.promptVersion || DEFAULT_PROMPT_VERSION);
|
|
41796
|
-
const runDir = (0,
|
|
41797
|
-
const replayFile = opts.replayFile ? (0,
|
|
42749
|
+
const runDir = (0, import_path20.resolve)(String(opts.outDir || defaultRunDir(opts.modelName, promptVersion)));
|
|
42750
|
+
const replayFile = opts.replayFile ? (0, import_path20.resolve)(String(opts.replayFile)) : void 0;
|
|
41798
42751
|
const knowledgeQuestion = opts.knowledgeQuestion ? String(opts.knowledgeQuestion) : void 0;
|
|
41799
42752
|
const expectedAnswer = opts.expectedAnswer ? String(opts.expectedAnswer) : void 0;
|
|
41800
42753
|
const agencyName = opts.agencyName ? String(opts.agencyName) : void 0;
|
|
41801
42754
|
const sourceUrl = opts.sourceUrl ? String(opts.sourceUrl) : void 0;
|
|
41802
42755
|
const branchLocation = opts.branchLocation ? String(opts.branchLocation) : void 0;
|
|
41803
42756
|
const targetMode = opts.targetMode ? String(opts.targetMode) : void 0;
|
|
41804
|
-
(0,
|
|
42757
|
+
(0, import_fs21.mkdirSync)(runDir, { recursive: true });
|
|
41805
42758
|
const runId = runDir.split(/[\\/]/).filter(Boolean).slice(-1)[0];
|
|
41806
42759
|
const promptPath = writePrompt(runDir, promptVersion, {
|
|
41807
42760
|
replayFile,
|
|
@@ -41839,7 +42792,7 @@ function registerEval(program3) {
|
|
|
41839
42792
|
}
|
|
41840
42793
|
};
|
|
41841
42794
|
writeSession(runDir, session);
|
|
41842
|
-
(0,
|
|
42795
|
+
(0, import_fs21.writeFileSync)((0, import_path20.join)(runDir, "notes.md"), "# External Agent Run Notes\n\n", "utf8");
|
|
41843
42796
|
let shellExitCode = null;
|
|
41844
42797
|
if (opts.shell !== false) {
|
|
41845
42798
|
process.stdout.write(`
|
|
@@ -41849,7 +42802,7 @@ Prompt: ${promptPath}
|
|
|
41849
42802
|
Exit the shell to finalize run.json.
|
|
41850
42803
|
|
|
41851
42804
|
`);
|
|
41852
|
-
const result = (0,
|
|
42805
|
+
const result = (0, import_child_process7.spawnSync)(shell.command, shell.args, {
|
|
41853
42806
|
stdio: "inherit",
|
|
41854
42807
|
env: {
|
|
41855
42808
|
...process.env,
|
|
@@ -41861,8 +42814,8 @@ Exit the shell to finalize run.json.
|
|
|
41861
42814
|
shellExitCode = typeof result.status === "number" ? result.status : null;
|
|
41862
42815
|
}
|
|
41863
42816
|
const artifact = buildRunArtifact({ runDir, session, status, reasonCode: opts.reasonCode, shellExitCode });
|
|
41864
|
-
const runPath = (0,
|
|
41865
|
-
(0,
|
|
42817
|
+
const runPath = (0, import_path20.join)(runDir, "run.json");
|
|
42818
|
+
(0, import_fs21.writeFileSync)(runPath, `${JSON.stringify(artifact, null, 2)}
|
|
41866
42819
|
`, "utf8");
|
|
41867
42820
|
format(cliEnvelope({
|
|
41868
42821
|
schemaVersion: "external_agent_capture_result.v1",
|
|
@@ -41872,7 +42825,7 @@ Exit the shell to finalize run.json.
|
|
|
41872
42825
|
artifacts: {
|
|
41873
42826
|
run: runPath,
|
|
41874
42827
|
prompt: promptPath,
|
|
41875
|
-
commands: (0,
|
|
42828
|
+
commands: (0, import_path20.join)(runDir, "commands.ndjson")
|
|
41876
42829
|
},
|
|
41877
42830
|
nextCommands: artifact.next_commands,
|
|
41878
42831
|
extra: { run: artifact }
|
|
@@ -41980,8 +42933,8 @@ Exit the shell to finalize run.json.
|
|
|
41980
42933
|
requireExplicitEvalAuth: true,
|
|
41981
42934
|
minimumEvalAuthTtlMs: (plan.timeout_minutes + 5) * 60 * 1e3
|
|
41982
42935
|
});
|
|
41983
|
-
const resultPath = (0,
|
|
41984
|
-
(0,
|
|
42936
|
+
const resultPath = (0, import_path20.join)(plan.batch_dir, "execution-result.json");
|
|
42937
|
+
(0, import_fs21.writeFileSync)(resultPath, `${JSON.stringify(result, null, 2)}
|
|
41985
42938
|
`, "utf8");
|
|
41986
42939
|
format(cliEnvelope({
|
|
41987
42940
|
schemaVersion: "external_agent_execution_result.v1",
|
|
@@ -42211,11 +43164,13 @@ registerCertify(program2);
|
|
|
42211
43164
|
registerDiag(program2);
|
|
42212
43165
|
registerBug(program2);
|
|
42213
43166
|
registerProve(program2);
|
|
43167
|
+
registerInteractive(program2);
|
|
42214
43168
|
registerAgentPublishCommand(program2, { publicAlias: true });
|
|
42215
43169
|
registerEval(program2);
|
|
42216
43170
|
registerUpdate(program2);
|
|
42217
43171
|
registerHome(program2);
|
|
42218
43172
|
hideInternalApiUrlOptions(program2);
|
|
43173
|
+
initializeCommandGraph(program2);
|
|
42219
43174
|
maybeDefaultToHome(process.argv);
|
|
42220
43175
|
program2.parseAsync(process.argv).catch((e) => {
|
|
42221
43176
|
if (e instanceof CliSoftExit) {
|