@okxweb3/a2a-node 0.2.2 → 0.2.3-beta-fa7df0d9e7-260810102542

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.
Files changed (3) hide show
  1. package/dist/cli.js +21515 -20340
  2. package/dist/index.js +2399 -1080
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3200,7 +3200,14 @@ function resolveCommandPath(command, env = process.env, platform = process.platf
3200
3200
  }
3201
3201
  function resolveCodexCommandPath(env = process.env, storedCommand, platform = process.platform, probe = probeCodexCommand) {
3202
3202
  if (platform !== "win32") {
3203
- return storedCommand && commandExists(storedCommand, env, platform) ? storedCommand : findExecutable("codex", env, platform);
3203
+ if (storedCommand && commandExists(storedCommand, env, platform)) {
3204
+ return storedCommand;
3205
+ }
3206
+ const onPath = findExecutable("codex", env, platform);
3207
+ if (onPath) {
3208
+ return onPath;
3209
+ }
3210
+ return collectCodexAppBundledCandidates(env, platform).find(isExecutable) ?? null;
3204
3211
  }
3205
3212
  for (const candidate of collectCodexCommandCandidates(env, storedCommand, platform)) {
3206
3213
  const result = probe(candidate, env, platform);
@@ -3225,6 +3232,9 @@ function collectCodexCommandCandidates(env = process.env, storedCommand, platfor
3225
3232
  }
3226
3233
  if (platform !== "win32") {
3227
3234
  add(findExecutable("codex", env, platform));
3235
+ for (const candidate of collectCodexAppBundledCandidates(env, platform).filter(isExecutable)) {
3236
+ add(candidate);
3237
+ }
3228
3238
  return candidates;
3229
3239
  }
3230
3240
  for (const candidate of localAppDataCodexCandidates(env)) {
@@ -3246,6 +3256,87 @@ function collectCodexCommandCandidates(env = process.env, storedCommand, platfor
3246
3256
  }
3247
3257
  return candidates;
3248
3258
  }
3259
+ function collectCodexAppBundledCandidates(env = process.env, platform = process.platform) {
3260
+ if (platform === "win32") {
3261
+ return localAppDataCodexCandidates(env);
3262
+ }
3263
+ const roots = (env[CODEX_APP_DIRS_ENV] ?? "").split(import_node_path10.delimiter).filter(Boolean);
3264
+ if (platform === "darwin") {
3265
+ roots.push("/Applications", (0, import_node_path10.join)(env.HOME || (0, import_node_os5.homedir)(), "Applications"));
3266
+ }
3267
+ const candidates = [];
3268
+ const seen = /* @__PURE__ */ new Set();
3269
+ for (const root of roots) {
3270
+ const candidate = (0, import_node_path10.join)(root, "Codex.app", "Contents", "Resources", "codex");
3271
+ if (seen.has(candidate)) {
3272
+ continue;
3273
+ }
3274
+ seen.add(candidate);
3275
+ candidates.push(candidate);
3276
+ }
3277
+ return candidates;
3278
+ }
3279
+ function resolveCodexCommandCandidates(env = process.env, storedCommand, platform = process.platform) {
3280
+ const candidates = [];
3281
+ const seen = /* @__PURE__ */ new Set();
3282
+ const add = (command, source) => {
3283
+ if (!command || seen.has(command)) {
3284
+ return;
3285
+ }
3286
+ seen.add(command);
3287
+ candidates.push({ command, source });
3288
+ };
3289
+ add(env[CODEX_COMMAND_OVERRIDE_ENV], "explicit_override");
3290
+ const appBundled = new Set(collectCodexAppBundledCandidates(env, platform));
3291
+ for (const command of collectCodexCommandCandidates(env, storedCommand, platform)) {
3292
+ if (storedCommand && command === storedCommand) {
3293
+ add(command, "persisted");
3294
+ continue;
3295
+ }
3296
+ add(command, appBundled.has(command) ? "app_bundled" : "path");
3297
+ }
3298
+ return candidates;
3299
+ }
3300
+ function resolveCodexCommandSource(env = process.env, storedCommand, platform = process.platform, probe = probeCodexCommand) {
3301
+ const override = env[CODEX_COMMAND_OVERRIDE_ENV];
3302
+ if (override) {
3303
+ return commandExists(override, env, platform) ? { command: override, source: "explicit_override" } : null;
3304
+ }
3305
+ const resolved = resolveCodexCommandPath(env, storedCommand, platform, probe);
3306
+ if (!resolved) {
3307
+ return null;
3308
+ }
3309
+ return {
3310
+ command: resolved,
3311
+ source: classifyResolvedCodexCommand(resolved, storedCommand, env, platform)
3312
+ };
3313
+ }
3314
+ function resolveAiProviderCommandWithSource(provider, env = process.env, storedCommand, platform = process.platform) {
3315
+ const override = env[`OKX_A2A_AI_${provider.toUpperCase()}_COMMAND`];
3316
+ if (override) {
3317
+ return { command: override, source: "explicit_override" };
3318
+ }
3319
+ if (provider === "codex") {
3320
+ const codex = resolveCodexCommandPath(env, storedCommand, platform);
3321
+ if (codex) {
3322
+ return { command: codex, source: classifyResolvedCodexCommand(codex, storedCommand, env, platform) };
3323
+ }
3324
+ }
3325
+ if (storedCommand && commandExists(storedCommand, env, platform)) {
3326
+ return { command: storedCommand, source: "persisted" };
3327
+ }
3328
+ const found = findExecutable(provider, env, platform);
3329
+ return found ? { command: found, source: "path" } : { command: provider, source: null };
3330
+ }
3331
+ function classifyResolvedCodexCommand(resolved, storedCommand, env, platform) {
3332
+ if (storedCommand && resolved === storedCommand) {
3333
+ return "persisted";
3334
+ }
3335
+ if (collectCodexAppBundledCandidates(env, platform).includes(resolved)) {
3336
+ return "app_bundled";
3337
+ }
3338
+ return "path";
3339
+ }
3249
3340
  function readAiProviderTimeoutMs(env = process.env) {
3250
3341
  const raw = env.OKX_A2A_AI_PROVIDER_TIMEOUT_MS ?? env.OKX_AGENT_TASK_AI_PROVIDER_TIMEOUT_MS;
3251
3342
  if (!raw) {
@@ -3417,7 +3508,7 @@ function isCodexCommand(command) {
3417
3508
  function isWindowsAppsPath(commandPath) {
3418
3509
  return /(?:^|[\\/])WindowsApps(?:[\\/]|$)/i.test(commandPath);
3419
3510
  }
3420
- var import_node_fs7, import_node_os5, import_node_path10, DEFAULT_AI_PROVIDER_TIMEOUT_MS, CODEX_PROBE_TIMEOUT_MS;
3511
+ var import_node_fs7, import_node_os5, import_node_path10, DEFAULT_AI_PROVIDER_TIMEOUT_MS, CODEX_PROBE_TIMEOUT_MS, CODEX_APP_DIRS_ENV, CODEX_COMMAND_OVERRIDE_ENV;
3421
3512
  var init_ai_command = __esm({
3422
3513
  "src/ai-command.ts"() {
3423
3514
  "use strict";
@@ -3427,918 +3518,238 @@ var init_ai_command = __esm({
3427
3518
  init_win_spawn();
3428
3519
  DEFAULT_AI_PROVIDER_TIMEOUT_MS = null;
3429
3520
  CODEX_PROBE_TIMEOUT_MS = 1e4;
3521
+ CODEX_APP_DIRS_ENV = "OKX_A2A_CODEX_APP_DIRS";
3522
+ CODEX_COMMAND_OVERRIDE_ENV = "OKX_A2A_AI_CODEX_COMMAND";
3430
3523
  }
3431
3524
  });
3432
3525
 
3433
- // src/ai-provider.ts
3434
- function normalizeAiProvider(value) {
3435
- const normalized = value.trim().toLowerCase();
3436
- if (normalized === "codex") {
3437
- return "codex";
3438
- }
3439
- if (normalized === "claude" || normalized === "claude-code" || normalized === "claude_code") {
3440
- return "claude";
3441
- }
3442
- if (normalized === "hermes") {
3443
- return "hermes";
3444
- }
3445
- if (normalized === "openclaw") {
3446
- return "openclaw";
3447
- }
3448
- throw new Error(`Unsupported AI provider "${value}". Use one of: ${AI_PROVIDERS.join(", ")}.`);
3449
- }
3450
- function detectAiProviders(commandExists2 = commandExists, env = process.env) {
3451
- const codex = commandExists2(readProviderCommand("codex", env));
3452
- const claude = commandExists2(readProviderCommand("claude", env));
3453
- const hermes = commandExists2(readProviderCommand("hermes", env));
3454
- const openclaw = commandExists2(readProviderCommand("openclaw", env));
3455
- return {
3456
- codex,
3457
- claude,
3458
- hermes,
3459
- openclaw,
3460
- available: [
3461
- ...codex ? ["codex"] : [],
3462
- ...claude ? ["claude"] : [],
3463
- ...hermes ? ["hermes"] : [],
3464
- ...openclaw ? ["openclaw"] : []
3465
- ]
3466
- };
3526
+ // src/codex-readiness.ts
3527
+ function codexReadinessCacheKey(env = process.env, persistOverrideCommand = false) {
3528
+ return `codex|${env[CODEX_COMMAND_OVERRIDE_ENV2] ?? ""}${persistOverrideCommand ? "|persist-override" : ""}`;
3467
3529
  }
3468
- function detectCurrentAiProvider(env = process.env) {
3469
- const explicit = env.OKX_A2A_AI_PROVIDER ?? env.OKX_AGENT_TASK_AI_CLI;
3470
- if (explicit) {
3471
- return normalizeAiProvider(explicit);
3472
- }
3473
- const runtime = detectRuntime(env);
3474
- return runtime === "unknown" ? null : runtime;
3475
- }
3476
- function hasAiRuntimeMarker(env = process.env) {
3477
- return !!(detectGatewayInvocation(env) || env.CODEX_THREAD_ID || isTruthyRuntimeMarker(env.CODEX_SHELL) || env.CODEX_MANAGED_BY_NPM || isTruthyRuntimeMarker(env.CODEX_CI) || env.CLAUDE_SESSION_ID || isTruthyRuntimeMarker(env.CLAUDECODE) || env.CLAUDE_CODE_SESSION_ID || env.CLAUDE_PLUGIN_DATA || env.HERMES_SESSION_ID && !env.HERMES_DESKTOP_CWD || env.HERMES_SESSION_KEY || env.HERMES_FLOW_ID || env.OPENCLAW_SHELL || env.OPENCLAW_CLI);
3478
- }
3479
- function detectRuntime(env = process.env, options = {}) {
3480
- const gatewayInvocation = detectGatewayInvocation(env);
3481
- const strongMatches = [
3482
- ...gatewayInvocation ? [gatewayInvocation] : [],
3483
- ...env.CODEX_THREAD_ID || isTruthyRuntimeMarker(env.CODEX_SHELL) || env.CODEX_MANAGED_BY_NPM || isTruthyRuntimeMarker(env.CODEX_CI) ? ["codex"] : [],
3484
- ...env.CLAUDE_SESSION_ID || isTruthyRuntimeMarker(env.CLAUDECODE) || env.CLAUDE_CODE_SESSION_ID || env.CLAUDE_PLUGIN_DATA ? ["claude"] : [],
3485
- ...env.HERMES_SESSION_ID && !env.HERMES_DESKTOP_CWD || env.HERMES_SESSION_KEY || env.HERMES_FLOW_ID ? ["hermes"] : [],
3486
- ...env.OPENCLAW_SHELL || env.OPENCLAW_CLI ? ["openclaw"] : [],
3487
- ...hasOpenClawParentProcess(options.parentPid ?? process.ppid, options.readParentProcessCommand ?? readParentProcessCommand) ? ["openclaw"] : []
3488
- ];
3489
- const uniqueMatches = [...new Set(strongMatches)];
3490
- if (uniqueMatches.length === 1) {
3491
- return uniqueMatches[0];
3530
+ function buildCodexRecoveryGuidance(state, commandSource = null) {
3531
+ if (state === "not_authenticated" && commandSource === "app_bundled") {
3532
+ return { recoveryAction: "run_login", recoveryGuidance: CODEX_APP_BUNDLED_LOGIN_GUIDANCE };
3492
3533
  }
3493
- return "unknown";
3534
+ return { ...CODEX_RECOVERY_BY_STATE[state] };
3494
3535
  }
3495
- function detectGatewayInvocation(env = process.env) {
3496
- const context = (env.OKX_A2A_RUNTIME_CONTEXT ?? "").trim().toLowerCase();
3497
- if (context === "openclaw-gateway" || context === "gateway:openclaw") {
3498
- return "openclaw";
3499
- }
3500
- if (context === "hermes-gateway" || context === "gateway:hermes") {
3501
- return "hermes";
3502
- }
3503
- if (isTruthyRuntimeMarker(env._HERMES_GATEWAY)) {
3504
- return "hermes";
3505
- }
3506
- if ((env.OPENCLAW_SERVICE_KIND ?? "").trim().toLowerCase() === "gateway") {
3507
- return "openclaw";
3508
- }
3509
- if (!isTruthyRuntimeMarker(env.OKX_A2A_IN_GATEWAY)) {
3510
- return null;
3511
- }
3512
- const provider = env.OKX_A2A_GATEWAY_PROVIDER;
3513
- if (!provider) {
3514
- return null;
3515
- }
3516
- try {
3517
- const normalized = normalizeAiProvider(provider);
3518
- return normalized === "openclaw" || normalized === "hermes" ? normalized : null;
3519
- } catch {
3520
- return null;
3521
- }
3536
+ function codexCommandResolvesWithoutOverride(env, storedCommand, platform) {
3537
+ const envWithoutOverride = { ...env };
3538
+ delete envWithoutOverride[CODEX_COMMAND_OVERRIDE_ENV2];
3539
+ return resolveCodexCommandSource(envWithoutOverride, storedCommand, platform) !== null;
3522
3540
  }
3523
- function switchProvider(options) {
3524
- const runtime = detectRuntime(options.env ?? process.env, {
3525
- parentPid: options.parentPid,
3526
- readParentProcessCommand: options.readParentProcessCommand
3527
- });
3528
- if (runtime === "unknown") {
3529
- const stored = options.store.getDefaultAiProvider();
3530
- if (stored) {
3531
- return {
3532
- ok: true,
3533
- runtime: "unknown",
3534
- provider: stored,
3535
- previousProvider: stored,
3536
- changed: false,
3537
- action: "kept_existing",
3538
- userMessage: `No AI runtime detected for the current environment; keeping the existing default provider (${formatAiProviderDisplayName(stored)}).`
3539
- };
3540
- }
3541
- return {
3542
- ok: false,
3543
- runtime: "unknown",
3544
- reason: "unknown_runtime",
3545
- state: "blocked",
3546
- message: "Could not determine the current AI provider.",
3547
- userMessage: "Could not detect the current AI runtime and no default AI provider is configured. Run this from your AI environment (OpenClaw / Codex / Claude / Hermes), or set a provider manually: `okx-a2a ai-provider set --provider <codex|claude|hermes|openclaw>`."
3541
+ function probeCodexAuthStatus(command, env, platform, timeoutMs) {
3542
+ return new Promise((resolvePromise) => {
3543
+ let settled = false;
3544
+ const settle = (result) => {
3545
+ if (settled) {
3546
+ return;
3547
+ }
3548
+ settled = true;
3549
+ resolvePromise(result);
3548
3550
  };
3549
- }
3550
- const previousProvider = options.store.getDefaultAiProvider();
3551
- const changed = previousProvider !== runtime;
3552
- options.store.setDefaultAiProvider(runtime);
3553
- return {
3554
- ok: true,
3555
- runtime,
3556
- provider: runtime,
3557
- previousProvider,
3558
- changed,
3559
- action: "switched",
3560
- userMessage: changed ? `Switched to ${formatAiProviderDisplayName(runtime)}\uFF5CNew tasks will be handled here; unfinished tasks should be continued in their original session` : ""
3561
- };
3551
+ let child;
3552
+ try {
3553
+ child = spawnCompat(command, ["login", "status"], {
3554
+ env,
3555
+ stdio: ["ignore", "pipe", "pipe"],
3556
+ windowsHide: true
3557
+ });
3558
+ } catch (error) {
3559
+ settle({ ok: false, state: spawnErrorReadinessState(error) });
3560
+ return;
3561
+ }
3562
+ child.stdout?.resume();
3563
+ child.stderr?.resume();
3564
+ const timer = setTimeout(() => {
3565
+ killProcessTree(child, "SIGTERM", platform);
3566
+ setTimeout(() => {
3567
+ if (!child.killed || child.exitCode === null) {
3568
+ killProcessTree(child, "SIGKILL", platform);
3569
+ }
3570
+ }, CODEX_PROBE_KILL_GRACE_MS).unref();
3571
+ settle({ ok: false, state: "probe_timeout" });
3572
+ }, timeoutMs);
3573
+ timer.unref();
3574
+ child.once("error", (error) => {
3575
+ clearTimeout(timer);
3576
+ settle({ ok: false, state: spawnErrorReadinessState(error) });
3577
+ });
3578
+ child.once("close", (code) => {
3579
+ clearTimeout(timer);
3580
+ settle(code === 0 ? { ok: true } : { ok: false, state: "not_authenticated" });
3581
+ });
3582
+ });
3562
3583
  }
3563
- function formatAiProviderDisplayName(provider) {
3564
- if (provider === "openclaw") {
3565
- return "OpenClaw";
3566
- }
3567
- return `${provider.charAt(0).toUpperCase()}${provider.slice(1)}`;
3584
+ function readCachedCodexReadiness(options = {}) {
3585
+ const env = options.env ?? process.env;
3586
+ const nowMs = options.nowMs ?? Date.now;
3587
+ const cached = readCacheEntry(options.cacheKey ?? codexReadinessCacheKey(env), nowMs());
3588
+ return cached ? { ...cached, fromCache: true } : null;
3568
3589
  }
3569
- function isTruthyRuntimeMarker(value) {
3570
- return value === "1" || value === "true" || value === "yes";
3590
+ function clearCodexReadinessCache() {
3591
+ readinessCache.clear();
3592
+ inFlightReadiness.clear();
3571
3593
  }
3572
- function hasOpenClawParentProcess(parentPid, readParentProcessCommand2) {
3573
- let pid = parentPid;
3574
- for (let i = 0; i < 8; i++) {
3575
- if (!pid || pid <= 1) {
3576
- return false;
3577
- }
3578
- const processInfo = readParentProcessCommand2(pid);
3579
- if (!processInfo) {
3580
- return false;
3594
+ function checkCodexReadiness(options = {}) {
3595
+ const env = options.env ?? process.env;
3596
+ const nowMs = options.nowMs ?? Date.now;
3597
+ const cacheKey2 = options.cacheKey ?? codexReadinessCacheKey(env, options.persistOverrideCommand === true);
3598
+ if (!options.forceRefresh) {
3599
+ const cached = readCacheEntry(cacheKey2, nowMs());
3600
+ if (cached) {
3601
+ return Promise.resolve({ ...cached, fromCache: true });
3602
+ }
3603
+ const inFlight = inFlightReadiness.get(cacheKey2);
3604
+ if (inFlight) {
3605
+ return inFlight;
3581
3606
  }
3582
- if (/openclaw/i.test(processInfo.command)) {
3583
- return true;
3607
+ }
3608
+ const pending = resolveCodexReadiness(options, env, cacheKey2, nowMs).finally(() => {
3609
+ if (inFlightReadiness.get(cacheKey2) === pending) {
3610
+ inFlightReadiness.delete(cacheKey2);
3584
3611
  }
3585
- if (!processInfo.parentPid || processInfo.parentPid === pid) {
3586
- return false;
3612
+ });
3613
+ inFlightReadiness.set(cacheKey2, pending);
3614
+ return pending;
3615
+ }
3616
+ async function resolveCodexReadiness(options, env, cacheKey2, nowMs) {
3617
+ try {
3618
+ const platform = options.platform ?? process.platform;
3619
+ const store = options.store ?? null;
3620
+ const probeAuth = options.probeAuth ?? probeCodexAuthStatus;
3621
+ const storedCommand = store ? store.getAiProviderCommand("codex") : null;
3622
+ const candidate = resolveCodexCommandSource(env, storedCommand, platform);
3623
+ if (!candidate) {
3624
+ return storeCacheEntry(cacheKey2, nowMs, buildCodexReadinessResult("not_installed", null, null, false));
3625
+ }
3626
+ const probe = await probeAuth(candidate.command, env, platform, CODEX_AUTH_PROBE_TIMEOUT_MS);
3627
+ if (!probe.ok) {
3628
+ return storeCacheEntry(
3629
+ cacheKey2,
3630
+ nowMs,
3631
+ buildCodexReadinessResult(probe.state, candidate.command, candidate.source, false)
3632
+ );
3587
3633
  }
3588
- pid = processInfo.parentPid;
3634
+ const commandPersisted = persistReadyCodexCommand(
3635
+ candidate,
3636
+ store,
3637
+ options.persist,
3638
+ options.persistOverrideCommand === true
3639
+ );
3640
+ return storeCacheEntry(
3641
+ cacheKey2,
3642
+ nowMs,
3643
+ buildCodexReadinessResult("ready", candidate.command, candidate.source, commandPersisted)
3644
+ );
3645
+ } catch {
3646
+ return storeCacheEntry(cacheKey2, nowMs, buildCodexReadinessResult("probe_failed", null, null, false));
3589
3647
  }
3590
- return false;
3591
- }
3592
- function readParentProcessCommand(pid) {
3593
- return readParentProcessCommandForPlatform(pid, process.platform);
3594
3648
  }
3595
- function readParentProcessCommandForPlatform(pid, platform) {
3596
- if (platform === "win32") {
3597
- return readWindowsParentProcessCommand(pid);
3649
+ function persistReadyCodexCommand(candidate, store, persist, persistOverrideCommand) {
3650
+ if (persist === false || !store) {
3651
+ return false;
3598
3652
  }
3599
- const commandResult = (0, import_node_child_process5.spawnSync)("ps", ["-p", String(pid), "-o", "comm="], {
3600
- encoding: "utf8",
3601
- stdio: ["ignore", "pipe", "ignore"]
3602
- });
3603
- const parentResult = (0, import_node_child_process5.spawnSync)("ps", ["-p", String(pid), "-o", "ppid="], {
3604
- encoding: "utf8",
3605
- stdio: ["ignore", "pipe", "ignore"]
3606
- });
3607
- const command = typeof commandResult.stdout === "string" ? commandResult.stdout.trim() : "";
3608
- const parentPidRaw = typeof parentResult.stdout === "string" ? parentResult.stdout.trim() : "";
3609
- const parentPid = Number(parentPidRaw);
3610
- if (commandResult.status !== 0 || parentResult.status !== 0 || !command || !Number.isFinite(parentPid)) {
3611
- return null;
3653
+ if (candidate.source === "explicit_override" && !persistOverrideCommand) {
3654
+ return false;
3612
3655
  }
3613
- return { command, parentPid };
3614
- }
3615
- function buildWindowsParentProcessProbe(pid) {
3616
- if (!Number.isInteger(pid)) {
3617
- return null;
3656
+ try {
3657
+ store.setAiProviderCommand("codex", candidate.command);
3658
+ return true;
3659
+ } catch {
3660
+ return false;
3618
3661
  }
3662
+ }
3663
+ function buildCodexReadinessResult(state, command, commandSource, commandPersisted) {
3664
+ const { recoveryAction, recoveryGuidance } = buildCodexRecoveryGuidance(state, commandSource);
3665
+ const ready = state === "ready";
3619
3666
  return {
3620
- command: "powershell.exe",
3621
- args: [
3622
- "-NoProfile",
3623
- "-NonInteractive",
3624
- "-Command",
3625
- `Get-CimInstance Win32_Process -Filter "ProcessId=${pid}" | Select-Object Name,ParentProcessId | ConvertTo-Json -Compress`
3626
- ]
3667
+ provider: "codex",
3668
+ ready,
3669
+ state,
3670
+ authenticated: ready,
3671
+ command,
3672
+ commandSource,
3673
+ commandPersisted,
3674
+ recoveryAction,
3675
+ recoveryGuidance,
3676
+ fromCache: false
3627
3677
  };
3628
3678
  }
3629
- function parseWindowsParentProcessJson(stdout) {
3630
- const trimmed = stdout.trim();
3631
- if (!trimmed) {
3632
- return null;
3633
- }
3634
- let parsed;
3635
- try {
3636
- parsed = JSON.parse(trimmed);
3637
- } catch {
3638
- return null;
3639
- }
3640
- const record = Array.isArray(parsed) ? parsed[0] : parsed;
3641
- if (!record || typeof record !== "object") {
3679
+ function readCacheEntry(cacheKey2, atMs) {
3680
+ const entry = readinessCache.get(cacheKey2);
3681
+ if (!entry) {
3642
3682
  return null;
3643
3683
  }
3644
- const name = record.Name;
3645
- if (typeof name !== "string" || name.trim() === "") {
3684
+ if (atMs >= entry.expiresAtMs) {
3685
+ readinessCache.delete(cacheKey2);
3646
3686
  return null;
3647
3687
  }
3648
- const rawParentPid = Number(record.ParentProcessId);
3649
- const parentPid = Number.isInteger(rawParentPid) && rawParentPid > 0 ? rawParentPid : null;
3650
- return { command: name.trim(), parentPid };
3688
+ return entry.result;
3651
3689
  }
3652
- function readWindowsParentProcessCommand(pid) {
3653
- const probe = buildWindowsParentProcessProbe(pid);
3654
- if (!probe) {
3655
- logWinCompat(`${WIN_COMPAT_LOG_PREFIX} parent-process probe rejected non-integer pid=${String(pid)}`);
3656
- return null;
3657
- }
3658
- const result = (0, import_node_child_process5.spawnSync)(probe.command, probe.args, {
3659
- encoding: "utf8",
3660
- stdio: ["ignore", "pipe", "ignore"],
3661
- timeout: 5e3,
3662
- windowsHide: true
3663
- });
3664
- if (result.error) {
3665
- const spawnError = result.error;
3666
- logWinCompat(
3667
- `${WIN_COMPAT_LOG_PREFIX} parent-process probe failed pid=${pid} command=${probe.command} errorCode=${spawnError.code ?? "unknown"} error=${spawnError.message}`
3668
- );
3669
- return null;
3670
- }
3671
- if (result.status !== 0) {
3672
- logWinCompat(
3673
- `${WIN_COMPAT_LOG_PREFIX} parent-process probe exited pid=${pid} command=${probe.command} status=${String(result.status)} signal=${String(result.signal)}`
3674
- );
3675
- return null;
3676
- }
3677
- const stdout = typeof result.stdout === "string" ? result.stdout : "";
3678
- const parsed = parseWindowsParentProcessJson(stdout);
3679
- if (!parsed) {
3680
- logWinCompat(
3681
- `${WIN_COMPAT_LOG_PREFIX} parent-process probe unparsable output pid=${pid} stdout=${JSON.stringify(stdout.slice(0, 200))}`
3682
- );
3683
- return null;
3684
- }
3685
- if (process.env.OKX_A2A_WIN_COMPAT_VERBOSE === "1") {
3686
- logWinCompat(
3687
- `${WIN_COMPAT_LOG_PREFIX} parent-process probe pid=${pid} -> command=${parsed.command} parentPid=${parsed.parentPid ?? "null"}`
3688
- );
3689
- }
3690
- return parsed;
3690
+ function storeCacheEntry(cacheKey2, nowMs, result) {
3691
+ readinessCache.set(cacheKey2, { result, expiresAtMs: nowMs() + CODEX_READINESS_CACHE_TTL_MS });
3692
+ return result;
3691
3693
  }
3692
- async function ensureDefaultAiProvider(options) {
3693
- const env = options.env ?? process.env;
3694
- const commandExists2 = options.commandExists ?? commandExists;
3695
- const detection = withStoredCodexAvailability(
3696
- detectAiProviders(commandExists2, env),
3697
- options.store,
3698
- commandExists2
3699
- );
3700
- if (options.requestedProvider) {
3701
- const provider = normalizeAiProvider(options.requestedProvider);
3702
- if (!options.allowUnavailableProvider) {
3703
- assertInstalled(provider, detection);
3704
- }
3705
- options.store.setDefaultAiProvider(provider);
3706
- return {
3707
- provider,
3708
- source: "requested",
3709
- message: isInstalled(provider, detection) ? `default AI provider set to ${provider}` : `default AI provider set to ${provider}; availability was not verified on PATH`
3710
- };
3711
- }
3712
- if (options.forcePrompt) {
3713
- const provider = await promptForAiProvider({
3714
- detection,
3715
- stdin: options.stdin,
3716
- stdout: options.stdout,
3717
- currentProvider: options.store.getDefaultAiProvider()
3718
- });
3719
- options.store.setDefaultAiProvider(provider);
3720
- return {
3721
- provider,
3722
- source: "detected",
3723
- message: `default AI provider set to ${provider}`
3694
+ function spawnErrorReadinessState(error) {
3695
+ const code = error?.code;
3696
+ return code === "ENOENT" ? "not_installed" : "probe_failed";
3697
+ }
3698
+ var CODEX_AUTH_PROBE_TIMEOUT_MS, CODEX_READINESS_CACHE_TTL_MS, CODEX_PROBE_KILL_GRACE_MS, CODEX_COMMAND_OVERRIDE_ENV2, CODEX_RECOVERY_BY_STATE, readinessCache, inFlightReadiness, CODEX_APP_BUNDLED_LOGIN_GUIDANCE;
3699
+ var init_codex_readiness = __esm({
3700
+ "src/codex-readiness.ts"() {
3701
+ "use strict";
3702
+ init_ai_command();
3703
+ init_win_spawn();
3704
+ CODEX_AUTH_PROBE_TIMEOUT_MS = 1e4;
3705
+ CODEX_READINESS_CACHE_TTL_MS = 6e4;
3706
+ CODEX_PROBE_KILL_GRACE_MS = 5e3;
3707
+ CODEX_COMMAND_OVERRIDE_ENV2 = "OKX_A2A_AI_CODEX_COMMAND";
3708
+ CODEX_RECOVERY_BY_STATE = {
3709
+ ready: { recoveryAction: "none", recoveryGuidance: "" },
3710
+ not_installed: {
3711
+ recoveryAction: "install_cli",
3712
+ recoveryGuidance: "Codex was not found. Install the Codex CLI or the Codex desktop app, then run `okx-a2a setup`."
3713
+ },
3714
+ not_authenticated: {
3715
+ recoveryAction: "run_login",
3716
+ recoveryGuidance: "Codex is installed but not logged in. Run `codex login`, then retry."
3717
+ },
3718
+ probe_timeout: {
3719
+ recoveryAction: "run_setup",
3720
+ recoveryGuidance: `Codex did not report its login status within ${CODEX_AUTH_PROBE_TIMEOUT_MS}ms. Run \`okx-a2a setup\` to re-verify the Codex CLI.`
3721
+ },
3722
+ probe_failed: {
3723
+ recoveryAction: "run_setup",
3724
+ recoveryGuidance: "Codex readiness could not be verified. Run `okx-a2a setup` to re-verify the Codex CLI."
3725
+ }
3724
3726
  };
3727
+ readinessCache = /* @__PURE__ */ new Map();
3728
+ inFlightReadiness = /* @__PURE__ */ new Map();
3729
+ CODEX_APP_BUNDLED_LOGIN_GUIDANCE = "Codex was found in the Codex app bundle but is not logged in. Sign in from the Codex app, or run `okx-a2a setup --json` to re-verify Codex. `codex login` will not work: the bundled executable is not on PATH.";
3725
3730
  }
3726
- if (env.OKX_AGENT_TASK_AI_CLI) {
3727
- const provider = normalizeAiProvider(env.OKX_AGENT_TASK_AI_CLI);
3728
- if (!options.allowUnavailableProvider) {
3729
- assertInstalled(provider, detection);
3731
+ });
3732
+
3733
+ // ../../node_modules/@sentry/utils/cjs/is.js
3734
+ var require_is = __commonJS({
3735
+ "../../node_modules/@sentry/utils/cjs/is.js"(exports2) {
3736
+ Object.defineProperty(exports2, "__esModule", { value: true });
3737
+ var objectToString = Object.prototype.toString;
3738
+ function isError(wat) {
3739
+ switch (objectToString.call(wat)) {
3740
+ case "[object Error]":
3741
+ case "[object Exception]":
3742
+ case "[object DOMException]":
3743
+ return true;
3744
+ default:
3745
+ return isInstanceOf(wat, Error);
3746
+ }
3730
3747
  }
3731
- options.store.setDefaultAiProvider(provider);
3732
- return {
3733
- provider,
3734
- source: "env",
3735
- message: isInstalled(provider, detection) ? `default AI provider set to ${provider} from OKX_AGENT_TASK_AI_CLI` : `default AI provider set to ${provider} from OKX_AGENT_TASK_AI_CLI; availability was not verified on PATH`
3736
- };
3737
- }
3738
- if (env.OKX_A2A_AI_PROVIDER) {
3739
- const provider = normalizeAiProvider(env.OKX_A2A_AI_PROVIDER);
3740
- if (!options.allowUnavailableProvider) {
3741
- assertInstalled(provider, detection);
3748
+ function isBuiltin(wat, className) {
3749
+ return objectToString.call(wat) === `[object ${className}]`;
3742
3750
  }
3743
- return {
3744
- provider,
3745
- source: "env",
3746
- message: isInstalled(provider, detection) ? `AI provider resolved from OKX_A2A_AI_PROVIDER: ${provider}` : `AI provider resolved from OKX_A2A_AI_PROVIDER: ${provider}; availability was not verified on PATH`
3747
- };
3748
- }
3749
- const stored = options.store.getDefaultAiProvider();
3750
- if (stored && (options.allowUnavailableProvider || isInstalled(stored, detection))) {
3751
- return {
3752
- provider: stored,
3753
- source: "stored",
3754
- message: isInstalled(stored, detection) ? `default AI provider is ${stored}` : `default AI provider is ${stored}; availability was not verified on PATH`
3755
- };
3756
- }
3757
- if (stored) {
3758
- options.stderr?.write(
3759
- `[okx-agent-task] stored AI provider "${stored}" is not installed or not on PATH; selecting again.
3760
- `
3761
- );
3762
- }
3763
- if (options.promptIfMissing) {
3764
- const provider = await promptForAiProvider({
3765
- detection,
3766
- stdin: options.stdin,
3767
- stdout: options.stdout,
3768
- currentProvider: stored ?? null
3769
- });
3770
- options.store.setDefaultAiProvider(provider);
3771
- return {
3772
- provider,
3773
- source: "detected",
3774
- message: `default AI provider set to ${provider}`
3775
- };
3776
- }
3777
- const current = detectCurrentAiProvider(env);
3778
- if (current && (options.allowUnavailableProvider || isInstalled(current, detection))) {
3779
- return {
3780
- provider: current,
3781
- source: "env",
3782
- message: isInstalled(current, detection) ? `AI provider resolved from current environment: ${current}` : `AI provider resolved from current environment: ${current}; availability was not verified on PATH`
3783
- };
3784
- }
3785
- if (detection.available.length === 0) {
3786
- throw new Error(
3787
- `No supported AI CLI found. Install one of: ${AI_PROVIDERS.join(", ")}.`
3788
- );
3789
- }
3790
- if (detection.available.length === 1) {
3791
- const provider = detection.available[0];
3792
- options.store.setDefaultAiProvider(provider);
3793
- return {
3794
- provider,
3795
- source: "detected",
3796
- message: `detected only ${provider}; default AI provider set to ${provider}`
3797
- };
3798
- }
3799
- throw new Error(
3800
- `Multiple AI CLIs are installed (${detection.available.join(", ")}) but no current AI environment was detected. Start the daemon from an AI session, pass --ai-provider <provider>, or set OKX_A2A_AI_PROVIDER.`
3801
- );
3802
- }
3803
- function resolveConfiguredAiProvider(options) {
3804
- const env = options.env ?? process.env;
3805
- const explicit = env.OKX_AGENT_TASK_AI_CLI ?? env.OKX_A2A_AI_PROVIDER;
3806
- if (explicit) {
3807
- return normalizeAiProvider(explicit);
3808
- }
3809
- return options.store.getDefaultAiProvider();
3810
- }
3811
- function resolveConfiguredAiProviderForJob(options) {
3812
- const jobId = normalizeOptionalJobId2(options.jobId);
3813
- if (jobId) {
3814
- const binding = options.store.getJobProviderBinding(jobId);
3815
- if (binding) {
3816
- bindHermesJobRouteFromEnvIfAvailable(options.store, {
3817
- jobId,
3818
- provider: binding.provider,
3819
- env: options.env
3820
- });
3821
- return binding.provider;
3822
- }
3823
- }
3824
- const provider = resolveConfiguredAiProvider({
3825
- store: options.store,
3826
- env: options.env
3827
- });
3828
- if (jobId && provider) {
3829
- return bindJobProviderWithRouteIfAvailable(options.store, {
3830
- jobId,
3831
- provider,
3832
- env: options.env
3833
- }).binding.provider;
3834
- }
3835
- return provider;
3836
- }
3837
- function bindJobProviderToCurrentDefaultIfMissing(options) {
3838
- const store = options.store ?? null;
3839
- const jobId = normalizeOptionalJobId2(options.jobId);
3840
- if (!store || !jobId) {
3841
- return null;
3842
- }
3843
- const existing = store.getJobProviderBinding(jobId);
3844
- if (existing) {
3845
- bindHermesJobRouteFromEnvIfAvailable(store, {
3846
- jobId,
3847
- provider: existing.provider,
3848
- env: options.env
3849
- });
3850
- return {
3851
- provider: existing.provider,
3852
- created: false
3853
- };
3854
- }
3855
- const provider = store.getDefaultAiProvider();
3856
- if (!provider) {
3857
- return null;
3858
- }
3859
- const result = bindJobProviderWithRouteIfAvailable(store, {
3860
- jobId,
3861
- provider,
3862
- env: options.env
3863
- });
3864
- return {
3865
- provider: result.binding.provider,
3866
- created: result.created
3867
- };
3868
- }
3869
- function bindJobProviderToCurrentRuntimeIfMissing(options) {
3870
- const store = options.store ?? null;
3871
- const jobId = normalizeOptionalJobId2(options.jobId);
3872
- if (!store || !jobId) {
3873
- return {
3874
- ok: false,
3875
- jobId: jobId ?? "",
3876
- provider: null,
3877
- currentProvider: null,
3878
- created: false,
3879
- binding: null,
3880
- reason: "missing_job_id"
3881
- };
3882
- }
3883
- const existing = store.getJobProviderBinding(jobId);
3884
- if (existing) {
3885
- const binding = bindHermesJobRouteFromEnvIfAvailable(store, {
3886
- jobId,
3887
- provider: existing.provider,
3888
- env: options.env
3889
- }) ?? existing;
3890
- return {
3891
- ok: true,
3892
- jobId,
3893
- provider: binding.provider,
3894
- currentProvider: null,
3895
- created: false,
3896
- binding,
3897
- reason: "already_bound"
3898
- };
3899
- }
3900
- const switched = switchProvider({ store, env: options.env ?? process.env });
3901
- if (!switched.ok || !switched.provider) {
3902
- return {
3903
- ok: false,
3904
- jobId,
3905
- provider: null,
3906
- currentProvider: null,
3907
- created: false,
3908
- binding: null,
3909
- reason: "unknown_runtime"
3910
- };
3911
- }
3912
- const currentProvider = switched.provider;
3913
- const result = bindJobProviderWithRouteIfAvailable(store, {
3914
- jobId,
3915
- provider: currentProvider,
3916
- env: options.env
3917
- });
3918
- return {
3919
- ok: true,
3920
- jobId,
3921
- provider: result.binding.provider,
3922
- currentProvider,
3923
- created: result.created,
3924
- binding: result.binding,
3925
- reason: result.created ? "created" : "already_bound"
3926
- };
3927
- }
3928
- function resolveAiProviderForDispatch(options) {
3929
- const env = options.env ?? process.env;
3930
- const commandExists2 = options.commandExists ?? commandExists;
3931
- const detection = withStoredCodexAvailability(
3932
- detectAiProviders(commandExists2, env),
3933
- options.store,
3934
- commandExists2
3935
- );
3936
- const jobId = normalizeOptionalJobId2(options.jobId);
3937
- if (jobId) {
3938
- const binding = options.store.getJobProviderBinding(jobId);
3939
- if (binding) {
3940
- assertInstalled(binding.provider, detection);
3941
- return binding.provider;
3942
- }
3943
- }
3944
- if (env.OKX_AGENT_TASK_AI_CLI) {
3945
- const provider = normalizeAiProvider(env.OKX_AGENT_TASK_AI_CLI);
3946
- assertInstalled(provider, detection);
3947
- return bindDispatchProviderIfNeeded(options.store, jobId, env, provider);
3948
- }
3949
- if (env.OKX_A2A_AI_PROVIDER) {
3950
- const provider = normalizeAiProvider(env.OKX_A2A_AI_PROVIDER);
3951
- assertInstalled(provider, detection);
3952
- return bindDispatchProviderIfNeeded(options.store, jobId, env, provider);
3953
- }
3954
- if (jobId) {
3955
- const stored2 = options.store.getDefaultAiProvider();
3956
- if (stored2) {
3957
- assertInstalled(stored2, detection);
3958
- return bindDispatchProviderIfNeeded(options.store, jobId, env, stored2);
3959
- }
3960
- }
3961
- const current = detectCurrentAiProvider(env);
3962
- if (current && isInstalled(current, detection)) {
3963
- return bindDispatchProviderIfNeeded(options.store, jobId, env, current);
3964
- }
3965
- if (options.sessionKey) {
3966
- const existing = options.store.listAiSessions(options.sessionKey).map((session) => session.provider).filter((provider) => isInstalled(provider, detection));
3967
- const unique = [...new Set(existing)];
3968
- if (unique.length === 1) {
3969
- return bindDispatchProviderIfNeeded(options.store, jobId, env, unique[0]);
3970
- }
3971
- }
3972
- const stored = options.store.getDefaultAiProvider();
3973
- if (stored) {
3974
- assertInstalled(stored, detection);
3975
- return bindDispatchProviderIfNeeded(options.store, jobId, env, stored);
3976
- }
3977
- if (detection.available.length === 1) {
3978
- return bindDispatchProviderIfNeeded(options.store, jobId, env, detection.available[0]);
3979
- }
3980
- if (detection.available.length > 1) {
3981
- throw new Error(
3982
- `Multiple AI CLIs are installed (${detection.available.join(", ")}) but no current AI environment was detected. Set OKX_A2A_AI_PROVIDER or pass --provider/--ai-provider for this run.`
3983
- );
3984
- }
3985
- throw new Error(`No supported AI CLI found. Install one of: ${AI_PROVIDERS.join(", ")}.`);
3986
- }
3987
- function bindDispatchProviderIfNeeded(store, jobId, env, provider) {
3988
- if (!jobId) {
3989
- return provider;
3990
- }
3991
- return bindJobProviderWithRouteIfAvailable(store, { jobId, provider, env }).binding.provider;
3992
- }
3993
- function withStoredCodexAvailability(detection, store, commandExists2) {
3994
- if (detection.codex) {
3995
- return detection;
3996
- }
3997
- const command = store.getAiProviderCommand("codex");
3998
- if (!command || !commandExists2(command)) {
3999
- return detection;
4000
- }
4001
- return {
4002
- ...detection,
4003
- codex: true,
4004
- available: detection.available.includes("codex") ? detection.available : ["codex", ...detection.available]
4005
- };
4006
- }
4007
- function bindJobProviderWithRouteIfAvailable(store, input) {
4008
- const result = store.bindJobProviderIfMissing({
4009
- jobId: input.jobId,
4010
- provider: input.provider
4011
- });
4012
- const routed = bindHermesJobRouteFromEnvIfAvailable(store, {
4013
- jobId: input.jobId,
4014
- provider: result.binding.provider,
4015
- env: input.env
4016
- });
4017
- return routed ? { ...result, binding: routed } : result;
4018
- }
4019
- function bindHermesJobRouteFromEnvIfAvailable(store, input) {
4020
- if (input.provider !== "hermes" || typeof store.upsertJobGatewayRoute !== "function") {
4021
- return null;
4022
- }
4023
- const route = currentHermesGatewayRouteFromEnv(input.env ?? process.env);
4024
- if (!route) {
4025
- return null;
4026
- }
4027
- return store.upsertJobGatewayRoute({
4028
- jobId: input.jobId,
4029
- provider: "hermes",
4030
- route
4031
- });
4032
- }
4033
- function currentHermesGatewayRouteFromEnv(env) {
4034
- const platform = normalizeOptionalText(env.OKX_A2A_CURRENT_GATEWAY_PLATFORM);
4035
- const chatId = normalizeOptionalText(env.OKX_A2A_CURRENT_GATEWAY_CHAT_ID);
4036
- const envGatewaySessionKey = normalizeOptionalText(env.OKX_A2A_CURRENT_GATEWAY_SESSION_KEY);
4037
- const parsedGatewaySession = parseHermesGatewaySessionKey(envGatewaySessionKey);
4038
- if (platform && chatId) {
4039
- const threadId = normalizeOptionalText(env.OKX_A2A_CURRENT_GATEWAY_THREAD_ID) ?? matchingParsedThreadId(parsedGatewaySession, { platform, chatId });
4040
- return buildHermesGatewayRoute({
4041
- platform,
4042
- chatId,
4043
- threadId,
4044
- sessionKey: envGatewaySessionKey
4045
- });
4046
- }
4047
- const sessionRoute = routeFromHermesSessionKey(env.HERMES_SESSION_KEY);
4048
- return sessionRoute ? buildHermesGatewayRoute(sessionRoute) : null;
4049
- }
4050
- function buildHermesGatewayRoute(input) {
4051
- const threadId = normalizeOptionalText(input.threadId) ?? "";
4052
- const sessionKey = normalizeOptionalText(input.sessionKey) ?? (threadId ? hermesThreadSessionKey(input.platform, input.chatId, threadId) : `agent:main:${input.platform}:dm:${encodeURIComponent(input.chatId)}`);
4053
- return {
4054
- platform: input.platform,
4055
- chatId: input.chatId,
4056
- chatName: "",
4057
- chatType: threadId ? "thread" : "dm",
4058
- threadId,
4059
- userId: "",
4060
- userName: "",
4061
- sessionKey,
4062
- gatewaySessionKey: sessionKey
4063
- };
4064
- }
4065
- function hermesThreadSessionKey(platform, chatId, threadId) {
4066
- const encodedChatId = encodeURIComponent(chatId);
4067
- const encodedThreadId = encodeURIComponent(threadId);
4068
- return platform === "telegram" ? `agent:main:${platform}:dm:${encodedChatId}:${encodedThreadId}` : `agent:main:${platform}:thread:${encodedChatId}:${encodedThreadId}`;
4069
- }
4070
- function routeFromHermesSessionKey(value) {
4071
- const sessionKey = normalizeOptionalText(value);
4072
- if (!sessionKey) {
4073
- return null;
4074
- }
4075
- const parsed = parseHermesGatewaySessionKey(sessionKey);
4076
- return parsed ? { ...parsed, sessionKey } : null;
4077
- }
4078
- function parseHermesGatewaySessionKey(sessionKey) {
4079
- const normalized = normalizeOptionalText(sessionKey);
4080
- if (!normalized) {
4081
- return null;
4082
- }
4083
- const parts = normalized.startsWith("agent:") ? normalized.split(":").slice(2) : normalized.split(":");
4084
- if (parts.length < 3) {
4085
- return null;
4086
- }
4087
- const [platform, scope, ...rest] = parts;
4088
- if (!platform || platform === "okx-a2a" || platform === "backup" || platform === "job") {
4089
- return null;
4090
- }
4091
- if (scope === "dm" && rest[0]) {
4092
- return {
4093
- platform,
4094
- chatId: safeDecodeURIComponent2(rest[0]),
4095
- ...platform === "telegram" && rest[1] ? { threadId: safeDecodeURIComponent2(rest.slice(1).join(":")) } : {}
4096
- };
4097
- }
4098
- if (scope === "thread" && rest[0] && rest[1]) {
4099
- return {
4100
- platform,
4101
- chatId: safeDecodeURIComponent2(rest[0]),
4102
- threadId: safeDecodeURIComponent2(rest.slice(1).join(":"))
4103
- };
4104
- }
4105
- return null;
4106
- }
4107
- function matchingParsedThreadId(parsed, expected) {
4108
- if (!parsed?.threadId) {
4109
- return null;
4110
- }
4111
- return parsed.platform === expected.platform && parsed.chatId === expected.chatId ? parsed.threadId : null;
4112
- }
4113
- function safeDecodeURIComponent2(value) {
4114
- try {
4115
- return decodeURIComponent(value);
4116
- } catch {
4117
- return value;
4118
- }
4119
- }
4120
- function normalizeOptionalJobId2(value) {
4121
- const normalized = value?.trim();
4122
- return normalized ? normalized : null;
4123
- }
4124
- function normalizeOptionalText(value) {
4125
- const normalized = value?.trim();
4126
- return normalized ? normalized : null;
4127
- }
4128
- function isInstalled(provider, detection) {
4129
- return detection.available.includes(provider);
4130
- }
4131
- function assertInstalled(provider, detection) {
4132
- if (!isInstalled(provider, detection)) {
4133
- throw new Error(`AI provider "${provider}" is not installed or not on PATH.`);
4134
- }
4135
- }
4136
- function readProviderCommand(provider, env) {
4137
- return env[`OKX_A2A_AI_${provider.toUpperCase()}_COMMAND`] ?? provider;
4138
- }
4139
- async function promptForAiProvider(options) {
4140
- const stdin = options.stdin ?? process.stdin;
4141
- const stdout = options.stdout ?? process.stdout;
4142
- if (!stdin.isTTY || !stdout.isTTY) {
4143
- throw new Error(
4144
- `No AI provider is configured. Run \`okx-a2a daemon start --ai-provider <${AI_PROVIDERS.join("|")}>\` or set OKX_A2A_AI_PROVIDER.`
4145
- );
4146
- }
4147
- stdout.write("AI provider is required before starting okx-a2a.\n");
4148
- stdout.write("Tip: if the selected AI provider is unavailable, task processing cannot proceed.\n\n");
4149
- if (options.currentProvider) {
4150
- stdout.write(`Current AI provider: ${options.currentProvider}
4151
-
4152
- `);
4153
- }
4154
- if (options.detection.available.length === 0) {
4155
- stdout.write("Supported AI providers:\n");
4156
- AI_PROVIDERS.forEach((provider, index2) => {
4157
- const status = isInstalled(provider, options.detection) ? "installed" : "not found on PATH";
4158
- stdout.write(` ${index2 + 1}. ${provider} (${status})
4159
- `);
4160
- });
4161
- stdout.write("\n");
4162
- throw new Error(`No supported AI CLI found. Install one of: ${AI_PROVIDERS.join(", ")}.`);
4163
- }
4164
- if (typeof stdin.setRawMode === "function") {
4165
- return promptForAiProviderWithArrows({
4166
- detection: options.detection,
4167
- stdin,
4168
- stdout,
4169
- currentProvider: options.currentProvider
4170
- });
4171
- }
4172
- stdout.write("Supported AI providers:\n");
4173
- AI_PROVIDERS.forEach((provider, index2) => {
4174
- const status = isInstalled(provider, options.detection) ? "installed" : "not found on PATH";
4175
- stdout.write(` ${index2 + 1}. ${provider} (${status})
4176
- `);
4177
- });
4178
- stdout.write("\n");
4179
- const rl = (0, import_promises6.createInterface)({ input: stdin, output: stdout });
4180
- try {
4181
- while (true) {
4182
- const answer = (await rl.question("Choose an installed AI provider by number or name: ")).trim();
4183
- const provider = parseProviderChoice(answer);
4184
- if (!provider) {
4185
- stdout.write(`Invalid choice. Use one of: ${AI_PROVIDERS.join(", ")}.
4186
- `);
4187
- continue;
4188
- }
4189
- if (!isInstalled(provider, options.detection)) {
4190
- stdout.write(`"${provider}" is not installed or not on PATH. Choose an installed provider.
4191
- `);
4192
- continue;
4193
- }
4194
- return provider;
4195
- }
4196
- } finally {
4197
- rl.close();
4198
- }
4199
- }
4200
- async function promptForAiProviderWithArrows(options) {
4201
- let selectedIndex = AI_PROVIDERS.findIndex((provider) => provider === options.currentProvider && isInstalled(provider, options.detection));
4202
- if (selectedIndex < 0) {
4203
- selectedIndex = AI_PROVIDERS.findIndex((provider) => isInstalled(provider, options.detection));
4204
- }
4205
- if (selectedIndex < 0) {
4206
- selectedIndex = 0;
4207
- }
4208
- let renderedLines = 0;
4209
- const render = (message) => {
4210
- if (renderedLines > 0) {
4211
- (0, import_node_readline.moveCursor)(options.stdout, 0, -renderedLines);
4212
- (0, import_node_readline.cursorTo)(options.stdout, 0);
4213
- (0, import_node_readline.clearScreenDown)(options.stdout);
4214
- }
4215
- const lines = [
4216
- "Use \u2191/\u2193 to move, Enter to confirm.",
4217
- "",
4218
- ...AI_PROVIDERS.map((provider, index2) => {
4219
- const selected = index2 === selectedIndex ? "\u276F" : " ";
4220
- const status = isInstalled(provider, options.detection) ? "installed" : "not found on PATH";
4221
- const current = provider === options.currentProvider ? ", current" : "";
4222
- return `${selected} ${provider} (${status}${current})`;
4223
- }),
4224
- ...message ? ["", message] : []
4225
- ];
4226
- options.stdout.write(`${lines.join("\n")}
4227
- `);
4228
- renderedLines = lines.length;
4229
- };
4230
- (0, import_node_readline.emitKeypressEvents)(options.stdin);
4231
- options.stdin.setRawMode(true);
4232
- options.stdin.resume();
4233
- options.stdout.write("\x1B[?25l");
4234
- render();
4235
- return await new Promise((resolve11, reject) => {
4236
- const cleanup = () => {
4237
- options.stdin.off("keypress", onKeypress);
4238
- options.stdin.setRawMode(false);
4239
- options.stdin.pause();
4240
- options.stdout.write("\x1B[?25h");
4241
- };
4242
- const clearRendered = () => {
4243
- if (renderedLines > 0) {
4244
- (0, import_node_readline.moveCursor)(options.stdout, 0, -renderedLines);
4245
- (0, import_node_readline.cursorTo)(options.stdout, 0);
4246
- (0, import_node_readline.clearScreenDown)(options.stdout);
4247
- }
4248
- };
4249
- const finish = (provider) => {
4250
- cleanup();
4251
- clearRendered();
4252
- options.stdout.write(`Selected AI provider: ${provider}
4253
- `);
4254
- resolve11(provider);
4255
- };
4256
- const onKeypress = (_str, key) => {
4257
- if (key.ctrl && key.name === "c") {
4258
- cleanup();
4259
- clearRendered();
4260
- options.stdout.write(
4261
- options.currentProvider ? `AI provider selection cancelled. Keeping current provider: ${options.currentProvider}
4262
- ` : "AI provider selection cancelled. No provider was configured.\n"
4263
- );
4264
- reject(new UserCancelledAiProviderSelectionError());
4265
- return;
4266
- }
4267
- if (key.name === "up") {
4268
- selectedIndex = (selectedIndex + AI_PROVIDERS.length - 1) % AI_PROVIDERS.length;
4269
- render();
4270
- return;
4271
- }
4272
- if (key.name === "down") {
4273
- selectedIndex = (selectedIndex + 1) % AI_PROVIDERS.length;
4274
- render();
4275
- return;
4276
- }
4277
- if (key.name === "return" || key.name === "enter") {
4278
- const provider = AI_PROVIDERS[selectedIndex];
4279
- if (!isInstalled(provider, options.detection)) {
4280
- render(`"${provider}" is not installed or not on PATH. Choose an installed provider.`);
4281
- return;
4282
- }
4283
- finish(provider);
4284
- }
4285
- };
4286
- options.stdin.on("keypress", onKeypress);
4287
- });
4288
- }
4289
- function parseProviderChoice(answer) {
4290
- if (!answer) {
4291
- return null;
4292
- }
4293
- const number = Number(answer);
4294
- if (Number.isInteger(number) && number >= 1 && number <= AI_PROVIDERS.length) {
4295
- return AI_PROVIDERS[number - 1];
4296
- }
4297
- try {
4298
- return normalizeAiProvider(answer);
4299
- } catch {
4300
- return null;
4301
- }
4302
- }
4303
- var import_node_child_process5, import_promises6, import_node_readline, AI_PROVIDERS, UserCancelledAiProviderSelectionError;
4304
- var init_ai_provider = __esm({
4305
- "src/ai-provider.ts"() {
4306
- "use strict";
4307
- import_node_child_process5 = require("node:child_process");
4308
- import_promises6 = require("node:readline/promises");
4309
- import_node_readline = require("node:readline");
4310
- init_ai_command();
4311
- init_win_spawn();
4312
- AI_PROVIDERS = ["codex", "claude", "hermes", "openclaw"];
4313
- UserCancelledAiProviderSelectionError = class extends Error {
4314
- constructor() {
4315
- super("AI provider selection cancelled by user.");
4316
- this.name = "UserCancelledAiProviderSelectionError";
4317
- }
4318
- };
4319
- }
4320
- });
4321
-
4322
- // ../../node_modules/@sentry/utils/cjs/is.js
4323
- var require_is = __commonJS({
4324
- "../../node_modules/@sentry/utils/cjs/is.js"(exports2) {
4325
- Object.defineProperty(exports2, "__esModule", { value: true });
4326
- var objectToString = Object.prototype.toString;
4327
- function isError(wat) {
4328
- switch (objectToString.call(wat)) {
4329
- case "[object Error]":
4330
- case "[object Exception]":
4331
- case "[object DOMException]":
4332
- return true;
4333
- default:
4334
- return isInstanceOf(wat, Error);
4335
- }
4336
- }
4337
- function isBuiltin(wat, className) {
4338
- return objectToString.call(wat) === `[object ${className}]`;
4339
- }
4340
- function isErrorEvent(wat) {
4341
- return isBuiltin(wat, "ErrorEvent");
3751
+ function isErrorEvent(wat) {
3752
+ return isBuiltin(wat, "ErrorEvent");
4342
3753
  }
4343
3754
  function isDOMError(wat) {
4344
3755
  return isBuiltin(wat, "DOMError");
@@ -6182,7 +5593,7 @@ var require_path = __commonJS({
6182
5593
  return outputParts.join("/");
6183
5594
  }
6184
5595
  function normalizePath(path2) {
6185
- const isPathAbsolute = isAbsolute3(path2);
5596
+ const isPathAbsolute = isAbsolute4(path2);
6186
5597
  const trailingSlash = path2.slice(-1) === "/";
6187
5598
  let normalizedPath = normalizeArray(
6188
5599
  path2.split("/").filter((p) => !!p),
@@ -6196,7 +5607,7 @@ var require_path = __commonJS({
6196
5607
  }
6197
5608
  return (isPathAbsolute ? "/" : "") + normalizedPath;
6198
5609
  }
6199
- function isAbsolute3(path2) {
5610
+ function isAbsolute4(path2) {
6200
5611
  return path2.charAt(0) === "/";
6201
5612
  }
6202
5613
  function join31(...args) {
@@ -6223,7 +5634,7 @@ var require_path = __commonJS({
6223
5634
  }
6224
5635
  exports2.basename = basename8;
6225
5636
  exports2.dirname = dirname9;
6226
- exports2.isAbsolute = isAbsolute3;
5637
+ exports2.isAbsolute = isAbsolute4;
6227
5638
  exports2.join = join31;
6228
5639
  exports2.normalizePath = normalizePath;
6229
5640
  exports2.relative = relative;
@@ -19627,6 +19038,14 @@ var init_events = __esm({
19627
19038
  AI_RUN_STARTED: "AI run started",
19628
19039
  AI_RUN_COMPLETED: "AI run completed",
19629
19040
  AI_RUN_SKIPPED: "AI run skipped",
19041
+ // Codex GUI runtime detection / binding checkpoints. All four are logger.info,
19042
+ // so each one must also be listed in SENTRY_INFO_ALLOWLIST (index.ts): info()
19043
+ // returns early for an unlisted event, which ships a checkpoint that emits
19044
+ // nothing while every code path looks implemented.
19045
+ RUNTIME_DETECTED: "Runtime detected",
19046
+ PROVIDER_READINESS_CHECKED: "Provider readiness checked",
19047
+ PROVIDER_SWITCHED: "Provider switched",
19048
+ JOB_PROVIDER_BOUND: "Job provider bound",
19630
19049
  // Hermes user-channel delivery only. The node and openclaw user-channel hops
19631
19050
  // keep emitting USER_DISPATCHED / PROMPT_USER_CHECKPOINT — one name per
19632
19051
  // transport is the whole point of splitting the send events.
@@ -19907,23 +19326,23 @@ function appendActiveXmtpTestMetric(level, eventName, extra) {
19907
19326
  }
19908
19327
  try {
19909
19328
  const homeDir = resolveA2aTaskHome();
19910
- const testsDir = (0, import_node_path13.join)(homeDir, "xmtp-tests");
19911
- const activePath = (0, import_node_path13.join)(testsDir, "active.json");
19912
- if (!(0, import_node_fs9.existsSync)(activePath)) {
19329
+ const testsDir = (0, import_node_path12.join)(homeDir, "xmtp-tests");
19330
+ const activePath = (0, import_node_path12.join)(testsDir, "active.json");
19331
+ if (!(0, import_node_fs8.existsSync)(activePath)) {
19913
19332
  return;
19914
19333
  }
19915
- const active = JSON.parse((0, import_node_fs9.readFileSync)(activePath, "utf8"));
19334
+ const active = JSON.parse((0, import_node_fs8.readFileSync)(activePath, "utf8"));
19916
19335
  const runId = typeof active.runId === "string" ? active.runId : "";
19917
19336
  if (!RUN_ID_PATTERN.test(runId)) {
19918
19337
  return;
19919
19338
  }
19920
- const runDir = (0, import_node_path13.resolve)(testsDir, runId);
19921
- const root = `${(0, import_node_path13.resolve)(testsDir)}${import_node_path13.sep}`;
19339
+ const runDir = (0, import_node_path12.resolve)(testsDir, runId);
19340
+ const root = `${(0, import_node_path12.resolve)(testsDir)}${import_node_path12.sep}`;
19922
19341
  if (!runDir.startsWith(root)) {
19923
19342
  return;
19924
19343
  }
19925
- (0, import_node_fs9.appendFileSync)(
19926
- (0, import_node_path13.join)(runDir, "events.jsonl"),
19344
+ (0, import_node_fs8.appendFileSync)(
19345
+ (0, import_node_path12.join)(runDir, "events.jsonl"),
19927
19346
  `${JSON.stringify({
19928
19347
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
19929
19348
  level,
@@ -19937,12 +19356,12 @@ function appendActiveXmtpTestMetric(level, eventName, extra) {
19937
19356
  } catch {
19938
19357
  }
19939
19358
  }
19940
- var import_node_fs9, import_node_path13, RUN_ID_PATTERN, XMTP_TEST_EVENTS;
19359
+ var import_node_fs8, import_node_path12, RUN_ID_PATTERN, XMTP_TEST_EVENTS;
19941
19360
  var init_xmtp_test_metrics = __esm({
19942
19361
  "../core/src/xmtp-test-metrics.ts"() {
19943
19362
  "use strict";
19944
- import_node_fs9 = require("node:fs");
19945
- import_node_path13 = require("node:path");
19363
+ import_node_fs8 = require("node:fs");
19364
+ import_node_path12 = require("node:path");
19946
19365
  init_a2a_paths();
19947
19366
  RUN_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
19948
19367
  XMTP_TEST_EVENTS = /* @__PURE__ */ new Set([
@@ -20182,6 +19601,9 @@ function applyFatalEventDefaults(event) {
20182
19601
  }
20183
19602
  return event;
20184
19603
  }
19604
+ function isInfoEventAllowlisted(name) {
19605
+ return SENTRY_INFO_ALLOWLIST.has(name);
19606
+ }
20185
19607
  function agentExtras(identity) {
20186
19608
  return {
20187
19609
  walletAddress: identity.walletAddress || UNKNOWN_FIELD,
@@ -20387,6 +19809,13 @@ var init_sentry_logger = __esm({
20387
19809
  LogEvent.AI_RUN_STARTED,
20388
19810
  LogEvent.AI_RUN_COMPLETED,
20389
19811
  LogEvent.AI_RUN_SKIPPED,
19812
+ // Codex runtime-binding checkpoints. Omitting any of these makes info() drop
19813
+ // the event silently, so the whole FR-4 trace would look implemented in code
19814
+ // and emit nothing in production.
19815
+ LogEvent.RUNTIME_DETECTED,
19816
+ LogEvent.PROVIDER_READINESS_CHECKED,
19817
+ LogEvent.PROVIDER_SWITCHED,
19818
+ LogEvent.JOB_PROVIDER_BOUND,
20390
19819
  LogEvent.USER_CHANNEL_MESSAGE_DELIVERED,
20391
19820
  LogEvent.SYSTEM_NOTIFICATION_RECEIVED,
20392
19821
  LogEvent.SYSTEM_NOTIFICATION_ROUTED,
@@ -20567,120 +19996,1637 @@ var init_sentry_logger = __esm({
20567
19996
  } catch {
20568
19997
  }
20569
19998
  }
20570
- async shutdown() {
20571
- try {
20572
- await Sentry.close(5e3);
20573
- } catch {
20574
- await Sentry.flush(5e3);
20575
- }
19999
+ async shutdown() {
20000
+ try {
20001
+ await Sentry.close(5e3);
20002
+ } catch {
20003
+ await Sentry.flush(5e3);
20004
+ }
20005
+ }
20006
+ flush() {
20007
+ while (this.buffer.length > 0) {
20008
+ const entry = this.buffer.shift();
20009
+ if (entry.level === "info") {
20010
+ this.captureInfo(entry.message, entry.extra ?? {});
20011
+ } else {
20012
+ this.captureError(entry.message, entry.error, entry.extra ?? {});
20013
+ }
20014
+ }
20015
+ }
20016
+ static sanitizeExtra(extra) {
20017
+ return Object.fromEntries(
20018
+ Object.entries(extra).filter(([, value]) => value !== void 0).filter(([key]) => !_SentryLogger.isBlockedExtraKey(key)).map(([key, value]) => [key, _SentryLogger.safeValueForKey(key, value)])
20019
+ );
20020
+ }
20021
+ static isBlockedExtraKey(key) {
20022
+ const lower = key.toLowerCase();
20023
+ const normalized = _SentryLogger.normalizeSensitiveKey(key);
20024
+ if (SENTRY_EXTRA_BLOCKLIST.has(lower) || SENTRY_EXTRA_BLOCKLIST.has(normalized)) {
20025
+ return true;
20026
+ }
20027
+ if (_SentryLogger.isSafeMetricKey(normalized)) {
20028
+ return false;
20029
+ }
20030
+ return SENTRY_EXTRA_BLOCKED_KEY_PARTS.some((needle) => normalized.includes(needle));
20031
+ }
20032
+ static isSafeMetricKey(normalized) {
20033
+ return /(bytes|count|length|ms|size)$/.test(normalized);
20034
+ }
20035
+ static safeValueForKey(key, value) {
20036
+ if (typeof value === "string" && SENTRY_FULL_STRING_EXTRA_KEYS.has(_SentryLogger.normalizeSensitiveKey(key))) {
20037
+ return value;
20038
+ }
20039
+ return _SentryLogger.safeValue(value);
20040
+ }
20041
+ static normalizeSensitiveKey(key) {
20042
+ return key.toLowerCase().replace(/[^a-z0-9]/g, "");
20043
+ }
20044
+ static safeValue(value) {
20045
+ if (typeof value === "string") {
20046
+ return value.length <= MAX_EXTRA_STRING_LENGTH ? value : `${value.slice(0, MAX_EXTRA_STRING_LENGTH)}...`;
20047
+ }
20048
+ if (value === null || value === void 0 || typeof value === "number" || typeof value === "boolean") {
20049
+ return value;
20050
+ }
20051
+ if (typeof value === "bigint") {
20052
+ return value.toString();
20053
+ }
20054
+ if (value instanceof Error) {
20055
+ return {
20056
+ name: value.name,
20057
+ messageLength: value.message.length
20058
+ };
20059
+ }
20060
+ if (Array.isArray(value)) {
20061
+ return value.slice(0, 20).map((item) => _SentryLogger.safeValue(item));
20062
+ }
20063
+ if (typeof value === "object") {
20064
+ return Object.fromEntries(
20065
+ Object.entries(value).slice(0, 50).filter(([, item]) => item !== void 0).filter(([key]) => !_SentryLogger.isBlockedExtraKey(key)).map(([key, item]) => [key, _SentryLogger.safeValueForKey(key, item)])
20066
+ );
20067
+ }
20068
+ const text = String(value);
20069
+ return text.length <= MAX_EXTRA_STRING_LENGTH ? text : `${text.slice(0, MAX_EXTRA_STRING_LENGTH)}...`;
20070
+ }
20071
+ static tagValue(value) {
20072
+ const text = String(value);
20073
+ return text.length <= MAX_TAG_VALUE_LENGTH ? text : `${text.slice(0, MAX_TAG_VALUE_LENGTH)}...`;
20074
+ }
20075
+ static normalizeTagKey(key) {
20076
+ return key.replace(/[^A-Za-z0-9_.:-]+/g, "_").slice(0, 32) || "unknown";
20077
+ }
20078
+ static applyDiagnostics(scope, eventName, extra) {
20079
+ const enriched = { eventName, ...extra };
20080
+ for (const [key, value] of Object.entries(enriched)) {
20081
+ if (!SENTRY_TAG_KEYS.has(key)) {
20082
+ continue;
20083
+ }
20084
+ scope.setTag(_SentryLogger.normalizeTagKey(key), _SentryLogger.tagValue(value));
20085
+ }
20086
+ const fingerprint = ["a2a", eventName];
20087
+ for (const key of SENTRY_FINGERPRINT_KEYS) {
20088
+ const value = enriched[key];
20089
+ if (value) {
20090
+ fingerprint.push(`${key}:${_SentryLogger.tagValue(value)}`);
20091
+ }
20092
+ }
20093
+ scope.setFingerprint(fingerprint);
20094
+ }
20095
+ static toPascalCase(str) {
20096
+ return str.split(/[\s:]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
20097
+ }
20098
+ static createSentryEvent(eventName) {
20099
+ const err = new Error(eventName);
20100
+ err.name = _SentryLogger.toPascalCase(eventName);
20101
+ err.stack = `${err.name}: ${eventName}`;
20102
+ return err;
20103
+ }
20104
+ };
20105
+ logger = SentryLogger.getInstance();
20106
+ initLogger = (config) => {
20107
+ return logger.init(config);
20108
+ };
20109
+ shutdown = () => {
20110
+ return logger.shutdown();
20111
+ };
20112
+ UNKNOWN_FIELD = "unknown";
20113
+ }
20114
+ });
20115
+
20116
+ // src/ai-provider.ts
20117
+ function normalizeAiProvider(value) {
20118
+ const normalized = value.trim().toLowerCase();
20119
+ if (normalized === "codex") {
20120
+ return "codex";
20121
+ }
20122
+ if (normalized === "claude" || normalized === "claude-code" || normalized === "claude_code") {
20123
+ return "claude";
20124
+ }
20125
+ if (normalized === "hermes") {
20126
+ return "hermes";
20127
+ }
20128
+ if (normalized === "openclaw") {
20129
+ return "openclaw";
20130
+ }
20131
+ throw new Error(`Unsupported AI provider "${value}". Use one of: ${AI_PROVIDERS.join(", ")}.`);
20132
+ }
20133
+ function detectAiProviders(commandExists2 = commandExists, env = process.env) {
20134
+ const codex = commandExists2(readProviderCommand("codex", env));
20135
+ const claude = commandExists2(readProviderCommand("claude", env));
20136
+ const hermes = commandExists2(readProviderCommand("hermes", env));
20137
+ const openclaw = commandExists2(readProviderCommand("openclaw", env));
20138
+ return {
20139
+ codex,
20140
+ claude,
20141
+ hermes,
20142
+ openclaw,
20143
+ available: [
20144
+ ...codex ? ["codex"] : [],
20145
+ ...claude ? ["claude"] : [],
20146
+ ...hermes ? ["hermes"] : [],
20147
+ ...openclaw ? ["openclaw"] : []
20148
+ ]
20149
+ };
20150
+ }
20151
+ function detectCurrentAiProvider(env = process.env) {
20152
+ const explicit = env.OKX_A2A_AI_PROVIDER ?? env.OKX_AGENT_TASK_AI_CLI;
20153
+ if (explicit) {
20154
+ return normalizeAiProvider(explicit);
20155
+ }
20156
+ const runtime = detectRuntime(env);
20157
+ return runtime === "unknown" ? null : runtime;
20158
+ }
20159
+ function hasAiRuntimeMarker(env = process.env) {
20160
+ return !!(detectGatewayInvocation(env) || env.CODEX_THREAD_ID || isTruthyRuntimeMarker(env.CODEX_SHELL) || env.CODEX_MANAGED_BY_NPM || isTruthyRuntimeMarker(env.CODEX_CI) || env.CLAUDE_SESSION_ID || isTruthyRuntimeMarker(env.CLAUDECODE) || env.CLAUDE_CODE_SESSION_ID || env.CLAUDE_PLUGIN_DATA || env.HERMES_SESSION_ID && !env.HERMES_DESKTOP_CWD || env.HERMES_SESSION_KEY || env.HERMES_FLOW_ID || env.OPENCLAW_SHELL || env.OPENCLAW_CLI);
20161
+ }
20162
+ function detectRuntime(env = process.env, options = {}) {
20163
+ const gatewayInvocation = detectGatewayInvocation(env);
20164
+ const strongMatches = [
20165
+ ...gatewayInvocation ? [gatewayInvocation] : [],
20166
+ ...env.CODEX_THREAD_ID || isTruthyRuntimeMarker(env.CODEX_SHELL) || env.CODEX_MANAGED_BY_NPM || isTruthyRuntimeMarker(env.CODEX_CI) ? ["codex"] : [],
20167
+ ...env.CLAUDE_SESSION_ID || isTruthyRuntimeMarker(env.CLAUDECODE) || env.CLAUDE_CODE_SESSION_ID || env.CLAUDE_PLUGIN_DATA ? ["claude"] : [],
20168
+ ...env.HERMES_SESSION_ID && !env.HERMES_DESKTOP_CWD || env.HERMES_SESSION_KEY || env.HERMES_FLOW_ID ? ["hermes"] : [],
20169
+ ...env.OPENCLAW_SHELL || env.OPENCLAW_CLI ? ["openclaw"] : [],
20170
+ ...hasOpenClawParentProcess(options.parentPid ?? process.ppid, options.readParentProcessCommand ?? readParentProcessCommand) ? ["openclaw"] : []
20171
+ ];
20172
+ const uniqueMatches = [...new Set(strongMatches)];
20173
+ if (uniqueMatches.length === 1) {
20174
+ return uniqueMatches[0];
20175
+ }
20176
+ return "unknown";
20177
+ }
20178
+ function detectGatewayInvocation(env = process.env) {
20179
+ const context = (env.OKX_A2A_RUNTIME_CONTEXT ?? "").trim().toLowerCase();
20180
+ if (context === "openclaw-gateway" || context === "gateway:openclaw") {
20181
+ return "openclaw";
20182
+ }
20183
+ if (context === "hermes-gateway" || context === "gateway:hermes") {
20184
+ return "hermes";
20185
+ }
20186
+ if (isTruthyRuntimeMarker(env._HERMES_GATEWAY)) {
20187
+ return "hermes";
20188
+ }
20189
+ if ((env.OPENCLAW_SERVICE_KIND ?? "").trim().toLowerCase() === "gateway") {
20190
+ return "openclaw";
20191
+ }
20192
+ if (!isTruthyRuntimeMarker(env.OKX_A2A_IN_GATEWAY)) {
20193
+ return null;
20194
+ }
20195
+ const provider = env.OKX_A2A_GATEWAY_PROVIDER;
20196
+ if (!provider) {
20197
+ return null;
20198
+ }
20199
+ try {
20200
+ const normalized = normalizeAiProvider(provider);
20201
+ return normalized === "openclaw" || normalized === "hermes" ? normalized : null;
20202
+ } catch {
20203
+ return null;
20204
+ }
20205
+ }
20206
+ function switchProvider(options) {
20207
+ const runtime = detectRuntime(options.env ?? process.env, {
20208
+ parentPid: options.parentPid,
20209
+ readParentProcessCommand: options.readParentProcessCommand
20210
+ });
20211
+ if (runtime === "unknown") {
20212
+ const stored = options.store.getDefaultAiProvider();
20213
+ if (stored) {
20214
+ return {
20215
+ ok: true,
20216
+ runtime: "unknown",
20217
+ provider: stored,
20218
+ previousProvider: stored,
20219
+ changed: false,
20220
+ action: "kept_existing",
20221
+ userMessage: `No AI runtime detected for the current environment; keeping the existing default provider (${formatAiProviderDisplayName(stored)}).`
20222
+ };
20223
+ }
20224
+ return {
20225
+ ok: false,
20226
+ runtime: "unknown",
20227
+ reason: "unknown_runtime",
20228
+ state: "blocked",
20229
+ message: "Could not determine the current AI provider.",
20230
+ userMessage: "Could not detect the current AI runtime and no default AI provider is configured. Run this from your AI environment (OpenClaw / Codex / Claude / Hermes), or set a provider manually: `okx-a2a ai-provider set --provider <codex|claude|hermes|openclaw>`."
20231
+ };
20232
+ }
20233
+ const previousProvider = options.store.getDefaultAiProvider();
20234
+ const changed = previousProvider !== runtime;
20235
+ options.store.setDefaultAiProvider(runtime);
20236
+ return {
20237
+ ok: true,
20238
+ runtime,
20239
+ provider: runtime,
20240
+ previousProvider,
20241
+ changed,
20242
+ action: "switched",
20243
+ userMessage: changed ? `Switched to ${formatAiProviderDisplayName(runtime)}\uFF5CNew tasks will be handled here; unfinished tasks should be continued in their original session` : ""
20244
+ };
20245
+ }
20246
+ function formatAiProviderDisplayName(provider) {
20247
+ if (provider === "openclaw") {
20248
+ return "OpenClaw";
20249
+ }
20250
+ return `${provider.charAt(0).toUpperCase()}${provider.slice(1)}`;
20251
+ }
20252
+ function isTruthyRuntimeMarker(value) {
20253
+ return value === "1" || value === "true" || value === "yes";
20254
+ }
20255
+ function hasOpenClawParentProcess(parentPid, readParentProcessCommand2) {
20256
+ let pid = parentPid;
20257
+ for (let i = 0; i < 8; i++) {
20258
+ if (!pid || pid <= 1) {
20259
+ return false;
20260
+ }
20261
+ const processInfo = readParentProcessCommand2(pid);
20262
+ if (!processInfo) {
20263
+ return false;
20264
+ }
20265
+ if (/openclaw/i.test(processInfo.command)) {
20266
+ return true;
20267
+ }
20268
+ if (!processInfo.parentPid || processInfo.parentPid === pid) {
20269
+ return false;
20270
+ }
20271
+ pid = processInfo.parentPid;
20272
+ }
20273
+ return false;
20274
+ }
20275
+ function readParentProcessCommand(pid) {
20276
+ return readParentProcessCommandForPlatform(pid, process.platform);
20277
+ }
20278
+ function readParentProcessCommandForPlatform(pid, platform) {
20279
+ if (platform === "win32") {
20280
+ return readWindowsParentProcessCommand(pid);
20281
+ }
20282
+ const commandResult = (0, import_node_child_process5.spawnSync)("ps", ["-p", String(pid), "-o", "comm="], {
20283
+ encoding: "utf8",
20284
+ stdio: ["ignore", "pipe", "ignore"]
20285
+ });
20286
+ const parentResult = (0, import_node_child_process5.spawnSync)("ps", ["-p", String(pid), "-o", "ppid="], {
20287
+ encoding: "utf8",
20288
+ stdio: ["ignore", "pipe", "ignore"]
20289
+ });
20290
+ const command = typeof commandResult.stdout === "string" ? commandResult.stdout.trim() : "";
20291
+ const parentPidRaw = typeof parentResult.stdout === "string" ? parentResult.stdout.trim() : "";
20292
+ const parentPid = Number(parentPidRaw);
20293
+ if (commandResult.status !== 0 || parentResult.status !== 0 || !command || !Number.isFinite(parentPid)) {
20294
+ return null;
20295
+ }
20296
+ return { command, parentPid };
20297
+ }
20298
+ function buildWindowsParentProcessProbe(pid) {
20299
+ if (!Number.isInteger(pid)) {
20300
+ return null;
20301
+ }
20302
+ return {
20303
+ command: "powershell.exe",
20304
+ args: [
20305
+ "-NoProfile",
20306
+ "-NonInteractive",
20307
+ "-Command",
20308
+ `Get-CimInstance Win32_Process -Filter "ProcessId=${pid}" | Select-Object Name,ParentProcessId | ConvertTo-Json -Compress`
20309
+ ]
20310
+ };
20311
+ }
20312
+ function parseWindowsParentProcessJson(stdout) {
20313
+ const trimmed = stdout.trim();
20314
+ if (!trimmed) {
20315
+ return null;
20316
+ }
20317
+ let parsed;
20318
+ try {
20319
+ parsed = JSON.parse(trimmed);
20320
+ } catch {
20321
+ return null;
20322
+ }
20323
+ const record = Array.isArray(parsed) ? parsed[0] : parsed;
20324
+ if (!record || typeof record !== "object") {
20325
+ return null;
20326
+ }
20327
+ const name = record.Name;
20328
+ if (typeof name !== "string" || name.trim() === "") {
20329
+ return null;
20330
+ }
20331
+ const rawParentPid = Number(record.ParentProcessId);
20332
+ const parentPid = Number.isInteger(rawParentPid) && rawParentPid > 0 ? rawParentPid : null;
20333
+ return { command: name.trim(), parentPid };
20334
+ }
20335
+ function readWindowsParentProcessCommand(pid) {
20336
+ const probe = buildWindowsParentProcessProbe(pid);
20337
+ if (!probe) {
20338
+ logWinCompat(`${WIN_COMPAT_LOG_PREFIX} parent-process probe rejected non-integer pid=${String(pid)}`);
20339
+ return null;
20340
+ }
20341
+ const result = (0, import_node_child_process5.spawnSync)(probe.command, probe.args, {
20342
+ encoding: "utf8",
20343
+ stdio: ["ignore", "pipe", "ignore"],
20344
+ timeout: 5e3,
20345
+ windowsHide: true
20346
+ });
20347
+ if (result.error) {
20348
+ const spawnError = result.error;
20349
+ logWinCompat(
20350
+ `${WIN_COMPAT_LOG_PREFIX} parent-process probe failed pid=${pid} command=${probe.command} errorCode=${spawnError.code ?? "unknown"} error=${spawnError.message}`
20351
+ );
20352
+ return null;
20353
+ }
20354
+ if (result.status !== 0) {
20355
+ logWinCompat(
20356
+ `${WIN_COMPAT_LOG_PREFIX} parent-process probe exited pid=${pid} command=${probe.command} status=${String(result.status)} signal=${String(result.signal)}`
20357
+ );
20358
+ return null;
20359
+ }
20360
+ const stdout = typeof result.stdout === "string" ? result.stdout : "";
20361
+ const parsed = parseWindowsParentProcessJson(stdout);
20362
+ if (!parsed) {
20363
+ logWinCompat(
20364
+ `${WIN_COMPAT_LOG_PREFIX} parent-process probe unparsable output pid=${pid} stdout=${JSON.stringify(stdout.slice(0, 200))}`
20365
+ );
20366
+ return null;
20367
+ }
20368
+ if (process.env.OKX_A2A_WIN_COMPAT_VERBOSE === "1") {
20369
+ logWinCompat(
20370
+ `${WIN_COMPAT_LOG_PREFIX} parent-process probe pid=${pid} -> command=${parsed.command} parentPid=${parsed.parentPid ?? "null"}`
20371
+ );
20372
+ }
20373
+ return parsed;
20374
+ }
20375
+ async function ensureDefaultAiProvider(options) {
20376
+ const env = options.env ?? process.env;
20377
+ const commandExists2 = options.commandExists ?? commandExists;
20378
+ const detection = withStoredCodexAvailability(
20379
+ detectAiProviders(commandExists2, env),
20380
+ options.store,
20381
+ commandExists2,
20382
+ env
20383
+ );
20384
+ if (options.requestedProvider) {
20385
+ const provider = normalizeAiProvider(options.requestedProvider);
20386
+ if (!options.allowUnavailableProvider) {
20387
+ assertInstalled(provider, detection);
20388
+ }
20389
+ options.store.setDefaultAiProvider(provider);
20390
+ return {
20391
+ provider,
20392
+ source: "requested",
20393
+ message: isInstalled(provider, detection) ? `default AI provider set to ${provider}` : `default AI provider set to ${provider}; availability was not verified on PATH`
20394
+ };
20395
+ }
20396
+ if (options.forcePrompt) {
20397
+ const provider = await promptForAiProvider({
20398
+ detection,
20399
+ stdin: options.stdin,
20400
+ stdout: options.stdout,
20401
+ currentProvider: options.store.getDefaultAiProvider()
20402
+ });
20403
+ options.store.setDefaultAiProvider(provider);
20404
+ return {
20405
+ provider,
20406
+ source: "detected",
20407
+ message: `default AI provider set to ${provider}`
20408
+ };
20409
+ }
20410
+ if (env.OKX_AGENT_TASK_AI_CLI) {
20411
+ const provider = normalizeAiProvider(env.OKX_AGENT_TASK_AI_CLI);
20412
+ if (!options.allowUnavailableProvider) {
20413
+ assertInstalled(provider, detection);
20414
+ }
20415
+ options.store.setDefaultAiProvider(provider);
20416
+ return {
20417
+ provider,
20418
+ source: "env",
20419
+ message: isInstalled(provider, detection) ? `default AI provider set to ${provider} from OKX_AGENT_TASK_AI_CLI` : `default AI provider set to ${provider} from OKX_AGENT_TASK_AI_CLI; availability was not verified on PATH`
20420
+ };
20421
+ }
20422
+ if (env.OKX_A2A_AI_PROVIDER) {
20423
+ const provider = normalizeAiProvider(env.OKX_A2A_AI_PROVIDER);
20424
+ if (!options.allowUnavailableProvider) {
20425
+ assertInstalled(provider, detection);
20426
+ }
20427
+ return {
20428
+ provider,
20429
+ source: "env",
20430
+ message: isInstalled(provider, detection) ? `AI provider resolved from OKX_A2A_AI_PROVIDER: ${provider}` : `AI provider resolved from OKX_A2A_AI_PROVIDER: ${provider}; availability was not verified on PATH`
20431
+ };
20432
+ }
20433
+ const stored = options.store.getDefaultAiProvider();
20434
+ if (stored && (options.allowUnavailableProvider || isInstalled(stored, detection))) {
20435
+ return {
20436
+ provider: stored,
20437
+ source: "stored",
20438
+ message: isInstalled(stored, detection) ? `default AI provider is ${stored}` : `default AI provider is ${stored}; availability was not verified on PATH`
20439
+ };
20440
+ }
20441
+ if (stored) {
20442
+ options.stderr?.write(
20443
+ `[okx-agent-task] stored AI provider "${stored}" is not installed or not on PATH; selecting again.
20444
+ `
20445
+ );
20446
+ }
20447
+ if (options.promptIfMissing) {
20448
+ const provider = await promptForAiProvider({
20449
+ detection,
20450
+ stdin: options.stdin,
20451
+ stdout: options.stdout,
20452
+ currentProvider: stored ?? null
20453
+ });
20454
+ options.store.setDefaultAiProvider(provider);
20455
+ return {
20456
+ provider,
20457
+ source: "detected",
20458
+ message: `default AI provider set to ${provider}`
20459
+ };
20460
+ }
20461
+ const current = detectCurrentAiProvider(env);
20462
+ if (current && (options.allowUnavailableProvider || isInstalled(current, detection))) {
20463
+ return {
20464
+ provider: current,
20465
+ source: "env",
20466
+ message: isInstalled(current, detection) ? `AI provider resolved from current environment: ${current}` : `AI provider resolved from current environment: ${current}; availability was not verified on PATH`
20467
+ };
20468
+ }
20469
+ if (detection.available.length === 0) {
20470
+ throw new Error(
20471
+ `No supported AI CLI found. Install one of: ${AI_PROVIDERS.join(", ")}.`
20472
+ );
20473
+ }
20474
+ if (detection.available.length === 1) {
20475
+ const provider = detection.available[0];
20476
+ options.store.setDefaultAiProvider(provider);
20477
+ return {
20478
+ provider,
20479
+ source: "detected",
20480
+ message: `detected only ${provider}; default AI provider set to ${provider}`
20481
+ };
20482
+ }
20483
+ throw new Error(
20484
+ `Multiple AI CLIs are installed (${detection.available.join(", ")}) but no current AI environment was detected. Start the daemon from an AI session, pass --ai-provider <provider>, or set OKX_A2A_AI_PROVIDER.`
20485
+ );
20486
+ }
20487
+ function resolveConfiguredAiProvider(options) {
20488
+ const env = options.env ?? process.env;
20489
+ const explicit = env.OKX_AGENT_TASK_AI_CLI ?? env.OKX_A2A_AI_PROVIDER;
20490
+ if (explicit) {
20491
+ return normalizeAiProvider(explicit);
20492
+ }
20493
+ return options.store.getDefaultAiProvider();
20494
+ }
20495
+ function resolveConfiguredAiProviderForJob(options) {
20496
+ const jobId = normalizeOptionalJobId2(options.jobId);
20497
+ if (jobId) {
20498
+ const binding = options.store.getJobProviderBinding(jobId);
20499
+ if (binding) {
20500
+ bindHermesJobRouteFromEnvIfAvailable(options.store, {
20501
+ jobId,
20502
+ provider: binding.provider,
20503
+ env: options.env
20504
+ });
20505
+ return binding.provider;
20506
+ }
20507
+ }
20508
+ const provider = resolveConfiguredAiProvider({
20509
+ store: options.store,
20510
+ env: options.env
20511
+ });
20512
+ if (jobId && provider) {
20513
+ return bindJobProviderWithRouteIfAvailable(options.store, {
20514
+ jobId,
20515
+ provider,
20516
+ env: options.env
20517
+ }).binding.provider;
20518
+ }
20519
+ return provider;
20520
+ }
20521
+ function bindJobProviderToCurrentDefaultIfMissing(options) {
20522
+ const store = options.store ?? null;
20523
+ const jobId = normalizeOptionalJobId2(options.jobId);
20524
+ if (!store || !jobId) {
20525
+ return null;
20526
+ }
20527
+ const existing = store.getJobProviderBinding(jobId);
20528
+ if (existing) {
20529
+ bindHermesJobRouteFromEnvIfAvailable(store, {
20530
+ jobId,
20531
+ provider: existing.provider,
20532
+ env: options.env
20533
+ });
20534
+ return {
20535
+ provider: existing.provider,
20536
+ created: false
20537
+ };
20538
+ }
20539
+ const provider = store.getDefaultAiProvider();
20540
+ if (!provider) {
20541
+ return null;
20542
+ }
20543
+ const result = bindJobProviderWithRouteIfAvailable(store, {
20544
+ jobId,
20545
+ provider,
20546
+ env: options.env
20547
+ });
20548
+ return {
20549
+ provider: result.binding.provider,
20550
+ created: result.created
20551
+ };
20552
+ }
20553
+ function bindJobProviderToCurrentRuntimeIfMissing(options) {
20554
+ const store = options.store ?? null;
20555
+ const jobId = normalizeOptionalJobId2(options.jobId);
20556
+ if (!store || !jobId) {
20557
+ return {
20558
+ ok: false,
20559
+ jobId: jobId ?? "",
20560
+ provider: null,
20561
+ currentProvider: null,
20562
+ created: false,
20563
+ binding: null,
20564
+ reason: "missing_job_id"
20565
+ };
20566
+ }
20567
+ const existing = store.getJobProviderBinding(jobId);
20568
+ if (existing) {
20569
+ const binding = bindHermesJobRouteFromEnvIfAvailable(store, {
20570
+ jobId,
20571
+ provider: existing.provider,
20572
+ env: options.env
20573
+ }) ?? existing;
20574
+ return {
20575
+ ok: true,
20576
+ jobId,
20577
+ provider: binding.provider,
20578
+ currentProvider: null,
20579
+ created: false,
20580
+ binding,
20581
+ reason: "already_bound"
20582
+ };
20583
+ }
20584
+ const switched = switchProvider({ store, env: options.env ?? process.env });
20585
+ if (!switched.ok || !switched.provider) {
20586
+ return {
20587
+ ok: false,
20588
+ jobId,
20589
+ provider: null,
20590
+ currentProvider: null,
20591
+ created: false,
20592
+ binding: null,
20593
+ reason: "unknown_runtime"
20594
+ };
20595
+ }
20596
+ const currentProvider = switched.provider;
20597
+ const result = bindJobProviderWithRouteIfAvailable(store, {
20598
+ jobId,
20599
+ provider: currentProvider,
20600
+ env: options.env
20601
+ });
20602
+ return {
20603
+ ok: true,
20604
+ jobId,
20605
+ provider: result.binding.provider,
20606
+ currentProvider,
20607
+ created: result.created,
20608
+ binding: result.binding,
20609
+ reason: result.created ? "created" : "already_bound"
20610
+ };
20611
+ }
20612
+ async function ensureProviderReadyForBinding(options) {
20613
+ if (options.provider !== "codex") {
20614
+ return notRequiredReadinessGate(options.provider);
20615
+ }
20616
+ const check = options.readiness ?? checkCodexReadiness;
20617
+ const result = await check({
20618
+ store: options.store ?? null,
20619
+ env: options.env ?? process.env,
20620
+ platform: options.platform,
20621
+ persistOverrideCommand: options.persistOverrideCommand
20622
+ });
20623
+ return {
20624
+ provider: "codex",
20625
+ ready: result.ready,
20626
+ state: result.state,
20627
+ commandSource: result.commandSource,
20628
+ commandPersisted: result.commandPersisted,
20629
+ recoveryAction: result.recoveryAction,
20630
+ recoveryGuidance: result.recoveryGuidance
20631
+ };
20632
+ }
20633
+ async function switchProviderWithReadinessGateCore(options) {
20634
+ const env = options.env ?? process.env;
20635
+ const runtime = detectRuntime(env, {
20636
+ parentPid: options.parentPid,
20637
+ readParentProcessCommand: options.readParentProcessCommand
20638
+ });
20639
+ const delegate = () => switchProvider({
20640
+ store: options.store,
20641
+ env,
20642
+ parentPid: options.parentPid,
20643
+ readParentProcessCommand: options.readParentProcessCommand
20644
+ });
20645
+ if (runtime !== "codex") {
20646
+ const result2 = delegate();
20647
+ return {
20648
+ result: result2.ok ? result2 : { ...result2, reasonCode: "runtime_undetectable" },
20649
+ runtime,
20650
+ readiness: null
20651
+ };
20652
+ }
20653
+ const readiness = await ensureProviderReadyForBinding({
20654
+ provider: "codex",
20655
+ store: options.store,
20656
+ env,
20657
+ platform: options.platform,
20658
+ readiness: options.readiness
20659
+ });
20660
+ if (!readiness.ready) {
20661
+ const previousProvider = options.store.getDefaultAiProvider();
20662
+ const keepingClause = previousProvider ? `keeping ${formatAiProviderDisplayName(previousProvider)} as the default provider` : "the default provider was not changed";
20663
+ return {
20664
+ result: {
20665
+ ok: false,
20666
+ runtime,
20667
+ reason: "provider_not_ready",
20668
+ reasonCode: "provider_not_ready",
20669
+ state: "blocked",
20670
+ message: `Codex is not ready (${readiness.state}); the default AI provider was not changed.`,
20671
+ userMessage: `Codex is not ready (${readiness.state}); ${keepingClause}. ${readiness.recoveryGuidance}`,
20672
+ previousProvider,
20673
+ detectedProvider: "codex",
20674
+ effectiveProvider: previousProvider,
20675
+ readinessState: readiness.state,
20676
+ commandSource: readiness.commandSource,
20677
+ recoveryAction: readiness.recoveryAction,
20678
+ recoveryGuidance: readiness.recoveryGuidance
20679
+ },
20680
+ runtime,
20681
+ readiness
20682
+ };
20683
+ }
20684
+ const result = delegate();
20685
+ if (!result.ok) {
20686
+ return { result, runtime, readiness };
20687
+ }
20688
+ return {
20689
+ result: {
20690
+ ...result,
20691
+ detectedProvider: "codex",
20692
+ effectiveProvider: result.provider,
20693
+ readinessState: readiness.state,
20694
+ commandSource: readiness.commandSource,
20695
+ commandPersisted: readiness.commandPersisted
20696
+ },
20697
+ runtime,
20698
+ readiness
20699
+ };
20700
+ }
20701
+ function classifyJobBindingSource(env = process.env, options = {}) {
20702
+ if (env.OKX_AGENT_TASK_AI_CLI ?? env.OKX_A2A_AI_PROVIDER) {
20703
+ return "explicit_env";
20704
+ }
20705
+ return detectRuntime(env, options) === "unknown" ? "default_provider" : "current_runtime";
20706
+ }
20707
+ async function bindJobProviderToCurrentDefaultIfReady(options) {
20708
+ const store = options.store ?? null;
20709
+ const jobId = normalizeOptionalJobId2(options.jobId);
20710
+ if (!store || !jobId) {
20711
+ return null;
20712
+ }
20713
+ const existing = store.getJobProviderBinding(jobId);
20714
+ if (existing) {
20715
+ return alreadyBoundGateResult(store, jobId, existing.provider, options.env);
20716
+ }
20717
+ const provider = store.getDefaultAiProvider();
20718
+ if (!provider) {
20719
+ return null;
20720
+ }
20721
+ return await bindResolvedJobProviderIfReady({
20722
+ store,
20723
+ jobId,
20724
+ provider,
20725
+ bindingSource: "default_provider",
20726
+ env: options.env,
20727
+ platform: options.platform,
20728
+ readiness: options.readiness
20729
+ });
20730
+ }
20731
+ async function bindJobProviderToCurrentRuntimeIfReady(options) {
20732
+ const store = options.store ?? null;
20733
+ const jobId = normalizeOptionalJobId2(options.jobId);
20734
+ if (!store || !jobId) {
20735
+ return {
20736
+ ok: false,
20737
+ jobId: jobId ?? "",
20738
+ provider: null,
20739
+ currentProvider: null,
20740
+ created: false,
20741
+ degraded: false,
20742
+ bindingSource: null,
20743
+ readiness: null,
20744
+ reason: "missing_job_id"
20745
+ };
20746
+ }
20747
+ const existing = store.getJobProviderBinding(jobId);
20748
+ if (existing) {
20749
+ return alreadyBoundGateResult(store, jobId, existing.provider, options.env);
20750
+ }
20751
+ const env = options.env ?? process.env;
20752
+ const explicit = env.OKX_AGENT_TASK_AI_CLI ?? env.OKX_A2A_AI_PROVIDER;
20753
+ if (explicit) {
20754
+ return await bindResolvedJobProviderIfReady({
20755
+ store,
20756
+ jobId,
20757
+ provider: normalizeAiProvider(explicit),
20758
+ bindingSource: "explicit_env",
20759
+ env,
20760
+ platform: options.platform,
20761
+ readiness: options.readiness
20762
+ });
20763
+ }
20764
+ const switched = await switchProviderWithReadinessGateCore({
20765
+ store,
20766
+ env,
20767
+ platform: options.platform,
20768
+ readiness: options.readiness
20769
+ });
20770
+ if (!switched.result.ok) {
20771
+ const notReady = switched.result.reason === "provider_not_ready";
20772
+ if (switched.readiness) {
20773
+ logProviderReadinessChecked(jobId, switched.readiness);
20774
+ }
20775
+ return {
20776
+ ok: false,
20777
+ jobId,
20778
+ provider: null,
20779
+ currentProvider: switched.result.detectedProvider ?? null,
20780
+ created: false,
20781
+ degraded: notReady,
20782
+ bindingSource: null,
20783
+ readiness: switched.readiness,
20784
+ reason: notReady ? "provider_not_ready" : "unknown_runtime"
20785
+ };
20786
+ }
20787
+ return await bindResolvedJobProviderIfReady({
20788
+ store,
20789
+ jobId,
20790
+ provider: switched.result.provider,
20791
+ bindingSource: switched.runtime === "unknown" ? "default_provider" : "current_runtime",
20792
+ env,
20793
+ platform: options.platform,
20794
+ readiness: options.readiness,
20795
+ // Reuse the gate the switch already evaluated instead of probing twice.
20796
+ gate: switched.readiness
20797
+ });
20798
+ }
20799
+ function persistDefaultProviderIfChanged(store, provider) {
20800
+ if (store.getDefaultAiProvider() === provider) {
20801
+ return false;
20802
+ }
20803
+ store.setDefaultAiProvider(provider);
20804
+ return true;
20805
+ }
20806
+ function resolveNewJobProviderCandidate(store, env) {
20807
+ const explicit = env.OKX_AGENT_TASK_AI_CLI ?? env.OKX_A2A_AI_PROVIDER;
20808
+ if (explicit) {
20809
+ return { provider: normalizeAiProvider(explicit), bindingSource: "explicit_env" };
20810
+ }
20811
+ const stored = store.getDefaultAiProvider();
20812
+ return stored ? { provider: stored, bindingSource: "default_provider" } : null;
20813
+ }
20814
+ async function bindNewJobProviderIfReady(options) {
20815
+ const store = options.store ?? null;
20816
+ const jobId = normalizeOptionalJobId2(options.jobId);
20817
+ if (!store || !jobId) {
20818
+ return null;
20819
+ }
20820
+ const existing = store.getJobProviderBinding(jobId);
20821
+ if (existing) {
20822
+ return alreadyBoundGateResult(store, jobId, existing.provider, options.env);
20823
+ }
20824
+ const env = options.env ?? process.env;
20825
+ const candidate = resolveNewJobProviderCandidate(store, env);
20826
+ if (!candidate) {
20827
+ return null;
20828
+ }
20829
+ return await bindResolvedJobProviderIfReady({
20830
+ store,
20831
+ jobId,
20832
+ provider: candidate.provider,
20833
+ bindingSource: candidate.bindingSource,
20834
+ env,
20835
+ platform: options.platform,
20836
+ readiness: options.readiness
20837
+ });
20838
+ }
20839
+ async function persistCurrentRuntimeSelectionForNewJob(options) {
20840
+ const store = options.store ?? null;
20841
+ if (!store) {
20842
+ return runtimeSelectionResult({ reason: "no_provider", ok: false });
20843
+ }
20844
+ const env = options.env ?? process.env;
20845
+ const jobId = normalizeOptionalJobId2(options.jobId);
20846
+ const previousProvider = store.getDefaultAiProvider();
20847
+ if (jobId && store.getJobProviderBinding(jobId)) {
20848
+ return runtimeSelectionResult({ reason: "already_bound", ok: true, previousProvider });
20849
+ }
20850
+ const explicit = env.OKX_AGENT_TASK_AI_CLI ?? env.OKX_A2A_AI_PROVIDER;
20851
+ const explicitProvider = explicit ? normalizeAiProvider(explicit) : null;
20852
+ let candidate = null;
20853
+ let bindingSource = null;
20854
+ if (explicitProvider) {
20855
+ candidate = explicitProvider;
20856
+ bindingSource = "explicit_env";
20857
+ } else {
20858
+ const runtime = detectRuntime(env, {
20859
+ parentPid: options.parentPid,
20860
+ readParentProcessCommand: options.readParentProcessCommand
20861
+ });
20862
+ if (runtime !== "unknown") {
20863
+ candidate = runtime;
20864
+ bindingSource = "current_runtime";
20865
+ } else if (previousProvider === "codex") {
20866
+ candidate = "codex";
20867
+ bindingSource = "default_provider";
20868
+ }
20869
+ }
20870
+ if (!candidate) {
20871
+ return runtimeSelectionResult({ reason: "runtime_undetectable", ok: true, previousProvider });
20872
+ }
20873
+ if (candidate !== "codex") {
20874
+ const changed2 = persistDefaultProviderIfChanged(store, candidate);
20875
+ return runtimeSelectionResult({
20876
+ reason: changed2 ? "persisted" : "unchanged",
20877
+ ok: true,
20878
+ provider: candidate,
20879
+ previousProvider,
20880
+ changed: changed2,
20881
+ bindingSource
20882
+ });
20883
+ }
20884
+ const readiness = await ensureProviderReadyForBinding({
20885
+ provider: "codex",
20886
+ store,
20887
+ env,
20888
+ platform: options.platform,
20889
+ readiness: options.readiness,
20890
+ // A validated override must be persisted here: this is exactly the case
20891
+ // where it has to outlive the current process.
20892
+ persistOverrideCommand: true
20893
+ });
20894
+ if (!readiness.ready) {
20895
+ return runtimeSelectionResult({
20896
+ reason: "provider_not_ready",
20897
+ ok: false,
20898
+ previousProvider,
20899
+ bindingSource,
20900
+ readiness,
20901
+ userMessage: `Codex is not ready (${readiness.state}); the default AI provider was not changed. ${readiness.recoveryGuidance}`.trim()
20902
+ });
20903
+ }
20904
+ const commandDurable = readiness.commandPersisted || codexCommandResolvesWithoutOverride(
20905
+ env,
20906
+ store.getAiProviderCommand("codex"),
20907
+ options.platform ?? process.platform
20908
+ );
20909
+ if (!commandDurable) {
20910
+ return runtimeSelectionResult({
20911
+ reason: "command_not_durable",
20912
+ ok: false,
20913
+ previousProvider,
20914
+ bindingSource,
20915
+ readiness,
20916
+ userMessage: "Codex is ready in this shell but only through OKX_A2A_AI_CODEX_COMMAND, which the background daemon does not inherit. The default AI provider was not changed. Run `okx-a2a setup --json` to persist a Codex command the daemon can resolve."
20917
+ });
20918
+ }
20919
+ const changed = persistDefaultProviderIfChanged(store, "codex");
20920
+ return runtimeSelectionResult({
20921
+ reason: changed ? "persisted" : "unchanged",
20922
+ ok: true,
20923
+ provider: "codex",
20924
+ previousProvider,
20925
+ changed,
20926
+ bindingSource,
20927
+ readiness,
20928
+ commandDurable: true
20929
+ });
20930
+ }
20931
+ async function prepareAndBindCurrentRuntimeForNewJob(options) {
20932
+ const store = options.store ?? null;
20933
+ const jobId = normalizeOptionalJobId2(options.jobId);
20934
+ if (!store || !jobId) {
20935
+ return {
20936
+ ok: false,
20937
+ jobId: jobId ?? "",
20938
+ provider: null,
20939
+ currentProvider: null,
20940
+ created: false,
20941
+ degraded: false,
20942
+ bindingSource: null,
20943
+ readiness: null,
20944
+ reason: "missing_job_id"
20945
+ };
20946
+ }
20947
+ const existing = store.getJobProviderBinding(jobId);
20948
+ if (existing) {
20949
+ return alreadyBoundGateResult(store, jobId, existing.provider, options.env);
20950
+ }
20951
+ const selection = await persistCurrentRuntimeSelectionForNewJob({
20952
+ store,
20953
+ jobId,
20954
+ env: options.env,
20955
+ platform: options.platform,
20956
+ readiness: options.readiness,
20957
+ parentPid: options.parentPid,
20958
+ readParentProcessCommand: options.readParentProcessCommand
20959
+ });
20960
+ if (selection.reason === "already_bound") {
20961
+ const raced = store.getJobProviderBinding(jobId);
20962
+ if (raced) {
20963
+ return alreadyBoundGateResult(store, jobId, raced.provider, options.env);
20964
+ }
20965
+ }
20966
+ if (!selection.ok) {
20967
+ return {
20968
+ ok: false,
20969
+ jobId,
20970
+ provider: null,
20971
+ currentProvider: selection.previousProvider,
20972
+ created: false,
20973
+ degraded: true,
20974
+ bindingSource: null,
20975
+ readiness: selection.readiness,
20976
+ reason: "provider_not_ready"
20977
+ };
20978
+ }
20979
+ if (!selection.provider) {
20980
+ return {
20981
+ ok: false,
20982
+ jobId,
20983
+ provider: null,
20984
+ currentProvider: selection.previousProvider,
20985
+ created: false,
20986
+ degraded: false,
20987
+ bindingSource: null,
20988
+ readiness: selection.readiness,
20989
+ reason: "unknown_runtime"
20990
+ };
20991
+ }
20992
+ return await bindResolvedJobProviderIfReady({
20993
+ store,
20994
+ jobId,
20995
+ provider: normalizeAiProvider(selection.provider),
20996
+ bindingSource: selection.bindingSource ?? "current_runtime",
20997
+ env: options.env,
20998
+ platform: options.platform,
20999
+ readiness: options.readiness,
21000
+ gate: selection.readiness
21001
+ });
21002
+ }
21003
+ function runtimeSelectionResult(input) {
21004
+ return {
21005
+ ok: input.ok,
21006
+ reason: input.reason,
21007
+ provider: input.provider ?? null,
21008
+ previousProvider: input.previousProvider ?? null,
21009
+ changed: input.changed ?? false,
21010
+ bindingSource: input.bindingSource ?? null,
21011
+ readiness: input.readiness ?? null,
21012
+ commandDurable: input.commandDurable ?? false,
21013
+ userMessage: input.userMessage ?? ""
21014
+ };
21015
+ }
21016
+ function notRequiredReadinessGate(provider) {
21017
+ return {
21018
+ provider,
21019
+ ready: true,
21020
+ state: "not_required",
21021
+ commandSource: null,
21022
+ commandPersisted: false,
21023
+ recoveryAction: "none",
21024
+ recoveryGuidance: ""
21025
+ };
21026
+ }
21027
+ function alreadyBoundGateResult(store, jobId, provider, env) {
21028
+ bindHermesJobRouteFromEnvIfAvailable(store, { jobId, provider, env });
21029
+ return {
21030
+ ok: true,
21031
+ jobId,
21032
+ provider,
21033
+ currentProvider: null,
21034
+ created: false,
21035
+ degraded: false,
21036
+ bindingSource: "existing_binding",
21037
+ readiness: null,
21038
+ reason: "already_bound"
21039
+ };
21040
+ }
21041
+ async function bindResolvedJobProviderIfReady(input) {
21042
+ const gate = input.gate ?? await ensureProviderReadyForBinding({
21043
+ provider: input.provider,
21044
+ store: input.store,
21045
+ env: input.env,
21046
+ platform: input.platform,
21047
+ readiness: input.readiness
21048
+ });
21049
+ logProviderReadinessChecked(input.jobId, gate);
21050
+ if (!gate.ready) {
21051
+ return {
21052
+ ok: false,
21053
+ jobId: input.jobId,
21054
+ provider: null,
21055
+ currentProvider: input.provider,
21056
+ created: false,
21057
+ degraded: true,
21058
+ bindingSource: null,
21059
+ readiness: gate,
21060
+ reason: "provider_not_ready"
21061
+ };
21062
+ }
21063
+ const result = bindJobProviderWithRouteIfAvailable(input.store, {
21064
+ jobId: input.jobId,
21065
+ provider: input.provider,
21066
+ env: input.env,
21067
+ // The gate above just returned ready for this exact provider.
21068
+ readinessVerified: true
21069
+ });
21070
+ const created = result.created;
21071
+ if (created) {
21072
+ logJobProviderBound(input.jobId, result.binding.provider, input.bindingSource, gate);
21073
+ }
21074
+ return {
21075
+ ok: true,
21076
+ jobId: input.jobId,
21077
+ provider: result.binding.provider,
21078
+ currentProvider: input.provider,
21079
+ created,
21080
+ degraded: false,
21081
+ // A concurrent writer won the INSERT: report the row as pre-existing rather
21082
+ // than claiming this call's candidate tier produced it.
21083
+ bindingSource: created ? input.bindingSource : "existing_binding",
21084
+ readiness: gate,
21085
+ reason: created ? "created" : "already_bound"
21086
+ };
21087
+ }
21088
+ function logProviderReadinessChecked(jobId, gate) {
21089
+ if (gate.state === "not_required") {
21090
+ return;
21091
+ }
21092
+ logger.info(LogEvent.PROVIDER_READINESS_CHECKED, {
21093
+ component: JOB_PROVIDER_BINDING_COMPONENT,
21094
+ jobId,
21095
+ provider: gate.provider,
21096
+ readinessState: gate.state,
21097
+ commandSource: gate.commandSource ?? "",
21098
+ commandPersisted: String(gate.commandPersisted),
21099
+ recoveryAction: gate.recoveryAction,
21100
+ ...logFieldExtras({
21101
+ checkpoint: gate.ready ? "provider_ready" : "provider_not_ready",
21102
+ outcome: gate.ready ? "success" : "failed",
21103
+ transport: "local",
21104
+ eventFamily: "dispatch"
21105
+ })
21106
+ });
21107
+ }
21108
+ function logJobProviderBound(jobId, provider, bindingSource, gate) {
21109
+ logger.info(LogEvent.JOB_PROVIDER_BOUND, {
21110
+ component: JOB_PROVIDER_BINDING_COMPONENT,
21111
+ jobId,
21112
+ provider,
21113
+ bindingSource,
21114
+ readinessState: gate.state,
21115
+ commandSource: gate.commandSource ?? "",
21116
+ ...logFieldExtras({
21117
+ checkpoint: "job_provider_bound",
21118
+ outcome: "success",
21119
+ transport: "local",
21120
+ eventFamily: "dispatch"
21121
+ })
21122
+ });
21123
+ }
21124
+ function resolveAiProviderForDispatch(options) {
21125
+ const env = options.env ?? process.env;
21126
+ const jobId = normalizeOptionalJobId2(options.jobId);
21127
+ return resolveDispatchProvider(
21128
+ options,
21129
+ (provider) => bindDispatchProviderIfNeeded(options.store, jobId, env, provider)
21130
+ );
21131
+ }
21132
+ async function resolveAiProviderForDispatchWithReadiness(options) {
21133
+ const env = options.env ?? process.env;
21134
+ const jobId = normalizeOptionalJobId2(options.jobId);
21135
+ if (!jobId || options.store.getJobProviderBinding(jobId)) {
21136
+ return resolveAiProviderForDispatch(options);
21137
+ }
21138
+ const candidate = resolveDispatchProvider(options, (provider) => provider);
21139
+ if (candidate !== "codex") {
21140
+ return resolveAiProviderForDispatch(options);
21141
+ }
21142
+ let gate = options.readiness ? await ensureProviderReadyForBinding({
21143
+ provider: candidate,
21144
+ store: options.store,
21145
+ env,
21146
+ platform: options.platform,
21147
+ readiness: options.readiness
21148
+ }) : cachedProviderReadinessGate(candidate, env);
21149
+ if (!gate) {
21150
+ gate = await ensureProviderReadyForBinding({
21151
+ provider: candidate,
21152
+ store: options.store,
21153
+ env,
21154
+ platform: options.platform
21155
+ });
21156
+ }
21157
+ logProviderReadinessChecked(jobId, gate);
21158
+ if (!gate.ready) {
21159
+ throw new ProviderNotReadyError(candidate, gate);
21160
+ }
21161
+ return resolveDispatchProvider(
21162
+ options,
21163
+ (provider) => bindDispatchProviderIfNeeded(options.store, jobId, env, provider, true)
21164
+ );
21165
+ }
21166
+ function cachedProviderReadinessGate(provider, env) {
21167
+ if (provider !== "codex") {
21168
+ return notRequiredReadinessGate(provider);
21169
+ }
21170
+ const cached = readCachedCodexReadiness({ env });
21171
+ return cached ? readinessGateFromResult(cached) : null;
21172
+ }
21173
+ function assertCodexBindingPermitted(store, input) {
21174
+ if (input.provider !== "codex" || input.readinessVerified) {
21175
+ return;
21176
+ }
21177
+ if (store.getJobProviderBinding?.(input.jobId)) {
21178
+ return;
21179
+ }
21180
+ const cached = readCachedCodexReadiness({ env: input.env ?? process.env });
21181
+ if (cached?.ready) {
21182
+ return;
21183
+ }
21184
+ throw new ProviderNotReadyError(
21185
+ input.provider,
21186
+ cached ? readinessGateFromResult(cached) : unverifiedCodexReadinessGate()
21187
+ );
21188
+ }
21189
+ function unverifiedCodexReadinessGate() {
21190
+ return {
21191
+ provider: "codex",
21192
+ ready: false,
21193
+ state: "probe_failed",
21194
+ commandSource: null,
21195
+ commandPersisted: false,
21196
+ recoveryAction: "run_setup",
21197
+ recoveryGuidance: "Codex readiness has not been verified for this job, so the binding was refused rather than pinned to a provider that may not be able to run. Run `okx-a2a setup --json` to verify Codex."
21198
+ };
21199
+ }
21200
+ function readinessGateFromResult(result) {
21201
+ return {
21202
+ provider: result.provider,
21203
+ ready: result.ready,
21204
+ state: result.state,
21205
+ commandSource: result.commandSource,
21206
+ commandPersisted: result.commandPersisted,
21207
+ recoveryAction: result.recoveryAction,
21208
+ recoveryGuidance: result.recoveryGuidance
21209
+ };
21210
+ }
21211
+ function resolveDispatchProvider(options, bind) {
21212
+ const env = options.env ?? process.env;
21213
+ const commandExists2 = options.commandExists ?? commandExists;
21214
+ const detection = withStoredCodexAvailability(
21215
+ detectAiProviders(commandExists2, env),
21216
+ options.store,
21217
+ commandExists2,
21218
+ env
21219
+ );
21220
+ const jobId = normalizeOptionalJobId2(options.jobId);
21221
+ if (jobId) {
21222
+ const binding = options.store.getJobProviderBinding(jobId);
21223
+ if (binding) {
21224
+ assertInstalled(binding.provider, detection);
21225
+ return binding.provider;
21226
+ }
21227
+ }
21228
+ if (env.OKX_AGENT_TASK_AI_CLI) {
21229
+ const provider = normalizeAiProvider(env.OKX_AGENT_TASK_AI_CLI);
21230
+ assertInstalled(provider, detection);
21231
+ return bind(provider);
21232
+ }
21233
+ if (env.OKX_A2A_AI_PROVIDER) {
21234
+ const provider = normalizeAiProvider(env.OKX_A2A_AI_PROVIDER);
21235
+ assertInstalled(provider, detection);
21236
+ return bind(provider);
21237
+ }
21238
+ if (jobId) {
21239
+ const stored2 = options.store.getDefaultAiProvider();
21240
+ if (stored2) {
21241
+ assertInstalled(stored2, detection);
21242
+ return bind(stored2);
21243
+ }
21244
+ }
21245
+ const current = detectCurrentAiProvider(env);
21246
+ if (current && isInstalled(current, detection)) {
21247
+ return bind(current);
21248
+ }
21249
+ if (options.sessionKey) {
21250
+ const existing = options.store.listAiSessions(options.sessionKey).map((session) => session.provider).filter((provider) => isInstalled(provider, detection));
21251
+ const unique = [...new Set(existing)];
21252
+ if (unique.length === 1) {
21253
+ return bind(unique[0]);
21254
+ }
21255
+ }
21256
+ const stored = options.store.getDefaultAiProvider();
21257
+ if (stored) {
21258
+ assertInstalled(stored, detection);
21259
+ return bind(stored);
21260
+ }
21261
+ if (detection.available.length === 1) {
21262
+ return bind(detection.available[0]);
21263
+ }
21264
+ if (detection.available.length > 1) {
21265
+ throw new Error(
21266
+ `Multiple AI CLIs are installed (${detection.available.join(", ")}) but no current AI environment was detected. Set OKX_A2A_AI_PROVIDER or pass --provider/--ai-provider for this run.`
21267
+ );
21268
+ }
21269
+ throw new Error(`No supported AI CLI found. Install one of: ${AI_PROVIDERS.join(", ")}.`);
21270
+ }
21271
+ function bindDispatchProviderIfNeeded(store, jobId, env, provider, readinessVerified = false) {
21272
+ if (!jobId) {
21273
+ return provider;
21274
+ }
21275
+ return bindJobProviderWithRouteIfAvailable(store, { jobId, provider, env, readinessVerified }).binding.provider;
21276
+ }
21277
+ function withStoredCodexAvailability(detection, store, commandExists2, env = process.env, platform = process.platform) {
21278
+ if (detection.codex) {
21279
+ return detection;
21280
+ }
21281
+ const command = store.getAiProviderCommand("codex");
21282
+ if (command && commandExists2(command)) {
21283
+ return withCodexAvailable(detection);
21284
+ }
21285
+ for (const candidate of collectCodexAppBundledCandidates(env, platform)) {
21286
+ if (commandExists2(candidate)) {
21287
+ return withCodexAvailable(detection);
21288
+ }
21289
+ }
21290
+ return detection;
21291
+ }
21292
+ function withCodexAvailable(detection) {
21293
+ return {
21294
+ ...detection,
21295
+ codex: true,
21296
+ available: detection.available.includes("codex") ? detection.available : ["codex", ...detection.available]
21297
+ };
21298
+ }
21299
+ function bindJobProviderWithRouteIfAvailable(store, input) {
21300
+ assertCodexBindingPermitted(store, input);
21301
+ const result = store.bindJobProviderIfMissing({
21302
+ jobId: input.jobId,
21303
+ provider: input.provider
21304
+ });
21305
+ const routed = bindHermesJobRouteFromEnvIfAvailable(store, {
21306
+ jobId: input.jobId,
21307
+ provider: result.binding.provider,
21308
+ env: input.env
21309
+ });
21310
+ return routed ? { ...result, binding: routed } : result;
21311
+ }
21312
+ function bindHermesJobRouteFromEnvIfAvailable(store, input) {
21313
+ if (input.provider !== "hermes" || typeof store.upsertJobGatewayRoute !== "function") {
21314
+ return null;
21315
+ }
21316
+ const route = currentHermesGatewayRouteFromEnv(input.env ?? process.env);
21317
+ if (!route) {
21318
+ return null;
21319
+ }
21320
+ return store.upsertJobGatewayRoute({
21321
+ jobId: input.jobId,
21322
+ provider: "hermes",
21323
+ route
21324
+ });
21325
+ }
21326
+ function currentHermesGatewayRouteFromEnv(env) {
21327
+ const platform = normalizeOptionalText(env.OKX_A2A_CURRENT_GATEWAY_PLATFORM);
21328
+ const chatId = normalizeOptionalText(env.OKX_A2A_CURRENT_GATEWAY_CHAT_ID);
21329
+ const envGatewaySessionKey = normalizeOptionalText(env.OKX_A2A_CURRENT_GATEWAY_SESSION_KEY);
21330
+ const parsedGatewaySession = parseHermesGatewaySessionKey(envGatewaySessionKey);
21331
+ if (platform && chatId) {
21332
+ const threadId = normalizeOptionalText(env.OKX_A2A_CURRENT_GATEWAY_THREAD_ID) ?? matchingParsedThreadId(parsedGatewaySession, { platform, chatId });
21333
+ return buildHermesGatewayRoute({
21334
+ platform,
21335
+ chatId,
21336
+ threadId,
21337
+ sessionKey: envGatewaySessionKey
21338
+ });
21339
+ }
21340
+ const sessionRoute = routeFromHermesSessionKey(env.HERMES_SESSION_KEY);
21341
+ return sessionRoute ? buildHermesGatewayRoute(sessionRoute) : null;
21342
+ }
21343
+ function buildHermesGatewayRoute(input) {
21344
+ const threadId = normalizeOptionalText(input.threadId) ?? "";
21345
+ const sessionKey = normalizeOptionalText(input.sessionKey) ?? (threadId ? hermesThreadSessionKey(input.platform, input.chatId, threadId) : `agent:main:${input.platform}:dm:${encodeURIComponent(input.chatId)}`);
21346
+ return {
21347
+ platform: input.platform,
21348
+ chatId: input.chatId,
21349
+ chatName: "",
21350
+ chatType: threadId ? "thread" : "dm",
21351
+ threadId,
21352
+ userId: "",
21353
+ userName: "",
21354
+ sessionKey,
21355
+ gatewaySessionKey: sessionKey
21356
+ };
21357
+ }
21358
+ function hermesThreadSessionKey(platform, chatId, threadId) {
21359
+ const encodedChatId = encodeURIComponent(chatId);
21360
+ const encodedThreadId = encodeURIComponent(threadId);
21361
+ return platform === "telegram" ? `agent:main:${platform}:dm:${encodedChatId}:${encodedThreadId}` : `agent:main:${platform}:thread:${encodedChatId}:${encodedThreadId}`;
21362
+ }
21363
+ function routeFromHermesSessionKey(value) {
21364
+ const sessionKey = normalizeOptionalText(value);
21365
+ if (!sessionKey) {
21366
+ return null;
21367
+ }
21368
+ const parsed = parseHermesGatewaySessionKey(sessionKey);
21369
+ return parsed ? { ...parsed, sessionKey } : null;
21370
+ }
21371
+ function parseHermesGatewaySessionKey(sessionKey) {
21372
+ const normalized = normalizeOptionalText(sessionKey);
21373
+ if (!normalized) {
21374
+ return null;
21375
+ }
21376
+ const parts = normalized.startsWith("agent:") ? normalized.split(":").slice(2) : normalized.split(":");
21377
+ if (parts.length < 3) {
21378
+ return null;
21379
+ }
21380
+ const [platform, scope, ...rest] = parts;
21381
+ if (!platform || platform === "okx-a2a" || platform === "backup" || platform === "job") {
21382
+ return null;
21383
+ }
21384
+ if (scope === "dm" && rest[0]) {
21385
+ return {
21386
+ platform,
21387
+ chatId: safeDecodeURIComponent2(rest[0]),
21388
+ ...platform === "telegram" && rest[1] ? { threadId: safeDecodeURIComponent2(rest.slice(1).join(":")) } : {}
21389
+ };
21390
+ }
21391
+ if (scope === "thread" && rest[0] && rest[1]) {
21392
+ return {
21393
+ platform,
21394
+ chatId: safeDecodeURIComponent2(rest[0]),
21395
+ threadId: safeDecodeURIComponent2(rest.slice(1).join(":"))
21396
+ };
21397
+ }
21398
+ return null;
21399
+ }
21400
+ function matchingParsedThreadId(parsed, expected) {
21401
+ if (!parsed?.threadId) {
21402
+ return null;
21403
+ }
21404
+ return parsed.platform === expected.platform && parsed.chatId === expected.chatId ? parsed.threadId : null;
21405
+ }
21406
+ function safeDecodeURIComponent2(value) {
21407
+ try {
21408
+ return decodeURIComponent(value);
21409
+ } catch {
21410
+ return value;
21411
+ }
21412
+ }
21413
+ function normalizeOptionalJobId2(value) {
21414
+ const normalized = value?.trim();
21415
+ return normalized ? normalized : null;
21416
+ }
21417
+ function normalizeOptionalText(value) {
21418
+ const normalized = value?.trim();
21419
+ return normalized ? normalized : null;
21420
+ }
21421
+ function isInstalled(provider, detection) {
21422
+ return detection.available.includes(provider);
21423
+ }
21424
+ function assertInstalled(provider, detection) {
21425
+ if (!isInstalled(provider, detection)) {
21426
+ throw new Error(`AI provider "${provider}" is not installed or not on PATH.`);
21427
+ }
21428
+ }
21429
+ function readProviderCommand(provider, env) {
21430
+ return env[`OKX_A2A_AI_${provider.toUpperCase()}_COMMAND`] ?? provider;
21431
+ }
21432
+ async function promptForAiProvider(options) {
21433
+ const stdin = options.stdin ?? process.stdin;
21434
+ const stdout = options.stdout ?? process.stdout;
21435
+ if (!stdin.isTTY || !stdout.isTTY) {
21436
+ throw new Error(
21437
+ `No AI provider is configured. Run \`okx-a2a daemon start --ai-provider <${AI_PROVIDERS.join("|")}>\` or set OKX_A2A_AI_PROVIDER.`
21438
+ );
21439
+ }
21440
+ stdout.write("AI provider is required before starting okx-a2a.\n");
21441
+ stdout.write("Tip: if the selected AI provider is unavailable, task processing cannot proceed.\n\n");
21442
+ if (options.currentProvider) {
21443
+ stdout.write(`Current AI provider: ${options.currentProvider}
21444
+
21445
+ `);
21446
+ }
21447
+ if (options.detection.available.length === 0) {
21448
+ stdout.write("Supported AI providers:\n");
21449
+ AI_PROVIDERS.forEach((provider, index2) => {
21450
+ const status = isInstalled(provider, options.detection) ? "installed" : "not found on PATH";
21451
+ stdout.write(` ${index2 + 1}. ${provider} (${status})
21452
+ `);
21453
+ });
21454
+ stdout.write("\n");
21455
+ throw new Error(`No supported AI CLI found. Install one of: ${AI_PROVIDERS.join(", ")}.`);
21456
+ }
21457
+ if (typeof stdin.setRawMode === "function") {
21458
+ return promptForAiProviderWithArrows({
21459
+ detection: options.detection,
21460
+ stdin,
21461
+ stdout,
21462
+ currentProvider: options.currentProvider
21463
+ });
21464
+ }
21465
+ stdout.write("Supported AI providers:\n");
21466
+ AI_PROVIDERS.forEach((provider, index2) => {
21467
+ const status = isInstalled(provider, options.detection) ? "installed" : "not found on PATH";
21468
+ stdout.write(` ${index2 + 1}. ${provider} (${status})
21469
+ `);
21470
+ });
21471
+ stdout.write("\n");
21472
+ const rl = (0, import_promises6.createInterface)({ input: stdin, output: stdout });
21473
+ try {
21474
+ while (true) {
21475
+ const answer = (await rl.question("Choose an installed AI provider by number or name: ")).trim();
21476
+ const provider = parseProviderChoice(answer);
21477
+ if (!provider) {
21478
+ stdout.write(`Invalid choice. Use one of: ${AI_PROVIDERS.join(", ")}.
21479
+ `);
21480
+ continue;
21481
+ }
21482
+ if (!isInstalled(provider, options.detection)) {
21483
+ stdout.write(`"${provider}" is not installed or not on PATH. Choose an installed provider.
21484
+ `);
21485
+ continue;
20576
21486
  }
20577
- flush() {
20578
- while (this.buffer.length > 0) {
20579
- const entry = this.buffer.shift();
20580
- if (entry.level === "info") {
20581
- this.captureInfo(entry.message, entry.extra ?? {});
20582
- } else {
20583
- this.captureError(entry.message, entry.error, entry.extra ?? {});
20584
- }
20585
- }
21487
+ return provider;
21488
+ }
21489
+ } finally {
21490
+ rl.close();
21491
+ }
21492
+ }
21493
+ async function promptForAiProviderWithArrows(options) {
21494
+ let selectedIndex = AI_PROVIDERS.findIndex((provider) => provider === options.currentProvider && isInstalled(provider, options.detection));
21495
+ if (selectedIndex < 0) {
21496
+ selectedIndex = AI_PROVIDERS.findIndex((provider) => isInstalled(provider, options.detection));
21497
+ }
21498
+ if (selectedIndex < 0) {
21499
+ selectedIndex = 0;
21500
+ }
21501
+ let renderedLines = 0;
21502
+ const render = (message) => {
21503
+ if (renderedLines > 0) {
21504
+ (0, import_node_readline.moveCursor)(options.stdout, 0, -renderedLines);
21505
+ (0, import_node_readline.cursorTo)(options.stdout, 0);
21506
+ (0, import_node_readline.clearScreenDown)(options.stdout);
21507
+ }
21508
+ const lines = [
21509
+ "Use \u2191/\u2193 to move, Enter to confirm.",
21510
+ "",
21511
+ ...AI_PROVIDERS.map((provider, index2) => {
21512
+ const selected = index2 === selectedIndex ? "\u276F" : " ";
21513
+ const status = isInstalled(provider, options.detection) ? "installed" : "not found on PATH";
21514
+ const current = provider === options.currentProvider ? ", current" : "";
21515
+ return `${selected} ${provider} (${status}${current})`;
21516
+ }),
21517
+ ...message ? ["", message] : []
21518
+ ];
21519
+ options.stdout.write(`${lines.join("\n")}
21520
+ `);
21521
+ renderedLines = lines.length;
21522
+ };
21523
+ (0, import_node_readline.emitKeypressEvents)(options.stdin);
21524
+ options.stdin.setRawMode(true);
21525
+ options.stdin.resume();
21526
+ options.stdout.write("\x1B[?25l");
21527
+ render();
21528
+ return await new Promise((resolve11, reject) => {
21529
+ const cleanup = () => {
21530
+ options.stdin.off("keypress", onKeypress);
21531
+ options.stdin.setRawMode(false);
21532
+ options.stdin.pause();
21533
+ options.stdout.write("\x1B[?25h");
21534
+ };
21535
+ const clearRendered = () => {
21536
+ if (renderedLines > 0) {
21537
+ (0, import_node_readline.moveCursor)(options.stdout, 0, -renderedLines);
21538
+ (0, import_node_readline.cursorTo)(options.stdout, 0);
21539
+ (0, import_node_readline.clearScreenDown)(options.stdout);
20586
21540
  }
20587
- static sanitizeExtra(extra) {
20588
- return Object.fromEntries(
20589
- Object.entries(extra).filter(([, value]) => value !== void 0).filter(([key]) => !_SentryLogger.isBlockedExtraKey(key)).map(([key, value]) => [key, _SentryLogger.safeValueForKey(key, value)])
21541
+ };
21542
+ const finish = (provider) => {
21543
+ cleanup();
21544
+ clearRendered();
21545
+ options.stdout.write(`Selected AI provider: ${provider}
21546
+ `);
21547
+ resolve11(provider);
21548
+ };
21549
+ const onKeypress = (_str, key) => {
21550
+ if (key.ctrl && key.name === "c") {
21551
+ cleanup();
21552
+ clearRendered();
21553
+ options.stdout.write(
21554
+ options.currentProvider ? `AI provider selection cancelled. Keeping current provider: ${options.currentProvider}
21555
+ ` : "AI provider selection cancelled. No provider was configured.\n"
20590
21556
  );
21557
+ reject(new UserCancelledAiProviderSelectionError());
21558
+ return;
20591
21559
  }
20592
- static isBlockedExtraKey(key) {
20593
- const lower = key.toLowerCase();
20594
- const normalized = _SentryLogger.normalizeSensitiveKey(key);
20595
- if (SENTRY_EXTRA_BLOCKLIST.has(lower) || SENTRY_EXTRA_BLOCKLIST.has(normalized)) {
20596
- return true;
20597
- }
20598
- if (_SentryLogger.isSafeMetricKey(normalized)) {
20599
- return false;
20600
- }
20601
- return SENTRY_EXTRA_BLOCKED_KEY_PARTS.some((needle) => normalized.includes(needle));
20602
- }
20603
- static isSafeMetricKey(normalized) {
20604
- return /(bytes|count|length|ms|size)$/.test(normalized);
20605
- }
20606
- static safeValueForKey(key, value) {
20607
- if (typeof value === "string" && SENTRY_FULL_STRING_EXTRA_KEYS.has(_SentryLogger.normalizeSensitiveKey(key))) {
20608
- return value;
20609
- }
20610
- return _SentryLogger.safeValue(value);
20611
- }
20612
- static normalizeSensitiveKey(key) {
20613
- return key.toLowerCase().replace(/[^a-z0-9]/g, "");
20614
- }
20615
- static safeValue(value) {
20616
- if (typeof value === "string") {
20617
- return value.length <= MAX_EXTRA_STRING_LENGTH ? value : `${value.slice(0, MAX_EXTRA_STRING_LENGTH)}...`;
20618
- }
20619
- if (value === null || value === void 0 || typeof value === "number" || typeof value === "boolean") {
20620
- return value;
20621
- }
20622
- if (typeof value === "bigint") {
20623
- return value.toString();
20624
- }
20625
- if (value instanceof Error) {
20626
- return {
20627
- name: value.name,
20628
- messageLength: value.message.length
20629
- };
20630
- }
20631
- if (Array.isArray(value)) {
20632
- return value.slice(0, 20).map((item) => _SentryLogger.safeValue(item));
20633
- }
20634
- if (typeof value === "object") {
20635
- return Object.fromEntries(
20636
- Object.entries(value).slice(0, 50).filter(([, item]) => item !== void 0).filter(([key]) => !_SentryLogger.isBlockedExtraKey(key)).map(([key, item]) => [key, _SentryLogger.safeValueForKey(key, item)])
20637
- );
20638
- }
20639
- const text = String(value);
20640
- return text.length <= MAX_EXTRA_STRING_LENGTH ? text : `${text.slice(0, MAX_EXTRA_STRING_LENGTH)}...`;
20641
- }
20642
- static tagValue(value) {
20643
- const text = String(value);
20644
- return text.length <= MAX_TAG_VALUE_LENGTH ? text : `${text.slice(0, MAX_TAG_VALUE_LENGTH)}...`;
21560
+ if (key.name === "up") {
21561
+ selectedIndex = (selectedIndex + AI_PROVIDERS.length - 1) % AI_PROVIDERS.length;
21562
+ render();
21563
+ return;
20645
21564
  }
20646
- static normalizeTagKey(key) {
20647
- return key.replace(/[^A-Za-z0-9_.:-]+/g, "_").slice(0, 32) || "unknown";
21565
+ if (key.name === "down") {
21566
+ selectedIndex = (selectedIndex + 1) % AI_PROVIDERS.length;
21567
+ render();
21568
+ return;
20648
21569
  }
20649
- static applyDiagnostics(scope, eventName, extra) {
20650
- const enriched = { eventName, ...extra };
20651
- for (const [key, value] of Object.entries(enriched)) {
20652
- if (!SENTRY_TAG_KEYS.has(key)) {
20653
- continue;
20654
- }
20655
- scope.setTag(_SentryLogger.normalizeTagKey(key), _SentryLogger.tagValue(value));
20656
- }
20657
- const fingerprint = ["a2a", eventName];
20658
- for (const key of SENTRY_FINGERPRINT_KEYS) {
20659
- const value = enriched[key];
20660
- if (value) {
20661
- fingerprint.push(`${key}:${_SentryLogger.tagValue(value)}`);
20662
- }
21570
+ if (key.name === "return" || key.name === "enter") {
21571
+ const provider = AI_PROVIDERS[selectedIndex];
21572
+ if (!isInstalled(provider, options.detection)) {
21573
+ render(`"${provider}" is not installed or not on PATH. Choose an installed provider.`);
21574
+ return;
20663
21575
  }
20664
- scope.setFingerprint(fingerprint);
20665
- }
20666
- static toPascalCase(str) {
20667
- return str.split(/[\s:]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
20668
- }
20669
- static createSentryEvent(eventName) {
20670
- const err = new Error(eventName);
20671
- err.name = _SentryLogger.toPascalCase(eventName);
20672
- err.stack = `${err.name}: ${eventName}`;
20673
- return err;
21576
+ finish(provider);
20674
21577
  }
20675
21578
  };
20676
- logger = SentryLogger.getInstance();
20677
- initLogger = (config) => {
20678
- return logger.init(config);
21579
+ options.stdin.on("keypress", onKeypress);
21580
+ });
21581
+ }
21582
+ function parseProviderChoice(answer) {
21583
+ if (!answer) {
21584
+ return null;
21585
+ }
21586
+ const number = Number(answer);
21587
+ if (Number.isInteger(number) && number >= 1 && number <= AI_PROVIDERS.length) {
21588
+ return AI_PROVIDERS[number - 1];
21589
+ }
21590
+ try {
21591
+ return normalizeAiProvider(answer);
21592
+ } catch {
21593
+ return null;
21594
+ }
21595
+ }
21596
+ var import_node_child_process5, import_promises6, import_node_readline, AI_PROVIDERS, UserCancelledAiProviderSelectionError, JOB_PROVIDER_BINDING_COMPONENT, ProviderNotReadyError;
21597
+ var init_ai_provider = __esm({
21598
+ "src/ai-provider.ts"() {
21599
+ "use strict";
21600
+ import_node_child_process5 = require("node:child_process");
21601
+ import_promises6 = require("node:readline/promises");
21602
+ import_node_readline = require("node:readline");
21603
+ init_ai_command();
21604
+ init_codex_readiness();
21605
+ init_sentry_logger();
21606
+ init_log_fields();
21607
+ init_win_spawn();
21608
+ AI_PROVIDERS = ["codex", "claude", "hermes", "openclaw"];
21609
+ UserCancelledAiProviderSelectionError = class extends Error {
21610
+ constructor() {
21611
+ super("AI provider selection cancelled by user.");
21612
+ this.name = "UserCancelledAiProviderSelectionError";
21613
+ }
20679
21614
  };
20680
- shutdown = () => {
20681
- return logger.shutdown();
21615
+ JOB_PROVIDER_BINDING_COMPONENT = "node_job_provider_binding";
21616
+ ProviderNotReadyError = class extends Error {
21617
+ provider;
21618
+ readinessState;
21619
+ recoveryAction;
21620
+ commandSource;
21621
+ constructor(provider, gate) {
21622
+ super(`AI provider "${provider}" is not ready (${gate.state}). ${gate.recoveryGuidance}`.trim());
21623
+ this.name = "ProviderNotReadyError";
21624
+ this.provider = provider;
21625
+ this.readinessState = gate.state;
21626
+ this.recoveryAction = gate.recoveryAction;
21627
+ this.commandSource = gate.commandSource;
21628
+ }
20682
21629
  };
20683
- UNKNOWN_FIELD = "unknown";
20684
21630
  }
20685
21631
  });
20686
21632
 
@@ -31149,7 +32095,7 @@ var init_sentry_config = __esm({
31149
32095
  environment = process.env.SENTRY_ENV === "dev" ? "dev" : "prod";
31150
32096
  SENTRY_CONFIG = {
31151
32097
  projectName: "okx/openclaw-okx-a2a-extension",
31152
- release: "0.2.2",
32098
+ release: "0.2.3-beta-fa7df0d9e7-260810102542",
31153
32099
  environment,
31154
32100
  runtimeContainer: normalizeRuntimeContainer(process.env.OKX_A2A_RUNTIME_CONTAINER)
31155
32101
  };
@@ -31362,6 +32308,7 @@ var init_win_native_launcher = __esm({
31362
32308
  var update_cli_exports = {};
31363
32309
  __export(update_cli_exports, {
31364
32310
  assertHermesSetupSupportedOnPlatform: () => assertHermesSetupSupportedOnPlatform,
32311
+ authStatusFromReason: () => authStatusFromReason,
31365
32312
  buildExternalCommandInvocation: () => buildExternalCommandInvocation,
31366
32313
  buildNpmPackageSpec: () => buildNpmPackageSpec,
31367
32314
  buildProviderMismatchWarning: () => buildProviderMismatchWarning,
@@ -31881,7 +32828,7 @@ function authStatusFromReason(reason) {
31881
32828
  if (reason === "provider_cli_login_failed") {
31882
32829
  return "login_failed";
31883
32830
  }
31884
- if (reason === "provider_cli_login_timeout") {
32831
+ if (reason === "provider_cli_login_timeout" || reason === "provider_cli_auth_status_timeout") {
31885
32832
  return "login_timeout";
31886
32833
  }
31887
32834
  return "blocked";
@@ -31980,8 +32927,13 @@ function quoteUserFacingCommandPath(commandPath) {
31980
32927
  }
31981
32928
  return `"${commandPath}"`;
31982
32929
  }
31983
- async function checkClaudeCliAuthStatus(command) {
31984
- const result = await runCommandCaptureStatus(command, ["auth", "status", "--json"]);
32930
+ async function checkClaudeCliAuthStatus(command, options = {}) {
32931
+ const result = await runCommandCaptureStatus(command, ["auth", "status", "--json"], {
32932
+ timeoutMs: options.timeoutMs ?? PROVIDER_CLI_AUTH_PROBE_TIMEOUT_MS
32933
+ });
32934
+ if (result.errorCode === "ETIMEDOUT") {
32935
+ return { ok: false, reason: "provider_cli_auth_status_timeout", detail: result.detail };
32936
+ }
31985
32937
  if (result.errorCode === "ENOENT") {
31986
32938
  return { ok: false, reason: "provider_cli_missing", detail: result.detail };
31987
32939
  }
@@ -32000,8 +32952,13 @@ async function checkClaudeCliAuthStatus(command) {
32000
32952
  ${result.stdout}`.trim() };
32001
32953
  }
32002
32954
  }
32003
- async function checkCodexCliAuthStatus(command) {
32004
- const result = await runCommandCaptureStatus(command, ["login", "status"]);
32955
+ async function checkCodexCliAuthStatus(command, options = {}) {
32956
+ const result = await runCommandCaptureStatus(command, ["login", "status"], {
32957
+ timeoutMs: options.timeoutMs ?? PROVIDER_CLI_AUTH_PROBE_TIMEOUT_MS
32958
+ });
32959
+ if (result.errorCode === "ETIMEDOUT") {
32960
+ return { ok: false, reason: "provider_cli_auth_status_timeout", detail: result.detail };
32961
+ }
32005
32962
  if (result.errorCode === "ENOENT") {
32006
32963
  return { ok: false, reason: "provider_cli_missing", detail: result.detail };
32007
32964
  }
@@ -32079,7 +33036,7 @@ async function getCurrentNodeCliVersion() {
32079
33036
  return await getGlobalNpmPackageVersion(UPDATE_PACKAGES.node) ?? getBundledNodeCliVersion();
32080
33037
  }
32081
33038
  function getBundledNodeCliVersion() {
32082
- return true ? "0.2.2" : null;
33039
+ return true ? "0.2.3-beta-fa7df0d9e7-260810102542" : null;
32083
33040
  }
32084
33041
  function readConfiguredAiProvider() {
32085
33042
  const explicit = process.env.OKX_A2A_AI_PROVIDER || process.env.OKX_AGENT_TASK_AI_CLI;
@@ -32289,7 +33246,7 @@ async function updateHermes(release, options) {
32289
33246
  }
32290
33247
  }
32291
33248
  async function installGatewayPluginForDoctor(target) {
32292
- const release = isPrereleaseVersion("0.2.2") ? "beta" : "latest";
33249
+ const release = isPrereleaseVersion("0.2.3-beta-fa7df0d9e7-260810102542") ? "beta" : "latest";
32293
33250
  const insideTargetGateway = detectGatewayInvocation() === target;
32294
33251
  const options = {
32295
33252
  restart: !insideTargetGateway,
@@ -32970,7 +33927,7 @@ async function runCommandCaptureOptional(command, args) {
32970
33927
  });
32971
33928
  });
32972
33929
  }
32973
- async function runCommandCaptureStatus(command, args) {
33930
+ async function runCommandCaptureStatus(command, args, options = {}) {
32974
33931
  return await new Promise((resolvePromise) => {
32975
33932
  const invocation = buildExternalCommandInvocation(command, args);
32976
33933
  const child = (0, import_node_child_process8.spawn)(invocation.command, invocation.args, {
@@ -32979,6 +33936,18 @@ async function runCommandCaptureStatus(command, args) {
32979
33936
  });
32980
33937
  let stdout = "";
32981
33938
  let stderr = "";
33939
+ let settled = false;
33940
+ let timeout = null;
33941
+ const settle = (result) => {
33942
+ if (settled) {
33943
+ return;
33944
+ }
33945
+ settled = true;
33946
+ if (timeout) {
33947
+ clearTimeout(timeout);
33948
+ }
33949
+ resolvePromise(result);
33950
+ };
32982
33951
  child.stdout.setEncoding("utf8");
32983
33952
  child.stderr.setEncoding("utf8");
32984
33953
  child.stdout.on("data", (chunk) => {
@@ -32987,9 +33956,28 @@ async function runCommandCaptureStatus(command, args) {
32987
33956
  child.stderr.on("data", (chunk) => {
32988
33957
  stderr += chunk;
32989
33958
  });
33959
+ if (options.timeoutMs && options.timeoutMs > 0) {
33960
+ const timeoutMs = options.timeoutMs;
33961
+ timeout = setTimeout(() => {
33962
+ killProcessTree(child, "SIGTERM");
33963
+ setTimeout(() => {
33964
+ if (!child.killed || child.exitCode === null) {
33965
+ killProcessTree(child, "SIGKILL");
33966
+ }
33967
+ }, 5e3).unref();
33968
+ settle({
33969
+ code: null,
33970
+ stdout,
33971
+ stderr,
33972
+ detail: `timed out after ${timeoutMs}ms`,
33973
+ errorCode: "ETIMEDOUT"
33974
+ });
33975
+ }, timeoutMs);
33976
+ timeout.unref();
33977
+ }
32990
33978
  child.on("error", (error) => {
32991
33979
  const detail = error.message;
32992
- resolvePromise({
33980
+ settle({
32993
33981
  code: null,
32994
33982
  stdout,
32995
33983
  stderr,
@@ -33000,7 +33988,7 @@ async function runCommandCaptureStatus(command, args) {
33000
33988
  child.on("close", (code, signal) => {
33001
33989
  const signalDetail = signal ? `signal=${signal}` : "";
33002
33990
  const detail = [stderr.trim(), stdout.trim(), signalDetail].filter(Boolean).join("\n");
33003
- resolvePromise({
33991
+ settle({
33004
33992
  code,
33005
33993
  stdout,
33006
33994
  stderr,
@@ -33015,7 +34003,7 @@ function buildExternalCommandInvocation(command, args, platform = process.platfo
33015
34003
  function formatExternalCommand(command, args) {
33016
34004
  return [command, ...args].join(" ");
33017
34005
  }
33018
- var import_node_child_process8, import_node_fs22, import_promises11, import_node_os11, import_node_path28, UPDATE_PACKAGES, CODEX_NPM_PACKAGE, OPENCLAW_FORCE_INSTALL_FLAG, OPENCLAW_UNSAFE_INSTALL_FLAG, OKX_A2A_PLUGIN_ID, OPENCLAW_SESSION_DM_SCOPE_CONFIG_PATH, OPENCLAW_SESSION_DM_SCOPE_CONFIG_VALUE, OPENCLAW_PLUGIN_ALLOW_CONFIG_PATH, OPENCLAW_OKX_A2A_CONVERSATION_HOOK_ACCESS_CONFIG_PATH, DEFAULT_AI_PROVIDER_LOGIN_TIMEOUT_MS, SetupBlockedError, redirectCommandStdoutToStderr;
34006
+ var import_node_child_process8, import_node_fs22, import_promises11, import_node_os11, import_node_path28, UPDATE_PACKAGES, CODEX_NPM_PACKAGE, OPENCLAW_FORCE_INSTALL_FLAG, OPENCLAW_UNSAFE_INSTALL_FLAG, OKX_A2A_PLUGIN_ID, OPENCLAW_SESSION_DM_SCOPE_CONFIG_PATH, OPENCLAW_SESSION_DM_SCOPE_CONFIG_VALUE, OPENCLAW_PLUGIN_ALLOW_CONFIG_PATH, OPENCLAW_OKX_A2A_CONVERSATION_HOOK_ACCESS_CONFIG_PATH, DEFAULT_AI_PROVIDER_LOGIN_TIMEOUT_MS, PROVIDER_CLI_AUTH_PROBE_TIMEOUT_MS, SetupBlockedError, redirectCommandStdoutToStderr;
33019
34007
  var init_update_cli = __esm({
33020
34008
  "src/update-cli.ts"() {
33021
34009
  "use strict";
@@ -33042,6 +34030,7 @@ var init_update_cli = __esm({
33042
34030
  OPENCLAW_PLUGIN_ALLOW_CONFIG_PATH = "plugins.allow";
33043
34031
  OPENCLAW_OKX_A2A_CONVERSATION_HOOK_ACCESS_CONFIG_PATH = "plugins.entries.okx-a2a.hooks.allowConversationAccess";
33044
34032
  DEFAULT_AI_PROVIDER_LOGIN_TIMEOUT_MS = 3 * 60 * 1e3;
34033
+ PROVIDER_CLI_AUTH_PROBE_TIMEOUT_MS = CODEX_PROBE_TIMEOUT_MS;
33045
34034
  SetupBlockedError = class extends Error {
33046
34035
  result;
33047
34036
  constructor(result) {
@@ -42950,6 +43939,13 @@ __export(index_exports, {
42950
43939
  AgentMessageDirection: () => AgentMessageDirection,
42951
43940
  AiRunner: () => AiRunner,
42952
43941
  BACKUP_JOB_ID: () => BACKUP_JOB_ID,
43942
+ CODEX_APP_DIRS_ENV: () => CODEX_APP_DIRS_ENV,
43943
+ CODEX_AUTH_PROBE_TIMEOUT_MS: () => CODEX_AUTH_PROBE_TIMEOUT_MS,
43944
+ CODEX_COMMAND_SOURCES: () => CODEX_COMMAND_SOURCES,
43945
+ CODEX_PROBE_TIMEOUT_MS: () => CODEX_PROBE_TIMEOUT_MS,
43946
+ CODEX_READINESS_CACHE_TTL_MS: () => CODEX_READINESS_CACHE_TTL_MS,
43947
+ CODEX_READINESS_STATES: () => CODEX_READINESS_STATES,
43948
+ CODEX_RECOVERY_ACTIONS: () => CODEX_RECOVERY_ACTIONS,
42953
43949
  CommandStore: () => CommandStore,
42954
43950
  DEFAULT_AI_PERMISSION_PRESET: () => DEFAULT_AI_PERMISSION_PRESET,
42955
43951
  DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC: () => DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC,
@@ -42959,10 +43955,13 @@ __export(index_exports, {
42959
43955
  HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS: () => HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS,
42960
43956
  InboundReplayGate: () => InboundReplayGate,
42961
43957
  InvalidXmtpMessageStore: () => InvalidXmtpMessageStore,
43958
+ JOB_PROVIDER_BINDING_SOURCES: () => JOB_PROVIDER_BINDING_SOURCES,
43959
+ LogEvent: () => LogEvent,
42962
43960
  MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG: () => MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG,
42963
43961
  MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN: () => MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN,
42964
43962
  NATIVE_LAUNCHER_EXE_NAME: () => NATIVE_LAUNCHER_EXE_NAME,
42965
43963
  OPENCLAW_GATEWAY_ROUTE_GROUP_ID: () => OPENCLAW_GATEWAY_ROUTE_GROUP_ID,
43964
+ ProviderNotReadyError: () => ProviderNotReadyError,
42966
43965
  SYSTEM_NOTIFICATION_SESSION_KEY: () => SYSTEM_NOTIFICATION_SESSION_KEY,
42967
43966
  SessionBusyTracker: () => SessionBusyTracker,
42968
43967
  SessionStore: () => SessionStore,
@@ -42983,15 +43982,20 @@ __export(index_exports, {
42983
43982
  activeDaemonPointerPath: () => activeDaemonPointerPath,
42984
43983
  aiRunSentryExtra: () => aiRunSentryExtra,
42985
43984
  assertHermesSetupSupportedOnPlatform: () => assertHermesSetupSupportedOnPlatform,
43985
+ authStatusFromReason: () => authStatusFromReason,
42986
43986
  bindJobProviderToCurrentDefaultIfMissing: () => bindJobProviderToCurrentDefaultIfMissing,
43987
+ bindJobProviderToCurrentDefaultIfReady: () => bindJobProviderToCurrentDefaultIfReady,
42987
43988
  bindJobProviderToCurrentRuntimeIfMissing: () => bindJobProviderToCurrentRuntimeIfMissing,
43989
+ bindJobProviderToCurrentRuntimeIfReady: () => bindJobProviderToCurrentRuntimeIfReady,
42988
43990
  bindMessageJobProviderToCurrentDefault: () => bindMessageJobProviderToCurrentDefault,
43991
+ bindNewJobProviderIfReady: () => bindNewJobProviderIfReady,
42989
43992
  bindOpenClawGatewayRouteFromEnv: () => bindOpenClawGatewayRouteFromEnv,
42990
43993
  buildAgentMessageNotice: () => buildAgentMessageNotice,
42991
43994
  buildAiAdapterCommand: () => buildAiAdapterCommand,
42992
43995
  buildAiProviderEnv: () => buildAiProviderEnv,
42993
43996
  buildAutostartVbs: () => buildAutostartVbs,
42994
43997
  buildBackupJobSessionKey: () => buildBackupJobSessionKey,
43998
+ buildCodexRecoveryGuidance: () => buildCodexRecoveryGuidance,
42995
43999
  buildDispatchSessionKey: () => buildDispatchSessionKey,
42996
44000
  buildDoctorSentryExtra: () => buildDoctorSentryExtra,
42997
44001
  buildExternalCommandInvocation: () => buildExternalCommandInvocation,
@@ -43020,7 +44024,15 @@ __export(index_exports, {
43020
44024
  callSessionsCreate: () => callSessionsCreate,
43021
44025
  callSessionsDelete: () => callSessionsDelete,
43022
44026
  callSessionsSend: () => callSessionsSend,
44027
+ checkClaudeCliAuthStatus: () => checkClaudeCliAuthStatus,
44028
+ checkCodexCliAuthStatus: () => checkCodexCliAuthStatus,
44029
+ checkCodexReadiness: () => checkCodexReadiness,
43023
44030
  claimActiveDaemon: () => claimActiveDaemon,
44031
+ classifyJobBindingSource: () => classifyJobBindingSource,
44032
+ clearCodexReadinessCache: () => clearCodexReadinessCache,
44033
+ codexCommandResolvesWithoutOverride: () => codexCommandResolvesWithoutOverride,
44034
+ codexReadinessCacheKey: () => codexReadinessCacheKey,
44035
+ collectCodexAppBundledCandidates: () => collectCodexAppBundledCandidates,
43024
44036
  collectCodexCommandCandidates: () => collectCodexCommandCandidates,
43025
44037
  commandExists: () => commandExists,
43026
44038
  createAiToolFailureTracker: () => createAiToolFailureTracker,
@@ -43036,6 +44048,7 @@ __export(index_exports, {
43036
44048
  ensureDaemonReady: () => ensureDaemonReady,
43037
44049
  ensureDefaultAiProvider: () => ensureDefaultAiProvider,
43038
44050
  ensureOpenClawOkxA2aPluginConfig: () => ensureOpenClawOkxA2aPluginConfig,
44051
+ ensureProviderReadyForBinding: () => ensureProviderReadyForBinding,
43039
44052
  ensureWindowsNativeLauncher: () => ensureWindowsNativeLauncher,
43040
44053
  evictPid: () => evictPid,
43041
44054
  exportDiagnosticLogs: () => exportDiagnosticLogs,
@@ -43063,6 +44076,7 @@ __export(index_exports, {
43063
44076
  isDefaultTaskHome: () => isDefaultTaskHome,
43064
44077
  isGatewayAvailableForHeartbeat: () => isGatewayAvailableForHeartbeat,
43065
44078
  isHermesGatewayPluginEnabled: () => isHermesGatewayPluginEnabled,
44079
+ isInfoEventAllowlisted: () => isInfoEventAllowlisted,
43066
44080
  isOfflineReplayForTrigger: () => isOfflineReplayForTrigger,
43067
44081
  isOpenClawA2aPluginAvailable: () => isOpenClawA2aPluginAvailable,
43068
44082
  isOpenClawGatewayAvailable: () => isOpenClawGatewayAvailable,
@@ -43088,11 +44102,15 @@ __export(index_exports, {
43088
44102
  parsePluginYamlVersion: () => parsePluginYamlVersion,
43089
44103
  parseWindowsParentProcessJson: () => parseWindowsParentProcessJson,
43090
44104
  performRuntimeSwitch: () => performRuntimeSwitch,
44105
+ persistCurrentRuntimeSelectionForNewJob: () => persistCurrentRuntimeSelectionForNewJob,
43091
44106
  pickOnchainosWin32Candidate: () => pickOnchainosWin32Candidate,
44107
+ prepareAndBindCurrentRuntimeForNewJob: () => prepareAndBindCurrentRuntimeForNewJob,
43092
44108
  printLogsExportUsage: () => printLogsExportUsage,
43093
44109
  printXmtpTestUsage: () => printXmtpTestUsage,
44110
+ probeCodexAuthStatus: () => probeCodexAuthStatus,
43094
44111
  processFileMessage: () => processFileMessage,
43095
44112
  readAiProviderTimeoutMs: () => readAiProviderTimeoutMs,
44113
+ readCachedCodexReadiness: () => readCachedCodexReadiness,
43096
44114
  readLastLines: () => readLastLines,
43097
44115
  readParentProcessCommandForPlatform: () => readParentProcessCommandForPlatform,
43098
44116
  readUserAttentionWatcherEvent: () => readUserAttentionWatcherEvent,
@@ -43106,8 +44124,12 @@ __export(index_exports, {
43106
44124
  resetResolvedOnchainosBinForTests: () => resetResolvedOnchainosBinForTests,
43107
44125
  resolveAiPermissionPreset: () => resolveAiPermissionPreset,
43108
44126
  resolveAiProviderCommand: () => resolveAiProviderCommand,
44127
+ resolveAiProviderCommandWithSource: () => resolveAiProviderCommandWithSource,
43109
44128
  resolveAiProviderForDispatch: () => resolveAiProviderForDispatch,
44129
+ resolveAiProviderForDispatchWithReadiness: () => resolveAiProviderForDispatchWithReadiness,
44130
+ resolveCodexCommandCandidates: () => resolveCodexCommandCandidates,
43110
44131
  resolveCodexCommandPath: () => resolveCodexCommandPath,
44132
+ resolveCodexCommandSource: () => resolveCodexCommandSource,
43111
44133
  resolveConfiguredAiProvider: () => resolveConfiguredAiProvider,
43112
44134
  resolveConfiguredAiProviderForJob: () => resolveConfiguredAiProviderForJob,
43113
44135
  resolveDirectCommunicationSessionTarget: () => resolveDirectCommunicationSessionTarget,
@@ -43119,6 +44141,7 @@ __export(index_exports, {
43119
44141
  resolveOpenClawGatewayConfig: () => resolveOpenClawGatewayConfig,
43120
44142
  resolveOpenClawGatewayRoute: () => resolveOpenClawGatewayRoute,
43121
44143
  resolveOpenClawGatewayRoutes: () => resolveOpenClawGatewayRoutes,
44144
+ resolveProviderCommandWithSelfHeal: () => resolveProviderCommandWithSelfHeal,
43122
44145
  resolveSystemNotificationTargets: () => resolveSystemNotificationTargets,
43123
44146
  resolveTaskConfigPath: () => resolveTaskConfigPath,
43124
44147
  resolveTaskHome: () => resolveTaskHome,
@@ -43146,6 +44169,8 @@ __export(index_exports, {
43146
44169
  subscribeUserAttentionEvents: () => subscribeUserAttentionEvents,
43147
44170
  summarizeXmtpTestEvents: () => summarizeXmtpTestEvents,
43148
44171
  switchProvider: () => switchProvider,
44172
+ switchProviderWithReadinessGate: () => switchProviderWithReadinessGate,
44173
+ switchProviderWithReadinessGateCore: () => switchProviderWithReadinessGateCore,
43149
44174
  systemdUnitHasCurrentRestartPolicy: () => systemdUnitHasCurrentRestartPolicy,
43150
44175
  toOpenClawGatewaySessionKey: () => toOpenClawGatewaySessionKey,
43151
44176
  toWindowsInvocation: () => toWindowsInvocation,
@@ -43455,8 +44480,8 @@ init_paths();
43455
44480
  init_session_store();
43456
44481
 
43457
44482
  // src/task-config.ts
43458
- var import_node_fs8 = require("node:fs");
43459
- var import_node_path12 = require("node:path");
44483
+ var import_node_fs9 = require("node:fs");
44484
+ var import_node_path13 = require("node:path");
43460
44485
  init_paths();
43461
44486
  var AI_PERMISSION_PRESETS = ["bypass", "auto"];
43462
44487
  var DEFAULT_AI_PERMISSION_PRESET = "bypass";
@@ -43472,23 +44497,23 @@ function resolveAiPermissionPreset(options = {}) {
43472
44497
  }
43473
44498
  function readAiPermissionPresetFromConfig(homeDir) {
43474
44499
  const configPath = resolveTaskConfigPath(homeDir);
43475
- if (!(0, import_node_fs8.existsSync)(configPath)) {
44500
+ if (!(0, import_node_fs9.existsSync)(configPath)) {
43476
44501
  return null;
43477
44502
  }
43478
- const raw = readSimpleTomlStringValue((0, import_node_fs8.readFileSync)(configPath, "utf8"), "ai.permissions", "preset");
44503
+ const raw = readSimpleTomlStringValue((0, import_node_fs9.readFileSync)(configPath, "utf8"), "ai.permissions", "preset");
43479
44504
  return raw ? normalizeAiPermissionPreset(raw, configPath) : null;
43480
44505
  }
43481
44506
  function writeAiPermissionPresetToConfig(homeDir, preset) {
43482
44507
  const normalized = normalizeAiPermissionPreset(preset, "permission preset");
43483
44508
  ensureTaskDir(homeDir);
43484
44509
  const configPath = resolveTaskConfigPath(homeDir);
43485
- const current = (0, import_node_fs8.existsSync)(configPath) ? (0, import_node_fs8.readFileSync)(configPath, "utf8") : "";
44510
+ const current = (0, import_node_fs9.existsSync)(configPath) ? (0, import_node_fs9.readFileSync)(configPath, "utf8") : "";
43486
44511
  const next = upsertSimpleTomlStringValue(current, "ai.permissions", "preset", normalized);
43487
- (0, import_node_fs8.writeFileSync)(configPath, next, "utf8");
44512
+ (0, import_node_fs9.writeFileSync)(configPath, next, "utf8");
43488
44513
  return normalized;
43489
44514
  }
43490
44515
  function resolveTaskConfigPath(homeDir) {
43491
- return (0, import_node_path12.join)(homeDir, "config.toml");
44516
+ return (0, import_node_path13.join)(homeDir, "config.toml");
43492
44517
  }
43493
44518
  function normalizeAiPermissionPreset(value, source) {
43494
44519
  const normalized = value.trim().toLowerCase();
@@ -43598,11 +44623,14 @@ function buildCodexPermissionPrefix(homeDir, cwd, env = process.env, permissionP
43598
44623
  cwd
43599
44624
  ];
43600
44625
  }
44626
+ function resolveAdapterProviderCommand(options, homeDir) {
44627
+ const storedCommand = options.provider === "codex" && options.homeDir ? readStoredAiProviderCommand(options.provider, homeDir) : null;
44628
+ return resolveAiProviderCommandWithSource(options.provider, options.env ?? process.env, storedCommand);
44629
+ }
43601
44630
  function buildAiAdapterCommand(options) {
43602
44631
  const env = options.env ?? process.env;
43603
44632
  const homeDir = options.homeDir ?? resolveTaskPaths().homeDir;
43604
- const storedCommand = options.provider === "codex" && options.homeDir ? readStoredAiProviderCommand(options.provider, homeDir) : null;
43605
- const command = resolveAiProviderCommand(options.provider, env, storedCommand);
44633
+ const { command } = resolveAdapterProviderCommand(options, homeDir);
43606
44634
  const template = readAdapterArgsTemplate(options.provider, !!options.sessionId, env);
43607
44635
  if (template) {
43608
44636
  return {
@@ -43774,6 +44802,7 @@ async function runAiAdapter(options) {
43774
44802
  );
43775
44803
  if (timedOut || exitCode !== 0 || !sessionId) {
43776
44804
  const reason = timedOut ? "timeout" : !sessionId ? "missing_ai_session_id" : "non_zero_exit";
44805
+ const { source: commandSource } = resolveAdapterProviderCommand({ ...options, homeDir }, homeDir);
43777
44806
  logger.error(
43778
44807
  timedOut ? LogEvent.AI_RUN_TIMEOUT : LogEvent.AI_RUN_FAILED,
43779
44808
  new Error(`${options.provider} AI adapter failed: ${reason}`),
@@ -43783,6 +44812,8 @@ async function runAiAdapter(options) {
43783
44812
  provider: options.provider,
43784
44813
  stage: "run_adapter",
43785
44814
  reason,
44815
+ // Category only — never the resolved command, its path or its arguments.
44816
+ commandSource: commandSource ?? "",
43786
44817
  sessionKey: options.sessionKey ?? "",
43787
44818
  timeoutMs: timeoutMs === null ? "" : String(timeoutMs),
43788
44819
  exitCode: exitCode === null ? "null" : String(exitCode),
@@ -44171,6 +45202,10 @@ function aiRunSentryExtra(input) {
44171
45202
  stage: input.stage ?? "",
44172
45203
  checkpoint: input.checkpoint ?? "",
44173
45204
  outcome: input.outcome ?? "",
45205
+ commandSource: input.commandSource ?? "",
45206
+ jobBindingSource: input.jobBindingSource ?? "",
45207
+ recoveryAction: input.recoveryAction ?? "",
45208
+ readinessState: input.readinessState ?? "",
44174
45209
  mode: input.mode ?? "",
44175
45210
  pid: input.pid ?? "",
44176
45211
  runAttempt: input.runAttempt ?? "",
@@ -44206,6 +45241,15 @@ function aiRunDeliveryId(input) {
44206
45241
  sessionKey: input.sessionKey
44207
45242
  });
44208
45243
  }
45244
+ function resolveProviderCommandWithSelfHeal(options) {
45245
+ const env = options.env ?? process.env;
45246
+ const storedCommand = options.provider === "codex" ? options.store.getAiProviderCommand(options.provider) : null;
45247
+ const resolved = resolveAiProviderCommandWithSource(options.provider, env, storedCommand, options.platform);
45248
+ if (options.provider === "codex" && resolved.source !== "explicit_override" && resolved.command !== storedCommand && (0, import_node_path16.isAbsolute)(resolved.command)) {
45249
+ options.store.setAiProviderCommand(options.provider, resolved.command);
45250
+ }
45251
+ return { command: resolved.command, commandSource: resolved.source };
45252
+ }
44209
45253
  var AiRunner = class {
44210
45254
  homeDir;
44211
45255
  logsDir;
@@ -44351,11 +45395,41 @@ var AiRunner = class {
44351
45395
  storeReadMs = storeReadEndedAt - storeReadStartedAt;
44352
45396
  }
44353
45397
  const resolveStartedAt = Date.now();
44354
- const provider = resolveAiProviderForDispatch({
44355
- store: this.sessionStore,
44356
- sessionKey: request.sessionKey,
44357
- jobId: request.jobId
44358
- });
45398
+ const jobBinding = this.readJobProviderBindingForTelemetry(request.jobId);
45399
+ let provider;
45400
+ try {
45401
+ provider = await resolveAiProviderForDispatchWithReadiness({
45402
+ store: this.sessionStore,
45403
+ sessionKey: request.sessionKey,
45404
+ jobId: request.jobId
45405
+ });
45406
+ } catch (err) {
45407
+ const notReady = err instanceof ProviderNotReadyError ? err : null;
45408
+ logger.error(
45409
+ LogEvent.AI_RUN_FAILED,
45410
+ err instanceof Error ? err : new Error(String(err)),
45411
+ aiRunSentryExtra({
45412
+ source: request.source,
45413
+ sessionKey: request.sessionKey,
45414
+ jobId: request.jobId,
45415
+ agentId: request.agentId,
45416
+ messageId: request.messageId,
45417
+ deliveryId,
45418
+ provider: notReady?.provider ?? jobBinding.provider ?? "unknown",
45419
+ stage: "resolve_provider",
45420
+ reason: "provider_not_ready",
45421
+ checkpoint: "ai_run/provider_not_ready",
45422
+ outcome: "failed",
45423
+ runAttempt,
45424
+ jobBindingSource: jobBinding.bindingSource,
45425
+ // Categories only — never the resolved command or its path (NFR-3).
45426
+ readinessState: notReady?.readinessState ?? null,
45427
+ commandSource: notReady?.commandSource ?? null,
45428
+ recoveryAction: notReady?.recoveryAction ?? "run_setup"
45429
+ })
45430
+ );
45431
+ throw err;
45432
+ }
44359
45433
  const sessionMeta = this.sessionStore.getSession(request.sessionKey);
44360
45434
  const existingAiSessionId = this.readRunAiSessionId(provider, request, existing, sessionMeta);
44361
45435
  const runMode = existingAiSessionId ? "resume" : "new";
@@ -44425,7 +45499,7 @@ var AiRunner = class {
44425
45499
  }
44426
45500
  }
44427
45501
  };
44428
- const command = this.resolveProviderCommand(provider);
45502
+ const { command, commandSource } = this.resolveProviderCommand(provider);
44429
45503
  let stderrText = "";
44430
45504
  let timedOut = false;
44431
45505
  const timeoutMs = readAiProviderTimeoutMs();
@@ -44488,6 +45562,8 @@ var AiRunner = class {
44488
45562
  provider,
44489
45563
  checkpoint: "ai_run/started",
44490
45564
  outcome: "pending",
45565
+ commandSource,
45566
+ jobBindingSource: jobBinding.bindingSource,
44491
45567
  mode: runMode,
44492
45568
  pid: child.pid ?? null,
44493
45569
  runAttempt,
@@ -44601,6 +45677,11 @@ var AiRunner = class {
44601
45677
  stage: "run_ai",
44602
45678
  checkpoint: overrides?.checkpoint ?? "ai_run/failed",
44603
45679
  outcome: overrides?.outcome ?? "failed",
45680
+ // Which tier produced the binary that failed, and how the job picked this
45681
+ // provider — the two dimensions that separate "GUI-only Codex was never
45682
+ // discovered" from "the CLI itself is broken".
45683
+ commandSource,
45684
+ jobBindingSource: jobBinding.bindingSource,
44604
45685
  runAttempt,
44605
45686
  timeoutMs,
44606
45687
  exitCode,
@@ -44946,8 +46027,32 @@ var AiRunner = class {
44946
46027
  return extractAiSessionId(provider, line);
44947
46028
  }
44948
46029
  resolveProviderCommand(provider) {
44949
- const storedCommand = provider === "codex" ? this.sessionStore.getAiProviderCommand(provider) : null;
44950
- return resolveAiProviderCommand(provider, process.env, storedCommand);
46030
+ return resolveProviderCommandWithSelfHeal({
46031
+ provider,
46032
+ store: this.sessionStore,
46033
+ env: process.env
46034
+ });
46035
+ }
46036
+ /**
46037
+ * Telemetry-only view of how this run's provider was decided. Never throws: a
46038
+ * store read that fails must not replace the error we are already reporting.
46039
+ */
46040
+ readJobProviderBindingForTelemetry(jobId) {
46041
+ const normalizedJobId = normalizeOptionalText2(jobId);
46042
+ if (normalizedJobId) {
46043
+ try {
46044
+ const bound = this.sessionStore.getJobProviderBinding(normalizedJobId);
46045
+ if (bound) {
46046
+ return { provider: bound.provider, bindingSource: "existing_binding" };
46047
+ }
46048
+ } catch {
46049
+ }
46050
+ }
46051
+ try {
46052
+ return { provider: this.sessionStore.getDefaultAiProvider(), bindingSource: "default_provider" };
46053
+ } catch {
46054
+ return { provider: null, bindingSource: "default_provider" };
46055
+ }
44951
46056
  }
44952
46057
  formatRunTarget(request) {
44953
46058
  if (request.source === "job-dispatch") {
@@ -58911,7 +60016,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
58911
60016
  client: {
58912
60017
  id: "gateway-client",
58913
60018
  displayName: "okx-a2a-node",
58914
- version: "0.2.2",
60019
+ version: "0.2.3-beta-fa7df0d9e7-260810102542",
58915
60020
  platform: "node",
58916
60021
  mode: "backend",
58917
60022
  instanceId
@@ -58922,7 +60027,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
58922
60027
  commands: [],
58923
60028
  permissions: {},
58924
60029
  locale: Intl.DateTimeFormat().resolvedOptions().locale || "en-US",
58925
- userAgent: `okx-a2a-node/${"0.2.2"}`,
60030
+ userAgent: `okx-a2a-node/${"0.2.3-beta-fa7df0d9e7-260810102542"}`,
58926
60031
  auth: {
58927
60032
  ...config.token ? { token: config.token } : {},
58928
60033
  ...config.password ? { password: config.password } : {}
@@ -60519,15 +61624,21 @@ async function createGroupForAddress(params) {
60519
61624
  { groupName: params.groupName }
60520
61625
  );
60521
61626
  }
60522
- function bindOutboundJobProviderToCurrentDefault(sessionStore, jobId) {
61627
+ async function bindOutboundJobProviderToCurrentDefault(sessionStore, jobId) {
60523
61628
  if (!jobId || jobId === BACKUP_JOB_ID) {
60524
61629
  return;
60525
61630
  }
60526
61631
  try {
60527
- const result = bindJobProviderToCurrentDefaultIfMissing({
61632
+ const result = await bindNewJobProviderIfReady({
60528
61633
  store: sessionStore,
60529
61634
  jobId
60530
61635
  });
61636
+ if (result?.degraded) {
61637
+ logWithTimestamp(
61638
+ `[okx-agent-task] job provider not ready from outbound-xmtp: job=${jobId} state=${result.readiness?.state ?? "not_required"} recovery=${result.readiness?.recoveryAction ?? "none"}; binding deferred`
61639
+ );
61640
+ return;
61641
+ }
60531
61642
  if (result?.created) {
60532
61643
  logWithTimestamp(`[okx-agent-task] job provider bound from outbound-xmtp: job=${jobId} provider=${result.provider}`);
60533
61644
  }
@@ -61251,7 +62362,7 @@ async function handleXmtpSendCommand(params) {
61251
62362
  const ownedStore2 = params.sessionStore ? null : new SessionStore();
61252
62363
  const sessionStore2 = params.sessionStore ?? ownedStore2;
61253
62364
  try {
61254
- bindOutboundJobProviderToCurrentDefault(sessionStore2, command.jobId);
62365
+ await bindOutboundJobProviderToCurrentDefault(sessionStore2, command.jobId);
61255
62366
  bindOutboundOpenClawRouteIfCurrent(sessionStore2, command.jobId, command.gatewaySessionKeys);
61256
62367
  return await handleSqliteGroupSendCommand({
61257
62368
  command,
@@ -61265,7 +62376,7 @@ async function handleXmtpSendCommand(params) {
61265
62376
  const ownedStore = params.sessionStore ? null : new SessionStore();
61266
62377
  const sessionStore = params.sessionStore ?? ownedStore;
61267
62378
  try {
61268
- bindOutboundJobProviderToCurrentDefault(sessionStore, command.jobId);
62379
+ await bindOutboundJobProviderToCurrentDefault(sessionStore, command.jobId);
61269
62380
  bindOutboundOpenClawRouteIfCurrent(sessionStore, command.jobId, command.gatewaySessionKeys);
61270
62381
  const { file, sessionAgentId } = await readFileForSend({
61271
62382
  command,
@@ -63050,41 +64161,76 @@ function upsertDirectCommunicationSession(deps, target) {
63050
64161
  toAgentXmtpAddress: remoteAgent?.communicationAddress ?? null
63051
64162
  });
63052
64163
  }
63053
- function bindMessageJobProviderToCurrentDefault(deps, jobId, stage = "inbound-xmtp") {
64164
+ function neutralProviderGateResult(currentProvider) {
64165
+ return {
64166
+ allowed: true,
64167
+ created: false,
64168
+ provider: null,
64169
+ currentProvider,
64170
+ degraded: false,
64171
+ bindingSource: null,
64172
+ readinessState: "not_required",
64173
+ recoveryAction: "none"
64174
+ };
64175
+ }
64176
+ async function bindMessageJobProviderToCurrentDefault(deps, jobId, stage = "inbound-xmtp", options = {}) {
63054
64177
  const currentProvider = deps.sessionStore?.getDefaultAiProvider() ?? null;
63055
64178
  if (!jobId || jobId === BACKUP_JOB_ID) {
63056
- return { allowed: true, created: false, provider: null, currentProvider };
64179
+ return neutralProviderGateResult(currentProvider);
63057
64180
  }
63058
64181
  try {
63059
- const result = bindJobProviderToCurrentDefaultIfMissing({
64182
+ const result = await bindNewJobProviderIfReady({
63060
64183
  store: deps.sessionStore,
63061
- jobId
64184
+ jobId,
64185
+ env: options.env,
64186
+ platform: options.platform,
64187
+ readiness: options.readiness
63062
64188
  });
63063
64189
  if (!result) {
63064
- return { allowed: true, created: false, provider: null, currentProvider };
64190
+ return neutralProviderGateResult(currentProvider);
63065
64191
  }
63066
- if (result?.created) {
64192
+ if (result.degraded) {
64193
+ logWithTimestamp(
64194
+ `[okx-agent-task:${deps.myXmtpAddress}] job provider not ready at ${stage}: job=${shortenLogValue(jobId)} state=${result.readiness?.state ?? "not_required"} recovery=${result.readiness?.recoveryAction ?? "none"}; binding deferred, message still processed`
64195
+ );
64196
+ return {
64197
+ allowed: true,
64198
+ created: false,
64199
+ provider: null,
64200
+ currentProvider,
64201
+ degraded: true,
64202
+ bindingSource: null,
64203
+ readinessState: result.readiness?.state ?? "not_required",
64204
+ recoveryAction: result.readiness?.recoveryAction ?? "none"
64205
+ };
64206
+ }
64207
+ const boundProvider = result.provider ? normalizeAiProvider(result.provider) : null;
64208
+ if (result.created) {
63067
64209
  logWithTimestamp(
63068
- `[okx-agent-task:${deps.myXmtpAddress}] job provider bound from ${stage}: job=${shortenLogValue(jobId)} provider=${result.provider}`
64210
+ `[okx-agent-task:${deps.myXmtpAddress}] job provider bound from ${stage}: job=${shortenLogValue(jobId)} provider=${boundProvider}`
63069
64211
  );
63070
64212
  }
63071
- if (currentProvider && result.provider !== currentProvider) {
64213
+ if (currentProvider && boundProvider !== currentProvider) {
63072
64214
  logWithTimestamp(
63073
- `[okx-agent-task:${deps.myXmtpAddress}] job provider owner differs from default at ${stage}: job=${shortenLogValue(jobId)} owner=${result.provider} default=${currentProvider}; dispatch will use owner binding`
64215
+ `[okx-agent-task:${deps.myXmtpAddress}] job provider owner differs from default at ${stage}: job=${shortenLogValue(jobId)} owner=${boundProvider} default=${currentProvider}; dispatch will use owner binding`
63074
64216
  );
63075
64217
  }
63076
64218
  return {
63077
64219
  allowed: true,
63078
64220
  created: result.created,
63079
- provider: result.provider,
63080
- currentProvider
64221
+ provider: boundProvider,
64222
+ currentProvider,
64223
+ degraded: false,
64224
+ bindingSource: result.bindingSource,
64225
+ readinessState: result.readiness?.state ?? "not_required",
64226
+ recoveryAction: result.readiness?.recoveryAction ?? "none"
63081
64227
  };
63082
64228
  } catch (err) {
63083
64229
  logWithTimestamp(
63084
64230
  `[okx-agent-task:${deps.myXmtpAddress}] job provider bind failed from ${stage}: job=${shortenLogValue(jobId)}`,
63085
64231
  err
63086
64232
  );
63087
- return { allowed: true, created: false, provider: null, currentProvider };
64233
+ return neutralProviderGateResult(currentProvider);
63088
64234
  }
63089
64235
  }
63090
64236
  function isCanonicalJobPeerSessionKey(sessionKey) {
@@ -63366,7 +64512,7 @@ async function dispatchDirectToUserNotification(params) {
63366
64512
  ...extra
63367
64513
  });
63368
64514
  const providerBindStartedAt = Date.now();
63369
- const providerGate = bindMessageJobProviderToCurrentDefault(deps, notification.jobId, "direct-to-user");
64515
+ const providerGate = await bindMessageJobProviderToCurrentDefault(deps, notification.jobId, "direct-to-user");
63370
64516
  timing.mark("providerBind", providerBindStartedAt);
63371
64517
  logger.info(LogEvent.SYSTEM_NOTIFICATION_RECEIVED, commonExtras({
63372
64518
  ...inboundStageExtras("inbound/direct_to_user_received", "success"),
@@ -63880,7 +65026,7 @@ async function processFileMessage(ctx, deps, options = {}) {
63880
65026
  return true;
63881
65027
  }
63882
65028
  const providerBindStartedAt = Date.now();
63883
- const providerGate = bindMessageJobProviderToCurrentDefault(deps, systemNotification.jobId, "system-notification");
65029
+ const providerGate = await bindMessageJobProviderToCurrentDefault(deps, systemNotification.jobId, "system-notification");
63884
65030
  timing.mark("providerBind", providerBindStartedAt);
63885
65031
  if (!providerGate.allowed) {
63886
65032
  logger.info(LogEvent.INBOUND_IGNORED_NON_BUSINESS, timing.extras({
@@ -64001,7 +65147,7 @@ async function processFileMessage(ctx, deps, options = {}) {
64001
65147
  timing.mark("routeResolve", routeStartedAt);
64002
65148
  if (route.route === "job") {
64003
65149
  const providerBindStartedAt = Date.now();
64004
- const providerGate = bindMessageJobProviderToCurrentDefault(deps, route.jobId, "inbound-xmtp");
65150
+ const providerGate = await bindMessageJobProviderToCurrentDefault(deps, route.jobId, "inbound-xmtp");
64005
65151
  timing.mark("providerBind", providerBindStartedAt);
64006
65152
  if (!providerGate.allowed) {
64007
65153
  logger.info(LogEvent.INBOUND_IGNORED_NON_BUSINESS, timing.extras({
@@ -64920,12 +66066,12 @@ async function runListenerWithLock(options, paths) {
64920
66066
  });
64921
66067
  }
64922
66068
  });
64923
- service.setPluginVersion("0.2.2");
66069
+ service.setPluginVersion("0.2.3-beta-fa7df0d9e7-260810102542");
64924
66070
  await service.init();
64925
66071
  const pluginVersionStatus = service.pluginVersionStatus;
64926
66072
  if (pluginVersionStatus.unavailable) {
64927
66073
  throw new Error(
64928
- `@okxweb3/a2a-node v${"0.2.2"} is below the required minimum v${pluginVersionStatus.minVersion}`
66074
+ `@okxweb3/a2a-node v${"0.2.3-beta-fa7df0d9e7-260810102542"} is below the required minimum v${pluginVersionStatus.minVersion}`
64929
66075
  );
64930
66076
  }
64931
66077
  const systemConfig = service.getSystemConfig();
@@ -64943,7 +66089,7 @@ async function runListenerWithLock(options, paths) {
64943
66089
  onchainosAgentId: "*",
64944
66090
  reason: "system-config missing sentryDsn",
64945
66091
  pluginId: "@okxweb3/a2a-node",
64946
- pluginVersion: "0.2.2"
66092
+ pluginVersion: "0.2.3-beta-fa7df0d9e7-260810102542"
64947
66093
  });
64948
66094
  }
64949
66095
  logWithTimestamp(
@@ -66210,6 +67356,32 @@ function validTimestamp(value) {
66210
67356
  init_session_store();
66211
67357
  init_ai_provider();
66212
67358
  init_ai_command();
67359
+ init_codex_readiness();
67360
+
67361
+ // src/codex-readiness-types.ts
67362
+ var CODEX_COMMAND_SOURCES = [
67363
+ "explicit_override",
67364
+ "persisted",
67365
+ "path",
67366
+ "app_bundled"
67367
+ ];
67368
+ var CODEX_READINESS_STATES = [
67369
+ "ready",
67370
+ "not_installed",
67371
+ "not_authenticated",
67372
+ "probe_timeout",
67373
+ "probe_failed"
67374
+ ];
67375
+ var CODEX_RECOVERY_ACTIONS = ["none", "run_setup", "run_login", "install_cli"];
67376
+ var JOB_PROVIDER_BINDING_SOURCES = [
67377
+ "existing_binding",
67378
+ "current_runtime",
67379
+ "default_provider",
67380
+ "explicit_env"
67381
+ ];
67382
+
67383
+ // src/index.ts
67384
+ init_sentry_logger();
66213
67385
  init_update_cli();
66214
67386
  init_win_spawn();
66215
67387
 
@@ -67563,7 +68735,7 @@ async function exportDiagnosticLogs(options) {
67563
68735
  node: process.version,
67564
68736
  platform: process.platform,
67565
68737
  arch: process.arch,
67566
- packageVersion: true ? "0.2.2" : "unknown",
68738
+ packageVersion: true ? "0.2.3-beta-fa7df0d9e7-260810102542" : "unknown",
67567
68739
  sensitiveContentIncluded: options.includeSensitiveContent,
67568
68740
  listenerAndLlmContentIncluded: true,
67569
68741
  credentialsAlwaysRedacted: true,
@@ -68432,6 +69604,7 @@ init_log();
68432
69604
  init_win_compat();
68433
69605
  init_ai_command();
68434
69606
  init_ai_provider();
69607
+ init_codex_readiness();
68435
69608
  init_autostart();
68436
69609
  init_autostart_windows();
68437
69610
  init_daemon();
@@ -68465,11 +69638,70 @@ async function refreshAgentsAndWait(timeoutMs = 6e4) {
68465
69638
 
68466
69639
  // src/runtime-switch.ts
68467
69640
  init_ai_provider();
68468
- async function performRuntimeSwitch(store) {
68469
- const result = switchProvider({ store, env: process.env });
69641
+ init_sentry_logger();
69642
+ init_log_fields();
69643
+ var RUNTIME_SWITCH_COMPONENT = "node_runtime_switch";
69644
+ async function performRuntimeSwitch(store, options = {}) {
69645
+ const result = await switchProviderWithReadinessGate({
69646
+ store,
69647
+ env: options.env ?? process.env,
69648
+ platform: options.platform,
69649
+ readiness: options.readiness
69650
+ });
68470
69651
  await dispatchRuntimeSwitchUserMessage(store, result);
68471
69652
  return result;
68472
69653
  }
69654
+ async function switchProviderWithReadinessGate(options) {
69655
+ const gated = await switchProviderWithReadinessGateCore(options);
69656
+ emitRuntimeSwitchCheckpoints(gated);
69657
+ return gated.result;
69658
+ }
69659
+ function emitRuntimeSwitchCheckpoints(gated) {
69660
+ const { result, runtime, readiness } = gated;
69661
+ logger.info(LogEvent.RUNTIME_DETECTED, {
69662
+ component: RUNTIME_SWITCH_COMPONENT,
69663
+ provider: runtime,
69664
+ detectedProvider: runtime,
69665
+ ...logFieldExtras({
69666
+ checkpoint: "runtime_detected",
69667
+ outcome: runtime === "unknown" ? "ignored" : "success",
69668
+ transport: "cli",
69669
+ eventFamily: "lifecycle"
69670
+ })
69671
+ });
69672
+ if (readiness) {
69673
+ logger.info(LogEvent.PROVIDER_READINESS_CHECKED, {
69674
+ component: RUNTIME_SWITCH_COMPONENT,
69675
+ provider: readiness.provider,
69676
+ readinessState: readiness.state,
69677
+ commandSource: readiness.commandSource ?? "",
69678
+ commandPersisted: String(readiness.commandPersisted),
69679
+ recoveryAction: readiness.recoveryAction,
69680
+ ...logFieldExtras({
69681
+ checkpoint: readiness.ready ? "provider_ready" : "provider_not_ready",
69682
+ outcome: readiness.ready ? "success" : "failed",
69683
+ transport: "cli",
69684
+ eventFamily: "lifecycle"
69685
+ })
69686
+ });
69687
+ }
69688
+ if (result.ok && result.action === "switched") {
69689
+ logger.info(LogEvent.PROVIDER_SWITCHED, {
69690
+ component: RUNTIME_SWITCH_COMPONENT,
69691
+ provider: result.provider,
69692
+ previousProvider: result.previousProvider ?? "",
69693
+ changed: String(result.changed),
69694
+ readinessState: result.readinessState ?? "not_required",
69695
+ commandSource: result.commandSource ?? "",
69696
+ ...logFieldExtras({
69697
+ checkpoint: "provider_switched",
69698
+ outcome: "success",
69699
+ transport: "cli",
69700
+ eventFamily: "lifecycle"
69701
+ })
69702
+ });
69703
+ }
69704
+ }
68473
69705
  async function dispatchRuntimeSwitchUserMessage(store, result) {
68474
69706
  if (!result.ok || !result.changed || !result.userMessage) {
68475
69707
  return;
@@ -68803,20 +70035,34 @@ var providerBindingChecker = {
68803
70035
  }
68804
70036
  }
68805
70037
  };
70038
+ function resolveProviderCliTarget(env, store) {
70039
+ const runtime = detectRuntime(env);
70040
+ if (runtime === "codex" || runtime === "claude") {
70041
+ return runtime;
70042
+ }
70043
+ if (runtime !== "unknown") {
70044
+ return null;
70045
+ }
70046
+ const storedDefault = store.getDefaultAiProvider();
70047
+ return storedDefault === "codex" || storedDefault === "claude" ? storedDefault : null;
70048
+ }
70049
+ function readProviderCliTarget(env) {
70050
+ const store = new SessionStore();
70051
+ try {
70052
+ const provider = resolveProviderCliTarget(env, store);
70053
+ return {
70054
+ provider,
70055
+ storedCommand: provider ? store.getAiProviderCommand(provider) : null
70056
+ };
70057
+ } finally {
70058
+ store.close();
70059
+ }
70060
+ }
68806
70061
  var providerCliChecker = {
68807
70062
  id: "provider_cli",
68808
70063
  title: "AI provider CLI",
68809
70064
  run: async (ctx) => {
68810
- const store = new SessionStore();
68811
- let provider;
68812
- let storedCommand;
68813
- try {
68814
- provider = store.getDefaultAiProvider();
68815
- provider = provider === "codex" || provider === "claude" ? provider : null;
68816
- storedCommand = provider ? store.getAiProviderCommand(provider) : null;
68817
- } finally {
68818
- store.close();
68819
- }
70065
+ const { provider, storedCommand } = readProviderCliTarget(ctx.env);
68820
70066
  if (!provider) {
68821
70067
  return null;
68822
70068
  }
@@ -68839,12 +70085,29 @@ var providerCliChecker = {
68839
70085
  }
68840
70086
  const auth = provider === "codex" ? await checkCodexCliAuthStatus(resolved) : await checkClaudeCliAuthStatus(resolved);
68841
70087
  if (auth.ok) {
70088
+ if (provider === "codex" && ctx.env.OKX_A2A_AI_CODEX_COMMAND && storedCommand !== resolved) {
70089
+ return {
70090
+ id: "provider_cli",
70091
+ title: "AI provider CLI",
70092
+ status: "fail",
70093
+ severity: "required",
70094
+ detail: "codex CLI is authenticated but its command is not persisted for the background daemon",
70095
+ fix: {
70096
+ kind: "auto",
70097
+ description: "Persist the validated Codex command for background daemon use."
70098
+ },
70099
+ data: { provider }
70100
+ };
70101
+ }
68842
70102
  return {
68843
70103
  id: "provider_cli",
68844
70104
  title: "AI provider CLI",
68845
70105
  status: "pass",
68846
70106
  severity: "required",
68847
- detail: `${provider} CLI at ${resolved} is logged in`,
70107
+ // `detail` is forwarded to Sentry (doctor-sentry.ts), so it carries no
70108
+ // resolved path or arguments. The exact command stays in `data`, which is
70109
+ // local report output only.
70110
+ detail: `${provider} CLI is installed and logged in`,
68848
70111
  data: { provider, command: resolved }
68849
70112
  };
68850
70113
  }
@@ -68855,7 +70118,8 @@ var providerCliChecker = {
68855
70118
  title: "AI provider CLI",
68856
70119
  status: "fail",
68857
70120
  severity: "required",
68858
- detail: `${provider} CLI at ${resolved} is not logged in (${auth.reason})`,
70121
+ // No resolved path in `detail`: it is forwarded to Sentry.
70122
+ detail: `${provider} CLI is installed but not logged in (${auth.reason})`,
68859
70123
  // Logging in needs a human (device/browser auth). In --non-interactive
68860
70124
  // mode never launch it — degrade to a manual instruction so unattended
68861
70125
  // callers (install scripts) cannot hang waiting on stdin.
@@ -68872,18 +70136,9 @@ var providerCliChecker = {
68872
70136
  };
68873
70137
  },
68874
70138
  applyFix: async (ctx) => {
68875
- const store = new SessionStore();
68876
- let provider;
68877
- let storedCommand;
68878
- try {
68879
- provider = store.getDefaultAiProvider();
68880
- provider = provider === "codex" || provider === "claude" ? provider : null;
68881
- storedCommand = provider ? store.getAiProviderCommand(provider) : null;
68882
- } finally {
68883
- store.close();
68884
- }
70139
+ const { provider, storedCommand } = readProviderCliTarget(ctx.env);
68885
70140
  if (!provider) {
68886
- throw new Error("no codex/claude provider bound");
70141
+ throw new Error("no codex/claude runtime detected and no codex/claude provider bound");
68887
70142
  }
68888
70143
  const resolveCli = () => provider === "codex" ? resolveCodexCommandPath(ctx.env, storedCommand, ctx.platform) : resolveCommandPath(resolveAiProviderCommand(provider, ctx.env, storedCommand, ctx.platform), ctx.env, ctx.platform);
68889
70144
  let resolved = resolveCli();
@@ -68918,6 +70173,28 @@ var providerCliChecker = {
68918
70173
  }
68919
70174
  steps.push("completed interactive login");
68920
70175
  }
70176
+ if (provider === "codex") {
70177
+ const store = new SessionStore();
70178
+ try {
70179
+ const readiness = await checkCodexReadiness({
70180
+ store,
70181
+ env: ctx.env,
70182
+ platform: ctx.platform,
70183
+ forceRefresh: true,
70184
+ persistOverrideCommand: true
70185
+ });
70186
+ if (!readiness.ready) {
70187
+ throw new Error(
70188
+ `codex readiness could not be confirmed (${readiness.state}); ${readiness.recoveryGuidance}`.trim()
70189
+ );
70190
+ }
70191
+ if (readiness.commandPersisted) {
70192
+ steps.push("persisted the validated codex command");
70193
+ }
70194
+ } finally {
70195
+ store.close();
70196
+ }
70197
+ }
68921
70198
  return steps.length > 0 ? steps.join("; ") : "CLI already installed and logged in";
68922
70199
  }
68923
70200
  };
@@ -69274,8 +70551,14 @@ var CHECKERS = [
69274
70551
  npmChecker,
69275
70552
  cliVersionChecker,
69276
70553
  windowsNativeLauncherChecker,
69277
- providerBindingChecker,
70554
+ // provider_cli BEFORE provider_binding, deliberately: the intended runtime has
70555
+ // to be installed, authenticated and durably persisted before the binding fixer
70556
+ // attempts its readiness-gated runtime switch. In the other order the switch
70557
+ // fails on a not-ready Codex, preserves the stale default, and the CLI checker
70558
+ // then inspects that stale provider instead of the detected one — so Codex is
70559
+ // never repaired and the run never converges.
69278
70560
  providerCliChecker,
70561
+ providerBindingChecker,
69279
70562
  gatewayPluginChecker,
69280
70563
  gatewayConfigChecker,
69281
70564
  autostartChecker,
@@ -69288,7 +70571,7 @@ async function runDoctor(options = {}) {
69288
70571
  platform: options.platform ?? process.platform,
69289
70572
  env: options.env ?? process.env,
69290
70573
  target: options.target ?? resolveDoctorTarget(options.env ?? process.env),
69291
- cliVersion: options.cliVersion ?? (true ? "0.2.2" : "0.0.0"),
70574
+ cliVersion: options.cliVersion ?? (true ? "0.2.3-beta-fa7df0d9e7-260810102542" : "0.0.0"),
69292
70575
  fixMode: options.fix === true,
69293
70576
  nonInteractive: options.nonInteractive === true,
69294
70577
  packageChanged: false,
@@ -69513,6 +70796,13 @@ init_autostart_windows();
69513
70796
  AgentMessageDirection,
69514
70797
  AiRunner,
69515
70798
  BACKUP_JOB_ID,
70799
+ CODEX_APP_DIRS_ENV,
70800
+ CODEX_AUTH_PROBE_TIMEOUT_MS,
70801
+ CODEX_COMMAND_SOURCES,
70802
+ CODEX_PROBE_TIMEOUT_MS,
70803
+ CODEX_READINESS_CACHE_TTL_MS,
70804
+ CODEX_READINESS_STATES,
70805
+ CODEX_RECOVERY_ACTIONS,
69516
70806
  CommandStore,
69517
70807
  DEFAULT_AI_PERMISSION_PRESET,
69518
70808
  DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC,
@@ -69522,10 +70812,13 @@ init_autostart_windows();
69522
70812
  HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS,
69523
70813
  InboundReplayGate,
69524
70814
  InvalidXmtpMessageStore,
70815
+ JOB_PROVIDER_BINDING_SOURCES,
70816
+ LogEvent,
69525
70817
  MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG,
69526
70818
  MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN,
69527
70819
  NATIVE_LAUNCHER_EXE_NAME,
69528
70820
  OPENCLAW_GATEWAY_ROUTE_GROUP_ID,
70821
+ ProviderNotReadyError,
69529
70822
  SYSTEM_NOTIFICATION_SESSION_KEY,
69530
70823
  SessionBusyTracker,
69531
70824
  SessionStore,
@@ -69546,15 +70839,20 @@ init_autostart_windows();
69546
70839
  activeDaemonPointerPath,
69547
70840
  aiRunSentryExtra,
69548
70841
  assertHermesSetupSupportedOnPlatform,
70842
+ authStatusFromReason,
69549
70843
  bindJobProviderToCurrentDefaultIfMissing,
70844
+ bindJobProviderToCurrentDefaultIfReady,
69550
70845
  bindJobProviderToCurrentRuntimeIfMissing,
70846
+ bindJobProviderToCurrentRuntimeIfReady,
69551
70847
  bindMessageJobProviderToCurrentDefault,
70848
+ bindNewJobProviderIfReady,
69552
70849
  bindOpenClawGatewayRouteFromEnv,
69553
70850
  buildAgentMessageNotice,
69554
70851
  buildAiAdapterCommand,
69555
70852
  buildAiProviderEnv,
69556
70853
  buildAutostartVbs,
69557
70854
  buildBackupJobSessionKey,
70855
+ buildCodexRecoveryGuidance,
69558
70856
  buildDispatchSessionKey,
69559
70857
  buildDoctorSentryExtra,
69560
70858
  buildExternalCommandInvocation,
@@ -69583,7 +70881,15 @@ init_autostart_windows();
69583
70881
  callSessionsCreate,
69584
70882
  callSessionsDelete,
69585
70883
  callSessionsSend,
70884
+ checkClaudeCliAuthStatus,
70885
+ checkCodexCliAuthStatus,
70886
+ checkCodexReadiness,
69586
70887
  claimActiveDaemon,
70888
+ classifyJobBindingSource,
70889
+ clearCodexReadinessCache,
70890
+ codexCommandResolvesWithoutOverride,
70891
+ codexReadinessCacheKey,
70892
+ collectCodexAppBundledCandidates,
69587
70893
  collectCodexCommandCandidates,
69588
70894
  commandExists,
69589
70895
  createAiToolFailureTracker,
@@ -69599,6 +70905,7 @@ init_autostart_windows();
69599
70905
  ensureDaemonReady,
69600
70906
  ensureDefaultAiProvider,
69601
70907
  ensureOpenClawOkxA2aPluginConfig,
70908
+ ensureProviderReadyForBinding,
69602
70909
  ensureWindowsNativeLauncher,
69603
70910
  evictPid,
69604
70911
  exportDiagnosticLogs,
@@ -69626,6 +70933,7 @@ init_autostart_windows();
69626
70933
  isDefaultTaskHome,
69627
70934
  isGatewayAvailableForHeartbeat,
69628
70935
  isHermesGatewayPluginEnabled,
70936
+ isInfoEventAllowlisted,
69629
70937
  isOfflineReplayForTrigger,
69630
70938
  isOpenClawA2aPluginAvailable,
69631
70939
  isOpenClawGatewayAvailable,
@@ -69651,11 +70959,15 @@ init_autostart_windows();
69651
70959
  parsePluginYamlVersion,
69652
70960
  parseWindowsParentProcessJson,
69653
70961
  performRuntimeSwitch,
70962
+ persistCurrentRuntimeSelectionForNewJob,
69654
70963
  pickOnchainosWin32Candidate,
70964
+ prepareAndBindCurrentRuntimeForNewJob,
69655
70965
  printLogsExportUsage,
69656
70966
  printXmtpTestUsage,
70967
+ probeCodexAuthStatus,
69657
70968
  processFileMessage,
69658
70969
  readAiProviderTimeoutMs,
70970
+ readCachedCodexReadiness,
69659
70971
  readLastLines,
69660
70972
  readParentProcessCommandForPlatform,
69661
70973
  readUserAttentionWatcherEvent,
@@ -69669,8 +70981,12 @@ init_autostart_windows();
69669
70981
  resetResolvedOnchainosBinForTests,
69670
70982
  resolveAiPermissionPreset,
69671
70983
  resolveAiProviderCommand,
70984
+ resolveAiProviderCommandWithSource,
69672
70985
  resolveAiProviderForDispatch,
70986
+ resolveAiProviderForDispatchWithReadiness,
70987
+ resolveCodexCommandCandidates,
69673
70988
  resolveCodexCommandPath,
70989
+ resolveCodexCommandSource,
69674
70990
  resolveConfiguredAiProvider,
69675
70991
  resolveConfiguredAiProviderForJob,
69676
70992
  resolveDirectCommunicationSessionTarget,
@@ -69682,6 +70998,7 @@ init_autostart_windows();
69682
70998
  resolveOpenClawGatewayConfig,
69683
70999
  resolveOpenClawGatewayRoute,
69684
71000
  resolveOpenClawGatewayRoutes,
71001
+ resolveProviderCommandWithSelfHeal,
69685
71002
  resolveSystemNotificationTargets,
69686
71003
  resolveTaskConfigPath,
69687
71004
  resolveTaskHome,
@@ -69709,6 +71026,8 @@ init_autostart_windows();
69709
71026
  subscribeUserAttentionEvents,
69710
71027
  summarizeXmtpTestEvents,
69711
71028
  switchProvider,
71029
+ switchProviderWithReadinessGate,
71030
+ switchProviderWithReadinessGateCore,
69712
71031
  systemdUnitHasCurrentRestartPolicy,
69713
71032
  toOpenClawGatewaySessionKey,
69714
71033
  toWindowsInvocation,