perf: slim hot test imports

This commit is contained in:
Peter Steinberger
2026-04-24 08:13:45 +01:00
parent 50e36983bb
commit 4e4aeacae4
15 changed files with 264 additions and 172 deletions

View File

@@ -1,9 +1,9 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { resolveAuthProfileOrder } from "./auth-profiles.js";
import { import {
ANTHROPIC_CFG, ANTHROPIC_CFG,
ANTHROPIC_STORE, ANTHROPIC_STORE,
} from "./auth-profiles.resolve-auth-profile-order.fixtures.js"; } from "./auth-profiles.resolve-auth-profile-order.fixtures.js";
import { resolveAuthProfileOrder } from "./auth-profiles/order.js";
import type { AuthProfileStore } from "./auth-profiles/types.js"; import type { AuthProfileStore } from "./auth-profiles/types.js";
describe("resolveAuthProfileOrder", () => { describe("resolveAuthProfileOrder", () => {

View File

@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { type AuthProfileStore, resolveAuthProfileOrder } from "./auth-profiles.js"; import { resolveAuthProfileOrder } from "./auth-profiles/order.js";
import type { AuthProfileStore } from "./auth-profiles/types.js";
function makeApiKeyStore(provider: string, profileIds: string[]): AuthProfileStore { function makeApiKeyStore(provider: string, profileIds: string[]): AuthProfileStore {
return { return {

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { resolveAuthProfileOrder } from "./auth-profiles.js"; import { resolveAuthProfileOrder } from "./auth-profiles/order.js";
describe("resolveAuthProfileOrder", () => { describe("resolveAuthProfileOrder", () => {
it("orders by lastUsed when no explicit order exists", () => { it("orders by lastUsed when no explicit order exists", () => {

View File

@@ -1,9 +1,9 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { resolveAuthProfileOrder } from "./auth-profiles.js";
import { import {
ANTHROPIC_CFG, ANTHROPIC_CFG,
ANTHROPIC_STORE, ANTHROPIC_STORE,
} from "./auth-profiles.resolve-auth-profile-order.fixtures.js"; } from "./auth-profiles.resolve-auth-profile-order.fixtures.js";
import { resolveAuthProfileOrder } from "./auth-profiles/order.js";
describe("resolveAuthProfileOrder", () => { describe("resolveAuthProfileOrder", () => {
const store = ANTHROPIC_STORE; const store = ANTHROPIC_STORE;

View File

@@ -1,17 +1,17 @@
import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { findNormalizedProviderValue, normalizeProviderId } from "../model-selection.js";
import { resolveProviderIdForAuth } from "../provider-auth-aliases.js"; import { resolveProviderIdForAuth } from "../provider-auth-aliases.js";
import { findNormalizedProviderValue, normalizeProviderId } from "../provider-id.js";
import { import {
evaluateStoredCredentialEligibility, evaluateStoredCredentialEligibility,
type AuthCredentialReasonCode, type AuthCredentialReasonCode,
} from "./credential-state.js"; } from "./credential-state.js";
import { dedupeProfileIds, listProfilesForProvider } from "./profiles.js"; import { dedupeProfileIds, listProfilesForProvider } from "./profile-list.js";
import type { AuthProfileStore } from "./types.js"; import type { AuthProfileStore } from "./types.js";
import { import {
clearExpiredCooldowns, clearExpiredCooldowns,
isProfileInCooldown, isProfileInCooldown,
resolveProfileUnusableUntil, resolveProfileUnusableUntil,
} from "./usage.js"; } from "./usage-state.js";
export type AuthProfileEligibilityReasonCode = export type AuthProfileEligibilityReasonCode =
| AuthCredentialReasonCode | AuthCredentialReasonCode

View File

@@ -0,0 +1,13 @@
import { resolveProviderIdForAuth } from "../provider-auth-aliases.js";
import type { AuthProfileStore } from "./types.js";
export function dedupeProfileIds(profileIds: string[]): string[] {
return [...new Set(profileIds)];
}
export function listProfilesForProvider(store: AuthProfileStore, provider: string): string[] {
const providerKey = resolveProviderIdForAuth(provider);
return Object.entries(store.profiles)
.filter(([, cred]) => resolveProviderIdForAuth(cred.provider) === providerKey)
.map(([id]) => id);
}

View File

@@ -2,16 +2,14 @@ import { normalizeStringEntries } from "../../shared/string-normalization.js";
import { normalizeSecretInput } from "../../utils/normalize-secret-input.js"; import { normalizeSecretInput } from "../../utils/normalize-secret-input.js";
import { resolveProviderIdForAuth } from "../provider-auth-aliases.js"; import { resolveProviderIdForAuth } from "../provider-auth-aliases.js";
import { normalizeProviderId } from "../provider-id.js"; import { normalizeProviderId } from "../provider-id.js";
import { dedupeProfileIds, listProfilesForProvider } from "./profile-list.js";
import { import {
ensureAuthProfileStoreForLocalUpdate, ensureAuthProfileStoreForLocalUpdate,
saveAuthProfileStore, saveAuthProfileStore,
updateAuthProfileStoreWithLock, updateAuthProfileStoreWithLock,
} from "./store.js"; } from "./store.js";
import type { AuthProfileCredential, AuthProfileStore } from "./types.js"; import type { AuthProfileCredential, AuthProfileStore } from "./types.js";
export { dedupeProfileIds, listProfilesForProvider } from "./profile-list.js";
export function dedupeProfileIds(profileIds: string[]): string[] {
return [...new Set(profileIds)];
}
export async function setAuthProfileOrder(params: { export async function setAuthProfileOrder(params: {
agentDir?: string; agentDir?: string;
@@ -124,13 +122,6 @@ export async function removeProviderAuthProfilesWithLock(params: {
}); });
} }
export function listProfilesForProvider(store: AuthProfileStore, provider: string): string[] {
const providerKey = resolveProviderIdForAuth(provider);
return Object.entries(store.profiles)
.filter(([, cred]) => resolveProviderIdForAuth(cred.provider) === providerKey)
.map(([id]) => id);
}
export async function markAuthProfileGood(params: { export async function markAuthProfileGood(params: {
store: AuthProfileStore; store: AuthProfileStore;
provider: string; provider: string;

View File

@@ -2,7 +2,7 @@ import type { AuthProfileConfig } from "../../config/types.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { findNormalizedProviderKey, normalizeProviderId } from "../provider-id.js"; import { findNormalizedProviderKey, normalizeProviderId } from "../provider-id.js";
import { resolveAuthProfileMetadata } from "./identity.js"; import { resolveAuthProfileMetadata } from "./identity.js";
import { dedupeProfileIds, listProfilesForProvider } from "./profiles.js"; import { dedupeProfileIds, listProfilesForProvider } from "./profile-list.js";
import type { AuthProfileIdRepairResult, AuthProfileStore } from "./types.js"; import type { AuthProfileIdRepairResult, AuthProfileStore } from "./types.js";
function getProfileSuffix(profileId: string): string { function getProfileSuffix(profileId: string): string {

View File

@@ -0,0 +1,138 @@
import { normalizeProviderId } from "../provider-id.js";
import type { AuthProfileStore, ProfileUsageStats } from "./types.js";
export function isAuthCooldownBypassedForProvider(provider: string | undefined): boolean {
const normalized = normalizeProviderId(provider ?? "");
return normalized === "openrouter" || normalized === "kilocode";
}
export function resolveProfileUnusableUntil(
stats: Pick<ProfileUsageStats, "cooldownUntil" | "disabledUntil">,
): number | null {
const values = [stats.cooldownUntil, stats.disabledUntil]
.filter((value): value is number => typeof value === "number")
.filter((value) => Number.isFinite(value) && value > 0);
if (values.length === 0) {
return null;
}
return Math.max(...values);
}
export function isActiveUnusableWindow(until: number | undefined, now: number): boolean {
return typeof until === "number" && Number.isFinite(until) && until > 0 && now < until;
}
export function shouldBypassModelScopedCooldown(
stats: Pick<ProfileUsageStats, "cooldownReason" | "cooldownModel" | "disabledUntil">,
now: number,
forModel?: string,
): boolean {
return !!(
forModel &&
stats.cooldownReason === "rate_limit" &&
stats.cooldownModel &&
stats.cooldownModel !== forModel &&
!isActiveUnusableWindow(stats.disabledUntil, now)
);
}
/**
* Check if a profile is currently in cooldown (due to rate limits, overload, or other transient failures).
*/
export function isProfileInCooldown(
store: AuthProfileStore,
profileId: string,
now?: number,
forModel?: string,
): boolean {
if (isAuthCooldownBypassedForProvider(store.profiles[profileId]?.provider)) {
return false;
}
const stats = store.usageStats?.[profileId];
if (!stats) {
return false;
}
const ts = now ?? Date.now();
// Model-aware bypass: if the cooldown was caused by a rate_limit on a
// specific model and the caller is requesting a *different* model, allow it.
// We still honour any active billing/auth disable (`disabledUntil`) — those
// are profile-wide and must not be short-circuited by model scoping.
if (shouldBypassModelScopedCooldown(stats, ts, forModel)) {
return false;
}
const unusableUntil = resolveProfileUnusableUntil(stats);
return unusableUntil ? ts < unusableUntil : false;
}
/**
* Clear expired cooldowns from all profiles in the store.
*
* When `cooldownUntil` or `disabledUntil` has passed, the corresponding fields
* are removed and error counters are reset so the profile gets a fresh start
* (circuit-breaker half-open -> closed). Without this, a stale `errorCount`
* causes the *next* transient failure to immediately escalate to a much longer
* cooldown -- the root cause of profiles appearing "stuck" after rate limits.
*
* `cooldownUntil` and `disabledUntil` are handled independently: if a profile
* has both and only one has expired, only that field is cleared.
*
* Mutates the in-memory store; disk persistence happens lazily on the next
* store write (e.g. `markAuthProfileUsed` / `markAuthProfileFailure`), which
* matches the existing save pattern throughout the auth-profiles module.
*
* @returns `true` if any profile was modified.
*/
export function clearExpiredCooldowns(store: AuthProfileStore, now?: number): boolean {
const usageStats = store.usageStats;
if (!usageStats) {
return false;
}
const ts = now ?? Date.now();
let mutated = false;
for (const [profileId, stats] of Object.entries(usageStats)) {
if (!stats) {
continue;
}
let profileMutated = false;
const cooldownExpired =
typeof stats.cooldownUntil === "number" &&
Number.isFinite(stats.cooldownUntil) &&
stats.cooldownUntil > 0 &&
ts >= stats.cooldownUntil;
const disabledExpired =
typeof stats.disabledUntil === "number" &&
Number.isFinite(stats.disabledUntil) &&
stats.disabledUntil > 0 &&
ts >= stats.disabledUntil;
if (cooldownExpired) {
stats.cooldownUntil = undefined;
stats.cooldownReason = undefined;
stats.cooldownModel = undefined;
profileMutated = true;
}
if (disabledExpired) {
stats.disabledUntil = undefined;
stats.disabledReason = undefined;
profileMutated = true;
}
// Reset error counters when ALL cooldowns have expired so the profile gets
// a fair retry window. Preserves lastFailureAt for the failureWindowMs
// decay check in computeNextProfileUsageStats.
if (profileMutated && !resolveProfileUnusableUntil(stats)) {
stats.errorCount = 0;
stats.failureCounts = undefined;
}
if (profileMutated) {
usageStats[profileId] = stats;
mutated = true;
}
}
return mutated;
}

View File

@@ -1,8 +1,21 @@
import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { normalizeProviderId } from "../model-selection.js"; import { normalizeProviderId } from "../provider-id.js";
import { logAuthProfileFailureStateChange } from "./state-observation.js"; import { logAuthProfileFailureStateChange } from "./state-observation.js";
import { saveAuthProfileStore, updateAuthProfileStoreWithLock } from "./store.js"; import { saveAuthProfileStore, updateAuthProfileStoreWithLock } from "./store.js";
import type { AuthProfileFailureReason, AuthProfileStore, ProfileUsageStats } from "./types.js"; import type { AuthProfileFailureReason, AuthProfileStore, ProfileUsageStats } from "./types.js";
import {
clearExpiredCooldowns,
isActiveUnusableWindow,
isAuthCooldownBypassedForProvider,
isProfileInCooldown,
resolveProfileUnusableUntil,
shouldBypassModelScopedCooldown,
} from "./usage-state.js";
export {
clearExpiredCooldowns,
isProfileInCooldown,
resolveProfileUnusableUntil,
} from "./usage-state.js";
const authProfileUsageDeps = { const authProfileUsageDeps = {
saveAuthProfileStore, saveAuthProfileStore,
@@ -70,11 +83,6 @@ type WhamCooldownProbeResult = {
reason: string; reason: string;
}; };
function isAuthCooldownBypassedForProvider(provider: string | undefined): boolean {
const normalized = normalizeProviderId(provider ?? "");
return normalized === "openrouter" || normalized === "kilocode";
}
function shouldProbeWhamForFailure( function shouldProbeWhamForFailure(
provider: string | undefined, provider: string | undefined,
reason: AuthProfileFailureReason, reason: AuthProfileFailureReason,
@@ -222,50 +230,6 @@ export async function probeWhamForCooldown(
} }
} }
export function resolveProfileUnusableUntil(
stats: Pick<ProfileUsageStats, "cooldownUntil" | "disabledUntil">,
): number | null {
const values = [stats.cooldownUntil, stats.disabledUntil]
.filter((value): value is number => typeof value === "number")
.filter((value) => Number.isFinite(value) && value > 0);
if (values.length === 0) {
return null;
}
return Math.max(...values);
}
/**
* Check if a profile is currently in cooldown (due to rate limits, overload, or other transient failures).
*/
export function isProfileInCooldown(
store: AuthProfileStore,
profileId: string,
now?: number,
forModel?: string,
): boolean {
if (isAuthCooldownBypassedForProvider(store.profiles[profileId]?.provider)) {
return false;
}
const stats = store.usageStats?.[profileId];
if (!stats) {
return false;
}
const ts = now ?? Date.now();
// Model-aware bypass: if the cooldown was caused by a rate_limit on a
// specific model and the caller is requesting a *different* model, allow it.
// We still honour any active billing/auth disable (`disabledUntil`) — those
// are profile-wide and must not be short-circuited by model scoping.
if (shouldBypassModelScopedCooldown(stats, ts, forModel)) {
return false;
}
const unusableUntil = resolveProfileUnusableUntil(stats);
return unusableUntil ? ts < unusableUntil : false;
}
function isActiveUnusableWindow(until: number | undefined, now: number): boolean {
return typeof until === "number" && Number.isFinite(until) && until > 0 && now < until;
}
/** /**
* Infer the most likely reason all candidate profiles are currently unavailable. * Infer the most likely reason all candidate profiles are currently unavailable.
* *
@@ -393,93 +357,6 @@ export function getSoonestCooldownExpiry(
return Math.min(soonest, latestMatchingModelCooldown); return Math.min(soonest, latestMatchingModelCooldown);
} }
function shouldBypassModelScopedCooldown(
stats: Pick<ProfileUsageStats, "cooldownReason" | "cooldownModel" | "disabledUntil">,
now: number,
forModel?: string,
): boolean {
return !!(
forModel &&
stats.cooldownReason === "rate_limit" &&
stats.cooldownModel &&
stats.cooldownModel !== forModel &&
!isActiveUnusableWindow(stats.disabledUntil, now)
);
}
/**
* Clear expired cooldowns from all profiles in the store.
*
* When `cooldownUntil` or `disabledUntil` has passed, the corresponding fields
* are removed and error counters are reset so the profile gets a fresh start
* (circuit-breaker half-open → closed). Without this, a stale `errorCount`
* causes the *next* transient failure to immediately escalate to a much longer
* cooldown — the root cause of profiles appearing "stuck" after rate limits.
*
* `cooldownUntil` and `disabledUntil` are handled independently: if a profile
* has both and only one has expired, only that field is cleared.
*
* Mutates the in-memory store; disk persistence happens lazily on the next
* store write (e.g. `markAuthProfileUsed` / `markAuthProfileFailure`), which
* matches the existing save pattern throughout the auth-profiles module.
*
* @returns `true` if any profile was modified.
*/
export function clearExpiredCooldowns(store: AuthProfileStore, now?: number): boolean {
const usageStats = store.usageStats;
if (!usageStats) {
return false;
}
const ts = now ?? Date.now();
let mutated = false;
for (const [profileId, stats] of Object.entries(usageStats)) {
if (!stats) {
continue;
}
let profileMutated = false;
const cooldownExpired =
typeof stats.cooldownUntil === "number" &&
Number.isFinite(stats.cooldownUntil) &&
stats.cooldownUntil > 0 &&
ts >= stats.cooldownUntil;
const disabledExpired =
typeof stats.disabledUntil === "number" &&
Number.isFinite(stats.disabledUntil) &&
stats.disabledUntil > 0 &&
ts >= stats.disabledUntil;
if (cooldownExpired) {
stats.cooldownUntil = undefined;
stats.cooldownReason = undefined;
stats.cooldownModel = undefined;
profileMutated = true;
}
if (disabledExpired) {
stats.disabledUntil = undefined;
stats.disabledReason = undefined;
profileMutated = true;
}
// Reset error counters when ALL cooldowns have expired so the profile gets
// a fair retry window. Preserves lastFailureAt for the failureWindowMs
// decay check in computeNextProfileUsageStats.
if (profileMutated && !resolveProfileUnusableUntil(stats)) {
stats.errorCount = 0;
stats.failureCounts = undefined;
}
if (profileMutated) {
usageStats[profileId] = stats;
mutated = true;
}
}
return mutated;
}
/** /**
* Mark a profile as successfully used. Resets error count and updates lastUsed. * Mark a profile as successfully used. Resets error count and updates lastUsed.
* Uses store lock to avoid overwriting concurrent usage updates. * Uses store lock to avoid overwriting concurrent usage updates.

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js";
import { getDmHistoryLimitFromSessionKey } from "./pi-embedded-runner.js"; import { getDmHistoryLimitFromSessionKey } from "./pi-embedded-runner/history.js";
describe("getDmHistoryLimitFromSessionKey", () => { describe("getDmHistoryLimitFromSessionKey", () => {
it("falls back to provider default when per-DM not set", () => { it("falls back to provider default when per-DM not set", () => {

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js";
import { getDmHistoryLimitFromSessionKey } from "./pi-embedded-runner.js"; import { getDmHistoryLimitFromSessionKey } from "./pi-embedded-runner/history.js";
describe("getDmHistoryLimitFromSessionKey", () => { describe("getDmHistoryLimitFromSessionKey", () => {
it("returns undefined when sessionKey is undefined", () => { it("returns undefined when sessionKey is undefined", () => {

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js";
import { getDmHistoryLimitFromSessionKey } from "./pi-embedded-runner.js"; import { getDmHistoryLimitFromSessionKey } from "./pi-embedded-runner/history.js";
describe("getDmHistoryLimitFromSessionKey", () => { describe("getDmHistoryLimitFromSessionKey", () => {
it("keeps backward compatibility for dm/direct session kinds", () => { it("keeps backward compatibility for dm/direct session kinds", () => {

View File

@@ -1,6 +1,6 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { AgentMessage } from "@mariozechner/pi-agent-core";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { limitHistoryTurns } from "./pi-embedded-runner.js"; import { limitHistoryTurns } from "./pi-embedded-runner/history.js";
describe("limitHistoryTurns", () => { describe("limitHistoryTurns", () => {
const mockUsage = { const mockUsage = {

View File

@@ -3,19 +3,94 @@ import { countPendingDescendantRunsFromRuns } from "../../../agents/subagent-reg
import { getSubagentRunsSnapshotForRead } from "../../../agents/subagent-registry-state.js"; import { getSubagentRunsSnapshotForRead } from "../../../agents/subagent-registry-state.js";
import { resolveStorePath } from "../../../config/sessions/paths.js"; import { resolveStorePath } from "../../../config/sessions/paths.js";
import { loadSessionStore } from "../../../config/sessions/store-load.js"; import { loadSessionStore } from "../../../config/sessions/store-load.js";
import { formatTimeAgo } from "../../../infra/format-time/format-relative.ts";
import { parseAgentSessionKey } from "../../../routing/session-key.js";
import { formatDurationCompact } from "../../../shared/subagents-format.js"; import { formatDurationCompact } from "../../../shared/subagents-format.js";
import { findTaskByRunIdForOwner } from "../../../tasks/task-owner-access.js"; import { findTaskByRunIdForOwner } from "../../../tasks/task-owner-access.js";
import { sanitizeTaskStatusText } from "../../../tasks/task-status.js"; import { sanitizeTaskStatusText } from "../../../tasks/task-status.js";
import type { CommandHandlerResult } from "../commands-types.js"; import type { CommandHandlerResult } from "../commands-types.js";
import { formatRunLabel } from "../subagents-utils.js";
import { import {
type SubagentsCommandContext, formatRunLabel,
formatTimestampWithAge, formatRunStatus,
loadSubagentSessionEntry, resolveSubagentTargetFromRuns,
resolveDisplayStatus, } from "../subagents-utils.js";
resolveSubagentEntryForToken, import { type SubagentsCommandContext } from "./shared.js";
stopWithText,
} from "./shared.js"; const RECENT_WINDOW_MINUTES = 30;
function stopWithText(text: string): CommandHandlerResult {
return { shouldContinue: false, reply: { text } };
}
function formatTimestamp(valueMs?: number) {
if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) {
return "n/a";
}
return new Date(valueMs).toISOString();
}
function formatTimestampWithAge(valueMs?: number) {
if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) {
return "n/a";
}
return `${formatTimestamp(valueMs)} (${formatTimeAgo(Date.now() - valueMs, { fallback: "n/a" })})`;
}
function resolveDisplayStatus(
entry: SubagentsCommandContext["runs"][number],
options?: { pendingDescendants?: number },
) {
const pendingDescendants = Math.max(0, options?.pendingDescendants ?? 0);
if (pendingDescendants > 0) {
const childLabel = pendingDescendants === 1 ? "child" : "children";
return `active (waiting on ${pendingDescendants} ${childLabel})`;
}
const status = formatRunStatus(entry);
return status === "error" ? "failed" : status;
}
function resolveSubagentEntryForToken(
runs: SubagentsCommandContext["runs"],
token: string | undefined,
): { entry: SubagentsCommandContext["runs"][number] } | { reply: CommandHandlerResult } {
const resolved = resolveSubagentTargetFromRuns({
runs,
token,
recentWindowMinutes: RECENT_WINDOW_MINUTES,
label: (entry) => formatRunLabel(entry),
isActive: (entry) =>
!entry.endedAt ||
Math.max(
0,
countPendingDescendantRunsFromRuns(
getSubagentRunsSnapshotForRead(subagentRuns),
entry.childSessionKey,
),
) > 0,
errors: {
missingTarget: "Missing subagent id.",
invalidIndex: (value) => `Invalid subagent index: ${value}`,
unknownSession: (value) => `Unknown subagent session: ${value}`,
ambiguousLabel: (value) => `Ambiguous subagent label: ${value}`,
ambiguousLabelPrefix: (value) => `Ambiguous subagent label prefix: ${value}`,
ambiguousRunIdPrefix: (value) => `Ambiguous run id prefix: ${value}`,
unknownTarget: (value) => `Unknown subagent id: ${value}`,
},
});
if (!resolved.entry) {
return { reply: stopWithText(`⚠️ ${resolved.error ?? "Unknown subagent."}`) };
}
return { entry: resolved.entry };
}
function loadSubagentSessionEntry(params: SubagentsCommandContext["params"], childKey: string) {
const parsed = parseAgentSessionKey(childKey);
const storePath = resolveStorePath(params.cfg.session?.store, {
agentId: parsed?.agentId,
});
const store = loadSessionStore(storePath);
return { entry: store[childKey] };
}
export function handleSubagentsInfoAction(ctx: SubagentsCommandContext): CommandHandlerResult { export function handleSubagentsInfoAction(ctx: SubagentsCommandContext): CommandHandlerResult {
const { params, requesterKey, runs, restTokens } = ctx; const { params, requesterKey, runs, restTokens } = ctx;
@@ -30,10 +105,7 @@ export function handleSubagentsInfoAction(ctx: SubagentsCommandContext): Command
} }
const run = targetResolution.entry; const run = targetResolution.entry;
const { entry: sessionEntry } = loadSubagentSessionEntry(params, run.childSessionKey, { const { entry: sessionEntry } = loadSubagentSessionEntry(params, run.childSessionKey);
loadSessionStore,
resolveStorePath,
});
const runtime = const runtime =
run.startedAt && Number.isFinite(run.startedAt) run.startedAt && Number.isFinite(run.startedAt)
? (formatDurationCompact((run.endedAt ?? Date.now()) - run.startedAt) ?? "n/a") ? (formatDurationCompact((run.endedAt ?? Date.now()) - run.startedAt) ?? "n/a")