From bdab46b3dd020114865bddbb1c6900ae810cc076 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 13 Jul 2026 19:35:14 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20Sub2API=20?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E8=BF=90=E8=A1=8C=E6=80=81=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../unidesk-sub2api/references/codex-pool.md | 9 +- .../platform-infra-sub2api-codex/accounts.ts | 4 + .../platform-infra-sub2api-codex/actions.ts | 33 +++- .../platform-infra-sub2api-codex/options.ts | 3 +- .../platform-infra-sub2api-codex/redaction.ts | 37 +++++ .../remote-python-sync.ts | 150 +++++++++++++++++- .../remote-scripts.ts | 4 + .../platform-infra-sub2api-codex/remote.ts | 3 +- .../src/platform-infra-sub2api-codex/types.ts | 3 +- 9 files changed, 238 insertions(+), 8 deletions(-) diff --git a/.agents/skills/unidesk-sub2api/references/codex-pool.md b/.agents/skills/unidesk-sub2api/references/codex-pool.md index bd414a78..1a4a2666 100644 --- a/.agents/skills/unidesk-sub2api/references/codex-pool.md +++ b/.agents/skills/unidesk-sub2api/references/codex-pool.md @@ -1,6 +1,12 @@ ## Codex Pool -`codex-pool plan|sync|validate` 同时覆盖 k3s target 和 PK01 host-Docker target。PK01 host-Docker 的 `sync --confirm` 通过 Sub2API admin API 对齐 group、YAML-managed accounts、统一消费 API key、capacity/loadFactor、WebSocket v2 标记和内置 `temp_unschedulable` 规则;统一消费 key 写入 YAML 声明的 `targets[PK01].hostDocker.envPath`,不创建 k8s Secret,不部署 sentinel 资源,也不触发 `sub2api apply`、Docker compose、Caddy reload 或容器重启。`sentinel-report`、`sentinel-probe`、`sentinel-image` 和部分 `trace` 能力仍以 k3s target 为主;需要这些能力时显式选择对应 k3s target。 +- `codex-pool plan|sync|validate|runtime-state` 同时覆盖 k3s target 和 PK01 host-Docker target: + - PK01 host-Docker 的 `sync --confirm` 通过 Sub2API admin API 对齐 group、YAML-managed accounts、统一消费 API key、capacity/loadFactor、WebSocket v2 标记和内置 `temp_unschedulable` 规则; + - 统一消费 key 写入 YAML 声明的 `targets[PK01].hostDocker.envPath`; + - PK01 路径不创建 k8s Secret、不部署 sentinel 资源,也不触发 `sub2api apply`、Docker compose、Caddy reload 或容器重启; + - `runtime-state` 只读列出 pool group 内全部账号,包括手工账号,并披露临时不可调度规则、当前冻结状态、`pool_mode`、代理绑定和相对 YAML 基线的差异; + - `runtime-state` 不输出 API key 或 credentials 正文,默认返回紧凑 JSON,使用 `--full` 或 `--raw` 下钻; + - `sentinel-report`、`sentinel-probe`、`sentinel-image` 和部分 `trace` 能力仍以 k3s target 为主,需要这些能力时显式选择对应 k3s target。 k3s 账号池操作示例: @@ -8,6 +14,7 @@ k3s 账号池操作示例: bun scripts/cli.ts platform-infra sub2api codex-pool plan --target D601 bun scripts/cli.ts platform-infra sub2api codex-pool sync --target D601 --confirm bun scripts/cli.ts platform-infra sub2api codex-pool validate --target D601 +bun scripts/cli.ts platform-infra sub2api codex-pool runtime-state --target PK01 bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status --target D601 bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --target D601 --confirm diff --git a/scripts/src/platform-infra-sub2api-codex/accounts.ts b/scripts/src/platform-infra-sub2api-codex/accounts.ts index 409e5267..029b844d 100644 --- a/scripts/src/platform-infra-sub2api-codex/accounts.ts +++ b/scripts/src/platform-infra-sub2api-codex/accounts.ts @@ -79,3 +79,7 @@ export function desiredAccountTempUnschedulableMap(pool: CodexPoolConfig): Recor } return policies; } + +export function desiredDefaultAccountTempUnschedulable(pool: CodexPoolConfig): Record { + return renderSub2ApiTempUnschedulableCredentials(pool.defaultTempUnschedulable); +} diff --git a/scripts/src/platform-infra-sub2api-codex/actions.ts b/scripts/src/platform-infra-sub2api-codex/actions.ts index d97e781d..9afab1bd 100644 --- a/scripts/src/platform-infra-sub2api-codex/actions.ts +++ b/scripts/src/platform-infra-sub2api-codex/actions.ts @@ -27,9 +27,9 @@ import { collectCodexProfiles, readCodexPoolConfig } from "./config"; import { codexPoolSentinelProbeConfigFingerprint, fingerprint } from "./config-utils"; import { apiKeyPreview, codexConsumerBaseUrl, fetchPoolApiKey, probePublicModels, validatePublicGatewayWithKey, writeLocalCodexConfig } from "./local-codex"; import { manualBindingSourcePlan, poolTarget, prepareTargetPublicExposureSecret, protectedManualAccountNamesForTarget, resolvedManualAccountProtections, secretMaterialSummary, sentinelProfileSecrets, targetFrpPublicExposure, targetPublicExposureApplyScript, targetPublicExposureSummary } from "./public-exposure"; -import { codexPoolConfigSummary, compactProfile, compactSentinelProbeResult, redactProfile, renderSub2ApiTempUnschedulableCredentials } from "./redaction"; +import { codexPoolConfigSummary, compactProfile, compactRuntimeAccountState, compactSentinelProbeResult, redactProfile, renderSub2ApiTempUnschedulableCredentials } from "./redaction"; import { boolField, capture, compactCapture, parseJsonOutput, runRemoteCodexPoolScript } from "./remote"; -import { cleanupProbesScript, sentinelImageBuildScript, sentinelImageStatusScript, sentinelProbeScript, sentinelReportScript, syncScript, traceScript, validateScript } from "./remote-scripts"; +import { cleanupProbesScript, runtimeStateScript, sentinelImageBuildScript, sentinelImageStatusScript, sentinelProbeScript, sentinelReportScript, syncScript, traceScript, validateScript } from "./remote-scripts"; import { codexPoolSyncSummary, codexPoolValidationSummary, renderCodexPoolPlan, renderCodexPoolSentinelProbeResult, renderCodexPoolSyncResult, renderCodexPoolValidateResult, renderSentinelReport, renderTraceReport, renderedCliResult } from "./render"; import { codexPoolRuntimeTarget, defaultCodexPoolRuntimeTargetId, targetFlag } from "./runtime-target"; import { codexPoolConfigPath, serviceName, sub2apiConfigPath } from "./types"; @@ -317,6 +317,35 @@ export async function codexPoolValidate(config: UniDeskConfig, options: Disclosu return options.full ? response : renderCodexPoolValidateResult(response); } +export async function codexPoolRuntimeState(config: UniDeskConfig, options: DisclosureOptions): Promise> { + const pool = readCodexPoolConfig(); + const runtimeTarget = codexPoolRuntimeTarget(options.targetId); + const result = await runRemoteCodexPoolScript(config, "runtime-state", runtimeStateScript(pool, runtimeTarget), runtimeTarget); + const parsed = parseJsonOutput(result.stdout); + if (options.raw) { + return { + ok: result.exitCode === 0 && boolField(parsed, "ok", false), + action: "platform-infra-sub2api-codex-pool-runtime-state", + remote: compactCapture(result, { full: true }), + parsed, + valuesPrinted: false, + }; + } + return { + ok: result.exitCode === 0 && boolField(parsed, "ok", false), + action: "platform-infra-sub2api-codex-pool-runtime-state", + target: { + id: runtimeTarget.id, + route: runtimeTarget.route, + namespace: runtimeTarget.namespace, + runtimeMode: runtimeTarget.runtimeMode, + }, + state: options.full ? parsed : compactRuntimeAccountState(parsed), + remote: compactCapture(result, { full: result.exitCode !== 0 || parsed === null }), + valuesPrinted: false, + }; +} + export async function codexPoolTrace(config: UniDeskConfig, options: TraceOptions): Promise | RenderedCliResult> { const pool = readCodexPoolConfig(); const runtimeTarget = codexPoolRuntimeTarget(options.targetId); diff --git a/scripts/src/platform-infra-sub2api-codex/options.ts b/scripts/src/platform-infra-sub2api-codex/options.ts index d2120e16..5c445d50 100644 --- a/scripts/src/platform-infra-sub2api-codex/options.ts +++ b/scripts/src/platform-infra-sub2api-codex/options.ts @@ -22,7 +22,7 @@ import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from ".. import { runSshCommandCapture, type SshCaptureResult } from "../ssh"; import type { ConfirmOptions, DisclosureOptions, SentinelImageOptions, SentinelProbeOptions, SentinelReportOptions, SyncOptions, TraceOptions } from "./types"; -import { codexPoolCleanupProbes, codexPoolConfigureLocal, codexPoolExpose, codexPoolPlan, codexPoolSentinelImage, codexPoolSentinelProbe, codexPoolSentinelReport, codexPoolSync, codexPoolTrace, codexPoolValidate } from "./actions"; +import { codexPoolCleanupProbes, codexPoolConfigureLocal, codexPoolExpose, codexPoolPlan, codexPoolRuntimeState, codexPoolSentinelImage, codexPoolSentinelProbe, codexPoolSentinelReport, codexPoolSync, codexPoolTrace, codexPoolValidate } from "./actions"; import { renderCodexPoolPlan } from "./render"; import { defaultCodexPoolRuntimeTargetId } from "./runtime-target"; import { codexPoolHelp } from "./types"; @@ -36,6 +36,7 @@ export async function runCodexPoolCommand(config: UniDeskConfig, args: string[]) } if (action === "sync") return await codexPoolSync(config, parseSyncOptions(args.slice(1))); if (action === "validate") return await codexPoolValidate(config, parseDisclosureOptions(args.slice(1))); + if (action === "runtime-state") return await codexPoolRuntimeState(config, parseDisclosureOptions(args.slice(1))); if (action === "trace") return await codexPoolTrace(config, parseTraceOptions(args.slice(1))); if (action === "sentinel-image") return await codexPoolSentinelImage(config, parseSentinelImageOptions(args.slice(1))); if (action === "sentinel-probe") return await codexPoolSentinelProbe(config, parseSentinelProbeOptions(args.slice(1))); diff --git a/scripts/src/platform-infra-sub2api-codex/redaction.ts b/scripts/src/platform-infra-sub2api-codex/redaction.ts index e2f22c63..34dcc5f9 100644 --- a/scripts/src/platform-infra-sub2api-codex/redaction.ts +++ b/scripts/src/platform-infra-sub2api-codex/redaction.ts @@ -188,6 +188,43 @@ export function pickSummaryFields(item: Record, keys: string[]) return result; } +export function compactRuntimeAccountState(parsed: Record | null): Record | null { + if (parsed === null) return null; + const state = isRecord(parsed.state) ? parsed.state : {}; + const accounts = recordArray(state.accounts).map((item) => { + const temp = isRecord(item.tempUnschedulable) ? item.tempUnschedulable : {}; + const poolMode = isRecord(item.poolMode) ? item.poolMode : {}; + const optimization = isRecord(item.optimization) ? item.optimization : {}; + return { + ...pickSummaryFields(item, ["accountName", "accountId", "type", "status", "schedulable", "proxyId"]), + tempEnabled: temp.enabled, + tempStatusCodes: temp.statusCodes, + tempActive: temp.active, + poolModeEnabled: poolMode.enabled, + tempUnschedulableNeedsAlignment: optimization.tempUnschedulableNeedsAlignment, + credentialsPrinted: false, + }; + }); + const runtimeImage = isRecord(parsed.runtimeImage) ? parsed.runtimeImage : {}; + return { + ok: parsed.ok, + mode: parsed.mode, + namespace: parsed.namespace, + serviceDns: parsed.serviceDns, + appPod: parsed.appPod, + runtimeImage: pickSummaryFields(runtimeImage, ["container", "image", "ready", "restartCount", "startedAt", "health"]), + group: state.group, + yamlBaseline: state.yamlBaseline, + summary: state.summary, + accounts, + disclosure: { + full: "bun scripts/cli.ts platform-infra sub2api codex-pool runtime-state --full", + raw: "bun scripts/cli.ts platform-infra sub2api codex-pool runtime-state --raw", + }, + valuesPrinted: false, + }; +} + export function compactStatusBlock(block: unknown, keys: string[]): Record | null { if (!isRecord(block)) return null; const items = recordArray(block.items); diff --git a/scripts/src/platform-infra-sub2api-codex/remote-python-sync.ts b/scripts/src/platform-infra-sub2api-codex/remote-python-sync.ts index 2ee7a07a..48abe73c 100644 --- a/scripts/src/platform-infra-sub2api-codex/remote-python-sync.ts +++ b/scripts/src/platform-infra-sub2api-codex/remote-python-sync.ts @@ -22,7 +22,7 @@ import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from ".. import { runSshCommandCapture, type SshCaptureResult } from "../ssh"; import type { CodexPoolConfig, CodexPoolRuntimeTarget } from "./types"; -import { desiredAccountCapacityMap, desiredAccountLoadFactorMap, desiredAccountTempUnschedulableMap, desiredAccountWebSocketsV2ModeMap } from "./accounts"; +import { desiredAccountCapacityMap, desiredAccountLoadFactorMap, desiredAccountTempUnschedulableMap, desiredAccountWebSocketsV2ModeMap, desiredDefaultAccountTempUnschedulable } from "./accounts"; import { resolvedManualAccountProtections } from "./public-exposure"; import { fieldManager } from "./types"; @@ -30,7 +30,7 @@ function pyJson(value: unknown): string { return `json.loads(${JSON.stringify(JSON.stringify(value))})`; } -export function remotePythonScript(mode: "sync" | "validate" | "trace" | "cleanup-probes" | "sentinel-probe", encodedPayload: string, pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): string { +export function remotePythonScript(mode: "sync" | "validate" | "runtime-state" | "trace" | "cleanup-probes" | "sentinel-probe", encodedPayload: string, pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): string { const hostDockerEnvPath = target.runtimeMode === "host-docker" ? target.hostDockerEnvPath : null; return ` set -u @@ -75,6 +75,7 @@ EXPECTED_ACCOUNT_CAPACITIES = ${pyJson(desiredAccountCapacityMap(pool))} EXPECTED_ACCOUNT_LOAD_FACTORS = ${pyJson(desiredAccountLoadFactorMap(pool))} EXPECTED_ACCOUNT_WS_MODES = json.loads(${JSON.stringify(JSON.stringify(desiredAccountWebSocketsV2ModeMap(pool)))}) EXPECTED_ACCOUNT_TEMP_UNSCHEDULABLE = json.loads(${JSON.stringify(JSON.stringify(desiredAccountTempUnschedulableMap(pool)))}) +EXPECTED_DEFAULT_TEMP_UNSCHEDULABLE = json.loads(${JSON.stringify(JSON.stringify(desiredDefaultAccountTempUnschedulable(pool)))}) MANUAL_ACCOUNT_PROTECTIONS = json.loads(${JSON.stringify(JSON.stringify(resolvedManualAccountProtections(pool, target)))}) SENTINEL_CONFIG = json.loads(${JSON.stringify(JSON.stringify(pool.sentinel))}) TARGET_EGRESS_PROXY = json.loads(${JSON.stringify(JSON.stringify(target.egressProxy))}) @@ -2436,6 +2437,134 @@ def account_temp_unschedulable_status(token): "valuesPrinted": False, } +def normalized_int(value): + if isinstance(value, bool): + return None + if isinstance(value, int): + return value + if isinstance(value, float): + return int(value) + if isinstance(value, str): + try: + return int(value.strip()) + except ValueError: + return None + return None + +def normalized_status_codes(value): + if not isinstance(value, list): + return None + codes = [] + for item in value: + code = normalized_int(item) + if code is not None and 100 <= code <= 599 and code not in codes: + codes.append(code) + return sorted(codes) + +def temp_unschedulable_active(until): + if not isinstance(until, str) or not until.strip(): + return False + try: + parsed = datetime.fromisoformat(until.replace("Z", "+00:00")) + if parsed.tzinfo is None: + parsed = parsed.replace(tzinfo=timezone.utc) + return parsed > datetime.now(timezone.utc) + except ValueError: + return False + +def account_runtime_state(token): + group = next((item for item in list_groups(token) if item.get("name") == POOL_GROUP_NAME), None) + if not isinstance(group, dict) or group.get("id") is None: + return { + "ok": False, + "group": {"name": POOL_GROUP_NAME, "id": None, "found": False}, + "accounts": [], + "error": "pool-group-not-found", + "valuesPrinted": False, + } + baseline = normalize_temp_unschedulable_credentials(EXPECTED_DEFAULT_TEMP_UNSCHEDULABLE) + baseline_codes = sorted(set(rule.get("error_code") for rule in baseline["rules"] if isinstance(rule.get("error_code"), int))) + protected_names = set(item.get("accountName") for item in MANUAL_ACCOUNT_PROTECTIONS if isinstance(item, dict) and isinstance(item.get("accountName"), str)) + accounts = list_accounts_for_group(token, group["id"]) + items = [] + for account in sorted(accounts, key=lambda item: str(item.get("name") or "")): + detail = get_account_detail(token, account) + credentials = runtime_account_credentials(detail) + runtime_policy = normalize_temp_unschedulable_credentials(credentials) + runtime_codes = sorted(set(rule.get("error_code") for rule in runtime_policy["rules"] if isinstance(rule.get("error_code"), int))) + name = detail.get("name") or account.get("name") + pool_mode = bool_value(credentials.get("pool_mode")) + temp_until = detail.get("temp_unschedulable_until") or detail.get("tempUnschedulableUntil") + temp_reason = detail.get("temp_unschedulable_reason") or detail.get("tempUnschedulableReason") or "" + origin = "yaml-managed" if name in EXPECTED_ACCOUNT_TEMP_UNSCHEDULABLE else ("yaml-protected-manual" if name in protected_names else "runtime-manual") + supports_policy = (detail.get("type") or account.get("type")) == "apikey" + policy_matches = runtime_policy == baseline if supports_policy else None + missing_codes = [code for code in baseline_codes if code not in runtime_codes] if supports_policy else [] + items.append({ + "accountName": name, + "accountId": detail.get("id") or account.get("id"), + "origin": origin, + "platform": detail.get("platform") or account.get("platform"), + "type": detail.get("type") or account.get("type"), + "status": detail.get("status") or account.get("status"), + "schedulable": detail.get("schedulable") if detail.get("schedulable") is not None else account.get("schedulable"), + "proxyId": detail.get("proxy_id") if detail.get("proxy_id") is not None else account.get("proxy_id"), + "priority": detail.get("priority") if detail.get("priority") is not None else account.get("priority"), + "concurrency": detail.get("concurrency") if detail.get("concurrency") is not None else account.get("concurrency"), + "currentConcurrency": account.get("current_concurrency"), + "baseUrl": normalize_runtime_base_url(credentials.get("base_url")), + "tempUnschedulable": { + "enabled": runtime_policy["enabled"], + "ruleCount": len(runtime_policy["rules"]), + "statusCodes": runtime_codes, + "rules": summarize_temp_unschedulable_rules(runtime_policy["rules"]), + "until": temp_until, + "reasonPreview": text(str(temp_reason), 500) if temp_reason else "", + "stateRecorded": temp_until is not None or bool(temp_reason), + "active": temp_unschedulable_active(temp_until), + "matchesYamlBaseline": policy_matches, + "missingYamlBaselineStatusCodes": missing_codes, + }, + "poolMode": { + "enabled": pool_mode, + "retryCountConfigured": normalized_int(credentials.get("pool_mode_retry_count")) if "pool_mode_retry_count" in credentials else None, + "retryStatusCodesConfigured": normalized_status_codes(credentials.get("pool_mode_retry_status_codes")) if "pool_mode_retry_status_codes" in credentials else None, + }, + "optimization": { + "tempUnschedulableNeedsAlignment": supports_policy and runtime_policy != baseline, + "poolModeShouldBeDisabled": pool_mode, + }, + "credentialsPrinted": False, + }) + temp_alignment = [item["accountName"] for item in items if item["optimization"]["tempUnschedulableNeedsAlignment"]] + pool_mode_enabled = [item["accountName"] for item in items if item["poolMode"]["enabled"]] + return { + "ok": True, + "group": { + "name": POOL_GROUP_NAME, + "id": group.get("id"), + "found": True, + "platform": group.get("platform"), + "status": group.get("status"), + }, + "yamlBaseline": { + "enabled": baseline["enabled"], + "ruleCount": len(baseline["rules"]), + "statusCodes": baseline_codes, + }, + "summary": { + "accountCount": len(items), + "tempUnschedulableEnabledCount": sum(1 for item in items if item["tempUnschedulable"]["enabled"]), + "tempUnschedulableNeedsAlignmentCount": len(temp_alignment), + "tempUnschedulableNeedsAlignment": temp_alignment, + "poolModeEnabledCount": len(pool_mode_enabled), + "poolModeEnabled": pool_mode_enabled, + "optimizationAvailable": bool(temp_alignment or pool_mode_enabled), + }, + "accounts": items, + "valuesPrinted": False, + } + def account_capacity_status(token): accounts = list_accounts(token) by_name = {item.get("name"): item for item in accounts if isinstance(item.get("name"), str)} @@ -2776,6 +2905,21 @@ def run_validate(): "validation": {"gatewayModels": gateway, "gatewayResponses": responses_smoke, "gatewayResponsesRecent": responses_evidence, "gatewayCompactRecent": compact_evidence}, } +def run_runtime_state(): + admin_email, token, admin_compliance = login() + state = account_runtime_state(token) + return { + "ok": state.get("ok") is True, + "mode": "runtime-state", + "namespace": NAMESPACE, + "serviceDns": SERVICE_DNS, + "appPod": APP_POD, + "admin": {"email": admin_email, "tokenPrinted": False, "compliance": admin_compliance}, + "runtimeImage": app_pod_runtime_image(), + "state": state, + "valuesPrinted": False, + } + def parse_log_line(line): json_start = line.find("{") if json_start < 0: @@ -3286,6 +3430,8 @@ def run_cleanup_probes(): try: if MODE == "sync": result = run_sync() + elif MODE == "runtime-state": + result = run_runtime_state() elif MODE == "trace": result = run_trace() elif MODE == "cleanup-probes": diff --git a/scripts/src/platform-infra-sub2api-codex/remote-scripts.ts b/scripts/src/platform-infra-sub2api-codex/remote-scripts.ts index 68e3630c..01a24bf7 100644 --- a/scripts/src/platform-infra-sub2api-codex/remote-scripts.ts +++ b/scripts/src/platform-infra-sub2api-codex/remote-scripts.ts @@ -35,6 +35,10 @@ export function validateScript(pool: CodexPoolConfig, target: CodexPoolRuntimeTa return remotePythonScript("validate", "", pool, target); } +export function runtimeStateScript(pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): string { + return remotePythonScript("runtime-state", "", pool, target); +} + export function traceScript(pool: CodexPoolConfig, options: TraceOptions, target: CodexPoolRuntimeTarget): string { const encoded = Buffer.from(JSON.stringify({ requestId: options.requestId, diff --git a/scripts/src/platform-infra-sub2api-codex/remote.ts b/scripts/src/platform-infra-sub2api-codex/remote.ts index cf89d506..c3d2f251 100644 --- a/scripts/src/platform-infra-sub2api-codex/remote.ts +++ b/scripts/src/platform-infra-sub2api-codex/remote.ts @@ -28,7 +28,7 @@ export async function capture(config: UniDeskConfig, target: string, args: strin return await runSshCommandCapture(config, target, args, input); } -export type RemoteCodexPoolMode = "sync" | "validate" | "sentinel-probe" | "sentinel-image-status" | "sentinel-image-build"; +export type RemoteCodexPoolMode = "sync" | "validate" | "runtime-state" | "sentinel-probe" | "sentinel-image-status" | "sentinel-image-build"; export async function runRemoteCodexPoolScript(config: UniDeskConfig, mode: RemoteCodexPoolMode, script: string, target = codexPoolRuntimeTarget()): Promise { const jobName = `codex-pool-${mode}-${Date.now().toString(36)}`.slice(0, 63); @@ -83,6 +83,7 @@ export async function runRemoteCodexPoolScript(config: UniDeskConfig, mode: Remo export function codexPoolModeCommand(mode: RemoteCodexPoolMode): string { if (mode === "sync") return "bun scripts/cli.ts platform-infra sub2api codex-pool sync --confirm"; + if (mode === "runtime-state") return "bun scripts/cli.ts platform-infra sub2api codex-pool runtime-state"; if (mode === "sentinel-probe") return "bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --account --confirm"; if (mode === "sentinel-image-status") return "bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status"; if (mode === "sentinel-image-build") return "bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --confirm"; diff --git a/scripts/src/platform-infra-sub2api-codex/types.ts b/scripts/src/platform-infra-sub2api-codex/types.ts index 9f401e03..c1b3aaa3 100644 --- a/scripts/src/platform-infra-sub2api-codex/types.ts +++ b/scripts/src/platform-infra-sub2api-codex/types.ts @@ -325,13 +325,14 @@ export function codexPoolHelp(): unknown { const pool = readCodexPoolConfig(); const runtimeTarget = codexPoolRuntimeTarget(); return { - command: "platform-infra sub2api codex-pool plan|sync|validate|trace|sentinel-image|sentinel-probe|sentinel-report|cleanup-probes|expose|configure-local", + command: "platform-infra sub2api codex-pool plan|sync|validate|runtime-state|trace|sentinel-image|sentinel-probe|sentinel-report|cleanup-probes|expose|configure-local", output: "json, except trace and sentinel-report default to low-noise text tables", usage: [ "bun scripts/cli.ts platform-infra sub2api codex-pool plan", "bun scripts/cli.ts platform-infra sub2api codex-pool plan --target D601", "bun scripts/cli.ts platform-infra sub2api codex-pool sync [--target D601] --confirm [--prune-removed]", "bun scripts/cli.ts platform-infra sub2api codex-pool validate [--target D601] [--full|--raw]", + "bun scripts/cli.ts platform-infra sub2api codex-pool runtime-state [--target PK01] [--full|--raw]", "bun scripts/cli.ts platform-infra sub2api codex-pool trace [--target D601] --request-id [--since 24h|--tail 20000|--context-seconds 300|--show-lines|--raw]", "bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status [--target D601]", "bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build [--target D601] --confirm",