@f-o-h/cli 0.1.83 → 0.1.85
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 -996
- 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",
|
|
@@ -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,21 @@ 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
|
|
33058
|
+
var injectedVersion = true ? String("0.1.85").trim() : "";
|
|
33059
|
+
var envVersion = String(process.env.FOH_CLI_VERSION || process.env.npm_package_version || "").trim();
|
|
33060
|
+
var CLI_VERSION = injectedVersion || envVersion || "0.0.0-dev";
|
|
32996
33061
|
|
|
32997
33062
|
// src/commands/mcp-serve.ts
|
|
32998
33063
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -33177,7 +33242,7 @@ async function runFohCli(params) {
|
|
|
33177
33242
|
effectiveArgv.push("--json");
|
|
33178
33243
|
}
|
|
33179
33244
|
const command = `foh ${effectiveArgv.join(" ")}`;
|
|
33180
|
-
return await new Promise((
|
|
33245
|
+
return await new Promise((resolve15) => {
|
|
33181
33246
|
const child = (0, import_node_child_process.spawn)(process.execPath, [cliEntry, ...effectiveArgv], {
|
|
33182
33247
|
stdio: ["ignore", "pipe", "pipe"],
|
|
33183
33248
|
env: {
|
|
@@ -33202,7 +33267,7 @@ async function runFohCli(params) {
|
|
|
33202
33267
|
});
|
|
33203
33268
|
child.once("error", (error2) => {
|
|
33204
33269
|
clearTimeout(timeoutHandle);
|
|
33205
|
-
|
|
33270
|
+
resolve15({
|
|
33206
33271
|
ok: false,
|
|
33207
33272
|
command,
|
|
33208
33273
|
argv: effectiveArgv,
|
|
@@ -33218,7 +33283,7 @@ async function runFohCli(params) {
|
|
|
33218
33283
|
const stderrText = finalizeBoundedText(stderrBuffer);
|
|
33219
33284
|
const exitCode = Number.isFinite(code ?? NaN) ? Number(code) : 1;
|
|
33220
33285
|
const stdoutJson = tryParseJson(stdoutText);
|
|
33221
|
-
|
|
33286
|
+
resolve15({
|
|
33222
33287
|
ok: !timedOut && exitCode === 0,
|
|
33223
33288
|
command,
|
|
33224
33289
|
argv: effectiveArgv,
|
|
@@ -35388,8 +35453,8 @@ function registerSetup(program3) {
|
|
|
35388
35453
|
}
|
|
35389
35454
|
try {
|
|
35390
35455
|
const manifest = await agentExport(resolvedAgentId, { apiUrlOverride: opts.apiUrl });
|
|
35391
|
-
const { writeFileSync:
|
|
35392
|
-
|
|
35456
|
+
const { writeFileSync: writeFileSync15 } = await import("fs");
|
|
35457
|
+
writeFileSync15(
|
|
35393
35458
|
"tenant.yaml",
|
|
35394
35459
|
`# tenant.yaml - Front Of House agent manifest
|
|
35395
35460
|
# Edit this file and run: foh plan tenant.yaml
|
|
@@ -35559,8 +35624,8 @@ function registerSim(program3) {
|
|
|
35559
35624
|
}
|
|
35560
35625
|
const cert = response.certificate;
|
|
35561
35626
|
if (opts.out) {
|
|
35562
|
-
const { writeFileSync:
|
|
35563
|
-
|
|
35627
|
+
const { writeFileSync: writeFileSync15 } = await import("fs");
|
|
35628
|
+
writeFileSync15(opts.out, JSON.stringify(cert, null, 2) + "\n", "utf-8");
|
|
35564
35629
|
process.stderr.write(` Certificate written to ${opts.out}
|
|
35565
35630
|
`);
|
|
35566
35631
|
}
|
|
@@ -35610,8 +35675,8 @@ function registerSim(program3) {
|
|
|
35610
35675
|
});
|
|
35611
35676
|
}
|
|
35612
35677
|
if (opts.out) {
|
|
35613
|
-
const { writeFileSync:
|
|
35614
|
-
|
|
35678
|
+
const { writeFileSync: writeFileSync15 } = await import("fs");
|
|
35679
|
+
writeFileSync15(opts.out, JSON.stringify(response.certificate, null, 2) + "\n", "utf-8");
|
|
35615
35680
|
process.stderr.write(` Final certificate written to ${opts.out}
|
|
35616
35681
|
`);
|
|
35617
35682
|
}
|
|
@@ -35647,7 +35712,7 @@ ${passIcon} Certification loop summary
|
|
|
35647
35712
|
}
|
|
35648
35713
|
|
|
35649
35714
|
// src/commands/certify.ts
|
|
35650
|
-
var
|
|
35715
|
+
var import_node_fs3 = require("node:fs");
|
|
35651
35716
|
function normalizeProfile(raw) {
|
|
35652
35717
|
const value = String(raw || "release").trim().toLowerCase();
|
|
35653
35718
|
if (value === "smoke" || value === "release" || value === "stress") return value;
|
|
@@ -35725,7 +35790,7 @@ function registerCertify(program3) {
|
|
|
35725
35790
|
next_commands: passed ? [`foh agent publish --agent ${opts.agent} --json`] : [`foh certify run --agent ${opts.agent} --profile ${profile} --json`]
|
|
35726
35791
|
};
|
|
35727
35792
|
if (opts.out) {
|
|
35728
|
-
(0,
|
|
35793
|
+
(0, import_node_fs3.writeFileSync)(opts.out, JSON.stringify(result, null, 2) + "\n", "utf-8");
|
|
35729
35794
|
}
|
|
35730
35795
|
if (opts.json ?? false) {
|
|
35731
35796
|
format(result, { json: true });
|
|
@@ -36675,6 +36740,22 @@ function registerOpsAgencySetupCommands(reporting) {
|
|
|
36675
36740
|
});
|
|
36676
36741
|
format(data, { json: opts.json ?? false });
|
|
36677
36742
|
}));
|
|
36743
|
+
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 () => {
|
|
36744
|
+
const body = {
|
|
36745
|
+
agency_name: String(opts.agencyName),
|
|
36746
|
+
requested_tool_surface: parseCsvOption(opts.tools) ?? [],
|
|
36747
|
+
target_exposure_mode: String(opts.targetMode)
|
|
36748
|
+
};
|
|
36749
|
+
if (opts.sourceUrl) body.source_url = String(opts.sourceUrl);
|
|
36750
|
+
if (opts.branchLocation) body.branch_location = String(opts.branchLocation);
|
|
36751
|
+
const data = await apiFetch("/v1/console/agency-setup/workflow", {
|
|
36752
|
+
method: "POST",
|
|
36753
|
+
body: JSON.stringify(body),
|
|
36754
|
+
orgId: opts.org,
|
|
36755
|
+
apiUrlOverride: opts.apiUrl
|
|
36756
|
+
});
|
|
36757
|
+
format(data, { json: opts.json ?? false });
|
|
36758
|
+
}));
|
|
36678
36759
|
}
|
|
36679
36760
|
|
|
36680
36761
|
// src/commands/ops-diagnostics.ts
|
|
@@ -36929,6 +37010,16 @@ function registerOpsReportingCommands(reporting) {
|
|
|
36929
37010
|
});
|
|
36930
37011
|
format(data, { json: opts.json ?? false });
|
|
36931
37012
|
}));
|
|
37013
|
+
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 () => {
|
|
37014
|
+
const params = new URLSearchParams();
|
|
37015
|
+
if (opts.environment) params.set("environment", String(opts.environment));
|
|
37016
|
+
appendSetupPreviewContext(params, opts);
|
|
37017
|
+
const data = await apiFetch(withQuery("/v1/console/customer-live-status", params), {
|
|
37018
|
+
orgId: opts.org,
|
|
37019
|
+
apiUrlOverride: opts.apiUrl
|
|
37020
|
+
});
|
|
37021
|
+
format(data, { json: opts.json ?? false });
|
|
37022
|
+
}));
|
|
36932
37023
|
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
37024
|
const limit = Math.max(1, Math.min(100, Number(opts.limit || 10) || 10));
|
|
36934
37025
|
const data = await apiFetch(`/v1/console/agents/${opts.agent}/release-scorecard?limit=${limit}`, {
|
|
@@ -37560,41 +37651,80 @@ function defaultImprovementArtifactPath() {
|
|
|
37560
37651
|
}
|
|
37561
37652
|
async function resolveBugReportWizardInputs(opts) {
|
|
37562
37653
|
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
|
-
|
|
37654
|
+
const resolved = await runWizardSession({
|
|
37655
|
+
step: "bug.report",
|
|
37656
|
+
remediation: "Run with --out and --command in non-interactive mode, or use --wizard in a TTY.",
|
|
37657
|
+
title: "Bug Report Wizard",
|
|
37658
|
+
initialState: { ...opts },
|
|
37659
|
+
enabled: true,
|
|
37660
|
+
prompts: [
|
|
37661
|
+
{
|
|
37662
|
+
key: "out",
|
|
37663
|
+
label: "Output file path",
|
|
37664
|
+
defaultValue: () => defaultArtifactPath(),
|
|
37665
|
+
required: true,
|
|
37666
|
+
shouldPrompt: (state) => !String(state.out ?? "").trim()
|
|
37667
|
+
},
|
|
37668
|
+
{
|
|
37669
|
+
key: "command",
|
|
37670
|
+
label: "Failing command",
|
|
37671
|
+
required: true,
|
|
37672
|
+
shouldPrompt: (state) => !String(state.command ?? "").trim()
|
|
37673
|
+
},
|
|
37674
|
+
{
|
|
37675
|
+
key: "requestMethod",
|
|
37676
|
+
label: "Request method",
|
|
37677
|
+
defaultValue: "GET",
|
|
37678
|
+
required: true,
|
|
37679
|
+
shouldPrompt: (state) => !String(state.requestMethod ?? "").trim()
|
|
37680
|
+
},
|
|
37681
|
+
{
|
|
37682
|
+
key: "requestLocation",
|
|
37683
|
+
label: "Request URL or path (for example /v1/console/agents)",
|
|
37684
|
+
required: true,
|
|
37685
|
+
shouldPrompt: (state) => !String(state.requestUrl ?? "").trim() && !String(state.requestPath ?? "").trim(),
|
|
37686
|
+
onResolved: (value, state) => {
|
|
37687
|
+
const location = String(value ?? "").trim();
|
|
37688
|
+
if (location.startsWith("http://") || location.startsWith("https://")) {
|
|
37689
|
+
state.requestUrl = location;
|
|
37690
|
+
state.requestPath = "";
|
|
37691
|
+
} else {
|
|
37692
|
+
state.requestPath = location;
|
|
37693
|
+
state.requestUrl = "";
|
|
37694
|
+
}
|
|
37695
|
+
}
|
|
37696
|
+
},
|
|
37697
|
+
{
|
|
37698
|
+
key: "responseStatus",
|
|
37699
|
+
label: "Response status",
|
|
37700
|
+
defaultValue: "500",
|
|
37701
|
+
required: true,
|
|
37702
|
+
shouldPrompt: (state) => !String(state.responseStatus ?? "").trim()
|
|
37703
|
+
},
|
|
37704
|
+
{
|
|
37705
|
+
key: "severity",
|
|
37706
|
+
label: "Severity",
|
|
37707
|
+
defaultValue: "medium",
|
|
37708
|
+
required: true,
|
|
37709
|
+
shouldPrompt: (state) => !String(state.severity ?? "").trim()
|
|
37710
|
+
},
|
|
37711
|
+
{
|
|
37712
|
+
key: "source",
|
|
37713
|
+
label: "Source",
|
|
37714
|
+
defaultValue: "cli",
|
|
37715
|
+
required: true,
|
|
37716
|
+
shouldPrompt: (state) => !String(state.source ?? "").trim()
|
|
37717
|
+
},
|
|
37718
|
+
{
|
|
37719
|
+
key: "submit",
|
|
37720
|
+
label: "Submit packet to FOH API now? [y/N]",
|
|
37721
|
+
kind: "confirm",
|
|
37722
|
+
confirmDefault: false,
|
|
37723
|
+
shouldPrompt: (state) => state.submit === void 0
|
|
37724
|
+
}
|
|
37725
|
+
]
|
|
37726
|
+
});
|
|
37727
|
+
delete resolved.requestLocation;
|
|
37598
37728
|
return resolved;
|
|
37599
37729
|
}
|
|
37600
37730
|
function ensureBugReportRequiredInputs(opts) {
|
|
@@ -37790,7 +37920,7 @@ function registerBug(program3) {
|
|
|
37790
37920
|
|
|
37791
37921
|
// src/lib/proof-cache.ts
|
|
37792
37922
|
var import_node_crypto2 = require("node:crypto");
|
|
37793
|
-
var
|
|
37923
|
+
var import_node_fs4 = require("node:fs");
|
|
37794
37924
|
var import_node_path = require("node:path");
|
|
37795
37925
|
var DEFAULT_MAX_AGE_MS = 15 * 60 * 1e3;
|
|
37796
37926
|
var DEFAULT_WAIT_MS = 180 * 1e3;
|
|
@@ -37814,7 +37944,7 @@ function publicPath(filePath) {
|
|
|
37814
37944
|
}
|
|
37815
37945
|
function readFreshCache(filePath, maxAgeMs) {
|
|
37816
37946
|
try {
|
|
37817
|
-
const payload = JSON.parse((0,
|
|
37947
|
+
const payload = JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf8"));
|
|
37818
37948
|
const createdAt = Date.parse(String(payload.created_at || ""));
|
|
37819
37949
|
if (!Number.isFinite(createdAt)) return null;
|
|
37820
37950
|
const ageMs = Date.now() - createdAt;
|
|
@@ -37826,8 +37956,8 @@ function readFreshCache(filePath, maxAgeMs) {
|
|
|
37826
37956
|
}
|
|
37827
37957
|
}
|
|
37828
37958
|
function writeCache(filePath, value) {
|
|
37829
|
-
(0,
|
|
37830
|
-
(0,
|
|
37959
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path.dirname)(filePath), { recursive: true });
|
|
37960
|
+
(0, import_node_fs4.writeFileSync)(filePath, `${JSON.stringify({
|
|
37831
37961
|
schema_version: "foh_proof_cache_entry.v1",
|
|
37832
37962
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37833
37963
|
value
|
|
@@ -37862,7 +37992,7 @@ async function withProofCache(options, run) {
|
|
|
37862
37992
|
const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
37863
37993
|
const waitMs = options.waitMs ?? Number(process.env.FOH_PROOF_CACHE_WAIT_MS || DEFAULT_WAIT_MS);
|
|
37864
37994
|
const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
|
|
37865
|
-
(0,
|
|
37995
|
+
(0, import_node_fs4.mkdirSync)(resolvedDir, { recursive: true });
|
|
37866
37996
|
const existing = readFreshCache(cachePath, maxAgeMs);
|
|
37867
37997
|
if (existing) {
|
|
37868
37998
|
return {
|
|
@@ -37881,7 +38011,7 @@ async function withProofCache(options, run) {
|
|
|
37881
38011
|
}
|
|
37882
38012
|
let lockOwner = false;
|
|
37883
38013
|
try {
|
|
37884
|
-
(0,
|
|
38014
|
+
(0, import_node_fs4.mkdirSync)(lockPath);
|
|
37885
38015
|
lockOwner = true;
|
|
37886
38016
|
} catch {
|
|
37887
38017
|
const started = Date.now();
|
|
@@ -37922,7 +38052,7 @@ async function withProofCache(options, run) {
|
|
|
37922
38052
|
}
|
|
37923
38053
|
};
|
|
37924
38054
|
} finally {
|
|
37925
|
-
if (lockOwner) (0,
|
|
38055
|
+
if (lockOwner) (0, import_node_fs4.rmSync)(lockPath, { recursive: true, force: true });
|
|
37926
38056
|
}
|
|
37927
38057
|
}
|
|
37928
38058
|
|
|
@@ -38412,7 +38542,582 @@ function registerProve(program3) {
|
|
|
38412
38542
|
}));
|
|
38413
38543
|
}
|
|
38414
38544
|
|
|
38415
|
-
// src/
|
|
38545
|
+
// src/commands/interactive.ts
|
|
38546
|
+
var import_readline2 = require("readline");
|
|
38547
|
+
var import_child_process2 = require("child_process");
|
|
38548
|
+
var import_fs11 = require("fs");
|
|
38549
|
+
var import_os2 = require("os");
|
|
38550
|
+
var import_path9 = require("path");
|
|
38551
|
+
|
|
38552
|
+
// src/commands/command-graph.ts
|
|
38553
|
+
var COMMAND_SURFACE_DEFINITIONS = [
|
|
38554
|
+
{
|
|
38555
|
+
id: "home",
|
|
38556
|
+
commandPath: ["home"],
|
|
38557
|
+
label: "home",
|
|
38558
|
+
descriptionFallback: "Open onboarding home",
|
|
38559
|
+
mutatesState: "read",
|
|
38560
|
+
shellSlash: "/home",
|
|
38561
|
+
includeInSuggestions: false,
|
|
38562
|
+
homeContexts: []
|
|
38563
|
+
},
|
|
38564
|
+
{
|
|
38565
|
+
id: "start",
|
|
38566
|
+
commandPath: ["home"],
|
|
38567
|
+
invokeArgs: ["start"],
|
|
38568
|
+
label: "start",
|
|
38569
|
+
descriptionFallback: "Run guided onboarding start",
|
|
38570
|
+
mutatesState: "read",
|
|
38571
|
+
shellSlash: "/start",
|
|
38572
|
+
includeInSuggestions: false,
|
|
38573
|
+
homeContexts: []
|
|
38574
|
+
},
|
|
38575
|
+
{
|
|
38576
|
+
id: "auth_login",
|
|
38577
|
+
commandPath: ["auth", "login"],
|
|
38578
|
+
label: "login now (recommended)",
|
|
38579
|
+
descriptionFallback: "Run interactive login flow.",
|
|
38580
|
+
mutatesState: "write",
|
|
38581
|
+
examples: ["foh auth login --wizard"],
|
|
38582
|
+
homeContexts: ["unauthenticated"],
|
|
38583
|
+
homeAliases: ["start", "login", "auth", "auth login"],
|
|
38584
|
+
includeInSuggestions: true
|
|
38585
|
+
},
|
|
38586
|
+
{
|
|
38587
|
+
id: "auth_login_help",
|
|
38588
|
+
commandPath: ["auth", "login"],
|
|
38589
|
+
invokeArgs: ["auth", "login", "--help"],
|
|
38590
|
+
label: "auth login help",
|
|
38591
|
+
descriptionFallback: "Show auth login examples.",
|
|
38592
|
+
mutatesState: "read",
|
|
38593
|
+
homeContexts: ["unauthenticated"],
|
|
38594
|
+
includeInSuggestions: true
|
|
38595
|
+
},
|
|
38596
|
+
{
|
|
38597
|
+
id: "auth_login_wizard",
|
|
38598
|
+
commandPath: ["auth", "login"],
|
|
38599
|
+
invokeArgs: ["auth", "login", "--wizard"],
|
|
38600
|
+
label: "auth login wizard",
|
|
38601
|
+
descriptionFallback: "Run login wizard",
|
|
38602
|
+
mutatesState: "write",
|
|
38603
|
+
shellSlash: "/login",
|
|
38604
|
+
includeInSuggestions: true
|
|
38605
|
+
},
|
|
38606
|
+
{
|
|
38607
|
+
id: "auth_whoami",
|
|
38608
|
+
commandPath: ["auth", "whoami"],
|
|
38609
|
+
label: "auth whoami",
|
|
38610
|
+
descriptionFallback: "Show current auth context",
|
|
38611
|
+
requiresAuth: true,
|
|
38612
|
+
mutatesState: "read",
|
|
38613
|
+
homeContexts: ["authenticated_no_org"],
|
|
38614
|
+
homeAliases: ["whoami"],
|
|
38615
|
+
shellSlash: "/auth",
|
|
38616
|
+
includeInSuggestions: true
|
|
38617
|
+
},
|
|
38618
|
+
{
|
|
38619
|
+
id: "org_list",
|
|
38620
|
+
commandPath: ["org", "list"],
|
|
38621
|
+
label: "list my orgs",
|
|
38622
|
+
descriptionFallback: "View org memberships.",
|
|
38623
|
+
requiresAuth: true,
|
|
38624
|
+
mutatesState: "read",
|
|
38625
|
+
homeContexts: ["authenticated_no_org"],
|
|
38626
|
+
homeAliases: ["org"],
|
|
38627
|
+
shellSlash: "/orgs",
|
|
38628
|
+
includeInSuggestions: true
|
|
38629
|
+
},
|
|
38630
|
+
{
|
|
38631
|
+
id: "org_use_help",
|
|
38632
|
+
commandPath: ["org", "use"],
|
|
38633
|
+
invokeArgs: ["org", "use", "--help"],
|
|
38634
|
+
label: "org use help",
|
|
38635
|
+
descriptionFallback: "Set default org guidance.",
|
|
38636
|
+
requiresAuth: true,
|
|
38637
|
+
mutatesState: "read",
|
|
38638
|
+
homeContexts: ["authenticated_no_org"],
|
|
38639
|
+
includeInSuggestions: true
|
|
38640
|
+
},
|
|
38641
|
+
{
|
|
38642
|
+
id: "tenant_status",
|
|
38643
|
+
commandPath: ["tenant", "status"],
|
|
38644
|
+
label: "tenant status",
|
|
38645
|
+
descriptionFallback: "Check tenant status and readiness.",
|
|
38646
|
+
requiresAuth: true,
|
|
38647
|
+
requiresOrg: true,
|
|
38648
|
+
mutatesState: "read",
|
|
38649
|
+
homeContexts: ["authenticated_with_org"],
|
|
38650
|
+
homeAliases: ["tenant"],
|
|
38651
|
+
shellSlash: "/status",
|
|
38652
|
+
includeInSuggestions: true
|
|
38653
|
+
},
|
|
38654
|
+
{
|
|
38655
|
+
id: "templates_list",
|
|
38656
|
+
commandPath: ["templates", "list"],
|
|
38657
|
+
label: "template list",
|
|
38658
|
+
descriptionFallback: "List templates.",
|
|
38659
|
+
requiresAuth: true,
|
|
38660
|
+
requiresOrg: true,
|
|
38661
|
+
mutatesState: "read",
|
|
38662
|
+
homeContexts: ["authenticated_with_org"],
|
|
38663
|
+
homeAliases: ["templates", "template list"],
|
|
38664
|
+
shellSlash: "/templates",
|
|
38665
|
+
includeInSuggestions: true
|
|
38666
|
+
},
|
|
38667
|
+
{
|
|
38668
|
+
id: "agent_list_all",
|
|
38669
|
+
commandPath: ["agent", "list"],
|
|
38670
|
+
invokeArgs: ["agent", "list", "--segment", "all"],
|
|
38671
|
+
label: "agent list",
|
|
38672
|
+
descriptionFallback: "List all agents.",
|
|
38673
|
+
requiresAuth: true,
|
|
38674
|
+
requiresOrg: true,
|
|
38675
|
+
mutatesState: "read",
|
|
38676
|
+
homeContexts: ["authenticated_with_org"],
|
|
38677
|
+
homeAliases: ["agent"],
|
|
38678
|
+
shellSlash: "/agent",
|
|
38679
|
+
includeInSuggestions: true
|
|
38680
|
+
},
|
|
38681
|
+
{
|
|
38682
|
+
id: "setup_help",
|
|
38683
|
+
commandPath: ["setup"],
|
|
38684
|
+
invokeArgs: ["setup", "--help"],
|
|
38685
|
+
label: "setup help",
|
|
38686
|
+
descriptionFallback: "Show setup flow options.",
|
|
38687
|
+
mutatesState: "read",
|
|
38688
|
+
homeContexts: ["authenticated_with_org"],
|
|
38689
|
+
homeAliases: ["setup"],
|
|
38690
|
+
shellSlash: "/setup",
|
|
38691
|
+
includeInSuggestions: true
|
|
38692
|
+
},
|
|
38693
|
+
{
|
|
38694
|
+
id: "bug_report_help",
|
|
38695
|
+
commandPath: ["bug", "report"],
|
|
38696
|
+
invokeArgs: ["bug", "report", "--help"],
|
|
38697
|
+
label: "bug report help",
|
|
38698
|
+
descriptionFallback: "Show bug-report guidance.",
|
|
38699
|
+
mutatesState: "read",
|
|
38700
|
+
homeContexts: ["authenticated_with_org"],
|
|
38701
|
+
homeAliases: ["bug report", "bug report help"],
|
|
38702
|
+
includeInSuggestions: false
|
|
38703
|
+
},
|
|
38704
|
+
{
|
|
38705
|
+
id: "bug_list",
|
|
38706
|
+
commandPath: ["bug", "list"],
|
|
38707
|
+
label: "bug list",
|
|
38708
|
+
descriptionFallback: "List centralized bug reports.",
|
|
38709
|
+
requiresAuth: true,
|
|
38710
|
+
requiresOrg: true,
|
|
38711
|
+
mutatesState: "read",
|
|
38712
|
+
homeContexts: ["authenticated_with_org"],
|
|
38713
|
+
homeAliases: ["bug list"],
|
|
38714
|
+
includeInSuggestions: false
|
|
38715
|
+
},
|
|
38716
|
+
{
|
|
38717
|
+
id: "interactive_shell",
|
|
38718
|
+
commandPath: ["interactive"],
|
|
38719
|
+
label: "interactive shell",
|
|
38720
|
+
descriptionFallback: "Open slash-command shell.",
|
|
38721
|
+
mutatesState: "read",
|
|
38722
|
+
homeContexts: ["unauthenticated", "authenticated_no_org", "authenticated_with_org"],
|
|
38723
|
+
homeAliases: ["interactive", "interactive shell", "shell"],
|
|
38724
|
+
includeInSuggestions: false
|
|
38725
|
+
},
|
|
38726
|
+
{
|
|
38727
|
+
id: "full_help",
|
|
38728
|
+
commandPath: ["home"],
|
|
38729
|
+
invokeArgs: ["--help"],
|
|
38730
|
+
label: "full help",
|
|
38731
|
+
descriptionFallback: "Show all CLI commands.",
|
|
38732
|
+
mutatesState: "read",
|
|
38733
|
+
homeContexts: ["unauthenticated", "authenticated_no_org"],
|
|
38734
|
+
homeAliases: ["full help"],
|
|
38735
|
+
includeInSuggestions: false
|
|
38736
|
+
},
|
|
38737
|
+
{
|
|
38738
|
+
id: "whatsapp_start",
|
|
38739
|
+
commandPath: ["channel", "whatsapp", "start"],
|
|
38740
|
+
label: "whatsapp start",
|
|
38741
|
+
descriptionFallback: "WhatsApp readiness path",
|
|
38742
|
+
mutatesState: "write",
|
|
38743
|
+
shellSlash: "/whatsapp",
|
|
38744
|
+
includeInSuggestions: true
|
|
38745
|
+
},
|
|
38746
|
+
{
|
|
38747
|
+
id: "voice_help",
|
|
38748
|
+
commandPath: ["voice"],
|
|
38749
|
+
invokeArgs: ["voice", "--help"],
|
|
38750
|
+
label: "voice help",
|
|
38751
|
+
descriptionFallback: "Voice command help",
|
|
38752
|
+
mutatesState: "read",
|
|
38753
|
+
shellSlash: "/voice",
|
|
38754
|
+
includeInSuggestions: true
|
|
38755
|
+
},
|
|
38756
|
+
{
|
|
38757
|
+
id: "widget_help",
|
|
38758
|
+
commandPath: ["widget"],
|
|
38759
|
+
invokeArgs: ["widget", "--help"],
|
|
38760
|
+
label: "widget help",
|
|
38761
|
+
descriptionFallback: "Widget command help",
|
|
38762
|
+
mutatesState: "read",
|
|
38763
|
+
shellSlash: "/widget",
|
|
38764
|
+
includeInSuggestions: true
|
|
38765
|
+
},
|
|
38766
|
+
{
|
|
38767
|
+
id: "diag_debug",
|
|
38768
|
+
commandPath: ["diag"],
|
|
38769
|
+
invokeArgs: ["debug"],
|
|
38770
|
+
label: "debug",
|
|
38771
|
+
descriptionFallback: "Run diagnostics",
|
|
38772
|
+
mutatesState: "read",
|
|
38773
|
+
shellSlash: "/debug",
|
|
38774
|
+
includeInSuggestions: true
|
|
38775
|
+
}
|
|
38776
|
+
];
|
|
38777
|
+
var SHELL_BUILTIN_COMMANDS = [
|
|
38778
|
+
{ slash: "/help", description: "Show shell help" },
|
|
38779
|
+
{ slash: "/?", description: "Show keymap help" },
|
|
38780
|
+
{ slash: "/keys", description: "Show keymap help" },
|
|
38781
|
+
{ slash: "/commands", description: "List available slash commands" },
|
|
38782
|
+
{ slash: "/again", description: "Replay last successful command" },
|
|
38783
|
+
{ slash: "/retry", description: "Replay last failed command" },
|
|
38784
|
+
{ slash: "/editlast", description: "Prefill last command for editing" },
|
|
38785
|
+
{ slash: "/snippet", description: "Manage named command snippets" },
|
|
38786
|
+
{ slash: "/clear", description: "Clear the terminal output" },
|
|
38787
|
+
{ slash: "/exit", description: "Exit shell" },
|
|
38788
|
+
{ slash: "/quit", description: "Exit shell" }
|
|
38789
|
+
];
|
|
38790
|
+
var HOME_QUICK_ACTION_ORDER = {
|
|
38791
|
+
unauthenticated: ["auth_login", "interactive_shell", "full_help", "auth_login_help"],
|
|
38792
|
+
authenticated_no_org: ["org_list", "interactive_shell", "org_use_help", "auth_whoami", "full_help"],
|
|
38793
|
+
authenticated_with_org: [
|
|
38794
|
+
"tenant_status",
|
|
38795
|
+
"interactive_shell",
|
|
38796
|
+
"templates_list",
|
|
38797
|
+
"agent_list_all",
|
|
38798
|
+
"setup_help",
|
|
38799
|
+
"bug_report_help",
|
|
38800
|
+
"bug_list"
|
|
38801
|
+
]
|
|
38802
|
+
};
|
|
38803
|
+
var commandPathIndex = /* @__PURE__ */ new Map();
|
|
38804
|
+
function contextFromState(state) {
|
|
38805
|
+
if (!state.authenticated) return "unauthenticated";
|
|
38806
|
+
if (!state.orgId) return "authenticated_no_org";
|
|
38807
|
+
return "authenticated_with_org";
|
|
38808
|
+
}
|
|
38809
|
+
function commandKey(parts) {
|
|
38810
|
+
return parts.join(" ");
|
|
38811
|
+
}
|
|
38812
|
+
function normalizeCommandToken(value) {
|
|
38813
|
+
return String(value ?? "").trim().toLowerCase();
|
|
38814
|
+
}
|
|
38815
|
+
function readCommandDescription(command) {
|
|
38816
|
+
try {
|
|
38817
|
+
return (command.description() ?? "").trim();
|
|
38818
|
+
} catch {
|
|
38819
|
+
return "";
|
|
38820
|
+
}
|
|
38821
|
+
}
|
|
38822
|
+
function collectCommandPaths(program3) {
|
|
38823
|
+
const paths = /* @__PURE__ */ new Map();
|
|
38824
|
+
const walk = (parentPath, scope) => {
|
|
38825
|
+
for (const child of scope.commands) {
|
|
38826
|
+
const childName = normalizeCommandToken(child.name());
|
|
38827
|
+
if (!childName) continue;
|
|
38828
|
+
const nextPath = [...parentPath, childName];
|
|
38829
|
+
const canonicalPathKey = commandKey(nextPath);
|
|
38830
|
+
const description = readCommandDescription(child);
|
|
38831
|
+
paths.set(canonicalPathKey, { pathKey: canonicalPathKey, description });
|
|
38832
|
+
const aliases = child.aliases().map((alias) => normalizeCommandToken(alias)).filter(Boolean);
|
|
38833
|
+
for (const alias of aliases) {
|
|
38834
|
+
const aliasPath = [...parentPath, alias];
|
|
38835
|
+
paths.set(commandKey(aliasPath), { pathKey: canonicalPathKey, description });
|
|
38836
|
+
}
|
|
38837
|
+
walk(nextPath, child);
|
|
38838
|
+
}
|
|
38839
|
+
};
|
|
38840
|
+
walk([], program3);
|
|
38841
|
+
return paths;
|
|
38842
|
+
}
|
|
38843
|
+
function resolveDescription(definition) {
|
|
38844
|
+
const record2 = commandPathIndex.get(commandKey(definition.commandPath));
|
|
38845
|
+
if (record2 && record2.description) return record2.description;
|
|
38846
|
+
return definition.descriptionFallback;
|
|
38847
|
+
}
|
|
38848
|
+
function buildEntry(definition) {
|
|
38849
|
+
return {
|
|
38850
|
+
id: definition.id,
|
|
38851
|
+
label: definition.label,
|
|
38852
|
+
description: resolveDescription(definition),
|
|
38853
|
+
args: [...definition.invokeArgs ?? definition.commandPath],
|
|
38854
|
+
commandPath: [...definition.commandPath],
|
|
38855
|
+
requiresAuth: definition.requiresAuth === true,
|
|
38856
|
+
requiresOrg: definition.requiresOrg === true,
|
|
38857
|
+
mutatesState: definition.mutatesState,
|
|
38858
|
+
examples: [...definition.examples ?? []],
|
|
38859
|
+
homeContexts: [...definition.homeContexts ?? []],
|
|
38860
|
+
homeAliases: [...definition.homeAliases ?? []],
|
|
38861
|
+
shellSlash: definition.shellSlash,
|
|
38862
|
+
includeInSuggestions: definition.includeInSuggestions !== false
|
|
38863
|
+
};
|
|
38864
|
+
}
|
|
38865
|
+
function initializeCommandGraph(program3) {
|
|
38866
|
+
commandPathIndex = collectCommandPaths(program3);
|
|
38867
|
+
const missing = COMMAND_SURFACE_DEFINITIONS.filter((definition) => !commandPathIndex.has(commandKey(definition.commandPath))).map((definition) => definition.id);
|
|
38868
|
+
if (missing.length > 0) {
|
|
38869
|
+
throw new Error(`command_graph_definition_missing_paths:${missing.join(",")}`);
|
|
38870
|
+
}
|
|
38871
|
+
}
|
|
38872
|
+
function getCommandGraphEntries() {
|
|
38873
|
+
return COMMAND_SURFACE_DEFINITIONS.map(buildEntry);
|
|
38874
|
+
}
|
|
38875
|
+
function getHomeQuickActions(state) {
|
|
38876
|
+
const context = contextFromState(state);
|
|
38877
|
+
const rank = new Map(HOME_QUICK_ACTION_ORDER[context].map((id, index) => [id, index]));
|
|
38878
|
+
return getCommandGraphEntries().filter((entry) => entry.homeContexts.includes(context)).sort((left, right) => {
|
|
38879
|
+
const leftRank = rank.get(left.id) ?? Number.MAX_SAFE_INTEGER;
|
|
38880
|
+
const rightRank = rank.get(right.id) ?? Number.MAX_SAFE_INTEGER;
|
|
38881
|
+
if (leftRank !== rightRank) return leftRank - rightRank;
|
|
38882
|
+
return left.label.localeCompare(right.label);
|
|
38883
|
+
}).map((entry) => ({
|
|
38884
|
+
label: entry.label,
|
|
38885
|
+
description: entry.description,
|
|
38886
|
+
args: [...entry.args],
|
|
38887
|
+
aliases: [...entry.homeAliases]
|
|
38888
|
+
}));
|
|
38889
|
+
}
|
|
38890
|
+
function getHomePaletteCommands(state) {
|
|
38891
|
+
return getCommandGraphEntries().filter((entry) => {
|
|
38892
|
+
if (entry.requiresAuth && !state.authenticated) return false;
|
|
38893
|
+
if (entry.requiresOrg && (!state.authenticated || !state.orgId)) return false;
|
|
38894
|
+
if (entry.homeContexts.length > 0) return true;
|
|
38895
|
+
return Boolean(entry.shellSlash || entry.includeInSuggestions);
|
|
38896
|
+
}).map((entry) => ({
|
|
38897
|
+
label: entry.args.join(" "),
|
|
38898
|
+
description: entry.description,
|
|
38899
|
+
args: [...entry.args],
|
|
38900
|
+
requiresAuth: entry.requiresAuth || void 0,
|
|
38901
|
+
requiresOrg: entry.requiresOrg || void 0,
|
|
38902
|
+
mutatesState: entry.mutatesState,
|
|
38903
|
+
examples: [...entry.examples]
|
|
38904
|
+
}));
|
|
38905
|
+
}
|
|
38906
|
+
function getInteractiveSlashCommands() {
|
|
38907
|
+
const generated = getCommandGraphEntries().filter((entry) => typeof entry.shellSlash === "string").map((entry) => ({
|
|
38908
|
+
slash: entry.shellSlash,
|
|
38909
|
+
description: entry.description,
|
|
38910
|
+
args: [...entry.args]
|
|
38911
|
+
})).sort((left, right) => left.slash.localeCompare(right.slash));
|
|
38912
|
+
return [...SHELL_BUILTIN_COMMANDS, ...generated];
|
|
38913
|
+
}
|
|
38914
|
+
function getInteractiveCommandSuggestions() {
|
|
38915
|
+
return getCommandGraphEntries().filter((entry) => entry.includeInSuggestions).map((entry) => entry.args.join(" ")).filter((value, index, values) => values.indexOf(value) === index);
|
|
38916
|
+
}
|
|
38917
|
+
function normalizeArgs(args) {
|
|
38918
|
+
return args.map((token) => normalizeCommandToken(token)).filter(Boolean);
|
|
38919
|
+
}
|
|
38920
|
+
function startsWithTokens(input, expected) {
|
|
38921
|
+
if (expected.length === 0 || input.length < expected.length) return false;
|
|
38922
|
+
for (let index = 0; index < expected.length; index += 1) {
|
|
38923
|
+
if (input[index] !== expected[index]) return false;
|
|
38924
|
+
}
|
|
38925
|
+
return true;
|
|
38926
|
+
}
|
|
38927
|
+
function getCommandGraphEntryForArgs(args) {
|
|
38928
|
+
const normalizedArgs = normalizeArgs(args);
|
|
38929
|
+
if (normalizedArgs.length === 0) return void 0;
|
|
38930
|
+
const entries = getCommandGraphEntries().map((entry) => ({
|
|
38931
|
+
entry,
|
|
38932
|
+
normalizedEntryArgs: normalizeArgs(entry.args),
|
|
38933
|
+
normalizedCommandPath: normalizeArgs(entry.commandPath),
|
|
38934
|
+
entryArgsMatch: 0,
|
|
38935
|
+
commandPathMatch: 0
|
|
38936
|
+
})).map((candidate) => ({
|
|
38937
|
+
...candidate,
|
|
38938
|
+
entryArgsMatch: startsWithTokens(normalizedArgs, candidate.normalizedEntryArgs) ? candidate.normalizedEntryArgs.length : 0,
|
|
38939
|
+
commandPathMatch: startsWithTokens(normalizedArgs, candidate.normalizedCommandPath) ? candidate.normalizedCommandPath.length : 0
|
|
38940
|
+
})).filter(({ entryArgsMatch, commandPathMatch }) => entryArgsMatch > 0 || commandPathMatch > 0).sort((left, right) => {
|
|
38941
|
+
if (left.entryArgsMatch !== right.entryArgsMatch) return right.entryArgsMatch - left.entryArgsMatch;
|
|
38942
|
+
if (left.commandPathMatch !== right.commandPathMatch) return right.commandPathMatch - left.commandPathMatch;
|
|
38943
|
+
return left.entry.id.localeCompare(right.entry.id);
|
|
38944
|
+
});
|
|
38945
|
+
return entries[0]?.entry;
|
|
38946
|
+
}
|
|
38947
|
+
function classifyCommandMutation(args) {
|
|
38948
|
+
return getCommandGraphEntryForArgs(args)?.mutatesState ?? "read";
|
|
38949
|
+
}
|
|
38950
|
+
|
|
38951
|
+
// src/commands/interactive.ts
|
|
38952
|
+
var SLASH_COMMANDS = getInteractiveSlashCommands();
|
|
38953
|
+
var COMMAND_SUGGESTIONS = getInteractiveCommandSuggestions();
|
|
38954
|
+
function nowIso2() {
|
|
38955
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
38956
|
+
}
|
|
38957
|
+
function interactiveSessionArtifactPath() {
|
|
38958
|
+
const override = String(process.env.FOH_CLI_INTERACTIVE_SESSION_OUT ?? "").trim();
|
|
38959
|
+
if (override.length > 0) return (0, import_path9.resolve)(override);
|
|
38960
|
+
return (0, import_path9.resolve)("test-results/interactive-shell-session.latest.json");
|
|
38961
|
+
}
|
|
38962
|
+
function createInteractiveSessionArtifact() {
|
|
38963
|
+
const seed = `${Date.now().toString(16)}${Math.random().toString(16).slice(2, 10)}`;
|
|
38964
|
+
return {
|
|
38965
|
+
schema_version: "foh_cli_interactive_session.v1",
|
|
38966
|
+
session_id: `shell_${seed}`,
|
|
38967
|
+
started_at: nowIso2(),
|
|
38968
|
+
status: "idle",
|
|
38969
|
+
command_count: 0,
|
|
38970
|
+
failure_count: 0,
|
|
38971
|
+
events: []
|
|
38972
|
+
};
|
|
38973
|
+
}
|
|
38974
|
+
function detectTerminalCapabilities() {
|
|
38975
|
+
const stdinTty = Boolean(process.stdin.isTTY);
|
|
38976
|
+
const stdoutTty = Boolean(process.stdout.isTTY);
|
|
38977
|
+
const supportsRawMode = stdinTty && typeof process.stdin.setRawMode === "function";
|
|
38978
|
+
const term = String(process.env.TERM ?? "").trim().toLowerCase();
|
|
38979
|
+
const supportsAnsiClear = stdoutTty && term !== "dumb";
|
|
38980
|
+
return {
|
|
38981
|
+
stdinTty,
|
|
38982
|
+
stdoutTty,
|
|
38983
|
+
supportsRawMode,
|
|
38984
|
+
supportsLinePrefill: supportsRawMode,
|
|
38985
|
+
supportsAnsiClear
|
|
38986
|
+
};
|
|
38987
|
+
}
|
|
38988
|
+
function recordInteractiveShellEvent(artifact, event) {
|
|
38989
|
+
artifact.events.push({
|
|
38990
|
+
at: nowIso2(),
|
|
38991
|
+
...event
|
|
38992
|
+
});
|
|
38993
|
+
if (event.type === "command_started") {
|
|
38994
|
+
artifact.status = "running";
|
|
38995
|
+
artifact.command_count += 1;
|
|
38996
|
+
artifact.last_command = event.command;
|
|
38997
|
+
} else if (event.type === "command_succeeded") {
|
|
38998
|
+
artifact.status = "idle";
|
|
38999
|
+
artifact.last_command = event.command;
|
|
39000
|
+
} else if (event.type === "command_failed" || event.type === "shell_error") {
|
|
39001
|
+
artifact.status = "failed";
|
|
39002
|
+
artifact.failure_count += 1;
|
|
39003
|
+
artifact.last_error = event.reason;
|
|
39004
|
+
artifact.last_command = event.command;
|
|
39005
|
+
} else if (event.type === "command_blocked") {
|
|
39006
|
+
artifact.status = "idle";
|
|
39007
|
+
artifact.last_error = event.reason;
|
|
39008
|
+
artifact.last_command = event.command;
|
|
39009
|
+
} else if (event.type === "shell_closed") {
|
|
39010
|
+
artifact.status = artifact.status === "failed" ? "failed" : "closed";
|
|
39011
|
+
artifact.completed_at = nowIso2();
|
|
39012
|
+
}
|
|
39013
|
+
}
|
|
39014
|
+
function flushInteractiveSessionArtifact(artifact) {
|
|
39015
|
+
const outputPath = interactiveSessionArtifactPath();
|
|
39016
|
+
(0, import_fs11.mkdirSync)((0, import_path9.dirname)(outputPath), { recursive: true });
|
|
39017
|
+
(0, import_fs11.writeFileSync)(outputPath, JSON.stringify(artifact, null, 2), "utf8");
|
|
39018
|
+
}
|
|
39019
|
+
function interactiveMemoryPath() {
|
|
39020
|
+
const override = String(process.env.FOH_CLI_INTERACTIVE_MEMORY_OUT ?? "").trim();
|
|
39021
|
+
if (override.length > 0) return (0, import_path9.resolve)(override);
|
|
39022
|
+
return (0, import_path9.resolve)((0, import_os2.homedir)(), ".foh", "interactive-shell-memory.json");
|
|
39023
|
+
}
|
|
39024
|
+
function loadInteractiveShellMemory() {
|
|
39025
|
+
const outputPath = interactiveMemoryPath();
|
|
39026
|
+
if (!(0, import_fs11.existsSync)(outputPath)) {
|
|
39027
|
+
return {
|
|
39028
|
+
schema_version: "foh_cli_interactive_memory.v1",
|
|
39029
|
+
updated_at: nowIso2(),
|
|
39030
|
+
history: [],
|
|
39031
|
+
snippets: {}
|
|
39032
|
+
};
|
|
39033
|
+
}
|
|
39034
|
+
try {
|
|
39035
|
+
const raw = JSON.parse((0, import_fs11.readFileSync)(outputPath, "utf8"));
|
|
39036
|
+
return {
|
|
39037
|
+
schema_version: "foh_cli_interactive_memory.v1",
|
|
39038
|
+
updated_at: String(raw.updated_at ?? nowIso2()),
|
|
39039
|
+
history: Array.isArray(raw.history) ? raw.history.map((entry) => String(entry)).filter(Boolean) : [],
|
|
39040
|
+
snippets: raw && typeof raw.snippets === "object" && raw.snippets !== null ? Object.fromEntries(
|
|
39041
|
+
Object.entries(raw.snippets).map(([key, value]) => [String(key), String(value)])
|
|
39042
|
+
) : {}
|
|
39043
|
+
};
|
|
39044
|
+
} catch {
|
|
39045
|
+
return {
|
|
39046
|
+
schema_version: "foh_cli_interactive_memory.v1",
|
|
39047
|
+
updated_at: nowIso2(),
|
|
39048
|
+
history: [],
|
|
39049
|
+
snippets: {}
|
|
39050
|
+
};
|
|
39051
|
+
}
|
|
39052
|
+
}
|
|
39053
|
+
function saveInteractiveShellMemory(memory) {
|
|
39054
|
+
const outputPath = interactiveMemoryPath();
|
|
39055
|
+
(0, import_fs11.mkdirSync)((0, import_path9.dirname)(outputPath), { recursive: true });
|
|
39056
|
+
(0, import_fs11.writeFileSync)(outputPath, JSON.stringify({ ...memory, updated_at: nowIso2() }, null, 2), "utf8");
|
|
39057
|
+
}
|
|
39058
|
+
function interactiveSessionReportPath() {
|
|
39059
|
+
const override = String(process.env.FOH_CLI_INTERACTIVE_SESSION_REPORT_OUT ?? "").trim();
|
|
39060
|
+
if (override.length > 0) return (0, import_path9.resolve)(override);
|
|
39061
|
+
return (0, import_path9.resolve)("test-results/interactive-cli-session-report.latest.json");
|
|
39062
|
+
}
|
|
39063
|
+
function median(values) {
|
|
39064
|
+
if (values.length === 0) return null;
|
|
39065
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
39066
|
+
const middle = Math.floor(sorted.length / 2);
|
|
39067
|
+
if (sorted.length % 2 === 1) return sorted[middle];
|
|
39068
|
+
return Math.round((sorted[middle - 1] + sorted[middle]) / 2);
|
|
39069
|
+
}
|
|
39070
|
+
function buildInteractiveSessionReport(artifact) {
|
|
39071
|
+
const commandStarts = [];
|
|
39072
|
+
const successDurations = [];
|
|
39073
|
+
const errorFamilies = {};
|
|
39074
|
+
let completedCommands = 0;
|
|
39075
|
+
let deadEndCount = 0;
|
|
39076
|
+
for (const event of artifact.events) {
|
|
39077
|
+
if (event.type === "command_started") {
|
|
39078
|
+
commandStarts.push(Date.parse(event.at));
|
|
39079
|
+
continue;
|
|
39080
|
+
}
|
|
39081
|
+
if (event.type === "command_succeeded") {
|
|
39082
|
+
completedCommands += 1;
|
|
39083
|
+
const startedAt = commandStarts.shift();
|
|
39084
|
+
if (startedAt !== void 0 && Number.isFinite(startedAt)) {
|
|
39085
|
+
const duration3 = Math.max(0, Date.parse(event.at) - startedAt);
|
|
39086
|
+
successDurations.push(duration3);
|
|
39087
|
+
}
|
|
39088
|
+
continue;
|
|
39089
|
+
}
|
|
39090
|
+
if (event.type === "command_failed" || event.type === "command_blocked" || event.type === "shell_error") {
|
|
39091
|
+
deadEndCount += 1;
|
|
39092
|
+
const key = String(event.reason ?? event.type).trim() || event.type;
|
|
39093
|
+
errorFamilies[key] = (errorFamilies[key] ?? 0) + 1;
|
|
39094
|
+
if (event.type === "command_failed") {
|
|
39095
|
+
commandStarts.shift();
|
|
39096
|
+
}
|
|
39097
|
+
}
|
|
39098
|
+
}
|
|
39099
|
+
const totalCommands = Math.max(artifact.command_count, 0);
|
|
39100
|
+
const completionRate = totalCommands > 0 ? Number((completedCommands / totalCommands).toFixed(4)) : 0;
|
|
39101
|
+
const topRemediationLoops = Object.entries(errorFamilies).sort((left, right) => right[1] - left[1]).slice(0, 5).map(([reason, count]) => ({ reason, count }));
|
|
39102
|
+
return {
|
|
39103
|
+
schema_version: "interactive_cli_session_report.v1",
|
|
39104
|
+
generated_at: nowIso2(),
|
|
39105
|
+
session_id: artifact.session_id,
|
|
39106
|
+
command_count: totalCommands,
|
|
39107
|
+
completion_rate: completionRate,
|
|
39108
|
+
dead_end_count: deadEndCount,
|
|
39109
|
+
command_error_families: errorFamilies,
|
|
39110
|
+
median_time_to_success_ms: median(successDurations),
|
|
39111
|
+
top_remediation_loops: topRemediationLoops,
|
|
39112
|
+
terminal_capabilities: artifact.terminal_capabilities
|
|
39113
|
+
};
|
|
39114
|
+
}
|
|
39115
|
+
function flushInteractiveSessionReport(artifact) {
|
|
39116
|
+
const outputPath = interactiveSessionReportPath();
|
|
39117
|
+
const report = buildInteractiveSessionReport(artifact);
|
|
39118
|
+
(0, import_fs11.mkdirSync)((0, import_path9.dirname)(outputPath), { recursive: true });
|
|
39119
|
+
(0, import_fs11.writeFileSync)(outputPath, JSON.stringify(report, null, 2), "utf8");
|
|
39120
|
+
}
|
|
38416
39121
|
function tokenizeInput(value) {
|
|
38417
39122
|
const tokens = [];
|
|
38418
39123
|
const matcher = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
@@ -38423,193 +39128,391 @@ function tokenizeInput(value) {
|
|
|
38423
39128
|
}
|
|
38424
39129
|
return tokens;
|
|
38425
39130
|
}
|
|
38426
|
-
function
|
|
38427
|
-
const
|
|
38428
|
-
if (!
|
|
38429
|
-
|
|
38430
|
-
const tokens = tokenizeInput(withoutLeadingSlash);
|
|
38431
|
-
if (tokens.length > 0 && tokens[0].toLowerCase() === "foh") {
|
|
38432
|
-
tokens.shift();
|
|
39131
|
+
async function runSelf(args, apiUrlOverride) {
|
|
39132
|
+
const spawnArgs = [...args];
|
|
39133
|
+
if (apiUrlOverride && !spawnArgs.includes("--api-url")) {
|
|
39134
|
+
spawnArgs.push("--api-url", apiUrlOverride);
|
|
38433
39135
|
}
|
|
38434
|
-
return
|
|
38435
|
-
|
|
38436
|
-
|
|
38437
|
-
|
|
38438
|
-
|
|
38439
|
-
|
|
38440
|
-
|
|
38441
|
-
|
|
39136
|
+
return await new Promise((resolve15, reject) => {
|
|
39137
|
+
const child = (0, import_child_process2.spawn)(process.execPath, [process.argv[1], ...spawnArgs], {
|
|
39138
|
+
stdio: "inherit",
|
|
39139
|
+
env: {
|
|
39140
|
+
...process.env,
|
|
39141
|
+
FOH_CLI_NO_HOME: "1",
|
|
39142
|
+
FOH_CLI_SUPPRESS_BANNER: "1"
|
|
39143
|
+
}
|
|
39144
|
+
});
|
|
39145
|
+
child.once("error", reject);
|
|
39146
|
+
child.once("close", (code) => resolve15(typeof code === "number" ? code : 1));
|
|
38442
39147
|
});
|
|
38443
39148
|
}
|
|
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
|
-
];
|
|
39149
|
+
function resolveInteractiveState(apiUrlOverride) {
|
|
39150
|
+
try {
|
|
39151
|
+
const creds = loadCredentials(apiUrlOverride);
|
|
39152
|
+
return {
|
|
39153
|
+
authenticated: true,
|
|
39154
|
+
orgId: creds.orgId
|
|
39155
|
+
};
|
|
39156
|
+
} catch {
|
|
39157
|
+
return { authenticated: false };
|
|
38482
39158
|
}
|
|
39159
|
+
}
|
|
39160
|
+
function nextRecommendedCommand(state) {
|
|
39161
|
+
const next = getHomeQuickActions(state)[0];
|
|
39162
|
+
if (!next) return "foh --help";
|
|
39163
|
+
return `foh ${next.args.join(" ")}`;
|
|
39164
|
+
}
|
|
39165
|
+
function keymapHelpText() {
|
|
38483
39166
|
return [
|
|
38484
|
-
|
|
38485
|
-
|
|
38486
|
-
|
|
38487
|
-
|
|
38488
|
-
|
|
38489
|
-
|
|
38490
|
-
|
|
39167
|
+
"",
|
|
39168
|
+
"Shell Keymap",
|
|
39169
|
+
"-----------",
|
|
39170
|
+
" Up / Down Recall command history",
|
|
39171
|
+
" Tab Complete ranked slash/command suggestions",
|
|
39172
|
+
" Enter Run current input",
|
|
39173
|
+
" Esc Cancel palette-style slash entry",
|
|
39174
|
+
" Ctrl+C Exit shell safely",
|
|
39175
|
+
"",
|
|
39176
|
+
"Fallback: if your terminal does not forward these keys, run equivalent slash commands (/help, /commands, /exit).",
|
|
39177
|
+
""
|
|
39178
|
+
].join("\n");
|
|
38491
39179
|
}
|
|
38492
|
-
function
|
|
38493
|
-
|
|
38494
|
-
|
|
38495
|
-
|
|
38496
|
-
|
|
38497
|
-
|
|
39180
|
+
function shellHelpText(state) {
|
|
39181
|
+
const slashLines = SLASH_COMMANDS.map((entry) => ` ${entry.slash.padEnd(12)} ${entry.description}`);
|
|
39182
|
+
return [
|
|
39183
|
+
"",
|
|
39184
|
+
"FOH Interactive Shell",
|
|
39185
|
+
"---------------------",
|
|
39186
|
+
"Use slash commands or type any normal `foh` command without the `foh` prefix.",
|
|
39187
|
+
"Keys: Up/Down history, Tab completion, Ctrl+C or /exit to leave. Run /keys for full mapping.",
|
|
39188
|
+
"",
|
|
39189
|
+
"Slash commands:",
|
|
39190
|
+
...slashLines,
|
|
39191
|
+
"",
|
|
39192
|
+
`Recommended next command: ${nextRecommendedCommand(state)}`,
|
|
39193
|
+
""
|
|
39194
|
+
].join("\n");
|
|
38498
39195
|
}
|
|
38499
|
-
function
|
|
38500
|
-
|
|
39196
|
+
function printPrompt(rl) {
|
|
39197
|
+
rl.setPrompt("foh> ");
|
|
39198
|
+
rl.prompt();
|
|
38501
39199
|
}
|
|
38502
|
-
function
|
|
38503
|
-
|
|
38504
|
-
|
|
38505
|
-
|
|
38506
|
-
|
|
38507
|
-
|
|
38508
|
-
|
|
38509
|
-
|
|
38510
|
-
|
|
38511
|
-
|
|
38512
|
-
|
|
38513
|
-
|
|
38514
|
-
|
|
38515
|
-
|
|
38516
|
-
|
|
38517
|
-
|
|
38518
|
-
|
|
38519
|
-
|
|
38520
|
-
|
|
38521
|
-
|
|
38522
|
-
|
|
38523
|
-
`
|
|
39200
|
+
function safePrintPrompt(rl) {
|
|
39201
|
+
if (rl.closed) return;
|
|
39202
|
+
printPrompt(rl);
|
|
39203
|
+
}
|
|
39204
|
+
function prefillCommand(rl, command) {
|
|
39205
|
+
rl.write(null, { ctrl: true, name: "u" });
|
|
39206
|
+
rl.write(command);
|
|
39207
|
+
}
|
|
39208
|
+
function resolveSlashCommand(input, state, terminal, context) {
|
|
39209
|
+
const normalized = input.trim().toLowerCase();
|
|
39210
|
+
if (normalized === "/exit" || normalized === "/quit") return { quit: true, args: null };
|
|
39211
|
+
if (normalized === "/?" || normalized === "/keys") return { quit: false, args: [], message: keymapHelpText() };
|
|
39212
|
+
if (normalized === "/help") return { quit: false, args: [], message: shellHelpText(state) };
|
|
39213
|
+
if (normalized === "/commands") {
|
|
39214
|
+
const rows = SLASH_COMMANDS.map((entry) => ` ${entry.slash.padEnd(12)} ${entry.description}`);
|
|
39215
|
+
return {
|
|
39216
|
+
quit: false,
|
|
39217
|
+
args: [],
|
|
39218
|
+
message: `
|
|
39219
|
+
Slash commands:
|
|
39220
|
+
${rows.join("\n")}
|
|
39221
|
+
`
|
|
39222
|
+
};
|
|
38524
39223
|
}
|
|
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
|
-
});
|
|
39224
|
+
if (normalized === "/clear") {
|
|
39225
|
+
if (terminal.supportsAnsiClear) return { quit: false, args: [], message: "\x1B[2J\x1B[H" };
|
|
39226
|
+
return {
|
|
39227
|
+
quit: false,
|
|
39228
|
+
args: [],
|
|
39229
|
+
message: "\n\n\n\n\nTerminal does not support ANSI clear. Screen reset skipped.\n"
|
|
39230
|
+
};
|
|
39231
|
+
}
|
|
39232
|
+
if (normalized === "/again") {
|
|
39233
|
+
if (context.lastSuccessfulArgs && context.lastSuccessfulArgs.length > 0) {
|
|
39234
|
+
return { quit: false, args: [...context.lastSuccessfulArgs] };
|
|
38552
39235
|
}
|
|
38553
|
-
|
|
38554
|
-
process.stdout.write('\nPress "/" to open command palette.\n');
|
|
39236
|
+
return { quit: false, args: [], message: "No successful command in current session yet.\n" };
|
|
38555
39237
|
}
|
|
38556
|
-
if (
|
|
38557
|
-
|
|
38558
|
-
|
|
38559
|
-
|
|
38560
|
-
|
|
38561
|
-
`);
|
|
39238
|
+
if (normalized === "/retry") {
|
|
39239
|
+
if (context.lastFailedArgs && context.lastFailedArgs.length > 0) {
|
|
39240
|
+
return { quit: false, args: [...context.lastFailedArgs] };
|
|
39241
|
+
}
|
|
39242
|
+
return { quit: false, args: [], message: "No failed command in current session yet.\n" };
|
|
38562
39243
|
}
|
|
39244
|
+
if (normalized === "/editlast") {
|
|
39245
|
+
const candidate = context.lastFailedArgs ?? context.lastSuccessfulArgs;
|
|
39246
|
+
if (!candidate || candidate.length === 0) {
|
|
39247
|
+
return { quit: false, args: [], message: "No previous command to prefill.\n" };
|
|
39248
|
+
}
|
|
39249
|
+
return {
|
|
39250
|
+
quit: false,
|
|
39251
|
+
args: [],
|
|
39252
|
+
prefill: candidate.join(" "),
|
|
39253
|
+
message: terminal.supportsLinePrefill ? "Prefilled last command. Edit and press Enter to run.\n" : `Terminal cannot prefill input. Replay manually: foh ${candidate.join(" ")}
|
|
39254
|
+
`
|
|
39255
|
+
};
|
|
39256
|
+
}
|
|
39257
|
+
if (normalized.startsWith("/run ")) {
|
|
39258
|
+
const raw = input.trim().slice("/run ".length).trim();
|
|
39259
|
+
const args = tokenizeInput(raw);
|
|
39260
|
+
return { quit: false, args: args.length > 0 ? args : null };
|
|
39261
|
+
}
|
|
39262
|
+
if (normalized === "/snippet list") {
|
|
39263
|
+
const rows = Object.entries(context.snippets).sort((left, right) => left[0].localeCompare(right[0])).map(([name, command]) => ` ${name.padEnd(16)} ${command}`);
|
|
39264
|
+
if (rows.length === 0) return { quit: false, args: [], message: "No snippets saved.\n" };
|
|
39265
|
+
return { quit: false, args: [], message: `
|
|
39266
|
+
Saved snippets:
|
|
39267
|
+
${rows.join("\n")}
|
|
39268
|
+
` };
|
|
39269
|
+
}
|
|
39270
|
+
if (normalized.startsWith("/snippet save ")) {
|
|
39271
|
+
const parts = tokenizeInput(input.trim());
|
|
39272
|
+
const name = String(parts[2] ?? "").trim().toLowerCase();
|
|
39273
|
+
const command = parts.slice(3).join(" ").trim();
|
|
39274
|
+
if (!name || !command) {
|
|
39275
|
+
return { quit: false, args: [], message: "Usage: /snippet save <name> <command>\n" };
|
|
39276
|
+
}
|
|
39277
|
+
const normalizedCommand = command.toLowerCase().startsWith("foh ") ? command.slice(4).trim() : command;
|
|
39278
|
+
context.snippets[name] = normalizedCommand;
|
|
39279
|
+
return { quit: false, args: [], message: `Saved snippet "${name}": ${normalizedCommand}
|
|
39280
|
+
` };
|
|
39281
|
+
}
|
|
39282
|
+
if (normalized.startsWith("/snippet run ")) {
|
|
39283
|
+
const parts = tokenizeInput(input.trim());
|
|
39284
|
+
const name = String(parts[2] ?? "").trim().toLowerCase();
|
|
39285
|
+
const command = context.snippets[name];
|
|
39286
|
+
if (!command) return { quit: false, args: [], message: `Snippet "${name}" not found.
|
|
39287
|
+
` };
|
|
39288
|
+
return { quit: false, args: tokenizeInput(command) };
|
|
39289
|
+
}
|
|
39290
|
+
if (normalized.startsWith("/snippet edit ")) {
|
|
39291
|
+
const parts = tokenizeInput(input.trim());
|
|
39292
|
+
const name = String(parts[2] ?? "").trim().toLowerCase();
|
|
39293
|
+
const command = context.snippets[name];
|
|
39294
|
+
if (!command) return { quit: false, args: [], message: `Snippet "${name}" not found.
|
|
39295
|
+
` };
|
|
39296
|
+
return {
|
|
39297
|
+
quit: false,
|
|
39298
|
+
args: [],
|
|
39299
|
+
prefill: command,
|
|
39300
|
+
message: terminal.supportsLinePrefill ? `Prefilled snippet "${name}".
|
|
39301
|
+
` : `Terminal cannot prefill input. Snippet "${name}": ${command}
|
|
39302
|
+
`
|
|
39303
|
+
};
|
|
39304
|
+
}
|
|
39305
|
+
const matched = SLASH_COMMANDS.find((entry) => entry.slash.toLowerCase() === normalized);
|
|
39306
|
+
if (matched?.args && matched.args.length > 0) {
|
|
39307
|
+
return { quit: false, args: [...matched.args] };
|
|
39308
|
+
}
|
|
39309
|
+
if (normalized.startsWith("/")) {
|
|
39310
|
+
return { quit: false, args: [], message: `Unknown slash command: ${input.trim()}
|
|
39311
|
+
Use /help.
|
|
39312
|
+
` };
|
|
39313
|
+
}
|
|
39314
|
+
return { quit: false, args: null };
|
|
38563
39315
|
}
|
|
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;
|
|
39316
|
+
function normalizeCommandInput(input) {
|
|
39317
|
+
const trimmed = input.trim();
|
|
39318
|
+
if (!trimmed) return [];
|
|
39319
|
+
const withoutPrefix = trimmed.toLowerCase().startsWith("foh ") ? trimmed.slice(4).trim() : trimmed;
|
|
39320
|
+
return tokenizeInput(withoutPrefix);
|
|
39321
|
+
}
|
|
39322
|
+
function hasYesFlag(args) {
|
|
39323
|
+
return args.some((token) => token === "--yes" || token === "-y");
|
|
39324
|
+
}
|
|
39325
|
+
async function confirmMutationIfNeeded(rl, args) {
|
|
39326
|
+
const mutationState = classifyCommandMutation(args);
|
|
39327
|
+
if (mutationState === "read" || hasYesFlag(args)) {
|
|
39328
|
+
return { approved: true };
|
|
38581
39329
|
}
|
|
38582
|
-
|
|
38583
|
-
|
|
38584
|
-
|
|
39330
|
+
const commandEntry = getCommandGraphEntryForArgs(args);
|
|
39331
|
+
const label = commandEntry ? `foh ${commandEntry.args.join(" ")}` : `foh ${args.join(" ")}`;
|
|
39332
|
+
const warning = mutationState === "high_risk" ? "high-risk" : "write";
|
|
39333
|
+
const approved = await new Promise((resolve15) => {
|
|
39334
|
+
rl.question(`Confirm ${warning} command ${label}? [y/N]: `, (answer) => {
|
|
39335
|
+
const normalized = answer.trim().toLowerCase();
|
|
39336
|
+
resolve15(normalized === "y" || normalized === "yes");
|
|
39337
|
+
});
|
|
39338
|
+
});
|
|
39339
|
+
if (approved) return { approved: true };
|
|
39340
|
+
return {
|
|
39341
|
+
approved: false,
|
|
39342
|
+
message: `Command blocked (interactive_confirmation_required): foh ${args.join(" ")}`
|
|
39343
|
+
};
|
|
39344
|
+
}
|
|
39345
|
+
function commandSuggestionsForState(state) {
|
|
39346
|
+
const contextual = getHomePaletteCommands(state).map((entry) => entry.args.join(" "));
|
|
39347
|
+
const merged = [...contextual, ...COMMAND_SUGGESTIONS];
|
|
39348
|
+
return merged.filter((entry, index, values) => values.indexOf(entry) === index);
|
|
39349
|
+
}
|
|
39350
|
+
function fuzzyScore(query, candidate) {
|
|
39351
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
39352
|
+
const normalizedCandidate = candidate.toLowerCase();
|
|
39353
|
+
if (!normalizedQuery) return 0;
|
|
39354
|
+
if (normalizedCandidate.startsWith(normalizedQuery)) return 0;
|
|
39355
|
+
const includesAt = normalizedCandidate.indexOf(normalizedQuery);
|
|
39356
|
+
if (includesAt >= 0) return 100 + includesAt;
|
|
39357
|
+
let queryIndex = 0;
|
|
39358
|
+
let gaps = 0;
|
|
39359
|
+
for (let i = 0; i < normalizedCandidate.length && queryIndex < normalizedQuery.length; i += 1) {
|
|
39360
|
+
if (normalizedCandidate[i] === normalizedQuery[queryIndex]) {
|
|
39361
|
+
queryIndex += 1;
|
|
39362
|
+
} else if (queryIndex > 0) {
|
|
39363
|
+
gaps += 1;
|
|
39364
|
+
}
|
|
39365
|
+
}
|
|
39366
|
+
if (queryIndex !== normalizedQuery.length) return Number.POSITIVE_INFINITY;
|
|
39367
|
+
return 200 + gaps;
|
|
39368
|
+
}
|
|
39369
|
+
function rankMatches(query, pool) {
|
|
39370
|
+
const scored = pool.map((candidate) => ({ candidate, score: fuzzyScore(query, candidate) })).filter((entry) => Number.isFinite(entry.score)).sort((left, right) => {
|
|
39371
|
+
if (left.score !== right.score) return left.score - right.score;
|
|
39372
|
+
return left.candidate.localeCompare(right.candidate);
|
|
39373
|
+
});
|
|
39374
|
+
return scored.map((entry) => entry.candidate);
|
|
39375
|
+
}
|
|
39376
|
+
function buildCompleter(apiUrlOverride) {
|
|
39377
|
+
const slashLabels = SLASH_COMMANDS.map((entry) => entry.slash);
|
|
39378
|
+
return (line) => {
|
|
39379
|
+
const raw = String(line ?? "");
|
|
39380
|
+
const lower = raw.toLowerCase();
|
|
39381
|
+
const state = resolveInteractiveState(apiUrlOverride);
|
|
39382
|
+
const commandLabels = commandSuggestionsForState(state);
|
|
39383
|
+
const pool = lower.trim().startsWith("/") ? slashLabels : commandLabels;
|
|
39384
|
+
const hits = rankMatches(lower, pool);
|
|
39385
|
+
return [hits.length > 0 ? hits : pool, raw];
|
|
39386
|
+
};
|
|
39387
|
+
}
|
|
39388
|
+
function registerInteractive(program3) {
|
|
39389
|
+
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) => {
|
|
39390
|
+
const sessionArtifact = createInteractiveSessionArtifact();
|
|
39391
|
+
const terminalCapabilities = detectTerminalCapabilities();
|
|
39392
|
+
sessionArtifact.terminal_capabilities = {
|
|
39393
|
+
stdin_tty: terminalCapabilities.stdinTty,
|
|
39394
|
+
stdout_tty: terminalCapabilities.stdoutTty,
|
|
39395
|
+
supports_raw_mode: terminalCapabilities.supportsRawMode,
|
|
39396
|
+
supports_line_prefill: terminalCapabilities.supportsLinePrefill,
|
|
39397
|
+
supports_ansi_clear: terminalCapabilities.supportsAnsiClear
|
|
39398
|
+
};
|
|
39399
|
+
const memory = loadInteractiveShellMemory();
|
|
39400
|
+
recordInteractiveShellEvent(sessionArtifact, { type: "shell_started" });
|
|
39401
|
+
ensureInteractive(
|
|
39402
|
+
"interactive.shell",
|
|
39403
|
+
"Run this in a TTY terminal. For automation use normal non-interactive commands with --json."
|
|
39404
|
+
);
|
|
39405
|
+
const rl = (0, import_readline2.createInterface)({
|
|
39406
|
+
input: process.stdin,
|
|
39407
|
+
output: process.stdout,
|
|
39408
|
+
terminal: true,
|
|
39409
|
+
historySize: 500,
|
|
39410
|
+
removeHistoryDuplicates: true,
|
|
39411
|
+
history: memory.history.slice(0, 500),
|
|
39412
|
+
completer: buildCompleter(opts.apiUrl)
|
|
39413
|
+
});
|
|
39414
|
+
process.stdout.write(shellHelpText(resolveInteractiveState(opts.apiUrl)));
|
|
39415
|
+
printPrompt(rl);
|
|
39416
|
+
let busy = false;
|
|
39417
|
+
let lastSuccessfulArgs = null;
|
|
39418
|
+
let lastFailedArgs = null;
|
|
39419
|
+
rl.on("line", (line) => {
|
|
39420
|
+
void (async () => {
|
|
39421
|
+
if (busy) return;
|
|
39422
|
+
const raw = String(line ?? "");
|
|
39423
|
+
const slash = resolveSlashCommand(raw, resolveInteractiveState(opts.apiUrl), terminalCapabilities, {
|
|
39424
|
+
lastSuccessfulArgs,
|
|
39425
|
+
lastFailedArgs,
|
|
39426
|
+
snippets: memory.snippets
|
|
39427
|
+
});
|
|
39428
|
+
if (slash.quit) {
|
|
39429
|
+
rl.close();
|
|
39430
|
+
return;
|
|
39431
|
+
}
|
|
39432
|
+
if (slash.message) {
|
|
39433
|
+
process.stdout.write(`${slash.message}${slash.message.endsWith("\n") ? "" : "\n"}`);
|
|
39434
|
+
}
|
|
39435
|
+
if (slash.prefill) {
|
|
39436
|
+
if (terminalCapabilities.supportsLinePrefill) {
|
|
39437
|
+
prefillCommand(rl, slash.prefill);
|
|
39438
|
+
}
|
|
39439
|
+
return;
|
|
39440
|
+
}
|
|
39441
|
+
const commandArgs = slash.args ?? normalizeCommandInput(raw);
|
|
39442
|
+
if (commandArgs.length === 0) {
|
|
39443
|
+
safePrintPrompt(rl);
|
|
39444
|
+
return;
|
|
39445
|
+
}
|
|
39446
|
+
const confirmation = await confirmMutationIfNeeded(rl, commandArgs);
|
|
39447
|
+
if (!confirmation.approved) {
|
|
39448
|
+
if (confirmation.message) {
|
|
39449
|
+
process.stdout.write(`${confirmation.message}
|
|
38585
39450
|
`);
|
|
38586
|
-
|
|
38587
|
-
|
|
38588
|
-
|
|
38589
|
-
|
|
38590
|
-
|
|
38591
|
-
|
|
38592
|
-
|
|
38593
|
-
|
|
38594
|
-
|
|
38595
|
-
|
|
38596
|
-
|
|
38597
|
-
|
|
38598
|
-
|
|
39451
|
+
recordInteractiveShellEvent(sessionArtifact, {
|
|
39452
|
+
type: "command_blocked",
|
|
39453
|
+
command: `foh ${commandArgs.join(" ")}`,
|
|
39454
|
+
reason: "interactive_confirmation_required"
|
|
39455
|
+
});
|
|
39456
|
+
}
|
|
39457
|
+
printPrompt(rl);
|
|
39458
|
+
return;
|
|
39459
|
+
}
|
|
39460
|
+
recordInteractiveShellEvent(sessionArtifact, {
|
|
39461
|
+
type: "command_started",
|
|
39462
|
+
command: `foh ${commandArgs.join(" ")}`
|
|
39463
|
+
});
|
|
39464
|
+
busy = true;
|
|
39465
|
+
try {
|
|
39466
|
+
const code = await runSelf(commandArgs, opts.apiUrl);
|
|
39467
|
+
if (code !== 0) {
|
|
39468
|
+
lastFailedArgs = [...commandArgs];
|
|
39469
|
+
process.stdout.write(`Command exited with code ${code}: foh ${commandArgs.join(" ")}
|
|
38599
39470
|
`);
|
|
38600
|
-
|
|
38601
|
-
|
|
39471
|
+
recordInteractiveShellEvent(sessionArtifact, {
|
|
39472
|
+
type: "command_failed",
|
|
39473
|
+
command: `foh ${commandArgs.join(" ")}`,
|
|
39474
|
+
exit_code: code,
|
|
39475
|
+
reason: "non_zero_exit"
|
|
39476
|
+
});
|
|
39477
|
+
} else {
|
|
39478
|
+
lastSuccessfulArgs = [...commandArgs];
|
|
39479
|
+
recordInteractiveShellEvent(sessionArtifact, {
|
|
39480
|
+
type: "command_succeeded",
|
|
39481
|
+
command: `foh ${commandArgs.join(" ")}`,
|
|
39482
|
+
exit_code: code
|
|
39483
|
+
});
|
|
39484
|
+
}
|
|
39485
|
+
} finally {
|
|
39486
|
+
busy = false;
|
|
39487
|
+
safePrintPrompt(rl);
|
|
39488
|
+
}
|
|
39489
|
+
})().catch((error2) => {
|
|
39490
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
39491
|
+
process.stdout.write(`Shell error: ${message}
|
|
38602
39492
|
`);
|
|
38603
|
-
|
|
38604
|
-
|
|
38605
|
-
|
|
38606
|
-
|
|
38607
|
-
|
|
38608
|
-
|
|
38609
|
-
|
|
38610
|
-
|
|
38611
|
-
|
|
38612
|
-
|
|
39493
|
+
recordInteractiveShellEvent(sessionArtifact, {
|
|
39494
|
+
type: "shell_error",
|
|
39495
|
+
reason: message
|
|
39496
|
+
});
|
|
39497
|
+
busy = false;
|
|
39498
|
+
safePrintPrompt(rl);
|
|
39499
|
+
});
|
|
39500
|
+
});
|
|
39501
|
+
rl.on("SIGINT", () => {
|
|
39502
|
+
rl.close();
|
|
39503
|
+
});
|
|
39504
|
+
await new Promise((resolve15) => {
|
|
39505
|
+
rl.on("close", () => {
|
|
39506
|
+
process.stdout.write("\n");
|
|
39507
|
+
memory.history = rl.history.slice(0, 500);
|
|
39508
|
+
recordInteractiveShellEvent(sessionArtifact, { type: "shell_closed" });
|
|
39509
|
+
flushInteractiveSessionArtifact(sessionArtifact);
|
|
39510
|
+
flushInteractiveSessionReport(sessionArtifact);
|
|
39511
|
+
saveInteractiveShellMemory(memory);
|
|
39512
|
+
resolve15();
|
|
39513
|
+
});
|
|
39514
|
+
});
|
|
39515
|
+
});
|
|
38613
39516
|
}
|
|
38614
39517
|
|
|
38615
39518
|
// src/commands/home-actions.ts
|
|
@@ -38625,65 +39528,6 @@ async function promptChoice(prompt) {
|
|
|
38625
39528
|
return await promptLine(`
|
|
38626
39529
|
${prompt}`, { allowEmpty: true });
|
|
38627
39530
|
}
|
|
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
39531
|
async function runGuidedStart(apiUrlOverride, executeCommand) {
|
|
38688
39532
|
process.stdout.write("\nGuided Start\n");
|
|
38689
39533
|
process.stdout.write("------------\n");
|
|
@@ -38798,13 +39642,153 @@ async function chooseDefaultOrg(orgs) {
|
|
|
38798
39642
|
`);
|
|
38799
39643
|
}
|
|
38800
39644
|
}
|
|
38801
|
-
function
|
|
38802
|
-
return
|
|
39645
|
+
function getHomeQuickActions2(state) {
|
|
39646
|
+
return getHomeQuickActions(state);
|
|
39647
|
+
}
|
|
39648
|
+
|
|
39649
|
+
// src/tui/command-palette.ts
|
|
39650
|
+
function tokenizeInput2(value) {
|
|
39651
|
+
const tokens = [];
|
|
39652
|
+
const matcher = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
39653
|
+
let match = matcher.exec(value);
|
|
39654
|
+
while (match) {
|
|
39655
|
+
tokens.push(match[1] ?? match[2] ?? match[3] ?? "");
|
|
39656
|
+
match = matcher.exec(value);
|
|
39657
|
+
}
|
|
39658
|
+
return tokens;
|
|
39659
|
+
}
|
|
39660
|
+
function normalizeTypedCommandInput(raw) {
|
|
39661
|
+
const normalized = String(raw ?? "").trim();
|
|
39662
|
+
if (!normalized) return [];
|
|
39663
|
+
const withoutLeadingSlash = normalized.startsWith("/") ? normalized.slice(1) : normalized;
|
|
39664
|
+
const tokens = tokenizeInput2(withoutLeadingSlash);
|
|
39665
|
+
if (tokens.length > 0 && tokens[0].toLowerCase() === "foh") {
|
|
39666
|
+
tokens.shift();
|
|
39667
|
+
}
|
|
39668
|
+
return tokens;
|
|
39669
|
+
}
|
|
39670
|
+
function filterPaletteCommands(commands, query) {
|
|
39671
|
+
const normalized = String(query ?? "").trim().toLowerCase();
|
|
39672
|
+
if (!normalized) return commands;
|
|
39673
|
+
return commands.filter((entry) => {
|
|
39674
|
+
const commandText = `foh ${entry.args.join(" ")}`.toLowerCase();
|
|
39675
|
+
return entry.label.toLowerCase().includes(normalized) || commandText.includes(normalized);
|
|
39676
|
+
});
|
|
39677
|
+
}
|
|
39678
|
+
function shouldStartPaletteFromHome(char) {
|
|
39679
|
+
if (typeof char !== "string" || char.length !== 1) return false;
|
|
39680
|
+
if (/\s/.test(char)) return false;
|
|
39681
|
+
if (char === "/") return false;
|
|
39682
|
+
return /^[0-9A-Za-z._:-]$/.test(char);
|
|
39683
|
+
}
|
|
39684
|
+
|
|
39685
|
+
// src/commands/home-render.ts
|
|
39686
|
+
function getQuickActions(state) {
|
|
39687
|
+
return getHomeQuickActions(state);
|
|
39688
|
+
}
|
|
39689
|
+
function getPaletteCommands(state) {
|
|
39690
|
+
return getHomePaletteCommands(state);
|
|
39691
|
+
}
|
|
39692
|
+
function filterHomePaletteCommands(state, query) {
|
|
39693
|
+
const commands = getPaletteCommands(state);
|
|
39694
|
+
const commandIndex = new Map(commands.map((entry) => [entry.args.join(" "), entry]));
|
|
39695
|
+
const filtered = filterPaletteCommands(commands, query);
|
|
39696
|
+
return filtered.map((entry) => commandIndex.get(entry.args.join(" "))).filter((entry) => entry !== void 0);
|
|
39697
|
+
}
|
|
39698
|
+
function renderInteractiveHomeScreen({
|
|
39699
|
+
state,
|
|
39700
|
+
actions,
|
|
39701
|
+
selectedActionIndex,
|
|
39702
|
+
paletteOpen,
|
|
39703
|
+
paletteQuery,
|
|
39704
|
+
paletteSuggestions,
|
|
39705
|
+
selectedPaletteIndex,
|
|
39706
|
+
notice,
|
|
39707
|
+
isBusy
|
|
39708
|
+
}) {
|
|
39709
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
39710
|
+
process.stdout.write("Front Of House CLI Home\n");
|
|
39711
|
+
process.stdout.write("=======================\n\n");
|
|
39712
|
+
if (!state.authenticated) {
|
|
39713
|
+
process.stdout.write("Status: Not authenticated\n");
|
|
39714
|
+
} else {
|
|
39715
|
+
process.stdout.write("Status: Authenticated\n");
|
|
39716
|
+
process.stdout.write(`API: ${state.apiUrl}
|
|
39717
|
+
`);
|
|
39718
|
+
process.stdout.write(`Default org: ${state.orgId ?? "(not set)"}
|
|
39719
|
+
`);
|
|
39720
|
+
}
|
|
39721
|
+
process.stdout.write("\nQuick actions (Up/Down, j/k, Enter, 1-9):\n");
|
|
39722
|
+
actions.forEach((action, index) => {
|
|
39723
|
+
const isSelected = !paletteOpen && index === selectedActionIndex;
|
|
39724
|
+
const cursor = isSelected ? ">" : " ";
|
|
39725
|
+
process.stdout.write(` ${cursor} [${index + 1}] ${action.label}
|
|
39726
|
+
`);
|
|
39727
|
+
process.stdout.write(` ${action.description}
|
|
39728
|
+
`);
|
|
39729
|
+
});
|
|
39730
|
+
process.stdout.write("\nShortcuts: [/] palette [h] redraw [q] exit [Ctrl+C] exit\n");
|
|
39731
|
+
process.stdout.write("Tip: start typing any command (for example auth, help, org list) to search instantly.\n");
|
|
39732
|
+
if (paletteOpen) {
|
|
39733
|
+
process.stdout.write("\nCommand palette\n");
|
|
39734
|
+
process.stdout.write("---------------\n");
|
|
39735
|
+
process.stdout.write(`/${paletteQuery}
|
|
39736
|
+
`);
|
|
39737
|
+
if (paletteSuggestions.length === 0) {
|
|
39738
|
+
process.stdout.write(" No matches. Press Enter to run typed command, or Esc to close.\n");
|
|
39739
|
+
} else {
|
|
39740
|
+
paletteSuggestions.slice(0, 8).forEach((entry, index) => {
|
|
39741
|
+
const isSelected = index === selectedPaletteIndex;
|
|
39742
|
+
const cursor = isSelected ? ">" : " ";
|
|
39743
|
+
process.stdout.write(` ${cursor} ${entry.label}
|
|
39744
|
+
`);
|
|
39745
|
+
process.stdout.write(` foh ${entry.args.join(" ")}
|
|
39746
|
+
`);
|
|
39747
|
+
});
|
|
39748
|
+
}
|
|
39749
|
+
} else {
|
|
39750
|
+
process.stdout.write('\nPress "/" to open command palette.\n');
|
|
39751
|
+
}
|
|
39752
|
+
if (isBusy) {
|
|
39753
|
+
process.stdout.write("\nRunning command...\n");
|
|
39754
|
+
} else if (notice) {
|
|
39755
|
+
process.stdout.write(`
|
|
39756
|
+
${notice}
|
|
39757
|
+
`);
|
|
39758
|
+
}
|
|
39759
|
+
}
|
|
39760
|
+
function printHome(state) {
|
|
39761
|
+
process.stdout.write("\nFront Of House CLI Home\n");
|
|
39762
|
+
process.stdout.write("-----------------------\n");
|
|
39763
|
+
if (!state.authenticated) {
|
|
39764
|
+
process.stdout.write("Status: Not authenticated\n\n");
|
|
39765
|
+
} else {
|
|
39766
|
+
process.stdout.write(`Status: Authenticated
|
|
39767
|
+
API: ${state.apiUrl}
|
|
39768
|
+
`);
|
|
39769
|
+
process.stdout.write(`Default org: ${state.orgId ?? "(not set)"}
|
|
39770
|
+
|
|
39771
|
+
`);
|
|
39772
|
+
}
|
|
39773
|
+
const actions = getQuickActions(state);
|
|
39774
|
+
if (actions.length > 0) {
|
|
39775
|
+
process.stdout.write("Quick options:\n");
|
|
39776
|
+
actions.forEach((action, index) => {
|
|
39777
|
+
process.stdout.write(` [${index + 1}] ${action.label}
|
|
39778
|
+
`);
|
|
39779
|
+
});
|
|
39780
|
+
process.stdout.write(" [h] redraw home\n");
|
|
39781
|
+
process.stdout.write(" [q] exit\n");
|
|
39782
|
+
}
|
|
39783
|
+
const exampleAction = actions.find((entry) => entry.args.length > 0);
|
|
39784
|
+
const example = exampleAction ? `Type any foh command (example: ${exampleAction.args.join(" ")})` : "Type any foh command (example: --help)";
|
|
39785
|
+
process.stdout.write(` ${example}
|
|
39786
|
+
`);
|
|
38803
39787
|
}
|
|
38804
39788
|
|
|
38805
39789
|
// src/commands/home-interaction.ts
|
|
38806
|
-
var
|
|
38807
|
-
var
|
|
39790
|
+
var import_child_process3 = require("child_process");
|
|
39791
|
+
var import_readline3 = require("readline");
|
|
38808
39792
|
|
|
38809
39793
|
// src/tui/keypress.ts
|
|
38810
39794
|
function normalizeInputChar(inputChar) {
|
|
@@ -38852,13 +39836,13 @@ function resolveNumericSelection(inputChar, length) {
|
|
|
38852
39836
|
}
|
|
38853
39837
|
|
|
38854
39838
|
// src/commands/home-interaction.ts
|
|
38855
|
-
async function
|
|
39839
|
+
async function runSelf2(args, apiUrlOverride) {
|
|
38856
39840
|
const spawnArgs = [...args];
|
|
38857
39841
|
if (apiUrlOverride && !spawnArgs.includes("--api-url")) {
|
|
38858
39842
|
spawnArgs.push("--api-url", apiUrlOverride);
|
|
38859
39843
|
}
|
|
38860
|
-
return await new Promise((
|
|
38861
|
-
const child = (0,
|
|
39844
|
+
return await new Promise((resolve15, reject) => {
|
|
39845
|
+
const child = (0, import_child_process3.spawn)(process.execPath, [process.argv[1], ...spawnArgs], {
|
|
38862
39846
|
stdio: "inherit",
|
|
38863
39847
|
env: {
|
|
38864
39848
|
...process.env,
|
|
@@ -38867,7 +39851,7 @@ async function runSelf(args, apiUrlOverride) {
|
|
|
38867
39851
|
}
|
|
38868
39852
|
});
|
|
38869
39853
|
child.once("error", reject);
|
|
38870
|
-
child.once("close", (code) =>
|
|
39854
|
+
child.once("close", (code) => resolve15(typeof code === "number" ? code : 1));
|
|
38871
39855
|
});
|
|
38872
39856
|
}
|
|
38873
39857
|
function shouldUseInteractiveHome(argv) {
|
|
@@ -38880,7 +39864,7 @@ function numericSelection(inputChar, actionCount) {
|
|
|
38880
39864
|
return resolveNumericSelection(inputChar, actionCount);
|
|
38881
39865
|
}
|
|
38882
39866
|
function renderHome(state, selectedActionIndex, paletteOpen, paletteQuery, paletteSuggestions, selectedPaletteIndex, notice, isBusy) {
|
|
38883
|
-
const actions =
|
|
39867
|
+
const actions = getHomeQuickActions2(state);
|
|
38884
39868
|
renderInteractiveHomeScreen({
|
|
38885
39869
|
state,
|
|
38886
39870
|
actions,
|
|
@@ -38893,31 +39877,6 @@ function renderHome(state, selectedActionIndex, paletteOpen, paletteQuery, palet
|
|
|
38893
39877
|
isBusy
|
|
38894
39878
|
});
|
|
38895
39879
|
}
|
|
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
39880
|
async function runKeyDrivenHomeLoop(apiUrlOverride, runCommand) {
|
|
38922
39881
|
const stateAccessor = () => resolveHomeState(apiUrlOverride);
|
|
38923
39882
|
let state = stateAccessor();
|
|
@@ -38929,7 +39888,7 @@ async function runKeyDrivenHomeLoop(apiUrlOverride, runCommand) {
|
|
|
38929
39888
|
let notice = null;
|
|
38930
39889
|
let isBusy = false;
|
|
38931
39890
|
let finished = false;
|
|
38932
|
-
let actions =
|
|
39891
|
+
let actions = getHomeQuickActions2(state);
|
|
38933
39892
|
const stdin = process.stdin;
|
|
38934
39893
|
const clampPaletteIndex = () => {
|
|
38935
39894
|
if (paletteSuggestions.length === 0) {
|
|
@@ -38942,7 +39901,7 @@ async function runKeyDrivenHomeLoop(apiUrlOverride, runCommand) {
|
|
|
38942
39901
|
}
|
|
38943
39902
|
};
|
|
38944
39903
|
const refreshDerivedState = () => {
|
|
38945
|
-
actions =
|
|
39904
|
+
actions = getHomeQuickActions2(state);
|
|
38946
39905
|
if (actions.length === 0) {
|
|
38947
39906
|
selectedActionIndex = 0;
|
|
38948
39907
|
} else if (selectedActionIndex >= actions.length) {
|
|
@@ -39106,7 +40065,7 @@ async function runKeyDrivenHomeLoop(apiUrlOverride, runCommand) {
|
|
|
39106
40065
|
if (stdin.isTTY) stdin.setRawMode(false);
|
|
39107
40066
|
};
|
|
39108
40067
|
const attachInput = () => {
|
|
39109
|
-
(0,
|
|
40068
|
+
(0, import_readline3.emitKeypressEvents)(stdin);
|
|
39110
40069
|
stdin.on("keypress", onKeypress);
|
|
39111
40070
|
if (stdin.isTTY) stdin.setRawMode(true);
|
|
39112
40071
|
stdin.resume();
|
|
@@ -39129,18 +40088,14 @@ function registerHome(program3) {
|
|
|
39129
40088
|
try {
|
|
39130
40089
|
const explicitStart = process.argv.slice(2).some((token) => token.toLowerCase() === "start");
|
|
39131
40090
|
if (explicitStart) {
|
|
39132
|
-
await runGuidedStart(opts.apiUrl,
|
|
40091
|
+
await runGuidedStart(opts.apiUrl, runSelf2);
|
|
39133
40092
|
}
|
|
39134
40093
|
const state = resolveHomeState(opts.apiUrl);
|
|
39135
40094
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
39136
40095
|
printHome(state);
|
|
39137
40096
|
return;
|
|
39138
40097
|
}
|
|
39139
|
-
|
|
39140
|
-
await runLegacyHomeLoop(state, opts.apiUrl, runSelf, (prompt) => promptChoice(prompt));
|
|
39141
|
-
return;
|
|
39142
|
-
}
|
|
39143
|
-
await runKeyDrivenHomeLoop(opts.apiUrl, runSelf);
|
|
40098
|
+
await runKeyDrivenHomeLoop(opts.apiUrl, runSelf2);
|
|
39144
40099
|
} catch (e) {
|
|
39145
40100
|
if (e instanceof FohError) {
|
|
39146
40101
|
formatError(e);
|
|
@@ -39157,9 +40112,9 @@ function maybeDefaultToHome(argv = process.argv) {
|
|
|
39157
40112
|
}
|
|
39158
40113
|
|
|
39159
40114
|
// src/lib/update.ts
|
|
39160
|
-
var
|
|
39161
|
-
var
|
|
39162
|
-
var
|
|
40115
|
+
var import_fs12 = require("fs");
|
|
40116
|
+
var import_path10 = require("path");
|
|
40117
|
+
var import_child_process4 = require("child_process");
|
|
39163
40118
|
var import_crypto5 = require("crypto");
|
|
39164
40119
|
function parseSemver(version2) {
|
|
39165
40120
|
const trimmed = String(version2 ?? "").trim();
|
|
@@ -39179,7 +40134,7 @@ function compareSemver(a, b) {
|
|
|
39179
40134
|
}
|
|
39180
40135
|
function readPackageJsonVersion(path2) {
|
|
39181
40136
|
try {
|
|
39182
|
-
const raw = (0,
|
|
40137
|
+
const raw = (0, import_fs12.readFileSync)(path2, "utf-8");
|
|
39183
40138
|
const parsed = JSON.parse(raw);
|
|
39184
40139
|
const version2 = String(parsed.version ?? "").trim();
|
|
39185
40140
|
return version2 || void 0;
|
|
@@ -39188,13 +40143,13 @@ function readPackageJsonVersion(path2) {
|
|
|
39188
40143
|
}
|
|
39189
40144
|
}
|
|
39190
40145
|
function findRepoRoot(startCwd = process.cwd()) {
|
|
39191
|
-
let current = (0,
|
|
40146
|
+
let current = (0, import_path10.resolve)(startCwd);
|
|
39192
40147
|
while (true) {
|
|
39193
|
-
const rootPackageJsonPath = (0,
|
|
39194
|
-
const cliPackageJsonPath = (0,
|
|
39195
|
-
if ((0,
|
|
40148
|
+
const rootPackageJsonPath = (0, import_path10.join)(current, "package.json");
|
|
40149
|
+
const cliPackageJsonPath = (0, import_path10.join)(current, "packages", "cli", "package.json");
|
|
40150
|
+
if ((0, import_fs12.existsSync)(rootPackageJsonPath) && (0, import_fs12.existsSync)(cliPackageJsonPath)) {
|
|
39196
40151
|
try {
|
|
39197
|
-
const raw = (0,
|
|
40152
|
+
const raw = (0, import_fs12.readFileSync)(rootPackageJsonPath, "utf-8");
|
|
39198
40153
|
const parsed = JSON.parse(raw);
|
|
39199
40154
|
if (String(parsed.name ?? "").trim() === "front-of-house") {
|
|
39200
40155
|
return current;
|
|
@@ -39202,7 +40157,7 @@ function findRepoRoot(startCwd = process.cwd()) {
|
|
|
39202
40157
|
} catch {
|
|
39203
40158
|
}
|
|
39204
40159
|
}
|
|
39205
|
-
const parent = (0,
|
|
40160
|
+
const parent = (0, import_path10.dirname)(current);
|
|
39206
40161
|
if (parent === current) return void 0;
|
|
39207
40162
|
current = parent;
|
|
39208
40163
|
}
|
|
@@ -39216,7 +40171,7 @@ function detectUpdateAvailability(currentVersion, cwd = process.cwd()) {
|
|
|
39216
40171
|
remediation: "Run this command from the Front Of House repo root to compare/install the latest CLI."
|
|
39217
40172
|
};
|
|
39218
40173
|
}
|
|
39219
|
-
const cliPackageJsonPath = (0,
|
|
40174
|
+
const cliPackageJsonPath = (0, import_path10.join)(repoRoot, "packages", "cli", "package.json");
|
|
39220
40175
|
const latestVersion = readPackageJsonVersion(cliPackageJsonPath);
|
|
39221
40176
|
if (!latestVersion) {
|
|
39222
40177
|
return {
|
|
@@ -39243,20 +40198,20 @@ function detectUpdateAvailability(currentVersion, cwd = process.cwd()) {
|
|
|
39243
40198
|
};
|
|
39244
40199
|
}
|
|
39245
40200
|
async function applyRepoUpdate(repoRoot) {
|
|
39246
|
-
const scriptPath = (0,
|
|
40201
|
+
const scriptPath = (0, import_path10.join)(repoRoot, "scripts", "Install-FohCli.ps1");
|
|
39247
40202
|
if (process.platform === "win32") {
|
|
39248
|
-
return await new Promise((
|
|
39249
|
-
const child = (0,
|
|
40203
|
+
return await new Promise((resolve15, reject) => {
|
|
40204
|
+
const child = (0, import_child_process4.spawn)(
|
|
39250
40205
|
"powershell",
|
|
39251
40206
|
["-ExecutionPolicy", "Bypass", "-File", scriptPath],
|
|
39252
40207
|
{ stdio: "inherit" }
|
|
39253
40208
|
);
|
|
39254
40209
|
child.once("error", reject);
|
|
39255
|
-
child.once("close", (code) =>
|
|
40210
|
+
child.once("close", (code) => resolve15(typeof code === "number" ? code : 1));
|
|
39256
40211
|
});
|
|
39257
40212
|
}
|
|
39258
|
-
return await new Promise((
|
|
39259
|
-
const child = (0,
|
|
40213
|
+
return await new Promise((resolve15, reject) => {
|
|
40214
|
+
const child = (0, import_child_process4.spawn)(
|
|
39260
40215
|
"corepack",
|
|
39261
40216
|
["pnpm", "cli:install:global"],
|
|
39262
40217
|
{
|
|
@@ -39265,7 +40220,7 @@ async function applyRepoUpdate(repoRoot) {
|
|
|
39265
40220
|
}
|
|
39266
40221
|
);
|
|
39267
40222
|
child.once("error", reject);
|
|
39268
|
-
child.once("close", (code) =>
|
|
40223
|
+
child.once("close", (code) => resolve15(typeof code === "number" ? code : 1));
|
|
39269
40224
|
});
|
|
39270
40225
|
}
|
|
39271
40226
|
function shouldShowUpdateNotice(argv = process.argv) {
|
|
@@ -39279,7 +40234,7 @@ function shouldShowUpdateNotice(argv = process.argv) {
|
|
|
39279
40234
|
}
|
|
39280
40235
|
function hashFileSha256(filePath) {
|
|
39281
40236
|
try {
|
|
39282
|
-
const bytes = (0,
|
|
40237
|
+
const bytes = (0, import_fs12.readFileSync)(filePath);
|
|
39283
40238
|
return (0, import_crypto5.createHash)("sha256").update(bytes).digest("hex");
|
|
39284
40239
|
} catch {
|
|
39285
40240
|
return void 0;
|
|
@@ -39289,10 +40244,10 @@ function verifyCliArtifactIntegrity(params = {}) {
|
|
|
39289
40244
|
const cwd = params.cwd ?? process.cwd();
|
|
39290
40245
|
const argv = params.argv ?? process.argv;
|
|
39291
40246
|
const expectedSha256 = String(params.expectedSha256 ?? "").trim().toLowerCase() || void 0;
|
|
39292
|
-
const runtimePath = (0,
|
|
40247
|
+
const runtimePath = (0, import_path10.resolve)(String(argv[1] || ""));
|
|
39293
40248
|
const runtimeHash = runtimePath ? hashFileSha256(runtimePath) : void 0;
|
|
39294
40249
|
const warnings = [];
|
|
39295
|
-
if (!runtimePath || !(0,
|
|
40250
|
+
if (!runtimePath || !(0, import_fs12.existsSync)(runtimePath)) {
|
|
39296
40251
|
warnings.push("runtime_path_unreadable");
|
|
39297
40252
|
}
|
|
39298
40253
|
if (!runtimeHash) {
|
|
@@ -39310,8 +40265,8 @@ function verifyCliArtifactIntegrity(params = {}) {
|
|
|
39310
40265
|
let repoDistHash;
|
|
39311
40266
|
let runtimeMatchesRepoDist;
|
|
39312
40267
|
if (repoRoot) {
|
|
39313
|
-
repoDistPath = (0,
|
|
39314
|
-
if ((0,
|
|
40268
|
+
repoDistPath = (0, import_path10.join)(repoRoot, "packages", "cli", "dist", "foh.js");
|
|
40269
|
+
if ((0, import_fs12.existsSync)(repoDistPath)) {
|
|
39315
40270
|
repoDistHash = hashFileSha256(repoDistPath);
|
|
39316
40271
|
if (runtimeHash && repoDistHash) {
|
|
39317
40272
|
runtimeMatchesRepoDist = runtimeHash === repoDistHash;
|
|
@@ -39401,13 +40356,13 @@ function registerUpdate(program3) {
|
|
|
39401
40356
|
}
|
|
39402
40357
|
|
|
39403
40358
|
// src/commands/eval.ts
|
|
39404
|
-
var
|
|
39405
|
-
var
|
|
39406
|
-
var
|
|
40359
|
+
var import_fs21 = require("fs");
|
|
40360
|
+
var import_path20 = require("path");
|
|
40361
|
+
var import_child_process7 = require("child_process");
|
|
39407
40362
|
|
|
39408
40363
|
// src/lib/external-agent-artifact-safety.ts
|
|
39409
|
-
var
|
|
39410
|
-
var
|
|
40364
|
+
var import_fs13 = require("fs");
|
|
40365
|
+
var import_path11 = require("path");
|
|
39411
40366
|
var DEFAULT_MAX_BYTES_PER_FILE = 5 * 1024 * 1024;
|
|
39412
40367
|
var TEXT_ARTIFACT_NAMES = /* @__PURE__ */ new Set([
|
|
39413
40368
|
"codex-events.jsonl",
|
|
@@ -39431,7 +40386,7 @@ function escapeRegExp(value) {
|
|
|
39431
40386
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
39432
40387
|
}
|
|
39433
40388
|
function pathVariants(path2) {
|
|
39434
|
-
const resolved = (0,
|
|
40389
|
+
const resolved = (0, import_path11.resolve)(path2);
|
|
39435
40390
|
const slash = resolved.replace(/\\/g, "/");
|
|
39436
40391
|
const backslash = resolved.replace(/\//g, "\\");
|
|
39437
40392
|
return Array.from(new Set([
|
|
@@ -39474,9 +40429,9 @@ function redactExternalAgentSecretText(text) {
|
|
|
39474
40429
|
return text.replace(SECRET_QUERY_PARAM_RE, "$1[redacted_secret]").replace(SECRET_RE2, "[redacted_secret]");
|
|
39475
40430
|
}
|
|
39476
40431
|
function artifactFiles(runDir) {
|
|
39477
|
-
if (!(0,
|
|
39478
|
-
return (0,
|
|
39479
|
-
const stat = (0,
|
|
40432
|
+
if (!(0, import_fs13.existsSync)(runDir)) return [];
|
|
40433
|
+
return (0, import_fs13.readdirSync)(runDir).map((name) => (0, import_path11.join)(runDir, name)).filter((path2) => {
|
|
40434
|
+
const stat = (0, import_fs13.statSync)(path2);
|
|
39480
40435
|
const name = path2.split(/[\\/]/).pop() || "";
|
|
39481
40436
|
if (name.endsWith(".redacted")) return false;
|
|
39482
40437
|
return stat.isFile() && (TEXT_ARTIFACT_NAMES.has(name) || name.startsWith("command-output-cmd_"));
|
|
@@ -39502,15 +40457,15 @@ function scanText(input) {
|
|
|
39502
40457
|
].filter(Boolean);
|
|
39503
40458
|
}
|
|
39504
40459
|
function scanExternalAgentArtifacts(options) {
|
|
39505
|
-
const runDir = (0,
|
|
39506
|
-
const privateRepoRoot = options.privateRepoRoot ? (0,
|
|
40460
|
+
const runDir = (0, import_path11.resolve)(options.runDir);
|
|
40461
|
+
const privateRepoRoot = options.privateRepoRoot ? (0, import_path11.resolve)(options.privateRepoRoot) : void 0;
|
|
39507
40462
|
const homeDir = options.homeDir || process.env.USERPROFILE || process.env.HOME;
|
|
39508
40463
|
const maxBytes = options.maxBytesPerFile ?? DEFAULT_MAX_BYTES_PER_FILE;
|
|
39509
40464
|
const files = artifactFiles(runDir);
|
|
39510
40465
|
const findings = [];
|
|
39511
40466
|
const redactedFiles = [];
|
|
39512
40467
|
for (const file2 of files) {
|
|
39513
|
-
const stat = (0,
|
|
40468
|
+
const stat = (0, import_fs13.statSync)(file2);
|
|
39514
40469
|
if (stat.size > maxBytes) {
|
|
39515
40470
|
findings.push({
|
|
39516
40471
|
kind: "artifact_too_large",
|
|
@@ -39520,12 +40475,12 @@ function scanExternalAgentArtifacts(options) {
|
|
|
39520
40475
|
});
|
|
39521
40476
|
continue;
|
|
39522
40477
|
}
|
|
39523
|
-
const text = (0,
|
|
40478
|
+
const text = (0, import_fs13.readFileSync)(file2, "utf8");
|
|
39524
40479
|
findings.push(...scanText({ text, file: file2, privateRepoRoot, homeDir }));
|
|
39525
40480
|
if (options.writeRedacted) {
|
|
39526
40481
|
const redacted = redactExternalAgentArtifactText(text, { privateRepoRoot, homeDir });
|
|
39527
40482
|
const out = `${file2}.redacted`;
|
|
39528
|
-
(0,
|
|
40483
|
+
(0, import_fs13.writeFileSync)(out, redacted, "utf8");
|
|
39529
40484
|
redactedFiles.push(out);
|
|
39530
40485
|
}
|
|
39531
40486
|
}
|
|
@@ -39546,8 +40501,8 @@ function scanExternalAgentArtifacts(options) {
|
|
|
39546
40501
|
}
|
|
39547
40502
|
|
|
39548
40503
|
// src/lib/external-agent-capture.ts
|
|
39549
|
-
var
|
|
39550
|
-
var
|
|
40504
|
+
var import_fs14 = require("fs");
|
|
40505
|
+
var import_path12 = require("path");
|
|
39551
40506
|
var EXTERNAL_AGENT_RUN_DIR_ENV = "FOH_EXTERNAL_AGENT_RUN_DIR";
|
|
39552
40507
|
var EXTERNAL_AGENT_PROMPT_VERSION_ENV = "FOH_EXTERNAL_AGENT_PROMPT_VERSION";
|
|
39553
40508
|
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 +40521,7 @@ function safeJsonLine(value) {
|
|
|
39566
40521
|
function getExternalAgentRunDir() {
|
|
39567
40522
|
const raw = process.env[EXTERNAL_AGENT_RUN_DIR_ENV];
|
|
39568
40523
|
if (!raw || !raw.trim()) return null;
|
|
39569
|
-
return (0,
|
|
40524
|
+
return (0, import_path12.resolve)(raw);
|
|
39570
40525
|
}
|
|
39571
40526
|
function commandIdFor(input) {
|
|
39572
40527
|
const seed = `${input.startedAt}:${input.argv.join("\0")}:${process.pid}`;
|
|
@@ -39617,7 +40572,7 @@ function recordExternalAgentCliInvocation(input) {
|
|
|
39617
40572
|
const runDir = getExternalAgentRunDir();
|
|
39618
40573
|
if (!runDir) return null;
|
|
39619
40574
|
try {
|
|
39620
|
-
(0,
|
|
40575
|
+
(0, import_fs14.mkdirSync)(runDir, { recursive: true });
|
|
39621
40576
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
39622
40577
|
const args = input.argv.slice(2).map((arg) => redactText(String(arg)));
|
|
39623
40578
|
const commandId = commandIdFor({ startedAt, argv: args });
|
|
@@ -39636,7 +40591,7 @@ function recordExternalAgentCliInvocation(input) {
|
|
|
39636
40591
|
prompt_version: promptVersion,
|
|
39637
40592
|
started_at: startedAt
|
|
39638
40593
|
};
|
|
39639
|
-
(0,
|
|
40594
|
+
(0, import_fs14.appendFileSync)((0, import_path12.join)(runDir, "commands.ndjson"), safeJsonLine(record2), "utf8");
|
|
39640
40595
|
return {
|
|
39641
40596
|
commandId,
|
|
39642
40597
|
runDir,
|
|
@@ -39697,7 +40652,7 @@ function completeExternalAgentCliInvocation(capture, input) {
|
|
|
39697
40652
|
let artifact = null;
|
|
39698
40653
|
if (output.trim()) {
|
|
39699
40654
|
artifact = outputArtifactName(capture.commandId);
|
|
39700
|
-
(0,
|
|
40655
|
+
(0, import_fs14.writeFileSync)((0, import_path12.join)(capture.runDir, artifact), output, "utf8");
|
|
39701
40656
|
}
|
|
39702
40657
|
const record2 = {
|
|
39703
40658
|
schema_version: "external_agent_cli_command.v2",
|
|
@@ -39720,12 +40675,12 @@ function completeExternalAgentCliInvocation(capture, input) {
|
|
|
39720
40675
|
output_artifact: artifact,
|
|
39721
40676
|
output_bytes: Buffer.byteLength(output, "utf8")
|
|
39722
40677
|
};
|
|
39723
|
-
(0,
|
|
40678
|
+
(0, import_fs14.appendFileSync)((0, import_path12.join)(capture.runDir, "commands.ndjson"), safeJsonLine(record2), "utf8");
|
|
39724
40679
|
}
|
|
39725
40680
|
function readCommandRecords(runDir) {
|
|
39726
|
-
const commandLogPath = (0,
|
|
39727
|
-
if (!(0,
|
|
39728
|
-
const records = (0,
|
|
40681
|
+
const commandLogPath = (0, import_path12.join)(runDir, "commands.ndjson");
|
|
40682
|
+
if (!(0, import_fs14.existsSync)(commandLogPath)) return [];
|
|
40683
|
+
const records = (0, import_fs14.readFileSync)(commandLogPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
|
|
39729
40684
|
const byId = /* @__PURE__ */ new Map();
|
|
39730
40685
|
const ordered = [];
|
|
39731
40686
|
for (const record2 of records) {
|
|
@@ -39737,13 +40692,13 @@ function readCommandRecords(runDir) {
|
|
|
39737
40692
|
}
|
|
39738
40693
|
|
|
39739
40694
|
// src/lib/external-agent-executor.ts
|
|
39740
|
-
var
|
|
39741
|
-
var
|
|
39742
|
-
var
|
|
39743
|
-
var
|
|
40695
|
+
var import_fs20 = require("fs");
|
|
40696
|
+
var import_os3 = require("os");
|
|
40697
|
+
var import_path19 = require("path");
|
|
40698
|
+
var import_child_process6 = require("child_process");
|
|
39744
40699
|
|
|
39745
40700
|
// src/lib/external-agent-executor-env.ts
|
|
39746
|
-
var
|
|
40701
|
+
var import_path13 = require("path");
|
|
39747
40702
|
var CODEX_EXECUTOR_DENIED_ENV_PREFIXES = [
|
|
39748
40703
|
"SUPABASE_",
|
|
39749
40704
|
"DATABASE_",
|
|
@@ -39807,7 +40762,7 @@ function buildCodexExecutorEnv(input) {
|
|
|
39807
40762
|
env[childKey] = value;
|
|
39808
40763
|
}
|
|
39809
40764
|
}
|
|
39810
|
-
env.npm_config_cache = (0,
|
|
40765
|
+
env.npm_config_cache = (0, import_path13.join)((0, import_path13.dirname)(input.runDir), ".npm-cache");
|
|
39811
40766
|
env.npm_config_prefer_online = "true";
|
|
39812
40767
|
env.npm_config_update_notifier = "false";
|
|
39813
40768
|
env.npm_config_yes = "true";
|
|
@@ -39819,12 +40774,12 @@ function buildCodexExecutorEnv(input) {
|
|
|
39819
40774
|
}
|
|
39820
40775
|
|
|
39821
40776
|
// src/lib/external-agent-executor-artifacts.ts
|
|
39822
|
-
var
|
|
39823
|
-
var
|
|
40777
|
+
var import_fs16 = require("fs");
|
|
40778
|
+
var import_path15 = require("path");
|
|
39824
40779
|
|
|
39825
40780
|
// src/lib/external-agent-metadata.ts
|
|
39826
|
-
var
|
|
39827
|
-
var
|
|
40781
|
+
var import_fs15 = require("fs");
|
|
40782
|
+
var import_path14 = require("path");
|
|
39828
40783
|
var EXTERNAL_AGENT_METADATA_FILENAMES = [
|
|
39829
40784
|
"external-agent-metadata.json",
|
|
39830
40785
|
"agent-metadata.json"
|
|
@@ -39846,10 +40801,10 @@ function collectDocsFrom(value, docs) {
|
|
|
39846
40801
|
}
|
|
39847
40802
|
function readExternalAgentMetadata(runDir) {
|
|
39848
40803
|
for (const filename of EXTERNAL_AGENT_METADATA_FILENAMES) {
|
|
39849
|
-
const path2 = (0,
|
|
39850
|
-
if (!(0,
|
|
40804
|
+
const path2 = (0, import_path14.join)(runDir, filename);
|
|
40805
|
+
if (!(0, import_fs15.existsSync)(path2)) continue;
|
|
39851
40806
|
try {
|
|
39852
|
-
const parsed = JSON.parse((0,
|
|
40807
|
+
const parsed = JSON.parse((0, import_fs15.readFileSync)(path2, "utf8"));
|
|
39853
40808
|
const docs = /* @__PURE__ */ new Set();
|
|
39854
40809
|
collectDocsFrom(parsed.docs_pages_used, docs);
|
|
39855
40810
|
collectDocsFrom(parsed.docs_pages_observed, docs);
|
|
@@ -39878,44 +40833,44 @@ function readExternalAgentMetadata(runDir) {
|
|
|
39878
40833
|
|
|
39879
40834
|
// src/lib/external-agent-executor-artifacts.ts
|
|
39880
40835
|
function redactArtifactFile(path2, input = {}) {
|
|
39881
|
-
if (!(0,
|
|
39882
|
-
const original = (0,
|
|
40836
|
+
if (!(0, import_fs16.existsSync)(path2)) return;
|
|
40837
|
+
const original = (0, import_fs16.readFileSync)(path2, "utf8");
|
|
39883
40838
|
const redacted = redactExternalAgentArtifactText(original, input);
|
|
39884
|
-
if (redacted !== original) (0,
|
|
40839
|
+
if (redacted !== original) (0, import_fs16.writeFileSync)(path2, redacted, "utf8");
|
|
39885
40840
|
}
|
|
39886
40841
|
function redactExternalAgentOutputArtifacts(run, input = {}) {
|
|
39887
40842
|
redactArtifactFile(run.outputs.jsonl, input);
|
|
39888
40843
|
redactArtifactFile(run.outputs.last_message, input);
|
|
39889
40844
|
redactArtifactFile(run.outputs.stderr, input);
|
|
39890
|
-
redactArtifactFile((0,
|
|
39891
|
-
if (!(0,
|
|
39892
|
-
for (const name of (0,
|
|
40845
|
+
redactArtifactFile((0, import_path15.join)(run.run_dir, "commands.ndjson"), input);
|
|
40846
|
+
if (!(0, import_fs16.existsSync)(run.run_dir)) return;
|
|
40847
|
+
for (const name of (0, import_fs16.readdirSync)(run.run_dir)) {
|
|
39893
40848
|
if (name.startsWith("command-output-cmd_") && !name.endsWith(".redacted")) {
|
|
39894
|
-
redactArtifactFile((0,
|
|
40849
|
+
redactArtifactFile((0, import_path15.join)(run.run_dir, name), input);
|
|
39895
40850
|
}
|
|
39896
40851
|
}
|
|
39897
40852
|
}
|
|
39898
40853
|
function copyExternalAgentCommandCaptureArtifacts(input) {
|
|
39899
|
-
const commandLog = (0,
|
|
39900
|
-
if ((0,
|
|
39901
|
-
(0,
|
|
40854
|
+
const commandLog = (0, import_path15.join)(input.captureDir, "commands.ndjson");
|
|
40855
|
+
if ((0, import_fs16.existsSync)(commandLog)) {
|
|
40856
|
+
(0, import_fs16.writeFileSync)((0, import_path15.join)(input.runDir, "commands.ndjson"), (0, import_fs16.readFileSync)(commandLog, "utf8"), "utf8");
|
|
39902
40857
|
}
|
|
39903
|
-
for (const name of (0,
|
|
40858
|
+
for (const name of (0, import_fs16.readdirSync)(input.captureDir)) {
|
|
39904
40859
|
if (name.startsWith("command-output-cmd_")) {
|
|
39905
|
-
(0,
|
|
40860
|
+
(0, import_fs16.copyFileSync)((0, import_path15.join)(input.captureDir, name), (0, import_path15.join)(input.runDir, name));
|
|
39906
40861
|
} else if (EXTERNAL_AGENT_METADATA_FILENAMES.includes(name)) {
|
|
39907
|
-
(0,
|
|
40862
|
+
(0, import_fs16.copyFileSync)((0, import_path15.join)(input.captureDir, name), (0, import_path15.join)(input.runDir, name));
|
|
39908
40863
|
}
|
|
39909
40864
|
}
|
|
39910
40865
|
}
|
|
39911
40866
|
|
|
39912
40867
|
// src/lib/external-agent-executor-classification.ts
|
|
39913
|
-
var
|
|
39914
|
-
var
|
|
40868
|
+
var import_fs18 = require("fs");
|
|
40869
|
+
var import_path17 = require("path");
|
|
39915
40870
|
|
|
39916
40871
|
// src/lib/external-agent-run-summary.ts
|
|
39917
|
-
var
|
|
39918
|
-
var
|
|
40872
|
+
var import_fs17 = require("fs");
|
|
40873
|
+
var import_path16 = require("path");
|
|
39919
40874
|
var REQUIRED_RUN_FIELDS = [
|
|
39920
40875
|
"schema_version",
|
|
39921
40876
|
"run_id",
|
|
@@ -39939,8 +40894,8 @@ function quoteShellArg(value) {
|
|
|
39939
40894
|
return `"${text.replace(/(["$`])/g, "\\$1")}"`;
|
|
39940
40895
|
}
|
|
39941
40896
|
function externalAgentSummaryCommand(root) {
|
|
39942
|
-
const summaryPath = (0,
|
|
39943
|
-
const reportPath = (0,
|
|
40897
|
+
const summaryPath = (0, import_path16.join)(root, "latest-summary.json");
|
|
40898
|
+
const reportPath = (0, import_path16.join)(root, "summary.report.json");
|
|
39944
40899
|
return [
|
|
39945
40900
|
"foh",
|
|
39946
40901
|
"eval",
|
|
@@ -39956,11 +40911,11 @@ function externalAgentSummaryCommand(root) {
|
|
|
39956
40911
|
].join(" ");
|
|
39957
40912
|
}
|
|
39958
40913
|
function readJson(filePath) {
|
|
39959
|
-
return JSON.parse((0,
|
|
40914
|
+
return JSON.parse((0, import_fs17.readFileSync)(filePath, "utf8").replace(/^\uFEFF/, ""));
|
|
39960
40915
|
}
|
|
39961
40916
|
function readNdjson(filePath) {
|
|
39962
|
-
if (!(0,
|
|
39963
|
-
return (0,
|
|
40917
|
+
if (!(0, import_fs17.existsSync)(filePath)) return [];
|
|
40918
|
+
return (0, import_fs17.readFileSync)(filePath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
39964
40919
|
try {
|
|
39965
40920
|
const parsed = JSON.parse(line);
|
|
39966
40921
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
@@ -39986,7 +40941,7 @@ function collectDocUrls(text) {
|
|
|
39986
40941
|
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
40942
|
}
|
|
39988
40943
|
function findRunCandidates(root) {
|
|
39989
|
-
if (!(0,
|
|
40944
|
+
if (!(0, import_fs17.existsSync)(root)) return [];
|
|
39990
40945
|
const candidates = [];
|
|
39991
40946
|
const seenRunDirs = /* @__PURE__ */ new Set();
|
|
39992
40947
|
const captureDirs = [];
|
|
@@ -39994,13 +40949,13 @@ function findRunCandidates(root) {
|
|
|
39994
40949
|
while (stack.length > 0) {
|
|
39995
40950
|
const current = stack.pop();
|
|
39996
40951
|
if (!current) continue;
|
|
39997
|
-
for (const entry of (0,
|
|
39998
|
-
const absolute = (0,
|
|
40952
|
+
for (const entry of (0, import_fs17.readdirSync)(current, { withFileTypes: true })) {
|
|
40953
|
+
const absolute = (0, import_path16.join)(current, entry.name);
|
|
39999
40954
|
if (entry.isDirectory()) {
|
|
40000
40955
|
stack.push(absolute);
|
|
40001
40956
|
} else if (entry.isFile() && entry.name === "run.json") {
|
|
40002
40957
|
candidates.push({ path: absolute, synthetic: false });
|
|
40003
|
-
seenRunDirs.add((0,
|
|
40958
|
+
seenRunDirs.add((0, import_path16.dirname)(absolute));
|
|
40004
40959
|
} else if (entry.isFile() && entry.name === "commands.ndjson") {
|
|
40005
40960
|
captureDirs.push(current);
|
|
40006
40961
|
}
|
|
@@ -40008,7 +40963,7 @@ function findRunCandidates(root) {
|
|
|
40008
40963
|
}
|
|
40009
40964
|
for (const captureDir of captureDirs) {
|
|
40010
40965
|
if (seenRunDirs.has(captureDir)) continue;
|
|
40011
|
-
candidates.push({ path: (0,
|
|
40966
|
+
candidates.push({ path: (0, import_path16.join)(captureDir, "run.json"), synthetic: true });
|
|
40012
40967
|
}
|
|
40013
40968
|
return candidates.sort((a, b) => a.path.localeCompare(b.path));
|
|
40014
40969
|
}
|
|
@@ -40086,9 +41041,9 @@ function syntheticStatusFromCommands(commands) {
|
|
|
40086
41041
|
return { status: "pass", reasonCode: null };
|
|
40087
41042
|
}
|
|
40088
41043
|
function synthesizeRunFromCapture(runPath) {
|
|
40089
|
-
const runDir = (0,
|
|
40090
|
-
const commands = collapseCommandRecords(readNdjson((0,
|
|
40091
|
-
const metadata = asObject((0,
|
|
41044
|
+
const runDir = (0, import_path16.dirname)(runPath);
|
|
41045
|
+
const commands = collapseCommandRecords(readNdjson((0, import_path16.join)(runDir, "commands.ndjson")));
|
|
41046
|
+
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
41047
|
const blockerCodes = toArray2(metadata?.blocker_reason_codes).map(String).filter(Boolean);
|
|
40093
41048
|
const commandClassification = syntheticStatusFromCommands(commands);
|
|
40094
41049
|
const status = commandClassification.status === "fail" ? "fail" : blockerCodes.length > 0 ? "hold" : commandClassification.status;
|
|
@@ -40097,7 +41052,7 @@ function synthesizeRunFromCapture(runPath) {
|
|
|
40097
41052
|
const startedAt = firstCommandTime(commands) || (/* @__PURE__ */ new Date(0)).toISOString();
|
|
40098
41053
|
const endedAt = latestCommandTime(commands) || startedAt;
|
|
40099
41054
|
const docs = toArray2(metadata?.docs_pages_used).map(String).filter(Boolean);
|
|
40100
|
-
const runId = (0,
|
|
41055
|
+
const runId = (0, import_path16.dirname)(runPath).split(/[\\/]/).filter(Boolean).slice(-3).join("-") || "capture-only-run";
|
|
40101
41056
|
return {
|
|
40102
41057
|
schema_version: "external_agent_run.v1",
|
|
40103
41058
|
run_id: runId,
|
|
@@ -40121,14 +41076,14 @@ function synthesizeRunFromCapture(runPath) {
|
|
|
40121
41076
|
docs_pages_used: docs,
|
|
40122
41077
|
artifacts: {
|
|
40123
41078
|
command_log: "commands.ndjson",
|
|
40124
|
-
agent_metadata: (0,
|
|
41079
|
+
agent_metadata: (0, import_fs17.existsSync)((0, import_path16.join)(runDir, "external-agent-metadata.json")) ? "external-agent-metadata.json" : null,
|
|
40125
41080
|
capture_only: true
|
|
40126
41081
|
},
|
|
40127
41082
|
summary: "Synthetic run record created from external-agent capture artifacts because run.json was missing."
|
|
40128
41083
|
};
|
|
40129
41084
|
}
|
|
40130
41085
|
function cohortIdForRunPath(root, runPath) {
|
|
40131
|
-
const normalized = (0,
|
|
41086
|
+
const normalized = (0, import_path16.relative)(root, (0, import_path16.dirname)(runPath)).replaceAll("\\", "/");
|
|
40132
41087
|
const parts = normalized.split("/").filter(Boolean);
|
|
40133
41088
|
if (parts.length === 0) return ".";
|
|
40134
41089
|
if (/^\d{4}-\d{2}-\d{2}$/.test(parts[0]) && parts[1]) return `${parts[0]}/${parts[1]}`;
|
|
@@ -40143,7 +41098,7 @@ function readRunRecords(root, cwd) {
|
|
|
40143
41098
|
const parsed = candidate.synthetic ? synthesizeRunFromCapture(file2) : readJson(file2);
|
|
40144
41099
|
const findings = validateExternalAgentRun(parsed);
|
|
40145
41100
|
if (findings.length > 0) {
|
|
40146
|
-
invalid_runs.push({ path: (0,
|
|
41101
|
+
invalid_runs.push({ path: (0, import_path16.relative)(cwd, file2).replaceAll("\\", "/"), findings });
|
|
40147
41102
|
continue;
|
|
40148
41103
|
}
|
|
40149
41104
|
const run = parsed;
|
|
@@ -40155,7 +41110,7 @@ function readRunRecords(root, cwd) {
|
|
|
40155
41110
|
});
|
|
40156
41111
|
} catch (error2) {
|
|
40157
41112
|
invalid_runs.push({
|
|
40158
|
-
path: (0,
|
|
41113
|
+
path: (0, import_path16.relative)(cwd, file2).replaceAll("\\", "/"),
|
|
40159
41114
|
findings: [{ id: "json_parse_failed", detail: error2 instanceof Error ? error2.message : String(error2) }]
|
|
40160
41115
|
});
|
|
40161
41116
|
}
|
|
@@ -40200,10 +41155,10 @@ function collapseCommandRecords(records) {
|
|
|
40200
41155
|
function readCommandOutputJson(runDir, command) {
|
|
40201
41156
|
const artifact = typeof command.output_artifact === "string" && command.output_artifact.trim() ? command.output_artifact.trim() : "";
|
|
40202
41157
|
if (!artifact || artifact.includes("..") || artifact.includes("/") || artifact.includes("\\")) return null;
|
|
40203
|
-
const artifactPath = (0,
|
|
40204
|
-
if (!(0,
|
|
41158
|
+
const artifactPath = (0, import_path16.join)(runDir, artifact);
|
|
41159
|
+
if (!(0, import_fs17.existsSync)(artifactPath)) return null;
|
|
40205
41160
|
try {
|
|
40206
|
-
const text = (0,
|
|
41161
|
+
const text = (0, import_fs17.readFileSync)(artifactPath, "utf8");
|
|
40207
41162
|
const firstObject = text.indexOf("{");
|
|
40208
41163
|
const lastObject = text.lastIndexOf("}");
|
|
40209
41164
|
if (firstObject < 0 || lastObject <= firstObject) return null;
|
|
@@ -40257,8 +41212,8 @@ function commandTimingBreakdown(command, output) {
|
|
|
40257
41212
|
return null;
|
|
40258
41213
|
}
|
|
40259
41214
|
function analyzeRunArtifacts(runPath, run, cwd) {
|
|
40260
|
-
const runDir = (0,
|
|
40261
|
-
const commands = collapseCommandRecords(readNdjson((0,
|
|
41215
|
+
const runDir = (0, import_path16.dirname)(runPath);
|
|
41216
|
+
const commands = collapseCommandRecords(readNdjson((0, import_path16.join)(runDir, "commands.ndjson")));
|
|
40262
41217
|
const reasonCounts = /* @__PURE__ */ new Map();
|
|
40263
41218
|
const slowSteps = [];
|
|
40264
41219
|
const timingBreakdowns = [];
|
|
@@ -40270,7 +41225,7 @@ function analyzeRunArtifacts(runPath, run, cwd) {
|
|
|
40270
41225
|
const breakdown = commandTimingBreakdown(command, output);
|
|
40271
41226
|
if (breakdown) timingBreakdowns.push({
|
|
40272
41227
|
run_id: run.run_id,
|
|
40273
|
-
run_path: (0,
|
|
41228
|
+
run_path: (0, import_path16.relative)(cwd, runPath).replaceAll("\\", "/"),
|
|
40274
41229
|
...breakdown
|
|
40275
41230
|
});
|
|
40276
41231
|
if (command.phase === "completed" || command.completed_at) completed += 1;
|
|
@@ -40279,7 +41234,7 @@ function analyzeRunArtifacts(runPath, run, cwd) {
|
|
|
40279
41234
|
totalDuration += command.duration_ms;
|
|
40280
41235
|
slowSteps.push({
|
|
40281
41236
|
run_id: run.run_id,
|
|
40282
|
-
run_path: (0,
|
|
41237
|
+
run_path: (0, import_path16.relative)(cwd, runPath).replaceAll("\\", "/"),
|
|
40283
41238
|
command: command.command || "",
|
|
40284
41239
|
duration_ms: command.duration_ms,
|
|
40285
41240
|
status: command.status || null,
|
|
@@ -40292,7 +41247,7 @@ function analyzeRunArtifacts(runPath, run, cwd) {
|
|
|
40292
41247
|
if (reasonCode) increment(reasonCounts, reasonCode);
|
|
40293
41248
|
}
|
|
40294
41249
|
}
|
|
40295
|
-
const codexEvents = readNdjson((0,
|
|
41250
|
+
const codexEvents = readNdjson((0, import_path16.join)(runDir, "codex-exec.jsonl"));
|
|
40296
41251
|
const codexDocs = /* @__PURE__ */ new Set();
|
|
40297
41252
|
let codexCommandExecutions = 0;
|
|
40298
41253
|
let codexFailedExitCodes = 0;
|
|
@@ -40309,7 +41264,7 @@ function analyzeRunArtifacts(runPath, run, cwd) {
|
|
|
40309
41264
|
...Array.from(codexDocs)
|
|
40310
41265
|
]);
|
|
40311
41266
|
return {
|
|
40312
|
-
command_log_present: (0,
|
|
41267
|
+
command_log_present: (0, import_fs17.existsSync)((0, import_path16.join)(runDir, "commands.ndjson")),
|
|
40313
41268
|
command_count: commands.length,
|
|
40314
41269
|
completed_command_count: completed,
|
|
40315
41270
|
missing_completion_count: Math.max(0, commands.length - completed),
|
|
@@ -40324,8 +41279,8 @@ function analyzeRunArtifacts(runPath, run, cwd) {
|
|
|
40324
41279
|
};
|
|
40325
41280
|
}
|
|
40326
41281
|
function summarizeExternalAgentRuns(options) {
|
|
40327
|
-
const cwd = (0,
|
|
40328
|
-
const root = (0,
|
|
41282
|
+
const cwd = (0, import_path16.resolve)(options.cwd || process.cwd());
|
|
41283
|
+
const root = (0, import_path16.resolve)(cwd, options.root);
|
|
40329
41284
|
const loaded = readRunRecords(root, cwd);
|
|
40330
41285
|
const selectedCohortId = options.cohortId || (options.currentBaselineOnly ? latestCohortId(loaded.records) : null);
|
|
40331
41286
|
const records = selectedCohortId ? loaded.records.filter((record2) => record2.cohort_id === selectedCohortId) : loaded.records;
|
|
@@ -40380,7 +41335,7 @@ function summarizeExternalAgentRuns(options) {
|
|
|
40380
41335
|
return {
|
|
40381
41336
|
schema_version: "external_agent_run_summary.v1",
|
|
40382
41337
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
40383
|
-
root: (0,
|
|
41338
|
+
root: (0, import_path16.relative)(cwd, root).replaceAll("\\", "/") || ".",
|
|
40384
41339
|
cohort_id: selectedCohortId,
|
|
40385
41340
|
current_baseline_only: Boolean(selectedCohortId),
|
|
40386
41341
|
run_count: records.length,
|
|
@@ -40415,7 +41370,7 @@ function summarizeExternalAgentRuns(options) {
|
|
|
40415
41370
|
},
|
|
40416
41371
|
next_commands: nextRecommendedFix ? [`foh bug improve --from external-agent-run --file <run_dir>/run.json --json`] : [],
|
|
40417
41372
|
invalid_runs: selectedCohortId ? [] : loaded.invalid_runs,
|
|
40418
|
-
run_paths: records.map((record2) => (0,
|
|
41373
|
+
run_paths: records.map((record2) => (0, import_path16.relative)(cwd, record2.path).replaceAll("\\", "/")).sort()
|
|
40419
41374
|
};
|
|
40420
41375
|
}
|
|
40421
41376
|
function runExternalAgentRunSummary(options) {
|
|
@@ -40435,13 +41390,13 @@ function runExternalAgentRunSummary(options) {
|
|
|
40435
41390
|
report: summary
|
|
40436
41391
|
};
|
|
40437
41392
|
if (options.out) {
|
|
40438
|
-
(0,
|
|
40439
|
-
(0,
|
|
41393
|
+
(0, import_fs17.mkdirSync)((0, import_path16.dirname)((0, import_path16.resolve)(options.cwd || process.cwd(), options.out)), { recursive: true });
|
|
41394
|
+
(0, import_fs17.writeFileSync)((0, import_path16.resolve)(options.cwd || process.cwd(), options.out), `${JSON.stringify(summary, null, 2)}
|
|
40440
41395
|
`, "utf8");
|
|
40441
41396
|
}
|
|
40442
41397
|
if (options.report) {
|
|
40443
|
-
(0,
|
|
40444
|
-
(0,
|
|
41398
|
+
(0, import_fs17.mkdirSync)((0, import_path16.dirname)((0, import_path16.resolve)(options.cwd || process.cwd(), options.report)), { recursive: true });
|
|
41399
|
+
(0, import_fs17.writeFileSync)((0, import_path16.resolve)(options.cwd || process.cwd(), options.report), `${JSON.stringify(report, null, 2)}
|
|
40445
41400
|
`, "utf8");
|
|
40446
41401
|
}
|
|
40447
41402
|
return { summary, report };
|
|
@@ -40449,20 +41404,20 @@ function runExternalAgentRunSummary(options) {
|
|
|
40449
41404
|
|
|
40450
41405
|
// src/lib/external-agent-executor-classification.ts
|
|
40451
41406
|
function proofArtifactPasses(runDir) {
|
|
40452
|
-
const proofPath = (0,
|
|
40453
|
-
if (!(0,
|
|
41407
|
+
const proofPath = (0, import_path17.join)(runDir, "proof.json");
|
|
41408
|
+
if (!(0, import_fs18.existsSync)(proofPath)) return false;
|
|
40454
41409
|
try {
|
|
40455
|
-
const parsed = JSON.parse((0,
|
|
41410
|
+
const parsed = JSON.parse((0, import_fs18.readFileSync)(proofPath, "utf8"));
|
|
40456
41411
|
return parsed.ok === true || parsed.status === "pass" || parsed.status === "passed";
|
|
40457
41412
|
} catch {
|
|
40458
41413
|
return false;
|
|
40459
41414
|
}
|
|
40460
41415
|
}
|
|
40461
41416
|
function readIfExists(path2) {
|
|
40462
|
-
return (0,
|
|
41417
|
+
return (0, import_fs18.existsSync)(path2) ? (0, import_fs18.readFileSync)(path2, "utf8") : "";
|
|
40463
41418
|
}
|
|
40464
41419
|
function relativeArtifactName(path2) {
|
|
40465
|
-
return (0,
|
|
41420
|
+
return (0, import_path17.basename)(path2);
|
|
40466
41421
|
}
|
|
40467
41422
|
function classifyExternalAgentRun(input) {
|
|
40468
41423
|
if (input.timedOut) return { status: "hold", reasonCode: `${input.run.command}_runner_timeout` };
|
|
@@ -40617,13 +41572,13 @@ function buildExecutedExternalAgentRunArtifact(input) {
|
|
|
40617
41572
|
},
|
|
40618
41573
|
artifacts: {
|
|
40619
41574
|
terminal_transcript: relativeArtifactName(input.run.outputs.jsonl),
|
|
40620
|
-
command_log: (0,
|
|
40621
|
-
proof_bundle: (0,
|
|
40622
|
-
replay_packet: (0,
|
|
40623
|
-
knowledge_packet: (0,
|
|
41575
|
+
command_log: (0, import_fs18.existsSync)((0, import_path17.join)(input.run.run_dir, "commands.ndjson")) ? "commands.ndjson" : null,
|
|
41576
|
+
proof_bundle: (0, import_fs18.existsSync)((0, import_path17.join)(input.run.run_dir, "proof.json")) ? "proof.json" : null,
|
|
41577
|
+
replay_packet: (0, import_fs18.existsSync)((0, import_path17.join)(input.run.run_dir, "replay.json")) ? "replay.json" : null,
|
|
41578
|
+
knowledge_packet: (0, import_fs18.existsSync)((0, import_path17.join)(input.run.run_dir, "knowledge.json")) ? "knowledge.json" : null,
|
|
40624
41579
|
improvement_packet: input.status === "pass" ? null : "improvement-packet.json",
|
|
40625
41580
|
agent_metadata: agentMetadata.path,
|
|
40626
|
-
notes: (0,
|
|
41581
|
+
notes: (0, import_fs18.existsSync)((0, import_path17.join)(input.run.run_dir, "notes.md")) ? "notes.md" : null,
|
|
40627
41582
|
runner_last_message: relativeArtifactName(input.run.outputs.last_message),
|
|
40628
41583
|
runner_stderr: relativeArtifactName(input.run.outputs.stderr),
|
|
40629
41584
|
codex_last_message: input.run.command === "codex" ? relativeArtifactName(input.run.outputs.last_message) : null,
|
|
@@ -40631,25 +41586,25 @@ function buildExecutedExternalAgentRunArtifact(input) {
|
|
|
40631
41586
|
artifact_safety: relativeArtifactName(input.run.outputs.artifact_safety)
|
|
40632
41587
|
},
|
|
40633
41588
|
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,
|
|
41589
|
+
next_commands: input.status === "pass" ? [externalAgentSummaryCommand((0, import_path17.dirname)(input.run.run_dir))] : [
|
|
40635
41590
|
"foh eval external-agent scan-artifacts --run-dir <run_dir> --private-repo-root <private_repo_root> --write-redacted --json",
|
|
40636
41591
|
"foh bug improve --from external-agent-run --file <run_dir>/run.json --out <run_dir>/improvement-packet.json --json",
|
|
40637
|
-
externalAgentSummaryCommand((0,
|
|
41592
|
+
externalAgentSummaryCommand((0, import_path17.dirname)(input.run.run_dir))
|
|
40638
41593
|
]
|
|
40639
41594
|
};
|
|
40640
41595
|
}
|
|
40641
41596
|
|
|
40642
41597
|
// src/lib/external-agent-runner-execution.ts
|
|
40643
|
-
var
|
|
40644
|
-
var
|
|
40645
|
-
var
|
|
41598
|
+
var import_child_process5 = require("child_process");
|
|
41599
|
+
var import_fs19 = require("fs");
|
|
41600
|
+
var import_path18 = require("path");
|
|
40646
41601
|
function buildCommandInvocation(command, args) {
|
|
40647
41602
|
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,
|
|
41603
|
+
const binDir = (0, import_path18.dirname)(command);
|
|
41604
|
+
const codexEntrypoint = (0, import_path18.join)(binDir, "node_modules", "@openai", "codex", "bin", "codex.js");
|
|
41605
|
+
if ((0, import_fs19.existsSync)(codexEntrypoint)) return { command: process.execPath, args: [codexEntrypoint, ...args] };
|
|
41606
|
+
const geminiEntrypoint = (0, import_path18.join)(binDir, "node_modules", "@google", "gemini-cli", "bundle", "gemini.js");
|
|
41607
|
+
if ((0, import_fs19.existsSync)(geminiEntrypoint)) return { command: process.execPath, args: ["--no-warnings=DEP0040", geminiEntrypoint, ...args] };
|
|
40653
41608
|
}
|
|
40654
41609
|
return { command, args };
|
|
40655
41610
|
}
|
|
@@ -40657,15 +41612,15 @@ function spawnExternalAgentRunner(input) {
|
|
|
40657
41612
|
return new Promise((resolveRun) => {
|
|
40658
41613
|
const started = Date.now();
|
|
40659
41614
|
const commandInvocation = buildCommandInvocation(input.command, input.args);
|
|
40660
|
-
const child = (0,
|
|
41615
|
+
const child = (0, import_child_process5.spawn)(commandInvocation.command, commandInvocation.args, {
|
|
40661
41616
|
cwd: input.cwd,
|
|
40662
41617
|
env: input.env,
|
|
40663
41618
|
shell: false,
|
|
40664
41619
|
stdio: ["pipe", "pipe", "pipe"],
|
|
40665
41620
|
windowsHide: true
|
|
40666
41621
|
});
|
|
40667
|
-
const stdout = (0,
|
|
40668
|
-
const stderr = (0,
|
|
41622
|
+
const stdout = (0, import_fs19.createWriteStream)(input.stdoutPath, { flags: "w" });
|
|
41623
|
+
const stderr = (0, import_fs19.createWriteStream)(input.stderrPath, { flags: "w" });
|
|
40669
41624
|
child.stdout.pipe(stdout);
|
|
40670
41625
|
child.stderr.pipe(stderr);
|
|
40671
41626
|
child.stdin.end(input.prompt);
|
|
@@ -40673,7 +41628,7 @@ function spawnExternalAgentRunner(input) {
|
|
|
40673
41628
|
const timer = setTimeout(() => {
|
|
40674
41629
|
timedOut = true;
|
|
40675
41630
|
if (child.pid && process.platform === "win32") {
|
|
40676
|
-
(0,
|
|
41631
|
+
(0, import_child_process5.spawnSync)("taskkill.exe", ["/pid", String(child.pid), "/t", "/f"], { stdio: "ignore" });
|
|
40677
41632
|
} else {
|
|
40678
41633
|
child.kill("SIGKILL");
|
|
40679
41634
|
}
|
|
@@ -40777,14 +41732,14 @@ async function runExternalAgentEvalAuthPreflight(env = process.env, options = {}
|
|
|
40777
41732
|
};
|
|
40778
41733
|
}
|
|
40779
41734
|
function normalizeForCompare(path2) {
|
|
40780
|
-
const resolved = (0,
|
|
41735
|
+
const resolved = (0, import_path19.resolve)(path2);
|
|
40781
41736
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
40782
41737
|
}
|
|
40783
41738
|
function isPathInside(childPath, parentPath) {
|
|
40784
41739
|
const child = normalizeForCompare(childPath);
|
|
40785
41740
|
const parent = normalizeForCompare(parentPath);
|
|
40786
|
-
const rel = (0,
|
|
40787
|
-
return rel === "" || !!rel && !rel.startsWith("..") && !(0,
|
|
41741
|
+
const rel = (0, import_path19.relative)(parent, child);
|
|
41742
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !(0, import_path19.isAbsolute)(rel);
|
|
40788
41743
|
}
|
|
40789
41744
|
function requireString(value, field) {
|
|
40790
41745
|
if (typeof value !== "string" || value.trim() === "") {
|
|
@@ -40793,10 +41748,10 @@ function requireString(value, field) {
|
|
|
40793
41748
|
return value;
|
|
40794
41749
|
}
|
|
40795
41750
|
function readBatch(batchPath) {
|
|
40796
|
-
if (!(0,
|
|
41751
|
+
if (!(0, import_fs20.existsSync)(batchPath)) {
|
|
40797
41752
|
throw new ExternalAgentExecutorError("external_agent_batch_not_found", `Batch file not found: ${batchPath}`);
|
|
40798
41753
|
}
|
|
40799
|
-
const parsed = JSON.parse((0,
|
|
41754
|
+
const parsed = JSON.parse((0, import_fs20.readFileSync)(batchPath, "utf8"));
|
|
40800
41755
|
if (parsed.schema_version !== "external_agent_batch_plan.v1") {
|
|
40801
41756
|
throw new ExternalAgentExecutorError("invalid_external_agent_batch", "Batch schema_version must be external_agent_batch_plan.v1.");
|
|
40802
41757
|
}
|
|
@@ -40811,11 +41766,11 @@ function defaultRunnerProbe(command, args) {
|
|
|
40811
41766
|
encoding: "utf8",
|
|
40812
41767
|
timeout: isGeminiHeadlessSmoke ? GEMINI_HEADLESS_PROBE_TIMEOUT_MS : void 0
|
|
40813
41768
|
};
|
|
40814
|
-
const result = process.platform === "win32" && command.toLowerCase().endsWith(".cmd") ? (0,
|
|
41769
|
+
const result = process.platform === "win32" && command.toLowerCase().endsWith(".cmd") ? (0, import_child_process6.spawnSync)(
|
|
40815
41770
|
"powershell.exe",
|
|
40816
41771
|
["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", `& ${[command, ...args].map(quotePowerShellArg).join(" ")}`],
|
|
40817
41772
|
spawnOptions
|
|
40818
|
-
) : (0,
|
|
41773
|
+
) : (0, import_child_process6.spawnSync)(command, args, spawnOptions);
|
|
40819
41774
|
return {
|
|
40820
41775
|
status: typeof result.status === "number" ? result.status : null,
|
|
40821
41776
|
stdout: String(result.stdout || ""),
|
|
@@ -40833,8 +41788,8 @@ function resolveCodexProbeCommand() {
|
|
|
40833
41788
|
if (process.platform !== "win32") return "codex";
|
|
40834
41789
|
const appData = process.env.APPDATA;
|
|
40835
41790
|
if (appData) {
|
|
40836
|
-
const appDataShim = (0,
|
|
40837
|
-
if ((0,
|
|
41791
|
+
const appDataShim = (0, import_path19.join)(appData, "npm", "codex.cmd");
|
|
41792
|
+
if ((0, import_fs20.existsSync)(appDataShim)) return appDataShim;
|
|
40838
41793
|
}
|
|
40839
41794
|
return "codex.cmd";
|
|
40840
41795
|
}
|
|
@@ -40845,8 +41800,8 @@ function resolveGeminiProbeCommand() {
|
|
|
40845
41800
|
if (process.platform !== "win32") return "gemini";
|
|
40846
41801
|
const appData = process.env.APPDATA;
|
|
40847
41802
|
if (appData) {
|
|
40848
|
-
const appDataShim = (0,
|
|
40849
|
-
if ((0,
|
|
41803
|
+
const appDataShim = (0, import_path19.join)(appData, "npm", "gemini.cmd");
|
|
41804
|
+
if ((0, import_fs20.existsSync)(appDataShim)) return appDataShim;
|
|
40850
41805
|
}
|
|
40851
41806
|
return "gemini.cmd";
|
|
40852
41807
|
}
|
|
@@ -41117,35 +42072,35 @@ function safeRunId(value) {
|
|
|
41117
42072
|
return value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "run";
|
|
41118
42073
|
}
|
|
41119
42074
|
function resolveWorkspaceRoot(input) {
|
|
41120
|
-
if (input.workspaceRoot) return (0,
|
|
41121
|
-
const batchStem = (0,
|
|
41122
|
-
const repoStem = (0,
|
|
41123
|
-
return (0,
|
|
42075
|
+
if (input.workspaceRoot) return (0, import_path19.resolve)(input.workspaceRoot);
|
|
42076
|
+
const batchStem = (0, import_path19.basename)((0, import_path19.resolve)(input.batchPath)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
|
|
42077
|
+
const repoStem = (0, import_path19.basename)((0, import_path19.resolve)(input.privateRepoRoot)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
|
|
42078
|
+
return (0, import_path19.resolve)((0, import_os3.tmpdir)(), "foh-external-agent-workspaces", repoStem, batchStem);
|
|
41124
42079
|
}
|
|
41125
42080
|
function findNearestGitRoot(startPath) {
|
|
41126
|
-
let current = (0,
|
|
42081
|
+
let current = (0, import_path19.resolve)(startPath);
|
|
41127
42082
|
while (true) {
|
|
41128
|
-
if ((0,
|
|
41129
|
-
const parent = (0,
|
|
42083
|
+
if ((0, import_fs20.existsSync)((0, import_path19.join)(current, ".git"))) return current;
|
|
42084
|
+
const parent = (0, import_path19.dirname)(current);
|
|
41130
42085
|
if (parent === current) return null;
|
|
41131
42086
|
current = parent;
|
|
41132
42087
|
}
|
|
41133
42088
|
}
|
|
41134
42089
|
function resolvePrivateRepoRoot(input) {
|
|
41135
42090
|
if (input.explicitPrivateRepoRoot) {
|
|
41136
|
-
return { root: (0,
|
|
42091
|
+
return { root: (0, import_path19.resolve)(input.explicitPrivateRepoRoot), explicit: true };
|
|
41137
42092
|
}
|
|
41138
|
-
const cwd = (0,
|
|
42093
|
+
const cwd = (0, import_path19.resolve)(input.cwd || process.cwd());
|
|
41139
42094
|
const gitRoot = findNearestGitRoot(cwd);
|
|
41140
42095
|
if (gitRoot) return { root: gitRoot, explicit: false };
|
|
41141
42096
|
return {
|
|
41142
|
-
root: (0,
|
|
42097
|
+
root: (0, import_path19.join)(cwd, ".foh-no-private-repo-root-sentinel"),
|
|
41143
42098
|
explicit: false
|
|
41144
42099
|
};
|
|
41145
42100
|
}
|
|
41146
42101
|
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";
|
|
42102
|
+
const raw = (0, import_fs20.readFileSync)(promptPath, "utf8");
|
|
42103
|
+
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
42104
|
if (raw.includes("Do not assume access to the private source repository")) return "blank-setup.v1";
|
|
41150
42105
|
return "unknown";
|
|
41151
42106
|
}
|
|
@@ -41154,7 +42109,7 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
41154
42109
|
if (runner !== "codex" && runner !== "gemini") {
|
|
41155
42110
|
throw new ExternalAgentExecutorError("unsupported_external_agent_runner", `Unsupported runner: ${runner}`);
|
|
41156
42111
|
}
|
|
41157
|
-
const batchPath = (0,
|
|
42112
|
+
const batchPath = (0, import_path19.resolve)(options.batchPath);
|
|
41158
42113
|
const batch = readBatch(batchPath);
|
|
41159
42114
|
const runnerProbe = validateRunner(options, runner);
|
|
41160
42115
|
const codexSandboxBackend = normalizeCodexSandboxBackend(options.codexSandboxBackend);
|
|
@@ -41173,17 +42128,17 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
41173
42128
|
`Workspace root must be outside the private repository. workspace=${workspaceRoot} repo=${privateRepoRoot}`
|
|
41174
42129
|
);
|
|
41175
42130
|
}
|
|
41176
|
-
(0,
|
|
41177
|
-
const batchDir = (0,
|
|
42131
|
+
(0, import_fs20.mkdirSync)(workspaceRoot, { recursive: true });
|
|
42132
|
+
const batchDir = (0, import_path19.resolve)(String(batch.batch_dir || (0, import_path19.resolve)(batchPath, "..")));
|
|
41178
42133
|
const timeoutMinutes = Number.isFinite(options.timeoutMinutes) && Number(options.timeoutMinutes) > 0 ? Number(options.timeoutMinutes) : 30;
|
|
41179
42134
|
const runs = batch.runs.map((run) => {
|
|
41180
42135
|
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,
|
|
42136
|
+
const runDir = (0, import_path19.resolve)(requireString(run.run_dir, `runs[${runId}].run_dir`));
|
|
42137
|
+
const promptPath = (0, import_path19.resolve)(requireString(run.prompt_path, `runs[${runId}].prompt_path`));
|
|
42138
|
+
const workspaceDir = (0, import_path19.join)(workspaceRoot, runId);
|
|
42139
|
+
(0, import_fs20.mkdirSync)(workspaceDir, { recursive: true });
|
|
42140
|
+
(0, import_fs20.writeFileSync)(
|
|
42141
|
+
(0, import_path19.join)(workspaceDir, "README.md"),
|
|
41187
42142
|
[
|
|
41188
42143
|
"# FOH External-Agent Workspace",
|
|
41189
42144
|
"",
|
|
@@ -41201,11 +42156,11 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
41201
42156
|
});
|
|
41202
42157
|
const promptVersion = String(env[EXTERNAL_AGENT_PROMPT_VERSION_ENV] || "unknown");
|
|
41203
42158
|
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,
|
|
42159
|
+
const jsonlPath = (0, import_path19.join)(runDir, `${outputStem}-exec.jsonl`);
|
|
42160
|
+
const lastMessagePath = (0, import_path19.join)(runDir, `${outputStem}-last-message.md`);
|
|
42161
|
+
const stderrPath = (0, import_path19.join)(runDir, `${outputStem}-stderr.txt`);
|
|
42162
|
+
const runPath = (0, import_path19.join)(runDir, "run.json");
|
|
42163
|
+
const artifactSafetyPath = (0, import_path19.join)(runDir, "artifact-safety.json");
|
|
41209
42164
|
const args = runner === "gemini" ? [
|
|
41210
42165
|
...runnerProbe.globalArgs,
|
|
41211
42166
|
...runnerProbe.execArgs
|
|
@@ -41296,9 +42251,9 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
41296
42251
|
};
|
|
41297
42252
|
}
|
|
41298
42253
|
function writeExternalAgentExecutorPlan(plan) {
|
|
41299
|
-
const path2 = (0,
|
|
41300
|
-
(0,
|
|
41301
|
-
(0,
|
|
42254
|
+
const path2 = (0, import_path19.join)(plan.batch_dir, "executor-plan.json");
|
|
42255
|
+
(0, import_fs20.mkdirSync)(plan.batch_dir, { recursive: true });
|
|
42256
|
+
(0, import_fs20.writeFileSync)(path2, `${JSON.stringify(plan, null, 2)}
|
|
41302
42257
|
`, "utf8");
|
|
41303
42258
|
return path2;
|
|
41304
42259
|
}
|
|
@@ -41313,7 +42268,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
41313
42268
|
if (authPreflight && !authPreflight.ok) {
|
|
41314
42269
|
const endedAt2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
41315
42270
|
const blockedResults = plan.runs.map((run) => {
|
|
41316
|
-
(0,
|
|
42271
|
+
(0, import_fs20.mkdirSync)(run.run_dir, { recursive: true });
|
|
41317
42272
|
const runArtifact = buildExecutedExternalAgentRunArtifact({
|
|
41318
42273
|
run,
|
|
41319
42274
|
startedAt,
|
|
@@ -41324,7 +42279,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
41324
42279
|
timedOut: false,
|
|
41325
42280
|
durationMs: 0
|
|
41326
42281
|
});
|
|
41327
|
-
(0,
|
|
42282
|
+
(0, import_fs20.writeFileSync)(run.outputs.run, `${JSON.stringify(runArtifact, null, 2)}
|
|
41328
42283
|
`, "utf8");
|
|
41329
42284
|
return {
|
|
41330
42285
|
run_id: run.run_id,
|
|
@@ -41351,8 +42306,8 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
41351
42306
|
}
|
|
41352
42307
|
for (const run of plan.runs) {
|
|
41353
42308
|
const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
41354
|
-
const commandCaptureDir = (0,
|
|
41355
|
-
(0,
|
|
42309
|
+
const commandCaptureDir = (0, import_path19.join)(run.workspace_dir, ".foh-capture");
|
|
42310
|
+
(0, import_fs20.mkdirSync)(commandCaptureDir, { recursive: true });
|
|
41356
42311
|
const env = buildCodexExecutorEnv({
|
|
41357
42312
|
sourceEnv: options.env,
|
|
41358
42313
|
runDir: commandCaptureDir,
|
|
@@ -41363,7 +42318,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
41363
42318
|
args: run.args,
|
|
41364
42319
|
cwd: run.workspace_dir,
|
|
41365
42320
|
env,
|
|
41366
|
-
prompt: (0,
|
|
42321
|
+
prompt: (0, import_fs20.readFileSync)(run.prompt_path, "utf8"),
|
|
41367
42322
|
stdoutPath: run.outputs.jsonl,
|
|
41368
42323
|
stderrPath: run.outputs.stderr,
|
|
41369
42324
|
timeoutMs: plan.timeout_minutes * 60 * 1e3
|
|
@@ -41376,7 +42331,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
41376
42331
|
privateRepoRoot,
|
|
41377
42332
|
writeRedacted: true
|
|
41378
42333
|
});
|
|
41379
|
-
(0,
|
|
42334
|
+
(0, import_fs20.writeFileSync)(run.outputs.artifact_safety, `${JSON.stringify(artifactSafety, null, 2)}
|
|
41380
42335
|
`, "utf8");
|
|
41381
42336
|
const runEndedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
41382
42337
|
const classification = classifyExternalAgentRun({
|
|
@@ -41395,7 +42350,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
41395
42350
|
timedOut: spawned.timedOut,
|
|
41396
42351
|
durationMs: spawned.durationMs
|
|
41397
42352
|
});
|
|
41398
|
-
(0,
|
|
42353
|
+
(0, import_fs20.writeFileSync)(run.outputs.run, `${JSON.stringify(runArtifact, null, 2)}
|
|
41399
42354
|
`, "utf8");
|
|
41400
42355
|
results.push({
|
|
41401
42356
|
run_id: run.run_id,
|
|
@@ -41434,7 +42389,7 @@ var PROMPTS = {
|
|
|
41434
42389
|
"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
42390
|
"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
42391
|
"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-
|
|
42392
|
+
"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
42393
|
"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
42394
|
"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
42395
|
"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 +42404,13 @@ function defaultRunDir(modelName, promptVersion) {
|
|
|
41449
42404
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 23);
|
|
41450
42405
|
const safeModel = String(modelName || "unknown-model").toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
|
|
41451
42406
|
const safePrompt = String(promptVersion || DEFAULT_PROMPT_VERSION).toLowerCase().replace(/[^a-z0-9_.-]+/g, "-");
|
|
41452
|
-
return (0,
|
|
42407
|
+
return (0, import_path20.resolve)("test-results", "external-agent-runs", date4, `${safeModel}-${safePrompt}-${stamp}`);
|
|
41453
42408
|
}
|
|
41454
42409
|
function defaultBatchDir(promptVersion) {
|
|
41455
42410
|
const date4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
41456
42411
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 23);
|
|
41457
42412
|
const safePrompt = String(promptVersion || DEFAULT_PROMPT_VERSION).toLowerCase().replace(/[^a-z0-9_.-]+/g, "-");
|
|
41458
|
-
return (0,
|
|
42413
|
+
return (0, import_path20.resolve)("test-results", "external-agent-runs", date4, `batch-${safePrompt}-${stamp}`);
|
|
41459
42414
|
}
|
|
41460
42415
|
function safeSlug(value) {
|
|
41461
42416
|
return String(value || "unknown").toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
|
|
@@ -41572,7 +42527,7 @@ function agencySetupPromptContext(context = {}) {
|
|
|
41572
42527
|
const targetMode = String(context.targetMode || "").trim();
|
|
41573
42528
|
if (!agencyName && !sourceUrl && !branchLocation && !targetMode) return "";
|
|
41574
42529
|
const previewArgs = [
|
|
41575
|
-
"ops reporting agency-setup-
|
|
42530
|
+
"ops reporting agency-setup-workflow",
|
|
41576
42531
|
agencyName ? `--agency-name ${quoteArg(agencyName)}` : "--agency-name <agency-name>",
|
|
41577
42532
|
sourceUrl ? `--source-url ${quoteArg(sourceUrl)}` : "--source-url <official-source-url>",
|
|
41578
42533
|
branchLocation ? `--branch-location ${quoteArg(branchLocation)}` : "--branch-location <branch-location>",
|
|
@@ -41587,7 +42542,7 @@ function agencySetupPromptContext(context = {}) {
|
|
|
41587
42542
|
...branchLocation ? [`- Branch/location: ${branchLocation}`] : [],
|
|
41588
42543
|
...targetMode ? [`- Target mode: ${targetMode}`] : [],
|
|
41589
42544
|
`- Start with: npx --yes @f-o-h/cli@latest ${previewArgs}`,
|
|
41590
|
-
"- Then
|
|
42545
|
+
"- Then inspect the workflow output and run only the returned proof/evidence commands using public CLI commands.",
|
|
41591
42546
|
"- Treat missing customer credentials or approvals as expected holds only if the CLI gives machine-readable reason codes and next actions."
|
|
41592
42547
|
].join("\n");
|
|
41593
42548
|
}
|
|
@@ -41598,14 +42553,14 @@ function writePrompt(runDir, promptVersion, context = {}) {
|
|
|
41598
42553
|
knowledgeMissPromptContext(context.knowledgeQuestion, context.expectedAnswer),
|
|
41599
42554
|
agencySetupPromptContext(context)
|
|
41600
42555
|
].join("");
|
|
41601
|
-
const path2 = (0,
|
|
41602
|
-
(0,
|
|
42556
|
+
const path2 = (0, import_path20.join)(runDir, "prompt.txt");
|
|
42557
|
+
(0, import_fs21.writeFileSync)(path2, `${prompt}
|
|
41603
42558
|
`, "utf8");
|
|
41604
42559
|
return path2;
|
|
41605
42560
|
}
|
|
41606
42561
|
function writeSession(runDir, session) {
|
|
41607
|
-
const path2 = (0,
|
|
41608
|
-
(0,
|
|
42562
|
+
const path2 = (0, import_path20.join)(runDir, "session.json");
|
|
42563
|
+
(0, import_fs21.writeFileSync)(path2, `${JSON.stringify(session, null, 2)}
|
|
41609
42564
|
`, "utf8");
|
|
41610
42565
|
return path2;
|
|
41611
42566
|
}
|
|
@@ -41685,9 +42640,9 @@ function buildRunArtifact(input) {
|
|
|
41685
42640
|
notes: "notes.md"
|
|
41686
42641
|
},
|
|
41687
42642
|
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,
|
|
42643
|
+
next_commands: status === "pass" ? [externalAgentSummaryCommand((0, import_path20.dirname)(input.runDir))] : [
|
|
42644
|
+
`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`,
|
|
42645
|
+
externalAgentSummaryCommand((0, import_path20.dirname)(input.runDir))
|
|
41691
42646
|
]
|
|
41692
42647
|
};
|
|
41693
42648
|
}
|
|
@@ -41696,8 +42651,8 @@ function registerEval(program3) {
|
|
|
41696
42651
|
const external = evalCommand.command("external-agent").description("Capture clean external coding-agent setup attempts");
|
|
41697
42652
|
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
42653
|
const promptVersion = String(opts.promptVersion || DEFAULT_PROMPT_VERSION);
|
|
41699
|
-
const batchDir = (0,
|
|
41700
|
-
const replayFile = opts.replayFile ? (0,
|
|
42654
|
+
const batchDir = (0, import_path20.resolve)(String(opts.outDir || defaultBatchDir(promptVersion)));
|
|
42655
|
+
const replayFile = opts.replayFile ? (0, import_path20.resolve)(String(opts.replayFile)) : void 0;
|
|
41701
42656
|
const knowledgeQuestion = opts.knowledgeQuestion ? String(opts.knowledgeQuestion) : void 0;
|
|
41702
42657
|
const expectedAnswer = opts.expectedAnswer ? String(opts.expectedAnswer) : void 0;
|
|
41703
42658
|
const agencyName = opts.agencyName ? String(opts.agencyName) : void 0;
|
|
@@ -41705,11 +42660,11 @@ function registerEval(program3) {
|
|
|
41705
42660
|
const branchLocation = opts.branchLocation ? String(opts.branchLocation) : void 0;
|
|
41706
42661
|
const targetMode = opts.targetMode ? String(opts.targetMode) : void 0;
|
|
41707
42662
|
const models = parseModelList(String(opts.models || DEFAULT_BATCH_MODELS));
|
|
41708
|
-
(0,
|
|
42663
|
+
(0, import_fs21.mkdirSync)(batchDir, { recursive: true });
|
|
41709
42664
|
const runs2 = models.map((model, index) => {
|
|
41710
42665
|
const runId = `${String(index + 1).padStart(2, "0")}-${safeSlug(model.provider)}-${safeSlug(model.name)}`;
|
|
41711
|
-
const runDir = (0,
|
|
41712
|
-
(0,
|
|
42666
|
+
const runDir = (0, import_path20.join)(batchDir, runId);
|
|
42667
|
+
(0, import_fs21.mkdirSync)(runDir, { recursive: true });
|
|
41713
42668
|
const promptPath = writePrompt(runDir, promptVersion, {
|
|
41714
42669
|
replayFile,
|
|
41715
42670
|
knowledgeQuestion,
|
|
@@ -41772,8 +42727,8 @@ function registerEval(program3) {
|
|
|
41772
42727
|
runs: runs2,
|
|
41773
42728
|
summary_command: externalAgentSummaryCommand(batchDir)
|
|
41774
42729
|
};
|
|
41775
|
-
const batchPath = (0,
|
|
41776
|
-
(0,
|
|
42730
|
+
const batchPath = (0, import_path20.join)(batchDir, "batch.json");
|
|
42731
|
+
(0, import_fs21.writeFileSync)(batchPath, `${JSON.stringify(batch, null, 2)}
|
|
41777
42732
|
`, "utf8");
|
|
41778
42733
|
format(cliEnvelope({
|
|
41779
42734
|
schemaVersion: "external_agent_batch_plan_result.v1",
|
|
@@ -41793,15 +42748,15 @@ function registerEval(program3) {
|
|
|
41793
42748
|
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
42749
|
const status = normalizeStatus(opts.status);
|
|
41795
42750
|
const promptVersion = String(opts.promptVersion || DEFAULT_PROMPT_VERSION);
|
|
41796
|
-
const runDir = (0,
|
|
41797
|
-
const replayFile = opts.replayFile ? (0,
|
|
42751
|
+
const runDir = (0, import_path20.resolve)(String(opts.outDir || defaultRunDir(opts.modelName, promptVersion)));
|
|
42752
|
+
const replayFile = opts.replayFile ? (0, import_path20.resolve)(String(opts.replayFile)) : void 0;
|
|
41798
42753
|
const knowledgeQuestion = opts.knowledgeQuestion ? String(opts.knowledgeQuestion) : void 0;
|
|
41799
42754
|
const expectedAnswer = opts.expectedAnswer ? String(opts.expectedAnswer) : void 0;
|
|
41800
42755
|
const agencyName = opts.agencyName ? String(opts.agencyName) : void 0;
|
|
41801
42756
|
const sourceUrl = opts.sourceUrl ? String(opts.sourceUrl) : void 0;
|
|
41802
42757
|
const branchLocation = opts.branchLocation ? String(opts.branchLocation) : void 0;
|
|
41803
42758
|
const targetMode = opts.targetMode ? String(opts.targetMode) : void 0;
|
|
41804
|
-
(0,
|
|
42759
|
+
(0, import_fs21.mkdirSync)(runDir, { recursive: true });
|
|
41805
42760
|
const runId = runDir.split(/[\\/]/).filter(Boolean).slice(-1)[0];
|
|
41806
42761
|
const promptPath = writePrompt(runDir, promptVersion, {
|
|
41807
42762
|
replayFile,
|
|
@@ -41839,7 +42794,7 @@ function registerEval(program3) {
|
|
|
41839
42794
|
}
|
|
41840
42795
|
};
|
|
41841
42796
|
writeSession(runDir, session);
|
|
41842
|
-
(0,
|
|
42797
|
+
(0, import_fs21.writeFileSync)((0, import_path20.join)(runDir, "notes.md"), "# External Agent Run Notes\n\n", "utf8");
|
|
41843
42798
|
let shellExitCode = null;
|
|
41844
42799
|
if (opts.shell !== false) {
|
|
41845
42800
|
process.stdout.write(`
|
|
@@ -41849,7 +42804,7 @@ Prompt: ${promptPath}
|
|
|
41849
42804
|
Exit the shell to finalize run.json.
|
|
41850
42805
|
|
|
41851
42806
|
`);
|
|
41852
|
-
const result = (0,
|
|
42807
|
+
const result = (0, import_child_process7.spawnSync)(shell.command, shell.args, {
|
|
41853
42808
|
stdio: "inherit",
|
|
41854
42809
|
env: {
|
|
41855
42810
|
...process.env,
|
|
@@ -41861,8 +42816,8 @@ Exit the shell to finalize run.json.
|
|
|
41861
42816
|
shellExitCode = typeof result.status === "number" ? result.status : null;
|
|
41862
42817
|
}
|
|
41863
42818
|
const artifact = buildRunArtifact({ runDir, session, status, reasonCode: opts.reasonCode, shellExitCode });
|
|
41864
|
-
const runPath = (0,
|
|
41865
|
-
(0,
|
|
42819
|
+
const runPath = (0, import_path20.join)(runDir, "run.json");
|
|
42820
|
+
(0, import_fs21.writeFileSync)(runPath, `${JSON.stringify(artifact, null, 2)}
|
|
41866
42821
|
`, "utf8");
|
|
41867
42822
|
format(cliEnvelope({
|
|
41868
42823
|
schemaVersion: "external_agent_capture_result.v1",
|
|
@@ -41872,7 +42827,7 @@ Exit the shell to finalize run.json.
|
|
|
41872
42827
|
artifacts: {
|
|
41873
42828
|
run: runPath,
|
|
41874
42829
|
prompt: promptPath,
|
|
41875
|
-
commands: (0,
|
|
42830
|
+
commands: (0, import_path20.join)(runDir, "commands.ndjson")
|
|
41876
42831
|
},
|
|
41877
42832
|
nextCommands: artifact.next_commands,
|
|
41878
42833
|
extra: { run: artifact }
|
|
@@ -41980,8 +42935,8 @@ Exit the shell to finalize run.json.
|
|
|
41980
42935
|
requireExplicitEvalAuth: true,
|
|
41981
42936
|
minimumEvalAuthTtlMs: (plan.timeout_minutes + 5) * 60 * 1e3
|
|
41982
42937
|
});
|
|
41983
|
-
const resultPath = (0,
|
|
41984
|
-
(0,
|
|
42938
|
+
const resultPath = (0, import_path20.join)(plan.batch_dir, "execution-result.json");
|
|
42939
|
+
(0, import_fs21.writeFileSync)(resultPath, `${JSON.stringify(result, null, 2)}
|
|
41985
42940
|
`, "utf8");
|
|
41986
42941
|
format(cliEnvelope({
|
|
41987
42942
|
schemaVersion: "external_agent_execution_result.v1",
|
|
@@ -42211,11 +43166,13 @@ registerCertify(program2);
|
|
|
42211
43166
|
registerDiag(program2);
|
|
42212
43167
|
registerBug(program2);
|
|
42213
43168
|
registerProve(program2);
|
|
43169
|
+
registerInteractive(program2);
|
|
42214
43170
|
registerAgentPublishCommand(program2, { publicAlias: true });
|
|
42215
43171
|
registerEval(program2);
|
|
42216
43172
|
registerUpdate(program2);
|
|
42217
43173
|
registerHome(program2);
|
|
42218
43174
|
hideInternalApiUrlOptions(program2);
|
|
43175
|
+
initializeCommandGraph(program2);
|
|
42219
43176
|
maybeDefaultToHome(process.argv);
|
|
42220
43177
|
program2.parseAsync(process.argv).catch((e) => {
|
|
42221
43178
|
if (e instanceof CliSoftExit) {
|