From a35d319580402df46dac04c416f1246f64386826 Mon Sep 17 00:00:00 2001 From: Nick DiMoro Date: Mon, 26 Jan 2026 01:05:44 -0700 Subject: [PATCH] fix: Add 'running' field to sessions_list for accurate subagent status sessions_list was reporting totalTokens: 0 for actively running subagent sessions because token counts are only persisted at run completion. This caused orchestration logic to incorrectly determine agent availability. Changes: - Add getRunningSubagentSessions() to subagent-registry.ts - Add isSubagentSessionRunning() helper for individual checks - Modify listSessionsFromStore() to include 'running' field - Update GatewaySessionRow and SessionListRow types The 'running' field is true when a session has an active subagent run (startedAt is set but endedAt is not). Fixes: #2129 --- src/agents/subagent-registry.ts | 29 ++++++++++++++++++++++++++ src/agents/tools/sessions-helpers.ts | 2 ++ src/agents/tools/sessions-list-tool.ts | 1 + src/gateway/session-utils.ts | 6 ++++++ src/gateway/session-utils.types.ts | 2 ++ 5 files changed, 40 insertions(+) diff --git a/src/agents/subagent-registry.ts b/src/agents/subagent-registry.ts index d325e40e2..a65086b4c 100644 --- a/src/agents/subagent-registry.ts +++ b/src/agents/subagent-registry.ts @@ -369,3 +369,32 @@ export function listSubagentRunsForRequester(requesterSessionKey: string): Subag export function initSubagentRegistry() { restoreSubagentRunsOnce(); } + +/** + * Check if a session key has an active (in-progress) subagent run. + * Returns true if a run exists with startedAt but no endedAt. + */ +export function isSubagentSessionRunning(childSessionKey: string): boolean { + const key = childSessionKey.trim(); + if (!key) return false; + for (const entry of subagentRuns.values()) { + if (entry.childSessionKey === key && entry.startedAt && !entry.endedAt) { + return true; + } + } + return false; +} + +/** + * Get all currently running subagent session keys. + * Returns a Set of session keys that have active runs. + */ +export function getRunningSubagentSessions(): Set { + const running = new Set(); + for (const entry of subagentRuns.values()) { + if (entry.startedAt && !entry.endedAt) { + running.add(entry.childSessionKey); + } + } + return running; +} diff --git a/src/agents/tools/sessions-helpers.ts b/src/agents/tools/sessions-helpers.ts index 6ddd2281e..9c34c3254 100644 --- a/src/agents/tools/sessions-helpers.ts +++ b/src/agents/tools/sessions-helpers.ts @@ -28,6 +28,8 @@ export type SessionListRow = { model?: string; contextTokens?: number | null; totalTokens?: number | null; + /** True if this session has an active subagent run in progress */ + running?: boolean; thinkingLevel?: string; verboseLevel?: string; systemSent?: boolean; diff --git a/src/agents/tools/sessions-list-tool.ts b/src/agents/tools/sessions-list-tool.ts index 148a33d30..56e3508ba 100644 --- a/src/agents/tools/sessions-list-tool.ts +++ b/src/agents/tools/sessions-list-tool.ts @@ -169,6 +169,7 @@ export function createSessionsListTool(opts?: { model: typeof entry.model === "string" ? entry.model : undefined, contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : undefined, totalTokens: typeof entry.totalTokens === "number" ? entry.totalTokens : undefined, + running: typeof entry.running === "boolean" ? entry.running : undefined, thinkingLevel: typeof entry.thinkingLevel === "string" ? entry.thinkingLevel : undefined, verboseLevel: typeof entry.verboseLevel === "string" ? entry.verboseLevel : undefined, systemSent: typeof entry.systemSent === "boolean" ? entry.systemSent : undefined, diff --git a/src/gateway/session-utils.ts b/src/gateway/session-utils.ts index c4046a08e..e0a9078ce 100644 --- a/src/gateway/session-utils.ts +++ b/src/gateway/session-utils.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import path from "node:path"; import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js"; +import { getRunningSubagentSessions } from "../agents/subagent-registry.js"; import { lookupContextTokens } from "../agents/context.js"; import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js"; import { resolveConfiguredModelRef } from "../agents/model-selection.js"; @@ -468,6 +469,9 @@ export function listSessionsFromStore(params: { const { cfg, storePath, store, opts } = params; const now = Date.now(); + // Get set of currently running subagent sessions for accurate status reporting + const runningSubagents = getRunningSubagentSessions(); + const includeGlobal = opts.includeGlobal === true; const includeUnknown = opts.includeUnknown === true; const includeDerivedTitles = opts.includeDerivedTitles === true; @@ -554,6 +558,8 @@ export function listSessionsFromStore(params: { inputTokens: entry?.inputTokens, outputTokens: entry?.outputTokens, totalTokens: total, + // Check if this is a running subagent (has active run but may not have tokens yet) + running: runningSubagents.has(key), responseUsage: entry?.responseUsage, modelProvider: entry?.modelProvider, model: entry?.model, diff --git a/src/gateway/session-utils.types.ts b/src/gateway/session-utils.types.ts index 074e9eaa8..5862fa4fe 100644 --- a/src/gateway/session-utils.types.ts +++ b/src/gateway/session-utils.types.ts @@ -33,6 +33,8 @@ export type GatewaySessionRow = { inputTokens?: number; outputTokens?: number; totalTokens?: number; + /** True if this session has an active subagent run in progress */ + running?: boolean; responseUsage?: "on" | "off" | "tokens" | "full"; modelProvider?: string; model?: string;