feat(status): show session task counts in slash status

This commit is contained in:
Vincent Koc
2026-04-01 05:07:48 +09:00
parent 913e7d5eba
commit 58ee76fc84
3 changed files with 52 additions and 0 deletions

View File

@@ -4,6 +4,8 @@ import {
resetSubagentRegistryForTests,
} from "../../agents/subagent-registry.js";
import type { OpenClawConfig } from "../../config/config.js";
import { createQueuedTaskRun, createRunningTaskRun } from "../../tasks/task-executor.js";
import { resetTaskRegistryForTests } from "../../tasks/task-registry.js";
import { buildStatusReply } from "./commands-status.js";
import { buildCommandTestParams } from "./commands.test-harness.js";
@@ -41,10 +43,12 @@ async function buildStatusReplyForTest(params: { sessionKey?: string; verbose?:
describe("buildStatusReply subagent summary", () => {
beforeEach(() => {
resetSubagentRegistryForTests();
resetTaskRegistryForTests();
});
afterEach(() => {
resetSubagentRegistryForTests();
resetTaskRegistryForTests();
});
it("counts ended orchestrators with active descendants as active", async () => {
@@ -178,4 +182,27 @@ describe("buildStatusReply subagent summary", () => {
expect(reply?.text).toContain("🤖 Subagents: 1 active");
});
it("includes active and total task counts for the current session", async () => {
createRunningTaskRun({
runtime: "subagent",
requesterSessionKey: "agent:main:main",
childSessionKey: "agent:main:subagent:status-task-running",
runId: "run-status-task-running",
task: "active background task",
progressSummary: "still working",
});
createQueuedTaskRun({
runtime: "cron",
requesterSessionKey: "agent:main:main",
childSessionKey: "agent:main:subagent:status-task-queued",
runId: "run-status-task-queued",
task: "queued background task",
});
const reply = await buildStatusReplyForTest({});
expect(reply?.text).toContain("📌 Tasks: 2 active · 2 total");
expect(reply?.text).toMatch(/📌 Tasks: 2 active · 2 total · (subagent|cron) · /);
});
});

View File

@@ -22,6 +22,7 @@ import {
resolveUsageProviderId,
} from "../../infra/provider-usage.js";
import type { MediaUnderstandingDecision } from "../../media-understanding/types.js";
import { listTasksForSessionKey } from "../../tasks/task-registry.js";
import { normalizeGroupActivation } from "../group-activation.js";
import { resolveSelectedAndActiveModel } from "../model-runtime.js";
import { buildStatusMessage } from "../status.js";
@@ -54,6 +55,25 @@ function shouldLoadUsageSummary(params: {
return Boolean(auth?.startsWith("oauth") || auth?.startsWith("token"));
}
function formatSessionTaskLine(sessionKey: string): string | undefined {
const tasks = listTasksForSessionKey(sessionKey);
if (tasks.length === 0) {
return undefined;
}
const latest = tasks[0];
const active = tasks.filter(
(task) => task.status === "queued" || task.status === "running",
).length;
const headline = `${active} active · ${tasks.length} total`;
const title = latest.label?.trim() || latest.task.trim();
const detail =
latest.status === "running" || latest.status === "queued"
? latest.progressSummary?.trim()
: latest.error?.trim() || latest.terminalSummary?.trim();
const parts = [headline, latest.runtime, title, detail].filter(Boolean);
return parts.length ? `📌 Tasks: ${parts.join(" · ")}` : undefined;
}
export async function buildStatusReply(params: {
cfg: OpenClawConfig;
command: CommandContext;
@@ -184,9 +204,11 @@ export async function buildStatusReply(params: {
);
let subagentsLine: string | undefined;
let taskLine: string | undefined;
if (sessionKey) {
const { mainKey, alias } = resolveMainSessionAlias(cfg);
const requesterKey = resolveInternalSessionKey({ key: sessionKey, alias, mainKey });
taskLine = formatSessionTaskLine(requesterKey);
const runs = listControlledSubagentRuns(requesterKey);
const verboseEnabled = resolvedVerboseLevel && resolvedVerboseLevel !== "off";
if (runs.length > 0) {
@@ -261,6 +283,7 @@ export async function buildStatusReply(params: {
showDetails: queueOverrides,
},
subagentsLine,
taskLine,
mediaDecisions: params.mediaDecisions,
includeTranscriptUsage: false,
});

View File

@@ -88,6 +88,7 @@ type StatusArgs = {
queue?: QueueStatus;
mediaDecisions?: ReadonlyArray<MediaUnderstandingDecision>;
subagentsLine?: string;
taskLine?: string;
includeTranscriptUsage?: boolean;
now?: number;
};
@@ -821,6 +822,7 @@ export function buildStatusMessage(args: StatusArgs): string {
args.usageLine,
`🧵 ${sessionLine}`,
args.subagentsLine,
args.taskLine,
`⚙️ ${optionsLine}`,
voiceLine,
activationLine,