openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
195 lines (194 loc) • 7.59 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { P as timestampMsToIsoString } from "./number-coercion-CJQ8TR--.js";
import { t as formatCliCommand } from "./command-format-CKGmlpAQ.js";
import { n as isRich, r as theme } from "./theme-vjDs9tao.js";
import { r as writeRuntimeJson } from "./runtime-B4lgFmsS.js";
import { n as info } from "./globals-GTrXU4s9.js";
import { t as sanitizeTerminalText } from "./safe-text-BkQYdJi1.js";
import { i as getRuntimeConfig } from "./io-Gi7-pyU-.js";
import "./config-C9RxTsn1.js";
import { I as listTaskFlowRecords, P as getTaskFlowById, m as listTasksForFlowId, z as resolveTaskFlowForLookupToken } from "./task-registry-FH-Ti23C.js";
import "./runtime-internal-DmbNZJaP.js";
import { l as getFlowTaskSummary, n as cancelFlowById } from "./task-executor-rbzfy5mW.js";
//#region src/commands/flows.ts
/** CLI commands for listing, inspecting, and cancelling TaskFlow records. */
const ID_PAD = 10;
const STATUS_PAD = 10;
const MODE_PAD = 14;
const REV_PAD = 6;
const CTRL_PAD = 20;
function formatFlowLookupMiss(lookup) {
return `TaskFlow not found: ${lookup}. Run ${formatCliCommand("openclaw tasks flow list")} to see recent flow ids.`;
}
function truncate(value, maxChars) {
if (value.length <= maxChars) return value;
if (maxChars <= 1) return value.slice(0, maxChars);
return `${value.slice(0, maxChars - 1)}…`;
}
function safeFlowDisplayText(value, maxChars) {
const sanitized = sanitizeTerminalText(value ?? "").trim();
if (!sanitized) return "n/a";
return typeof maxChars === "number" ? truncate(sanitized, maxChars) : sanitized;
}
function shortToken(value, maxChars = ID_PAD) {
const trimmed = normalizeOptionalString(value);
if (!trimmed) return "n/a";
return truncate(trimmed, maxChars);
}
function formatFlowTimestamp(value) {
return timestampMsToIsoString(value) ?? "n/a";
}
function formatFlowStatusCell(status, rich) {
const padded = status.padEnd(STATUS_PAD);
if (!rich) return padded;
if (status === "succeeded") return theme.success(padded);
if (status === "failed" || status === "lost") return theme.error(padded);
if (status === "running") return theme.accentBright(padded);
if (status === "blocked") return theme.warn(padded);
return theme.muted(padded);
}
function formatFlowRows(flows, rich) {
const header = [
"TaskFlow".padEnd(ID_PAD),
"Mode".padEnd(MODE_PAD),
"Status".padEnd(STATUS_PAD),
"Rev".padEnd(REV_PAD),
"Controller".padEnd(CTRL_PAD),
"Tasks".padEnd(14),
"Goal"
].join(" ");
const lines = [rich ? theme.heading(header) : header];
for (const flow of flows) {
const taskSummary = getFlowTaskSummary(flow.flowId);
const counts = `${taskSummary.active} active/${taskSummary.total} total`;
lines.push([
shortToken(flow.flowId).padEnd(ID_PAD),
flow.syncMode.padEnd(MODE_PAD),
formatFlowStatusCell(flow.status, rich),
String(flow.revision).padEnd(REV_PAD),
safeFlowDisplayText(flow.controllerId, CTRL_PAD).padEnd(CTRL_PAD),
counts.padEnd(14),
safeFlowDisplayText(flow.goal, 80)
].join(" "));
}
return lines;
}
function formatFlowListSummary(flows) {
return `${flows.filter((flow) => flow.status === "queued" || flow.status === "running").length} active · ${flows.filter((flow) => flow.status === "blocked").length} blocked · ${flows.filter((flow) => flow.cancelRequestedAt != null).length} cancel-requested · ${flows.length} total`;
}
function summarizeWait(flow) {
if (flow.waitJson == null) return "n/a";
if (typeof flow.waitJson === "string" || typeof flow.waitJson === "number" || typeof flow.waitJson === "boolean") return String(flow.waitJson);
if (Array.isArray(flow.waitJson)) return `array(${flow.waitJson.length})`;
return Object.keys(flow.waitJson).toSorted().join(", ") || "object";
}
function summarizeFlowState(flow) {
if (flow.status === "blocked") {
if (flow.blockedSummary) return flow.blockedSummary;
if (flow.blockedTaskId) return `blocked by ${flow.blockedTaskId}`;
return "blocked";
}
if (flow.status === "waiting" && flow.waitJson != null) return summarizeWait(flow);
return null;
}
/** Lists TaskFlows with optional status filtering and JSON output. */
async function flowsListCommand(opts, runtime) {
const statusFilter = opts.status?.trim();
const flows = listTaskFlowRecords().filter((flow) => {
if (statusFilter && flow.status !== statusFilter) return false;
return true;
});
if (opts.json) {
writeRuntimeJson(runtime, {
count: flows.length,
status: statusFilter ?? null,
flows: flows.map((flow) => ({
...flow,
tasks: listTasksForFlowId(flow.flowId),
taskSummary: getFlowTaskSummary(flow.flowId)
}))
});
return;
}
runtime.log(info(`TaskFlows: ${flows.length}`));
runtime.log(info(`TaskFlow pressure: ${formatFlowListSummary(flows)}`));
if (statusFilter) runtime.log(info(`Status filter: ${statusFilter}`));
if (flows.length === 0) {
runtime.log(`No TaskFlows found. Run ${formatCliCommand("openclaw tasks list")} to inspect standalone background tasks.`);
return;
}
const rich = isRich();
for (const line of formatFlowRows(flows, rich)) runtime.log(line);
}
/** Shows one TaskFlow and its linked task summary. */
async function flowsShowCommand(opts, runtime) {
const flow = resolveTaskFlowForLookupToken(opts.lookup);
if (!flow) {
runtime.error(formatFlowLookupMiss(opts.lookup));
runtime.exit(1);
return;
}
const tasks = listTasksForFlowId(flow.flowId);
const taskSummary = getFlowTaskSummary(flow.flowId);
const stateSummary = summarizeFlowState(flow);
if (opts.json) {
writeRuntimeJson(runtime, {
...flow,
tasks,
taskSummary
});
return;
}
const lines = [
"TaskFlow:",
`flowId: ${flow.flowId}`,
`status: ${flow.status}`,
`goal: ${safeFlowDisplayText(flow.goal)}`,
`currentStep: ${safeFlowDisplayText(flow.currentStep)}`,
`owner: ${safeFlowDisplayText(flow.ownerKey)}`,
`notify: ${flow.notifyPolicy}`,
...stateSummary ? [`state: ${safeFlowDisplayText(stateSummary)}`] : [],
...flow.cancelRequestedAt ? [`cancelRequestedAt: ${formatFlowTimestamp(flow.cancelRequestedAt)}`] : [],
`createdAt: ${formatFlowTimestamp(flow.createdAt)}`,
`updatedAt: ${formatFlowTimestamp(flow.updatedAt)}`,
`endedAt: ${formatFlowTimestamp(flow.endedAt)}`,
`tasks: ${taskSummary.total} total · ${taskSummary.active} active · ${taskSummary.failures} issues`
];
for (const line of lines) runtime.log(line);
if (tasks.length === 0) {
runtime.log("Linked tasks: none");
return;
}
runtime.log("Linked tasks:");
for (const task of tasks) {
const safeLabel = safeFlowDisplayText(task.label ?? task.task);
runtime.log(`- ${task.taskId} ${task.status} ${task.runId ?? "n/a"} ${safeLabel}`);
}
}
/** Requests cancellation for one TaskFlow selected by id or lookup token. */
async function flowsCancelCommand(opts, runtime) {
const flow = resolveTaskFlowForLookupToken(opts.lookup);
if (!flow) {
runtime.error(formatFlowLookupMiss(opts.lookup));
runtime.exit(1);
return;
}
const result = await cancelFlowById({
cfg: getRuntimeConfig(),
flowId: flow.flowId
});
if (!result.found) {
runtime.error(result.reason ?? formatFlowLookupMiss(opts.lookup));
runtime.exit(1);
return;
}
if (!result.cancelled) {
runtime.error(result.reason ?? `Could not cancel TaskFlow: ${opts.lookup}`);
runtime.exit(1);
return;
}
const updated = getTaskFlowById(flow.flowId) ?? result.flow ?? flow;
runtime.log(`Cancelled ${updated.flowId} (${updated.syncMode}) with status ${updated.status}.`);
}
//#endregion
export { flowsCancelCommand, flowsListCommand, flowsShowCommand };