mirror of https://github.com/openclaw/openclaw.git
237 lines
6.8 KiB
TypeScript
237 lines
6.8 KiB
TypeScript
import { loadConfig } from "../config/config.js";
|
|
import { info } from "../globals.js";
|
|
import type { RuntimeEnv } from "../runtime.js";
|
|
import { cancelTaskById, getTaskById, updateTaskNotifyPolicyById } from "../tasks/task-registry.js";
|
|
import {
|
|
reconcileInspectableTasks,
|
|
reconcileTaskLookupToken,
|
|
} from "../tasks/task-registry.reconcile.js";
|
|
import type { TaskNotifyPolicy, TaskRecord } from "../tasks/task-registry.types.js";
|
|
import { isRich, theme } from "../terminal/theme.js";
|
|
|
|
const RUNTIME_PAD = 8;
|
|
const STATUS_PAD = 10;
|
|
const DELIVERY_PAD = 14;
|
|
const ID_PAD = 10;
|
|
const RUN_PAD = 10;
|
|
|
|
function truncate(value: string, maxChars: number) {
|
|
if (value.length <= maxChars) {
|
|
return value;
|
|
}
|
|
if (maxChars <= 1) {
|
|
return value.slice(0, maxChars);
|
|
}
|
|
return `${value.slice(0, maxChars - 1)}…`;
|
|
}
|
|
|
|
function shortToken(value: string | undefined, maxChars = ID_PAD): string {
|
|
const trimmed = value?.trim();
|
|
if (!trimmed) {
|
|
return "n/a";
|
|
}
|
|
return truncate(trimmed, maxChars);
|
|
}
|
|
|
|
function formatTaskStatusCell(status: string, rich: boolean) {
|
|
const padded = status.padEnd(STATUS_PAD);
|
|
if (!rich) {
|
|
return padded;
|
|
}
|
|
if (status === "succeeded") {
|
|
return theme.success(padded);
|
|
}
|
|
if (status === "failed" || status === "lost" || status === "timed_out") {
|
|
return theme.error(padded);
|
|
}
|
|
if (status === "running") {
|
|
return theme.accentBright(padded);
|
|
}
|
|
return theme.muted(padded);
|
|
}
|
|
|
|
function formatTaskRows(tasks: TaskRecord[], rich: boolean) {
|
|
const header = [
|
|
"Task".padEnd(ID_PAD),
|
|
"Kind".padEnd(RUNTIME_PAD),
|
|
"Status".padEnd(STATUS_PAD),
|
|
"Delivery".padEnd(DELIVERY_PAD),
|
|
"Run".padEnd(RUN_PAD),
|
|
"Child Session",
|
|
"Summary",
|
|
].join(" ");
|
|
const lines = [rich ? theme.heading(header) : header];
|
|
for (const task of tasks) {
|
|
const summary = truncate(
|
|
task.terminalSummary?.trim() ||
|
|
task.progressSummary?.trim() ||
|
|
task.label?.trim() ||
|
|
task.task.trim(),
|
|
80,
|
|
);
|
|
const line = [
|
|
shortToken(task.taskId).padEnd(ID_PAD),
|
|
task.runtime.padEnd(RUNTIME_PAD),
|
|
formatTaskStatusCell(task.status, rich),
|
|
task.deliveryStatus.padEnd(DELIVERY_PAD),
|
|
shortToken(task.runId, RUN_PAD).padEnd(RUN_PAD),
|
|
truncate(task.childSessionKey?.trim() || "n/a", 36).padEnd(36),
|
|
summary,
|
|
].join(" ");
|
|
lines.push(line.trimEnd());
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
export async function tasksListCommand(
|
|
opts: { json?: boolean; runtime?: string; status?: string },
|
|
runtime: RuntimeEnv,
|
|
) {
|
|
const runtimeFilter = opts.runtime?.trim();
|
|
const statusFilter = opts.status?.trim();
|
|
const tasks = reconcileInspectableTasks().filter((task) => {
|
|
if (runtimeFilter && task.runtime !== runtimeFilter) {
|
|
return false;
|
|
}
|
|
if (statusFilter && task.status !== statusFilter) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
if (opts.json) {
|
|
runtime.log(
|
|
JSON.stringify(
|
|
{
|
|
count: tasks.length,
|
|
runtime: runtimeFilter ?? null,
|
|
status: statusFilter ?? null,
|
|
tasks,
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
|
|
runtime.log(info(`Background tasks: ${tasks.length}`));
|
|
if (runtimeFilter) {
|
|
runtime.log(info(`Runtime filter: ${runtimeFilter}`));
|
|
}
|
|
if (statusFilter) {
|
|
runtime.log(info(`Status filter: ${statusFilter}`));
|
|
}
|
|
if (tasks.length === 0) {
|
|
runtime.log("No background tasks found.");
|
|
return;
|
|
}
|
|
const rich = isRich();
|
|
for (const line of formatTaskRows(tasks, rich)) {
|
|
runtime.log(line);
|
|
}
|
|
}
|
|
|
|
export async function tasksShowCommand(
|
|
opts: { json?: boolean; lookup: string },
|
|
runtime: RuntimeEnv,
|
|
) {
|
|
const task = reconcileTaskLookupToken(opts.lookup);
|
|
if (!task) {
|
|
runtime.error(`Task not found: ${opts.lookup}`);
|
|
runtime.exit(1);
|
|
return;
|
|
}
|
|
|
|
if (opts.json) {
|
|
runtime.log(JSON.stringify(task, null, 2));
|
|
return;
|
|
}
|
|
|
|
const lines = [
|
|
"Background task:",
|
|
`taskId: ${task.taskId}`,
|
|
`kind: ${task.runtime}`,
|
|
`sourceId: ${task.sourceId ?? "n/a"}`,
|
|
`status: ${task.status}`,
|
|
`result: ${task.terminalOutcome ?? "n/a"}`,
|
|
`delivery: ${task.deliveryStatus}`,
|
|
`notify: ${task.notifyPolicy}`,
|
|
`requesterSessionKey: ${task.requesterSessionKey}`,
|
|
`childSessionKey: ${task.childSessionKey ?? "n/a"}`,
|
|
`parentTaskId: ${task.parentTaskId ?? "n/a"}`,
|
|
`agentId: ${task.agentId ?? "n/a"}`,
|
|
`runId: ${task.runId ?? "n/a"}`,
|
|
`label: ${task.label ?? "n/a"}`,
|
|
`task: ${task.task}`,
|
|
`createdAt: ${new Date(task.createdAt).toISOString()}`,
|
|
`startedAt: ${task.startedAt ? new Date(task.startedAt).toISOString() : "n/a"}`,
|
|
`endedAt: ${task.endedAt ? new Date(task.endedAt).toISOString() : "n/a"}`,
|
|
`lastEventAt: ${task.lastEventAt ? new Date(task.lastEventAt).toISOString() : "n/a"}`,
|
|
`cleanupAfter: ${task.cleanupAfter ? new Date(task.cleanupAfter).toISOString() : "n/a"}`,
|
|
...(task.error ? [`error: ${task.error}`] : []),
|
|
...(task.progressSummary ? [`progressSummary: ${task.progressSummary}`] : []),
|
|
...(task.terminalSummary ? [`terminalSummary: ${task.terminalSummary}`] : []),
|
|
...(task.recentEvents?.length
|
|
? task.recentEvents.map(
|
|
(event, index) =>
|
|
`recentEvent[${index}]: ${new Date(event.at).toISOString()} ${event.kind}${
|
|
event.summary ? ` ${event.summary}` : ""
|
|
}`,
|
|
)
|
|
: []),
|
|
];
|
|
for (const line of lines) {
|
|
runtime.log(line);
|
|
}
|
|
}
|
|
|
|
export async function tasksNotifyCommand(
|
|
opts: { lookup: string; notify: TaskNotifyPolicy },
|
|
runtime: RuntimeEnv,
|
|
) {
|
|
const task = reconcileTaskLookupToken(opts.lookup);
|
|
if (!task) {
|
|
runtime.error(`Task not found: ${opts.lookup}`);
|
|
runtime.exit(1);
|
|
return;
|
|
}
|
|
const updated = updateTaskNotifyPolicyById({
|
|
taskId: task.taskId,
|
|
notifyPolicy: opts.notify,
|
|
});
|
|
if (!updated) {
|
|
runtime.error(`Task not found: ${opts.lookup}`);
|
|
runtime.exit(1);
|
|
return;
|
|
}
|
|
runtime.log(`Updated ${updated.taskId} notify policy to ${updated.notifyPolicy}.`);
|
|
}
|
|
|
|
export async function tasksCancelCommand(opts: { lookup: string }, runtime: RuntimeEnv) {
|
|
const task = reconcileTaskLookupToken(opts.lookup);
|
|
if (!task) {
|
|
runtime.error(`Task not found: ${opts.lookup}`);
|
|
runtime.exit(1);
|
|
return;
|
|
}
|
|
const result = await cancelTaskById({
|
|
cfg: loadConfig(),
|
|
taskId: task.taskId,
|
|
});
|
|
if (!result.found) {
|
|
runtime.error(result.reason ?? `Task not found: ${opts.lookup}`);
|
|
runtime.exit(1);
|
|
return;
|
|
}
|
|
if (!result.cancelled) {
|
|
runtime.error(result.reason ?? `Could not cancel task: ${opts.lookup}`);
|
|
runtime.exit(1);
|
|
return;
|
|
}
|
|
const updated = getTaskById(task.taskId);
|
|
runtime.log(
|
|
`Cancelled ${updated?.taskId ?? task.taskId} (${updated?.runtime ?? task.runtime})${updated?.runId ? ` run ${updated.runId}` : ""}.`,
|
|
);
|
|
}
|