@okxweb3/a2a-node 0.2.2 → 0.2.3-beta-6f2a8ec2c7-260810184309

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 +21739 -20400
  2. package/dist/index.js +2584 -1099
  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([
@@ -20027,7 +19446,15 @@ function classifyError(error, code) {
20027
19446
  if (normalizedCode === "ABORT_ERR" || name === "aborterror") {
20028
19447
  return "cancelled";
20029
19448
  }
20030
- if (name === "syntaxerror" || /(?:json|response).*(?:parse|invalid)/.test(message)) {
19449
+ if (name === "syntaxerror") {
19450
+ return "invalid_response";
19451
+ }
19452
+ if (/connection\s+(?:reset|closed|interrupted)|broken\s+pipe|unexpected\s+eof/.test(
19453
+ message
19454
+ )) {
19455
+ return "connection_interrupted";
19456
+ }
19457
+ if (/(?:json|response).*(?:parse|invalid)/.test(message)) {
20031
19458
  return "invalid_response";
20032
19459
  }
20033
19460
  if (/session\s+(?:is\s+)?expired|login\s+expired/.test(message)) {
@@ -20182,6 +19609,9 @@ function applyFatalEventDefaults(event) {
20182
19609
  }
20183
19610
  return event;
20184
19611
  }
19612
+ function isInfoEventAllowlisted(name) {
19613
+ return SENTRY_INFO_ALLOWLIST.has(name);
19614
+ }
20185
19615
  function agentExtras(identity) {
20186
19616
  return {
20187
19617
  walletAddress: identity.walletAddress || UNKNOWN_FIELD,
@@ -20321,6 +19751,7 @@ var init_sentry_logger = __esm({
20321
19751
  "releaseBase",
20322
19752
  "releaseChannel",
20323
19753
  "releaseCode",
19754
+ "replayOutcome",
20324
19755
  "role",
20325
19756
  "runId",
20326
19757
  "runtimeContainer",
@@ -20387,6 +19818,13 @@ var init_sentry_logger = __esm({
20387
19818
  LogEvent.AI_RUN_STARTED,
20388
19819
  LogEvent.AI_RUN_COMPLETED,
20389
19820
  LogEvent.AI_RUN_SKIPPED,
19821
+ // Codex runtime-binding checkpoints. Omitting any of these makes info() drop
19822
+ // the event silently, so the whole FR-4 trace would look implemented in code
19823
+ // and emit nothing in production.
19824
+ LogEvent.RUNTIME_DETECTED,
19825
+ LogEvent.PROVIDER_READINESS_CHECKED,
19826
+ LogEvent.PROVIDER_SWITCHED,
19827
+ LogEvent.JOB_PROVIDER_BOUND,
20390
19828
  LogEvent.USER_CHANNEL_MESSAGE_DELIVERED,
20391
19829
  LogEvent.SYSTEM_NOTIFICATION_RECEIVED,
20392
19830
  LogEvent.SYSTEM_NOTIFICATION_ROUTED,
@@ -20567,120 +20005,1637 @@ var init_sentry_logger = __esm({
20567
20005
  } catch {
20568
20006
  }
20569
20007
  }
20570
- async shutdown() {
20571
- try {
20572
- await Sentry.close(5e3);
20573
- } catch {
20574
- await Sentry.flush(5e3);
20575
- }
20008
+ async shutdown() {
20009
+ try {
20010
+ await Sentry.close(5e3);
20011
+ } catch {
20012
+ await Sentry.flush(5e3);
20013
+ }
20014
+ }
20015
+ flush() {
20016
+ while (this.buffer.length > 0) {
20017
+ const entry = this.buffer.shift();
20018
+ if (entry.level === "info") {
20019
+ this.captureInfo(entry.message, entry.extra ?? {});
20020
+ } else {
20021
+ this.captureError(entry.message, entry.error, entry.extra ?? {});
20022
+ }
20023
+ }
20024
+ }
20025
+ static sanitizeExtra(extra) {
20026
+ return Object.fromEntries(
20027
+ Object.entries(extra).filter(([, value]) => value !== void 0).filter(([key]) => !_SentryLogger.isBlockedExtraKey(key)).map(([key, value]) => [key, _SentryLogger.safeValueForKey(key, value)])
20028
+ );
20029
+ }
20030
+ static isBlockedExtraKey(key) {
20031
+ const lower = key.toLowerCase();
20032
+ const normalized = _SentryLogger.normalizeSensitiveKey(key);
20033
+ if (SENTRY_EXTRA_BLOCKLIST.has(lower) || SENTRY_EXTRA_BLOCKLIST.has(normalized)) {
20034
+ return true;
20035
+ }
20036
+ if (_SentryLogger.isSafeMetricKey(normalized)) {
20037
+ return false;
20038
+ }
20039
+ return SENTRY_EXTRA_BLOCKED_KEY_PARTS.some((needle) => normalized.includes(needle));
20040
+ }
20041
+ static isSafeMetricKey(normalized) {
20042
+ return /(bytes|count|length|ms|size)$/.test(normalized);
20043
+ }
20044
+ static safeValueForKey(key, value) {
20045
+ if (typeof value === "string" && SENTRY_FULL_STRING_EXTRA_KEYS.has(_SentryLogger.normalizeSensitiveKey(key))) {
20046
+ return value;
20047
+ }
20048
+ return _SentryLogger.safeValue(value);
20049
+ }
20050
+ static normalizeSensitiveKey(key) {
20051
+ return key.toLowerCase().replace(/[^a-z0-9]/g, "");
20052
+ }
20053
+ static safeValue(value) {
20054
+ if (typeof value === "string") {
20055
+ return value.length <= MAX_EXTRA_STRING_LENGTH ? value : `${value.slice(0, MAX_EXTRA_STRING_LENGTH)}...`;
20056
+ }
20057
+ if (value === null || value === void 0 || typeof value === "number" || typeof value === "boolean") {
20058
+ return value;
20059
+ }
20060
+ if (typeof value === "bigint") {
20061
+ return value.toString();
20062
+ }
20063
+ if (value instanceof Error) {
20064
+ return {
20065
+ name: value.name,
20066
+ messageLength: value.message.length
20067
+ };
20068
+ }
20069
+ if (Array.isArray(value)) {
20070
+ return value.slice(0, 20).map((item) => _SentryLogger.safeValue(item));
20071
+ }
20072
+ if (typeof value === "object") {
20073
+ return Object.fromEntries(
20074
+ Object.entries(value).slice(0, 50).filter(([, item]) => item !== void 0).filter(([key]) => !_SentryLogger.isBlockedExtraKey(key)).map(([key, item]) => [key, _SentryLogger.safeValueForKey(key, item)])
20075
+ );
20076
+ }
20077
+ const text = String(value);
20078
+ return text.length <= MAX_EXTRA_STRING_LENGTH ? text : `${text.slice(0, MAX_EXTRA_STRING_LENGTH)}...`;
20079
+ }
20080
+ static tagValue(value) {
20081
+ const text = String(value);
20082
+ return text.length <= MAX_TAG_VALUE_LENGTH ? text : `${text.slice(0, MAX_TAG_VALUE_LENGTH)}...`;
20083
+ }
20084
+ static normalizeTagKey(key) {
20085
+ return key.replace(/[^A-Za-z0-9_.:-]+/g, "_").slice(0, 32) || "unknown";
20086
+ }
20087
+ static applyDiagnostics(scope, eventName, extra) {
20088
+ const enriched = { eventName, ...extra };
20089
+ for (const [key, value] of Object.entries(enriched)) {
20090
+ if (!SENTRY_TAG_KEYS.has(key)) {
20091
+ continue;
20092
+ }
20093
+ scope.setTag(_SentryLogger.normalizeTagKey(key), _SentryLogger.tagValue(value));
20094
+ }
20095
+ const fingerprint = ["a2a", eventName];
20096
+ for (const key of SENTRY_FINGERPRINT_KEYS) {
20097
+ const value = enriched[key];
20098
+ if (value) {
20099
+ fingerprint.push(`${key}:${_SentryLogger.tagValue(value)}`);
20100
+ }
20101
+ }
20102
+ scope.setFingerprint(fingerprint);
20103
+ }
20104
+ static toPascalCase(str) {
20105
+ return str.split(/[\s:]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
20106
+ }
20107
+ static createSentryEvent(eventName) {
20108
+ const err = new Error(eventName);
20109
+ err.name = _SentryLogger.toPascalCase(eventName);
20110
+ err.stack = `${err.name}: ${eventName}`;
20111
+ return err;
20112
+ }
20113
+ };
20114
+ logger = SentryLogger.getInstance();
20115
+ initLogger = (config) => {
20116
+ return logger.init(config);
20117
+ };
20118
+ shutdown = () => {
20119
+ return logger.shutdown();
20120
+ };
20121
+ UNKNOWN_FIELD = "unknown";
20122
+ }
20123
+ });
20124
+
20125
+ // src/ai-provider.ts
20126
+ function normalizeAiProvider(value) {
20127
+ const normalized = value.trim().toLowerCase();
20128
+ if (normalized === "codex") {
20129
+ return "codex";
20130
+ }
20131
+ if (normalized === "claude" || normalized === "claude-code" || normalized === "claude_code") {
20132
+ return "claude";
20133
+ }
20134
+ if (normalized === "hermes") {
20135
+ return "hermes";
20136
+ }
20137
+ if (normalized === "openclaw") {
20138
+ return "openclaw";
20139
+ }
20140
+ throw new Error(`Unsupported AI provider "${value}". Use one of: ${AI_PROVIDERS.join(", ")}.`);
20141
+ }
20142
+ function detectAiProviders(commandExists2 = commandExists, env = process.env) {
20143
+ const codex = commandExists2(readProviderCommand("codex", env));
20144
+ const claude = commandExists2(readProviderCommand("claude", env));
20145
+ const hermes = commandExists2(readProviderCommand("hermes", env));
20146
+ const openclaw = commandExists2(readProviderCommand("openclaw", env));
20147
+ return {
20148
+ codex,
20149
+ claude,
20150
+ hermes,
20151
+ openclaw,
20152
+ available: [
20153
+ ...codex ? ["codex"] : [],
20154
+ ...claude ? ["claude"] : [],
20155
+ ...hermes ? ["hermes"] : [],
20156
+ ...openclaw ? ["openclaw"] : []
20157
+ ]
20158
+ };
20159
+ }
20160
+ function detectCurrentAiProvider(env = process.env) {
20161
+ const explicit = env.OKX_A2A_AI_PROVIDER ?? env.OKX_AGENT_TASK_AI_CLI;
20162
+ if (explicit) {
20163
+ return normalizeAiProvider(explicit);
20164
+ }
20165
+ const runtime = detectRuntime(env);
20166
+ return runtime === "unknown" ? null : runtime;
20167
+ }
20168
+ function hasAiRuntimeMarker(env = process.env) {
20169
+ 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);
20170
+ }
20171
+ function detectRuntime(env = process.env, options = {}) {
20172
+ const gatewayInvocation = detectGatewayInvocation(env);
20173
+ const strongMatches = [
20174
+ ...gatewayInvocation ? [gatewayInvocation] : [],
20175
+ ...env.CODEX_THREAD_ID || isTruthyRuntimeMarker(env.CODEX_SHELL) || env.CODEX_MANAGED_BY_NPM || isTruthyRuntimeMarker(env.CODEX_CI) ? ["codex"] : [],
20176
+ ...env.CLAUDE_SESSION_ID || isTruthyRuntimeMarker(env.CLAUDECODE) || env.CLAUDE_CODE_SESSION_ID || env.CLAUDE_PLUGIN_DATA ? ["claude"] : [],
20177
+ ...env.HERMES_SESSION_ID && !env.HERMES_DESKTOP_CWD || env.HERMES_SESSION_KEY || env.HERMES_FLOW_ID ? ["hermes"] : [],
20178
+ ...env.OPENCLAW_SHELL || env.OPENCLAW_CLI ? ["openclaw"] : [],
20179
+ ...hasOpenClawParentProcess(options.parentPid ?? process.ppid, options.readParentProcessCommand ?? readParentProcessCommand) ? ["openclaw"] : []
20180
+ ];
20181
+ const uniqueMatches = [...new Set(strongMatches)];
20182
+ if (uniqueMatches.length === 1) {
20183
+ return uniqueMatches[0];
20184
+ }
20185
+ return "unknown";
20186
+ }
20187
+ function detectGatewayInvocation(env = process.env) {
20188
+ const context = (env.OKX_A2A_RUNTIME_CONTEXT ?? "").trim().toLowerCase();
20189
+ if (context === "openclaw-gateway" || context === "gateway:openclaw") {
20190
+ return "openclaw";
20191
+ }
20192
+ if (context === "hermes-gateway" || context === "gateway:hermes") {
20193
+ return "hermes";
20194
+ }
20195
+ if (isTruthyRuntimeMarker(env._HERMES_GATEWAY)) {
20196
+ return "hermes";
20197
+ }
20198
+ if ((env.OPENCLAW_SERVICE_KIND ?? "").trim().toLowerCase() === "gateway") {
20199
+ return "openclaw";
20200
+ }
20201
+ if (!isTruthyRuntimeMarker(env.OKX_A2A_IN_GATEWAY)) {
20202
+ return null;
20203
+ }
20204
+ const provider = env.OKX_A2A_GATEWAY_PROVIDER;
20205
+ if (!provider) {
20206
+ return null;
20207
+ }
20208
+ try {
20209
+ const normalized = normalizeAiProvider(provider);
20210
+ return normalized === "openclaw" || normalized === "hermes" ? normalized : null;
20211
+ } catch {
20212
+ return null;
20213
+ }
20214
+ }
20215
+ function switchProvider(options) {
20216
+ const runtime = detectRuntime(options.env ?? process.env, {
20217
+ parentPid: options.parentPid,
20218
+ readParentProcessCommand: options.readParentProcessCommand
20219
+ });
20220
+ if (runtime === "unknown") {
20221
+ const stored = options.store.getDefaultAiProvider();
20222
+ if (stored) {
20223
+ return {
20224
+ ok: true,
20225
+ runtime: "unknown",
20226
+ provider: stored,
20227
+ previousProvider: stored,
20228
+ changed: false,
20229
+ action: "kept_existing",
20230
+ userMessage: `No AI runtime detected for the current environment; keeping the existing default provider (${formatAiProviderDisplayName(stored)}).`
20231
+ };
20232
+ }
20233
+ return {
20234
+ ok: false,
20235
+ runtime: "unknown",
20236
+ reason: "unknown_runtime",
20237
+ state: "blocked",
20238
+ message: "Could not determine the current AI provider.",
20239
+ 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>`."
20240
+ };
20241
+ }
20242
+ const previousProvider = options.store.getDefaultAiProvider();
20243
+ const changed = previousProvider !== runtime;
20244
+ options.store.setDefaultAiProvider(runtime);
20245
+ return {
20246
+ ok: true,
20247
+ runtime,
20248
+ provider: runtime,
20249
+ previousProvider,
20250
+ changed,
20251
+ action: "switched",
20252
+ userMessage: changed ? `Switched to ${formatAiProviderDisplayName(runtime)}\uFF5CNew tasks will be handled here; unfinished tasks should be continued in their original session` : ""
20253
+ };
20254
+ }
20255
+ function formatAiProviderDisplayName(provider) {
20256
+ if (provider === "openclaw") {
20257
+ return "OpenClaw";
20258
+ }
20259
+ return `${provider.charAt(0).toUpperCase()}${provider.slice(1)}`;
20260
+ }
20261
+ function isTruthyRuntimeMarker(value) {
20262
+ return value === "1" || value === "true" || value === "yes";
20263
+ }
20264
+ function hasOpenClawParentProcess(parentPid, readParentProcessCommand2) {
20265
+ let pid = parentPid;
20266
+ for (let i = 0; i < 8; i++) {
20267
+ if (!pid || pid <= 1) {
20268
+ return false;
20269
+ }
20270
+ const processInfo = readParentProcessCommand2(pid);
20271
+ if (!processInfo) {
20272
+ return false;
20273
+ }
20274
+ if (/openclaw/i.test(processInfo.command)) {
20275
+ return true;
20276
+ }
20277
+ if (!processInfo.parentPid || processInfo.parentPid === pid) {
20278
+ return false;
20279
+ }
20280
+ pid = processInfo.parentPid;
20281
+ }
20282
+ return false;
20283
+ }
20284
+ function readParentProcessCommand(pid) {
20285
+ return readParentProcessCommandForPlatform(pid, process.platform);
20286
+ }
20287
+ function readParentProcessCommandForPlatform(pid, platform) {
20288
+ if (platform === "win32") {
20289
+ return readWindowsParentProcessCommand(pid);
20290
+ }
20291
+ const commandResult = (0, import_node_child_process5.spawnSync)("ps", ["-p", String(pid), "-o", "comm="], {
20292
+ encoding: "utf8",
20293
+ stdio: ["ignore", "pipe", "ignore"]
20294
+ });
20295
+ const parentResult = (0, import_node_child_process5.spawnSync)("ps", ["-p", String(pid), "-o", "ppid="], {
20296
+ encoding: "utf8",
20297
+ stdio: ["ignore", "pipe", "ignore"]
20298
+ });
20299
+ const command = typeof commandResult.stdout === "string" ? commandResult.stdout.trim() : "";
20300
+ const parentPidRaw = typeof parentResult.stdout === "string" ? parentResult.stdout.trim() : "";
20301
+ const parentPid = Number(parentPidRaw);
20302
+ if (commandResult.status !== 0 || parentResult.status !== 0 || !command || !Number.isFinite(parentPid)) {
20303
+ return null;
20304
+ }
20305
+ return { command, parentPid };
20306
+ }
20307
+ function buildWindowsParentProcessProbe(pid) {
20308
+ if (!Number.isInteger(pid)) {
20309
+ return null;
20310
+ }
20311
+ return {
20312
+ command: "powershell.exe",
20313
+ args: [
20314
+ "-NoProfile",
20315
+ "-NonInteractive",
20316
+ "-Command",
20317
+ `Get-CimInstance Win32_Process -Filter "ProcessId=${pid}" | Select-Object Name,ParentProcessId | ConvertTo-Json -Compress`
20318
+ ]
20319
+ };
20320
+ }
20321
+ function parseWindowsParentProcessJson(stdout) {
20322
+ const trimmed = stdout.trim();
20323
+ if (!trimmed) {
20324
+ return null;
20325
+ }
20326
+ let parsed;
20327
+ try {
20328
+ parsed = JSON.parse(trimmed);
20329
+ } catch {
20330
+ return null;
20331
+ }
20332
+ const record = Array.isArray(parsed) ? parsed[0] : parsed;
20333
+ if (!record || typeof record !== "object") {
20334
+ return null;
20335
+ }
20336
+ const name = record.Name;
20337
+ if (typeof name !== "string" || name.trim() === "") {
20338
+ return null;
20339
+ }
20340
+ const rawParentPid = Number(record.ParentProcessId);
20341
+ const parentPid = Number.isInteger(rawParentPid) && rawParentPid > 0 ? rawParentPid : null;
20342
+ return { command: name.trim(), parentPid };
20343
+ }
20344
+ function readWindowsParentProcessCommand(pid) {
20345
+ const probe = buildWindowsParentProcessProbe(pid);
20346
+ if (!probe) {
20347
+ logWinCompat(`${WIN_COMPAT_LOG_PREFIX} parent-process probe rejected non-integer pid=${String(pid)}`);
20348
+ return null;
20349
+ }
20350
+ const result = (0, import_node_child_process5.spawnSync)(probe.command, probe.args, {
20351
+ encoding: "utf8",
20352
+ stdio: ["ignore", "pipe", "ignore"],
20353
+ timeout: 5e3,
20354
+ windowsHide: true
20355
+ });
20356
+ if (result.error) {
20357
+ const spawnError = result.error;
20358
+ logWinCompat(
20359
+ `${WIN_COMPAT_LOG_PREFIX} parent-process probe failed pid=${pid} command=${probe.command} errorCode=${spawnError.code ?? "unknown"} error=${spawnError.message}`
20360
+ );
20361
+ return null;
20362
+ }
20363
+ if (result.status !== 0) {
20364
+ logWinCompat(
20365
+ `${WIN_COMPAT_LOG_PREFIX} parent-process probe exited pid=${pid} command=${probe.command} status=${String(result.status)} signal=${String(result.signal)}`
20366
+ );
20367
+ return null;
20368
+ }
20369
+ const stdout = typeof result.stdout === "string" ? result.stdout : "";
20370
+ const parsed = parseWindowsParentProcessJson(stdout);
20371
+ if (!parsed) {
20372
+ logWinCompat(
20373
+ `${WIN_COMPAT_LOG_PREFIX} parent-process probe unparsable output pid=${pid} stdout=${JSON.stringify(stdout.slice(0, 200))}`
20374
+ );
20375
+ return null;
20376
+ }
20377
+ if (process.env.OKX_A2A_WIN_COMPAT_VERBOSE === "1") {
20378
+ logWinCompat(
20379
+ `${WIN_COMPAT_LOG_PREFIX} parent-process probe pid=${pid} -> command=${parsed.command} parentPid=${parsed.parentPid ?? "null"}`
20380
+ );
20381
+ }
20382
+ return parsed;
20383
+ }
20384
+ async function ensureDefaultAiProvider(options) {
20385
+ const env = options.env ?? process.env;
20386
+ const commandExists2 = options.commandExists ?? commandExists;
20387
+ const detection = withStoredCodexAvailability(
20388
+ detectAiProviders(commandExists2, env),
20389
+ options.store,
20390
+ commandExists2,
20391
+ env
20392
+ );
20393
+ if (options.requestedProvider) {
20394
+ const provider = normalizeAiProvider(options.requestedProvider);
20395
+ if (!options.allowUnavailableProvider) {
20396
+ assertInstalled(provider, detection);
20397
+ }
20398
+ options.store.setDefaultAiProvider(provider);
20399
+ return {
20400
+ provider,
20401
+ source: "requested",
20402
+ message: isInstalled(provider, detection) ? `default AI provider set to ${provider}` : `default AI provider set to ${provider}; availability was not verified on PATH`
20403
+ };
20404
+ }
20405
+ if (options.forcePrompt) {
20406
+ const provider = await promptForAiProvider({
20407
+ detection,
20408
+ stdin: options.stdin,
20409
+ stdout: options.stdout,
20410
+ currentProvider: options.store.getDefaultAiProvider()
20411
+ });
20412
+ options.store.setDefaultAiProvider(provider);
20413
+ return {
20414
+ provider,
20415
+ source: "detected",
20416
+ message: `default AI provider set to ${provider}`
20417
+ };
20418
+ }
20419
+ if (env.OKX_AGENT_TASK_AI_CLI) {
20420
+ const provider = normalizeAiProvider(env.OKX_AGENT_TASK_AI_CLI);
20421
+ if (!options.allowUnavailableProvider) {
20422
+ assertInstalled(provider, detection);
20423
+ }
20424
+ options.store.setDefaultAiProvider(provider);
20425
+ return {
20426
+ provider,
20427
+ source: "env",
20428
+ 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`
20429
+ };
20430
+ }
20431
+ if (env.OKX_A2A_AI_PROVIDER) {
20432
+ const provider = normalizeAiProvider(env.OKX_A2A_AI_PROVIDER);
20433
+ if (!options.allowUnavailableProvider) {
20434
+ assertInstalled(provider, detection);
20435
+ }
20436
+ return {
20437
+ provider,
20438
+ source: "env",
20439
+ 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`
20440
+ };
20441
+ }
20442
+ const stored = options.store.getDefaultAiProvider();
20443
+ if (stored && (options.allowUnavailableProvider || isInstalled(stored, detection))) {
20444
+ return {
20445
+ provider: stored,
20446
+ source: "stored",
20447
+ message: isInstalled(stored, detection) ? `default AI provider is ${stored}` : `default AI provider is ${stored}; availability was not verified on PATH`
20448
+ };
20449
+ }
20450
+ if (stored) {
20451
+ options.stderr?.write(
20452
+ `[okx-agent-task] stored AI provider "${stored}" is not installed or not on PATH; selecting again.
20453
+ `
20454
+ );
20455
+ }
20456
+ if (options.promptIfMissing) {
20457
+ const provider = await promptForAiProvider({
20458
+ detection,
20459
+ stdin: options.stdin,
20460
+ stdout: options.stdout,
20461
+ currentProvider: stored ?? null
20462
+ });
20463
+ options.store.setDefaultAiProvider(provider);
20464
+ return {
20465
+ provider,
20466
+ source: "detected",
20467
+ message: `default AI provider set to ${provider}`
20468
+ };
20469
+ }
20470
+ const current = detectCurrentAiProvider(env);
20471
+ if (current && (options.allowUnavailableProvider || isInstalled(current, detection))) {
20472
+ return {
20473
+ provider: current,
20474
+ source: "env",
20475
+ message: isInstalled(current, detection) ? `AI provider resolved from current environment: ${current}` : `AI provider resolved from current environment: ${current}; availability was not verified on PATH`
20476
+ };
20477
+ }
20478
+ if (detection.available.length === 0) {
20479
+ throw new Error(
20480
+ `No supported AI CLI found. Install one of: ${AI_PROVIDERS.join(", ")}.`
20481
+ );
20482
+ }
20483
+ if (detection.available.length === 1) {
20484
+ const provider = detection.available[0];
20485
+ options.store.setDefaultAiProvider(provider);
20486
+ return {
20487
+ provider,
20488
+ source: "detected",
20489
+ message: `detected only ${provider}; default AI provider set to ${provider}`
20490
+ };
20491
+ }
20492
+ throw new Error(
20493
+ `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.`
20494
+ );
20495
+ }
20496
+ function resolveConfiguredAiProvider(options) {
20497
+ const env = options.env ?? process.env;
20498
+ const explicit = env.OKX_AGENT_TASK_AI_CLI ?? env.OKX_A2A_AI_PROVIDER;
20499
+ if (explicit) {
20500
+ return normalizeAiProvider(explicit);
20501
+ }
20502
+ return options.store.getDefaultAiProvider();
20503
+ }
20504
+ function resolveConfiguredAiProviderForJob(options) {
20505
+ const jobId = normalizeOptionalJobId2(options.jobId);
20506
+ if (jobId) {
20507
+ const binding = options.store.getJobProviderBinding(jobId);
20508
+ if (binding) {
20509
+ bindHermesJobRouteFromEnvIfAvailable(options.store, {
20510
+ jobId,
20511
+ provider: binding.provider,
20512
+ env: options.env
20513
+ });
20514
+ return binding.provider;
20515
+ }
20516
+ }
20517
+ const provider = resolveConfiguredAiProvider({
20518
+ store: options.store,
20519
+ env: options.env
20520
+ });
20521
+ if (jobId && provider) {
20522
+ return bindJobProviderWithRouteIfAvailable(options.store, {
20523
+ jobId,
20524
+ provider,
20525
+ env: options.env
20526
+ }).binding.provider;
20527
+ }
20528
+ return provider;
20529
+ }
20530
+ function bindJobProviderToCurrentDefaultIfMissing(options) {
20531
+ const store = options.store ?? null;
20532
+ const jobId = normalizeOptionalJobId2(options.jobId);
20533
+ if (!store || !jobId) {
20534
+ return null;
20535
+ }
20536
+ const existing = store.getJobProviderBinding(jobId);
20537
+ if (existing) {
20538
+ bindHermesJobRouteFromEnvIfAvailable(store, {
20539
+ jobId,
20540
+ provider: existing.provider,
20541
+ env: options.env
20542
+ });
20543
+ return {
20544
+ provider: existing.provider,
20545
+ created: false
20546
+ };
20547
+ }
20548
+ const provider = store.getDefaultAiProvider();
20549
+ if (!provider) {
20550
+ return null;
20551
+ }
20552
+ const result = bindJobProviderWithRouteIfAvailable(store, {
20553
+ jobId,
20554
+ provider,
20555
+ env: options.env
20556
+ });
20557
+ return {
20558
+ provider: result.binding.provider,
20559
+ created: result.created
20560
+ };
20561
+ }
20562
+ function bindJobProviderToCurrentRuntimeIfMissing(options) {
20563
+ const store = options.store ?? null;
20564
+ const jobId = normalizeOptionalJobId2(options.jobId);
20565
+ if (!store || !jobId) {
20566
+ return {
20567
+ ok: false,
20568
+ jobId: jobId ?? "",
20569
+ provider: null,
20570
+ currentProvider: null,
20571
+ created: false,
20572
+ binding: null,
20573
+ reason: "missing_job_id"
20574
+ };
20575
+ }
20576
+ const existing = store.getJobProviderBinding(jobId);
20577
+ if (existing) {
20578
+ const binding = bindHermesJobRouteFromEnvIfAvailable(store, {
20579
+ jobId,
20580
+ provider: existing.provider,
20581
+ env: options.env
20582
+ }) ?? existing;
20583
+ return {
20584
+ ok: true,
20585
+ jobId,
20586
+ provider: binding.provider,
20587
+ currentProvider: null,
20588
+ created: false,
20589
+ binding,
20590
+ reason: "already_bound"
20591
+ };
20592
+ }
20593
+ const switched = switchProvider({ store, env: options.env ?? process.env });
20594
+ if (!switched.ok || !switched.provider) {
20595
+ return {
20596
+ ok: false,
20597
+ jobId,
20598
+ provider: null,
20599
+ currentProvider: null,
20600
+ created: false,
20601
+ binding: null,
20602
+ reason: "unknown_runtime"
20603
+ };
20604
+ }
20605
+ const currentProvider = switched.provider;
20606
+ const result = bindJobProviderWithRouteIfAvailable(store, {
20607
+ jobId,
20608
+ provider: currentProvider,
20609
+ env: options.env
20610
+ });
20611
+ return {
20612
+ ok: true,
20613
+ jobId,
20614
+ provider: result.binding.provider,
20615
+ currentProvider,
20616
+ created: result.created,
20617
+ binding: result.binding,
20618
+ reason: result.created ? "created" : "already_bound"
20619
+ };
20620
+ }
20621
+ async function ensureProviderReadyForBinding(options) {
20622
+ if (options.provider !== "codex") {
20623
+ return notRequiredReadinessGate(options.provider);
20624
+ }
20625
+ const check = options.readiness ?? checkCodexReadiness;
20626
+ const result = await check({
20627
+ store: options.store ?? null,
20628
+ env: options.env ?? process.env,
20629
+ platform: options.platform,
20630
+ persistOverrideCommand: options.persistOverrideCommand
20631
+ });
20632
+ return {
20633
+ provider: "codex",
20634
+ ready: result.ready,
20635
+ state: result.state,
20636
+ commandSource: result.commandSource,
20637
+ commandPersisted: result.commandPersisted,
20638
+ recoveryAction: result.recoveryAction,
20639
+ recoveryGuidance: result.recoveryGuidance
20640
+ };
20641
+ }
20642
+ async function switchProviderWithReadinessGateCore(options) {
20643
+ const env = options.env ?? process.env;
20644
+ const runtime = detectRuntime(env, {
20645
+ parentPid: options.parentPid,
20646
+ readParentProcessCommand: options.readParentProcessCommand
20647
+ });
20648
+ const delegate = () => switchProvider({
20649
+ store: options.store,
20650
+ env,
20651
+ parentPid: options.parentPid,
20652
+ readParentProcessCommand: options.readParentProcessCommand
20653
+ });
20654
+ if (runtime !== "codex") {
20655
+ const result2 = delegate();
20656
+ return {
20657
+ result: result2.ok ? result2 : { ...result2, reasonCode: "runtime_undetectable" },
20658
+ runtime,
20659
+ readiness: null
20660
+ };
20661
+ }
20662
+ const readiness = await ensureProviderReadyForBinding({
20663
+ provider: "codex",
20664
+ store: options.store,
20665
+ env,
20666
+ platform: options.platform,
20667
+ readiness: options.readiness
20668
+ });
20669
+ if (!readiness.ready) {
20670
+ const previousProvider = options.store.getDefaultAiProvider();
20671
+ const keepingClause = previousProvider ? `keeping ${formatAiProviderDisplayName(previousProvider)} as the default provider` : "the default provider was not changed";
20672
+ return {
20673
+ result: {
20674
+ ok: false,
20675
+ runtime,
20676
+ reason: "provider_not_ready",
20677
+ reasonCode: "provider_not_ready",
20678
+ state: "blocked",
20679
+ message: `Codex is not ready (${readiness.state}); the default AI provider was not changed.`,
20680
+ userMessage: `Codex is not ready (${readiness.state}); ${keepingClause}. ${readiness.recoveryGuidance}`,
20681
+ previousProvider,
20682
+ detectedProvider: "codex",
20683
+ effectiveProvider: previousProvider,
20684
+ readinessState: readiness.state,
20685
+ commandSource: readiness.commandSource,
20686
+ recoveryAction: readiness.recoveryAction,
20687
+ recoveryGuidance: readiness.recoveryGuidance
20688
+ },
20689
+ runtime,
20690
+ readiness
20691
+ };
20692
+ }
20693
+ const result = delegate();
20694
+ if (!result.ok) {
20695
+ return { result, runtime, readiness };
20696
+ }
20697
+ return {
20698
+ result: {
20699
+ ...result,
20700
+ detectedProvider: "codex",
20701
+ effectiveProvider: result.provider,
20702
+ readinessState: readiness.state,
20703
+ commandSource: readiness.commandSource,
20704
+ commandPersisted: readiness.commandPersisted
20705
+ },
20706
+ runtime,
20707
+ readiness
20708
+ };
20709
+ }
20710
+ function classifyJobBindingSource(env = process.env, options = {}) {
20711
+ if (env.OKX_AGENT_TASK_AI_CLI ?? env.OKX_A2A_AI_PROVIDER) {
20712
+ return "explicit_env";
20713
+ }
20714
+ return detectRuntime(env, options) === "unknown" ? "default_provider" : "current_runtime";
20715
+ }
20716
+ async function bindJobProviderToCurrentDefaultIfReady(options) {
20717
+ const store = options.store ?? null;
20718
+ const jobId = normalizeOptionalJobId2(options.jobId);
20719
+ if (!store || !jobId) {
20720
+ return null;
20721
+ }
20722
+ const existing = store.getJobProviderBinding(jobId);
20723
+ if (existing) {
20724
+ return alreadyBoundGateResult(store, jobId, existing.provider, options.env);
20725
+ }
20726
+ const provider = store.getDefaultAiProvider();
20727
+ if (!provider) {
20728
+ return null;
20729
+ }
20730
+ return await bindResolvedJobProviderIfReady({
20731
+ store,
20732
+ jobId,
20733
+ provider,
20734
+ bindingSource: "default_provider",
20735
+ env: options.env,
20736
+ platform: options.platform,
20737
+ readiness: options.readiness
20738
+ });
20739
+ }
20740
+ async function bindJobProviderToCurrentRuntimeIfReady(options) {
20741
+ const store = options.store ?? null;
20742
+ const jobId = normalizeOptionalJobId2(options.jobId);
20743
+ if (!store || !jobId) {
20744
+ return {
20745
+ ok: false,
20746
+ jobId: jobId ?? "",
20747
+ provider: null,
20748
+ currentProvider: null,
20749
+ created: false,
20750
+ degraded: false,
20751
+ bindingSource: null,
20752
+ readiness: null,
20753
+ reason: "missing_job_id"
20754
+ };
20755
+ }
20756
+ const existing = store.getJobProviderBinding(jobId);
20757
+ if (existing) {
20758
+ return alreadyBoundGateResult(store, jobId, existing.provider, options.env);
20759
+ }
20760
+ const env = options.env ?? process.env;
20761
+ const explicit = env.OKX_AGENT_TASK_AI_CLI ?? env.OKX_A2A_AI_PROVIDER;
20762
+ if (explicit) {
20763
+ return await bindResolvedJobProviderIfReady({
20764
+ store,
20765
+ jobId,
20766
+ provider: normalizeAiProvider(explicit),
20767
+ bindingSource: "explicit_env",
20768
+ env,
20769
+ platform: options.platform,
20770
+ readiness: options.readiness
20771
+ });
20772
+ }
20773
+ const switched = await switchProviderWithReadinessGateCore({
20774
+ store,
20775
+ env,
20776
+ platform: options.platform,
20777
+ readiness: options.readiness
20778
+ });
20779
+ if (!switched.result.ok) {
20780
+ const notReady = switched.result.reason === "provider_not_ready";
20781
+ if (switched.readiness) {
20782
+ logProviderReadinessChecked(jobId, switched.readiness);
20783
+ }
20784
+ return {
20785
+ ok: false,
20786
+ jobId,
20787
+ provider: null,
20788
+ currentProvider: switched.result.detectedProvider ?? null,
20789
+ created: false,
20790
+ degraded: notReady,
20791
+ bindingSource: null,
20792
+ readiness: switched.readiness,
20793
+ reason: notReady ? "provider_not_ready" : "unknown_runtime"
20794
+ };
20795
+ }
20796
+ return await bindResolvedJobProviderIfReady({
20797
+ store,
20798
+ jobId,
20799
+ provider: switched.result.provider,
20800
+ bindingSource: switched.runtime === "unknown" ? "default_provider" : "current_runtime",
20801
+ env,
20802
+ platform: options.platform,
20803
+ readiness: options.readiness,
20804
+ // Reuse the gate the switch already evaluated instead of probing twice.
20805
+ gate: switched.readiness
20806
+ });
20807
+ }
20808
+ function persistDefaultProviderIfChanged(store, provider) {
20809
+ if (store.getDefaultAiProvider() === provider) {
20810
+ return false;
20811
+ }
20812
+ store.setDefaultAiProvider(provider);
20813
+ return true;
20814
+ }
20815
+ function resolveNewJobProviderCandidate(store, env) {
20816
+ const explicit = env.OKX_AGENT_TASK_AI_CLI ?? env.OKX_A2A_AI_PROVIDER;
20817
+ if (explicit) {
20818
+ return { provider: normalizeAiProvider(explicit), bindingSource: "explicit_env" };
20819
+ }
20820
+ const stored = store.getDefaultAiProvider();
20821
+ return stored ? { provider: stored, bindingSource: "default_provider" } : null;
20822
+ }
20823
+ async function bindNewJobProviderIfReady(options) {
20824
+ const store = options.store ?? null;
20825
+ const jobId = normalizeOptionalJobId2(options.jobId);
20826
+ if (!store || !jobId) {
20827
+ return null;
20828
+ }
20829
+ const existing = store.getJobProviderBinding(jobId);
20830
+ if (existing) {
20831
+ return alreadyBoundGateResult(store, jobId, existing.provider, options.env);
20832
+ }
20833
+ const env = options.env ?? process.env;
20834
+ const candidate = resolveNewJobProviderCandidate(store, env);
20835
+ if (!candidate) {
20836
+ return null;
20837
+ }
20838
+ return await bindResolvedJobProviderIfReady({
20839
+ store,
20840
+ jobId,
20841
+ provider: candidate.provider,
20842
+ bindingSource: candidate.bindingSource,
20843
+ env,
20844
+ platform: options.platform,
20845
+ readiness: options.readiness
20846
+ });
20847
+ }
20848
+ async function persistCurrentRuntimeSelectionForNewJob(options) {
20849
+ const store = options.store ?? null;
20850
+ if (!store) {
20851
+ return runtimeSelectionResult({ reason: "no_provider", ok: false });
20852
+ }
20853
+ const env = options.env ?? process.env;
20854
+ const jobId = normalizeOptionalJobId2(options.jobId);
20855
+ const previousProvider = store.getDefaultAiProvider();
20856
+ if (jobId && store.getJobProviderBinding(jobId)) {
20857
+ return runtimeSelectionResult({ reason: "already_bound", ok: true, previousProvider });
20858
+ }
20859
+ const explicit = env.OKX_AGENT_TASK_AI_CLI ?? env.OKX_A2A_AI_PROVIDER;
20860
+ const explicitProvider = explicit ? normalizeAiProvider(explicit) : null;
20861
+ let candidate = null;
20862
+ let bindingSource = null;
20863
+ if (explicitProvider) {
20864
+ candidate = explicitProvider;
20865
+ bindingSource = "explicit_env";
20866
+ } else {
20867
+ const runtime = detectRuntime(env, {
20868
+ parentPid: options.parentPid,
20869
+ readParentProcessCommand: options.readParentProcessCommand
20870
+ });
20871
+ if (runtime !== "unknown") {
20872
+ candidate = runtime;
20873
+ bindingSource = "current_runtime";
20874
+ } else if (previousProvider === "codex") {
20875
+ candidate = "codex";
20876
+ bindingSource = "default_provider";
20877
+ }
20878
+ }
20879
+ if (!candidate) {
20880
+ return runtimeSelectionResult({ reason: "runtime_undetectable", ok: true, previousProvider });
20881
+ }
20882
+ if (candidate !== "codex") {
20883
+ const changed2 = persistDefaultProviderIfChanged(store, candidate);
20884
+ return runtimeSelectionResult({
20885
+ reason: changed2 ? "persisted" : "unchanged",
20886
+ ok: true,
20887
+ provider: candidate,
20888
+ previousProvider,
20889
+ changed: changed2,
20890
+ bindingSource
20891
+ });
20892
+ }
20893
+ const readiness = await ensureProviderReadyForBinding({
20894
+ provider: "codex",
20895
+ store,
20896
+ env,
20897
+ platform: options.platform,
20898
+ readiness: options.readiness,
20899
+ // A validated override must be persisted here: this is exactly the case
20900
+ // where it has to outlive the current process.
20901
+ persistOverrideCommand: true
20902
+ });
20903
+ if (!readiness.ready) {
20904
+ return runtimeSelectionResult({
20905
+ reason: "provider_not_ready",
20906
+ ok: false,
20907
+ previousProvider,
20908
+ bindingSource,
20909
+ readiness,
20910
+ userMessage: `Codex is not ready (${readiness.state}); the default AI provider was not changed. ${readiness.recoveryGuidance}`.trim()
20911
+ });
20912
+ }
20913
+ const commandDurable = readiness.commandPersisted || codexCommandResolvesWithoutOverride(
20914
+ env,
20915
+ store.getAiProviderCommand("codex"),
20916
+ options.platform ?? process.platform
20917
+ );
20918
+ if (!commandDurable) {
20919
+ return runtimeSelectionResult({
20920
+ reason: "command_not_durable",
20921
+ ok: false,
20922
+ previousProvider,
20923
+ bindingSource,
20924
+ readiness,
20925
+ 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."
20926
+ });
20927
+ }
20928
+ const changed = persistDefaultProviderIfChanged(store, "codex");
20929
+ return runtimeSelectionResult({
20930
+ reason: changed ? "persisted" : "unchanged",
20931
+ ok: true,
20932
+ provider: "codex",
20933
+ previousProvider,
20934
+ changed,
20935
+ bindingSource,
20936
+ readiness,
20937
+ commandDurable: true
20938
+ });
20939
+ }
20940
+ async function prepareAndBindCurrentRuntimeForNewJob(options) {
20941
+ const store = options.store ?? null;
20942
+ const jobId = normalizeOptionalJobId2(options.jobId);
20943
+ if (!store || !jobId) {
20944
+ return {
20945
+ ok: false,
20946
+ jobId: jobId ?? "",
20947
+ provider: null,
20948
+ currentProvider: null,
20949
+ created: false,
20950
+ degraded: false,
20951
+ bindingSource: null,
20952
+ readiness: null,
20953
+ reason: "missing_job_id"
20954
+ };
20955
+ }
20956
+ const existing = store.getJobProviderBinding(jobId);
20957
+ if (existing) {
20958
+ return alreadyBoundGateResult(store, jobId, existing.provider, options.env);
20959
+ }
20960
+ const selection = await persistCurrentRuntimeSelectionForNewJob({
20961
+ store,
20962
+ jobId,
20963
+ env: options.env,
20964
+ platform: options.platform,
20965
+ readiness: options.readiness,
20966
+ parentPid: options.parentPid,
20967
+ readParentProcessCommand: options.readParentProcessCommand
20968
+ });
20969
+ if (selection.reason === "already_bound") {
20970
+ const raced = store.getJobProviderBinding(jobId);
20971
+ if (raced) {
20972
+ return alreadyBoundGateResult(store, jobId, raced.provider, options.env);
20973
+ }
20974
+ }
20975
+ if (!selection.ok) {
20976
+ return {
20977
+ ok: false,
20978
+ jobId,
20979
+ provider: null,
20980
+ currentProvider: selection.previousProvider,
20981
+ created: false,
20982
+ degraded: true,
20983
+ bindingSource: null,
20984
+ readiness: selection.readiness,
20985
+ reason: "provider_not_ready"
20986
+ };
20987
+ }
20988
+ if (!selection.provider) {
20989
+ return {
20990
+ ok: false,
20991
+ jobId,
20992
+ provider: null,
20993
+ currentProvider: selection.previousProvider,
20994
+ created: false,
20995
+ degraded: false,
20996
+ bindingSource: null,
20997
+ readiness: selection.readiness,
20998
+ reason: "unknown_runtime"
20999
+ };
21000
+ }
21001
+ return await bindResolvedJobProviderIfReady({
21002
+ store,
21003
+ jobId,
21004
+ provider: normalizeAiProvider(selection.provider),
21005
+ bindingSource: selection.bindingSource ?? "current_runtime",
21006
+ env: options.env,
21007
+ platform: options.platform,
21008
+ readiness: options.readiness,
21009
+ gate: selection.readiness
21010
+ });
21011
+ }
21012
+ function runtimeSelectionResult(input) {
21013
+ return {
21014
+ ok: input.ok,
21015
+ reason: input.reason,
21016
+ provider: input.provider ?? null,
21017
+ previousProvider: input.previousProvider ?? null,
21018
+ changed: input.changed ?? false,
21019
+ bindingSource: input.bindingSource ?? null,
21020
+ readiness: input.readiness ?? null,
21021
+ commandDurable: input.commandDurable ?? false,
21022
+ userMessage: input.userMessage ?? ""
21023
+ };
21024
+ }
21025
+ function notRequiredReadinessGate(provider) {
21026
+ return {
21027
+ provider,
21028
+ ready: true,
21029
+ state: "not_required",
21030
+ commandSource: null,
21031
+ commandPersisted: false,
21032
+ recoveryAction: "none",
21033
+ recoveryGuidance: ""
21034
+ };
21035
+ }
21036
+ function alreadyBoundGateResult(store, jobId, provider, env) {
21037
+ bindHermesJobRouteFromEnvIfAvailable(store, { jobId, provider, env });
21038
+ return {
21039
+ ok: true,
21040
+ jobId,
21041
+ provider,
21042
+ currentProvider: null,
21043
+ created: false,
21044
+ degraded: false,
21045
+ bindingSource: "existing_binding",
21046
+ readiness: null,
21047
+ reason: "already_bound"
21048
+ };
21049
+ }
21050
+ async function bindResolvedJobProviderIfReady(input) {
21051
+ const gate = input.gate ?? await ensureProviderReadyForBinding({
21052
+ provider: input.provider,
21053
+ store: input.store,
21054
+ env: input.env,
21055
+ platform: input.platform,
21056
+ readiness: input.readiness
21057
+ });
21058
+ logProviderReadinessChecked(input.jobId, gate);
21059
+ if (!gate.ready) {
21060
+ return {
21061
+ ok: false,
21062
+ jobId: input.jobId,
21063
+ provider: null,
21064
+ currentProvider: input.provider,
21065
+ created: false,
21066
+ degraded: true,
21067
+ bindingSource: null,
21068
+ readiness: gate,
21069
+ reason: "provider_not_ready"
21070
+ };
21071
+ }
21072
+ const result = bindJobProviderWithRouteIfAvailable(input.store, {
21073
+ jobId: input.jobId,
21074
+ provider: input.provider,
21075
+ env: input.env,
21076
+ // The gate above just returned ready for this exact provider.
21077
+ readinessVerified: true
21078
+ });
21079
+ const created = result.created;
21080
+ if (created) {
21081
+ logJobProviderBound(input.jobId, result.binding.provider, input.bindingSource, gate);
21082
+ }
21083
+ return {
21084
+ ok: true,
21085
+ jobId: input.jobId,
21086
+ provider: result.binding.provider,
21087
+ currentProvider: input.provider,
21088
+ created,
21089
+ degraded: false,
21090
+ // A concurrent writer won the INSERT: report the row as pre-existing rather
21091
+ // than claiming this call's candidate tier produced it.
21092
+ bindingSource: created ? input.bindingSource : "existing_binding",
21093
+ readiness: gate,
21094
+ reason: created ? "created" : "already_bound"
21095
+ };
21096
+ }
21097
+ function logProviderReadinessChecked(jobId, gate) {
21098
+ if (gate.state === "not_required") {
21099
+ return;
21100
+ }
21101
+ logger.info(LogEvent.PROVIDER_READINESS_CHECKED, {
21102
+ component: JOB_PROVIDER_BINDING_COMPONENT,
21103
+ jobId,
21104
+ provider: gate.provider,
21105
+ readinessState: gate.state,
21106
+ commandSource: gate.commandSource ?? "",
21107
+ commandPersisted: String(gate.commandPersisted),
21108
+ recoveryAction: gate.recoveryAction,
21109
+ ...logFieldExtras({
21110
+ checkpoint: gate.ready ? "provider_ready" : "provider_not_ready",
21111
+ outcome: gate.ready ? "success" : "failed",
21112
+ transport: "local",
21113
+ eventFamily: "dispatch"
21114
+ })
21115
+ });
21116
+ }
21117
+ function logJobProviderBound(jobId, provider, bindingSource, gate) {
21118
+ logger.info(LogEvent.JOB_PROVIDER_BOUND, {
21119
+ component: JOB_PROVIDER_BINDING_COMPONENT,
21120
+ jobId,
21121
+ provider,
21122
+ bindingSource,
21123
+ readinessState: gate.state,
21124
+ commandSource: gate.commandSource ?? "",
21125
+ ...logFieldExtras({
21126
+ checkpoint: "job_provider_bound",
21127
+ outcome: "success",
21128
+ transport: "local",
21129
+ eventFamily: "dispatch"
21130
+ })
21131
+ });
21132
+ }
21133
+ function resolveAiProviderForDispatch(options) {
21134
+ const env = options.env ?? process.env;
21135
+ const jobId = normalizeOptionalJobId2(options.jobId);
21136
+ return resolveDispatchProvider(
21137
+ options,
21138
+ (provider) => bindDispatchProviderIfNeeded(options.store, jobId, env, provider)
21139
+ );
21140
+ }
21141
+ async function resolveAiProviderForDispatchWithReadiness(options) {
21142
+ const env = options.env ?? process.env;
21143
+ const jobId = normalizeOptionalJobId2(options.jobId);
21144
+ if (!jobId || options.store.getJobProviderBinding(jobId)) {
21145
+ return resolveAiProviderForDispatch(options);
21146
+ }
21147
+ const candidate = resolveDispatchProvider(options, (provider) => provider);
21148
+ if (candidate !== "codex") {
21149
+ return resolveAiProviderForDispatch(options);
21150
+ }
21151
+ let gate = options.readiness ? await ensureProviderReadyForBinding({
21152
+ provider: candidate,
21153
+ store: options.store,
21154
+ env,
21155
+ platform: options.platform,
21156
+ readiness: options.readiness
21157
+ }) : cachedProviderReadinessGate(candidate, env);
21158
+ if (!gate) {
21159
+ gate = await ensureProviderReadyForBinding({
21160
+ provider: candidate,
21161
+ store: options.store,
21162
+ env,
21163
+ platform: options.platform
21164
+ });
21165
+ }
21166
+ logProviderReadinessChecked(jobId, gate);
21167
+ if (!gate.ready) {
21168
+ throw new ProviderNotReadyError(candidate, gate);
21169
+ }
21170
+ return resolveDispatchProvider(
21171
+ options,
21172
+ (provider) => bindDispatchProviderIfNeeded(options.store, jobId, env, provider, true)
21173
+ );
21174
+ }
21175
+ function cachedProviderReadinessGate(provider, env) {
21176
+ if (provider !== "codex") {
21177
+ return notRequiredReadinessGate(provider);
21178
+ }
21179
+ const cached = readCachedCodexReadiness({ env });
21180
+ return cached ? readinessGateFromResult(cached) : null;
21181
+ }
21182
+ function assertCodexBindingPermitted(store, input) {
21183
+ if (input.provider !== "codex" || input.readinessVerified) {
21184
+ return;
21185
+ }
21186
+ if (store.getJobProviderBinding?.(input.jobId)) {
21187
+ return;
21188
+ }
21189
+ const cached = readCachedCodexReadiness({ env: input.env ?? process.env });
21190
+ if (cached?.ready) {
21191
+ return;
21192
+ }
21193
+ throw new ProviderNotReadyError(
21194
+ input.provider,
21195
+ cached ? readinessGateFromResult(cached) : unverifiedCodexReadinessGate()
21196
+ );
21197
+ }
21198
+ function unverifiedCodexReadinessGate() {
21199
+ return {
21200
+ provider: "codex",
21201
+ ready: false,
21202
+ state: "probe_failed",
21203
+ commandSource: null,
21204
+ commandPersisted: false,
21205
+ recoveryAction: "run_setup",
21206
+ 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."
21207
+ };
21208
+ }
21209
+ function readinessGateFromResult(result) {
21210
+ return {
21211
+ provider: result.provider,
21212
+ ready: result.ready,
21213
+ state: result.state,
21214
+ commandSource: result.commandSource,
21215
+ commandPersisted: result.commandPersisted,
21216
+ recoveryAction: result.recoveryAction,
21217
+ recoveryGuidance: result.recoveryGuidance
21218
+ };
21219
+ }
21220
+ function resolveDispatchProvider(options, bind) {
21221
+ const env = options.env ?? process.env;
21222
+ const commandExists2 = options.commandExists ?? commandExists;
21223
+ const detection = withStoredCodexAvailability(
21224
+ detectAiProviders(commandExists2, env),
21225
+ options.store,
21226
+ commandExists2,
21227
+ env
21228
+ );
21229
+ const jobId = normalizeOptionalJobId2(options.jobId);
21230
+ if (jobId) {
21231
+ const binding = options.store.getJobProviderBinding(jobId);
21232
+ if (binding) {
21233
+ assertInstalled(binding.provider, detection);
21234
+ return binding.provider;
21235
+ }
21236
+ }
21237
+ if (env.OKX_AGENT_TASK_AI_CLI) {
21238
+ const provider = normalizeAiProvider(env.OKX_AGENT_TASK_AI_CLI);
21239
+ assertInstalled(provider, detection);
21240
+ return bind(provider);
21241
+ }
21242
+ if (env.OKX_A2A_AI_PROVIDER) {
21243
+ const provider = normalizeAiProvider(env.OKX_A2A_AI_PROVIDER);
21244
+ assertInstalled(provider, detection);
21245
+ return bind(provider);
21246
+ }
21247
+ if (jobId) {
21248
+ const stored2 = options.store.getDefaultAiProvider();
21249
+ if (stored2) {
21250
+ assertInstalled(stored2, detection);
21251
+ return bind(stored2);
21252
+ }
21253
+ }
21254
+ const current = detectCurrentAiProvider(env);
21255
+ if (current && isInstalled(current, detection)) {
21256
+ return bind(current);
21257
+ }
21258
+ if (options.sessionKey) {
21259
+ const existing = options.store.listAiSessions(options.sessionKey).map((session) => session.provider).filter((provider) => isInstalled(provider, detection));
21260
+ const unique = [...new Set(existing)];
21261
+ if (unique.length === 1) {
21262
+ return bind(unique[0]);
21263
+ }
21264
+ }
21265
+ const stored = options.store.getDefaultAiProvider();
21266
+ if (stored) {
21267
+ assertInstalled(stored, detection);
21268
+ return bind(stored);
21269
+ }
21270
+ if (detection.available.length === 1) {
21271
+ return bind(detection.available[0]);
21272
+ }
21273
+ if (detection.available.length > 1) {
21274
+ throw new Error(
21275
+ `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.`
21276
+ );
21277
+ }
21278
+ throw new Error(`No supported AI CLI found. Install one of: ${AI_PROVIDERS.join(", ")}.`);
21279
+ }
21280
+ function bindDispatchProviderIfNeeded(store, jobId, env, provider, readinessVerified = false) {
21281
+ if (!jobId) {
21282
+ return provider;
21283
+ }
21284
+ return bindJobProviderWithRouteIfAvailable(store, { jobId, provider, env, readinessVerified }).binding.provider;
21285
+ }
21286
+ function withStoredCodexAvailability(detection, store, commandExists2, env = process.env, platform = process.platform) {
21287
+ if (detection.codex) {
21288
+ return detection;
21289
+ }
21290
+ const command = store.getAiProviderCommand("codex");
21291
+ if (command && commandExists2(command)) {
21292
+ return withCodexAvailable(detection);
21293
+ }
21294
+ for (const candidate of collectCodexAppBundledCandidates(env, platform)) {
21295
+ if (commandExists2(candidate)) {
21296
+ return withCodexAvailable(detection);
21297
+ }
21298
+ }
21299
+ return detection;
21300
+ }
21301
+ function withCodexAvailable(detection) {
21302
+ return {
21303
+ ...detection,
21304
+ codex: true,
21305
+ available: detection.available.includes("codex") ? detection.available : ["codex", ...detection.available]
21306
+ };
21307
+ }
21308
+ function bindJobProviderWithRouteIfAvailable(store, input) {
21309
+ assertCodexBindingPermitted(store, input);
21310
+ const result = store.bindJobProviderIfMissing({
21311
+ jobId: input.jobId,
21312
+ provider: input.provider
21313
+ });
21314
+ const routed = bindHermesJobRouteFromEnvIfAvailable(store, {
21315
+ jobId: input.jobId,
21316
+ provider: result.binding.provider,
21317
+ env: input.env
21318
+ });
21319
+ return routed ? { ...result, binding: routed } : result;
21320
+ }
21321
+ function bindHermesJobRouteFromEnvIfAvailable(store, input) {
21322
+ if (input.provider !== "hermes" || typeof store.upsertJobGatewayRoute !== "function") {
21323
+ return null;
21324
+ }
21325
+ const route = currentHermesGatewayRouteFromEnv(input.env ?? process.env);
21326
+ if (!route) {
21327
+ return null;
21328
+ }
21329
+ return store.upsertJobGatewayRoute({
21330
+ jobId: input.jobId,
21331
+ provider: "hermes",
21332
+ route
21333
+ });
21334
+ }
21335
+ function currentHermesGatewayRouteFromEnv(env) {
21336
+ const platform = normalizeOptionalText(env.OKX_A2A_CURRENT_GATEWAY_PLATFORM);
21337
+ const chatId = normalizeOptionalText(env.OKX_A2A_CURRENT_GATEWAY_CHAT_ID);
21338
+ const envGatewaySessionKey = normalizeOptionalText(env.OKX_A2A_CURRENT_GATEWAY_SESSION_KEY);
21339
+ const parsedGatewaySession = parseHermesGatewaySessionKey(envGatewaySessionKey);
21340
+ if (platform && chatId) {
21341
+ const threadId = normalizeOptionalText(env.OKX_A2A_CURRENT_GATEWAY_THREAD_ID) ?? matchingParsedThreadId(parsedGatewaySession, { platform, chatId });
21342
+ return buildHermesGatewayRoute({
21343
+ platform,
21344
+ chatId,
21345
+ threadId,
21346
+ sessionKey: envGatewaySessionKey
21347
+ });
21348
+ }
21349
+ const sessionRoute = routeFromHermesSessionKey(env.HERMES_SESSION_KEY);
21350
+ return sessionRoute ? buildHermesGatewayRoute(sessionRoute) : null;
21351
+ }
21352
+ function buildHermesGatewayRoute(input) {
21353
+ const threadId = normalizeOptionalText(input.threadId) ?? "";
21354
+ const sessionKey = normalizeOptionalText(input.sessionKey) ?? (threadId ? hermesThreadSessionKey(input.platform, input.chatId, threadId) : `agent:main:${input.platform}:dm:${encodeURIComponent(input.chatId)}`);
21355
+ return {
21356
+ platform: input.platform,
21357
+ chatId: input.chatId,
21358
+ chatName: "",
21359
+ chatType: threadId ? "thread" : "dm",
21360
+ threadId,
21361
+ userId: "",
21362
+ userName: "",
21363
+ sessionKey,
21364
+ gatewaySessionKey: sessionKey
21365
+ };
21366
+ }
21367
+ function hermesThreadSessionKey(platform, chatId, threadId) {
21368
+ const encodedChatId = encodeURIComponent(chatId);
21369
+ const encodedThreadId = encodeURIComponent(threadId);
21370
+ return platform === "telegram" ? `agent:main:${platform}:dm:${encodedChatId}:${encodedThreadId}` : `agent:main:${platform}:thread:${encodedChatId}:${encodedThreadId}`;
21371
+ }
21372
+ function routeFromHermesSessionKey(value) {
21373
+ const sessionKey = normalizeOptionalText(value);
21374
+ if (!sessionKey) {
21375
+ return null;
21376
+ }
21377
+ const parsed = parseHermesGatewaySessionKey(sessionKey);
21378
+ return parsed ? { ...parsed, sessionKey } : null;
21379
+ }
21380
+ function parseHermesGatewaySessionKey(sessionKey) {
21381
+ const normalized = normalizeOptionalText(sessionKey);
21382
+ if (!normalized) {
21383
+ return null;
21384
+ }
21385
+ const parts = normalized.startsWith("agent:") ? normalized.split(":").slice(2) : normalized.split(":");
21386
+ if (parts.length < 3) {
21387
+ return null;
21388
+ }
21389
+ const [platform, scope, ...rest] = parts;
21390
+ if (!platform || platform === "okx-a2a" || platform === "backup" || platform === "job") {
21391
+ return null;
21392
+ }
21393
+ if (scope === "dm" && rest[0]) {
21394
+ return {
21395
+ platform,
21396
+ chatId: safeDecodeURIComponent2(rest[0]),
21397
+ ...platform === "telegram" && rest[1] ? { threadId: safeDecodeURIComponent2(rest.slice(1).join(":")) } : {}
21398
+ };
21399
+ }
21400
+ if (scope === "thread" && rest[0] && rest[1]) {
21401
+ return {
21402
+ platform,
21403
+ chatId: safeDecodeURIComponent2(rest[0]),
21404
+ threadId: safeDecodeURIComponent2(rest.slice(1).join(":"))
21405
+ };
21406
+ }
21407
+ return null;
21408
+ }
21409
+ function matchingParsedThreadId(parsed, expected) {
21410
+ if (!parsed?.threadId) {
21411
+ return null;
21412
+ }
21413
+ return parsed.platform === expected.platform && parsed.chatId === expected.chatId ? parsed.threadId : null;
21414
+ }
21415
+ function safeDecodeURIComponent2(value) {
21416
+ try {
21417
+ return decodeURIComponent(value);
21418
+ } catch {
21419
+ return value;
21420
+ }
21421
+ }
21422
+ function normalizeOptionalJobId2(value) {
21423
+ const normalized = value?.trim();
21424
+ return normalized ? normalized : null;
21425
+ }
21426
+ function normalizeOptionalText(value) {
21427
+ const normalized = value?.trim();
21428
+ return normalized ? normalized : null;
21429
+ }
21430
+ function isInstalled(provider, detection) {
21431
+ return detection.available.includes(provider);
21432
+ }
21433
+ function assertInstalled(provider, detection) {
21434
+ if (!isInstalled(provider, detection)) {
21435
+ throw new Error(`AI provider "${provider}" is not installed or not on PATH.`);
21436
+ }
21437
+ }
21438
+ function readProviderCommand(provider, env) {
21439
+ return env[`OKX_A2A_AI_${provider.toUpperCase()}_COMMAND`] ?? provider;
21440
+ }
21441
+ async function promptForAiProvider(options) {
21442
+ const stdin = options.stdin ?? process.stdin;
21443
+ const stdout = options.stdout ?? process.stdout;
21444
+ if (!stdin.isTTY || !stdout.isTTY) {
21445
+ throw new Error(
21446
+ `No AI provider is configured. Run \`okx-a2a daemon start --ai-provider <${AI_PROVIDERS.join("|")}>\` or set OKX_A2A_AI_PROVIDER.`
21447
+ );
21448
+ }
21449
+ stdout.write("AI provider is required before starting okx-a2a.\n");
21450
+ stdout.write("Tip: if the selected AI provider is unavailable, task processing cannot proceed.\n\n");
21451
+ if (options.currentProvider) {
21452
+ stdout.write(`Current AI provider: ${options.currentProvider}
21453
+
21454
+ `);
21455
+ }
21456
+ if (options.detection.available.length === 0) {
21457
+ stdout.write("Supported AI providers:\n");
21458
+ AI_PROVIDERS.forEach((provider, index2) => {
21459
+ const status = isInstalled(provider, options.detection) ? "installed" : "not found on PATH";
21460
+ stdout.write(` ${index2 + 1}. ${provider} (${status})
21461
+ `);
21462
+ });
21463
+ stdout.write("\n");
21464
+ throw new Error(`No supported AI CLI found. Install one of: ${AI_PROVIDERS.join(", ")}.`);
21465
+ }
21466
+ if (typeof stdin.setRawMode === "function") {
21467
+ return promptForAiProviderWithArrows({
21468
+ detection: options.detection,
21469
+ stdin,
21470
+ stdout,
21471
+ currentProvider: options.currentProvider
21472
+ });
21473
+ }
21474
+ stdout.write("Supported AI providers:\n");
21475
+ AI_PROVIDERS.forEach((provider, index2) => {
21476
+ const status = isInstalled(provider, options.detection) ? "installed" : "not found on PATH";
21477
+ stdout.write(` ${index2 + 1}. ${provider} (${status})
21478
+ `);
21479
+ });
21480
+ stdout.write("\n");
21481
+ const rl = (0, import_promises6.createInterface)({ input: stdin, output: stdout });
21482
+ try {
21483
+ while (true) {
21484
+ const answer = (await rl.question("Choose an installed AI provider by number or name: ")).trim();
21485
+ const provider = parseProviderChoice(answer);
21486
+ if (!provider) {
21487
+ stdout.write(`Invalid choice. Use one of: ${AI_PROVIDERS.join(", ")}.
21488
+ `);
21489
+ continue;
21490
+ }
21491
+ if (!isInstalled(provider, options.detection)) {
21492
+ stdout.write(`"${provider}" is not installed or not on PATH. Choose an installed provider.
21493
+ `);
21494
+ continue;
20576
21495
  }
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
- }
21496
+ return provider;
21497
+ }
21498
+ } finally {
21499
+ rl.close();
21500
+ }
21501
+ }
21502
+ async function promptForAiProviderWithArrows(options) {
21503
+ let selectedIndex = AI_PROVIDERS.findIndex((provider) => provider === options.currentProvider && isInstalled(provider, options.detection));
21504
+ if (selectedIndex < 0) {
21505
+ selectedIndex = AI_PROVIDERS.findIndex((provider) => isInstalled(provider, options.detection));
21506
+ }
21507
+ if (selectedIndex < 0) {
21508
+ selectedIndex = 0;
21509
+ }
21510
+ let renderedLines = 0;
21511
+ const render = (message) => {
21512
+ if (renderedLines > 0) {
21513
+ (0, import_node_readline.moveCursor)(options.stdout, 0, -renderedLines);
21514
+ (0, import_node_readline.cursorTo)(options.stdout, 0);
21515
+ (0, import_node_readline.clearScreenDown)(options.stdout);
21516
+ }
21517
+ const lines = [
21518
+ "Use \u2191/\u2193 to move, Enter to confirm.",
21519
+ "",
21520
+ ...AI_PROVIDERS.map((provider, index2) => {
21521
+ const selected = index2 === selectedIndex ? "\u276F" : " ";
21522
+ const status = isInstalled(provider, options.detection) ? "installed" : "not found on PATH";
21523
+ const current = provider === options.currentProvider ? ", current" : "";
21524
+ return `${selected} ${provider} (${status}${current})`;
21525
+ }),
21526
+ ...message ? ["", message] : []
21527
+ ];
21528
+ options.stdout.write(`${lines.join("\n")}
21529
+ `);
21530
+ renderedLines = lines.length;
21531
+ };
21532
+ (0, import_node_readline.emitKeypressEvents)(options.stdin);
21533
+ options.stdin.setRawMode(true);
21534
+ options.stdin.resume();
21535
+ options.stdout.write("\x1B[?25l");
21536
+ render();
21537
+ return await new Promise((resolve11, reject) => {
21538
+ const cleanup = () => {
21539
+ options.stdin.off("keypress", onKeypress);
21540
+ options.stdin.setRawMode(false);
21541
+ options.stdin.pause();
21542
+ options.stdout.write("\x1B[?25h");
21543
+ };
21544
+ const clearRendered = () => {
21545
+ if (renderedLines > 0) {
21546
+ (0, import_node_readline.moveCursor)(options.stdout, 0, -renderedLines);
21547
+ (0, import_node_readline.cursorTo)(options.stdout, 0);
21548
+ (0, import_node_readline.clearScreenDown)(options.stdout);
20586
21549
  }
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)])
21550
+ };
21551
+ const finish = (provider) => {
21552
+ cleanup();
21553
+ clearRendered();
21554
+ options.stdout.write(`Selected AI provider: ${provider}
21555
+ `);
21556
+ resolve11(provider);
21557
+ };
21558
+ const onKeypress = (_str, key) => {
21559
+ if (key.ctrl && key.name === "c") {
21560
+ cleanup();
21561
+ clearRendered();
21562
+ options.stdout.write(
21563
+ options.currentProvider ? `AI provider selection cancelled. Keeping current provider: ${options.currentProvider}
21564
+ ` : "AI provider selection cancelled. No provider was configured.\n"
20590
21565
  );
21566
+ reject(new UserCancelledAiProviderSelectionError());
21567
+ return;
20591
21568
  }
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)}...`;
21569
+ if (key.name === "up") {
21570
+ selectedIndex = (selectedIndex + AI_PROVIDERS.length - 1) % AI_PROVIDERS.length;
21571
+ render();
21572
+ return;
20645
21573
  }
20646
- static normalizeTagKey(key) {
20647
- return key.replace(/[^A-Za-z0-9_.:-]+/g, "_").slice(0, 32) || "unknown";
21574
+ if (key.name === "down") {
21575
+ selectedIndex = (selectedIndex + 1) % AI_PROVIDERS.length;
21576
+ render();
21577
+ return;
20648
21578
  }
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
- }
21579
+ if (key.name === "return" || key.name === "enter") {
21580
+ const provider = AI_PROVIDERS[selectedIndex];
21581
+ if (!isInstalled(provider, options.detection)) {
21582
+ render(`"${provider}" is not installed or not on PATH. Choose an installed provider.`);
21583
+ return;
20663
21584
  }
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;
21585
+ finish(provider);
20674
21586
  }
20675
21587
  };
20676
- logger = SentryLogger.getInstance();
20677
- initLogger = (config) => {
20678
- return logger.init(config);
21588
+ options.stdin.on("keypress", onKeypress);
21589
+ });
21590
+ }
21591
+ function parseProviderChoice(answer) {
21592
+ if (!answer) {
21593
+ return null;
21594
+ }
21595
+ const number = Number(answer);
21596
+ if (Number.isInteger(number) && number >= 1 && number <= AI_PROVIDERS.length) {
21597
+ return AI_PROVIDERS[number - 1];
21598
+ }
21599
+ try {
21600
+ return normalizeAiProvider(answer);
21601
+ } catch {
21602
+ return null;
21603
+ }
21604
+ }
21605
+ var import_node_child_process5, import_promises6, import_node_readline, AI_PROVIDERS, UserCancelledAiProviderSelectionError, JOB_PROVIDER_BINDING_COMPONENT, ProviderNotReadyError;
21606
+ var init_ai_provider = __esm({
21607
+ "src/ai-provider.ts"() {
21608
+ "use strict";
21609
+ import_node_child_process5 = require("node:child_process");
21610
+ import_promises6 = require("node:readline/promises");
21611
+ import_node_readline = require("node:readline");
21612
+ init_ai_command();
21613
+ init_codex_readiness();
21614
+ init_sentry_logger();
21615
+ init_log_fields();
21616
+ init_win_spawn();
21617
+ AI_PROVIDERS = ["codex", "claude", "hermes", "openclaw"];
21618
+ UserCancelledAiProviderSelectionError = class extends Error {
21619
+ constructor() {
21620
+ super("AI provider selection cancelled by user.");
21621
+ this.name = "UserCancelledAiProviderSelectionError";
21622
+ }
20679
21623
  };
20680
- shutdown = () => {
20681
- return logger.shutdown();
21624
+ JOB_PROVIDER_BINDING_COMPONENT = "node_job_provider_binding";
21625
+ ProviderNotReadyError = class extends Error {
21626
+ provider;
21627
+ readinessState;
21628
+ recoveryAction;
21629
+ commandSource;
21630
+ constructor(provider, gate) {
21631
+ super(`AI provider "${provider}" is not ready (${gate.state}). ${gate.recoveryGuidance}`.trim());
21632
+ this.name = "ProviderNotReadyError";
21633
+ this.provider = provider;
21634
+ this.readinessState = gate.state;
21635
+ this.recoveryAction = gate.recoveryAction;
21636
+ this.commandSource = gate.commandSource;
21637
+ }
20682
21638
  };
20683
- UNKNOWN_FIELD = "unknown";
20684
21639
  }
20685
21640
  });
20686
21641
 
@@ -31149,7 +32104,7 @@ var init_sentry_config = __esm({
31149
32104
  environment = process.env.SENTRY_ENV === "dev" ? "dev" : "prod";
31150
32105
  SENTRY_CONFIG = {
31151
32106
  projectName: "okx/openclaw-okx-a2a-extension",
31152
- release: "0.2.2",
32107
+ release: "0.2.3-beta-6f2a8ec2c7-260810184309",
31153
32108
  environment,
31154
32109
  runtimeContainer: normalizeRuntimeContainer(process.env.OKX_A2A_RUNTIME_CONTAINER)
31155
32110
  };
@@ -31362,6 +32317,7 @@ var init_win_native_launcher = __esm({
31362
32317
  var update_cli_exports = {};
31363
32318
  __export(update_cli_exports, {
31364
32319
  assertHermesSetupSupportedOnPlatform: () => assertHermesSetupSupportedOnPlatform,
32320
+ authStatusFromReason: () => authStatusFromReason,
31365
32321
  buildExternalCommandInvocation: () => buildExternalCommandInvocation,
31366
32322
  buildNpmPackageSpec: () => buildNpmPackageSpec,
31367
32323
  buildProviderMismatchWarning: () => buildProviderMismatchWarning,
@@ -31881,7 +32837,7 @@ function authStatusFromReason(reason) {
31881
32837
  if (reason === "provider_cli_login_failed") {
31882
32838
  return "login_failed";
31883
32839
  }
31884
- if (reason === "provider_cli_login_timeout") {
32840
+ if (reason === "provider_cli_login_timeout" || reason === "provider_cli_auth_status_timeout") {
31885
32841
  return "login_timeout";
31886
32842
  }
31887
32843
  return "blocked";
@@ -31980,8 +32936,13 @@ function quoteUserFacingCommandPath(commandPath) {
31980
32936
  }
31981
32937
  return `"${commandPath}"`;
31982
32938
  }
31983
- async function checkClaudeCliAuthStatus(command) {
31984
- const result = await runCommandCaptureStatus(command, ["auth", "status", "--json"]);
32939
+ async function checkClaudeCliAuthStatus(command, options = {}) {
32940
+ const result = await runCommandCaptureStatus(command, ["auth", "status", "--json"], {
32941
+ timeoutMs: options.timeoutMs ?? PROVIDER_CLI_AUTH_PROBE_TIMEOUT_MS
32942
+ });
32943
+ if (result.errorCode === "ETIMEDOUT") {
32944
+ return { ok: false, reason: "provider_cli_auth_status_timeout", detail: result.detail };
32945
+ }
31985
32946
  if (result.errorCode === "ENOENT") {
31986
32947
  return { ok: false, reason: "provider_cli_missing", detail: result.detail };
31987
32948
  }
@@ -32000,8 +32961,13 @@ async function checkClaudeCliAuthStatus(command) {
32000
32961
  ${result.stdout}`.trim() };
32001
32962
  }
32002
32963
  }
32003
- async function checkCodexCliAuthStatus(command) {
32004
- const result = await runCommandCaptureStatus(command, ["login", "status"]);
32964
+ async function checkCodexCliAuthStatus(command, options = {}) {
32965
+ const result = await runCommandCaptureStatus(command, ["login", "status"], {
32966
+ timeoutMs: options.timeoutMs ?? PROVIDER_CLI_AUTH_PROBE_TIMEOUT_MS
32967
+ });
32968
+ if (result.errorCode === "ETIMEDOUT") {
32969
+ return { ok: false, reason: "provider_cli_auth_status_timeout", detail: result.detail };
32970
+ }
32005
32971
  if (result.errorCode === "ENOENT") {
32006
32972
  return { ok: false, reason: "provider_cli_missing", detail: result.detail };
32007
32973
  }
@@ -32079,7 +33045,7 @@ async function getCurrentNodeCliVersion() {
32079
33045
  return await getGlobalNpmPackageVersion(UPDATE_PACKAGES.node) ?? getBundledNodeCliVersion();
32080
33046
  }
32081
33047
  function getBundledNodeCliVersion() {
32082
- return true ? "0.2.2" : null;
33048
+ return true ? "0.2.3-beta-6f2a8ec2c7-260810184309" : null;
32083
33049
  }
32084
33050
  function readConfiguredAiProvider() {
32085
33051
  const explicit = process.env.OKX_A2A_AI_PROVIDER || process.env.OKX_AGENT_TASK_AI_CLI;
@@ -32289,7 +33255,7 @@ async function updateHermes(release, options) {
32289
33255
  }
32290
33256
  }
32291
33257
  async function installGatewayPluginForDoctor(target) {
32292
- const release = isPrereleaseVersion("0.2.2") ? "beta" : "latest";
33258
+ const release = isPrereleaseVersion("0.2.3-beta-6f2a8ec2c7-260810184309") ? "beta" : "latest";
32293
33259
  const insideTargetGateway = detectGatewayInvocation() === target;
32294
33260
  const options = {
32295
33261
  restart: !insideTargetGateway,
@@ -32970,7 +33936,7 @@ async function runCommandCaptureOptional(command, args) {
32970
33936
  });
32971
33937
  });
32972
33938
  }
32973
- async function runCommandCaptureStatus(command, args) {
33939
+ async function runCommandCaptureStatus(command, args, options = {}) {
32974
33940
  return await new Promise((resolvePromise) => {
32975
33941
  const invocation = buildExternalCommandInvocation(command, args);
32976
33942
  const child = (0, import_node_child_process8.spawn)(invocation.command, invocation.args, {
@@ -32979,6 +33945,18 @@ async function runCommandCaptureStatus(command, args) {
32979
33945
  });
32980
33946
  let stdout = "";
32981
33947
  let stderr = "";
33948
+ let settled = false;
33949
+ let timeout = null;
33950
+ const settle = (result) => {
33951
+ if (settled) {
33952
+ return;
33953
+ }
33954
+ settled = true;
33955
+ if (timeout) {
33956
+ clearTimeout(timeout);
33957
+ }
33958
+ resolvePromise(result);
33959
+ };
32982
33960
  child.stdout.setEncoding("utf8");
32983
33961
  child.stderr.setEncoding("utf8");
32984
33962
  child.stdout.on("data", (chunk) => {
@@ -32987,9 +33965,28 @@ async function runCommandCaptureStatus(command, args) {
32987
33965
  child.stderr.on("data", (chunk) => {
32988
33966
  stderr += chunk;
32989
33967
  });
33968
+ if (options.timeoutMs && options.timeoutMs > 0) {
33969
+ const timeoutMs = options.timeoutMs;
33970
+ timeout = setTimeout(() => {
33971
+ killProcessTree(child, "SIGTERM");
33972
+ setTimeout(() => {
33973
+ if (!child.killed || child.exitCode === null) {
33974
+ killProcessTree(child, "SIGKILL");
33975
+ }
33976
+ }, 5e3).unref();
33977
+ settle({
33978
+ code: null,
33979
+ stdout,
33980
+ stderr,
33981
+ detail: `timed out after ${timeoutMs}ms`,
33982
+ errorCode: "ETIMEDOUT"
33983
+ });
33984
+ }, timeoutMs);
33985
+ timeout.unref();
33986
+ }
32990
33987
  child.on("error", (error) => {
32991
33988
  const detail = error.message;
32992
- resolvePromise({
33989
+ settle({
32993
33990
  code: null,
32994
33991
  stdout,
32995
33992
  stderr,
@@ -33000,7 +33997,7 @@ async function runCommandCaptureStatus(command, args) {
33000
33997
  child.on("close", (code, signal) => {
33001
33998
  const signalDetail = signal ? `signal=${signal}` : "";
33002
33999
  const detail = [stderr.trim(), stdout.trim(), signalDetail].filter(Boolean).join("\n");
33003
- resolvePromise({
34000
+ settle({
33004
34001
  code,
33005
34002
  stdout,
33006
34003
  stderr,
@@ -33015,7 +34012,7 @@ function buildExternalCommandInvocation(command, args, platform = process.platfo
33015
34012
  function formatExternalCommand(command, args) {
33016
34013
  return [command, ...args].join(" ");
33017
34014
  }
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;
34015
+ 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
34016
  var init_update_cli = __esm({
33020
34017
  "src/update-cli.ts"() {
33021
34018
  "use strict";
@@ -33042,6 +34039,7 @@ var init_update_cli = __esm({
33042
34039
  OPENCLAW_PLUGIN_ALLOW_CONFIG_PATH = "plugins.allow";
33043
34040
  OPENCLAW_OKX_A2A_CONVERSATION_HOOK_ACCESS_CONFIG_PATH = "plugins.entries.okx-a2a.hooks.allowConversationAccess";
33044
34041
  DEFAULT_AI_PROVIDER_LOGIN_TIMEOUT_MS = 3 * 60 * 1e3;
34042
+ PROVIDER_CLI_AUTH_PROBE_TIMEOUT_MS = CODEX_PROBE_TIMEOUT_MS;
33045
34043
  SetupBlockedError = class extends Error {
33046
34044
  result;
33047
34045
  constructor(result) {
@@ -42950,6 +43948,13 @@ __export(index_exports, {
42950
43948
  AgentMessageDirection: () => AgentMessageDirection,
42951
43949
  AiRunner: () => AiRunner,
42952
43950
  BACKUP_JOB_ID: () => BACKUP_JOB_ID,
43951
+ CODEX_APP_DIRS_ENV: () => CODEX_APP_DIRS_ENV,
43952
+ CODEX_AUTH_PROBE_TIMEOUT_MS: () => CODEX_AUTH_PROBE_TIMEOUT_MS,
43953
+ CODEX_COMMAND_SOURCES: () => CODEX_COMMAND_SOURCES,
43954
+ CODEX_PROBE_TIMEOUT_MS: () => CODEX_PROBE_TIMEOUT_MS,
43955
+ CODEX_READINESS_CACHE_TTL_MS: () => CODEX_READINESS_CACHE_TTL_MS,
43956
+ CODEX_READINESS_STATES: () => CODEX_READINESS_STATES,
43957
+ CODEX_RECOVERY_ACTIONS: () => CODEX_RECOVERY_ACTIONS,
42953
43958
  CommandStore: () => CommandStore,
42954
43959
  DEFAULT_AI_PERMISSION_PRESET: () => DEFAULT_AI_PERMISSION_PRESET,
42955
43960
  DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC: () => DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC,
@@ -42959,10 +43964,13 @@ __export(index_exports, {
42959
43964
  HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS: () => HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS,
42960
43965
  InboundReplayGate: () => InboundReplayGate,
42961
43966
  InvalidXmtpMessageStore: () => InvalidXmtpMessageStore,
43967
+ JOB_PROVIDER_BINDING_SOURCES: () => JOB_PROVIDER_BINDING_SOURCES,
43968
+ LogEvent: () => LogEvent,
42962
43969
  MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG: () => MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG,
42963
43970
  MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN: () => MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN,
42964
43971
  NATIVE_LAUNCHER_EXE_NAME: () => NATIVE_LAUNCHER_EXE_NAME,
42965
43972
  OPENCLAW_GATEWAY_ROUTE_GROUP_ID: () => OPENCLAW_GATEWAY_ROUTE_GROUP_ID,
43973
+ ProviderNotReadyError: () => ProviderNotReadyError,
42966
43974
  SYSTEM_NOTIFICATION_SESSION_KEY: () => SYSTEM_NOTIFICATION_SESSION_KEY,
42967
43975
  SessionBusyTracker: () => SessionBusyTracker,
42968
43976
  SessionStore: () => SessionStore,
@@ -42983,15 +43991,20 @@ __export(index_exports, {
42983
43991
  activeDaemonPointerPath: () => activeDaemonPointerPath,
42984
43992
  aiRunSentryExtra: () => aiRunSentryExtra,
42985
43993
  assertHermesSetupSupportedOnPlatform: () => assertHermesSetupSupportedOnPlatform,
43994
+ authStatusFromReason: () => authStatusFromReason,
42986
43995
  bindJobProviderToCurrentDefaultIfMissing: () => bindJobProviderToCurrentDefaultIfMissing,
43996
+ bindJobProviderToCurrentDefaultIfReady: () => bindJobProviderToCurrentDefaultIfReady,
42987
43997
  bindJobProviderToCurrentRuntimeIfMissing: () => bindJobProviderToCurrentRuntimeIfMissing,
43998
+ bindJobProviderToCurrentRuntimeIfReady: () => bindJobProviderToCurrentRuntimeIfReady,
42988
43999
  bindMessageJobProviderToCurrentDefault: () => bindMessageJobProviderToCurrentDefault,
44000
+ bindNewJobProviderIfReady: () => bindNewJobProviderIfReady,
42989
44001
  bindOpenClawGatewayRouteFromEnv: () => bindOpenClawGatewayRouteFromEnv,
42990
44002
  buildAgentMessageNotice: () => buildAgentMessageNotice,
42991
44003
  buildAiAdapterCommand: () => buildAiAdapterCommand,
42992
44004
  buildAiProviderEnv: () => buildAiProviderEnv,
42993
44005
  buildAutostartVbs: () => buildAutostartVbs,
42994
44006
  buildBackupJobSessionKey: () => buildBackupJobSessionKey,
44007
+ buildCodexRecoveryGuidance: () => buildCodexRecoveryGuidance,
42995
44008
  buildDispatchSessionKey: () => buildDispatchSessionKey,
42996
44009
  buildDoctorSentryExtra: () => buildDoctorSentryExtra,
42997
44010
  buildExternalCommandInvocation: () => buildExternalCommandInvocation,
@@ -43020,7 +44033,15 @@ __export(index_exports, {
43020
44033
  callSessionsCreate: () => callSessionsCreate,
43021
44034
  callSessionsDelete: () => callSessionsDelete,
43022
44035
  callSessionsSend: () => callSessionsSend,
44036
+ checkClaudeCliAuthStatus: () => checkClaudeCliAuthStatus,
44037
+ checkCodexCliAuthStatus: () => checkCodexCliAuthStatus,
44038
+ checkCodexReadiness: () => checkCodexReadiness,
43023
44039
  claimActiveDaemon: () => claimActiveDaemon,
44040
+ classifyJobBindingSource: () => classifyJobBindingSource,
44041
+ clearCodexReadinessCache: () => clearCodexReadinessCache,
44042
+ codexCommandResolvesWithoutOverride: () => codexCommandResolvesWithoutOverride,
44043
+ codexReadinessCacheKey: () => codexReadinessCacheKey,
44044
+ collectCodexAppBundledCandidates: () => collectCodexAppBundledCandidates,
43024
44045
  collectCodexCommandCandidates: () => collectCodexCommandCandidates,
43025
44046
  commandExists: () => commandExists,
43026
44047
  createAiToolFailureTracker: () => createAiToolFailureTracker,
@@ -43036,6 +44057,7 @@ __export(index_exports, {
43036
44057
  ensureDaemonReady: () => ensureDaemonReady,
43037
44058
  ensureDefaultAiProvider: () => ensureDefaultAiProvider,
43038
44059
  ensureOpenClawOkxA2aPluginConfig: () => ensureOpenClawOkxA2aPluginConfig,
44060
+ ensureProviderReadyForBinding: () => ensureProviderReadyForBinding,
43039
44061
  ensureWindowsNativeLauncher: () => ensureWindowsNativeLauncher,
43040
44062
  evictPid: () => evictPid,
43041
44063
  exportDiagnosticLogs: () => exportDiagnosticLogs,
@@ -43063,6 +44085,7 @@ __export(index_exports, {
43063
44085
  isDefaultTaskHome: () => isDefaultTaskHome,
43064
44086
  isGatewayAvailableForHeartbeat: () => isGatewayAvailableForHeartbeat,
43065
44087
  isHermesGatewayPluginEnabled: () => isHermesGatewayPluginEnabled,
44088
+ isInfoEventAllowlisted: () => isInfoEventAllowlisted,
43066
44089
  isOfflineReplayForTrigger: () => isOfflineReplayForTrigger,
43067
44090
  isOpenClawA2aPluginAvailable: () => isOpenClawA2aPluginAvailable,
43068
44091
  isOpenClawGatewayAvailable: () => isOpenClawGatewayAvailable,
@@ -43088,11 +44111,15 @@ __export(index_exports, {
43088
44111
  parsePluginYamlVersion: () => parsePluginYamlVersion,
43089
44112
  parseWindowsParentProcessJson: () => parseWindowsParentProcessJson,
43090
44113
  performRuntimeSwitch: () => performRuntimeSwitch,
44114
+ persistCurrentRuntimeSelectionForNewJob: () => persistCurrentRuntimeSelectionForNewJob,
43091
44115
  pickOnchainosWin32Candidate: () => pickOnchainosWin32Candidate,
44116
+ prepareAndBindCurrentRuntimeForNewJob: () => prepareAndBindCurrentRuntimeForNewJob,
43092
44117
  printLogsExportUsage: () => printLogsExportUsage,
43093
44118
  printXmtpTestUsage: () => printXmtpTestUsage,
44119
+ probeCodexAuthStatus: () => probeCodexAuthStatus,
43094
44120
  processFileMessage: () => processFileMessage,
43095
44121
  readAiProviderTimeoutMs: () => readAiProviderTimeoutMs,
44122
+ readCachedCodexReadiness: () => readCachedCodexReadiness,
43096
44123
  readLastLines: () => readLastLines,
43097
44124
  readParentProcessCommandForPlatform: () => readParentProcessCommandForPlatform,
43098
44125
  readUserAttentionWatcherEvent: () => readUserAttentionWatcherEvent,
@@ -43100,14 +44127,19 @@ __export(index_exports, {
43100
44127
  registerUserAttentionWatcher: () => registerUserAttentionWatcher,
43101
44128
  releaseActiveDaemon: () => releaseActiveDaemon,
43102
44129
  removeUserAttentionWatcher: () => removeUserAttentionWatcher,
44130
+ replayScanBackoffMs: () => replayScanBackoffMs,
43103
44131
  reportDoctorRunToSentry: () => reportDoctorRunToSentry,
43104
44132
  reportUserNotificationFailure: () => reportUserNotificationFailure,
43105
44133
  resetMessageEligibleOfflineReplayCapabilityForTests: () => resetMessageEligibleOfflineReplayCapabilityForTests,
43106
44134
  resetResolvedOnchainosBinForTests: () => resetResolvedOnchainosBinForTests,
43107
44135
  resolveAiPermissionPreset: () => resolveAiPermissionPreset,
43108
44136
  resolveAiProviderCommand: () => resolveAiProviderCommand,
44137
+ resolveAiProviderCommandWithSource: () => resolveAiProviderCommandWithSource,
43109
44138
  resolveAiProviderForDispatch: () => resolveAiProviderForDispatch,
44139
+ resolveAiProviderForDispatchWithReadiness: () => resolveAiProviderForDispatchWithReadiness,
44140
+ resolveCodexCommandCandidates: () => resolveCodexCommandCandidates,
43110
44141
  resolveCodexCommandPath: () => resolveCodexCommandPath,
44142
+ resolveCodexCommandSource: () => resolveCodexCommandSource,
43111
44143
  resolveConfiguredAiProvider: () => resolveConfiguredAiProvider,
43112
44144
  resolveConfiguredAiProviderForJob: () => resolveConfiguredAiProviderForJob,
43113
44145
  resolveDirectCommunicationSessionTarget: () => resolveDirectCommunicationSessionTarget,
@@ -43119,6 +44151,7 @@ __export(index_exports, {
43119
44151
  resolveOpenClawGatewayConfig: () => resolveOpenClawGatewayConfig,
43120
44152
  resolveOpenClawGatewayRoute: () => resolveOpenClawGatewayRoute,
43121
44153
  resolveOpenClawGatewayRoutes: () => resolveOpenClawGatewayRoutes,
44154
+ resolveProviderCommandWithSelfHeal: () => resolveProviderCommandWithSelfHeal,
43122
44155
  resolveSystemNotificationTargets: () => resolveSystemNotificationTargets,
43123
44156
  resolveTaskConfigPath: () => resolveTaskConfigPath,
43124
44157
  resolveTaskHome: () => resolveTaskHome,
@@ -43146,6 +44179,8 @@ __export(index_exports, {
43146
44179
  subscribeUserAttentionEvents: () => subscribeUserAttentionEvents,
43147
44180
  summarizeXmtpTestEvents: () => summarizeXmtpTestEvents,
43148
44181
  switchProvider: () => switchProvider,
44182
+ switchProviderWithReadinessGate: () => switchProviderWithReadinessGate,
44183
+ switchProviderWithReadinessGateCore: () => switchProviderWithReadinessGateCore,
43149
44184
  systemdUnitHasCurrentRestartPolicy: () => systemdUnitHasCurrentRestartPolicy,
43150
44185
  toOpenClawGatewaySessionKey: () => toOpenClawGatewaySessionKey,
43151
44186
  toWindowsInvocation: () => toWindowsInvocation,
@@ -43455,8 +44490,8 @@ init_paths();
43455
44490
  init_session_store();
43456
44491
 
43457
44492
  // src/task-config.ts
43458
- var import_node_fs8 = require("node:fs");
43459
- var import_node_path12 = require("node:path");
44493
+ var import_node_fs9 = require("node:fs");
44494
+ var import_node_path13 = require("node:path");
43460
44495
  init_paths();
43461
44496
  var AI_PERMISSION_PRESETS = ["bypass", "auto"];
43462
44497
  var DEFAULT_AI_PERMISSION_PRESET = "bypass";
@@ -43472,23 +44507,23 @@ function resolveAiPermissionPreset(options = {}) {
43472
44507
  }
43473
44508
  function readAiPermissionPresetFromConfig(homeDir) {
43474
44509
  const configPath = resolveTaskConfigPath(homeDir);
43475
- if (!(0, import_node_fs8.existsSync)(configPath)) {
44510
+ if (!(0, import_node_fs9.existsSync)(configPath)) {
43476
44511
  return null;
43477
44512
  }
43478
- const raw = readSimpleTomlStringValue((0, import_node_fs8.readFileSync)(configPath, "utf8"), "ai.permissions", "preset");
44513
+ const raw = readSimpleTomlStringValue((0, import_node_fs9.readFileSync)(configPath, "utf8"), "ai.permissions", "preset");
43479
44514
  return raw ? normalizeAiPermissionPreset(raw, configPath) : null;
43480
44515
  }
43481
44516
  function writeAiPermissionPresetToConfig(homeDir, preset) {
43482
44517
  const normalized = normalizeAiPermissionPreset(preset, "permission preset");
43483
44518
  ensureTaskDir(homeDir);
43484
44519
  const configPath = resolveTaskConfigPath(homeDir);
43485
- const current = (0, import_node_fs8.existsSync)(configPath) ? (0, import_node_fs8.readFileSync)(configPath, "utf8") : "";
44520
+ const current = (0, import_node_fs9.existsSync)(configPath) ? (0, import_node_fs9.readFileSync)(configPath, "utf8") : "";
43486
44521
  const next = upsertSimpleTomlStringValue(current, "ai.permissions", "preset", normalized);
43487
- (0, import_node_fs8.writeFileSync)(configPath, next, "utf8");
44522
+ (0, import_node_fs9.writeFileSync)(configPath, next, "utf8");
43488
44523
  return normalized;
43489
44524
  }
43490
44525
  function resolveTaskConfigPath(homeDir) {
43491
- return (0, import_node_path12.join)(homeDir, "config.toml");
44526
+ return (0, import_node_path13.join)(homeDir, "config.toml");
43492
44527
  }
43493
44528
  function normalizeAiPermissionPreset(value, source) {
43494
44529
  const normalized = value.trim().toLowerCase();
@@ -43598,11 +44633,14 @@ function buildCodexPermissionPrefix(homeDir, cwd, env = process.env, permissionP
43598
44633
  cwd
43599
44634
  ];
43600
44635
  }
44636
+ function resolveAdapterProviderCommand(options, homeDir) {
44637
+ const storedCommand = options.provider === "codex" && options.homeDir ? readStoredAiProviderCommand(options.provider, homeDir) : null;
44638
+ return resolveAiProviderCommandWithSource(options.provider, options.env ?? process.env, storedCommand);
44639
+ }
43601
44640
  function buildAiAdapterCommand(options) {
43602
44641
  const env = options.env ?? process.env;
43603
44642
  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);
44643
+ const { command } = resolveAdapterProviderCommand(options, homeDir);
43606
44644
  const template = readAdapterArgsTemplate(options.provider, !!options.sessionId, env);
43607
44645
  if (template) {
43608
44646
  return {
@@ -43774,6 +44812,7 @@ async function runAiAdapter(options) {
43774
44812
  );
43775
44813
  if (timedOut || exitCode !== 0 || !sessionId) {
43776
44814
  const reason = timedOut ? "timeout" : !sessionId ? "missing_ai_session_id" : "non_zero_exit";
44815
+ const { source: commandSource } = resolveAdapterProviderCommand({ ...options, homeDir }, homeDir);
43777
44816
  logger.error(
43778
44817
  timedOut ? LogEvent.AI_RUN_TIMEOUT : LogEvent.AI_RUN_FAILED,
43779
44818
  new Error(`${options.provider} AI adapter failed: ${reason}`),
@@ -43783,6 +44822,8 @@ async function runAiAdapter(options) {
43783
44822
  provider: options.provider,
43784
44823
  stage: "run_adapter",
43785
44824
  reason,
44825
+ // Category only — never the resolved command, its path or its arguments.
44826
+ commandSource: commandSource ?? "",
43786
44827
  sessionKey: options.sessionKey ?? "",
43787
44828
  timeoutMs: timeoutMs === null ? "" : String(timeoutMs),
43788
44829
  exitCode: exitCode === null ? "null" : String(exitCode),
@@ -44171,6 +45212,10 @@ function aiRunSentryExtra(input) {
44171
45212
  stage: input.stage ?? "",
44172
45213
  checkpoint: input.checkpoint ?? "",
44173
45214
  outcome: input.outcome ?? "",
45215
+ commandSource: input.commandSource ?? "",
45216
+ jobBindingSource: input.jobBindingSource ?? "",
45217
+ recoveryAction: input.recoveryAction ?? "",
45218
+ readinessState: input.readinessState ?? "",
44174
45219
  mode: input.mode ?? "",
44175
45220
  pid: input.pid ?? "",
44176
45221
  runAttempt: input.runAttempt ?? "",
@@ -44206,6 +45251,15 @@ function aiRunDeliveryId(input) {
44206
45251
  sessionKey: input.sessionKey
44207
45252
  });
44208
45253
  }
45254
+ function resolveProviderCommandWithSelfHeal(options) {
45255
+ const env = options.env ?? process.env;
45256
+ const storedCommand = options.provider === "codex" ? options.store.getAiProviderCommand(options.provider) : null;
45257
+ const resolved = resolveAiProviderCommandWithSource(options.provider, env, storedCommand, options.platform);
45258
+ if (options.provider === "codex" && resolved.source !== "explicit_override" && resolved.command !== storedCommand && (0, import_node_path16.isAbsolute)(resolved.command)) {
45259
+ options.store.setAiProviderCommand(options.provider, resolved.command);
45260
+ }
45261
+ return { command: resolved.command, commandSource: resolved.source };
45262
+ }
44209
45263
  var AiRunner = class {
44210
45264
  homeDir;
44211
45265
  logsDir;
@@ -44351,11 +45405,41 @@ var AiRunner = class {
44351
45405
  storeReadMs = storeReadEndedAt - storeReadStartedAt;
44352
45406
  }
44353
45407
  const resolveStartedAt = Date.now();
44354
- const provider = resolveAiProviderForDispatch({
44355
- store: this.sessionStore,
44356
- sessionKey: request.sessionKey,
44357
- jobId: request.jobId
44358
- });
45408
+ const jobBinding = this.readJobProviderBindingForTelemetry(request.jobId);
45409
+ let provider;
45410
+ try {
45411
+ provider = await resolveAiProviderForDispatchWithReadiness({
45412
+ store: this.sessionStore,
45413
+ sessionKey: request.sessionKey,
45414
+ jobId: request.jobId
45415
+ });
45416
+ } catch (err) {
45417
+ const notReady = err instanceof ProviderNotReadyError ? err : null;
45418
+ logger.error(
45419
+ LogEvent.AI_RUN_FAILED,
45420
+ err instanceof Error ? err : new Error(String(err)),
45421
+ aiRunSentryExtra({
45422
+ source: request.source,
45423
+ sessionKey: request.sessionKey,
45424
+ jobId: request.jobId,
45425
+ agentId: request.agentId,
45426
+ messageId: request.messageId,
45427
+ deliveryId,
45428
+ provider: notReady?.provider ?? jobBinding.provider ?? "unknown",
45429
+ stage: "resolve_provider",
45430
+ reason: "provider_not_ready",
45431
+ checkpoint: "ai_run/provider_not_ready",
45432
+ outcome: "failed",
45433
+ runAttempt,
45434
+ jobBindingSource: jobBinding.bindingSource,
45435
+ // Categories only — never the resolved command or its path (NFR-3).
45436
+ readinessState: notReady?.readinessState ?? null,
45437
+ commandSource: notReady?.commandSource ?? null,
45438
+ recoveryAction: notReady?.recoveryAction ?? "run_setup"
45439
+ })
45440
+ );
45441
+ throw err;
45442
+ }
44359
45443
  const sessionMeta = this.sessionStore.getSession(request.sessionKey);
44360
45444
  const existingAiSessionId = this.readRunAiSessionId(provider, request, existing, sessionMeta);
44361
45445
  const runMode = existingAiSessionId ? "resume" : "new";
@@ -44425,7 +45509,7 @@ var AiRunner = class {
44425
45509
  }
44426
45510
  }
44427
45511
  };
44428
- const command = this.resolveProviderCommand(provider);
45512
+ const { command, commandSource } = this.resolveProviderCommand(provider);
44429
45513
  let stderrText = "";
44430
45514
  let timedOut = false;
44431
45515
  const timeoutMs = readAiProviderTimeoutMs();
@@ -44488,6 +45572,8 @@ var AiRunner = class {
44488
45572
  provider,
44489
45573
  checkpoint: "ai_run/started",
44490
45574
  outcome: "pending",
45575
+ commandSource,
45576
+ jobBindingSource: jobBinding.bindingSource,
44491
45577
  mode: runMode,
44492
45578
  pid: child.pid ?? null,
44493
45579
  runAttempt,
@@ -44601,6 +45687,11 @@ var AiRunner = class {
44601
45687
  stage: "run_ai",
44602
45688
  checkpoint: overrides?.checkpoint ?? "ai_run/failed",
44603
45689
  outcome: overrides?.outcome ?? "failed",
45690
+ // Which tier produced the binary that failed, and how the job picked this
45691
+ // provider — the two dimensions that separate "GUI-only Codex was never
45692
+ // discovered" from "the CLI itself is broken".
45693
+ commandSource,
45694
+ jobBindingSource: jobBinding.bindingSource,
44604
45695
  runAttempt,
44605
45696
  timeoutMs,
44606
45697
  exitCode,
@@ -44946,8 +46037,32 @@ var AiRunner = class {
44946
46037
  return extractAiSessionId(provider, line);
44947
46038
  }
44948
46039
  resolveProviderCommand(provider) {
44949
- const storedCommand = provider === "codex" ? this.sessionStore.getAiProviderCommand(provider) : null;
44950
- return resolveAiProviderCommand(provider, process.env, storedCommand);
46040
+ return resolveProviderCommandWithSelfHeal({
46041
+ provider,
46042
+ store: this.sessionStore,
46043
+ env: process.env
46044
+ });
46045
+ }
46046
+ /**
46047
+ * Telemetry-only view of how this run's provider was decided. Never throws: a
46048
+ * store read that fails must not replace the error we are already reporting.
46049
+ */
46050
+ readJobProviderBindingForTelemetry(jobId) {
46051
+ const normalizedJobId = normalizeOptionalText2(jobId);
46052
+ if (normalizedJobId) {
46053
+ try {
46054
+ const bound = this.sessionStore.getJobProviderBinding(normalizedJobId);
46055
+ if (bound) {
46056
+ return { provider: bound.provider, bindingSource: "existing_binding" };
46057
+ }
46058
+ } catch {
46059
+ }
46060
+ }
46061
+ try {
46062
+ return { provider: this.sessionStore.getDefaultAiProvider(), bindingSource: "default_provider" };
46063
+ } catch {
46064
+ return { provider: null, bindingSource: "default_provider" };
46065
+ }
44951
46066
  }
44952
46067
  formatRunTarget(request) {
44953
46068
  if (request.source === "job-dispatch") {
@@ -54754,6 +55869,8 @@ function compareTimestamp(a, b) {
54754
55869
  var DEFAULT_DATA_DIR = resolveA2aTaskPaths().xmtpDir;
54755
55870
  var XMTP_INSTALLATION_WARNING_THRESHOLD = 2;
54756
55871
  var XMTP_INSTALLATION_NEAR_LIMIT_THRESHOLD = 8;
55872
+ var REPLAY_SCAN_INITIAL_BACKOFF_MS = 3e4;
55873
+ var REPLAY_SCAN_MAX_BACKOFF_MS = 5 * 6e4;
54757
55874
  function cachePath(dataDir, fileName) {
54758
55875
  return (0, import_node_path21.join)(dataDir, fileName);
54759
55876
  }
@@ -55238,6 +56355,13 @@ function parseGroupPayload(content) {
55238
56355
  return null;
55239
56356
  }
55240
56357
  }
56358
+ function replayScanBackoffMs(consecutiveFailures) {
56359
+ const exponent = Math.max(0, Math.floor(consecutiveFailures) - 1);
56360
+ return Math.min(
56361
+ REPLAY_SCAN_INITIAL_BACKOFF_MS * 2 ** exponent,
56362
+ REPLAY_SCAN_MAX_BACKOFF_MS
56363
+ );
56364
+ }
55241
56365
  function isOfflineReplayForTrigger(trigger) {
55242
56366
  return trigger === "startup";
55243
56367
  }
@@ -55273,6 +56397,11 @@ var XmtpService = class _XmtpService {
55273
56397
  dataDir = DEFAULT_DATA_DIR;
55274
56398
  syncByAddress = /* @__PURE__ */ new Map();
55275
56399
  offlineReplaySingleFlight = new KeyedSingleFlight();
56400
+ replayScanFailuresByAddress = /* @__PURE__ */ new Map();
56401
+ replayScanRetryByAddress = /* @__PURE__ */ new Map();
56402
+ replayScanNowMs = () => Date.now();
56403
+ replayScanSetTimeout = (callback, delayMs) => setTimeout(callback, delayMs);
56404
+ replayScanClearTimeout = (timer) => clearTimeout(timer);
55276
56405
  inboundReplayGates = /* @__PURE__ */ new Map();
55277
56406
  streamRecoveryCleanupByAddress = /* @__PURE__ */ new Map();
55278
56407
  nextClientGeneration = 1;
@@ -55751,6 +56880,8 @@ var XmtpService = class _XmtpService {
55751
56880
  }
55752
56881
  this.clients.delete(address);
55753
56882
  this.inboundReplayGates.delete(address.toLowerCase());
56883
+ this.replayScanFailuresByAddress.delete(address.toLowerCase());
56884
+ this.clearReplayScanRetry(address);
55754
56885
  logger.info(LogEvent.AGENT_CLIENT_REMOVED, {
55755
56886
  ...agentExtras({
55756
56887
  walletAddress: address,
@@ -55965,6 +57096,8 @@ var XmtpService = class _XmtpService {
55965
57096
  await Promise.resolve(oldClient.stop());
55966
57097
  this.clients.delete(address);
55967
57098
  this.inboundReplayGates.delete(addressKey);
57099
+ this.replayScanFailuresByAddress.delete(addressKey);
57100
+ this.clearReplayScanRetry(address);
55968
57101
  logWithTimestamp(
55969
57102
  `[xmtp-sdk] recycle: stopped client: ${address} (agentId=${agentInfo.agentId})`
55970
57103
  );
@@ -56514,6 +57647,60 @@ var XmtpService = class _XmtpService {
56514
57647
  this.clientGenerationByClient.set(client, generation);
56515
57648
  return generation;
56516
57649
  }
57650
+ clearReplayScanRetry(address) {
57651
+ const addressKey = address.toLowerCase();
57652
+ const scheduled = this.replayScanRetryByAddress.get(addressKey);
57653
+ if (!scheduled) {
57654
+ return;
57655
+ }
57656
+ this.replayScanRetryByAddress.delete(addressKey);
57657
+ this.replayScanClearTimeout(scheduled.timer);
57658
+ }
57659
+ scheduleReplayScanRetry(address, expectedClient, clientGeneration, retryTrigger, delayMs) {
57660
+ const addressKey = address.toLowerCase();
57661
+ const scheduled = this.replayScanRetryByAddress.get(addressKey);
57662
+ if (scheduled?.clientGeneration === clientGeneration) {
57663
+ return;
57664
+ }
57665
+ if (scheduled) {
57666
+ this.clearReplayScanRetry(address);
57667
+ }
57668
+ const timer = this.replayScanSetTimeout(() => {
57669
+ const current = this.replayScanRetryByAddress.get(addressKey);
57670
+ if (current?.timer !== timer) {
57671
+ return;
57672
+ }
57673
+ this.replayScanRetryByAddress.delete(addressKey);
57674
+ if (this.clients.get(address) !== expectedClient || this.stoppedAddresses.has(addressKey)) {
57675
+ return;
57676
+ }
57677
+ void this.replayOfflineMessagesForAddressSingleFlight(
57678
+ address,
57679
+ retryTrigger,
57680
+ void 0,
57681
+ expectedClient
57682
+ ).catch((err) => {
57683
+ logWithTimestamp(
57684
+ `[xmtp-sdk:replay:${address}] scheduled replay scan retry failed:`,
57685
+ err
57686
+ );
57687
+ logger.error(
57688
+ LogEvent.OFFLINE_REPLAY_FAILED,
57689
+ err instanceof Error ? err : new Error(String(err)),
57690
+ {
57691
+ walletAddress: address,
57692
+ stage: "offlineReplay/scheduledRetry",
57693
+ outcome: "failed"
57694
+ }
57695
+ );
57696
+ });
57697
+ }, Math.max(0, delayMs));
57698
+ timer.unref?.();
57699
+ this.replayScanRetryByAddress.set(addressKey, {
57700
+ clientGeneration,
57701
+ timer
57702
+ });
57703
+ }
56517
57704
  notifyAgentsWakeupAfterReplay() {
56518
57705
  const notifyAgentsWakeup2 = this.tools.notifyAgentsWakeup;
56519
57706
  const agentIds = this.allAgents.map((agent) => agent.agentId);
@@ -56752,7 +57939,7 @@ var XmtpService = class _XmtpService {
56752
57939
  String(recovery.recoverySequence),
56753
57940
  agent
56754
57941
  );
56755
- if (!recoveryDisposed && this.clients.get(address) === agent) {
57942
+ if (summary.outcome === "completed" && !recoveryDisposed && this.clients.get(address) === agent) {
56756
57943
  logger.info(LogEvent.AGENT_STREAM_RECOVERY_REPLAY_COMPLETED, {
56757
57944
  ...agentExtras(identity),
56758
57945
  clientGeneration: String(clientGeneration),
@@ -56760,7 +57947,9 @@ var XmtpService = class _XmtpService {
56760
57947
  replayed: String(summary.replayed),
56761
57948
  skipped: String(summary.skipped),
56762
57949
  conversationCount: String(summary.conversations),
56763
- replayDurationMs: String(summary.durationMs)
57950
+ replayDurationMs: String(summary.durationMs),
57951
+ outcome: "success",
57952
+ replayOutcome: summary.outcome
56764
57953
  });
56765
57954
  }
56766
57955
  } catch (err) {
@@ -56826,10 +58015,6 @@ var XmtpService = class _XmtpService {
56826
58015
  summary.durationMs = Date.now() - addressReplayStartedAt;
56827
58016
  return summary;
56828
58017
  }
56829
- const sentAfterNs = BigInt(lastSyncMs) * 1000000n;
56830
- logWithTimestamp(
56831
- `${tag} offline_replay_start agentId=${this.getAgentByAddress(address)?.agentId ?? ""} address=${address} since=${formatLogTimestamp2(lastSyncMs)} sinceMs=${lastSyncMs}`
56832
- );
56833
58018
  const myInboxId = agent.client.inboxId;
56834
58019
  const handlerDeps = {
56835
58020
  myXmtpAddress: address,
@@ -56844,7 +58029,87 @@ var XmtpService = class _XmtpService {
56844
58029
  summary.durationMs = Date.now() - addressReplayStartedAt;
56845
58030
  return summary;
56846
58031
  }
58032
+ const addressKey = address.toLowerCase();
58033
+ const clientGeneration = this.getClientGeneration(agent);
58034
+ const attemptNowMs = this.replayScanNowMs();
58035
+ let priorFailure = this.replayScanFailuresByAddress.get(addressKey);
58036
+ if (priorFailure && priorFailure.clientGeneration !== clientGeneration) {
58037
+ this.replayScanFailuresByAddress.delete(addressKey);
58038
+ this.clearReplayScanRetry(address);
58039
+ priorFailure = void 0;
58040
+ }
58041
+ if (priorFailure && attemptNowMs < priorFailure.nextAttemptAtMs) {
58042
+ priorFailure.suppressedAttempts++;
58043
+ summary.outcome = "suppressed_backoff";
58044
+ summary.failureStage = priorFailure.lastFailureStage;
58045
+ summary.consecutiveFailures = priorFailure.consecutiveFailures;
58046
+ summary.backoffMs = priorFailure.nextAttemptAtMs - attemptNowMs;
58047
+ summary.suppressedAttempts = priorFailure.suppressedAttempts;
58048
+ if (priorFailure.suppressedAttempts === 1) {
58049
+ logWithTimestamp(
58050
+ `${tag} replay scan suppressed stage=${priorFailure.lastFailureStage} consecutiveFailures=${priorFailure.consecutiveFailures} retryInMs=${summary.backoffMs}`
58051
+ );
58052
+ }
58053
+ this.scheduleReplayScanRetry(
58054
+ address,
58055
+ agent,
58056
+ clientGeneration,
58057
+ priorFailure.retryTrigger,
58058
+ summary.backoffMs
58059
+ );
58060
+ summary.durationMs = Date.now() - addressReplayStartedAt;
58061
+ return summary;
58062
+ }
58063
+ const sentAfterNs = BigInt(lastSyncMs) * 1000000n;
58064
+ logWithTimestamp(
58065
+ `${tag} offline_replay_start agentId=${this.getAgentByAddress(address)?.agentId ?? ""} address=${address} since=${formatLogTimestamp2(lastSyncMs)} sinceMs=${lastSyncMs}`
58066
+ );
56847
58067
  let conversations;
58068
+ const recordScanFailure = (stage, err) => {
58069
+ const previous = this.replayScanFailuresByAddress.get(addressKey);
58070
+ const consecutiveFailures = (previous?.consecutiveFailures ?? 0) + 1;
58071
+ const backoffMs = replayScanBackoffMs(consecutiveFailures);
58072
+ const suppressedAttempts = previous?.suppressedAttempts ?? 0;
58073
+ const failureNowMs = this.replayScanNowMs();
58074
+ const retryTrigger = isOfflineReplayForTrigger(trigger) ? "startup" : "periodic_repair";
58075
+ this.replayScanFailuresByAddress.set(addressKey, {
58076
+ clientGeneration,
58077
+ consecutiveFailures,
58078
+ nextAttemptAtMs: failureNowMs + backoffMs,
58079
+ retryTrigger,
58080
+ suppressedAttempts: 0,
58081
+ lastFailureStage: stage
58082
+ });
58083
+ this.scheduleReplayScanRetry(
58084
+ address,
58085
+ agent,
58086
+ clientGeneration,
58087
+ retryTrigger,
58088
+ backoffMs
58089
+ );
58090
+ summary.outcome = stage === "syncAll" ? "sync_failed" : "list_failed";
58091
+ summary.failureStage = stage;
58092
+ summary.consecutiveFailures = consecutiveFailures;
58093
+ summary.backoffMs = backoffMs;
58094
+ summary.suppressedAttempts = suppressedAttempts;
58095
+ logWithTimestamp(`${tag} conversation ${stage} failed, backing off:`, err);
58096
+ logger.error(
58097
+ LogEvent.OFFLINE_REPLAY_FAILED,
58098
+ err instanceof Error ? err : void 0,
58099
+ {
58100
+ walletAddress: address,
58101
+ stage: `offlineReplay/${stage}`,
58102
+ trigger,
58103
+ outcome: "failed",
58104
+ replayOutcome: summary.outcome,
58105
+ consecutiveFailures: String(consecutiveFailures),
58106
+ backoffMs: String(backoffMs),
58107
+ suppressedAttempts: String(suppressedAttempts)
58108
+ }
58109
+ );
58110
+ summary.durationMs = Date.now() - addressReplayStartedAt;
58111
+ return summary;
58112
+ };
56848
58113
  try {
56849
58114
  const syncAllStartedAt = Date.now();
56850
58115
  await agent.client.conversations.syncAll([
@@ -56852,6 +58117,10 @@ var XmtpService = class _XmtpService {
56852
58117
  import_node_bindings2.ConsentState.Unknown
56853
58118
  ]);
56854
58119
  summary.syncAllMs = Date.now() - syncAllStartedAt;
58120
+ } catch (err) {
58121
+ return recordScanFailure("syncAll", err);
58122
+ }
58123
+ try {
56855
58124
  const listStartedAt = Date.now();
56856
58125
  conversations = await agent.client.conversations.list({
56857
58126
  consentStates: [import_node_bindings2.ConsentState.Allowed, import_node_bindings2.ConsentState.Unknown]
@@ -56859,18 +58128,11 @@ var XmtpService = class _XmtpService {
56859
58128
  summary.listMs = Date.now() - listStartedAt;
56860
58129
  summary.conversations = conversations.length;
56861
58130
  } catch (err) {
56862
- logWithTimestamp(`${tag} conversation sync/list failed, skipping:`, err);
56863
- logger.error(
56864
- LogEvent.OFFLINE_REPLAY_FAILED,
56865
- err instanceof Error ? err : void 0,
56866
- {
56867
- walletAddress: address,
56868
- stage: "syncAll/list"
56869
- }
56870
- );
56871
- summary.durationMs = Date.now() - addressReplayStartedAt;
56872
- return summary;
58131
+ return recordScanFailure("list", err);
56873
58132
  }
58133
+ const recoveredFailure = this.replayScanFailuresByAddress.get(addressKey);
58134
+ this.replayScanFailuresByAddress.delete(addressKey);
58135
+ this.clearReplayScanRetry(address);
56874
58136
  for (const conv of conversations) {
56875
58137
  try {
56876
58138
  const messagesStartedAt = Date.now();
@@ -57012,8 +58274,15 @@ var XmtpService = class _XmtpService {
57012
58274
  replayed: String(summary.replayed),
57013
58275
  skipped: String(summary.skipped),
57014
58276
  conversationCount: String(summary.conversations),
57015
- durationMs: String(Date.now() - addressReplayStartedAt)
58277
+ durationMs: String(Date.now() - addressReplayStartedAt),
58278
+ outcome: "success",
58279
+ replayOutcome: "completed",
58280
+ recoveredAfterFailures: String(recoveredFailure?.consecutiveFailures ?? 0),
58281
+ suppressedAttempts: String(recoveredFailure?.suppressedAttempts ?? 0)
57016
58282
  });
58283
+ summary.outcome = "completed";
58284
+ summary.consecutiveFailures = recoveredFailure?.consecutiveFailures;
58285
+ summary.suppressedAttempts = recoveredFailure?.suppressedAttempts;
57017
58286
  summary.durationMs = Date.now() - addressReplayStartedAt;
57018
58287
  const newestSyncMs = this.syncByAddress.get(address) ?? lastSyncMs;
57019
58288
  logWithTimestamp(
@@ -57080,6 +58349,7 @@ var XmtpService = class _XmtpService {
57080
58349
  function createOfflineReplayAddressSummary(address) {
57081
58350
  return {
57082
58351
  address,
58352
+ outcome: "skipped",
57083
58353
  replayed: 0,
57084
58354
  skipped: 0,
57085
58355
  conversations: 0,
@@ -58911,7 +60181,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
58911
60181
  client: {
58912
60182
  id: "gateway-client",
58913
60183
  displayName: "okx-a2a-node",
58914
- version: "0.2.2",
60184
+ version: "0.2.3-beta-6f2a8ec2c7-260810184309",
58915
60185
  platform: "node",
58916
60186
  mode: "backend",
58917
60187
  instanceId
@@ -58922,7 +60192,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
58922
60192
  commands: [],
58923
60193
  permissions: {},
58924
60194
  locale: Intl.DateTimeFormat().resolvedOptions().locale || "en-US",
58925
- userAgent: `okx-a2a-node/${"0.2.2"}`,
60195
+ userAgent: `okx-a2a-node/${"0.2.3-beta-6f2a8ec2c7-260810184309"}`,
58926
60196
  auth: {
58927
60197
  ...config.token ? { token: config.token } : {},
58928
60198
  ...config.password ? { password: config.password } : {}
@@ -60519,15 +61789,21 @@ async function createGroupForAddress(params) {
60519
61789
  { groupName: params.groupName }
60520
61790
  );
60521
61791
  }
60522
- function bindOutboundJobProviderToCurrentDefault(sessionStore, jobId) {
61792
+ async function bindOutboundJobProviderToCurrentDefault(sessionStore, jobId) {
60523
61793
  if (!jobId || jobId === BACKUP_JOB_ID) {
60524
61794
  return;
60525
61795
  }
60526
61796
  try {
60527
- const result = bindJobProviderToCurrentDefaultIfMissing({
61797
+ const result = await bindNewJobProviderIfReady({
60528
61798
  store: sessionStore,
60529
61799
  jobId
60530
61800
  });
61801
+ if (result?.degraded) {
61802
+ logWithTimestamp(
61803
+ `[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`
61804
+ );
61805
+ return;
61806
+ }
60531
61807
  if (result?.created) {
60532
61808
  logWithTimestamp(`[okx-agent-task] job provider bound from outbound-xmtp: job=${jobId} provider=${result.provider}`);
60533
61809
  }
@@ -61251,7 +62527,7 @@ async function handleXmtpSendCommand(params) {
61251
62527
  const ownedStore2 = params.sessionStore ? null : new SessionStore();
61252
62528
  const sessionStore2 = params.sessionStore ?? ownedStore2;
61253
62529
  try {
61254
- bindOutboundJobProviderToCurrentDefault(sessionStore2, command.jobId);
62530
+ await bindOutboundJobProviderToCurrentDefault(sessionStore2, command.jobId);
61255
62531
  bindOutboundOpenClawRouteIfCurrent(sessionStore2, command.jobId, command.gatewaySessionKeys);
61256
62532
  return await handleSqliteGroupSendCommand({
61257
62533
  command,
@@ -61265,7 +62541,7 @@ async function handleXmtpSendCommand(params) {
61265
62541
  const ownedStore = params.sessionStore ? null : new SessionStore();
61266
62542
  const sessionStore = params.sessionStore ?? ownedStore;
61267
62543
  try {
61268
- bindOutboundJobProviderToCurrentDefault(sessionStore, command.jobId);
62544
+ await bindOutboundJobProviderToCurrentDefault(sessionStore, command.jobId);
61269
62545
  bindOutboundOpenClawRouteIfCurrent(sessionStore, command.jobId, command.gatewaySessionKeys);
61270
62546
  const { file, sessionAgentId } = await readFileForSend({
61271
62547
  command,
@@ -63050,41 +64326,76 @@ function upsertDirectCommunicationSession(deps, target) {
63050
64326
  toAgentXmtpAddress: remoteAgent?.communicationAddress ?? null
63051
64327
  });
63052
64328
  }
63053
- function bindMessageJobProviderToCurrentDefault(deps, jobId, stage = "inbound-xmtp") {
64329
+ function neutralProviderGateResult(currentProvider) {
64330
+ return {
64331
+ allowed: true,
64332
+ created: false,
64333
+ provider: null,
64334
+ currentProvider,
64335
+ degraded: false,
64336
+ bindingSource: null,
64337
+ readinessState: "not_required",
64338
+ recoveryAction: "none"
64339
+ };
64340
+ }
64341
+ async function bindMessageJobProviderToCurrentDefault(deps, jobId, stage = "inbound-xmtp", options = {}) {
63054
64342
  const currentProvider = deps.sessionStore?.getDefaultAiProvider() ?? null;
63055
64343
  if (!jobId || jobId === BACKUP_JOB_ID) {
63056
- return { allowed: true, created: false, provider: null, currentProvider };
64344
+ return neutralProviderGateResult(currentProvider);
63057
64345
  }
63058
64346
  try {
63059
- const result = bindJobProviderToCurrentDefaultIfMissing({
64347
+ const result = await bindNewJobProviderIfReady({
63060
64348
  store: deps.sessionStore,
63061
- jobId
64349
+ jobId,
64350
+ env: options.env,
64351
+ platform: options.platform,
64352
+ readiness: options.readiness
63062
64353
  });
63063
64354
  if (!result) {
63064
- return { allowed: true, created: false, provider: null, currentProvider };
64355
+ return neutralProviderGateResult(currentProvider);
63065
64356
  }
63066
- if (result?.created) {
64357
+ if (result.degraded) {
64358
+ logWithTimestamp(
64359
+ `[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`
64360
+ );
64361
+ return {
64362
+ allowed: true,
64363
+ created: false,
64364
+ provider: null,
64365
+ currentProvider,
64366
+ degraded: true,
64367
+ bindingSource: null,
64368
+ readinessState: result.readiness?.state ?? "not_required",
64369
+ recoveryAction: result.readiness?.recoveryAction ?? "none"
64370
+ };
64371
+ }
64372
+ const boundProvider = result.provider ? normalizeAiProvider(result.provider) : null;
64373
+ if (result.created) {
63067
64374
  logWithTimestamp(
63068
- `[okx-agent-task:${deps.myXmtpAddress}] job provider bound from ${stage}: job=${shortenLogValue(jobId)} provider=${result.provider}`
64375
+ `[okx-agent-task:${deps.myXmtpAddress}] job provider bound from ${stage}: job=${shortenLogValue(jobId)} provider=${boundProvider}`
63069
64376
  );
63070
64377
  }
63071
- if (currentProvider && result.provider !== currentProvider) {
64378
+ if (currentProvider && boundProvider !== currentProvider) {
63072
64379
  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`
64380
+ `[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
64381
  );
63075
64382
  }
63076
64383
  return {
63077
64384
  allowed: true,
63078
64385
  created: result.created,
63079
- provider: result.provider,
63080
- currentProvider
64386
+ provider: boundProvider,
64387
+ currentProvider,
64388
+ degraded: false,
64389
+ bindingSource: result.bindingSource,
64390
+ readinessState: result.readiness?.state ?? "not_required",
64391
+ recoveryAction: result.readiness?.recoveryAction ?? "none"
63081
64392
  };
63082
64393
  } catch (err) {
63083
64394
  logWithTimestamp(
63084
64395
  `[okx-agent-task:${deps.myXmtpAddress}] job provider bind failed from ${stage}: job=${shortenLogValue(jobId)}`,
63085
64396
  err
63086
64397
  );
63087
- return { allowed: true, created: false, provider: null, currentProvider };
64398
+ return neutralProviderGateResult(currentProvider);
63088
64399
  }
63089
64400
  }
63090
64401
  function isCanonicalJobPeerSessionKey(sessionKey) {
@@ -63366,7 +64677,7 @@ async function dispatchDirectToUserNotification(params) {
63366
64677
  ...extra
63367
64678
  });
63368
64679
  const providerBindStartedAt = Date.now();
63369
- const providerGate = bindMessageJobProviderToCurrentDefault(deps, notification.jobId, "direct-to-user");
64680
+ const providerGate = await bindMessageJobProviderToCurrentDefault(deps, notification.jobId, "direct-to-user");
63370
64681
  timing.mark("providerBind", providerBindStartedAt);
63371
64682
  logger.info(LogEvent.SYSTEM_NOTIFICATION_RECEIVED, commonExtras({
63372
64683
  ...inboundStageExtras("inbound/direct_to_user_received", "success"),
@@ -63880,7 +65191,7 @@ async function processFileMessage(ctx, deps, options = {}) {
63880
65191
  return true;
63881
65192
  }
63882
65193
  const providerBindStartedAt = Date.now();
63883
- const providerGate = bindMessageJobProviderToCurrentDefault(deps, systemNotification.jobId, "system-notification");
65194
+ const providerGate = await bindMessageJobProviderToCurrentDefault(deps, systemNotification.jobId, "system-notification");
63884
65195
  timing.mark("providerBind", providerBindStartedAt);
63885
65196
  if (!providerGate.allowed) {
63886
65197
  logger.info(LogEvent.INBOUND_IGNORED_NON_BUSINESS, timing.extras({
@@ -64001,7 +65312,7 @@ async function processFileMessage(ctx, deps, options = {}) {
64001
65312
  timing.mark("routeResolve", routeStartedAt);
64002
65313
  if (route.route === "job") {
64003
65314
  const providerBindStartedAt = Date.now();
64004
- const providerGate = bindMessageJobProviderToCurrentDefault(deps, route.jobId, "inbound-xmtp");
65315
+ const providerGate = await bindMessageJobProviderToCurrentDefault(deps, route.jobId, "inbound-xmtp");
64005
65316
  timing.mark("providerBind", providerBindStartedAt);
64006
65317
  if (!providerGate.allowed) {
64007
65318
  logger.info(LogEvent.INBOUND_IGNORED_NON_BUSINESS, timing.extras({
@@ -64920,12 +66231,12 @@ async function runListenerWithLock(options, paths) {
64920
66231
  });
64921
66232
  }
64922
66233
  });
64923
- service.setPluginVersion("0.2.2");
66234
+ service.setPluginVersion("0.2.3-beta-6f2a8ec2c7-260810184309");
64924
66235
  await service.init();
64925
66236
  const pluginVersionStatus = service.pluginVersionStatus;
64926
66237
  if (pluginVersionStatus.unavailable) {
64927
66238
  throw new Error(
64928
- `@okxweb3/a2a-node v${"0.2.2"} is below the required minimum v${pluginVersionStatus.minVersion}`
66239
+ `@okxweb3/a2a-node v${"0.2.3-beta-6f2a8ec2c7-260810184309"} is below the required minimum v${pluginVersionStatus.minVersion}`
64929
66240
  );
64930
66241
  }
64931
66242
  const systemConfig = service.getSystemConfig();
@@ -64943,7 +66254,7 @@ async function runListenerWithLock(options, paths) {
64943
66254
  onchainosAgentId: "*",
64944
66255
  reason: "system-config missing sentryDsn",
64945
66256
  pluginId: "@okxweb3/a2a-node",
64946
- pluginVersion: "0.2.2"
66257
+ pluginVersion: "0.2.3-beta-6f2a8ec2c7-260810184309"
64947
66258
  });
64948
66259
  }
64949
66260
  logWithTimestamp(
@@ -66210,6 +67521,32 @@ function validTimestamp(value) {
66210
67521
  init_session_store();
66211
67522
  init_ai_provider();
66212
67523
  init_ai_command();
67524
+ init_codex_readiness();
67525
+
67526
+ // src/codex-readiness-types.ts
67527
+ var CODEX_COMMAND_SOURCES = [
67528
+ "explicit_override",
67529
+ "persisted",
67530
+ "path",
67531
+ "app_bundled"
67532
+ ];
67533
+ var CODEX_READINESS_STATES = [
67534
+ "ready",
67535
+ "not_installed",
67536
+ "not_authenticated",
67537
+ "probe_timeout",
67538
+ "probe_failed"
67539
+ ];
67540
+ var CODEX_RECOVERY_ACTIONS = ["none", "run_setup", "run_login", "install_cli"];
67541
+ var JOB_PROVIDER_BINDING_SOURCES = [
67542
+ "existing_binding",
67543
+ "current_runtime",
67544
+ "default_provider",
67545
+ "explicit_env"
67546
+ ];
67547
+
67548
+ // src/index.ts
67549
+ init_sentry_logger();
66213
67550
  init_update_cli();
66214
67551
  init_win_spawn();
66215
67552
 
@@ -67563,7 +68900,7 @@ async function exportDiagnosticLogs(options) {
67563
68900
  node: process.version,
67564
68901
  platform: process.platform,
67565
68902
  arch: process.arch,
67566
- packageVersion: true ? "0.2.2" : "unknown",
68903
+ packageVersion: true ? "0.2.3-beta-6f2a8ec2c7-260810184309" : "unknown",
67567
68904
  sensitiveContentIncluded: options.includeSensitiveContent,
67568
68905
  listenerAndLlmContentIncluded: true,
67569
68906
  credentialsAlwaysRedacted: true,
@@ -68432,6 +69769,7 @@ init_log();
68432
69769
  init_win_compat();
68433
69770
  init_ai_command();
68434
69771
  init_ai_provider();
69772
+ init_codex_readiness();
68435
69773
  init_autostart();
68436
69774
  init_autostart_windows();
68437
69775
  init_daemon();
@@ -68465,11 +69803,70 @@ async function refreshAgentsAndWait(timeoutMs = 6e4) {
68465
69803
 
68466
69804
  // src/runtime-switch.ts
68467
69805
  init_ai_provider();
68468
- async function performRuntimeSwitch(store) {
68469
- const result = switchProvider({ store, env: process.env });
69806
+ init_sentry_logger();
69807
+ init_log_fields();
69808
+ var RUNTIME_SWITCH_COMPONENT = "node_runtime_switch";
69809
+ async function performRuntimeSwitch(store, options = {}) {
69810
+ const result = await switchProviderWithReadinessGate({
69811
+ store,
69812
+ env: options.env ?? process.env,
69813
+ platform: options.platform,
69814
+ readiness: options.readiness
69815
+ });
68470
69816
  await dispatchRuntimeSwitchUserMessage(store, result);
68471
69817
  return result;
68472
69818
  }
69819
+ async function switchProviderWithReadinessGate(options) {
69820
+ const gated = await switchProviderWithReadinessGateCore(options);
69821
+ emitRuntimeSwitchCheckpoints(gated);
69822
+ return gated.result;
69823
+ }
69824
+ function emitRuntimeSwitchCheckpoints(gated) {
69825
+ const { result, runtime, readiness } = gated;
69826
+ logger.info(LogEvent.RUNTIME_DETECTED, {
69827
+ component: RUNTIME_SWITCH_COMPONENT,
69828
+ provider: runtime,
69829
+ detectedProvider: runtime,
69830
+ ...logFieldExtras({
69831
+ checkpoint: "runtime_detected",
69832
+ outcome: runtime === "unknown" ? "ignored" : "success",
69833
+ transport: "cli",
69834
+ eventFamily: "lifecycle"
69835
+ })
69836
+ });
69837
+ if (readiness) {
69838
+ logger.info(LogEvent.PROVIDER_READINESS_CHECKED, {
69839
+ component: RUNTIME_SWITCH_COMPONENT,
69840
+ provider: readiness.provider,
69841
+ readinessState: readiness.state,
69842
+ commandSource: readiness.commandSource ?? "",
69843
+ commandPersisted: String(readiness.commandPersisted),
69844
+ recoveryAction: readiness.recoveryAction,
69845
+ ...logFieldExtras({
69846
+ checkpoint: readiness.ready ? "provider_ready" : "provider_not_ready",
69847
+ outcome: readiness.ready ? "success" : "failed",
69848
+ transport: "cli",
69849
+ eventFamily: "lifecycle"
69850
+ })
69851
+ });
69852
+ }
69853
+ if (result.ok && result.action === "switched") {
69854
+ logger.info(LogEvent.PROVIDER_SWITCHED, {
69855
+ component: RUNTIME_SWITCH_COMPONENT,
69856
+ provider: result.provider,
69857
+ previousProvider: result.previousProvider ?? "",
69858
+ changed: String(result.changed),
69859
+ readinessState: result.readinessState ?? "not_required",
69860
+ commandSource: result.commandSource ?? "",
69861
+ ...logFieldExtras({
69862
+ checkpoint: "provider_switched",
69863
+ outcome: "success",
69864
+ transport: "cli",
69865
+ eventFamily: "lifecycle"
69866
+ })
69867
+ });
69868
+ }
69869
+ }
68473
69870
  async function dispatchRuntimeSwitchUserMessage(store, result) {
68474
69871
  if (!result.ok || !result.changed || !result.userMessage) {
68475
69872
  return;
@@ -68803,20 +70200,34 @@ var providerBindingChecker = {
68803
70200
  }
68804
70201
  }
68805
70202
  };
70203
+ function resolveProviderCliTarget(env, store) {
70204
+ const runtime = detectRuntime(env);
70205
+ if (runtime === "codex" || runtime === "claude") {
70206
+ return runtime;
70207
+ }
70208
+ if (runtime !== "unknown") {
70209
+ return null;
70210
+ }
70211
+ const storedDefault = store.getDefaultAiProvider();
70212
+ return storedDefault === "codex" || storedDefault === "claude" ? storedDefault : null;
70213
+ }
70214
+ function readProviderCliTarget(env) {
70215
+ const store = new SessionStore();
70216
+ try {
70217
+ const provider = resolveProviderCliTarget(env, store);
70218
+ return {
70219
+ provider,
70220
+ storedCommand: provider ? store.getAiProviderCommand(provider) : null
70221
+ };
70222
+ } finally {
70223
+ store.close();
70224
+ }
70225
+ }
68806
70226
  var providerCliChecker = {
68807
70227
  id: "provider_cli",
68808
70228
  title: "AI provider CLI",
68809
70229
  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
- }
70230
+ const { provider, storedCommand } = readProviderCliTarget(ctx.env);
68820
70231
  if (!provider) {
68821
70232
  return null;
68822
70233
  }
@@ -68839,12 +70250,29 @@ var providerCliChecker = {
68839
70250
  }
68840
70251
  const auth = provider === "codex" ? await checkCodexCliAuthStatus(resolved) : await checkClaudeCliAuthStatus(resolved);
68841
70252
  if (auth.ok) {
70253
+ if (provider === "codex" && ctx.env.OKX_A2A_AI_CODEX_COMMAND && storedCommand !== resolved) {
70254
+ return {
70255
+ id: "provider_cli",
70256
+ title: "AI provider CLI",
70257
+ status: "fail",
70258
+ severity: "required",
70259
+ detail: "codex CLI is authenticated but its command is not persisted for the background daemon",
70260
+ fix: {
70261
+ kind: "auto",
70262
+ description: "Persist the validated Codex command for background daemon use."
70263
+ },
70264
+ data: { provider }
70265
+ };
70266
+ }
68842
70267
  return {
68843
70268
  id: "provider_cli",
68844
70269
  title: "AI provider CLI",
68845
70270
  status: "pass",
68846
70271
  severity: "required",
68847
- detail: `${provider} CLI at ${resolved} is logged in`,
70272
+ // `detail` is forwarded to Sentry (doctor-sentry.ts), so it carries no
70273
+ // resolved path or arguments. The exact command stays in `data`, which is
70274
+ // local report output only.
70275
+ detail: `${provider} CLI is installed and logged in`,
68848
70276
  data: { provider, command: resolved }
68849
70277
  };
68850
70278
  }
@@ -68855,7 +70283,8 @@ var providerCliChecker = {
68855
70283
  title: "AI provider CLI",
68856
70284
  status: "fail",
68857
70285
  severity: "required",
68858
- detail: `${provider} CLI at ${resolved} is not logged in (${auth.reason})`,
70286
+ // No resolved path in `detail`: it is forwarded to Sentry.
70287
+ detail: `${provider} CLI is installed but not logged in (${auth.reason})`,
68859
70288
  // Logging in needs a human (device/browser auth). In --non-interactive
68860
70289
  // mode never launch it — degrade to a manual instruction so unattended
68861
70290
  // callers (install scripts) cannot hang waiting on stdin.
@@ -68872,18 +70301,9 @@ var providerCliChecker = {
68872
70301
  };
68873
70302
  },
68874
70303
  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
- }
70304
+ const { provider, storedCommand } = readProviderCliTarget(ctx.env);
68885
70305
  if (!provider) {
68886
- throw new Error("no codex/claude provider bound");
70306
+ throw new Error("no codex/claude runtime detected and no codex/claude provider bound");
68887
70307
  }
68888
70308
  const resolveCli = () => provider === "codex" ? resolveCodexCommandPath(ctx.env, storedCommand, ctx.platform) : resolveCommandPath(resolveAiProviderCommand(provider, ctx.env, storedCommand, ctx.platform), ctx.env, ctx.platform);
68889
70309
  let resolved = resolveCli();
@@ -68918,6 +70338,28 @@ var providerCliChecker = {
68918
70338
  }
68919
70339
  steps.push("completed interactive login");
68920
70340
  }
70341
+ if (provider === "codex") {
70342
+ const store = new SessionStore();
70343
+ try {
70344
+ const readiness = await checkCodexReadiness({
70345
+ store,
70346
+ env: ctx.env,
70347
+ platform: ctx.platform,
70348
+ forceRefresh: true,
70349
+ persistOverrideCommand: true
70350
+ });
70351
+ if (!readiness.ready) {
70352
+ throw new Error(
70353
+ `codex readiness could not be confirmed (${readiness.state}); ${readiness.recoveryGuidance}`.trim()
70354
+ );
70355
+ }
70356
+ if (readiness.commandPersisted) {
70357
+ steps.push("persisted the validated codex command");
70358
+ }
70359
+ } finally {
70360
+ store.close();
70361
+ }
70362
+ }
68921
70363
  return steps.length > 0 ? steps.join("; ") : "CLI already installed and logged in";
68922
70364
  }
68923
70365
  };
@@ -69274,8 +70716,14 @@ var CHECKERS = [
69274
70716
  npmChecker,
69275
70717
  cliVersionChecker,
69276
70718
  windowsNativeLauncherChecker,
69277
- providerBindingChecker,
70719
+ // provider_cli BEFORE provider_binding, deliberately: the intended runtime has
70720
+ // to be installed, authenticated and durably persisted before the binding fixer
70721
+ // attempts its readiness-gated runtime switch. In the other order the switch
70722
+ // fails on a not-ready Codex, preserves the stale default, and the CLI checker
70723
+ // then inspects that stale provider instead of the detected one — so Codex is
70724
+ // never repaired and the run never converges.
69278
70725
  providerCliChecker,
70726
+ providerBindingChecker,
69279
70727
  gatewayPluginChecker,
69280
70728
  gatewayConfigChecker,
69281
70729
  autostartChecker,
@@ -69288,7 +70736,7 @@ async function runDoctor(options = {}) {
69288
70736
  platform: options.platform ?? process.platform,
69289
70737
  env: options.env ?? process.env,
69290
70738
  target: options.target ?? resolveDoctorTarget(options.env ?? process.env),
69291
- cliVersion: options.cliVersion ?? (true ? "0.2.2" : "0.0.0"),
70739
+ cliVersion: options.cliVersion ?? (true ? "0.2.3-beta-6f2a8ec2c7-260810184309" : "0.0.0"),
69292
70740
  fixMode: options.fix === true,
69293
70741
  nonInteractive: options.nonInteractive === true,
69294
70742
  packageChanged: false,
@@ -69513,6 +70961,13 @@ init_autostart_windows();
69513
70961
  AgentMessageDirection,
69514
70962
  AiRunner,
69515
70963
  BACKUP_JOB_ID,
70964
+ CODEX_APP_DIRS_ENV,
70965
+ CODEX_AUTH_PROBE_TIMEOUT_MS,
70966
+ CODEX_COMMAND_SOURCES,
70967
+ CODEX_PROBE_TIMEOUT_MS,
70968
+ CODEX_READINESS_CACHE_TTL_MS,
70969
+ CODEX_READINESS_STATES,
70970
+ CODEX_RECOVERY_ACTIONS,
69516
70971
  CommandStore,
69517
70972
  DEFAULT_AI_PERMISSION_PRESET,
69518
70973
  DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC,
@@ -69522,10 +70977,13 @@ init_autostart_windows();
69522
70977
  HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS,
69523
70978
  InboundReplayGate,
69524
70979
  InvalidXmtpMessageStore,
70980
+ JOB_PROVIDER_BINDING_SOURCES,
70981
+ LogEvent,
69525
70982
  MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG,
69526
70983
  MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN,
69527
70984
  NATIVE_LAUNCHER_EXE_NAME,
69528
70985
  OPENCLAW_GATEWAY_ROUTE_GROUP_ID,
70986
+ ProviderNotReadyError,
69529
70987
  SYSTEM_NOTIFICATION_SESSION_KEY,
69530
70988
  SessionBusyTracker,
69531
70989
  SessionStore,
@@ -69546,15 +71004,20 @@ init_autostart_windows();
69546
71004
  activeDaemonPointerPath,
69547
71005
  aiRunSentryExtra,
69548
71006
  assertHermesSetupSupportedOnPlatform,
71007
+ authStatusFromReason,
69549
71008
  bindJobProviderToCurrentDefaultIfMissing,
71009
+ bindJobProviderToCurrentDefaultIfReady,
69550
71010
  bindJobProviderToCurrentRuntimeIfMissing,
71011
+ bindJobProviderToCurrentRuntimeIfReady,
69551
71012
  bindMessageJobProviderToCurrentDefault,
71013
+ bindNewJobProviderIfReady,
69552
71014
  bindOpenClawGatewayRouteFromEnv,
69553
71015
  buildAgentMessageNotice,
69554
71016
  buildAiAdapterCommand,
69555
71017
  buildAiProviderEnv,
69556
71018
  buildAutostartVbs,
69557
71019
  buildBackupJobSessionKey,
71020
+ buildCodexRecoveryGuidance,
69558
71021
  buildDispatchSessionKey,
69559
71022
  buildDoctorSentryExtra,
69560
71023
  buildExternalCommandInvocation,
@@ -69583,7 +71046,15 @@ init_autostart_windows();
69583
71046
  callSessionsCreate,
69584
71047
  callSessionsDelete,
69585
71048
  callSessionsSend,
71049
+ checkClaudeCliAuthStatus,
71050
+ checkCodexCliAuthStatus,
71051
+ checkCodexReadiness,
69586
71052
  claimActiveDaemon,
71053
+ classifyJobBindingSource,
71054
+ clearCodexReadinessCache,
71055
+ codexCommandResolvesWithoutOverride,
71056
+ codexReadinessCacheKey,
71057
+ collectCodexAppBundledCandidates,
69587
71058
  collectCodexCommandCandidates,
69588
71059
  commandExists,
69589
71060
  createAiToolFailureTracker,
@@ -69599,6 +71070,7 @@ init_autostart_windows();
69599
71070
  ensureDaemonReady,
69600
71071
  ensureDefaultAiProvider,
69601
71072
  ensureOpenClawOkxA2aPluginConfig,
71073
+ ensureProviderReadyForBinding,
69602
71074
  ensureWindowsNativeLauncher,
69603
71075
  evictPid,
69604
71076
  exportDiagnosticLogs,
@@ -69626,6 +71098,7 @@ init_autostart_windows();
69626
71098
  isDefaultTaskHome,
69627
71099
  isGatewayAvailableForHeartbeat,
69628
71100
  isHermesGatewayPluginEnabled,
71101
+ isInfoEventAllowlisted,
69629
71102
  isOfflineReplayForTrigger,
69630
71103
  isOpenClawA2aPluginAvailable,
69631
71104
  isOpenClawGatewayAvailable,
@@ -69651,11 +71124,15 @@ init_autostart_windows();
69651
71124
  parsePluginYamlVersion,
69652
71125
  parseWindowsParentProcessJson,
69653
71126
  performRuntimeSwitch,
71127
+ persistCurrentRuntimeSelectionForNewJob,
69654
71128
  pickOnchainosWin32Candidate,
71129
+ prepareAndBindCurrentRuntimeForNewJob,
69655
71130
  printLogsExportUsage,
69656
71131
  printXmtpTestUsage,
71132
+ probeCodexAuthStatus,
69657
71133
  processFileMessage,
69658
71134
  readAiProviderTimeoutMs,
71135
+ readCachedCodexReadiness,
69659
71136
  readLastLines,
69660
71137
  readParentProcessCommandForPlatform,
69661
71138
  readUserAttentionWatcherEvent,
@@ -69663,14 +71140,19 @@ init_autostart_windows();
69663
71140
  registerUserAttentionWatcher,
69664
71141
  releaseActiveDaemon,
69665
71142
  removeUserAttentionWatcher,
71143
+ replayScanBackoffMs,
69666
71144
  reportDoctorRunToSentry,
69667
71145
  reportUserNotificationFailure,
69668
71146
  resetMessageEligibleOfflineReplayCapabilityForTests,
69669
71147
  resetResolvedOnchainosBinForTests,
69670
71148
  resolveAiPermissionPreset,
69671
71149
  resolveAiProviderCommand,
71150
+ resolveAiProviderCommandWithSource,
69672
71151
  resolveAiProviderForDispatch,
71152
+ resolveAiProviderForDispatchWithReadiness,
71153
+ resolveCodexCommandCandidates,
69673
71154
  resolveCodexCommandPath,
71155
+ resolveCodexCommandSource,
69674
71156
  resolveConfiguredAiProvider,
69675
71157
  resolveConfiguredAiProviderForJob,
69676
71158
  resolveDirectCommunicationSessionTarget,
@@ -69682,6 +71164,7 @@ init_autostart_windows();
69682
71164
  resolveOpenClawGatewayConfig,
69683
71165
  resolveOpenClawGatewayRoute,
69684
71166
  resolveOpenClawGatewayRoutes,
71167
+ resolveProviderCommandWithSelfHeal,
69685
71168
  resolveSystemNotificationTargets,
69686
71169
  resolveTaskConfigPath,
69687
71170
  resolveTaskHome,
@@ -69709,6 +71192,8 @@ init_autostart_windows();
69709
71192
  subscribeUserAttentionEvents,
69710
71193
  summarizeXmtpTestEvents,
69711
71194
  switchProvider,
71195
+ switchProviderWithReadinessGate,
71196
+ switchProviderWithReadinessGateCore,
69712
71197
  systemdUnitHasCurrentRestartPolicy,
69713
71198
  toOpenClawGatewaySessionKey,
69714
71199
  toWindowsInvocation,