mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:30:44 +00:00
perf: slim hot test imports
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveAuthProfileOrder } from "./auth-profiles.js";
|
||||
import {
|
||||
ANTHROPIC_CFG,
|
||||
ANTHROPIC_STORE,
|
||||
} from "./auth-profiles.resolve-auth-profile-order.fixtures.js";
|
||||
import { resolveAuthProfileOrder } from "./auth-profiles/order.js";
|
||||
import type { AuthProfileStore } from "./auth-profiles/types.js";
|
||||
|
||||
describe("resolveAuthProfileOrder", () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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 {
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveAuthProfileOrder } from "./auth-profiles.js";
|
||||
import { resolveAuthProfileOrder } from "./auth-profiles/order.js";
|
||||
|
||||
describe("resolveAuthProfileOrder", () => {
|
||||
it("orders by lastUsed when no explicit order exists", () => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveAuthProfileOrder } from "./auth-profiles.js";
|
||||
import {
|
||||
ANTHROPIC_CFG,
|
||||
ANTHROPIC_STORE,
|
||||
} from "./auth-profiles.resolve-auth-profile-order.fixtures.js";
|
||||
import { resolveAuthProfileOrder } from "./auth-profiles/order.js";
|
||||
|
||||
describe("resolveAuthProfileOrder", () => {
|
||||
const store = ANTHROPIC_STORE;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { findNormalizedProviderValue, normalizeProviderId } from "../model-selection.js";
|
||||
import { resolveProviderIdForAuth } from "../provider-auth-aliases.js";
|
||||
import { findNormalizedProviderValue, normalizeProviderId } from "../provider-id.js";
|
||||
import {
|
||||
evaluateStoredCredentialEligibility,
|
||||
type AuthCredentialReasonCode,
|
||||
} from "./credential-state.js";
|
||||
import { dedupeProfileIds, listProfilesForProvider } from "./profiles.js";
|
||||
import { dedupeProfileIds, listProfilesForProvider } from "./profile-list.js";
|
||||
import type { AuthProfileStore } from "./types.js";
|
||||
import {
|
||||
clearExpiredCooldowns,
|
||||
isProfileInCooldown,
|
||||
resolveProfileUnusableUntil,
|
||||
} from "./usage.js";
|
||||
} from "./usage-state.js";
|
||||
|
||||
export type AuthProfileEligibilityReasonCode =
|
||||
| AuthCredentialReasonCode
|
||||
|
||||
13
src/agents/auth-profiles/profile-list.ts
Normal file
13
src/agents/auth-profiles/profile-list.ts
Normal 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);
|
||||
}
|
||||
@@ -2,16 +2,14 @@ import { normalizeStringEntries } from "../../shared/string-normalization.js";
|
||||
import { normalizeSecretInput } from "../../utils/normalize-secret-input.js";
|
||||
import { resolveProviderIdForAuth } from "../provider-auth-aliases.js";
|
||||
import { normalizeProviderId } from "../provider-id.js";
|
||||
import { dedupeProfileIds, listProfilesForProvider } from "./profile-list.js";
|
||||
import {
|
||||
ensureAuthProfileStoreForLocalUpdate,
|
||||
saveAuthProfileStore,
|
||||
updateAuthProfileStoreWithLock,
|
||||
} from "./store.js";
|
||||
import type { AuthProfileCredential, AuthProfileStore } from "./types.js";
|
||||
|
||||
export function dedupeProfileIds(profileIds: string[]): string[] {
|
||||
return [...new Set(profileIds)];
|
||||
}
|
||||
export { dedupeProfileIds, listProfilesForProvider } from "./profile-list.js";
|
||||
|
||||
export async function setAuthProfileOrder(params: {
|
||||
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: {
|
||||
store: AuthProfileStore;
|
||||
provider: string;
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { AuthProfileConfig } from "../../config/types.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { findNormalizedProviderKey, normalizeProviderId } from "../provider-id.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";
|
||||
|
||||
function getProfileSuffix(profileId: string): string {
|
||||
|
||||
138
src/agents/auth-profiles/usage-state.ts
Normal file
138
src/agents/auth-profiles/usage-state.ts
Normal 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;
|
||||
}
|
||||
@@ -1,8 +1,21 @@
|
||||
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 { saveAuthProfileStore, updateAuthProfileStoreWithLock } from "./store.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 = {
|
||||
saveAuthProfileStore,
|
||||
@@ -70,11 +83,6 @@ type WhamCooldownProbeResult = {
|
||||
reason: string;
|
||||
};
|
||||
|
||||
function isAuthCooldownBypassedForProvider(provider: string | undefined): boolean {
|
||||
const normalized = normalizeProviderId(provider ?? "");
|
||||
return normalized === "openrouter" || normalized === "kilocode";
|
||||
}
|
||||
|
||||
function shouldProbeWhamForFailure(
|
||||
provider: string | undefined,
|
||||
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.
|
||||
*
|
||||
@@ -393,93 +357,6 @@ export function getSoonestCooldownExpiry(
|
||||
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.
|
||||
* Uses store lock to avoid overwriting concurrent usage updates.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { getDmHistoryLimitFromSessionKey } from "./pi-embedded-runner.js";
|
||||
import { getDmHistoryLimitFromSessionKey } from "./pi-embedded-runner/history.js";
|
||||
|
||||
describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
it("falls back to provider default when per-DM not set", () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { getDmHistoryLimitFromSessionKey } from "./pi-embedded-runner.js";
|
||||
import { getDmHistoryLimitFromSessionKey } from "./pi-embedded-runner/history.js";
|
||||
|
||||
describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
it("returns undefined when sessionKey is undefined", () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { getDmHistoryLimitFromSessionKey } from "./pi-embedded-runner.js";
|
||||
import { getDmHistoryLimitFromSessionKey } from "./pi-embedded-runner/history.js";
|
||||
|
||||
describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
it("keeps backward compatibility for dm/direct session kinds", () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { limitHistoryTurns } from "./pi-embedded-runner.js";
|
||||
import { limitHistoryTurns } from "./pi-embedded-runner/history.js";
|
||||
|
||||
describe("limitHistoryTurns", () => {
|
||||
const mockUsage = {
|
||||
|
||||
@@ -3,19 +3,94 @@ import { countPendingDescendantRunsFromRuns } from "../../../agents/subagent-reg
|
||||
import { getSubagentRunsSnapshotForRead } from "../../../agents/subagent-registry-state.js";
|
||||
import { resolveStorePath } from "../../../config/sessions/paths.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 { findTaskByRunIdForOwner } from "../../../tasks/task-owner-access.js";
|
||||
import { sanitizeTaskStatusText } from "../../../tasks/task-status.js";
|
||||
import type { CommandHandlerResult } from "../commands-types.js";
|
||||
import { formatRunLabel } from "../subagents-utils.js";
|
||||
import {
|
||||
type SubagentsCommandContext,
|
||||
formatTimestampWithAge,
|
||||
loadSubagentSessionEntry,
|
||||
resolveDisplayStatus,
|
||||
resolveSubagentEntryForToken,
|
||||
stopWithText,
|
||||
} from "./shared.js";
|
||||
formatRunLabel,
|
||||
formatRunStatus,
|
||||
resolveSubagentTargetFromRuns,
|
||||
} from "../subagents-utils.js";
|
||||
import { type SubagentsCommandContext } 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 {
|
||||
const { params, requesterKey, runs, restTokens } = ctx;
|
||||
@@ -30,10 +105,7 @@ export function handleSubagentsInfoAction(ctx: SubagentsCommandContext): Command
|
||||
}
|
||||
|
||||
const run = targetResolution.entry;
|
||||
const { entry: sessionEntry } = loadSubagentSessionEntry(params, run.childSessionKey, {
|
||||
loadSessionStore,
|
||||
resolveStorePath,
|
||||
});
|
||||
const { entry: sessionEntry } = loadSubagentSessionEntry(params, run.childSessionKey);
|
||||
const runtime =
|
||||
run.startedAt && Number.isFinite(run.startedAt)
|
||||
? (formatDurationCompact((run.endedAt ?? Date.now()) - run.startedAt) ?? "n/a")
|
||||
|
||||
Reference in New Issue
Block a user