refactor: dedupe subagent session metrics

This commit is contained in:
Peter Steinberger
2026-04-06 20:39:13 +01:00
parent 1b9a1328a1
commit 1919332fd3
3 changed files with 91 additions and 138 deletions

View File

@@ -10,12 +10,20 @@ import {
} from "../config/sessions.js";
import { defaultRuntime } from "../runtime.js";
import { type SubagentRunOutcome } from "./subagent-announce.js";
import {
SUBAGENT_ENDED_REASON_ERROR,
SUBAGENT_ENDED_REASON_KILLED,
} from "./subagent-lifecycle-events.js";
import { SUBAGENT_ENDED_REASON_ERROR } from "./subagent-lifecycle-events.js";
import { runOutcomesEqual } from "./subagent-registry-completion.js";
import type { SubagentRunRecord } from "./subagent-registry.types.js";
import {
getSubagentSessionRuntimeMs,
getSubagentSessionStartedAt,
resolveSubagentSessionStatus,
} from "./subagent-session-metrics.js";
export {
getSubagentSessionRuntimeMs,
getSubagentSessionStartedAt,
resolveSubagentSessionStatus,
} from "./subagent-session-metrics.js";
export const MIN_ANNOUNCE_RETRY_DELAY_MS = 1_000;
export const MAX_ANNOUNCE_RETRY_DELAY_MS = 8_000;
@@ -77,73 +85,6 @@ function findSessionEntryByKey(store: Record<string, SessionEntry>, sessionKey:
return undefined;
}
export function resolveSubagentSessionStatus(
entry: Pick<SubagentRunRecord, "endedAt" | "endedReason" | "outcome"> | null | undefined,
): SessionEntry["status"] {
if (!entry) {
return undefined;
}
if (!entry.endedAt) {
return "running";
}
if (entry.endedReason === SUBAGENT_ENDED_REASON_KILLED) {
return "killed";
}
const status = entry.outcome?.status;
if (status === "error") {
return "failed";
}
if (status === "timeout") {
return "timeout";
}
return "done";
}
function resolveSubagentSessionStartedAt(
entry: Pick<SubagentRunRecord, "sessionStartedAt" | "startedAt" | "createdAt">,
): number | undefined {
if (typeof entry.sessionStartedAt === "number" && Number.isFinite(entry.sessionStartedAt)) {
return entry.sessionStartedAt;
}
if (typeof entry.startedAt === "number" && Number.isFinite(entry.startedAt)) {
return entry.startedAt;
}
return typeof entry.createdAt === "number" && Number.isFinite(entry.createdAt)
? entry.createdAt
: undefined;
}
export function getSubagentSessionStartedAt(
entry: Pick<SubagentRunRecord, "sessionStartedAt" | "startedAt" | "createdAt"> | null | undefined,
): number | undefined {
return entry ? resolveSubagentSessionStartedAt(entry) : undefined;
}
export function getSubagentSessionRuntimeMs(
entry:
| Pick<SubagentRunRecord, "startedAt" | "endedAt" | "accumulatedRuntimeMs">
| null
| undefined,
now = Date.now(),
): number | undefined {
if (!entry) {
return undefined;
}
const accumulatedRuntimeMs =
typeof entry.accumulatedRuntimeMs === "number" && Number.isFinite(entry.accumulatedRuntimeMs)
? Math.max(0, entry.accumulatedRuntimeMs)
: 0;
if (typeof entry.startedAt !== "number" || !Number.isFinite(entry.startedAt)) {
return entry.accumulatedRuntimeMs != null ? accumulatedRuntimeMs : undefined;
}
const currentRunEndedAt =
typeof entry.endedAt === "number" && Number.isFinite(entry.endedAt) ? entry.endedAt : now;
return Math.max(0, accumulatedRuntimeMs + Math.max(0, currentRunEndedAt - entry.startedAt));
}
export async function persistSubagentSessionTiming(entry: SubagentRunRecord) {
const childSessionKey = entry.childSessionKey?.trim();
if (!childSessionKey) {

View File

@@ -1,4 +1,3 @@
import { SUBAGENT_ENDED_REASON_KILLED } from "./subagent-lifecycle-events.js";
import { subagentRuns } from "./subagent-registry-memory.js";
import {
countActiveDescendantRunsFromRuns,
@@ -7,73 +6,17 @@ import {
} from "./subagent-registry-queries.js";
import { getSubagentRunsSnapshotForRead } from "./subagent-registry-state.js";
import type { SubagentRunRecord } from "./subagent-registry.types.js";
import {
getSubagentSessionRuntimeMs,
getSubagentSessionStartedAt,
resolveSubagentSessionStatus,
} from "./subagent-session-metrics.js";
function resolveSubagentSessionStartedAt(
entry: Pick<SubagentRunRecord, "sessionStartedAt" | "startedAt" | "createdAt">,
): number | undefined {
if (typeof entry.sessionStartedAt === "number" && Number.isFinite(entry.sessionStartedAt)) {
return entry.sessionStartedAt;
}
if (typeof entry.startedAt === "number" && Number.isFinite(entry.startedAt)) {
return entry.startedAt;
}
return typeof entry.createdAt === "number" && Number.isFinite(entry.createdAt)
? entry.createdAt
: undefined;
}
export function getSubagentSessionStartedAt(
entry: Pick<SubagentRunRecord, "sessionStartedAt" | "startedAt" | "createdAt"> | null | undefined,
): number | undefined {
return entry ? resolveSubagentSessionStartedAt(entry) : undefined;
}
export function getSubagentSessionRuntimeMs(
entry:
| Pick<SubagentRunRecord, "startedAt" | "endedAt" | "accumulatedRuntimeMs">
| null
| undefined,
now = Date.now(),
): number | undefined {
if (!entry) {
return undefined;
}
const accumulatedRuntimeMs =
typeof entry.accumulatedRuntimeMs === "number" && Number.isFinite(entry.accumulatedRuntimeMs)
? Math.max(0, entry.accumulatedRuntimeMs)
: 0;
if (typeof entry.startedAt !== "number" || !Number.isFinite(entry.startedAt)) {
return entry.accumulatedRuntimeMs != null ? accumulatedRuntimeMs : undefined;
}
const currentRunEndedAt =
typeof entry.endedAt === "number" && Number.isFinite(entry.endedAt) ? entry.endedAt : now;
return Math.max(0, accumulatedRuntimeMs + Math.max(0, currentRunEndedAt - entry.startedAt));
}
export function resolveSubagentSessionStatus(
entry: Pick<SubagentRunRecord, "endedAt" | "endedReason" | "outcome"> | null | undefined,
): "running" | "killed" | "failed" | "timeout" | "done" | undefined {
if (!entry) {
return undefined;
}
if (!entry.endedAt) {
return "running";
}
if (entry.endedReason === SUBAGENT_ENDED_REASON_KILLED) {
return "killed";
}
const status = entry.outcome?.status;
if (status === "error") {
return "failed";
}
if (status === "timeout") {
return "timeout";
}
return "done";
}
export {
getSubagentSessionRuntimeMs,
getSubagentSessionStartedAt,
resolveSubagentSessionStatus,
} from "./subagent-session-metrics.js";
export function listSubagentRunsForController(controllerSessionKey: string): SubagentRunRecord[] {
return listRunsForControllerFromRuns(

View File

@@ -0,0 +1,69 @@
import { SUBAGENT_ENDED_REASON_KILLED } from "./subagent-lifecycle-events.js";
import type { SubagentRunRecord } from "./subagent-registry.types.js";
function resolveSubagentSessionStartedAtInternal(
entry: Pick<SubagentRunRecord, "sessionStartedAt" | "startedAt" | "createdAt">,
): number | undefined {
if (typeof entry.sessionStartedAt === "number" && Number.isFinite(entry.sessionStartedAt)) {
return entry.sessionStartedAt;
}
if (typeof entry.startedAt === "number" && Number.isFinite(entry.startedAt)) {
return entry.startedAt;
}
return typeof entry.createdAt === "number" && Number.isFinite(entry.createdAt)
? entry.createdAt
: undefined;
}
export function getSubagentSessionStartedAt(
entry: Pick<SubagentRunRecord, "sessionStartedAt" | "startedAt" | "createdAt"> | null | undefined,
): number | undefined {
return entry ? resolveSubagentSessionStartedAtInternal(entry) : undefined;
}
export function getSubagentSessionRuntimeMs(
entry:
| Pick<SubagentRunRecord, "startedAt" | "endedAt" | "accumulatedRuntimeMs">
| null
| undefined,
now = Date.now(),
): number | undefined {
if (!entry) {
return undefined;
}
const accumulatedRuntimeMs =
typeof entry.accumulatedRuntimeMs === "number" && Number.isFinite(entry.accumulatedRuntimeMs)
? Math.max(0, entry.accumulatedRuntimeMs)
: 0;
if (typeof entry.startedAt !== "number" || !Number.isFinite(entry.startedAt)) {
return entry.accumulatedRuntimeMs != null ? accumulatedRuntimeMs : undefined;
}
const currentRunEndedAt =
typeof entry.endedAt === "number" && Number.isFinite(entry.endedAt) ? entry.endedAt : now;
return Math.max(0, accumulatedRuntimeMs + Math.max(0, currentRunEndedAt - entry.startedAt));
}
export function resolveSubagentSessionStatus(
entry: Pick<SubagentRunRecord, "endedAt" | "endedReason" | "outcome"> | null | undefined,
): "running" | "killed" | "failed" | "timeout" | "done" | undefined {
if (!entry) {
return undefined;
}
if (!entry.endedAt) {
return "running";
}
if (entry.endedReason === SUBAGENT_ENDED_REASON_KILLED) {
return "killed";
}
const status = entry.outcome?.status;
if (status === "error") {
return "failed";
}
if (status === "timeout") {
return "timeout";
}
return "done";
}