Files
openclaw/src/commands/status.scan.bootstrap-shared.ts
T
snowzlmbot 17c2ce05d8 fix(secrets): keep startup alive when TTS SecretRefs are missing (#101265)
* fix(secrets): degrade missing TTS SecretRefs at startup

* test(secrets): keep non-activating startup strict

* test(secrets): mark denied key fixture synthetic

* test(secrets): use synthetic TTS key fixture

* test(secrets): use neutral TTS key placeholder

* test(secrets): isolate TTS key placeholder

* test(secrets): shorten TTS ref fixture name

* test(secrets): normalize synthetic credential fixtures

* test(secrets): isolate optional redaction coverage

* fix(secrets): preserve degraded TTS ref ownership

* refactor(secrets): keep optional resolver internal

* test(secrets): cover default provider alias misses

* test(secrets): pin explicit provider ownership

* style(secrets): format optional assignment imports

* refactor(secrets): keep optional metadata private

* style(secrets): restore collector file header

* fix(secrets): isolate unavailable SecretRef owners

Co-authored-by: snowzlmbot <293528334+snowzlmbot@users.noreply.github.com>

* test(secrets): complete provider fixtures

* style(status): avoid degraded path shadowing

* style(secrets): satisfy runtime lint

* refactor(secrets): keep error codes internal

* fix(secrets): keep unowned assignments fail closed

* fix(secrets): preserve provider resolution batching

* fix(secrets): normalize stalled resolution errors

* fix(secrets): reject provider limit violations

---------

Co-authored-by: snowzlmbot <293528334+snowzlmbot@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-16 21:07:01 -07:00

148 lines
5.1 KiB
TypeScript

// Shared bootstrap for status scans.
// Starts update, Tailscale, agent, and gateway probes with cold-start shortcuts for first-run users.
import type { OpenClawConfig } from "../config/types.js";
import type { UpdateCheckResult } from "../infra/update-check.js";
import { runExec } from "../process/exec.js";
import { createEmptyTaskAuditSummary } from "../tasks/task-registry.audit.shared.js";
import { createEmptyTaskRegistrySummary } from "../tasks/task-registry.summary.js";
import { buildTailscaleHttpsUrl, resolveGatewayProbeSnapshot } from "./status.scan.shared.js";
function buildColdStartUpdateResult(): UpdateCheckResult {
return {
root: null,
installKind: "unknown",
packageManager: "unknown",
};
}
function buildColdStartAgentLocalStatuses() {
return {
defaultId: "main",
agents: [],
totalSessions: 0,
bootstrapPendingCount: 0,
};
}
/** Builds an empty summary for cold-start status paths that skip network and session work. */
export function buildColdStartStatusSummary() {
return {
runtimeVersion: null,
heartbeat: {
defaultAgentId: "main",
agents: [],
},
channelSummary: [],
queuedSystemEvents: [],
degradedSecretOwners: [],
tasks: createEmptyTaskRegistrySummary(),
taskAudit: createEmptyTaskAuditSummary(),
sessions: {
paths: [],
count: 0,
defaults: { model: null, contextTokens: null },
recent: [],
byAgent: [],
},
};
}
function shouldSkipStatusScanNetworkChecks(params: {
coldStart: boolean;
hasConfiguredChannels: boolean;
all?: boolean;
}): boolean {
// First-run users without channels should get instant status instead of waiting on network probes.
return params.coldStart && !params.hasConfiguredChannels && params.all !== true;
}
type StatusScanExecRunner = (
command: string,
args: string[],
opts?: number | { timeoutMs?: number; maxBuffer?: number; cwd?: string },
) => Promise<{ stdout: string; stderr: string }>;
type StatusScanCoreBootstrapParams<TAgentStatus> = {
coldStart: boolean;
cfg: OpenClawConfig;
hasConfiguredChannels: boolean;
opts: { timeoutMs?: number; all?: boolean };
skipUpdateCheck?: boolean;
fetchGitUpdate?: boolean;
includeRegistryUpdate?: boolean;
includeLocalStatusRpcFallback?: boolean;
gatewayProbeTimeoutMs?: number;
getTailnetHostname: (runner: StatusScanExecRunner) => Promise<string | null>;
getUpdateCheckResult: (params: {
timeoutMs: number;
fetchGit: boolean;
includeRegistry: boolean;
updateConfigChannel?: string | null;
}) => Promise<UpdateCheckResult>;
getAgentLocalStatuses: (cfg: OpenClawConfig) => Promise<TAgentStatus>;
};
/** Starts the common async probes used by status scans and exposes their promises to callers. */
export async function createStatusScanCoreBootstrap<TAgentStatus>(
params: StatusScanCoreBootstrapParams<TAgentStatus>,
) {
const tailscaleMode = params.cfg.gateway?.tailscale?.mode ?? "off";
const skipColdStartNetworkChecks = shouldSkipStatusScanNetworkChecks({
coldStart: params.coldStart,
hasConfiguredChannels: params.hasConfiguredChannels,
all: params.opts.all,
});
const statusTimeoutMs = params.opts.timeoutMs ?? 10_000;
const updateTimeoutMs = Math.min(params.opts.all ? 6500 : 2500, statusTimeoutMs);
const tailscaleTimeoutMs = Math.min(1200, statusTimeoutMs);
const tailscaleDnsPromise =
tailscaleMode === "off"
? Promise.resolve<string | null>(null)
: params
.getTailnetHostname((cmd, args) =>
runExec(cmd, args, { timeoutMs: tailscaleTimeoutMs, maxBuffer: 200_000 }),
)
.catch(() => null);
const skipNetworkUpdate = skipColdStartNetworkChecks || params.skipUpdateCheck === true;
// Update checks can hit git/registry, so cold-start status uses a synthetic unknown result.
const updatePromise = skipNetworkUpdate
? Promise.resolve(buildColdStartUpdateResult())
: params.getUpdateCheckResult({
timeoutMs: updateTimeoutMs,
fetchGit: params.fetchGitUpdate ?? true,
includeRegistry: params.includeRegistryUpdate ?? true,
updateConfigChannel: params.cfg.update?.channel ?? null,
});
const agentStatusPromise = skipColdStartNetworkChecks
? Promise.resolve(buildColdStartAgentLocalStatuses() as TAgentStatus)
: params.getAgentLocalStatuses(params.cfg);
const gatewayProbePromise = resolveGatewayProbeSnapshot({
cfg: params.cfg,
opts: {
...params.opts,
...(params.gatewayProbeTimeoutMs !== undefined
? { timeoutMs: params.gatewayProbeTimeoutMs }
: {}),
...(skipColdStartNetworkChecks ? { skipProbe: true } : {}),
localStatusRpcFallback: params.includeLocalStatusRpcFallback !== false,
},
});
return {
tailscaleMode,
tailscaleDnsPromise,
updatePromise,
agentStatusPromise,
gatewayProbePromise,
skipColdStartNetworkChecks,
resolveTailscaleHttpsUrl: async () =>
buildTailscaleHttpsUrl({
tailscaleMode,
tailscaleDns: await tailscaleDnsPromise,
serviceName: params.cfg.gateway?.tailscale?.serviceName,
controlUiBasePath: params.cfg.gateway?.controlUi?.basePath,
}),
};
}