mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 15:50:20 +00:00
feat(status): surface task run pressure (#57350)
* feat(status): surface task run pressure * Update src/commands/tasks.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
@@ -10,7 +10,8 @@ import {
|
||||
resolveTaskForLookupToken,
|
||||
updateTaskRecordById,
|
||||
} from "./task-registry.js";
|
||||
import type { TaskRecord } from "./task-registry.types.js";
|
||||
import { summarizeTaskRecords } from "./task-registry.summary.js";
|
||||
import type { TaskRecord, TaskRegistrySummary } from "./task-registry.types.js";
|
||||
|
||||
const TASK_RECONCILE_GRACE_MS = 5 * 60_000;
|
||||
const TASK_RETENTION_MS = 7 * 24 * 60 * 60_000;
|
||||
@@ -124,6 +125,10 @@ export function reconcileInspectableTasks(): TaskRecord[] {
|
||||
return listTaskRecords().map((task) => reconcileTaskRecordForOperatorInspection(task));
|
||||
}
|
||||
|
||||
export function getInspectableTaskRegistrySummary(): TaskRegistrySummary {
|
||||
return summarizeTaskRecords(reconcileInspectableTasks());
|
||||
}
|
||||
|
||||
export function reconcileTaskLookupToken(token: string): TaskRecord | undefined {
|
||||
ensureTaskRegistryReady();
|
||||
const task = resolveTaskForLookupToken(token);
|
||||
|
||||
56
src/tasks/task-registry.summary.ts
Normal file
56
src/tasks/task-registry.summary.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type {
|
||||
TaskRecord,
|
||||
TaskRegistrySummary,
|
||||
TaskRuntimeCounts,
|
||||
TaskStatusCounts,
|
||||
} from "./task-registry.types.js";
|
||||
|
||||
function createEmptyTaskStatusCounts(): TaskStatusCounts {
|
||||
return {
|
||||
queued: 0,
|
||||
running: 0,
|
||||
succeeded: 0,
|
||||
failed: 0,
|
||||
timed_out: 0,
|
||||
cancelled: 0,
|
||||
lost: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function createEmptyTaskRuntimeCounts(): TaskRuntimeCounts {
|
||||
return {
|
||||
subagent: 0,
|
||||
acp: 0,
|
||||
cli: 0,
|
||||
cron: 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function createEmptyTaskRegistrySummary(): TaskRegistrySummary {
|
||||
return {
|
||||
total: 0,
|
||||
active: 0,
|
||||
terminal: 0,
|
||||
failures: 0,
|
||||
byStatus: createEmptyTaskStatusCounts(),
|
||||
byRuntime: createEmptyTaskRuntimeCounts(),
|
||||
};
|
||||
}
|
||||
|
||||
export function summarizeTaskRecords(records: Iterable<TaskRecord>): TaskRegistrySummary {
|
||||
const summary = createEmptyTaskRegistrySummary();
|
||||
for (const task of records) {
|
||||
summary.total += 1;
|
||||
summary.byStatus[task.status] += 1;
|
||||
summary.byRuntime[task.runtime] += 1;
|
||||
if (task.status === "queued" || task.status === "running") {
|
||||
summary.active += 1;
|
||||
} else {
|
||||
summary.terminal += 1;
|
||||
}
|
||||
if (task.status === "failed" || task.status === "timed_out" || task.status === "lost") {
|
||||
summary.failures += 1;
|
||||
}
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
createTaskRecord,
|
||||
findTaskByRunId,
|
||||
getTaskById,
|
||||
getTaskRegistrySummary,
|
||||
listTaskRecords,
|
||||
maybeDeliverTaskStateChangeUpdate,
|
||||
maybeDeliverTaskTerminalUpdate,
|
||||
@@ -141,6 +142,60 @@ describe("task-registry", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("summarizes task pressure by status and runtime", async () => {
|
||||
await withTempDir({ prefix: "openclaw-task-registry-" }, async (root) => {
|
||||
process.env.OPENCLAW_STATE_DIR = root;
|
||||
resetTaskRegistryForTests();
|
||||
|
||||
createTaskRecord({
|
||||
runtime: "acp",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
runId: "run-summary-acp",
|
||||
task: "Investigate issue",
|
||||
status: "queued",
|
||||
deliveryStatus: "pending",
|
||||
});
|
||||
createTaskRecord({
|
||||
runtime: "cron",
|
||||
requesterSessionKey: "",
|
||||
runId: "run-summary-cron",
|
||||
task: "Daily digest",
|
||||
status: "running",
|
||||
deliveryStatus: "not_applicable",
|
||||
});
|
||||
createTaskRecord({
|
||||
runtime: "subagent",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
runId: "run-summary-subagent",
|
||||
task: "Write patch",
|
||||
status: "timed_out",
|
||||
deliveryStatus: "session_queued",
|
||||
});
|
||||
|
||||
expect(getTaskRegistrySummary()).toEqual({
|
||||
total: 3,
|
||||
active: 2,
|
||||
terminal: 1,
|
||||
failures: 1,
|
||||
byStatus: {
|
||||
queued: 1,
|
||||
running: 1,
|
||||
succeeded: 0,
|
||||
failed: 0,
|
||||
timed_out: 1,
|
||||
cancelled: 0,
|
||||
lost: 0,
|
||||
},
|
||||
byRuntime: {
|
||||
subagent: 1,
|
||||
acp: 1,
|
||||
cli: 0,
|
||||
cron: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("delivers ACP completion to the requester channel when a delivery origin exists", async () => {
|
||||
await withTempDir({ prefix: "openclaw-task-registry-" }, async (root) => {
|
||||
process.env.OPENCLAW_STATE_DIR = root;
|
||||
|
||||
@@ -15,12 +15,14 @@ import {
|
||||
resetTaskRegistryRuntimeForTests,
|
||||
type TaskRegistryHookEvent,
|
||||
} from "./task-registry.store.js";
|
||||
import { summarizeTaskRecords } from "./task-registry.summary.js";
|
||||
import type {
|
||||
TaskDeliveryStatus,
|
||||
TaskEventKind,
|
||||
TaskEventRecord,
|
||||
TaskNotifyPolicy,
|
||||
TaskRecord,
|
||||
TaskRegistrySummary,
|
||||
TaskRegistrySnapshot,
|
||||
TaskRuntime,
|
||||
TaskStatus,
|
||||
@@ -1049,6 +1051,11 @@ export function listTaskRecords(): TaskRecord[] {
|
||||
.toSorted((a, b) => b.createdAt - a.createdAt);
|
||||
}
|
||||
|
||||
export function getTaskRegistrySummary(): TaskRegistrySummary {
|
||||
ensureTaskRegistryReady();
|
||||
return summarizeTaskRecords(tasks.values());
|
||||
}
|
||||
|
||||
export function getTaskRegistrySnapshot(): TaskRegistrySnapshot {
|
||||
return {
|
||||
tasks: listTaskRecords(),
|
||||
|
||||
@@ -23,6 +23,18 @@ export type TaskNotifyPolicy = "done_only" | "state_changes" | "silent";
|
||||
|
||||
export type TaskTerminalOutcome = "succeeded" | "blocked";
|
||||
|
||||
export type TaskStatusCounts = Record<TaskStatus, number>;
|
||||
export type TaskRuntimeCounts = Record<TaskRuntime, number>;
|
||||
|
||||
export type TaskRegistrySummary = {
|
||||
total: number;
|
||||
active: number;
|
||||
terminal: number;
|
||||
failures: number;
|
||||
byStatus: TaskStatusCounts;
|
||||
byRuntime: TaskRuntimeCounts;
|
||||
};
|
||||
|
||||
export type TaskEventKind = TaskStatus | "progress";
|
||||
|
||||
export type TaskEventRecord = {
|
||||
|
||||
Reference in New Issue
Block a user